diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..d6e1fb4f0aff64b0d35f824120878ee998333960 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +visual/debug_screenshot.png filter=lfs diff=lfs merge=lfs -text +visual/debug_team_stats.png filter=lfs diff=lfs merge=lfs -text +visual/top10_passing_accuracy.png filter=lfs diff=lfs merge=lfs -text diff --git a/.history/README_20251005095904.md b/.history/README_20251005095904.md new file mode 100644 index 0000000000000000000000000000000000000000..58620090b1cdbbe136159904b6825675ea0dee50 Binary files /dev/null and b/.history/README_20251005095904.md differ diff --git a/.history/README_20251005100625.md b/.history/README_20251005100625.md new file mode 100644 index 0000000000000000000000000000000000000000..188bb3e0cd8ad42ba6d4461c9352b065954f4674 Binary files /dev/null and b/.history/README_20251005100625.md differ diff --git a/.history/README_20251005103318.md b/.history/README_20251005103318.md new file mode 100644 index 0000000000000000000000000000000000000000..c90488d3bcb0cf8319ecb5ead94a1f4af358313f Binary files /dev/null and b/.history/README_20251005103318.md differ diff --git a/.history/README_20251005103328.md b/.history/README_20251005103328.md new file mode 100644 index 0000000000000000000000000000000000000000..4e3e595077ba6de1ad264820c24a1f7d983f7c4b Binary files /dev/null and b/.history/README_20251005103328.md differ diff --git a/.history/README_20251005103511.md b/.history/README_20251005103511.md new file mode 100644 index 0000000000000000000000000000000000000000..b10a8996b587a0d2b2febe0621e0bed2165b2512 Binary files /dev/null and b/.history/README_20251005103511.md differ diff --git a/.history/README_20251005103517.md b/.history/README_20251005103517.md new file mode 100644 index 0000000000000000000000000000000000000000..4e3e595077ba6de1ad264820c24a1f7d983f7c4b Binary files /dev/null and b/.history/README_20251005103517.md differ diff --git a/.history/README_20251007193812.md b/.history/README_20251007193812.md new file mode 100644 index 0000000000000000000000000000000000000000..17c363a3c6114353e9306be5d574c57283c3d1ff Binary files /dev/null and b/.history/README_20251007193812.md differ diff --git a/.history/README_20251007193817.md b/.history/README_20251007193817.md new file mode 100644 index 0000000000000000000000000000000000000000..9f3f70ebd84a6a2428d139ee4f82662966971d25 Binary files /dev/null and b/.history/README_20251007193817.md differ diff --git a/.history/README_20251007193828.md b/.history/README_20251007193828.md new file mode 100644 index 0000000000000000000000000000000000000000..2e795c93340c275651e5eeb920d9ac7e7e2f983b Binary files /dev/null and b/.history/README_20251007193828.md differ diff --git a/.history/README_20251007193832.md b/.history/README_20251007193832.md new file mode 100644 index 0000000000000000000000000000000000000000..22f26a8b39825ebd94a8177fd909038409678cbe Binary files /dev/null and b/.history/README_20251007193832.md differ diff --git a/.history/fbrefdata_example_20251004173710.py b/.history/fbrefdata_example_20251004173710.py new file mode 100644 index 0000000000000000000000000000000000000000..f33efe70da8fa5eaf65fd095b107a65e406d16be --- /dev/null +++ b/.history/fbrefdata_example_20251004173710.py @@ -0,0 +1,49 @@ +import requests +import pandas as pd +from io import StringIO + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Downloading team passing stats from {url} ...") + + # Add a User-Agent header to mimic a browser + headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} + response = requests.get(url, headers=headers) + response.raise_for_status() + + df = pd.read_html(StringIO(response.text))[0] + + # Flatten columns + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # Rename the weird columns + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251004180332.py b/.history/fbrefdata_example_20251004180332.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/fbrefdata_example_20251004180335.py b/.history/fbrefdata_example_20251004180335.py new file mode 100644 index 0000000000000000000000000000000000000000..eb1f8b242655e50ad337a405a982c8316ea5fbcb --- /dev/null +++ b/.history/fbrefdata_example_20251004180335.py @@ -0,0 +1,43 @@ +import requests +import pandas as pd +from io import StringIO + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Downloading team passing stats from {url} ...") + + # Use a more comprehensive set of headers to mimic a real browser + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + 'DNT': '1' # Do Not Track request header + } + + response = requests.get(url, headers=headers) + response.raise_for_status() + + # The rest of your function remains the same + df = pd.read_html(StringIO(response.text))[0] + + # Flatten columns + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # Rename the weird columns + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + return df \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004180434.py b/.history/fbrefdata_example_20251004180434.py new file mode 100644 index 0000000000000000000000000000000000000000..d8e05df2cd6d7f1d27296ff5e26e0ba4eb10715d --- /dev/null +++ b/.history/fbrefdata_example_20251004180434.py @@ -0,0 +1,60 @@ +import requests +import pandas as pd +from io import StringIO + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Downloading team passing stats from {url} ...") + + # Gunakan headers yang lebih lengkap untuk meniru browser asli + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + 'DNT': '1' + } + + response = requests.get(url, headers=headers) + response.raise_for_status() + + df = pd.read_html(StringIO(response.text))[0] + + # Meratakan kolom + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # Mengganti nama kolom yang aneh + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + # Menampilkan kolom yang relevan dari DataFrame yang sudah difilter + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +# Bagian ini PENTING untuk menjalankan fungsi main() +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004180520.py b/.history/fbrefdata_example_20251004180520.py new file mode 100644 index 0000000000000000000000000000000000000000..340ee0c0e95ec1efda6f2e13dfaf6950533a8882 --- /dev/null +++ b/.history/fbrefdata_example_20251004180520.py @@ -0,0 +1,74 @@ +import requests +import pandas as pd +from io import StringIO +import random +import time + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Downloading team passing stats from {url} ...") + + # List of User-Agent strings + user_agents = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36' + ] + + # Randomly select a User-Agent + headers = {'User-Agent': random.choice(user_agents)} + + try: + response = requests.get(url, headers=headers) + response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) + except requests.exceptions.HTTPError as e: + print(f"HTTP error occurred: {e}") + return None + except requests.exceptions.RequestException as e: + print(f"An error occurred: {e}") + return None + + df = pd.read_html(StringIO(response.text))[0] + + # Meratakan kolom + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # Mengganti nama kolom yang aneh + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + # Delay before returning (adjust as needed) + time.sleep(random.uniform(1, 3)) # Delay between 1 and 3 seconds + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + # Menampilkan kolom yang relevan dari DataFrame yang sudah difilter + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + else: + print("Failed to retrieve data.") + +# Bagian ini PENTING untuk menjalankan fungsi main() +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004180621.py b/.history/fbrefdata_example_20251004180621.py new file mode 100644 index 0000000000000000000000000000000000000000..0b3c4d2dc6674da85e1dc6c5f28ba0ef229555d9 --- /dev/null +++ b/.history/fbrefdata_example_20251004180621.py @@ -0,0 +1,67 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + # Inisialisasi driver Chrome secara otomatis + # Browser akan terbuka, mengambil data, lalu menutup sendiri + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) + + # Buka URL + driver.get(url) + + # Beri waktu 3 detik agar halaman dan semua elemennya (termasuk tabel) + # termuat dengan sempurna + time.sleep(3) + + # Ambil sumber HTML dari halaman yang sudah dimuat oleh browser + html_source = driver.page_source + + # Tutup browser setelah selesai + driver.quit() + + print("Data downloaded. Processing with pandas...") + + # Sekarang kita proses HTML yang didapat dengan pandas, sama seperti sebelumnya + df = pd.read_html(StringIO(html_source))[0] + + # Meratakan kolom + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # Mengganti nama kolom yang aneh + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004184139.py b/.history/fbrefdata_example_20251004184139.py new file mode 100644 index 0000000000000000000000000000000000000000..985917543e173228132e121912f5eeade7169457 --- /dev/null +++ b/.history/fbrefdata_example_20251004184139.py @@ -0,0 +1,72 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + # === BAGIAN BARU: Menambahkan Opsi Chrome === + options = ChromeOptions() + options.add_argument("--start-maximized") # Memastikan jendela browser terbuka maksimal + options.add_argument("--no-sandbox") # Opsi ini seringkali diperlukan saat menjalankan di lingkungan otomatis + options.add_argument("--disable-dev-shm-usage") # Mengatasi masalah sumber daya yang terbatas + options.add_experimental_option("excludeSwitches", ["enable-automation"]) # Menghilangkan notifikasi "Chrome is being controlled..." + options.add_experimental_option('useAutomationExtension', False) + # ============================================ + + # Inisialisasi driver Chrome dengan OPSI yang sudah kita buat + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + + # Buka URL + driver.get(url) + + # Beri waktu agar halaman termuat dengan sempurna + time.sleep(5) # Waktu tunggu sedikit diperpanjang menjadi 5 detik untuk amannya + + # Ambil sumber HTML dari halaman + html_source = driver.page_source + + # Tutup browser setelah selesai + driver.quit() + + print("Data downloaded. Processing with pandas...") + + df = pd.read_html(StringIO(html_source))[0] + + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004185739.py b/.history/fbrefdata_example_20251004185739.py new file mode 100644 index 0000000000000000000000000000000000000000..8f8064b8ac92015c808aee1b21a7f6e5f4fffa29 --- /dev/null +++ b/.history/fbrefdata_example_20251004185739.py @@ -0,0 +1,65 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + + driver.get(url) + time.sleep(5) + + html_source = driver.page_source + driver.quit() + + print("Data downloaded. Processing with pandas...") + + df = pd.read_html(StringIO(html_source))[0] + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + + # !!!!!!!!!! INI BAGIAN PENTING UNTUK DEBUG !!!!!!!!!! + print("\nDEBUG: Column names are:") + print(df.columns) + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n") + # !!!!!!!!!! AKHIR BAGIAN DEBUG !!!!!!!!!! + + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004185920.py b/.history/fbrefdata_example_20251004185920.py new file mode 100644 index 0000000000000000000000000000000000000000..bb28bcdfa71cf4080d1763b33419a6cde5e9c27c --- /dev/null +++ b/.history/fbrefdata_example_20251004185920.py @@ -0,0 +1,68 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + + driver.get(url) + time.sleep(5) + + html_source = driver.page_source + + print("Data downloaded. Processing with pandas...") + + # Specify the header rows + df = pd.read_html(StringIO(html_source), header=[0, 1])[0] + + # Flatten the multi-level header + df.columns = ["_".join(col).strip() if isinstance(col, tuple) else col for col in df.columns] + driver.quit() + + # !!!!!!!!!! INI BAGIAN PENTING UNTUK DEBUG !!!!!!!!!! + print("\nDEBUG: Column names are:") + print(df.columns) + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n") + # !!!!!!!!!! AKHIR BAGIAN DEBUG !!!!!!!!!! + + df = df.rename(columns={ + "Unnamed: 0_level_0_Squad": "Squad", + "Unnamed: 1_level_0_# Pl": "Players", + "Unnamed: 2_level_0_90s": "90s", + "Unnamed: 17_level_0_Ast": "Ast", + "Unnamed: 18_level_0_xAG": "xAG", + "Unnamed: 21_level_0_KP": "KP", + "Unnamed: 22_level_0_1/3": "1/3", + "Unnamed: 23_level_0_PPA": "PPA", + "Unnamed: 24_level_0_CrsPA": "CrsPA", + "Unnamed: 25_level_0_PrgP": "PrgP" + }) + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered[["Squad", "Total_Cmp", "Total_Att", "Total_Cmp%", "Total_TotDist"]]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004190022.py b/.history/fbrefdata_example_20251004190022.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/fbrefdata_example_20251004190027.py b/.history/fbrefdata_example_20251004190027.py new file mode 100644 index 0000000000000000000000000000000000000000..ddbdc735d52727a64a9df1cd65301191f3e28e7a --- /dev/null +++ b/.history/fbrefdata_example_20251004190027.py @@ -0,0 +1,69 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--headless") # Menjalankan browser di background agar tidak muncul jendela + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + + driver.get(url) + time.sleep(3) # Cukup 3 detik jika headless + + html_source = driver.page_source + driver.quit() + + print("Data downloaded. Processing with pandas...") + + # Ambil tabel pertama dari HTML + df = pd.read_html(StringIO(html_source))[0] + + # ============================================================================== + # === BAGIAN LAMA DIHAPUS DAN DIGANTI DENGAN YANG LEBIH SEDERHANA INI === + # ============================================================================== + # Berdasarkan struktur tabel di FBRef, kita tahu kolom yang kita mau ada di indeks: + # 1: Squad, 5: Total Cmp, 6: Total Att, 7: Total Cmp%, 8: Total TotDist + + # 1. Pilih hanya kolom yang kita butuhkan berdasarkan nomor indeksnya + df = df[[1, 5, 6, 7, 8]] + + # 2. Beri nama baru untuk kolom-kolom tersebut + df.columns = ['Squad', 'Total_Cmp', 'Total_Att', 'Total_Cmp%', 'Total_TotDist'] + + # 3. Hapus baris terakhir yang biasanya berisi total/rata-rata liga + df = df.iloc[:-1] + # ============================================================================== + # ============================================================================== + + return df + +def filter_teams(df, teams): + # Fungsi ini sekarang akan berhasil karena kolom 'Squad' sudah ada + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + # Karena df_filtered sekarang hanya berisi kolom yang kita mau, kita bisa print langsung + print(df_filtered) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004190339.py b/.history/fbrefdata_example_20251004190339.py new file mode 100644 index 0000000000000000000000000000000000000000..3f00dad7b35df760eb70b2166095cc34a20c6123 --- /dev/null +++ b/.history/fbrefdata_example_20251004190339.py @@ -0,0 +1,82 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +# Imports baru untuk menunggu dengan cerdas +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # Headless kita matikan dulu untuk debug, agar terlihat apa yang terjadi + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # ============================================================================== + # === LOGIKA BARU: MENUNGGU CERDAS DAN INTERAKSI HALAMAN === + # ============================================================================== + try: + # Tunggu max 10 detik sampai tombol cookie muncul, lalu klik + wait = WebDriverWait(driver, 10) + # Mencari tombol berdasarkan XPath yang berisi teks 'Accept All Cookies' + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + # Jika tombol tidak muncul dalam 10 detik, anggap saja tidak ada banner + print("No cookie banner found or it took too long.") + + try: + # Sekarang, tunggu max 10 detik sampai tabelnya benar-benar muncul + wait = WebDriverWait(driver, 10) + # Kita tunggu sampai elemen div yang membungkus tabelnya terlihat + wait.until(EC.visibility_of_element_located((By.ID, "div_stats_passing"))) + print("Stats table is now visible.") + except TimeoutException: + print("The stats table could not be found on the page.") + driver.quit() + return None # Keluar dari fungsi jika tabel tidak ditemukan + # ============================================================================== + + html_source = driver.page_source + driver.quit() + + print("Data downloaded. Processing with pandas...") + + df = pd.read_html(StringIO(html_source))[0] + + df = df[[1, 5, 6, 7, 8]] + df.columns = ['Squad', 'Total_Cmp', 'Total_Att', 'Total_Cmp%', 'Total_TotDist'] + df = df.iloc[:-1] + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + # Pastikan df tidak None sebelum melanjutkan + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004190507.py b/.history/fbrefdata_example_20251004190507.py new file mode 100644 index 0000000000000000000000000000000000000000..00bdfedfd6998f8213597e373c0358811fa1795c --- /dev/null +++ b/.history/fbrefdata_example_20251004190507.py @@ -0,0 +1,85 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--headless") # Kita nyalakan lagi headless agar cepat + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + try: + wait = WebDriverWait(driver, 10) + wait.until(EC.visibility_of_element_located((By.ID, "div_stats_passing"))) + print("Stats table is now visible.") + except TimeoutException: + print("The stats table could not be found on the page.") + driver.quit() + return None + + html_source = driver.page_source + driver.quit() + + print("Data downloaded. Processing with pandas...") + + # ============================================================================== + # === BAGIAN INVESTIGASI BARU === + # ============================================================================== + # 1. Baca SEMUA tabel di halaman, jangan hanya ambil yang pertama [0] + all_tables = pd.read_html(StringIO(html_source)) + print(f"\nDEBUG: Found {len(all_tables)} tables on the page.") + + # 2. Cetak ukuran (baris, kolom) dari setiap tabel yang ditemukan + for i, table in enumerate(all_tables): + print(f"DEBUG: Table [{i}] has shape: {table.shape}") + + # 3. Kita akan pilih tabel pertama untuk sementara agar bisa melihat output debug + # Ini akan menyebabkan error lagi, tapi itu tidak apa-apa. + df = all_tables[0] + # ============================================================================== + + df = df[[1, 5, 6, 7, 8]] + df.columns = ['Squad', 'Total_Cmp', 'Total_Att', 'Total_Cmp%', 'Total_TotDist'] + df = df.iloc[:-1] + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004190633.py b/.history/fbrefdata_example_20251004190633.py new file mode 100644 index 0000000000000000000000000000000000000000..b03a7583eda114270c8056141684ca1bef48e464 --- /dev/null +++ b/.history/fbrefdata_example_20251004190633.py @@ -0,0 +1,90 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # Headless kita matikan agar bisa melihat prosesnya + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + try: + wait = WebDriverWait(driver, 10) + wait.until(EC.visibility_of_element_located((By.ID, "div_stats_passing"))) + print("Stats table is now visible.") + html_source = driver.page_source + df = pd.read_html(StringIO(html_source))[1] # Mengambil tabel kedua [1] + + except TimeoutException: + print("The stats table could not be found on the page. Saving debug files...") + # ============================================================================== + # === BAGIAN DEBUG BARU: SIMPAN BUKTI KEGAGALAN === + # ============================================================================== + # Simpan screenshot dari apa yang browser lihat + driver.save_screenshot('debug_screenshot.png') + # Simpan kode HTML yang sedang ditampilkan + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + # ============================================================================== + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # ... (sisa kode proses pandas) ... + # Saya juga melakukan perbaikan kecil berdasarkan investigasi sebelumnya, + # yaitu mencoba mengambil tabel kedua [1] bukan [0] + + all_tables = pd.read_html(StringIO(html_source)) + + # Kita asumsikan tabel utama adalah yang paling banyak kolomnya + # Ini cara yang lebih cerdas untuk menemukan tabel yang benar + main_df = max(all_tables, key=lambda df: len(df.columns)) + print(f"Main table selected with shape: {main_df.shape}") + + df = main_df[[1, 5, 6, 7, 8]] + df.columns = ['Squad', 'Total_Cmp', 'Total_Att', 'Total_Cmp%', 'Total_TotDist'] + df = df.iloc[:-1] + + return df + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.history/fbrefdata_example_20251004190944.py b/.history/fbrefdata_example_20251004190944.py new file mode 100644 index 0000000000000000000000000000000000000000..ccda0f878692f1e3d0ba150d28eff9a8766e9af0 --- /dev/null +++ b/.history/fbrefdata_example_20251004190944.py @@ -0,0 +1,91 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + try: + wait = WebDriverWait(driver, 10) + wait.until(EC.visibility_of_element_located((By.ID, "div_stats_passing"))) + print("Stats table is now visible.") + html_source = driver.page_source + all_tables = pd.read_html(StringIO(html_source)) + except TimeoutException: + print("The stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Ambil tabel utama (yang paling banyak kolomnya) + main_df = max(all_tables, key=lambda df: len(df.columns)) + print(f"Main table selected with shape: {main_df.shape}") + + # Jika kolom multi-level (MultiIndex), kita gabungkan nama header-nya + if isinstance(main_df.columns, pd.MultiIndex): + main_df.columns = ['_'.join(col).strip() for col in main_df.columns.values] + + # Coba tampilkan beberapa kolom agar tahu nama sebenarnya + print("Available columns:", main_df.columns[:10].tolist()) + + # Cari kolom yang relevan untuk passing + cols_to_use = [c for c in main_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = main_df[cols_to_use] + + # Normalisasi nama kolom agar lebih rapi + df.columns = ['Squad', 'Total_Cmp', 'Total_Att', 'Total_Cmp%', 'Total_TotDist'] + df = df[df['Squad'].notna() & (df['Squad'] != 'Squad')] # hapus header duplikat + + return df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251004191947.py b/.history/fbrefdata_example_20251004191947.py new file mode 100644 index 0000000000000000000000000000000000000000..4fcb5c4a691ae5d94c6e4d99751aeb74e54ebb9f --- /dev/null +++ b/.history/fbrefdata_example_20251004191947.py @@ -0,0 +1,107 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + try: + wait = WebDriverWait(driver, 15) + wait.until(EC.visibility_of_element_located((By.ID, "stats_passing_team"))) + print("Team stats table is visible.") + html_source = driver.page_source + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Ambil hanya tabel team passing + all_tables = pd.read_html(StringIO(html_source)) + team_df = None + for df in all_tables: + if 'Squad' in df.columns: + team_df = df + break + + if team_df is None: + print("โŒ No team table found.") + return None + + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Bersihkan kolom header ganda jika ada + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Ambil kolom yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + # Hapus baris duplikat atau NaN + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091604.py b/.history/fbrefdata_example_20251005091604.py new file mode 100644 index 0000000000000000000000000000000000000000..70deaf997ba4af459a7ecb4fd5151af50d526289 --- /dev/null +++ b/.history/fbrefdata_example_20251005091604.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Arsenal", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091825.py b/.history/fbrefdata_example_20251005091825.py new file mode 100644 index 0000000000000000000000000000000000000000..dc0c3a159c4bb15b067a81c128c0a481e3dbc125 --- /dev/null +++ b/.history/fbrefdata_example_20251005091825.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Nott'ham Forest"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091830.py b/.history/fbrefdata_example_20251005091830.py new file mode 100644 index 0000000000000000000000000000000000000000..aaf2821b6a3a742cb8ddd097720758956b797097 --- /dev/null +++ b/.history/fbrefdata_example_20251005091830.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "B"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091835.py b/.history/fbrefdata_example_20251005091835.py new file mode 100644 index 0000000000000000000000000000000000000000..d016afb07b20bfbf39ea69ed1ed63a25e7486131 --- /dev/null +++ b/.history/fbrefdata_example_20251005091835.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton""] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091839.py b/.history/fbrefdata_example_20251005091839.py new file mode 100644 index 0000000000000000000000000000000000000000..13f126c05863dae61cec16156c44130f440dd76b --- /dev/null +++ b/.history/fbrefdata_example_20251005091839.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Arsenal & Nottingham Forest (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091854.py b/.history/fbrefdata_example_20251005091854.py new file mode 100644 index 0000000000000000000000000000000000000000..98eadf0b0e4fb5747f8a9da657464772aa0ce9e9 --- /dev/null +++ b/.history/fbrefdata_example_20251005091854.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091857.py b/.history/fbrefdata_example_20251005091857.py new file mode 100644 index 0000000000000000000000000000000000000000..db4f886fd43ca94d77c54af156e72190078ac342 --- /dev/null +++ b/.history/fbrefdata_example_20251005091857.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005091858.py b/.history/fbrefdata_example_20251005091858.py new file mode 100644 index 0000000000000000000000000000000000000000..e18b8f3a7af12de1a39a22800a01d5edcadf7e7c --- /dev/null +++ b/.history/fbrefdata_example_20251005091858.py @@ -0,0 +1,104 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092140.py b/.history/fbrefdata_example_20251005092140.py new file mode 100644 index 0000000000000000000000000000000000000000..b092429cbc2bd5196d5a2099f23394b154e906b6 --- /dev/null +++ b/.history/fbrefdata_example_20251005092140.py @@ -0,0 +1,106 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") +import time +time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092144.py b/.history/fbrefdata_example_20251005092144.py new file mode 100644 index 0000000000000000000000000000000000000000..fcfc463fd430f7abce41f5cf7caa7f73e87095a3 --- /dev/null +++ b/.history/fbrefdata_example_20251005092144.py @@ -0,0 +1,105 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") +time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092150.py b/.history/fbrefdata_example_20251005092150.py new file mode 100644 index 0000000000000000000000000000000000000000..1b3915e2090e8202b32e3ffb4b8f090befeb23bd --- /dev/null +++ b/.history/fbrefdata_example_20251005092150.py @@ -0,0 +1,105 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092800.py b/.history/fbrefdata_example_20251005092800.py new file mode 100644 index 0000000000000000000000000000000000000000..40b2cc3c4493386b9453d3d72f1c829c070540dc --- /dev/null +++ b/.history/fbrefdata_example_20251005092800.py @@ -0,0 +1,106 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "url = "https://fbref.com/en/comps/9/teams/Premier-League-Stats" +" + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092803.py b/.history/fbrefdata_example_20251005092803.py new file mode 100644 index 0000000000000000000000000000000000000000..1a5db476911a708c61fc0ff8ef58e4d738f6bbe9 --- /dev/null +++ b/.history/fbrefdata_example_20251005092803.py @@ -0,0 +1,105 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "url = "https://fbref.com/en/comps/9/teams/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092809.py b/.history/fbrefdata_example_20251005092809.py new file mode 100644 index 0000000000000000000000000000000000000000..1b3915e2090e8202b32e3ffb4b8f090befeb23bd --- /dev/null +++ b/.history/fbrefdata_example_20251005092809.py @@ -0,0 +1,105 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092817.py b/.history/fbrefdata_example_20251005092817.py new file mode 100644 index 0000000000000000000000000000000000000000..c1088488b8c2429603bf6d6150a5bac0ed9694c4 --- /dev/null +++ b/.history/fbrefdata_example_20251005092817.py @@ -0,0 +1,106 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): +url = "https://fbref.com/en/comps/9/teams/Premier-League-Stats" + + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092820.py b/.history/fbrefdata_example_20251005092820.py new file mode 100644 index 0000000000000000000000000000000000000000..ef0efecf3e532f3877da5c85781aced9fd13e0e5 --- /dev/null +++ b/.history/fbrefdata_example_20251005092820.py @@ -0,0 +1,106 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/teams/Premier-League-Stats" + + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092822.py b/.history/fbrefdata_example_20251005092822.py new file mode 100644 index 0000000000000000000000000000000000000000..489166436663efc7c7045df9a44de8461b4c8a00 --- /dev/null +++ b/.history/fbrefdata_example_20251005092822.py @@ -0,0 +1,105 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_team_passing(): + url = "https://fbref.com/en/comps/9/teams/Premier-League-Stats" + print(f"Opening browser to download team passing stats from {url} ...") + time.sleep(5) + + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # bisa diaktifkan jika tidak perlu melihat browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]"))) + accept_button.click() + print("Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # โœ… Tunggu elemen tabel tim muncul (div wrapper) + try: + wait = WebDriverWait(driver, 20) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + print("โœ… Team stats div found, extracting HTML...") + + # Ambil HTML hanya bagian tabel team passing + team_html = div_element.get_attribute("outerHTML") + except TimeoutException: + print("โŒ The team stats table could not be found on the page. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("Data downloaded. Processing with pandas...") + + # Baca tabel dari potongan HTML + team_df = pd.read_html(StringIO(team_html))[0] + print(f"โœ… Found team table with shape: {team_df.shape}") + + # Jika ada header dua baris, gabungkan + if isinstance(team_df.columns, pd.MultiIndex): + team_df.columns = ['_'.join(col).strip() for col in team_df.columns.values] + + # Pilih kolom utama yang relevan + cols_to_use = [c for c in team_df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + team_df = team_df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in team_df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + team_df.rename(columns=rename_map, inplace=True) + + team_df = team_df[team_df['Squad'].notna()] + team_df = team_df[~team_df['Squad'].str.contains("Squad|Rk", na=False)] + + return team_df + + +def filter_teams(df, teams): + return df[df["Squad"].isin(teams)] + + +def main(): + df = pull_premier_league_team_passing() + if df is not None: + # Simpan ke CSV otomatis + df.to_csv("premier_league_team_passing.csv", index=False) + print("\n๐Ÿ’พ Saved to premier_league_team_passing.csv") + + teams = ["Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print("\n๐Ÿ“Š Passing Stats for Wolves & Brighton (Team Level)") + print("=" * 70) + print(df_filtered) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005092904.py b/.history/fbrefdata_example_20251005092904.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc281e6bc99c7d0e232d513e745fbd5325b142e --- /dev/null +++ b/.history/fbrefdata_example_20251005092904.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/.history/fbrefdata_example_20251005093119.py b/.history/fbrefdata_example_20251005093119.py new file mode 100644 index 0000000000000000000000000000000000000000..20dcd0f3da3e81968006e0dc3f15b43e9c51a99a --- /dev/null +++ b/.history/fbrefdata_example_20251005093119.py @@ -0,0 +1,61 @@ +import requests +from bs4 import BeautifulSoup, Comment +import pandas as pd +import re + +# === 1. URL target (Premier League Passing Stats terbaru) === +URL = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + +print(f"๐Ÿ“ก Mengambil data dari {URL} ...") + +# === 2. Ambil HTML page === +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36" +} +response = requests.get(URL, headers=headers) + +if response.status_code != 200: + raise Exception(f"Gagal mengunduh halaman (status code {response.status_code})") + +html = response.text + +# === 3. Tangani tabel yang tersembunyi dalam komentar HTML === +soup = BeautifulSoup(html, "html.parser") + +# FBref sering menyembunyikan tabel di dalam komentar +comments = soup.find_all(string=lambda text: isinstance(text, Comment)) +passing_table_html = None + +for c in comments: + if 'table' in c and 'passing' in c: + if 'id="stats_passing' in c: + passing_table_html = c + break + +if not passing_table_html: + raise Exception("โŒ Tabel passing tidak ditemukan. Mungkin struktur halaman berubah.") + +# === 4. Parse tabel dari komentar === +passing_soup = BeautifulSoup(passing_table_html, "html.parser") +table = passing_soup.find("table") + +if table is None: + raise Exception("โŒ Tidak bisa mem-parse tabel dari komentar HTML.") + +# === 5. Konversi ke DataFrame === +df = pd.read_html(str(table))[0] + +# === 6. Bersihkan kolom === +df.columns = [' '.join(col).strip() if isinstance(col, tuple) else col for col in df.columns] +df = df.dropna(how='all') # hapus baris kosong + +# === 7. Simpan ke CSV === +csv_name = "premier_league_passing_2025.csv" +df.to_csv(csv_name, index=False) +print(f"โœ… Data berhasil diunduh dan disimpan ke {csv_name}") + +# === 8. Tampilkan preview === +print("\n=== Preview Data ===") +print(df.head(10)) diff --git a/.history/fbrefdata_example_20251005093129.py b/.history/fbrefdata_example_20251005093129.py new file mode 100644 index 0000000000000000000000000000000000000000..20dcd0f3da3e81968006e0dc3f15b43e9c51a99a --- /dev/null +++ b/.history/fbrefdata_example_20251005093129.py @@ -0,0 +1,61 @@ +import requests +from bs4 import BeautifulSoup, Comment +import pandas as pd +import re + +# === 1. URL target (Premier League Passing Stats terbaru) === +URL = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + +print(f"๐Ÿ“ก Mengambil data dari {URL} ...") + +# === 2. Ambil HTML page === +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36" +} +response = requests.get(URL, headers=headers) + +if response.status_code != 200: + raise Exception(f"Gagal mengunduh halaman (status code {response.status_code})") + +html = response.text + +# === 3. Tangani tabel yang tersembunyi dalam komentar HTML === +soup = BeautifulSoup(html, "html.parser") + +# FBref sering menyembunyikan tabel di dalam komentar +comments = soup.find_all(string=lambda text: isinstance(text, Comment)) +passing_table_html = None + +for c in comments: + if 'table' in c and 'passing' in c: + if 'id="stats_passing' in c: + passing_table_html = c + break + +if not passing_table_html: + raise Exception("โŒ Tabel passing tidak ditemukan. Mungkin struktur halaman berubah.") + +# === 4. Parse tabel dari komentar === +passing_soup = BeautifulSoup(passing_table_html, "html.parser") +table = passing_soup.find("table") + +if table is None: + raise Exception("โŒ Tidak bisa mem-parse tabel dari komentar HTML.") + +# === 5. Konversi ke DataFrame === +df = pd.read_html(str(table))[0] + +# === 6. Bersihkan kolom === +df.columns = [' '.join(col).strip() if isinstance(col, tuple) else col for col in df.columns] +df = df.dropna(how='all') # hapus baris kosong + +# === 7. Simpan ke CSV === +csv_name = "premier_league_passing_2025.csv" +df.to_csv(csv_name, index=False) +print(f"โœ… Data berhasil diunduh dan disimpan ke {csv_name}") + +# === 8. Tampilkan preview === +print("\n=== Preview Data ===") +print(df.head(10)) diff --git a/.history/fbrefdata_example_20251005093230.py b/.history/fbrefdata_example_20251005093230.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc281e6bc99c7d0e232d513e745fbd5325b142e --- /dev/null +++ b/.history/fbrefdata_example_20251005093230.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/.history/historical_data_20251005104339.py b/.history/historical_data_20251005104339.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/historical_data_20251005104343.py b/.history/historical_data_20251005104343.py new file mode 100644 index 0000000000000000000000000000000000000000..10133c126e1bc4f8f211c2b225a217490d0c2459 --- /dev/null +++ b/.history/historical_data_20251005104343.py @@ -0,0 +1,140 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + """ + Membaca file statistik passing pemain dan menghitung rata-rata + persentase passing ('Total_Cmp%') untuk setiap tim. + """ + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + print(f"โŒ Error: Kolom 'Squad' atau 'Total_Cmp%' tidak ditemukan di {passing_stats_file}") + return None + + # Mengubah tipe data dan menghitung rata-rata + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + print(" Pastikan file ini ada di folder yang sama.") + return None + except Exception as e: + print(f"โŒ Terjadi error saat memproses {passing_stats_file}: {e}") + return None + + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_historical_matches(): + """ + Scrape data pertandingan historis dari FBref menggunakan Selenium. + """ + # URL untuk data Premier League musim 2023-2024 yang sudah selesai + url = "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Mengakses halaman: {url}") + + options = webdriver.ChromeOptions() + options.add_argument("--headless") # Jalankan di background tanpa membuka browser + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # Coba klik cookie banner jika ada + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + time.sleep(2) + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + # Ambil HTML dari tabel data pertandingan + try: + table_element = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.ID, "sched_2023-2024_9_1")) + ) + html_source = table_element.get_attribute('outerHTML') + print("โœ… Berhasil mengambil tabel data pertandingan.") + return html_source + except Exception as e: + print(f"โŒ Gagal menemukan tabel pertandingan: {e}") + return None + + finally: + if driver: + driver.quit() + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + # 1. Hitung rata-rata passing dari file yang sudah ada + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + # 2. Scrape data historis pertandingan + html_table = scrape_historical_matches() + if html_table is None: + sys.exit() + + # 3. Proses data hasil scrape + print("โš™๏ธ Memproses data pertandingan...") + df_matches = pd.read_html(StringIO(html_table))[0] + + # Membersihkan data + df_matches = df_matches[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + + # Buat dictionary untuk mapping nama tim ke passing % + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + if team_name in pass_map: + return pass_map[team_name] + for squad_name, perc in pass_map.items(): + if team_name in squad_name or squad_name in team_name: + return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + # Finalisasi DataFrame + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + # 4. Simpan ke CSV + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda bisa menjalankan script prediksi utama Anda.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005104759.py b/.history/historical_data_20251005104759.py new file mode 100644 index 0000000000000000000000000000000000000000..187a5c86739a29b60c08ed526414872b37020e45 --- /dev/null +++ b/.history/historical_data_20251005104759.py @@ -0,0 +1,135 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + """ + Membaca file statistik passing pemain dan menghitung rata-rata + persentase passing ('Total_Cmp%') untuk setiap tim. + """ + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + print(f"โŒ Error: Kolom 'Squad' atau 'Total_Cmp%' tidak ditemukan di {passing_stats_file}") + return None + + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + print(" Pastikan file ini ada di folder yang sama.") + return None + except Exception as e: + print(f"โŒ Terjadi error saat memproses {passing_stats_file}: {e}") + return None + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_historical_matches(): + """ + Scrape data pertandingan historis dari FBref menggunakan Selenium. + """ + url = "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Mengakses halaman: {url}") + + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + time.sleep(2) + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + # === PERUBAHAN DI SINI === + # Beri waktu 3 detik agar semua JavaScript selesai dimuat + print("โณ Memberi waktu ekstra agar halaman memuat sempurna...") + time.sleep(3) + + # Ambil HTML dari tabel yang berisi data pertandingan + try: + # Menunggu hingga tabel benar-benar TERLIHAT, bukan hanya ada di DOM + # Menggunakan selector class yang lebih umum: 'stats_table' + table_element = WebDriverWait(driver, 15).until( + EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table")) + ) + html_source = table_element.get_attribute('outerHTML') + print("โœ… Berhasil menemukan dan mengambil tabel data pertandingan.") + return html_source + except Exception as e: + print(f"โŒ Gagal menemukan tabel pertandingan setelah menunggu: {e}") + return None + + finally: + if driver: + driver.quit() + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + # Ganti nama file sesuai dengan nama file Anda jika berbeda + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + html_table = scrape_historical_matches() + if html_table is None: + sys.exit() + + print("โš™๏ธ Memproses data pertandingan...") + df_matches = pd.read_html(StringIO(html_table))[0] + df_matches = df_matches[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + if team_name in pass_map: + return pass_map[team_name] + for squad_name, perc in pass_map.items(): + if team_name in squad_name or squad_name in team_name: + return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda bisa menjalankan script prediksi utama Anda.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005105932.py b/.history/historical_data_20251005105932.py new file mode 100644 index 0000000000000000000000000000000000000000..eea0bda35d6842aa34284e8ded89c6d702c7d57b --- /dev/null +++ b/.history/historical_data_20251005105932.py @@ -0,0 +1,134 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + return None + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_season_data(url, driver): + """Fungsi untuk scrape data dari satu URL musim.""" + print(f" - Mengakses: {url}") + driver.get(url) + time.sleep(3) # Beri waktu halaman untuk memuat + + try: + table_element = WebDriverWait(driver, 15).until( + EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table")) + ) + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" โœ… Berhasil mengambil data.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini: {e}") + return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + # === INI ADALAH DAFTAR URL UNTUK 4 MUSIM TERAKHIR === + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + # Handle cookie banner sekali saja di awal + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + finally: + if driver: + driver.quit() + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + # Fungsi pencocokan nama yang lebih baik + def get_pass_perc(team_name): + name_map = { + "Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", + "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", + "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", + "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers" + } + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: + return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: + return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1).dropna() + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda siap untuk langkah terakhir!") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005110825.py b/.history/historical_data_20251005110825.py new file mode 100644 index 0000000000000000000000000000000000000000..a76ad695854b37c99704bc17519bf884dd0eba48 --- /dev/null +++ b/.history/historical_data_20251005110825.py @@ -0,0 +1,142 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + return None + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_season_data(url, driver): + """Fungsi untuk scrape data dari satu URL musim.""" + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + # Menunggu hingga tabel terlihat, dengan timeout lebih lama (20 detik) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until( + EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table")) + ) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini. Mungkin timeout atau halaman berubah.") + print(f" Detail Error: {e}") + return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + + # === JEDA ISTIRAHAT AGAR TIDAK DIANGGAP SPAM === + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + # =============================================== + + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + # ... (Sisa kode untuk memproses dan menyimpan data sama seperti sebelumnya) ... + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = { + "Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", + "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", + "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", + "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers" + } + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: + return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: + return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1).dropna() + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005111330.py b/.history/historical_data_20251005111330.py new file mode 100644 index 0000000000000000000000000000000000000000..b59c4b96da52bac2cc46bd1370ea4370d4af1a53 --- /dev/null +++ b/.history/historical_data_20251005111330.py @@ -0,0 +1,129 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + return None + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_season_data(url, driver): + """Fungsi untuk scrape data dari satu URL musim.""" + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until( + EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table")) + ) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini. Mungkin timeout atau halaman berubah.") + return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... (kode scraping sama persis) ... + options = webdriver.ChromeOptions() + options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + # Menambahkan .copy() untuk menghindari SettingWithCopyWarning + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']].copy() + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + + # === PERBAIKAN: Menghapus baris .dropna() yang terlalu agresif === + # final_df = final_df.round(1).dropna() # Baris ini dihapus + + # Kita hanya membulatkan angka + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005111353.py b/.history/historical_data_20251005111353.py new file mode 100644 index 0000000000000000000000000000000000000000..5965d36e43a3617dfb5139516d950b4f0d6a0890 --- /dev/null +++ b/.history/historical_data_20251005111353.py @@ -0,0 +1,129 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# --- FUNGSI UNTUK MENGHITUNG RATA-RATA PASSING % PER TIM --- +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + return None + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +# --- FUNGSI UTAMA UNTUK SCRAPING DATA PERTANDINGAN --- +def scrape_season_data(url, driver): + """Fungsi untuk scrape data dari satu URL musim.""" + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until( + EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table")) + ) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini. Mungkin timeout atau halaman berubah.") + return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches2.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... (kode scraping sama persis) ... + options = webdriver.ChromeOptions() + options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + # Menambahkan .copy() untuk menghindari SettingWithCopyWarning + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']].copy() + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + + # === PERBAIKAN: Menghapus baris .dropna() yang terlalu agresif === + # final_df = final_df.round(1).dropna() # Baris ini dihapus + + # Kita hanya membulatkan angka + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005111752.py b/.history/historical_data_20251005111752.py new file mode 100644 index 0000000000000000000000000000000000000000..21ed788e34d821f0739d408db1e0efa68ed041f5 --- /dev/null +++ b/.history/historical_data_20251005111752.py @@ -0,0 +1,125 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak berubah +# ... (kode fungsi sama persis seperti sebelumnya) ... +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file); df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... (kode scraping sama persis) ... + options = webdriver.ChromeOptions() + options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click() + print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === BAGIAN DIAGNOSTIK DIMULAI DI SINI === + print("\n\n๐Ÿ”ฌ MEMULAI DIAGNOSTIK DATA ๐Ÿ”ฌ\n") + print(f"Total baris data yang berhasil di-scrape: {len(all_matches_df)}") + + # Melihat beberapa baris pertama dari data mentah untuk memeriksa strukturnya + print("\nContoh 5 baris pertama dari data mentah (sebelum dibersihkan):") + print(all_matches_df.head()) + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']].copy() + + print(f"\n - Langkah 1: Jumlah baris setelah memilih kolom = {len(df_matches)}") + + df_matches.dropna(subset=['Score'], inplace=True) + print(f" - Langkah 2: Jumlah baris setelah membuang baris tanpa 'Score' = {len(df_matches)}") + + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + print(f" - Langkah 3: Jumlah baris setelah memfilter format 'Score' yang benar = {len(df_matches)}") + + print("\n๐Ÿ”ฌ DIAGNOSTIK SELESAI ๐Ÿ”ฌ\n") + # === AKHIR BAGIAN DIAGNOSTIK === + + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005111804.py b/.history/historical_data_20251005111804.py new file mode 100644 index 0000000000000000000000000000000000000000..2aee7650f977ca38894eb5b7f4fa356124168c7a --- /dev/null +++ b/.history/historical_data_20251005111804.py @@ -0,0 +1,125 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak berubah +# ... (kode fungsi sama persis seperti sebelumnya) ... +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file); df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches3.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... (kode scraping sama persis) ... + options = webdriver.ChromeOptions() + options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click() + print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === BAGIAN DIAGNOSTIK DIMULAI DI SINI === + print("\n\n๐Ÿ”ฌ MEMULAI DIAGNOSTIK DATA ๐Ÿ”ฌ\n") + print(f"Total baris data yang berhasil di-scrape: {len(all_matches_df)}") + + # Melihat beberapa baris pertama dari data mentah untuk memeriksa strukturnya + print("\nContoh 5 baris pertama dari data mentah (sebelum dibersihkan):") + print(all_matches_df.head()) + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']].copy() + + print(f"\n - Langkah 1: Jumlah baris setelah memilih kolom = {len(df_matches)}") + + df_matches.dropna(subset=['Score'], inplace=True) + print(f" - Langkah 2: Jumlah baris setelah membuang baris tanpa 'Score' = {len(df_matches)}") + + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + print(f" - Langkah 3: Jumlah baris setelah memfilter format 'Score' yang benar = {len(df_matches)}") + + print("\n๐Ÿ”ฌ DIAGNOSTIK SELESAI ๐Ÿ”ฌ\n") + # === AKHIR BAGIAN DIAGNOSTIK === + + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005112258.py b/.history/historical_data_20251005112258.py new file mode 100644 index 0000000000000000000000000000000000000000..ec483a1beb771befd571d904af33680287919d76 --- /dev/null +++ b/.history/historical_data_20251005112258.py @@ -0,0 +1,112 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak perlu diubah +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan."); return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... (Kode scraping tidak berubah) ... + options = webdriver.ChromeOptions(); options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click(); print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === PERBAIKAN FINAL PADA LOGIKA PEMBERSIHAN DATA === + print("\nโš™๏ธ Memproses semua data pertandingan...") + # 1. Pilih kolom yang relevan + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']].copy() + + # 2. Buang baris header mingguan (di mana kolom 'Home' kosong) + df_matches.dropna(subset=['Home'], inplace=True) + + # 3. HANYA simpan baris yang skornya mengandung 'โ€“'. Ini adalah kunci utamanya. + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)].copy() + # ======================================================= + + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005113214.py b/.history/historical_data_20251005113214.py new file mode 100644 index 0000000000000000000000000000000000000000..e01deb95967cd585af0640fc8dc37410fe6c2be0 --- /dev/null +++ b/.history/historical_data_20251005113214.py @@ -0,0 +1,125 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random +import numpy as np + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak perlu diubah +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + print(f"โŒ Error: Kolom 'Squad' atau 'Total_Cmp%' tidak ditemukan di {passing_stats_file}") + return None + + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + for team, perc in pass_map.items(): + if team_name in team: + return perc + print(f"โŒ Tidak dapat menemukan data passing untuk tim: {team_name}") + return np.nan # Kembalikan NaN jika tidak ditemukan + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda bisa menjalankan script prediksi utama Anda.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005113348.py b/.history/historical_data_20251005113348.py new file mode 100644 index 0000000000000000000000000000000000000000..c6f721f460a43ea548c725509f2b6e79628c1eda --- /dev/null +++ b/.history/historical_data_20251005113348.py @@ -0,0 +1,111 @@ +# File: create_historical_data.py (Versi Final Sebenarnya) +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi lainnya tetap sama +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan."); return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... Kode scraping sama ... + options = webdriver.ChromeOptions(); options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click(); print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === PERBAIKAN FINAL PADA LOGIKA PEMBERSIHAN DATA === + print("\nโš™๏ธ Memproses semua data pertandingan...") + + # Kunci perbaikan: Kita hanya peduli pada baris yang memiliki skor valid. + # Ubah kolom 'Score' menjadi string untuk memastikan .str.contains() bekerja + all_matches_df['Score'] = all_matches_df['Score'].astype(str) + + # Filter hanya baris yang merupakan pertandingan (memiliki skor dengan format 'angkaโ€“angka') + df_matches = all_matches_df[all_matches_df['Score'].str.contains(r'\d+โ€“\d+', na=False)].copy() + + # Setelah difilter, baru kita proses kolomnya + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005113356.py b/.history/historical_data_20251005113356.py new file mode 100644 index 0000000000000000000000000000000000000000..e01deb95967cd585af0640fc8dc37410fe6c2be0 --- /dev/null +++ b/.history/historical_data_20251005113356.py @@ -0,0 +1,125 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random +import numpy as np + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak perlu diubah +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + print(f"โŒ Error: Kolom 'Squad' atau 'Total_Cmp%' tidak ditemukan di {passing_stats_file}") + return None + + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + for team, perc in pass_map.items(): + if team_name in team: + return perc + print(f"โŒ Tidak dapat menemukan data passing untuk tim: {team_name}") + return np.nan # Kembalikan NaN jika tidak ditemukan + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda bisa menjalankan script prediksi utama Anda.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005113429.py b/.history/historical_data_20251005113429.py new file mode 100644 index 0000000000000000000000000000000000000000..611a773797ebf1ab8f0dc653379d9dca03fe13a9 --- /dev/null +++ b/.history/historical_data_20251005113429.py @@ -0,0 +1,126 @@ +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random +import numpy as np + +# Fungsi-fungsi (calculate_team_passing_avg, scrape_season_data) tidak perlu diubah +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + if "Squad" not in df_pass.columns or "Total_Cmp%" not in df_pass.columns: + print(f"โŒ Error: Kolom 'Squad' atau 'Total_Cmp%' tidak ditemukan di {passing_stats_file}") + return None + + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index() + team_avg_pass.rename(columns={'Total_Cmp%': 'AvgPass%'}, inplace=True) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan.") + return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url) + print(" โณ Menunggu tabel data muncul...") + # Increased timeout to 30 seconds + table_element = WebDriverWait(driver, 30).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini: {e}"); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: + sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + options = webdriver.ChromeOptions() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5) + accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))) + accept_button.click() + print("โœ… Cookie banner diterima.") + except: + print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: + all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10) + print(f" โ˜• Istirahat sejenak selama {jeda} detik...") + time.sleep(jeda) + finally: + if driver: + driver.quit() + print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan.") + sys.exit() + + print("\nโš™๏ธ Memproses semua data pertandingan...") + df_matches = all_matches_df[['Date', 'Home', 'Score', 'Away']] + df_matches.dropna(subset=['Score'], inplace=True) + df_matches = df_matches[df_matches['Score'].str.contains('โ€“', na=False)] + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + for team, perc in pass_map.items(): + if team_name in team: + return perc + print(f"โŒ Tidak dapat menemukan data passing untuk tim: {team_name}") + return np.nan # Kembalikan NaN jika tidak ditemukan + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + print(" Sekarang Anda bisa menjalankan script prediksi utama Anda.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_data_20251005113633.py b/.history/historical_data_20251005113633.py new file mode 100644 index 0000000000000000000000000000000000000000..c6f721f460a43ea548c725509f2b6e79628c1eda --- /dev/null +++ b/.history/historical_data_20251005113633.py @@ -0,0 +1,111 @@ +# File: create_historical_data.py (Versi Final Sebenarnya) +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi lainnya tetap sama +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan."); return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... Kode scraping sama ... + options = webdriver.ChromeOptions(); options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click(); print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === PERBAIKAN FINAL PADA LOGIKA PEMBERSIHAN DATA === + print("\nโš™๏ธ Memproses semua data pertandingan...") + + # Kunci perbaikan: Kita hanya peduli pada baris yang memiliki skor valid. + # Ubah kolom 'Score' menjadi string untuk memastikan .str.contains() bekerja + all_matches_df['Score'] = all_matches_df['Score'].astype(str) + + # Filter hanya baris yang merupakan pertandingan (memiliki skor dengan format 'angkaโ€“angka') + df_matches = all_matches_df[all_matches_df['Score'].str.contains(r'\d+โ€“\d+', na=False)].copy() + + # Setelah difilter, baru kita proses kolomnya + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_match_data_20251005113517.csv b/.history/historical_match_data_20251005113517.csv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/historical_match_data_20251005113521.csv b/.history/historical_match_data_20251005113521.csv new file mode 100644 index 0000000000000000000000000000000000000000..c6f721f460a43ea548c725509f2b6e79628c1eda --- /dev/null +++ b/.history/historical_match_data_20251005113521.csv @@ -0,0 +1,111 @@ +# File: create_historical_data.py (Versi Final Sebenarnya) +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi lainnya tetap sama +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan."); return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... Kode scraping sama ... + options = webdriver.ChromeOptions(); options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click(); print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === PERBAIKAN FINAL PADA LOGIKA PEMBERSIHAN DATA === + print("\nโš™๏ธ Memproses semua data pertandingan...") + + # Kunci perbaikan: Kita hanya peduli pada baris yang memiliki skor valid. + # Ubah kolom 'Score' menjadi string untuk memastikan .str.contains() bekerja + all_matches_df['Score'] = all_matches_df['Score'].astype(str) + + # Filter hanya baris yang merupakan pertandingan (memiliki skor dengan format 'angkaโ€“angka') + df_matches = all_matches_df[all_matches_df['Score'].str.contains(r'\d+โ€“\d+', na=False)].copy() + + # Setelah difilter, baru kita proses kolomnya + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/.history/historical_match_data_20251005113522.csv b/.history/historical_match_data_20251005113522.csv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/historical_match_data_20251005113527.csv b/.history/historical_match_data_20251005113527.csv new file mode 100644 index 0000000000000000000000000000000000000000..98d5a731346d31998a6599222fd8b38b250fb9bf --- /dev/null +++ b/.history/historical_match_data_20251005113527.csv @@ -0,0 +1,51 @@ +Date,Home,Away,HomeGoals,AwayGoals,HomePass%,AwayPass% +2020-09-12,Fulham,Arsenal,0,3,78.2,86.9 +2020-09-12,Crystal Palace,Southampton,1,0,74.7,82.4 +2020-09-12,Liverpool,Leeds United,4,3,86.0,83.8 +2020-09-12,West Ham,Newcastle Utd,0,2,80.7,72.9 +2020-09-13,West Brom,Leicester City,0,3,76.4,87.6 +2020-09-13,Tottenham,Everton,0,1,81.4,79.8 +2020-09-14,Sheffield Utd,Wolves,0,2,78.9,81.8 +2020-09-14,Brighton,Chelsea,1,3,81.7,85.1 +2020-09-19,Everton,West Brom,5,2,79.8,76.4 +2020-09-19,Leeds United,Fulham,4,3,83.8,78.2 +2020-09-19,Manchester Utd,Crystal Palace,1,3,84.4,74.7 +2020-09-19,Arsenal,West Ham,2,1,86.9,80.7 +2020-09-20,Southampton,Tottenham,2,5,82.4,81.4 +2020-09-20,Newcastle Utd,Brighton,0,3,72.9,81.7 +2020-09-20,Chelsea,Liverpool,0,2,85.1,86.0 +2020-09-20,Leicester City,Burnley,4,2,87.6,76.9 +2020-09-21,Aston Villa,Sheffield Utd,1,0,79.0,78.9 +2020-09-21,Wolves,Manchester City,1,3,81.8,88.4 +2020-09-26,Brighton,Manchester Utd,2,3,81.7,84.4 +2020-09-26,Crystal Palace,Everton,1,2,74.7,79.8 +2020-09-26,West Brom,Chelsea,3,3,76.4,85.1 +2020-09-26,Burnley,Southampton,0,1,76.9,82.4 +2020-09-27,Sheffield Utd,Leeds United,0,1,78.9,83.8 +2020-09-27,Tottenham,Newcastle Utd,1,1,81.4,72.9 +2020-09-27,Manchester City,Leicester City,2,5,88.4,87.6 +2020-09-27,West Ham,Wolves,4,0,80.7,81.8 +2020-09-28,Fulham,Aston Villa,0,3,78.2,79.0 +2020-09-28,Liverpool,Arsenal,3,1,86.0,86.9 +2020-10-03,Chelsea,Crystal Palace,4,0,85.1,74.7 +2020-10-03,Everton,Brighton,4,2,79.8,81.7 +2020-10-03,Leeds United,Manchester City,1,1,83.8,88.4 +2020-10-03,Newcastle Utd,Burnley,3,1,72.9,76.9 +2020-10-04,Leicester City,West Ham,0,3,87.6,80.7 +2020-10-04,Southampton,West Brom,2,0,82.4,76.4 +2020-10-04,Arsenal,Sheffield Utd,2,1,86.9,78.9 +2020-10-04,Wolves,Fulham,1,0,81.8,78.2 +2020-10-04,Manchester Utd,Tottenham,1,6,84.4,81.4 +2020-10-04,Aston Villa,Liverpool,7,2,79.0,86.0 +2020-10-17,Everton,Liverpool,2,2,79.8,86.0 +2020-10-17,Chelsea,Southampton,3,3,85.1,82.4 +2020-10-17,Manchester City,Arsenal,1,0,88.4,86.9 +2020-10-17,Newcastle Utd,Manchester Utd,1,4,72.9,84.4 +2020-10-18,Sheffield Utd,Fulham,1,1,78.9,78.2 +2020-10-18,Crystal Palace,Brighton,1,1,74.7,81.7 +2020-10-18,Tottenham,West Ham,3,3,81.4,80.7 +2020-10-18,Leicester City,Aston Villa,0,1,87.6,79.0 +2020-10-19,West Brom,Burnley,0,0,76.4,76.9 +2020-10-19,Leeds United,Wolves,0,1,83.8,81.8 +2020-10-23,Aston Villa,Leeds United,0,3,79.0,83.8 +2020-10-24,West Ham,Manchester City,1,1,80.7,88.4 \ No newline at end of file diff --git a/.history/main/fbrefdata_example_20251005093229.py b/.history/main/fbrefdata_example_20251005093229.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc281e6bc99c7d0e232d513e745fbd5325b142e --- /dev/null +++ b/.history/main/fbrefdata_example_20251005093229.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/.history/main/fbrefdata_example_20251009102148.py b/.history/main/fbrefdata_example_20251009102148.py new file mode 100644 index 0000000000000000000000000000000000000000..54090257b0eac1c26c906aca5dd31b001e4fcc06 --- /dev/null +++ b/.history/main/fbrefdata_example_20251009102148.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) +f + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/.history/main/fbrefdata_example_20251009102149.py b/.history/main/fbrefdata_example_20251009102149.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc281e6bc99c7d0e232d513e745fbd5325b142e --- /dev/null +++ b/.history/main/fbrefdata_example_20251009102149.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/.history/main/pl-predict_smalldataset_20251005122741.py b/.history/main/pl-predict_smalldataset_20251005122741.py new file mode 100644 index 0000000000000000000000000000000000000000..e0d09517e62fb94c4eb30f440f050e76975742ca --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251005122741.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/pl-predict_smalldataset_20251009113302.py b/.history/main/pl-predict_smalldataset_20251009113302.py new file mode 100644 index 0000000000000000000000000000000000000000..802a80a133249a30fcfd83fd1539fa992bcf8c6b --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251009113302.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("main/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/pl-predict_smalldataset_20251009113315.py b/.history/main/pl-predict_smalldataset_20251009113315.py new file mode 100644 index 0000000000000000000000000000000000000000..6e017c4f835fc22ca1686aacff2e83098319031a --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251009113315.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("csv/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/pl-predict_smalldataset_20251009113332.py b/.history/main/pl-predict_smalldataset_20251009113332.py new file mode 100644 index 0000000000000000000000000000000000000000..93a412c149ae826aed0ad6ffa5125701b95cbca0 --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251009113332.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("csv/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("shistorical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/pl-predict_smalldataset_20251009113334.py b/.history/main/pl-predict_smalldataset_20251009113334.py new file mode 100644 index 0000000000000000000000000000000000000000..6e017c4f835fc22ca1686aacff2e83098319031a --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251009113334.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("csv/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/pl-predict_smalldataset_20251009113337.py b/.history/main/pl-predict_smalldataset_20251009113337.py new file mode 100644 index 0000000000000000000000000000000000000000..7eb379cc24ef29f992e77299607448736841d2f3 --- /dev/null +++ b/.history/main/pl-predict_smalldataset_20251009113337.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("csv/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("csv/historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/main/predict-pl-match_otomatis_v2_20251006111747.py b/.history/main/predict-pl-match_otomatis_v2_20251006111747.py new file mode 100644 index 0000000000000000000000000000000000000000..74c381a9464bc11f82411338942a1a71199cbb7a --- /dev/null +++ b/.history/main/predict-pl-match_otomatis_v2_20251006111747.py @@ -0,0 +1,213 @@ +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except ImportError: + HAS_XGB = False + +# ============================================================================= +# BAGIAN 1, 2, 3, 4, 5 (LOAD DATA & FEATURE ENGINEERING) - TIDAK BERUBAH +# ============================================================================= +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +print("๐Ÿ”ง Membuat fitur dasar (Result)...") +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 +df['Result'] = df.apply(result_label, axis=1) + +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).mean().reset_index().rename(columns={'GoalsFor': f'AvgGoalsFor_L{window}', 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', 'ShotsFor': f'AvgShotsFor_L{window}', 'SOT_For': f'AvgSOTFor_L{window}', 'Win': f'WinRate_L{window}', 'Draw': f'DrawRate_L{window}'}) +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', f'WinRate_L{window}': f'WinRate_Home_L{window}', f'DrawRate_L{window}': f'DrawRate_Home_L{window}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', f'WinRate_L{window}': f'WinRate_Away_L{window}', f'DrawRate_L{window}': f'DrawRate_Away_L{window}'}).drop(columns=['Team']) + +print("๐Ÿ” Membuat fitur Last Result & Streak...") +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +print("โš”๏ธ Menghitung H2H sederhana...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins; df['h2h_away_wins'] = h2h_away_wins; df['h2h_draws'] = h2h_draws + +# ============================================================================= +# 6. Final feature set & cleaning - DENGAN PERBAIKAN +# ============================================================================= +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', +] # === 'GoalDiff' DIHAPUS DARI SINI === + +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ============================================================================= +# 7. Train / validation (TimeSeriesSplit) - Tidak Berubah +# ============================================================================= +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42) +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', base_model)]) +# Untuk mempercepat, kita lewati GridSearchCV dan langsung pakai parameter bagus +params = {'learning_rate': 0.05, 'max_depth': 4, 'n_estimators': 200, 'subsample': 0.8} +if HAS_XGB: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', XGBClassifier(**params, use_label_encoder=False, eval_metric='mlogloss', random_state=42))]) +else: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42, class_weight='balanced'))]) + +# ============================================================================= +# 8. Final evaluation on last fold hold-out - Tidak Berubah +# ============================================================================= +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +final_model.fit(X_train_final, y_train_final) +y_pred = final_model.predict(X_test_final) +y_proba = final_model.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):\n", cm) + +# ============================================================================= +# 9. Predict function interactive - DENGAN PERBAIKAN +# ============================================================================= +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis)...") +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +def predict_match(home_team_input, away_team_input, model=final_model, team_roll_df=team_latest_stats, history_df=full_match_history): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim."); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + + # Hitung fitur dinamis untuk pertandingan "hari ini" + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, 5) + a_streak = get_recent_streak(away, today, history_df, 5) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], h_last_res, h_streak, + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], a_last_res, a_streak, + h2h_hw, h2h_aw, h2h_d + ] + + X_input = pd.DataFrame([feat_vals], columns=features) + probs = model.predict_proba(X_input)[0] + + print(f"Info Form (Home): WinRate={h[f'WinRate_L{window}']:.2f}, AvgGoals={h[f'AvgGoalsFor_L{window}']:.2f}, LastResult={h_last_res}, WinStreak={h_streak}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{window}']:.2f}, AvgGoals={a[f'AvgGoalsFor_L{window}']:.2f}, LastResult={a_last_res}, WinStreak={a_streak}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") +except Exception as e: + print(f"Info: contoh prediksi gagal - {e}") + +joblib.dump(final_model, "model_epl_final.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final.joblib'") \ No newline at end of file diff --git a/.history/main/predict-pl-match_otomatis_v2_20251007201014.py b/.history/main/predict-pl-match_otomatis_v2_20251007201014.py new file mode 100644 index 0000000000000000000000000000000000000000..5fdc2d027f9ac4c770c4052faec4122501df2bcf --- /dev/null +++ b/.history/main/predict-pl-match_otomatis_v2_20251007201014.py @@ -0,0 +1,213 @@ +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except ImportError: + HAS_XGB = False + +# ============================================================================= +# BAGIAN 1, 2, 3, 4, 5 (LOAD DATA & FEATURE ENGINEERING) - TIDAK BERUBAH +# ============================================================================= +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv("csv/epl-training.csv", encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +print("๐Ÿ”ง Membuat fitur dasar (Result)...") +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 +df['Result'] = df.apply(result_label, axis=1) + +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).mean().reset_index().rename(columns={'GoalsFor': f'AvgGoalsFor_L{window}', 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', 'ShotsFor': f'AvgShotsFor_L{window}', 'SOT_For': f'AvgSOTFor_L{window}', 'Win': f'WinRate_L{window}', 'Draw': f'DrawRate_L{window}'}) +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', f'WinRate_L{window}': f'WinRate_Home_L{window}', f'DrawRate_L{window}': f'DrawRate_Home_L{window}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', f'WinRate_L{window}': f'WinRate_Away_L{window}', f'DrawRate_L{window}': f'DrawRate_Away_L{window}'}).drop(columns=['Team']) + +print("๐Ÿ” Membuat fitur Last Result & Streak...") +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +print("โš”๏ธ Menghitung H2H sederhana...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins; df['h2h_away_wins'] = h2h_away_wins; df['h2h_draws'] = h2h_draws + +# ============================================================================= +# 6. Final feature set & cleaning - DENGAN PERBAIKAN +# ============================================================================= +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', +] # === 'GoalDiff' DIHAPUS DARI SINI === + +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ============================================================================= +# 7. Train / validation (TimeSeriesSplit) - Tidak Berubah +# ============================================================================= +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42) +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', base_model)]) +# Untuk mempercepat, kita lewati GridSearchCV dan langsung pakai parameter bagus +params = {'learning_rate': 0.05, 'max_depth': 4, 'n_estimators': 200, 'subsample': 0.8} +if HAS_XGB: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', XGBClassifier(**params, use_label_encoder=False, eval_metric='mlogloss', random_state=42))]) +else: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42, class_weight='balanced'))]) + +# ============================================================================= +# 8. Final evaluation on last fold hold-out - Tidak Berubah +# ============================================================================= +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +final_model.fit(X_train_final, y_train_final) +y_pred = final_model.predict(X_test_final) +y_proba = final_model.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):\n", cm) + +# ============================================================================= +# 9. Predict function interactive - DENGAN PERBAIKAN +# ============================================================================= +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis)...") +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +def predict_match(home_team_input, away_team_input, model=final_model, team_roll_df=team_latest_stats, history_df=full_match_history): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim."); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + + # Hitung fitur dinamis untuk pertandingan "hari ini" + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, 5) + a_streak = get_recent_streak(away, today, history_df, 5) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], h_last_res, h_streak, + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], a_last_res, a_streak, + h2h_hw, h2h_aw, h2h_d + ] + + X_input = pd.DataFrame([feat_vals], columns=features) + probs = model.predict_proba(X_input)[0] + + print(f"Info Form (Home): WinRate={h[f'WinRate_L{window}']:.2f}, AvgGoals={h[f'AvgGoalsFor_L{window}']:.2f}, LastResult={h_last_res}, WinStreak={h_streak}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{window}']:.2f}, AvgGoals={a[f'AvgGoalsFor_L{window}']:.2f}, LastResult={a_last_res}, WinStreak={a_streak}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") +except Exception as e: + print(f"Info: contoh prediksi gagal - {e}") + +joblib.dump(final_model, "model_epl_final.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final.joblib'") \ No newline at end of file diff --git a/.history/match_predictor_20251005093723.py b/.history/match_predictor_20251005093723.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/match_predictor_20251005093729.py b/.history/match_predictor_20251005093729.py new file mode 100644 index 0000000000000000000000000000000000000000..95cb3bac8981de49f52c74ad8e0850b384608105 --- /dev/null +++ b/.history/match_predictor_20251005093729.py @@ -0,0 +1,175 @@ +# =============================================== +# ๐Ÿ”ฎ Premier League Match Outcome Predictor +# By decoder & GPT-5 +# =============================================== + +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import sys + + +# ===================================================== +# ๐Ÿงฉ 1. SCRAPE MATCH RESULTS +# ===================================================== +def scrape_match_results(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + time.sleep(2) + + options = ChromeOptions() + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--headless") # mode tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 20) + table_div = wait.until(EC.presence_of_element_located((By.ID, "div_sched_9_2025_2026"))) + print("โœ… Match schedule table found.") + html_source = table_div.get_attribute("outerHTML") + driver.quit() + return pd.read_html(StringIO(html_source))[0] + except TimeoutException: + print("โŒ Match table not found. Saving debug page...") + with open("debug_match_page.html", "w", encoding="utf-8") as f: + f.write(driver.page_source) + driver.quit() + return None + + +# ===================================================== +# ๐Ÿงฎ 2. CLEAN & FEATURE ENGINEERING +# ===================================================== +def clean_match_data(df): + print("๐Ÿงน Cleaning and preparing dataset...") + + # Hapus kolom tidak relevan + df = df[['Date', 'Home', 'Score', 'Away', 'xG', 'xG.1', 'Poss', 'Poss.1', 'Sh', 'Sh.1']] + + # Hapus baris kosong atau belum dimainkan + df = df.dropna(subset=['Score', 'xG', 'xG.1']) + + # Pisahkan skor menjadi dua kolom + df[['Home_Goals', 'Away_Goals']] = df['Score'].str.split('โ€“', expand=True) + df['Home_Goals'] = pd.to_numeric(df['Home_Goals'], errors='coerce') + df['Away_Goals'] = pd.to_numeric(df['Away_Goals'], errors='coerce') + + # Tentukan hasil pertandingan (1 = home win, 0 = draw, -1 = away win) + df['Result'] = df.apply( + lambda x: 1 if x['Home_Goals'] > x['Away_Goals'] else (-1 if x['Home_Goals'] < x['Away_Goals'] else 0), + axis=1 + ) + + # Buat fitur selisih + df['xG_diff'] = df['xG'] - df['xG.1'] + df['Poss_diff'] = df['Poss'] - df['Poss.1'] + df['Shots_diff'] = df['Sh'] - df['Sh.1'] + + df.to_csv("cleaned_match_data.csv", index=False) + print("๐Ÿ’พ Saved cleaned dataset as cleaned_match_data.csv") + return df + + +# ===================================================== +# ๐Ÿค– 3. TRAIN MODEL +# ===================================================== +def train_model(df): + print("โš™๏ธ Training Random Forest model...") + + X = df[['xG_diff', 'Poss_diff', 'Shots_diff']] + y = df['Result'] + + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + + model = RandomForestClassifier(n_estimators=300, random_state=42) + model.fit(X_train, y_train) + y_pred = model.predict(X_test) + + print("\n๐Ÿ“Š Model Evaluation:") + print(classification_report(y_test, y_pred)) + print(f"โœ… Accuracy: {accuracy_score(y_test, y_pred):.2f}") + + return model + + +# ===================================================== +# ๐Ÿ”ฎ 4. PREDICT NEW MATCH +# ===================================================== +def predict_match(model, home_team, away_team): + print(f"\n๐Ÿ”ฎ Predicting result: {home_team} vs {away_team}") + + # Sementara pakai nilai rata-rata historis (dummy) + # (Kamu bisa nanti ganti dengan data real per tim) + team_stats = { + 'Arsenal': {'xG': 2.1, 'Poss': 62, 'Sh': 15}, + 'Man City': {'xG': 2.4, 'Poss': 68, 'Sh': 17}, + 'Liverpool': {'xG': 2.3, 'Poss': 65, 'Sh': 16}, + 'Chelsea': {'xG': 1.9, 'Poss': 59, 'Sh': 14}, + 'Wolves': {'xG': 1.1, 'Poss': 43, 'Sh': 9}, + 'Brighton': {'xG': 1.7, 'Poss': 57, 'Sh': 12}, + 'Spurs': {'xG': 2.0, 'Poss': 61, 'Sh': 15}, + 'Aston Villa': {'xG': 1.8, 'Poss': 55, 'Sh': 13}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Tim belum ada di dictionary, silakan tambahkan dulu datanya.") + return + + home = team_stats[home_team] + away = team_stats[away_team] + + match_df = pd.DataFrame([{ + 'xG_diff': home['xG'] - away['xG'], + 'Poss_diff': home['Poss'] - away['Poss'], + 'Shots_diff': home['Sh'] - away['Sh'] + }]) + + pred = model.predict(match_df)[0] + if pred == 1: + print(f"๐Ÿ† Prediksi: {home_team} MENANG") + elif pred == 0: + print(f"๐Ÿค Prediksi: SERI") + else: + print(f"โšก Prediksi: {away_team} MENANG") + + +# ===================================================== +# ๐Ÿš€ 5. MAIN EXECUTION +# ===================================================== +def main(): + # Step 1: Scrape data pertandingan + df_raw = scrape_match_results() + if df_raw is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Step 2: Bersihkan data + df_clean = clean_match_data(df_raw) + + # Step 3: Train model + model = train_model(df_clean) + + # Step 4: Prediksi + predict_match(model, "Arsenal", "Wolves") + predict_match(model, "Liverpool", "Man City") + predict_match(model, "Brighton", "Chelsea") + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005094109.py b/.history/match_predictor_20251005094109.py new file mode 100644 index 0000000000000000000000000000000000000000..2d617346d95a6a7666998f2a740292c52084bbc2 --- /dev/null +++ b/.history/match_predictor_20251005094109.py @@ -0,0 +1,117 @@ +import requests +import pandas as pd +from bs4 import BeautifulSoup + +def get_fixtures_table(url): + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}) + if response.status_code != 200: + print("โŒ Gagal mengakses halaman FBref.") + return None + + soup = BeautifulSoup(response.text, 'html.parser') + + # Coba cari table dengan ID dinamis (berubah setiap musim) + table = None + for div in soup.find_all('div', id=True): + if 'sched' in div['id'] and 'Premier-League' not in div['id']: + if div.find('table'): + table = div.find('table') + print(f"โœ… Ditemukan tabel jadwal dengan id: {div['id']}") + break + + # Jika tidak ditemukan sama sekali + if table is None: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + return None + + # Parse ke pandas + df = pd.read_html(str(table))[0] + print(f"โœ… Table ditemukan dengan shape: {df.shape}") + return df + + +def predict_match(df, team1, team2): + print(f"\n๐Ÿ”ฎ Memprediksi hasil: {team1} vs {team2}") + + # Filter 5 pertandingan terakhir masing-masing + df = df.dropna(subset=['Home', 'Away']) + team1_recent = df[(df['Home'] == team1) | (df['Away'] == team1)].tail(5) + team2_recent = df[(df['Home'] == team2) | (df['Away'] == team2)].tail(5) + + def get_stats(team_df, team_name): + win = 0 + lose = 0 + draw = 0 + goals_scored = 0 + goals_conceded = 0 + for _, row in team_df.iterrows(): + if pd.isna(row['Score']): + continue + try: + home_goals, away_goals = map(int, row['Score'].split('โ€“')) + except: + continue + if row['Home'] == team_name: + goals_scored += home_goals + goals_conceded += away_goals + if home_goals > away_goals: + win += 1 + elif home_goals < away_goals: + lose += 1 + else: + draw += 1 + else: + goals_scored += away_goals + goals_conceded += home_goals + if away_goals > home_goals: + win += 1 + elif away_goals < home_goals: + lose += 1 + else: + draw += 1 + total = win + lose + draw if (win + lose + draw) > 0 else 1 + return { + "win_rate": win / total, + "avg_goals": goals_scored / total, + "avg_conceded": goals_conceded / total + } + + team1_stats = get_stats(team1_recent, team1) + team2_stats = get_stats(team2_recent, team2) + + # Hitung skor prediksi sederhana + team1_strength = (team1_stats["win_rate"] * 0.6 + + team1_stats["avg_goals"] * 0.3 - + team1_stats["avg_conceded"] * 0.1) + team2_strength = (team2_stats["win_rate"] * 0.6 + + team2_stats["avg_goals"] * 0.3 - + team2_stats["avg_conceded"] * 0.1) + + prob_team1 = round((team1_strength / (team1_strength + team2_strength)) * 100, 1) + prob_team2 = round(100 - prob_team1, 1) + + print(f"\n๐Ÿ“Š Probabilitas Prediksi:") + print(f"{team1} menang: {prob_team1}%") + print(f"{team2} menang: {prob_team2}%") + + if prob_team1 > 55: + result = f"๐Ÿ† Prediksi: {team1} Menang" + elif prob_team2 > 55: + result = f"๐Ÿ† Prediksi: {team2} Menang" + else: + result = "๐Ÿค Prediksi: Seri" + + print(f"\n{result}\n") + return result + + +if __name__ == "__main__": + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + df = get_fixtures_table(url) + if df is not None: + predict_match(df, "Brighton", "Wolves") + else: + print("โŒ Gagal mendapatkan data pertandingan.") diff --git a/.history/match_predictor_20251005094146.py b/.history/match_predictor_20251005094146.py new file mode 100644 index 0000000000000000000000000000000000000000..cc1a7f4d84b99eede935441333bbe55924821e22 --- /dev/null +++ b/.history/match_predictor_20251005094146.py @@ -0,0 +1,168 @@ +import requests +import pandas as pd +from bs4 import BeautifulSoup + +# ========================= +# 1๏ธโƒฃ SCRAPER FBREF +# ========================= +def get_fixtures_table(url): + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + headers = {'User-Agent': 'Mozilla/5.0'} + response = requests.get(url, headers=headers) + + if response.status_code != 200: + print("โŒ Gagal mengakses halaman FBref.") + return None + + soup = BeautifulSoup(response.text, 'html.parser') + + # ๐Ÿ” Smart detection: cari div dengan id dinamis yang mengandung jadwal pertandingan + table = None + for div in soup.find_all('div', id=True): + if 'sched' in div['id'] and div.find('table'): + table = div.find('table') + print(f"โœ… Ditemukan tabel jadwal dengan id: {div['id']}") + break + + # ๐Ÿšซ Jika tidak ditemukan + if table is None: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + return None + + # ๐Ÿ“Š Parse tabel ke dataframe + df = pd.read_html(str(table))[0] + print(f"โœ… Table ditemukan dengan shape: {df.shape}") + + # ๐Ÿงน Bersihkan kolom tidak penting + if 'Match Report' in df.columns: + df = df.drop(columns=['Match Report']) + if 'Notes' in df.columns: + df = df.drop(columns=['Notes']) + + # Normalisasi kolom nama + rename_cols = { + 'Home': 'Home', + 'Away': 'Away', + 'Score': 'Score', + 'Date': 'Date' + } + df.rename(columns=rename_cols, inplace=True, errors='ignore') + + return df + + +# ========================= +# 2๏ธโƒฃ PREDIKSI HASIL PERTANDINGAN +# ========================= +def predict_match(df, team1, team2): + print(f"\n๐Ÿ”ฎ Memprediksi hasil: {team1} vs {team2}") + + # Filter hanya baris dengan tim yang valid + df = df.dropna(subset=['Home', 'Away']) + df = df[df['Score'].notna()] # hanya pertandingan yang sudah dimainkan + + # Ambil 5 pertandingan terakhir masing-masing tim + team1_recent = df[(df['Home'] == team1) | (df['Away'] == team1)].tail(5) + team2_recent = df[(df['Home'] == team2) | (df['Away'] == team2)].tail(5) + + # Jika salah satu tim belum punya cukup data + if team1_recent.empty or team2_recent.empty: + print("โš ๏ธ Salah satu tim belum punya cukup data pertandingan.") + return None + + # Fungsi bantu untuk hitung statistik tiap tim + def get_stats(team_df, team_name): + win = lose = draw = 0 + goals_scored = goals_conceded = 0 + + for _, row in team_df.iterrows(): + if pd.isna(row['Score']): + continue + + # Kadang separator skor bisa โ€œโ€“โ€ atau "-" + try: + home_goals, away_goals = map(int, row['Score'].replace('-', 'โ€“').split('โ€“')) + except: + continue + + if row['Home'] == team_name: + goals_scored += home_goals + goals_conceded += away_goals + if home_goals > away_goals: + win += 1 + elif home_goals < away_goals: + lose += 1 + else: + draw += 1 + else: + goals_scored += away_goals + goals_conceded += home_goals + if away_goals > home_goals: + win += 1 + elif away_goals < home_goals: + lose += 1 + else: + draw += 1 + + total = win + lose + draw if (win + lose + draw) > 0 else 1 + return { + "win_rate": win / total, + "avg_goals": goals_scored / total, + "avg_conceded": goals_conceded / total + } + + # Ambil statistik kedua tim + team1_stats = get_stats(team1_recent, team1) + team2_stats = get_stats(team2_recent, team2) + + print(f"\n๐Ÿ“ˆ Statistik {team1} (5 pertandingan terakhir): {team1_stats}") + print(f"๐Ÿ“‰ Statistik {team2} (5 pertandingan terakhir): {team2_stats}") + + # ========================= + # 3๏ธโƒฃ HITUNG PROBABILITAS + # ========================= + team1_strength = ( + team1_stats["win_rate"] * 0.6 + + team1_stats["avg_goals"] * 0.3 - + team1_stats["avg_conceded"] * 0.1 + ) + + team2_strength = ( + team2_stats["win_rate"] * 0.6 + + team2_stats["avg_goals"] * 0.3 - + team2_stats["avg_conceded"] * 0.1 + ) + + prob_team1 = round((team1_strength / (team1_strength + team2_strength)) * 100, 1) + prob_team2 = round(100 - prob_team1, 1) + + print(f"\n๐Ÿ“Š Probabilitas Prediksi:") + print(f"โšฝ {team1} menang: {prob_team1}%") + print(f"โšฝ {team2} menang: {prob_team2}%") + + # ========================= + # 4๏ธโƒฃ TENTUKAN HASIL + # ========================= + if prob_team1 > 55: + result = f"๐Ÿ† Prediksi: {team1} Menang" + elif prob_team2 > 55: + result = f"๐Ÿ† Prediksi: {team2} Menang" + else: + result = "๐Ÿค Prediksi: Seri" + + print(f"\n{result}\n") + return result + + +# ========================= +# 5๏ธโƒฃ MAIN PROGRAM +# ========================= +if __name__ == "__main__": + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + df = get_fixtures_table(url) + if df is not None: + predict_match(df, "Brighton", "Wolves") + else: + print("โŒ Gagal mendapatkan data pertandingan.") diff --git a/.history/match_predictor_20251005094310.py b/.history/match_predictor_20251005094310.py new file mode 100644 index 0000000000000000000000000000000000000000..2631935902a1c497ae3bfef2d0ad0dbda2e31b27 --- /dev/null +++ b/.history/match_predictor_20251005094310.py @@ -0,0 +1,145 @@ +import time +import pandas as pd +from bs4 import BeautifulSoup +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +# =================================================== +# 1๏ธโƒฃ SCRAPER MENGGUNAKAN SELENIUM (ANTI CLOUDFLARE) +# =================================================== +def get_fixtures_table(url): + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + # Setup browser + options = ChromeOptions() + options.add_argument("--headless=new") # Jalankan tanpa GUI + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # Tunggu halaman load + time.sleep(5) + + try: + # Tunggu tabel schedule muncul + wait = WebDriverWait(driver, 15) + div_element = wait.until(EC.presence_of_element_located((By.XPATH, "//div[contains(@id, 'sched') and .//table]"))) + print(f"โœ… Ditemukan tabel jadwal dengan id: {div_element.get_attribute('id')}") + + html = div_element.get_attribute("outerHTML") + soup = BeautifulSoup(html, "html.parser") + table = soup.find("table") + + df = pd.read_html(str(table))[0] + driver.quit() + print(f"โœ… Table ditemukan dengan shape: {df.shape}") + return df + + except TimeoutException: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(driver.page_source) + driver.quit() + return None + + +# =================================================== +# 2๏ธโƒฃ FUNGSI PREDIKSI HASIL PERTANDINGAN +# =================================================== +def predict_match(df, team1, team2): + print(f"\n๐Ÿ”ฎ Memprediksi hasil: {team1} vs {team2}") + + df = df.dropna(subset=['Home', 'Away']) + df = df[df['Score'].notna()] + + # Ambil 5 laga terakhir + team1_recent = df[(df['Home'] == team1) | (df['Away'] == team1)].tail(5) + team2_recent = df[(df['Home'] == team2) | (df['Away'] == team2)].tail(5) + + if team1_recent.empty or team2_recent.empty: + print("โš ๏ธ Salah satu tim belum punya cukup data.") + return None + + def get_stats(team_df, name): + win = lose = draw = 0 + goals_scored = goals_conceded = 0 + for _, row in team_df.iterrows(): + try: + home_goals, away_goals = map(int, str(row['Score']).replace('-', 'โ€“').split('โ€“')) + except: + continue + if row['Home'] == name: + goals_scored += home_goals + goals_conceded += away_goals + if home_goals > away_goals: + win += 1 + elif home_goals < away_goals: + lose += 1 + else: + draw += 1 + else: + goals_scored += away_goals + goals_conceded += home_goals + if away_goals > home_goals: + win += 1 + elif away_goals < home_goals: + lose += 1 + else: + draw += 1 + + total = win + lose + draw if (win + lose + draw) > 0 else 1 + return { + "win_rate": win / total, + "avg_goals": goals_scored / total, + "avg_conceded": goals_conceded / total + } + + # Hitung statistik + t1 = get_stats(team1_recent, team1) + t2 = get_stats(team2_recent, team2) + + print(f"\n๐Ÿ“ˆ Statistik {team1}: {t1}") + print(f"๐Ÿ“‰ Statistik {team2}: {t2}") + + # Hitung kekuatan relatif + s1 = t1['win_rate'] * 0.6 + t1['avg_goals'] * 0.3 - t1['avg_conceded'] * 0.1 + s2 = t2['win_rate'] * 0.6 + t2['avg_goals'] * 0.3 - t2['avg_conceded'] * 0.1 + + p1 = round((s1 / (s1 + s2)) * 100, 1) + p2 = round(100 - p1, 1) + + print(f"\n๐Ÿ“Š Probabilitas Prediksi:") + print(f"โšฝ {team1} menang: {p1}%") + print(f"โšฝ {team2} menang: {p2}%") + + # Hasil + if p1 > 55: + print(f"\n๐Ÿ† Prediksi: {team1} Menang\n") + elif p2 > 55: + print(f"\n๐Ÿ† Prediksi: {team2} Menang\n") + else: + print("\n๐Ÿค Prediksi: Seri\n") + + +# =================================================== +# 3๏ธโƒฃ MAIN PROGRAM +# =================================================== +if __name__ == "__main__": + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + df = get_fixtures_table(url) + if df is not None: + predict_match(df, "Brighton", "Wolves") + else: + print("โŒ Gagal mendapatkan data pertandingan.") diff --git a/.history/match_predictor_20251005094415.py b/.history/match_predictor_20251005094415.py new file mode 100644 index 0000000000000000000000000000000000000000..f65f6d09205d5e0abe2115d3112de4d10fe31380 --- /dev/null +++ b/.history/match_predictor_20251005094415.py @@ -0,0 +1,85 @@ +import requests +from bs4 import BeautifulSoup +import pandas as pd +import numpy as np + +# URL jadwal Premier League +URL = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + +print(f"๐ŸŒ Opening browser to scrape data from: {URL}") + +try: + response = requests.get(URL, headers={"User-Agent": "Mozilla/5.0"}) + response.raise_for_status() +except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + print("โŒ Gagal mendapatkan data pertandingan.") + exit() + +soup = BeautifulSoup(response.text, "html.parser") + +# --- Coba cari tabel utama --- +table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) # pencarian fleksibel +if table is None: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + +if table is None: + print("โš ๏ธ Tidak menemukan tabel dengan ID 'schedule'. Mencoba pencarian adaptif...") + + # cari tabel berdasarkan heading + possible_tables = soup.find_all("table") + for t in possible_tables: + if "Scores & Fixtures" in t.get_text(): + table = t + break + +if table is None: + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + print("โŒ Match table not found. Saved debug_schedule.html for inspection.") + exit() + +# --- Parsing isi tabel --- +rows = table.find_all("tr") +data = [] +for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) > 6: # baris valid + data.append(cols[:8]) # ambil kolom pertama +df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) +print(f"โœ… Match table loaded: {len(df)} rows") + +# --- Filter pertandingan Brighton vs Wolves --- +mask1 = (df["Home"].str.contains("Brighton", na=False) & df["Away"].str.contains("Wolves", na=False)) +mask2 = (df["Home"].str.contains("Wolves", na=False) & df["Away"].str.contains("Brighton", na=False)) +match_df = df[mask1 | mask2] + +if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves.") +else: + print("\n๐Ÿ“… Pertandingan Brighton vs Wolves ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away", "Score"]].to_string(index=False)) + +# --- Prediksi probabilitas sederhana --- +brighton_form = np.random.uniform(0.55, 0.75) # performa +wolves_form = np.random.uniform(0.35, 0.55) + +p_brighton = brighton_form / (brighton_form + wolves_form) +p_wolves = 1 - p_brighton +p_draw = 0.15 + +print("\n๐Ÿ”ฎ Prediksi probabilitas:") +print(f" โšซ Brighton menang: {p_brighton:.2%}") +print(f" ๐ŸŸ  Wolves menang: {p_wolves:.2%}") +print(f" โšช Seri: {p_draw:.2%}") + +# --- Prediksi skor --- +if p_brighton > 0.6: + score_pred = "2 - 1" +elif p_brighton > 0.5: + score_pred = "1 - 0" +else: + score_pred = "1 - 1" + +print(f"\n๐Ÿ“Š Prediksi Skor: Brighton {score_pred} Wolves") +print("โœ… Analisis selesai.") diff --git a/.history/match_predictor_20251005094608.py b/.history/match_predictor_20251005094608.py new file mode 100644 index 0000000000000000000000000000000000000000..a05f4fe8a072480a4b1cbe7d0de5a5e7e1beca7f --- /dev/null +++ b/.history/match_predictor_20251005094608.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brighton", "Wolves", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101017.py b/.history/match_predictor_20251005101017.py new file mode 100644 index 0000000000000000000000000000000000000000..49670fb4072f419d5416b768b62d96632e4a4f06 --- /dev/null +++ b/.history/match_predictor_20251005101017.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("B", "Wolves", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101025.py b/.history/match_predictor_20251005101025.py new file mode 100644 index 0000000000000000000000000000000000000000..0b13a681a8ade68f3b4578b75f02418f1c7d6c32 --- /dev/null +++ b/.history/match_predictor_20251005101025.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Bren", "Wolves", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101029.py b/.history/match_predictor_20251005101029.py new file mode 100644 index 0000000000000000000000000000000000000000..8d13614d3f73de72169dcf4ab2c45c4e5bf190c6 --- /dev/null +++ b/.history/match_predictor_20251005101029.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Wolves", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101032.py b/.history/match_predictor_20251005101032.py new file mode 100644 index 0000000000000000000000000000000000000000..bcad597ebaf18bd21ca795e992986be8b6f6e81e --- /dev/null +++ b/.history/match_predictor_20251005101032.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101036.py b/.history/match_predictor_20251005101036.py new file mode 100644 index 0000000000000000000000000000000000000000..5aaf31f229b7d701bc1462ce00c4d764356d03af --- /dev/null +++ b/.history/match_predictor_20251005101036.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101526.py b/.history/match_predictor_20251005101526.py new file mode 100644 index 0000000000000000000000000000000000000000..dfbdadbeef27c201d5ab1b4d62737627e329a430 --- /dev/null +++ b/.history/match_predictor_20251005101526.py @@ -0,0 +1,181 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { +"Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, +"Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101530.py b/.history/match_predictor_20251005101530.py new file mode 100644 index 0000000000000000000000000000000000000000..5aaf31f229b7d701bc1462ce00c4d764356d03af --- /dev/null +++ b/.history/match_predictor_20251005101530.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Brighton": {"form": 0.68, "goals": 1.9, "concede": 1.1, "home_adv": 1.1}, + "Wolves": {"form": 0.42, "goals": 0.8, "concede": 1.7, "home_adv": 0.9}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101534.py b/.history/match_predictor_20251005101534.py new file mode 100644 index 0000000000000000000000000000000000000000..90a3b5946711ee7889cdb74429f59f4e2d2ecd75 --- /dev/null +++ b/.history/match_predictor_20251005101534.py @@ -0,0 +1,181 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { +"Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, +"Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101537.py b/.history/match_predictor_20251005101537.py new file mode 100644 index 0000000000000000000000000000000000000000..d46dd75a973f678fbd828c3bcffcacbc1fa9158f --- /dev/null +++ b/.history/match_predictor_20251005101537.py @@ -0,0 +1,181 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, +"Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101540.py b/.history/match_predictor_20251005101540.py new file mode 100644 index 0000000000000000000000000000000000000000..5da614226f58878813735f5b161e1f43e618ae95 --- /dev/null +++ b/.history/match_predictor_20251005101540.py @@ -0,0 +1,181 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, + "Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/match_predictor_20251005101542.py b/.history/match_predictor_20251005101542.py new file mode 100644 index 0000000000000000000000000000000000000000..7292d3076e8bd146c6799c1901ab0a0416963f76 --- /dev/null +++ b/.history/match_predictor_20251005101542.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, + "Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/.history/model_report_20251005121637.py b/.history/model_report_20251005121637.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/model_report_20251005121640.py b/.history/model_report_20251005121640.py new file mode 100644 index 0000000000000000000000000000000000000000..d657d5a7a0a00acea458005aaed086782bfaaa83 --- /dev/null +++ b/.history/model_report_20251005121640.py @@ -0,0 +1,141 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report # Pastikan ini di-import +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + +# === BAGIAN INI DIAKTIFKAN UNTUK CEK AKURASI DETAIL === +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) +# ======================================================= + + +# ================================ +# 5๏ธโƒฃ & 6๏ธโƒฃ Fungsi Prediksi & Contoh +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + # ... (sisa fungsi ini tidak berubah) + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Man United", "Spurs": "Tottenham"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/model_report_20251005122108.py b/.history/model_report_20251005122108.py new file mode 100644 index 0000000000000000000000000000000000000000..cabf52f858f1a5f64221a76922fbdd80497ec16d --- /dev/null +++ b/.history/model_report_20251005122108.py @@ -0,0 +1,144 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Memproses Data) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + # === PERBAIKAN BUG H2H === + # Standarisasi nama tim agar sesuai dengan dataset Kaggle + name_map_kaggle = {"Man Utd": "Man United", "Spurs": "Tottenham"} + home_team_kaggle = name_map_kaggle.get(home_team_input, home_team_input) + away_team_kaggle = name_map_kaggle.get(away_team_input, away_team_input) + + def avg_pass_perc(team_name, passing_stats_df): + # Standarisasi nama tim agar sesuai dengan data passing + name_map_pass = {"Man Utd": "Manchester United", "Man City": "Manchester City", "Spurs": "Tottenham Hotspur"} + team_name_pass = name_map_pass.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(team_name_pass, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team_input, passing_df) + away_pass = avg_pass_perc(away_team_input, passing_df) + + # Gunakan nama yang sudah distandarisasi untuk H2H + h2h_stats = calculate_h2h_stats(home_team_kaggle, away_team_kaggle, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team_kaggle} Menang ({h2h_home}), {away_team_kaggle} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_lightgbm_20251005131049.py b/.history/pl-predict_lightgbm_20251005131049.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_lightgbm_20251005131100.py b/.history/pl-predict_lightgbm_20251005131100.py new file mode 100644 index 0000000000000000000000000000000000000000..4d88bccf0b7bad1298ed0265738e929608d553e1 --- /dev/null +++ b/.history/pl-predict_lightgbm_20251005131100.py @@ -0,0 +1,135 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb # Menggunakan LightGBM +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3, 4 (Memuat & Feature Engineering) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1; else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1; else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL LIGHTGBM +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +# === PERUBAHAN: Mengganti model dengan LightGBM === +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") +# =============================================== + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ & 7๏ธโƒฃ Fungsi Prediksi & Contoh (Tidak berubah) +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang."; elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang."; else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_lightgbm_20251005131213.py b/.history/pl-predict_lightgbm_20251005131213.py new file mode 100644 index 0000000000000000000000000000000000000000..05636a26a59343afc870e6e1b97d6cc2c832eb4c --- /dev/null +++ b/.history/pl-predict_lightgbm_20251005131213.py @@ -0,0 +1,147 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Feature Engineering) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (DENGAN PERBAIKAN SYNTAX) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_lightgbm_20251005131216.py b/.history/pl-predict_lightgbm_20251005131216.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_lightgbm_20251005131220.py b/.history/pl-predict_lightgbm_20251005131220.py new file mode 100644 index 0000000000000000000000000000000000000000..05636a26a59343afc870e6e1b97d6cc2c832eb4c --- /dev/null +++ b/.history/pl-predict_lightgbm_20251005131220.py @@ -0,0 +1,147 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Feature Engineering) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (DENGAN PERBAIKAN SYNTAX) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_lightgbm_20251005182746.py b/.history/pl-predict_lightgbm_20251005182746.py new file mode 100644 index 0000000000000000000000000000000000000000..d861e14f7dca58ab699f14052e02a78388d5eaee --- /dev/null +++ b/.history/pl-predict_lightgbm_20251005182746.py @@ -0,0 +1,148 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Feature Engineering) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (DENGAN PERBAIKAN SYNTAX) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) +predict_match("Man Utd", "Tottenham, hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_lightgbm_20251005182751.py b/.history/pl-predict_lightgbm_20251005182751.py new file mode 100644 index 0000000000000000000000000000000000000000..e6c5aa74074404beee28a7faaa8d2c0f3ba96628 --- /dev/null +++ b/.history/pl-predict_lightgbm_20251005182751.py @@ -0,0 +1,148 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Feature Engineering) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (DENGAN PERBAIKAN SYNTAX) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) +predict_match("Man Utd", "Liverpool", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122319.py b/.history/pl-predict_smalldataset_20251005122319.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_smalldataset_20251005122323.py b/.history/pl-predict_smalldataset_20251005122323.py new file mode 100644 index 0000000000000000000000000000000000000000..f626f4253f28b47cb3cfedc398eb4357c19de810 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122323.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122533.py b/.history/pl-predict_smalldataset_20251005122533.py new file mode 100644 index 0000000000000000000000000000000000000000..a2eb585c8851b6e68a1b3333cdd56bde07c598d1 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122533.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsl", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122534.py b/.history/pl-predict_smalldataset_20251005122534.py new file mode 100644 index 0000000000000000000000000000000000000000..f626f4253f28b47cb3cfedc398eb4357c19de810 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122534.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122539.py b/.history/pl-predict_smalldataset_20251005122539.py new file mode 100644 index 0000000000000000000000000000000000000000..ca50217ffecca8ac7a42e54f0dcd72c037acf759 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122539.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122547.py b/.history/pl-predict_smalldataset_20251005122547.py new file mode 100644 index 0000000000000000000000000000000000000000..42f2c4fc29a0db1b0114f76b035722517f712759 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122547.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Man City", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122554.py b/.history/pl-predict_smalldataset_20251005122554.py new file mode 100644 index 0000000000000000000000000000000000000000..6b9b49b78ffe24d99f91fac10b9d0e4d55db2080 --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122554.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Man City", "Liverpool", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122728.py b/.history/pl-predict_smalldataset_20251005122728.py new file mode 100644 index 0000000000000000000000000000000000000000..aec9fbbe99f5727cd4530e5b442812fe9f89ecfb --- /dev/null +++ b/.history/pl-predict_smalldataset_20251005122728.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liu", "Liverpool", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_smalldataset_20251005122740.py b/.history/pl-predict_smalldataset_20251005122740.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_smalldataset_20251005122742.py b/.history/pl-predict_smalldataset_20251005122742.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_with-xgboost_20251005124405.py b/.history/pl-predict_with-xgboost_20251005124405.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/pl-predict_with-xgboost_20251005124409.py b/.history/pl-predict_with-xgboost_20251005124409.py new file mode 100644 index 0000000000000000000000000000000000000000..a892123da585ca4bef0fb9219b0c6b9f13951092 --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005124409.py @@ -0,0 +1,176 @@ +import pandas as pd +import numpy as np +import xgboost as xgb # Menggunakan XGBoost +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + # MENAMBAHKAN KOLOM BARU: Corners, Fouls, Yellow Cards + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', + 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', + 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError as e: + print(f"โŒ Error: Kolom {e} tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ FEATURE ENGINEERING TINGKAT LANJUT: "TEAM FORM" (DENGAN FITUR BARU) +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + # Menambahkan data baru ke dalam list statistik + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) + +# Menghitung rolling average untuk semua fitur baru +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={ + 'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', + 'SOT_For': 'AvgSOT_For_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', + 'Yellows': 'AvgYellows_L5' +}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_For_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] + +# Menggabungkan statistik form kembali ke dataframe utama +hist_df = pd.merge(hist_df, rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').rename(columns=lambda x: x.replace('_L5', '_Home_L5')).drop('Team', axis=1) +hist_df = pd.merge(hist_df, rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').rename(columns=lambda x: x.replace('_L5', '_Away_L5')).drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (Tetap digunakan) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL BARU & FITUR BARU +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 # XGBoost mengharapkan label dari 0, 1, 2 + else: return 2 # Mengubah urutan: 0=Tamu Menang, 1=Tuan Rumah Menang, 2=Seri +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +# FITUR JAUH LEBIH BANYAK +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_For_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_For_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model XGBoost Classifier...") +# MENGGUNAKAN XGBOOST +model = xgb.XGBClassifier(n_estimators=200, random_state=42, objective='multi:softprob', eval_metric='mlogloss') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model XGBoost...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +# Sesuaikan target_names dengan urutan baru: 0=Tamu Menang, 1=Tuan Rumah Menang, 2=Seri +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], + home_form['AvgSOT_For_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], + away_form['AvgSOT_For_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0] + ] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})") + print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + + input_features = np.array([form_values + h2h_values]) + probs = model.predict_proba(input_features)[0] + # Sesuaikan urutan probabilitas dengan urutan label XGBoost: 0=Tamu, 1=Tuan Rumah, 2=Seri + p_away, p_home, p_draw = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005124703.py b/.history/pl-predict_with-xgboost_20251005124703.py new file mode 100644 index 0000000000000000000000000000000000000000..040ed9308db56c4fab386e65a665443fbfa4715f --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005124703.py @@ -0,0 +1,179 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', + 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', + 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError as e: + print(f"โŒ Error: Kolom {e} tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ FEATURE ENGINEERING TINGKAT LANJUT: "TEAM FORM" (VERSI PERBAIKAN FINAL) +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) + +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={ + 'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', + 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', + 'Yellows': 'AvgYellows_L5' +}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] + +# === INI BAGIAN YANG DIPERBAIKI SECARA TOTAL === +# Buat dataframe statistik terpisah untuk Home dan Away SEBELUM di-merge +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() + +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] + +# Merge satu per satu dengan dataframe yang namanya sudah benar +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +# =============================================== + +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL BARU & FITUR BARU +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model XGBoost Classifier...") +model = xgb.XGBClassifier(n_estimators=200, random_state=42, objective='multi:softprob', eval_metric='mlogloss', use_label_encoder=False) +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model XGBoost...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], + home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], + away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0] + ] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})") + print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + + input_features = np.array([form_values + h2h_values]) + probs = model.predict_proba(input_features)[0] + p_away, p_home, p_draw = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005125142.py b/.history/pl-predict_with-xgboost_20251005125142.py new file mode 100644 index 0000000000000000000000000000000000000000..7b7a9ca808b88a4157fab39819408607cb69cd1e --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005125142.py @@ -0,0 +1,145 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler # Import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3, 4 (Memuat & Feature Engineering) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': 'AwayShotsOnTarget', 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan FITUR SCALING +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +# === PERUBAHAN: Menambahkan Feature Scaling === +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") +# ============================================ + +print("\n๐Ÿง  Melatih model XGBoost Classifier dengan data yang sudah di-scale...") +model = xgb.XGBClassifier(n_estimators=200, random_state=42, objective='multi:softprob', eval_metric='mlogloss', use_label_encoder=False) +model.fit(X_train_scaled, y_train) # Melatih model dengan data yang sudah di-scale +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model XGBoost yang terkalibrasi...") +y_pred = model.predict(X_test_scaled) # Mengevaluasi dengan data tes yang sudah di-scale +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): # Tambahkan scaler sebagai argumen + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + + form_values = [...] # ... (kode form values sama) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + + # === PERUBAHAN: Scale input features sebelum prediksi === + input_features_raw = np.array([form_values + h2h_values]) + input_features_scaled = scaler.transform(input_features_raw) + # ======================================================= + + probs = model.predict_proba(input_features_scaled)[0] # Gunakan data yang sudah di-scale + p_away, p_home, p_draw = probs[0], probs[1], probs[2] + + # ... sisa fungsi (print hasil) sama persis ... + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Sekarang kita harus passing 'scaler' yang sudah dilatih ke dalam fungsi +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005125330.py b/.history/pl-predict_with-xgboost_20251005125330.py new file mode 100644 index 0000000000000000000000000000000000000000..ce342ffec55f4a4c0a838b9eba3204591d75a1ab --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005125330.py @@ -0,0 +1,169 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', + 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', + 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + + # === PERBAIKAN: Membersihkan dan Memvalidasi Tipe Data === + stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', + 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] + for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') + + hist_df.dropna(subset=stats_cols, inplace=True) + print("โœ… Data statistik berhasil divalidasi dan dibersihkan.") + # ======================================================= + + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError as e: + print(f"โŒ Error: Kolom {e} tidak ditemukan di 'epl-training.csv'."); exit() + +# ================================ +# Sisa script dari sini ke bawah tidak ada perubahan +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost Classifier...") +model = xgb.XGBClassifier(n_estimators=200, random_state=42, objective='multi:softprob', eval_metric='mlogloss', use_label_encoder=False) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model XGBoost...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], + home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], + away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0] + ] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})") + print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + + input_features_raw = np.array([form_values + h2h_values]) + input_features_scaled = scaler.transform(input_features_raw) + + probs = model.predict_proba(input_features_scaled)[0] + p_away, p_home, p_draw = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005125946.py b/.history/pl-predict_with-xgboost_20251005125946.py new file mode 100644 index 0000000000000000000000000000000000000000..c38924524be14bcff19b69a7d8159cb65a471fe2 --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005125946.py @@ -0,0 +1,144 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3, 4 (Memuat & Feature Engineering) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1; else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1; else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL YANG DI-TUNING +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1; + elif row["HomeGoals"] < row["AwayGoals"]: return 0; + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +# === PERUBAHAN: Menambahkan Hyperparameter untuk Tuning === +model = xgb.XGBClassifier( + n_estimators=1000, # Tambah jumlah pohon + learning_rate=0.01, # Perkecil learning rate agar lebih hati-hati + max_depth=3, # Batasi kedalaman pohon + subsample=0.8, # Gunakan 80% data per pohon + colsample_bytree=0.8, # Gunakan 80% fitur per pohon + gamma=0.1, # Regularisasi untuk mencegah overfitting + random_state=42, + objective='multi:softprob', + eval_metric='mlogloss', + use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") +# ======================================================= + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + + +# ================================ +# 6๏ธโƒฃ & 7๏ธโƒฃ Fungsi Prediksi & Contoh (Tidak berubah) +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + # ... (Fungsi ini sama persis seperti sebelumnya) + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang."; elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang."; else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005130004.py b/.history/pl-predict_with-xgboost_20251005130004.py new file mode 100644 index 0000000000000000000000000000000000000000..c38924524be14bcff19b69a7d8159cb65a471fe2 --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005130004.py @@ -0,0 +1,144 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3, 4 (Memuat & Feature Engineering) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1; else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1; else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL YANG DI-TUNING +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1; + elif row["HomeGoals"] < row["AwayGoals"]: return 0; + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +# === PERUBAHAN: Menambahkan Hyperparameter untuk Tuning === +model = xgb.XGBClassifier( + n_estimators=1000, # Tambah jumlah pohon + learning_rate=0.01, # Perkecil learning rate agar lebih hati-hati + max_depth=3, # Batasi kedalaman pohon + subsample=0.8, # Gunakan 80% data per pohon + colsample_bytree=0.8, # Gunakan 80% fitur per pohon + gamma=0.1, # Regularisasi untuk mencegah overfitting + random_state=42, + objective='multi:softprob', + eval_metric='mlogloss', + use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") +# ======================================================= + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + + +# ================================ +# 6๏ธโƒฃ & 7๏ธโƒฃ Fungsi Prediksi & Contoh (Tidak berubah) +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + # ... (Fungsi ini sama persis seperti sebelumnya) + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang."; elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang."; else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005130137.py b/.history/pl-predict_with-xgboost_20251005130137.py new file mode 100644 index 0000000000000000000000000000000000000000..30d4d926538f5dd08afbce68ea53583fd8cb838a --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005130137.py @@ -0,0 +1,146 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1 & 2 (Memuat & Menyesuaikan Data) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +# ================================ +# Bagian 3 (Feature Engineering 'Team Form') - Tidak Berubah +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# Bagian 4 (Feature Engineering H2H) - DENGAN PERBAIKAN SYNTAX +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1; + elif row["HomeGoals"] < row["AwayGoals"]: return 0; + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +model = xgb.XGBClassifier( + n_estimators=1000, learning_rate=0.01, max_depth=3, subsample=0.8, + colsample_bytree=0.8, gamma=0.1, random_state=42, objective='multi:softprob', + eval_metric='mlogloss', use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang."; elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang."; else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005130241.py b/.history/pl-predict_with-xgboost_20251005130241.py new file mode 100644 index 0000000000000000000000000000000000000000..70c8e9368f06aaedc4aac775535b87fa8c1f450e --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005130241.py @@ -0,0 +1,154 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +# ================================ +# 3๏ธโƒฃ Feature Engineering 'Team Form' +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +model = xgb.XGBClassifier( + n_estimators=1000, learning_rate=0.01, max_depth=3, subsample=0.8, + colsample_bytree=0.8, gamma=0.1, random_state=42, objective='multi:softprob', + eval_metric='mlogloss', use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # === INI BAGIAN YANG DIPERBAIKI === + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + # ================================== + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/pl-predict_with-xgboost_20251005130707.py b/.history/pl-predict_with-xgboost_20251005130707.py new file mode 100644 index 0000000000000000000000000000000000000000..19bb01c0f5856cd6b9c1e153627f37e6c166bfc6 --- /dev/null +++ b/.history/pl-predict_with-xgboost_20251005130707.py @@ -0,0 +1,163 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +# ================================ +# 3๏ธโƒฃ Feature Engineering 'Team Form' +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL YANG DI-TUNING +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +# === PERUBAHAN: Menambahkan Hyperparameter untuk Tuning === +model = xgb.XGBClassifier( + n_estimators=1000, # Tambah jumlah pohon untuk belajar lebih detail + learning_rate=0.01, # Perkecil learning rate agar belajar lebih hati-hati + max_depth=3, # Batasi kedalaman pohon agar tidak terlalu spesifik + subsample=0.8, # Gunakan 80% data per pohon untuk mencegah overfitting + colsample_bytree=0.8, # Gunakan 80% fitur per pohon + gamma=0.1, # Regularisasi untuk "menghukum" model yang terlalu kompleks + random_state=42, + objective='multi:softprob', + eval_metric='mlogloss', + use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") +# ======================================================= + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/.history/player_passing_20251005093257.py b/.history/player_passing_20251005093257.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/player_passing_20251005093301.py b/.history/player_passing_20251005093301.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8c34e056f37bc3fa286b9bd967e9dab9f53d02 --- /dev/null +++ b/.history/player_passing_20251005093301.py @@ -0,0 +1,60 @@ +import pandas as pd +import matplotlib.pyplot as plt + +# --- 1. Load data hasil scraping --- +df = pd.read_csv("premier_league_player_passing.csv") + +print("โœ… Data loaded successfully!") +print(df.head()) + +# --- 2. Bersihkan kolom angka (hapus simbol %, ubah ke float) --- +def clean_numeric(x): + if isinstance(x, str): + x = x.replace('%', '').replace(',', '').strip() + return pd.to_numeric(x, errors='coerce') + +df['Total_Cmp%'] = df['Total_Cmp%'].apply(clean_numeric) +df['Total_Att'] = df['Total_Att'].apply(clean_numeric) +df['Total_Cmp'] = df['Total_Cmp'].apply(clean_numeric) + +# --- 3. 10 pemain dengan akurasi passing tertinggi --- +top_accuracy = df.sort_values('Total_Cmp%', ascending=False).head(10)[['Player', 'Squad', 'Total_Cmp%']] +print("\n๐ŸŽฏ Top 10 Players by Passing Accuracy:") +print(top_accuracy) + +# --- 4. 10 pemain dengan jumlah umpan terbanyak --- +top_volume = df.sort_values('Total_Att', ascending=False).head(10)[['Player', 'Squad', 'Total_Att']] +print("\n๐Ÿ“ˆ Top 10 Players by Total Passes Attempted:") +print(top_volume) + +# --- 5. Rata-rata akurasi passing per klub --- +club_avg = df.groupby('Squad')['Total_Cmp%'].mean().sort_values(ascending=False).reset_index() +print("\n๐ŸŸ๏ธ Average Passing Accuracy per Team:") +print(club_avg.head(10)) + +# --- 6. Simpan hasil analisis --- +output_df = { + 'Top 10 Accuracy': top_accuracy, + 'Top 10 Volume': top_volume, + 'Team Average Accuracy': club_avg +} +with pd.ExcelWriter("premier_league_passing_analysis.xlsx") as writer: + top_accuracy.to_excel(writer, sheet_name='Top_Accuracy', index=False) + top_volume.to_excel(writer, sheet_name='Top_Volume', index=False) + club_avg.to_excel(writer, sheet_name='Team_Average', index=False) + +print("\n๐Ÿ’พ Analysis results saved to premier_league_passing_analysis.xlsx") + +# --- 7. Visualisasi: rata-rata akurasi passing antar tim --- +plt.figure(figsize=(10,6)) +top10_clubs = club_avg.head(10) +plt.barh(top10_clubs['Squad'], top10_clubs['Total_Cmp%'], color='skyblue') +plt.xlabel("Average Passing Accuracy (%)") +plt.ylabel("Club") +plt.title("Top 10 Premier League Teams by Passing Accuracy (2025/2026)") +plt.gca().invert_yaxis() # Biar ranking teratas di atas +plt.tight_layout() +plt.savefig("top10_passing_accuracy.png", dpi=300) +plt.show() + +print("๐Ÿ“Š Visualization saved as top10_passing_accuracy.png") diff --git a/.history/predict-pl-match_otomatis_20251005101717.py b/.history/predict-pl-match_otomatis_20251005101717.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005101722.py b/.history/predict-pl-match_otomatis_20251005101722.py new file mode 100644 index 0000000000000000000000000000000000000000..4ca8c6c858d7cff4de1f95d91a03fb60e7ddca38 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005101722.py @@ -0,0 +1,196 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + +# ========================= +# 1๏ธโƒฃ Scrape Jadwal EPL +# ========================= +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Scraping data from {url} ...") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument( + "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" + ) + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + print("โŒ Table not found") + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + finally: + driver.quit() + + +# ========================= +# 2๏ธโƒฃ Load Player Passing Data +# ========================= +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ premier_league_player_passing.csv not found") + return None + + +# ========================= +# 3๏ธโƒฃ Hitung Statistik Tim Otomatis +# ========================= +def get_team_stats(schedule_df, passing_df): + teams = schedule_df["Home"].unique() + team_stats = {} + + for team in teams: + # Formasi: menang=1, draw=0.5, kalah=0 + team_games = schedule_df[(schedule_df["Home"] == team) | (schedule_df["Away"] == team)] + total_games = len(team_games) + if total_games == 0: + continue + + wins, draws, losses = 0, 0, 0 + goals_for, goals_against = 0, 0 + for _, row in team_games.iterrows(): + if pd.isna(row["Score"]) or "-" not in row["Score"]: + continue + try: + h, a = row["Score"].split("โ€“") + h, a = int(h), int(a) + except: + continue + + if row["Home"] == team: + goals_for += h + goals_against += a + if h > a: + wins += 1 + elif h == a: + draws += 1 + else: + losses += 1 + else: + goals_for += a + goals_against += h + if a > h: + wins += 1 + elif a == h: + draws += 1 + else: + losses += 1 + + form = (wins + 0.5 * draws) / (wins + draws + losses) if (wins + draws + losses) > 0 else 0.5 + avg_goals = goals_for / total_games if total_games > 0 else 1.0 + avg_concede = goals_against / total_games if total_games > 0 else 1.0 + + # Passing accuracy rata-rata pemain + team_pass = passing_df[passing_df["Squad"].str.contains(team, case=False, na=False)] + if not team_pass.empty: + try: + avg_pass = team_pass["Total_Cmp%"].astype(float).mean() / 100 + except: + avg_pass = 0.8 + else: + avg_pass = 0.8 + + # Faktor home advantage default + home_adv = 1.1 + team_stats[team] = {"form": form, "goals": avg_goals, "concede": avg_concede, "home_adv": home_adv, "passing": avg_pass} + + return team_stats + + +# ========================= +# 4๏ธโƒฃ Prediksi Pertandingan +# ========================= +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Predicting match: {home_team} vs {away_team}") + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Missing team data, using defaults") + defaults = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0, "passing": 0.8} + if home_team not in team_stats: + team_stats[home_team] = defaults + if away_team not in team_stats: + team_stats[away_team] = defaults + + ht = team_stats[home_team] + at = team_stats[away_team] + + home_score = ht["form"] * 0.5 + ht["goals"] / (at["concede"] + 0.1) * 0.3 + ht["passing"] * 0.1 + ht["home_adv"] * 0.1 + away_score = at["form"] * 0.5 + at["goals"] / (ht["concede"] + 0.1) * 0.3 + at["passing"] * 0.1 + at["home_adv"] * 0.1 + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilities:") + print(f" ๐Ÿ  {home_team} win : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} win : {p_away:.2%}") + print(f" ๐Ÿค Draw : {p_draw:.2%}") + + exp_home_goals = round(ht["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(at["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Predicted Score: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Final Analysis: {result}") + + +# ========================= +# 5๏ธโƒฃ MAIN +# ========================= +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None or passing_df is None: + print("โŒ Cannot continue, missing data") + return + + team_stats = get_team_stats(schedule_df, passing_df) + + # Ganti nama tim yang ingin diprediksi di sini + home_team = "Brentford" + away_team = "Man City" + + predict_match(home_team, away_team, team_stats) + + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005102725.py b/.history/predict-pl-match_otomatis_20251005102725.py new file mode 100644 index 0000000000000000000000000000000000000000..18812b76cd5a76e592c1eb04cf34621741fdb9bf --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005102725.py @@ -0,0 +1,93 @@ +# predict-pl-match_otomatis.py +import pandas as pd +import numpy as np + +# ================================================== +# 1๏ธโƒฃ Load Player Passing CSV +# ================================================== +def load_passing_data(csv_file="premier_league_player_passing.csv"): + try: + df = pd.read_csv(csv_file) + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print(f"โš ๏ธ File {csv_file} tidak ditemukan.") + return None + +# ================================================== +# 2๏ธโƒฃ Hitung statistik tim otomatis dari CSV +# ================================================== +def get_team_stats(passing_df): + teams = passing_df['Squad'].unique() + stats = {} + for team in teams: + team_df = passing_df[passing_df['Squad'] == team] + if team_df.empty: + continue + try: + avg_cmp_perc = team_df['Total_Cmp%'].astype(float).mean() / 100 + except: + avg_cmp_perc = 0.8 + # Contoh parameter sederhana: form, goals, concede, home_adv + stats[team] = { + "form": avg_cmp_perc, # ganti sesuai logika + "goals": team_df['Total_TotDist'].astype(float).mean()/50, # dummy goals proxy + "concede": 1.0, # bisa diganti CSV hasil gol kebobolan + "home_adv": 1.1 if team in ["Brighton","Man City","Arsenal"] else 1.0 + } + return stats + +# ================================================== +# 3๏ธโƒฃ Prediksi pertandingan +# ================================================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Prediksi pertandingan: {home_team} vs {away_team}") + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + home = team_stats[home_team] + away = team_stats[away_team] + + # Perhitungan skor probabilitas sederhana + home_score = home["form"]*0.5 + home["goals"]/(away["concede"]+0.1)*0.3 + home["home_adv"]*0.2 + away_score = away["form"]*0.5 + away["goals"]/(home["concede"]+0.1)*0.3 + away["home_adv"]*0.2 + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + exp_home_goals = round(home["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(away["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + result = home_team if p_home>p_away else away_team if p_away>p_home else "Seri" + print(f"\n๐Ÿง  Analisis Akhir: {result} berpeluang menang\n") + print("โœ… Prediksi selesai.") + +# ================================================== +# 4๏ธโƒฃ MAIN +# ================================================== +def main(): + passing_df = load_passing_data() + if passing_df is None: + return + + team_stats = get_team_stats(passing_df) + + # Ganti tim di sini + home_team = "Brighton" + away_team = "Wolves" + + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103222.py b/.history/predict-pl-match_otomatis_20251005103222.py new file mode 100644 index 0000000000000000000000000000000000000000..31c6c299c99036ae398543e362aec468999115a4 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103222.py @@ -0,0 +1,128 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException +from webdriver_manager.chrome import ChromeDriverManager + +# --------------------------- +# Fungsi scrape team stats +# --------------------------- +def scrape_team_stats(): + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Scraping team stats from {url} ...") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + driver.get(url) + + try: + wait = WebDriverWait(driver, 15) + div_element = wait.until(EC.presence_of_element_located((By.ID, "div_stats_passing_team"))) + html = div_element.get_attribute("outerHTML") + df = pd.read_html(html)[0] + + # Gabungkan header jika multiindex + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom penting + cols = [c for c in df.columns if any(x in c for x in ['Squad', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Team stats loaded: {df.shape}") + driver.quit() + return df + except TimeoutException: + print("โŒ Failed to load team stats, saving debug file...") + driver.save_screenshot('debug_team_stats.png') + with open('debug_team_stats.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + +# --------------------------- +# Fungsi prediksi skor +# --------------------------- +def predict_match(home_team, away_team, df_stats): + print(f"\n๐Ÿ”ฎ Predicting: {home_team} vs {away_team}") + + def get_team_pass(df, team_name): + row = df[df['Squad'].str.contains(team_name, case=False, na=False)] + if row.empty: return 0.8 # default + try: + return row['Total_Cmp%'].astype(float).mean() / 100 + except: + return 0.8 + + home_pass = get_team_pass(df_stats, home_team) + away_pass = get_team_pass(df_stats, away_team) + + # Contoh data form dan home advantage (dapat disesuaikan) + team_form = { + home_team: {"form": 0.6, "goals": 1.7, "concede": 1.2, "home_adv": 1.1}, + away_team: {"form": 0.55, "goals": 1.5, "concede": 1.3, "home_adv": 1.0}, + } + + # Skor probabilitas + home_score = (team_form[home_team]["form"] * 0.5 + + team_form[home_team]["goals"] / (team_form[away_team]["concede"] + 0.1) * 0.3 + + home_pass * 0.2) + away_score = (team_form[away_team]["form"] * 0.5 + + team_form[away_team]["goals"] / (team_form[home_team]["concede"] + 0.1) * 0.3 + + away_pass * 0.2) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + # Prediksi skor realistis + exp_home_goals = round(team_form[home_team]["goals"] * p_home) + exp_away_goals = round(team_form[away_team]["goals"] * p_away) + + print("\n๐Ÿ“Š Probabilities:") + print(f" ๐Ÿ  {home_team} win: {p_home:.2%}") + print(f" ๐Ÿš— {away_team} win: {p_away:.2%}") + print(f" ๐Ÿค Draw: {p_draw:.2%}") + print(f"\nโšฝ Predicted Score: {home_team} {exp_home_goals} - {exp_away_goals} {away_team}") + +# --------------------------- +# MAIN +# --------------------------- +def main(): + df_stats = scrape_team_stats() + if df_stats is None: + print("โŒ Cannot continue, missing data") + return + + # Masukkan tim yang ingin diprediksi + home_team = "Brighton" + away_team = "Wolves" + + predict_match(home_team, away_team, df_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103523.py b/.history/predict-pl-match_otomatis_20251005103523.py new file mode 100644 index 0000000000000000000000000000000000000000..ecdcfe60d1127bdb2f0a7e250162357068ec9609 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103523.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Brighton" + away_team = "Wolves" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103550.py b/.history/predict-pl-match_otomatis_20251005103550.py new file mode 100644 index 0000000000000000000000000000000000000000..7a57d80ae2a34e4ec653427602abb01729a24056 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103550.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Brentford" + away_team = "Wolves" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103555.py b/.history/predict-pl-match_otomatis_20251005103555.py new file mode 100644 index 0000000000000000000000000000000000000000..57de307dff1bf2c22b5895ed43c1a1e469a04d26 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103555.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Brentford" + away_team = "Man City" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103700.py b/.history/predict-pl-match_otomatis_20251005103700.py new file mode 100644 index 0000000000000000000000000000000000000000..002fa1b13e0640e8efc38bacf0f52b06b3e9c722 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103700.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Aston Villa" + away_team = "Man City" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103704.py b/.history/predict-pl-match_otomatis_20251005103704.py new file mode 100644 index 0000000000000000000000000000000000000000..3841e40cecd931701001ade056cbeba961702d84 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103704.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Aston Villa" + away_team = "Burnsley" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103707.py b/.history/predict-pl-match_otomatis_20251005103707.py new file mode 100644 index 0000000000000000000000000000000000000000..235cc920c5ddc0b2337487b562db27d648f625b6 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103707.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Aston Villa" + away_team = "Burnley" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005103827.py b/.history/predict-pl-match_otomatis_20251005103827.py new file mode 100644 index 0000000000000000000000000000000000000000..e411b227b9b93e0504bafc6f5ae8c34c15ca52a9 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005103827.py @@ -0,0 +1,109 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report + +# ================================ +# 1๏ธโƒฃ Load Data +# ================================ +print("๐Ÿ“Š Loading passing stats...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") + print(f"โœ… Loaded team stats: {passing_df.shape[0]} players") +except FileNotFoundError: + print("โŒ premier_league_player_passing.csv not found") + exit() + +# Contoh data historis untuk model (harus ada CSV historical_matches.csv) +# Columns: Date, Home, Away, HomeGoals, AwayGoals, HomePass%, AwayPass% +print("๐Ÿ“Š Loading historical match data...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Historical matches loaded: {hist_df.shape[0]} rows") +except FileNotFoundError: + print("โŒ historical_matches.csv not found, cannot train model") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing +# ================================ +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: + return 1 # Home Win + elif row["HomeGoals"] < row["AwayGoals"]: + return 2 # Away Win + else: + return 0 # Draw + +hist_df["Result"] = hist_df.apply(get_result, axis=1) +X = hist_df[["HomePass%", "AwayPass%"]] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +# ================================ +# 3๏ธโƒฃ Train Model +# ================================ +model = RandomForestClassifier(n_estimators=200, random_state=42) +model.fit(X_train, y_train) + +# ================================ +# 4๏ธโƒฃ Evaluate Model +# ================================ +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"\nโœ… Model Accuracy: {acc*100:.2f}%") +print("\nClassification Report:\n", classification_report(y_test, y_pred)) + +# ================================ +# 5๏ธโƒฃ Predict Next Match +# ================================ +def predict_match(home_team, away_team, passing_df): + print(f"\n๐Ÿ”ฎ Predicting match: {home_team} vs {away_team}") + + def avg_pass(team_name): + team_df = passing_df[passing_df["Squad"].str.contains(team_name, case=False, na=False)] + if team_df.empty: + return 0.8 # default + try: + return team_df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_pass = avg_pass(home_team) + away_pass = avg_pass(away_team) + + # Prediksi probabilitas dengan model + probs = model.predict_proba([[home_pass, away_pass]])[0] + + p_home = probs[1] + p_away = probs[2] + p_draw = probs[0] + + print("\n๐Ÿ“ˆ Probabilities:") + print(f" ๐Ÿ  {home_team} win : {p_home*100:.2f}%") + print(f" ๐Ÿš— {away_team} win : {p_away*100:.2f}%") + print(f" ๐Ÿค Draw : {p_draw*100:.2f}%") + + # Skor perkiraan (proporsional) + exp_home_goals = round(np.random.poisson(1.5) * p_home * 2) + exp_away_goals = round(np.random.poisson(1.5) * p_away * 2) + + print(f"\nโšฝ Predicted Score: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > max(p_draw, p_away): + result = f"{home_team} likely to win" + elif p_away > max(p_home, p_draw): + result = f"{away_team} likely to win" + else: + result = "Draw likely" + + print(f"\n๐Ÿง  Final Analysis: {result}") + print("โœ… Prediction complete.\n") + +# ================================ +# 6๏ธโƒฃ Run Example +# ================================ +predict_match("Brighton", "Wolves") +predict_match("Brentford", "Man City") diff --git a/.history/predict-pl-match_otomatis_20251005104206.py b/.history/predict-pl-match_otomatis_20251005104206.py new file mode 100644 index 0000000000000000000000000000000000000000..235cc920c5ddc0b2337487b562db27d648f625b6 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104206.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Aston Villa" + away_team = "Burnley" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005104229.py b/.history/predict-pl-match_otomatis_20251005104229.py new file mode 100644 index 0000000000000000000000000000000000000000..93dc95fc83ce503797dcfbd36777d040b2899d2b --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104229.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "N" + away_team = "Burnley" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005104231.py b/.history/predict-pl-match_otomatis_20251005104231.py new file mode 100644 index 0000000000000000000000000000000000000000..ae7ab1bebc3a19d52671e48f313be68fae088baa --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104231.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Newcastle" + away_team = "Burnley" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005104234.py b/.history/predict-pl-match_otomatis_20251005104234.py new file mode 100644 index 0000000000000000000000000000000000000000..b17c7d9deb37f2eaaa659658c2c5d303c91c8225 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104234.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Newcastle" + away_team = "Nott" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005104238.py b/.history/predict-pl-match_otomatis_20251005104238.py new file mode 100644 index 0000000000000000000000000000000000000000..2848bf0e68da074a8d49dc8d50d647cad5c4df22 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104238.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Newcastle" + away_team = "Nottingham Forest" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005104302.py b/.history/predict-pl-match_otomatis_20251005104302.py new file mode 100644 index 0000000000000000000000000000000000000000..e40b38b20a2faf3d69a2177fbc18e5fd63fab968 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005104302.py @@ -0,0 +1,84 @@ +import pandas as pd +import numpy as np + +# =============================== +# 1๏ธโƒฃ Load Data Passing +# =============================== +def load_team_stats(csv_path="premier_league_player_passing.csv"): + df = pd.read_csv(csv_path) + # Ambil rata-rata passing per tim + team_stats = df.groupby("Squad").agg( + Total_Cmp_percent=pd.NamedAgg(column="Total_Cmp%", aggfunc="mean"), + Total_Cmp=pd.NamedAgg(column="Total_Cmp", aggfunc="sum"), + Total_Att=pd.NamedAgg(column="Total_Att", aggfunc="sum") + ).reset_index() + return team_stats + +# =============================== +# 2๏ธโƒฃ Hitung skor prediksi +# =============================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # Ambil data passing + home = team_stats[team_stats["Squad"]==home_team].squeeze() + away = team_stats[team_stats["Squad"]==away_team].squeeze() + + if home.empty or away.empty: + print("โš ๏ธ Salah satu tim tidak ditemukan di data, pakai default") + home_pass = 0.82 + away_pass = 0.78 + else: + home_pass = home["Total_Cmp_percent"] / 100 + away_pass = away["Total_Cmp_percent"] / 100 + + # Asumsi form & home advantage + home_adv = 1.1 + away_adv = 1.0 + home_form = home_pass * 0.6 + 0.4 + away_form = away_pass * 0.6 + 0.4 + + # Skor probabilitas + home_score = home_form * home_adv + away_score = away_form * away_adv + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor (menggunakan proporsi probabilitas + random) + exp_home_goals = max(round(p_home * 3 + np.random.uniform(0, 1)), 0) + exp_away_goals = max(round(p_away * 3 + np.random.uniform(0, 1)), 0) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + # Analisis akhir + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# =============================== +# 3๏ธโƒฃ Main +# =============================== +def main(): + team_stats = load_team_stats() + print(f"๐Ÿ“Š Loaded team stats: {team_stats.shape[0]} teams") + + # Ubah tim sesuai yang ingin diprediksi + home_team = "Nottingham Forest" + away_team = "Chealse" + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict-pl-match_otomatis_20251005105008.py b/.history/predict-pl-match_otomatis_20251005105008.py new file mode 100644 index 0000000000000000000000000000000000000000..2315ce5e85e422be0059079a259454d1fc7b2ca1 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005105008.py @@ -0,0 +1,126 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +# Mengabaikan peringatan yang mungkin muncul saat memproses nama tim +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") + print(f"โœ… Berhasil memuat statistik: {passing_df.shape[0]} pemain") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + print(" Pastikan file tersebut ada di folder yang sama.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan.") + print(" Jalankan script `create_historical_data.py` terlebih dahulu.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +# Fungsi untuk menentukan hasil pertandingan (Menang Tuan Rumah=1, Menang Tamu=2, Seri=0) +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: + return 1 # Home Win + elif row["HomeGoals"] < row["AwayGoals"]: + return 2 # Away Win + else: + return 0 # Draw + +hist_df["Result"] = hist_df.apply(get_result, axis=1) +X = hist_df[["HomePass%", "AwayPass%"]] +y = hist_df["Result"] + +# Membagi data menjadi data training (80%) dan data testing (20%) +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 3๏ธโƒฃ Melatih Model Machine Learning +# ================================ +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +# Model akan belajar dari 80% data historis +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +# ================================ +# 4๏ธโƒฃ Mengevaluasi Kinerja Model +# ================================ +print("\nโš–๏ธ Mengevaluasi akurasi model...") +# Model akan diuji pada 20% data yang belum pernah dilihat sebelumnya +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model: {acc*100:.2f}%") +# print("\nLaporan Klasifikasi:\n", classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi untuk Prediksi Pertandingan +# ================================ +def predict_match(home_team, away_team, passing_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + # Fungsi untuk mencari rata-rata passing % sebuah tim + def avg_pass_perc(team_name): + # Mencari tim berdasarkan nama yang cocok sebagian (e.g., "Man City" akan cocok dengan "Manchester City") + team_df = passing_df[passing_df["Squad"].str.contains(team_name, case=False, na=False)] + if team_df.empty: + print(f" โš ๏ธ Peringatan: Tidak ditemukan data untuk '{team_name}'. Menggunakan rata-rata liga.") + return passing_df["Total_Cmp%"].astype(float).mean() + + # Mengambil rata-rata dari kolom 'Total_Cmp%' + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team) + away_pass = avg_pass_perc(away_team) + + print(f" - Rata-rata Pass {home_team}: {home_pass:.2f}%") + print(f" - Rata-rata Pass {away_team}: {away_pass:.2f}%") + + # Model memprediksi probabilitas berdasarkan data passing + # Format probabilitas: [P(Seri), P(Menang Tuan Rumah), P(Menang Tamu)] + probs = model.predict_proba([[home_pass, away_pass]])[0] + + p_draw = probs[0] + p_home = probs[1] + p_away = probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # Menentukan hasil yang paling mungkin + if p_home > max(p_draw, p_away): + result = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + result = f"{away_team} kemungkinan besar akan menang." + else: + result = "Pertandingan ini kemungkinan besar akan berakhir seri." + + print(f"\n๐Ÿ’ก Kesimpulan: {result}") + print("-" * 40) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Anda bisa mengganti nama tim di bawah ini untuk prediksi lain +predict_match("Brighton", "Wolves", passing_df, model) +predict_match("Brentford", "Man City", passing_df, model) +predict_match("Arsenal", "Manchester Utd", passing_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005105134.py b/.history/predict-pl-match_otomatis_20251005105134.py new file mode 100644 index 0000000000000000000000000000000000000000..cbc5f22a49d4c55e73449c136cc59d3ef2012c83 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005105134.py @@ -0,0 +1,111 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") + print(f"โœ… Berhasil memuat statistik: {passing_df.shape[0]} pemain") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: + return 1 + elif row["HomeGoals"] < row["AwayGoals"]: + return 2 + else: + return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) +X = hist_df[["HomePass%", "AwayPass%"]] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 3๏ธโƒฃ Melatih Model Machine Learning +# ================================ +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +# ================================ +# 4๏ธโƒฃ Mengevaluasi Kinerja Model +# ================================ +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + +# === BAGIAN YANG DITAMBAHKAN UNTUK DETAIL === +print("\nLaporan Detail Kinerja Model:\n") +# Menampilkan laporan presisi, recall, dan f1-score untuk setiap kategori hasil +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) +# ============================================ + +# ================================ +# 5๏ธโƒฃ Fungsi untuk Prediksi Pertandingan +# ================================ +def predict_match(home_team, away_team, passing_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name): + team_df = passing_df[passing_df["Squad"].str.contains(team_name, case=False, na=False)] + if team_df.empty: + print(f" โš ๏ธ Peringatan: Tidak ditemukan data untuk '{team_name}'. Menggunakan rata-rata liga.") + return passing_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team) + away_pass = avg_pass_perc(away_team) + print(f" - Rata-rata Pass {home_team}: {home_pass:.2f}%") + print(f" - Rata-rata Pass {away_team}: {away_pass:.2f}%") + + probs = model.predict_proba([[home_pass, away_pass]])[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): + result = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + result = f"{away_team} kemungkinan besar akan menang." + else: + result = "Pertandingan ini kemungkinan besar akan berakhir seri." + + print(f"\n๐Ÿ’ก Kesimpulan: {result}") + print("-" * 40) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Manchester Utd", passing_df, model) +predict_match("Liverpool", "Man City", passing_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005105511.py b/.history/predict-pl-match_otomatis_20251005105511.py new file mode 100644 index 0000000000000000000000000000000000000000..7f437a032c86a034733a58a02043c805d4ff7abe --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005105511.py @@ -0,0 +1,163 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Feature Engineering: Menambahkan Data Head-to-Head (H2H) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur baru dari data Head-to-Head (H2H)...") + +# Fungsi untuk menghitung statistik H2H antara dua tim +def calculate_h2h_stats(home_team, away_team, all_matches_df): + # Filter semua pertandingan antara kedua tim + h2h_matches = all_matches_df[ + ((all_matches_df['Home'] == home_team) & (all_matches_df['Away'] == away_team)) | + ((all_matches_df['Home'] == away_team) & (all_matches_df['Away'] == home_team)) + ] + + home_team_wins = 0 + away_team_wins = 0 + draws = 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for index, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_team_wins += 1 + else: + away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_team_wins += 1 + else: + home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# Terapkan fungsi ini ke setiap baris di dataframe historis untuk membuat fitur baru +h2h_features = hist_df.apply( + lambda row: calculate_h2h_stats(row['Home'], row['Away'], hist_df), + axis=1 +) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, h2h_features.apply(pd.Series)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: + return 1 + elif row["HomeGoals"] < row["AwayGoals"]: + return 2 + else: + return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +# === PERBARUI FITUR YANG DIGUNAKAN MODEL === +# Sekarang kita gunakan 5 fitur, bukan hanya 2 +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur H2H...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Diperbarui dengan H2H) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + # --- Bagian 1: Dapatkan statistik passing (sama seperti sebelumnya) --- + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # --- Bagian 2: Dapatkan statistik H2H --- + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins = h2h_stats['h2h_home_wins'] + h2h_away_wins = h2h_stats['h2h_away_wins'] + h2h_draws = h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + # --- Bagian 3: Buat prediksi menggunakan SEMUA fitur --- + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): + result = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + result = f"{away_team} kemungkinan besar akan menang." + else: + result = "Pertandingan ini kemungkinan besar akan berakhir seri." + + print(f"\n๐Ÿ’ก Kesimpulan: {result}") + print("-" * 40) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) # Merseyside Derby +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005111716.py b/.history/predict-pl-match_otomatis_20251005111716.py new file mode 100644 index 0000000000000000000000000000000000000000..f626f4253f28b47cb3cfedc398eb4357c19de810 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005111716.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005112129.py b/.history/predict-pl-match_otomatis_20251005112129.py new file mode 100644 index 0000000000000000000000000000000000000000..e7e9695e804072546bada9712d1f0b35fd478d62 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005112129.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches3.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005112641.py b/.history/predict-pl-match_otomatis_20251005112641.py new file mode 100644 index 0000000000000000000000000000000000000000..f626f4253f28b47cb3cfedc398eb4357c19de810 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005112641.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005113549.py b/.history/predict-pl-match_otomatis_20251005113549.py new file mode 100644 index 0000000000000000000000000000000000000000..41043f586840ccfbcf1fc79727fec0aebee18b20 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005113549.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_match_data.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005113950.py b/.history/predict-pl-match_otomatis_20251005113950.py new file mode 100644 index 0000000000000000000000000000000000000000..03f8f6f5467a79dd69b9d18c249c750679666dad --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005113950.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005113955.py b/.history/predict-pl-match_otomatis_20251005113955.py new file mode 100644 index 0000000000000000000000000000000000000000..c772a4c36972907ddca3bde3e25e599842ffb18e --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005113955.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_match.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005113957.py b/.history/predict-pl-match_otomatis_20251005113957.py new file mode 100644 index 0000000000000000000000000000000000000000..f626f4253f28b47cb3cfedc398eb4357c19de810 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005113957.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114208.py b/.history/predict-pl-match_otomatis_20251005114208.py new file mode 100644 index 0000000000000000000000000000000000000000..a006ea8afb31df1fcf0591626bcacba90347f747 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114208.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114221.py b/.history/predict-pl-match_otomatis_20251005114221.py new file mode 100644 index 0000000000000000000000000000000000000000..19509d8a7445236898f90655fa35af0e522defc5 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114221.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Brighton", "Man Utd", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114228.py b/.history/predict-pl-match_otomatis_20251005114228.py new file mode 100644 index 0000000000000000000000000000000000000000..5930bfe395524a629165f6e76efa5e4646b961e0 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114228.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Brighton", "Volves", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114238.py b/.history/predict-pl-match_otomatis_20251005114238.py new file mode 100644 index 0000000000000000000000000000000000000000..79d6b9eb02c2edbeefd826a35b367f0068a59f4f --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114238.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Brighton", "Brig", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114245.py b/.history/predict-pl-match_otomatis_20251005114245.py new file mode 100644 index 0000000000000000000000000000000000000000..fa17a27b14171acdcdc2a01af74578e13e69cd0b --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114245.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Brighton", "Brighton", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114252.py b/.history/predict-pl-match_otomatis_20251005114252.py new file mode 100644 index 0000000000000000000000000000000000000000..1efd734f50801e52b984c4a622ae5d10b8a517e4 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114252.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Wolves", "Brighton", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114303.py b/.history/predict-pl-match_otomatis_20251005114303.py new file mode 100644 index 0000000000000000000000000000000000000000..814e975bca0ba3683821b7cb004968f1aa2cd123 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114303.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Everton", "Everton", passing_df, hist_df, model) +predict_match("Wolves", "Brighton", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114311.py b/.history/predict-pl-match_otomatis_20251005114311.py new file mode 100644 index 0000000000000000000000000000000000000000..00dc67a5069d252fcdd40d96d33af521b9805395 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114311.py @@ -0,0 +1,155 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Everton", "Crystal Palace", passing_df, hist_df, model) +predict_match("Wolves", "Brighton", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005114744.py b/.history/predict-pl-match_otomatis_20251005114744.py new file mode 100644 index 0000000000000000000000000000000000000000..79cee6b53eb61af75fc411d1a3d6f647df824a7b --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005114744.py @@ -0,0 +1,178 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + # PASTIKAN ANDA MENGGUNAKAN DATASET YANG LENGKAP (1520+ LAGA) + hist_df = pd.read_csv("historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + if hist_df.shape[0] < 1000: + print(" โš ๏ธ PERINGATAN: Dataset historis Anda sangat kecil. Akurasi model mungkin rendah.") + print(" Pastikan 'historical_matches.csv' adalah file yang berisi 1500+ pertandingan.") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + exit() + + +# ================================ +# 2๏ธโƒฃ Feature Engineering H2H (Tanpa Kebocoran Data) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception: + print("โŒ Gagal memproses kolom tanggal. Mengabaikan pengurutan tanggal.") + +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_team_wins, away_team_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Diperbarui untuk Mengembalikan Hasil) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + # Fungsi untuk mencari rata-rata passing + def avg_pass_perc(team_name, passing_stats_df): + name_map = { + "Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur", + "Nott'ham Forest": "Nottingham Forest", "Wolves": "Wolverhampton Wanderers", + "West Ham": "West Ham United", "AFC Bournemouth": "Bournemouth" + } + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + + # Dapatkan statistik H2H + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + # Buat prediksi + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + # Tentukan kesimpulan + if p_home > max(p_draw, p_away): conclusion = f"{home_team} Menang" + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} Menang" + else: conclusion = "Seri" + + # Kembalikan hasil dalam bentuk dictionary agar bisa dikumpulkan + return { + "Tim Tuan Rumah": home_team, + "Tim Tamu": away_team, + "Peluang Menang Tuan Rumah (%)": round(p_home * 100, 2), + "Peluang Seri (%)": round(p_draw * 100, 2), + "Peluang Menang Tamu (%)": round(p_away * 100, 2), + "Kesimpulan": conclusion + } + +# ================================ +# 6๏ธโƒฃ BAGIAN BARU: Prediksi Massal dari File Jadwal +# ================================ +print("\n" + "="*40) +print("๐Ÿ”ฎ MEMULAI PREDIKSI MASSAL DARI FILE JADWAL ๐Ÿ”ฎ") +print("="*40) + +try: + schedule_df = pd.read_csv("epl-test.csv") + print(f"โœ… Berhasil memuat {len(schedule_df)} pertandingan dari 'epl-test.csv'") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-test.csv' tidak ditemukan. Prediksi massal dibatalkan.") + schedule_df = pd.DataFrame() # Buat dataframe kosong jika file tidak ada + +predictions = [] +if not schedule_df.empty: + # Loop melalui setiap baris di file jadwal + for index, row in schedule_df.iterrows(): + home = row['HomeTeam'] + away = row['AwayTeam'] + print(f" - Memprediksi: {home} vs {away}") + + # Panggil fungsi prediksi untuk setiap pertandingan + prediction_result = predict_match(home, away, passing_df, hist_df, model) + predictions.append(prediction_result) + + # Ubah daftar hasil prediksi menjadi DataFrame yang rapi + predictions_df = pd.DataFrame(predictions) + + # Tampilkan hasil di layar + print("\n\nโœ… PREDIKSI SELESAI. HASIL LENGKAP:\n") + print(predictions_df.to_string()) + + # Simpan hasil ke file CSV baru + try: + output_filename = "hasil_prediksi_epl.csv" + predictions_df.to_csv(output_filename, index=False) + print(f"\n\n๐Ÿ’พ Hasil prediksi telah disimpan ke file: '{output_filename}'") + except Exception as e: + print(f"\n\nโŒ Gagal menyimpan hasil prediksi ke file CSV: {e}") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005115420.py b/.history/predict-pl-match_otomatis_20251005115420.py new file mode 100644 index 0000000000000000000000000000000000000000..b57008e1a00f7cf18807370b446808b5c7d0bd0d --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005115420.py @@ -0,0 +1,176 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + # --- PERUBAHAN 1: Membaca file epl-training.csv --- + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +# --- PERUBAHAN 2: Mengubah nama kolom agar sesuai --- +# FTHG = Full Time Home Goals, FTAG = Full Time Away Goals +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', + 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', + 'FTAG': 'AwayGoals' + }, inplace=True) + + # --- PERUBAHAN 3: Menyesuaikan format tanggal --- + # Mengubah format DD/MM/YYYY menjadi format standar yang bisa diurutkan + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + # === CATATAN PENTING TENTANG FITUR PASSING === + # Dataset historis ini sangat besar, namun data passing (passing_df) kemungkinan + # hanya dari musim terbaru. Jadi kita akan membuat fitur passing berdasarkan data terbaru ini. + # Untuk model yang lebih canggih, kita bisa menggunakan fitur lain dari dataset Kaggle + # seperti Shots (HS, AS) atau Corners (HC, AC). + + # Membuat fitur HomePass% dan AwayPass% untuk data historis + # Ini adalah sebuah penyederhanaan, kita asumsikan %passing tim relatif stabil + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + # Isi data tim yang tidak ada di data passing (misal, tim lama) dengan rata-rata + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan (misal: 'HomeTeam', 'FTHG') tidak ditemukan di 'epl-training.csv'.") + exit() + + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H (Logika Tetap Sama) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_team_wins, away_team_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Sisa Script (Training, Evaluasi, Prediksi Massal) tidak berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +print("\n" + "="*40) +print("๐Ÿ”ฎ MEMULAI PREDIKSI MASSAL DARI FILE JADWAL ๐Ÿ”ฎ") +print("="*40) + +def predict_match(home_team, away_team, passing_df, historical_df, model): + # ... (Fungsi predict_match sama persis seperti sebelumnya) + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur", "Nott'ham Forest": "Nottingham Forest", "Wolves": "Wolverhampton Wanderers", "West Ham": "West Ham United", "AFC Bournemouth": "Bournemouth"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + if p_home > max(p_draw, p_away): conclusion = f"{home_team} Menang" + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} Menang" + else: conclusion = "Seri" + return {"Tim Tuan Rumah": home_team, "Tim Tamu": away_team, "Peluang Menang Tuan Rumah (%)": round(p_home * 100, 2), "Peluang Seri (%)": round(p_draw * 100, 2), "Peluang Menang Tamu (%)": round(p_away * 100, 2), "Kesimpulan": conclusion} + +try: + schedule_df = pd.read_csv("epl-test.csv") + print(f"โœ… Berhasil memuat {len(schedule_df)} pertandingan dari 'epl-test.csv'") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-test.csv' tidak ditemukan. Prediksi massal dibatalkan.") + schedule_df = pd.DataFrame() + +predictions = [] +if not schedule_df.empty: + for index, row in schedule_df.iterrows(): + home = row['HomeTeam'] + away = row['AwayTeam'] + print(f" - Memprediksi: {home} vs {away}") + prediction_result = predict_match(home, away, passing_df, hist_df, model) + predictions.append(prediction_result) + predictions_df = pd.DataFrame(predictions) + print("\n\nโœ… PREDIKSI SELESAI. HASIL LENGKAP:\n") + print(predictions_df.to_string()) + try: + output_filename = "hasil_prediksi_epl.csv" + predictions_df.to_csv(output_filename, index=False) + print(f"\n\n๐Ÿ’พ Hasil prediksi telah disimpan ke file: '{output_filename}'") + except Exception as e: + print(f"\n\nโŒ Gagal menyimpan hasil prediksi ke file CSV: {e}") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005115543.py b/.history/predict-pl-match_otomatis_20251005115543.py new file mode 100644 index 0000000000000000000000000000000000000000..9de27d5e52e0149d2abf65f4e361ecc53111395b --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005115543.py @@ -0,0 +1,176 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + # --- PERUBAHAN 1: Membaca file epl-training.csv --- + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +# --- PERUBAHAN 2: Mengubah nama kolom agar sesuai --- +# FTHG = Full Time Home Goals, FTAG = Full Time Away Goals +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', + 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', + 'FTAG': 'AwayGoals' + }, inplace=True) + + # --- PERUBAHAN 3: Menyesuaikan format tanggal --- + # Mengubah format DD/MM/YYYY menjadi format standar yang bisa diurutkan + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + # === CATATAN PENTING TENTANG FITUR PASSING === + # Dataset historis ini sangat besar, namun data passing (passing_df) kemungkinan + # hanya dari musim terbaru. Jadi kita akan membuat fitur passing berdasarkan data terbaru ini. + # Untuk model yang lebih canggih, kita bisa menggunakan fitur lain dari dataset Kaggle + # seperti Shots (HS, AS) atau Corners (HC, AC). + + # Membuat fitur HomePass% dan AwayPass% untuk data historis + # Ini adalah sebuah penyederhanaan, kita asumsikan %passing tim relatif stabil + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + # Isi data tim yang tidak ada di data passing (misal, tim lama) dengan rata-rata + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan (misal: 'HomeTeam', 'FTHG') tidak ditemukan di 'epl-training.csv'.") + exit() + + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H (Logika Tetap Sama) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_team_wins, away_team_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Sisa Script (Training, Evaluasi, Prediksi Massal) tidak berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +print("\n" + "="*40) +print("๐Ÿ”ฎ MEMULAI PREDIKSI MASSAL DARI FILE JADWAL ๐Ÿ”ฎ") +print("="*40) + +def predict_match(home_team, away_team, passing_df, historical_df, model): + # ... (Fungsi predict_match sama persis seperti sebelumnya) + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur", "Nott'ham Forest": "Nottingham Forest", "Wolves": "Wolverhampton Wanderers", "West Ham": "West Ham United", "AFC Bournemouth": "Bournemouth"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + if p_home > max(p_draw, p_away): conclusion = f"{home_team} Menang" + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} Menang" + else: conclusion = "Seri" + return {"Tim Tuan Rumah": home_team, "Tim Tamu": away_team, "Peluang Menang Tuan Rumah (%)": round(p_home * 100, 2), "Peluang Seri (%)": round(p_draw * 100, 2), "Peluang Menang Tamu (%)": round(p_away * 100, 2), "Kesimpulan": conclusion} + +try: + schedule_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat {len(schedule_df)} pertandingan dari 'epl-test.csv'") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-test.csv' tidak ditemukan. Prediksi massal dibatalkan.") + schedule_df = pd.DataFrame() + +predictions = [] +if not schedule_df.empty: + for index, row in schedule_df.iterrows(): + home = row['HomeTeam'] + away = row['AwayTeam'] + print(f" - Memprediksi: {home} vs {away}") + prediction_result = predict_match(home, away, passing_df, hist_df, model) + predictions.append(prediction_result) + predictions_df = pd.DataFrame(predictions) + print("\n\nโœ… PREDIKSI SELESAI. HASIL LENGKAP:\n") + print(predictions_df.to_string()) + try: + output_filename = "hasil_prediksi_epl.csv" + predictions_df.to_csv(output_filename, index=False) + print(f"\n\n๐Ÿ’พ Hasil prediksi telah disimpan ke file: '{output_filename}'") + except Exception as e: + print(f"\n\nโŒ Gagal menyimpan hasil prediksi ke file CSV: {e}") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005115548.py b/.history/predict-pl-match_otomatis_20251005115548.py new file mode 100644 index 0000000000000000000000000000000000000000..4c1a257ad01fbade6ad0a7a6fe449a60ed0acfaf --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005115548.py @@ -0,0 +1,176 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + # --- PERUBAHAN 1: Membaca file epl-training.csv --- + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +# --- PERUBAHAN 2: Mengubah nama kolom agar sesuai --- +# FTHG = Full Time Home Goals, FTAG = Full Time Away Goals +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', + 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', + 'FTAG': 'AwayGoals' + }, inplace=True) + + # --- PERUBAHAN 3: Menyesuaikan format tanggal --- + # Mengubah format DD/MM/YYYY menjadi format standar yang bisa diurutkan + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + # === CATATAN PENTING TENTANG FITUR PASSING === + # Dataset historis ini sangat besar, namun data passing (passing_df) kemungkinan + # hanya dari musim terbaru. Jadi kita akan membuat fitur passing berdasarkan data terbaru ini. + # Untuk model yang lebih canggih, kita bisa menggunakan fitur lain dari dataset Kaggle + # seperti Shots (HS, AS) atau Corners (HC, AC). + + # Membuat fitur HomePass% dan AwayPass% untuk data historis + # Ini adalah sebuah penyederhanaan, kita asumsikan %passing tim relatif stabil + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + # Isi data tim yang tidak ada di data passing (misal, tim lama) dengan rata-rata + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan (misal: 'HomeTeam', 'FTHG') tidak ditemukan di 'epl-training.csv'.") + exit() + + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H (Logika Tetap Sama) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_team_wins, away_team_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Sisa Script (Training, Evaluasi, Prediksi Massal) tidak berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +print("\n" + "="*40) +print("๐Ÿ”ฎ MEMULAI PREDIKSI MASSAL DARI FILE JADWAL ๐Ÿ”ฎ") +print("="*40) + +def predict_match(home_team, away_team, passing_df, historical_df, model): + # ... (Fungsi predict_match sama persis seperti sebelumnya) + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur", "Nott'ham Forest": "Nottingham Forest", "Wolves": "Wolverhampton Wanderers", "West Ham": "West Ham United", "AFC Bournemouth": "Bournemouth"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + if p_home > max(p_draw, p_away): conclusion = f"{home_team} Menang" + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} Menang" + else: conclusion = "Seri" + return {"Tim Tuan Rumah": home_team, "Tim Tamu": away_team, "Peluang Menang Tuan Rumah (%)": round(p_home * 100, 2), "Peluang Seri (%)": round(p_draw * 100, 2), "Peluang Menang Tamu (%)": round(p_away * 100, 2), "Kesimpulan": conclusion} + +try: + schedule_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat {len(schedule_df)} pertandingan dari 'epl-test.csv'") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan. Prediksi massal dibatalkan.") + schedule_df = pd.DataFrame() + +predictions = [] +if not schedule_df.empty: + for index, row in schedule_df.iterrows(): + home = row['HomeTeam'] + away = row['AwayTeam'] + print(f" - Memprediksi: {home} vs {away}") + prediction_result = predict_match(home, away, passing_df, hist_df, model) + predictions.append(prediction_result) + predictions_df = pd.DataFrame(predictions) + print("\n\nโœ… PREDIKSI SELESAI. HASIL LENGKAP:\n") + print(predictions_df.to_string()) + try: + output_filename = "hasil_prediksi_epl.csv" + predictions_df.to_csv(output_filename, index=False) + print(f"\n\n๐Ÿ’พ Hasil prediksi telah disimpan ke file: '{output_filename}'") + except Exception as e: + print(f"\n\nโŒ Gagal menyimpan hasil prediksi ke file CSV: {e}") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005115555.py b/.history/predict-pl-match_otomatis_20251005115555.py new file mode 100644 index 0000000000000000000000000000000000000000..7b24160468ecacb61808c2c9a9671408485d877f --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005115555.py @@ -0,0 +1,176 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + # --- PERUBAHAN 1: Membaca file epl-training.csv --- + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +# --- PERUBAHAN 2: Mengubah nama kolom agar sesuai --- +# FTHG = Full Time Home Goals, FTAG = Full Time Away Goals +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', + 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', + 'FTAG': 'AwayGoals' + }, inplace=True) + + # --- PERUBAHAN 3: Menyesuaikan format tanggal --- + # Mengubah format DD/MM/YYYY menjadi format standar yang bisa diurutkan + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + # === CATATAN PENTING TENTANG FITUR PASSING === + # Dataset historis ini sangat besar, namun data passing (passing_df) kemungkinan + # hanya dari musim terbaru. Jadi kita akan membuat fitur passing berdasarkan data terbaru ini. + # Untuk model yang lebih canggih, kita bisa menggunakan fitur lain dari dataset Kaggle + # seperti Shots (HS, AS) atau Corners (HC, AC). + + # Membuat fitur HomePass% dan AwayPass% untuk data historis + # Ini adalah sebuah penyederhanaan, kita asumsikan %passing tim relatif stabil + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + # Isi data tim yang tidak ada di data passing (misal, tim lama) dengan rata-rata + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan (misal: 'HomeTeam', 'FTHG') tidak ditemukan di 'epl-training.csv'.") + exit() + + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H (Logika Tetap Sama) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_team_wins, away_team_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Sisa Script (Training, Evaluasi, Prediksi Massal) tidak berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +print("\n" + "="*40) +print("๐Ÿ”ฎ MEMULAI PREDIKSI MASSAL DARI FILE JADWAL ๐Ÿ”ฎ") +print("="*40) + +def predict_match(home_team, away_team, passing_df, historical_df, model): + # ... (Fungsi predict_match sama persis seperti sebelumnya) + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur", "Nott'ham Forest": "Nottingham Forest", "Wolves": "Wolverhampton Wanderers", "West Ham": "West Ham United", "AFC Bournemouth": "Bournemouth"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + if p_home > max(p_draw, p_away): conclusion = f"{home_team} Menang" + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} Menang" + else: conclusion = "Seri" + return {"Tim Tuan Rumah": home_team, "Tim Tamu": away_team, "Peluang Menang Tuan Rumah (%)": round(p_home * 100, 2), "Peluang Seri (%)": round(p_draw * 100, 2), "Peluang Menang Tamu (%)": round(p_away * 100, 2), "Kesimpulan": conclusion} + +try: + schedule_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat {len(schedule_df)} pertandingan dari 'epl-training.csv'") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan. Prediksi massal dibatalkan.") + schedule_df = pd.DataFrame() + +predictions = [] +if not schedule_df.empty: + for index, row in schedule_df.iterrows(): + home = row['HomeTeam'] + away = row['AwayTeam'] + print(f" - Memprediksi: {home} vs {away}") + prediction_result = predict_match(home, away, passing_df, hist_df, model) + predictions.append(prediction_result) + predictions_df = pd.DataFrame(predictions) + print("\n\nโœ… PREDIKSI SELESAI. HASIL LENGKAP:\n") + print(predictions_df.to_string()) + try: + output_filename = "hasil_prediksi_epl.csv" + predictions_df.to_csv(output_filename, index=False) + print(f"\n\n๐Ÿ’พ Hasil prediksi telah disimpan ke file: '{output_filename}'") + except Exception as e: + print(f"\n\nโŒ Gagal menyimpan hasil prediksi ke file CSV: {e}") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005120904.py b/.history/predict-pl-match_otomatis_20251005120904.py new file mode 100644 index 0000000000000000000000000000000000000000..69471c3c424de2b465a40345757c774e0a1b3786 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005120904.py @@ -0,0 +1,152 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +# Mengabaikan peringatan teknis dari Pandas yang tidak krusial +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Ganti nama tim di bawah ini untuk memprediksi pertandingan lain +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005121414.py b/.history/predict-pl-match_otomatis_20251005121414.py new file mode 100644 index 0000000000000000000000000000000000000000..a8c58d5097b654e850627846dd81a183df36eed7 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005121414.py @@ -0,0 +1,152 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +# Mengabaikan peringatan teknis dari Pandas yang tidak krusial +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Ganti nama tim di bawah ini untuk memprediksi pertandingan lain +predict_match("Wolves", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005121419.py b/.history/predict-pl-match_otomatis_20251005121419.py new file mode 100644 index 0000000000000000000000000000000000000000..a5d025fc4cc009c65db265a4601730f8edf1a044 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005121419.py @@ -0,0 +1,152 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +# Mengabaikan peringatan teknis dari Pandas yang tidak krusial +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Ganti nama tim di bawah ini untuk memprediksi pertandingan lain +predict_match("Wolves", "Bright", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005121421.py b/.history/predict-pl-match_otomatis_20251005121421.py new file mode 100644 index 0000000000000000000000000000000000000000..c4d7b57c5f3051877218327940f9516889a59577 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005121421.py @@ -0,0 +1,152 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +# Mengabaikan peringatan teknis dari Pandas yang tidak krusial +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") + +try: + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' + }, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} + +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +# Ganti nama tim di bawah ini untuk memprediksi pertandingan lain +predict_match("Wolves", "Brighton", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005121559.py b/.history/predict-pl-match_otomatis_20251005121559.py new file mode 100644 index 0000000000000000000000000000000000000000..e0e805db9678f2a4a6ae223e8f36773cfa518594 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005121559.py @@ -0,0 +1,145 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Memproses Data) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +# --- PERUBAHAN 2: Menghapus class_weight='balanced' untuk eksperimen --- +model = RandomForestClassifier(n_estimators=200, random_state=42) # Dihapus: class_weight='balanced' +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + # --- PERUBAHAN 1: Standarisasi nama tim di awal --- + name_map = { + "Man City": "Manchester City", "Man Utd": "Man United", "Spurs": "Tottenham" + # Tambahkan nama lain jika perlu, misal "Nott'ham Forest": "Nott'm Forest" + } + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + # ================================================= + + def avg_pass_perc(team_name, passing_stats_df): + # Gunakan nama yang sudah distandarisasi + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(team_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + + # Gunakan nama yang sudah distandarisasi untuk H2H + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005122038.py b/.history/predict-pl-match_otomatis_20251005122038.py new file mode 100644 index 0000000000000000000000000000000000000000..076fd620b8a1811fcd67cd8e82a05ca16d4fac83 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005122038.py @@ -0,0 +1,153 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ, 2๏ธโƒฃ, 3๏ธโƒฃ (Memuat & Memproses Data) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +# Mengembalikan class_weight='balanced' karena biasanya memberikan hasil yang lebih baik +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") + +# === PERUBAHAN 2: Laporan akurasi detail diaktifkan kembali === +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) +# ============================================================= + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + # === PERUBAHAN 1: Peta nama tim disesuaikan dengan dataset Kaggle === + name_map = { + # Input dari kita -> Nama di file epl-training.csv + "Man Utd": "Man United", + "Spurs": "Tottenham" + # "Man City" dihapus dari sini, karena nama input dan nama di file sudah sama + } + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + # ===================================================================== + + def avg_pass_perc(team_name, passing_stats_df): + # Kita gunakan nama standar dari data passing, karena sumbernya berbeda + pass_name_map = {"Man Utd": "Manchester United", "Man City": "Manchester City", "Spurs": "Tottenham Hotspur"} + pass_standard_name = pass_name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(pass_standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team_input, passing_df) # Gunakan input asli untuk passing + away_pass = avg_pass_perc(away_team_input, passing_df) + + # Gunakan nama yang sudah distandarisasi untuk H2H + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005122714.py b/.history/predict-pl-match_otomatis_20251005122714.py new file mode 100644 index 0000000000000000000000000000000000000000..0ff9b9ed4982625021abf7baa3eac42edbf3a1d9 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005122714.py @@ -0,0 +1,154 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data Historis dari Kaggle +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +# ================================ +# 2๏ธโƒฃ Preprocessing & Penyesuaian Data +# ================================ +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + # Memilih hanya kolom yang akan kita gunakan + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] + hist_df = hist_df[relevant_cols] + + hist_df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' + }, inplace=True) + + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan (misal: 'HomeTeam', 'HS') tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ Feature Engineering H2H (Tetap kita gunakan karena penting) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model dengan FITUR BARU +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +# === FITUR BARU & LEBIH BAIK === +features = [ + 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier dengan fitur baru...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi yang Lebih Cerdas +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + name_map_kaggle = {"Man Utd": "Man United", "Spurs": "Tottenham"} + home_team = name_map_kaggle.get(home_team_input, home_team_input) + away_team = name_map_kaggle.get(away_team_input, away_team_input) + + # Hitung rata-rata statistik historis untuk kedua tim + home_stats = historical_df[historical_df['Home'] == home_team] + away_stats = historical_df[historical_df['Away'] == away_team] + + avg_home_shots = home_stats['HomeShots'].mean() + avg_away_shots = away_stats['AwayShots'].mean() + avg_home_sot = home_stats['HomeShotsOnTarget'].mean() + avg_away_sot = away_stats['AwayShotsOnTarget'].mean() + + print(f" - Rata-rata Shots (Kandang): {home_team} ({avg_home_shots:.2f}), (Tandang): {away_team} ({avg_away_shots:.2f})") + print(f" - Rata-rata SOT (Kandang): {home_team} ({avg_home_sot:.2f}), (Tandang): {away_team} ({avg_away_sot:.2f})") + + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H dari dataset: {home_team} Menang ({h2h_home}), {away_team} Menang ({h2h_away}), Seri ({h2h_draw})") + + # Gunakan rata-rata stats sebagai input untuk prediksi + input_features = np.array([[ + avg_home_shots, avg_away_shots, avg_home_sot, avg_away_sot, + h2h_home, h2h_away, h2h_draw + ]]) + + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005122927.py b/.history/predict-pl-match_otomatis_20251005122927.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005122935.py b/.history/predict-pl-match_otomatis_20251005122935.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005122942.py b/.history/predict-pl-match_otomatis_20251005122942.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005122955.py b/.history/predict-pl-match_otomatis_20251005122955.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123003.py b/.history/predict-pl-match_otomatis_20251005123003.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123010.py b/.history/predict-pl-match_otomatis_20251005123010.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123019.py b/.history/predict-pl-match_otomatis_20251005123019.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123026.py b/.history/predict-pl-match_otomatis_20251005123026.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123027.py b/.history/predict-pl-match_otomatis_20251005123027.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123034.py b/.history/predict-pl-match_otomatis_20251005123034.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_20251005123503.py b/.history/predict-pl-match_otomatis_20251005123503.py new file mode 100644 index 0000000000000000000000000000000000000000..bce857281572838c34377d97342645c989cbf9a7 --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005123503.py @@ -0,0 +1,173 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ FEATURE ENGINEERING TINGKAT LANJUT: "TEAM FORM" +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") + +# Mengubah dataframe menjadi format yang lebih mudah untuk dihitung per tim +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'ShotsAgainst': row['AwayShots'], 'SOT_For': row['HomeShotsOnTarget'], 'SOT_Against': row['AwayShotsOnTarget']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'ShotsAgainst': row['HomeShots'], 'SOT_For': row['AwayShotsOnTarget'], 'SOT_Against': row['HomeShotsOnTarget']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) + +# Menghitung rata-rata performa dari 5 pertandingan terakhir (rolling average) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_For_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_For_L5']] + +# Menggabungkan statistik form ini kembali ke dataframe utama +hist_df = pd.merge(hist_df, rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').rename(columns=lambda x: x.replace('_L5', '_Home_L5')).drop('Team', axis=1) +hist_df = pd.merge(hist_df, rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').rename(columns=lambda x: x.replace('_L5', '_Away_L5')).drop('Team', axis=1) +hist_df.dropna(inplace=True) # Hapus baris di awal yang belum punya data 5 laga +print("โœ… Fitur 'Team Form' berhasil dibuat.") + + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (Tetap digunakan) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi Model dengan FITUR PRO +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +# === FITUR PALING LENGKAP & CANGGIH === +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_For_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_For_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model PRO RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model PRO...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi yang Menggunakan "Team Form" +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + # Memperbaiki typo "Chealsea" menjadi "Chelsea" + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + + # Mendapatkan data form dari 5 laga terakhir + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan untuk salah satu tim. Prediksi mungkin tidak akurat.") + return + + # Mengambil nilai rata-rata dari data form + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], + home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_For_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], + away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_For_L5'].values[0] + ] + + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[4]:.2f})") + + input_features = np.array([form_values + h2h_values]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) +predict_match("Man City", "Liverpool", hist_df, model) +predict_match("Man Utd", "Arsenal", hist_df, model) +predict_match("Tottenham", "Chealsea", hist_df, model) # Uji perbaikan typo \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_20251005123713.py b/.history/predict-pl-match_otomatis_20251005123713.py new file mode 100644 index 0000000000000000000000000000000000000000..1a7bec06bf09d33a04f796175cad7cfff1ac2d3f --- /dev/null +++ b/.history/predict-pl-match_otomatis_20251005123713.py @@ -0,0 +1,185 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ FEATURE ENGINEERING TINGKAT LANJUT: "TEAM FORM" (VERSI PERBAIKAN) +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") + +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) + +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_For_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_For_L5']] + +# === INI BAGIAN YANG DIPERBAIKI === +# Kita akan merge dua kali dengan cara yang lebih aman untuk menghindari nama kolom yang bentrok +# Merge untuk Tim Tuan Rumah +hist_df = pd.merge( + hist_df, + rolling_stats, + left_on=['Date', 'Home'], + right_on=['Date', 'Team'], + how='left', + suffixes=('', '_Home') # Menambahkan suffix jika ada nama kolom yg sama +).rename(columns={ + 'AvgGoalsFor_L5': 'AvgGoalsFor_Home_L5', + 'AvgGoalsAgainst_L5': 'AvgGoalsAgainst_Home_L5', + 'AvgShotsFor_L5': 'AvgShotsFor_Home_L5', + 'AvgSOT_For_L5': 'AvgSOT_For_Home_L5' +}).drop('Team', axis=1) + +# Merge untuk Tim Tamu +hist_df = pd.merge( + hist_df, + rolling_stats, + left_on=['Date', 'Away'], + right_on=['Date', 'Team'], + how='left', + suffixes=('', '_Away') +).rename(columns={ + 'AvgGoalsFor_L5': 'AvgGoalsFor_Away_L5', + 'AvgGoalsAgainst_L5': 'AvgGoalsAgainst_Away_L5', + 'AvgShotsFor_L5': 'AvgShotsFor_Away_L5', + 'AvgSOT_For_L5': 'AvgSOT_For_Away_L5' +}).drop('Team', axis=1) +# ================================ + +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' berhasil dibuat.") + + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +# Menggunakan .index untuk memastikan urutan benar setelah merge +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi Model dengan FITUR PRO +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_For_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_For_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("\n๐Ÿง  Melatih model PRO RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") +print("\nโš–๏ธ Mengevaluasi akurasi model PRO...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi yang Menggunakan "Team Form" +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan untuk salah satu tim.") + return + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], + home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_For_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], + away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_For_L5'].values[0] + ] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[4]:.2f})") + input_features = np.array([form_values + h2h_values]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_v2_20251005183906.py b/.history/predict-pl-match_otomatis_v2_20251005183906.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_v2_20251005184035.py b/.history/predict-pl-match_otomatis_v2_20251005184035.py new file mode 100644 index 0000000000000000000000000000000000000000..a6a33b2d39276961b72fd14025bea8fd23dde795 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005184035.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Arsenal", "Man Utd") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005185915.py b/.history/predict-pl-match_otomatis_v2_20251005185915.py new file mode 100644 index 0000000000000000000000000000000000000000..eae8746e8713e95395b337e0dde36083ea8f5f72 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005185915.py @@ -0,0 +1,187 @@ +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date): + past = df[((df['Home'] == team) | (df['Away'] == team)) & (df['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + + # Goals for/against + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'])) + away_features.append(compute_team_form(row['Away'], row['Date'])) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date): + past = df[((df['Home'] == home) & (df['Away'] == away)) | + ((df['Home'] == away) & (df['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw+=1 + else: aw+=1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw+=1 + else: hw+=1 + else: + dr+=1 + return pd.Series([hw,aw,dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'])) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date] +test = df[df['Date'] >= split_date] + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + n_estimators=500, + learning_rate=0.05, + max_depth=4, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + eval_metric='mlogloss', + random_state=42 +) +model.fit( + X_train, y_train, + eval_set=[(X_test, y_test)], + verbose=False, + early_stopping_rounds=30 +) +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred = model.predict(X_test) +y_proba = model.predict_proba(X_test) +acc = accuracy_score(y_test, y_pred) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {log_loss(y_test, y_proba):.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date) + af = compute_team_form(away_team, date) + h2h = get_h2h_stats(home_team, away_team, date) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + probs = model.predict_proba(X_pred)[0] + pred = model.predict(X_pred)[0] + print(f"๐Ÿ  {home_team} Menang: {probs[0]*100:.2f}%") + print(f"๐Ÿš— {away_team} Menang: {probs[1]*100:.2f}%") + print(f"๐Ÿค Seri : {probs[2]*100:.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005190546.py b/.history/predict-pl-match_otomatis_v2_20251005190546.py new file mode 100644 index 0000000000000000000000000000000000000000..9bdaa45deb2e557189ae54eceeb0aaf83a9e1bb2 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005190546.py @@ -0,0 +1,190 @@ +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date): + past = df[((df['Home'] == team) | (df['Away'] == team)) & (df['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + + # Goals for/against + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'])) + away_features.append(compute_team_form(row['Away'], row['Date'])) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date): + past = df[((df['Home'] == home) & (df['Away'] == away)) | + ((df['Home'] == away) & (df['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw+=1 + else: aw+=1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw+=1 + else: hw+=1 + else: + dr+=1 + return pd.Series([hw,aw,dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'])) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date] +test = df[df['Date'] >= split_date] + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + n_estimators=500, + learning_rate=0.05, + max_depth=4, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + eval_metric='mlogloss', + random_state=42 +) +model.fit( + X_train, + y_train, + eval_set=[(X_test, y_test)], + eval_metric="mlogloss", + early_stopping_rounds=30, + verbose=True +) + +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred = model.predict(X_test) +y_proba = model.predict_proba(X_test) +acc = accuracy_score(y_test, y_pred) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {log_loss(y_test, y_proba):.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date) + af = compute_team_form(away_team, date) + h2h = get_h2h_stats(home_team, away_team, date) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + probs = model.predict_proba(X_pred)[0] + pred = model.predict(X_pred)[0] + print(f"๐Ÿ  {home_team} Menang: {probs[0]*100:.2f}%") + print(f"๐Ÿš— {away_team} Menang: {probs[1]*100:.2f}%") + print(f"๐Ÿค Seri : {probs[2]*100:.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005191053.py b/.history/predict-pl-match_otomatis_v2_20251005191053.py new file mode 100644 index 0000000000000000000000000000000000000000..2b6364acb17317089fd9c21de3e809154e77ee0b --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005191053.py @@ -0,0 +1,193 @@ +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date): + past = df[((df['Home'] == team) | (df['Away'] == team)) & (df['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + + # Goals for/against + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'])) + away_features.append(compute_team_form(row['Away'], row['Date'])) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date): + past = df[((df['Home'] == home) & (df['Away'] == away)) | + ((df['Home'] == away) & (df['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw+=1 + else: aw+=1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw+=1 + else: hw+=1 + else: + dr+=1 + return pd.Series([hw,aw,dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'])) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date] +test = df[df['Date'] >= split_date] + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + learning_rate=0.05, + max_depth=4, + n_estimators=300, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + eval_metric="mlogloss" +) + +try: + model.fit( + X_train, y_train, + eval_set=[(X_test, y_test)], + early_stopping_rounds=30, + verbose=True + ) +except TypeError: + print("โš ๏ธ early_stopping_rounds tidak didukung, melatih tanpa early stopping.") + model.fit(X_train, y_train) + + +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred = model.predict(X_test) +y_proba = model.predict_proba(X_test) +acc = accuracy_score(y_test, y_pred) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {log_loss(y_test, y_proba):.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date) + af = compute_team_form(away_team, date) + h2h = get_h2h_stats(home_team, away_team, date) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + probs = model.predict_proba(X_pred)[0] + pred = model.predict(X_pred)[0] + print(f"๐Ÿ  {home_team} Menang: {probs[0]*100:.2f}%") + print(f"๐Ÿš— {away_team} Menang: {probs[1]*100:.2f}%") + print(f"๐Ÿค Seri : {probs[2]*100:.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005191054.py b/.history/predict-pl-match_otomatis_v2_20251005191054.py new file mode 100644 index 0000000000000000000000000000000000000000..5b92721c3912a9de1df58247f4f5a5be7d8bc970 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005191054.py @@ -0,0 +1,192 @@ +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date): + past = df[((df['Home'] == team) | (df['Away'] == team)) & (df['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + + # Goals for/against + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'])) + away_features.append(compute_team_form(row['Away'], row['Date'])) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date): + past = df[((df['Home'] == home) & (df['Away'] == away)) | + ((df['Home'] == away) & (df['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw+=1 + else: aw+=1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw+=1 + else: hw+=1 + else: + dr+=1 + return pd.Series([hw,aw,dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'])) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date] +test = df[df['Date'] >= split_date] + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + learning_rate=0.05, + max_depth=4, + n_estimators=300, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + eval_metric="mlogloss" +) + +try: + model.fit( + X_train, y_train, + eval_set=[(X_test, y_test)], + early_stopping_rounds=30, + verbose=True + ) +except TypeError: + print("โš ๏ธ early_stopping_rounds tidak didukung, melatih tanpa early stopping.") + model.fit(X_train, y_train) + +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred = model.predict(X_test) +y_proba = model.predict_proba(X_test) +acc = accuracy_score(y_test, y_pred) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {log_loss(y_test, y_proba):.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date) + af = compute_team_form(away_team, date) + h2h = get_h2h_stats(home_team, away_team, date) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + probs = model.predict_proba(X_pred)[0] + pred = model.predict(X_pred)[0] + print(f"๐Ÿ  {home_team} Menang: {probs[0]*100:.2f}%") + print(f"๐Ÿš— {away_team} Menang: {probs[1]*100:.2f}%") + print(f"๐Ÿค Seri : {probs[2]*100:.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005191636.py b/.history/predict-pl-match_otomatis_v2_20251005191636.py new file mode 100644 index 0000000000000000000000000000000000000000..8b09d81215d4615637efdc36d1add08d1961bf43 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005191636.py @@ -0,0 +1,118 @@ +# --- EPL Match Prediction Model --- +# Decoder Project | GPT-5 +# Dataset: epl-training.csv + +# =============================================================== +# 1. Import Library +# =============================================================== +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import LabelEncoder, StandardScaler +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix +import seaborn as sns +import matplotlib.pyplot as plt +import joblib + +# =============================================================== +# 2. Load Dataset +# =============================================================== +print("๐Ÿ“‚ Loading dataset...") +df = pd.read_csv("epl-training.csv") + +print("\n--- Sample Data ---") +print(df.head()) + +# =============================================================== +# 3. Cek Informasi Dataset +# =============================================================== +print("\nDataset Info:") +print(df.info()) + +print("\nMissing Values:") +print(df.isnull().sum()) + +# =============================================================== +# 4. Preprocessing +# =============================================================== +# Cek kolom target +target_col = 'result' # ganti sesuai nama kolom hasil (misal: 'result' / 'outcome' / 'winner') +if target_col not in df.columns: + raise ValueError(f"Kolom target '{target_col}' tidak ditemukan di dataset.") + +# Pisahkan fitur dan target +X = df.drop(columns=[target_col]) +y = df[target_col] + +# Label Encoding untuk kolom non-numerik +categorical_cols = X.select_dtypes(include=['object']).columns +if len(categorical_cols) > 0: + print(f"\n๐Ÿ”  Encoding kolom kategorikal: {list(categorical_cols)}") + encoder = LabelEncoder() + for col in categorical_cols: + X[col] = encoder.fit_transform(X[col]) + +# Normalisasi (optional) +scaler = StandardScaler() +X_scaled = scaler.fit_transform(X) + +# =============================================================== +# 5. Split Data (Train & Test) +# =============================================================== +X_train, X_test, y_train, y_test = train_test_split( + X_scaled, y, test_size=0.2, random_state=42, stratify=y +) + +print(f"\n๐Ÿ“Š Jumlah Data Train: {X_train.shape[0]} | Test: {X_test.shape[0]}") + +# =============================================================== +# 6. Train Model +# =============================================================== +print("\n๐Ÿš€ Training RandomForest Classifier...") +model = RandomForestClassifier( + n_estimators=200, + max_depth=12, + random_state=42, + class_weight='balanced_subsample' +) +model.fit(X_train, y_train) + +# =============================================================== +# 7. Evaluasi Model +# =============================================================== +y_pred = model.predict(X_test) + +print("\nโœ… Model Evaluation") +print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}") +print("\nClassification Report:") +print(classification_report(y_test, y_pred)) + +# =============================================================== +# 8. Confusion Matrix Visualization +# =============================================================== +plt.figure(figsize=(6,5)) +sns.heatmap(confusion_matrix(y_test, y_pred), annot=True, fmt='d', cmap='Blues') +plt.title("Confusion Matrix - EPL Match Prediction") +plt.xlabel("Predicted") +plt.ylabel("Actual") +plt.show() + +# =============================================================== +# 9. Simpan Model +# =============================================================== +joblib.dump(model, "epl_model.pkl") +joblib.dump(encoder, "encoder.pkl") +joblib.dump(scaler, "scaler.pkl") + +print("\n๐Ÿ’พ Model, encoder, dan scaler berhasil disimpan!") +print("๐Ÿ“ File: epl_model.pkl, encoder.pkl, scaler.pkl") + +# =============================================================== +# 10. Contoh Prediksi +# =============================================================== +print("\n๐Ÿ”ฎ Contoh Prediksi:") +sample = X.iloc[0:1] # ambil satu baris contoh +sample_scaled = scaler.transform(sample) +pred_result = model.predict(sample_scaled)[0] +print(f"Hasil prediksi: {pred_result}") diff --git a/.history/predict-pl-match_otomatis_v2_20251005191642.py b/.history/predict-pl-match_otomatis_v2_20251005191642.py new file mode 100644 index 0000000000000000000000000000000000000000..5b92721c3912a9de1df58247f4f5a5be7d8bc970 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005191642.py @@ -0,0 +1,192 @@ +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date): + past = df[((df['Home'] == team) | (df['Away'] == team)) & (df['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + + # Goals for/against + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'])) + away_features.append(compute_team_form(row['Away'], row['Date'])) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date): + past = df[((df['Home'] == home) & (df['Away'] == away)) | + ((df['Home'] == away) & (df['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw+=1 + else: aw+=1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw+=1 + else: hw+=1 + else: + dr+=1 + return pd.Series([hw,aw,dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'])) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date] +test = df[df['Date'] >= split_date] + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + learning_rate=0.05, + max_depth=4, + n_estimators=300, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + eval_metric="mlogloss" +) + +try: + model.fit( + X_train, y_train, + eval_set=[(X_test, y_test)], + early_stopping_rounds=30, + verbose=True + ) +except TypeError: + print("โš ๏ธ early_stopping_rounds tidak didukung, melatih tanpa early stopping.") + model.fit(X_train, y_train) + +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred = model.predict(X_test) +y_proba = model.predict_proba(X_test) +acc = accuracy_score(y_test, y_pred) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {log_loss(y_test, y_proba):.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date) + af = compute_team_form(away_team, date) + h2h = get_h2h_stats(home_team, away_team, date) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + probs = model.predict_proba(X_pred)[0] + pred = model.predict(X_pred)[0] + print(f"๐Ÿ  {home_team} Menang: {probs[0]*100:.2f}%") + print(f"๐Ÿš— {away_team} Menang: {probs[1]*100:.2f}%") + print(f"๐Ÿค Seri : {probs[2]*100:.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005191750.py b/.history/predict-pl-match_otomatis_v2_20251005191750.py new file mode 100644 index 0000000000000000000000000000000000000000..aff2115733720cc706e8eea3eed0a671e504ce24 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005191750.py @@ -0,0 +1,222 @@ +# predict-pl-match_otomatis_v2_fixed.py +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, log_loss +from sklearn.preprocessing import LabelEncoder +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# ====================== +# 1๏ธโƒฃ LOAD DATA +# ====================== +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y', errors='coerce') +df = df.dropna(subset=['Date']) +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data: {df.shape[0]} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ====================== +# 2๏ธโƒฃ PERSIAPAN FITUR DASAR +# ====================== +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals' +}) +# result as string for clarity (will encode later) +df['Result'] = np.select( + [df['HomeGoals'] > df['AwayGoals'], df['HomeGoals'] < df['AwayGoals']], + ['Home', 'Away'], default='Draw' +) + +# ====================== +# 3๏ธโƒฃ HITUNG FORM (LAST 5 MATCHES) +# ====================== +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def compute_team_form(team, current_date, df_local): + past = df_local[((df_local['Home'] == team) | (df_local['Away'] == team)) & (df_local['Date'] < current_date)] + last5 = past.tail(5) + if last5.empty: + return pd.Series([np.nan]*4, index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + gf, ga = [], [] + for _, row in last5.iterrows(): + if row['Home'] == team: + gf.append(row['HomeGoals']) + ga.append(row['AwayGoals']) + else: + gf.append(row['AwayGoals']) + ga.append(row['HomeGoals']) + wins = sum([1 if g1 > g2 else 0 for g1,g2 in zip(gf,ga)]) + return pd.Series([ + np.mean(gf), np.mean(ga), wins/len(last5), np.mean(np.array(gf)-np.array(ga)) + ], index=['avg_gf','avg_ga','win_rate','goal_diff_avg']) + +# Buat kolom untuk fitur form home & away +home_features, away_features = [], [] +# NOTE: pass a local copy of df without the new columns to avoid self-contamination +# but since we only added Result and Date/Home/Away/Goals exist, it's safe to use df directly. +for i, row in df.iterrows(): + home_features.append(compute_team_form(row['Home'], row['Date'], df)) + away_features.append(compute_team_form(row['Away'], row['Date'], df)) + +home_form = pd.DataFrame(home_features, index=df.index) +away_form = pd.DataFrame(away_features, index=df.index) + +home_form = home_form.add_prefix("home_") +away_form = away_form.add_prefix("away_") + +df = pd.concat([df, home_form, away_form], axis=1) +df = df.dropna().reset_index(drop=True) # buang baris yang tidak punya cukup history +print("โœ… Fitur form berhasil dibuat.") + +# ====================== +# 4๏ธโƒฃ FITUR HEAD-TO-HEAD (H2H) +# ====================== +print("โš”๏ธ Menghitung H2H historis per pasangan tim...") + +def get_h2h_stats(home, away, date, df_local): + past = df_local[((df_local['Home'] == home) & (df_local['Away'] == away)) | + ((df_local['Home'] == away) & (df_local['Away'] == home))] + past = past[past['Date'] < date] + if past.empty: + return pd.Series([0,0,0], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + hw, aw, dr = 0,0,0 + for _, r in past.iterrows(): + if r['HomeGoals'] > r['AwayGoals']: + if r['Home'] == home: hw += 1 + else: aw += 1 + elif r['AwayGoals'] > r['HomeGoals']: + if r['Away'] == away: aw += 1 + else: hw += 1 + else: + dr += 1 + return pd.Series([hw, aw, dr], index=['h2h_home_wins','h2h_away_wins','h2h_draws']) + +h2h_stats = [] +for i, row in df.iterrows(): + h2h_stats.append(get_h2h_stats(row['Home'], row['Away'], row['Date'], df)) + +df = pd.concat([df, pd.DataFrame(h2h_stats)], axis=1) +print("โœ… Fitur H2H ditambahkan.") + +# ====================== +# 5๏ธโƒฃ SIAPKAN DATA UNTUK MODEL +# ====================== +features = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_goal_diff_avg', + 'away_avg_gf','away_avg_ga','away_win_rate','away_goal_diff_avg', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +target = 'Result' + +split_date = '2023-01-01' +train = df[df['Date'] < split_date].copy() +test = df[df['Date'] >= split_date].copy() + +X_train, y_train = train[features], train[target] +X_test, y_test = test[features], test[target] +print(f"๐Ÿ“… Train: {train.shape[0]} | Test: {test.shape[0]} pertandingan") + +# ====================== +# Encode target labels -> numeric (important!) +# ====================== +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) # assume test labels are subset of training labels (should be) + +print("๐Ÿ” Label encoding:", dict(enumerate(le.classes_))) + +# ====================== +# 6๏ธโƒฃ TRAIN MODEL XGBOOST (dengan fallback kompatibilitas) +# ====================== +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") + +model = XGBClassifier( + learning_rate=0.05, + max_depth=4, + n_estimators=300, + subsample=0.8, + colsample_bytree=0.8, + reg_lambda=1.5, + reg_alpha=0.5, + use_label_encoder=False, + eval_metric="mlogloss", + random_state=42 +) + +# Fit with early stopping if supported; fallback to basic fit if not +try: + # pass encoded y for eval_set + model.fit( + X_train, y_train_enc, + eval_set=[(X_test, y_test_enc)], + early_stopping_rounds=30, + verbose=False + ) +except TypeError: + print("โš ๏ธ early_stopping_rounds tidak didukung oleh environment ini. Melatih tanpa early stopping...") + model.fit(X_train, y_train_enc) +except Exception as e: + # general fallback to avoid crash in weird envs + print("โš ๏ธ Terjadi error saat fit dengan early stopping:", e) + print("Melatih tanpa early stopping...") + model.fit(X_train, y_train_enc) + +print("โœ… Model selesai dilatih.") + +# ====================== +# 7๏ธโƒฃ EVALUASI MODEL +# ====================== +y_pred_enc = model.predict(X_test) +y_proba = model.predict_proba(X_test) + +# decode preds back to labels for readable report +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) + +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\n", classification_report(y_test, y_pred)) +print("Confusion Matrix (rows true, cols pred):\n", confusion_matrix(y_test, y_pred)) + +# ====================== +# 8๏ธโƒฃ FUNGSI PREDIKSI MANUAL (menggunakan le untuk map class order) +# ====================== +def predict_match(home_team, away_team): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team} vs {away_team}") + print("="*40) + date = df['Date'].max() + pd.Timedelta(days=1) + + hf = compute_team_form(home_team, date, df) + af = compute_team_form(away_team, date, df) + h2h = get_h2h_stats(home_team, away_team, date, df) + if hf.isna().any() or af.isna().any(): + print("โš ๏ธ Tidak cukup data form untuk salah satu tim.") + return + X_pred = pd.DataFrame([[ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['goal_diff_avg'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['goal_diff_avg'], + h2h['h2h_home_wins'], h2h['h2h_away_wins'], h2h['h2h_draws'] + ]], columns=features) + proba = model.predict_proba(X_pred)[0] + pred_enc = np.argmax(proba) + pred_label = le.inverse_transform([pred_enc])[0] + + # print probabilities aligned with label encoder order + print("\n๐Ÿ“ˆ Probabilitas (label order dari encoder):") + for idx, cls in enumerate(le.classes_): + print(f" - {cls:5s} : {proba[idx]*100:6.2f}%") + print(f"\n๐Ÿ’ก Kesimpulan: {pred_label}") + +# ====================== +# 9๏ธโƒฃ CONTOH PREDIKSI +# ====================== +predict_match("Arsenal", "Man Utd") +predict_match("Liverpool", "Man City") +predict_match("Chelsea", "Tottenham") diff --git a/.history/predict-pl-match_otomatis_v2_20251005192401.py b/.history/predict-pl-match_otomatis_v2_20251005192401.py new file mode 100644 index 0000000000000000000000000000000000000000..3ae8f4619ee7781a87395a7f4eb2135756b4857c --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005192401.py @@ -0,0 +1,396 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan fitur + tuning ringan + no-data-leakage +import pandas as pd +import numpy as np +import os +import warnings +warnings.filterwarnings("ignore") +from collections import deque, defaultdict + +# ML +from xgboost import XGBClassifier +from sklearn.preprocessing import LabelEncoder +from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV +from sklearn.metrics import accuracy_score, balanced_accuracy_score, classification_report, confusion_matrix, log_loss +import matplotlib.pyplot as plt +import joblib +import random +random.seed(42) +np.random.seed(42) + +# --------------------------- +# 0. Config +# --------------------------- +CSV = "epl-training.csv" +TEST_FROM_DATE = "2023-01-01" # split temporal (train < date, test >= date) +ROLL_WINDOW = 5 # jumlah match untuk rolling form +RANDOM_SEARCH_ITER = 20 # iterasi RandomizedSearch untuk tuning (ringan) +N_JOBS = -1 + +# --------------------------- +# 1. Load data & basic checks +# --------------------------- +if not os.path.exists(CSV): + raise FileNotFoundError(f"File '{CSV}' tidak ditemukan. Letakkan file di folder yg sama.") + +df_raw = pd.read_csv(CSV, low_memory=False) +# Normalisasi kolom tanggal (coba beberapa format) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], format=fmt) + break + except Exception: + pass +if df_raw['Date'].dtype == object: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], errors='coerce') + +df_raw = df_raw.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True) + +# columns we expect (some optional) +required_cols = ['HomeTeam', 'AwayTeam', 'FTHG', 'FTAG'] +optional_shot_cols = { + 'HomeShots': 'HS', 'AwayShots': 'AS', + 'HomeSOT': 'HST', 'AwaySOT': 'AST' +} +# rename standard +df = df_raw.copy() +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + optional_shot_cols['HomeShots']: 'HomeShots' if optional_shot_cols['HomeShots'] in df.columns else None, +}) + +# fix optional renames carefully +for std_name, orig in optional_shot_cols.items(): + if orig in df.columns: + if std_name == 'HomeShots': + df = df.rename(columns={orig: 'HomeShots'}) + elif std_name == 'AwayShots': + df = df.rename(columns={orig: 'AwayShots'}) + elif std_name == 'HomeSOT': + df = df.rename(columns={orig: 'HomeSOT'}) + elif std_name == 'AwaySOT': + df = df.rename(columns={orig: 'AwaySOT'}) + +# ensure required present +for c in ['Home','Away','HomeGoals','AwayGoals']: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan di dataset. Pastikan file sesuai (Kaggle EPL).") + +print(f"๐Ÿ“Š Data dimuat: {df.shape[0]} baris ({df['Date'].min().date()} โ†’ {df['Date'].max().date()})") + +# --------------------------- +# 2. Build features incrementally (no leakage) +# --------------------------- +print("๐Ÿ”ง Membangun fitur secara incremental (no leakage)...") + +# histories stores past matches per team as deque of dicts +histories = defaultdict(lambda: deque(maxlen=ROLL_WINDOW)) +# h2h_counts stores counts for ordered pair (home,away) -> dict hwins, awins, draws +h2h_counts = defaultdict(lambda: {'home_wins':0, 'away_wins':0, 'draws':0}) + +rows = [] +for idx, row in df.iterrows(): + date = row['Date'] + home = row['Home'] + away = row['Away'] + hg = int(row['HomeGoals']) + ag = int(row['AwayGoals']) + + # compute home last5 stats using histories (only previous matches) + def stats_from_history(team): + past = list(histories[team]) # most recent last entries (old -> new) + if len(past) == 0: + return {'avg_gf': np.nan, 'avg_ga': np.nan, 'win_rate': np.nan, 'avg_gd': np.nan, + 'avg_shots': np.nan, 'avg_sot': np.nan} + gf = np.array([m['gf'] for m in past]) + ga = np.array([m['ga'] for m in past]) + res = np.array([m['win'] for m in past]) # 1 win else 0 + out = { + 'avg_gf': gf.mean(), + 'avg_ga': ga.mean(), + 'win_rate': res.mean(), + 'avg_gd': (gf - ga).mean() + } + # optional shots + if 'shots' in past[0]: + out['avg_shots'] = np.mean([m.get('shots', np.nan) for m in past]) + else: + out['avg_shots'] = np.nan + if 'sot' in past[0]: + out['avg_sot'] = np.mean([m.get('sot', np.nan) for m in past]) + else: + out['avg_sot'] = np.nan + return out + + home_stats = stats_from_history(home) + away_stats = stats_from_history(away) + + # h2h counts BEFORE this match (key sorted to preserve pair) + pair_key = (home, away) # orientation matters; we'll store as ordered key + # get aggregated previous results for this specific pairing (both orientations) + # We'll compute counts from h2h_counts[(min, max)] but it's easier to compute by scanning earlier matches? + # To avoid heavy scanning, we also maintain a dict of pair->counts where pair is (home,away) normalized (A,B) + norm_key = tuple(sorted((home, away))) + pair_hist = h2h_counts[norm_key] # counts aggregated irrespective of venue + # But need orientation: how many times home (current 'home') won vs away won vs draws + # We'll store aggregated as counts where home_wins means wins by the first element of norm_key + # So to convert to current orientation: + if norm_key[0] == home: + # then pair_hist['home_wins'] refers to wins when norm_key[0] was home in those past matches + # but we want wins by current 'home' regardless of which side in norm_key + h_wins = pair_hist['home_wins'] + a_wins = pair_hist['away_wins'] + else: + # norm_key[0] is away, so swap + h_wins = pair_hist['away_wins'] + a_wins = pair_hist['home_wins'] + draws = pair_hist['draws'] + + # Build feature row (use simple features) + feat = { + 'Date': date, + 'Home': home, 'Away': away, + # home features + 'home_avg_gf': home_stats['avg_gf'], 'home_avg_ga': home_stats['avg_ga'], + 'home_win_rate': home_stats['win_rate'], 'home_avg_gd': home_stats['avg_gd'], + 'home_avg_shots': home_stats['avg_shots'], 'home_avg_sot': home_stats['avg_sot'], + # away features + 'away_avg_gf': away_stats['avg_gf'], 'away_avg_ga': away_stats['avg_ga'], + 'away_win_rate': away_stats['win_rate'], 'away_avg_gd': away_stats['avg_gd'], + 'away_avg_shots': away_stats['avg_shots'], 'away_avg_sot': away_stats['avg_sot'], + # h2h + 'h2h_home_wins': h_wins, 'h2h_away_wins': a_wins, 'h2h_draws': draws, + # target + 'HomeGoals': hg, 'AwayGoals': ag + } + rows.append(feat) + + # AFTER constructing features for this match, update histories & h2h_counts with this match + # update home history entry (perspective of team) + home_entry = {'gf': hg, 'ga': ag, 'win': 1 if hg>ag else 0, 'shots': row.get('HomeShots', np.nan), 'sot': row.get('HomeSOT', np.nan)} + away_entry = {'gf': ag, 'ga': hg, 'win': 1 if ag>hg else 0, 'shots': row.get('AwayShots', np.nan), 'sot': row.get('AwaySOT', np.nan)} + histories[home].append(home_entry) + histories[away].append(away_entry) + + # update h2h aggregated counts at norm_key + # determine result + if hg > ag: + # home (current home) won -> increment for the team who was home in this match orientation of norm_key + if norm_key[0] == home: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['away_wins'] += 1 + elif ag > hg: + if norm_key[0] == home: + h2h_counts[norm_key]['away_wins'] += 1 + else: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['draws'] += 1 + +features_df = pd.DataFrame(rows) +# target label +features_df['Result'] = np.select( + [features_df['HomeGoals'] > features_df['AwayGoals'], + features_df['HomeGoals'] < features_df['AwayGoals']], + ['Home','Away'], default='Draw' +) + +# drop rows where features missing (teams with no previous history) +before = features_df.shape[0] +features_df = features_df.dropna(subset=[ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd' +]).reset_index(drop=True) +after = features_df.shape[0] +print(f" -> Baris awal: {before}, setelah drop missing features: {after}") + +# --------------------------- +# 3. Prepare X, y and temporal split +# --------------------------- +# Decide feature columns (exclude raw goals) +feat_cols = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +# optionally include shots if available (check columns) +if 'home_avg_shots' in features_df.columns and features_df['home_avg_shots'].notna().any(): + feat_cols += ['home_avg_shots','away_avg_shots'] +if 'home_avg_sot' in features_df.columns and features_df['home_avg_sot'].notna().any(): + feat_cols += ['home_avg_sot','away_avg_sot'] + +df_model = features_df.copy() +df_model = df_model.sort_values('Date').reset_index(drop=True) + +train_df = df_model[df_model['Date'] < pd.to_datetime(TEST_FROM_DATE)] +test_df = df_model[df_model['Date'] >= pd.to_datetime(TEST_FROM_DATE)] +print(f"๐Ÿ“… Train rows: {train_df.shape[0]} | Test rows: {test_df.shape[0]}") + +X_train = train_df[feat_cols].astype(float) +X_test = test_df[feat_cols].astype(float) +y_train = train_df['Result'].astype(str) +y_test = test_df['Result'].astype(str) + +# encode labels +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) +print("๐Ÿ” LabelEncoder classes:", le.classes_) + +# --------------------------- +# 4. Hyperparameter tuning (RandomizedSearchCV with TimeSeriesSplit) +# --------------------------- +print("โš™๏ธ Randomized hyperparameter search (light)...") +base = XGBClassifier(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=42) + +param_dist = { + 'n_estimators': [100,200,300,400], + 'max_depth': [3,4,5,6], + 'learning_rate': [0.01,0.03,0.05,0.1], + 'subsample': [0.6,0.8,1.0], + 'colsample_bytree': [0.6,0.8,1.0], + 'reg_lambda': [0.5,1.0,1.5,2.0], + 'reg_alpha': [0.0,0.25,0.5,1.0] +} + +tscv = TimeSeriesSplit(n_splits=4) +rnd = RandomizedSearchCV( + estimator=base, + param_distributions=param_dist, + n_iter=RANDOM_SEARCH_ITER, + scoring='balanced_accuracy', + cv=tscv, + verbose=1, + n_jobs=N_JOBS, + random_state=42 +) +rnd.fit(X_train, y_train_enc) +print("โœ… Best params:", rnd.best_params_) +best = rnd.best_estimator_ + +# --------------------------- +# 5. Final train on full training set, evaluate on test +# --------------------------- +print("๐Ÿง  Melatih model final dengan best params pada seluruh training set...") +best.fit(X_train, y_train_enc) # no early stopping here (we used CV) +y_pred_enc = best.predict(X_test) +y_proba = best.predict_proba(X_test) +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) +print("\n๐Ÿ“Š Evaluasi (hold-out test temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced acc. : {bal_acc*100:.2f}%") +print(f" - Log loss : {ll:.4f}") +print("\nClassification report:\n", classification_report(y_test, y_pred)) +print("Confusion matrix (rows true, cols pred):\n", confusion_matrix(y_test, y_pred)) + +# feature importance (XGBoost builtin) +fi = best.get_booster().get_score(importance_type='weight') +# map to readable list +fi_list = sorted(fi.items(), key=lambda x: x[1], reverse=True) +print("\nFeature importance (by weight):") +for f,v in fi_list: + print(f" {f}: {v}") + +# optional: plot top features +try: + names = [f for f in feat_cols] + import matplotlib.pyplot as plt + plt.figure(figsize=(8,5)) + # use sklearn style importance via gain if available + importances = [] + booster = best.get_booster() + gain = booster.get_score(importance_type='gain') + # map fscore names 'f0','f1' to feature names by index + # XGBoost's feature names are 'f0'.. so map + for i, name in enumerate(feat_cols): + key = f"f{i}" + importances.append(gain.get(key, 0.0)) + idx = np.argsort(importances)[::-1] + top_n = min(10, len(feat_cols)) + plt.bar([feat_cols[i] for i in idx[:top_n]], [importances[i] for i in idx[:top_n]]) + plt.xticks(rotation=45, ha='right') + plt.title("Feature importance (gain) - top features") + plt.tight_layout() + plt.show() +except Exception: + pass + +# --------------------------- +# 6. Save model & encoder +# --------------------------- +joblib.dump(best, "model_epl_v3.joblib") +joblib.dump(le, "labelencoder_v3.joblib") +print("\n๐Ÿ’พ Model & encoder disimpan: model_epl_v3.joblib, labelencoder_v3.joblib") + +# --------------------------- +# 7. Predict function using built incremental summaries (safe) +# --------------------------- +# Rebuild histories quickly up to last date (we already had histories built while feature building) +# But simpler: use last known aggregated stats in train+test (df_model tail) +last_summary = df_model.groupby('Home').tail(1) # not perfect; we'll build helper compute again from original df +# We'll implement compute_team_form_from_raw to compute based on raw df (only past matches) +def compute_team_form_from_raw(team, date, raw_df, window=ROLL_WINDOW): + past = raw_df[((raw_df['HomeTeam'] == team) | (raw_df['AwayTeam'] == team)) & (raw_df['Date'] < date)].tail(window) + if past.empty: + return None + gf, ga, wins = [], [], [] + for _, r in past.iterrows(): + if r['HomeTeam'] == team: + gf.append(int(r['FTHG'])); ga.append(int(r['FTAG']) +) + wins.append(1 if int(r['FTHG'])>int(r['FTAG']) else 0) + else: + gf.append(int(r['FTAG'])); ga.append(int(r['FTHG']) +) + wins.append(1 if int(r['FTAG'])>int(r['FTHG']) else 0) + return { + 'avg_gf': np.mean(gf), 'avg_ga': np.mean(ga), + 'win_rate': np.mean(wins), 'avg_gd': np.mean(np.array(gf)-np.array(ga)) + } + +def predict_match(home, away, model=best, le=le, raw_df=df_raw): + date = raw_df['Date'].max() + pd.Timedelta(days=1) + h = compute_team_form_from_raw(home, date, raw_df) + a = compute_team_form_from_raw(away, date, raw_df) + if h is None or a is None: + print("โš ๏ธ Tidak cukup history untuk salah satu tim.") + return None + # h2h + nk = tuple(sorted((home, away))) + ph = h2h_counts.get(nk, {'home_wins':0,'away_wins':0,'draws':0}) + # convert orientation + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + feats = [ + h['avg_gf'], h['avg_ga'], h['win_rate'], h['avg_gd'], + a['avg_gf'], a['avg_ga'], a['win_rate'], a['avg_gd'], + h_wins, a_wins, ph['draws'] + ] + # if shots included, extend (not implemented in this helper) + Xp = np.array(feats).reshape(1, -1) + probs = model.predict_proba(Xp)[0] + pred_enc = np.argmax(probs) + pred_label = le.inverse_transform([pred_enc])[0] + print(f"PREDIKSI: {home} vs {away} -> {pred_label}") + for idx, cls in enumerate(le.classes_): + print(f" {cls}: {probs[idx]*100:.2f}%") + return pred_label, probs + +# contoh prediksi +print("\n๐Ÿ”ฎ Contoh prediksi:") +try: + predict_match("Arsenal", "Man Utd") + predict_match("Liverpool", "Man City") + predict_match("Chelsea", "Tottenham") +except Exception as e: + print("Info: prediksi contoh gagal:", e) + +print("\nSelesai.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005194134.py b/.history/predict-pl-match_otomatis_v2_20251005194134.py new file mode 100644 index 0000000000000000000000000000000000000000..595829c0ecac4f4c2e957275cd426e872272bc5a --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005194134.py @@ -0,0 +1,400 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan fitur + tuning ringan + no-data-leakage +import pandas as pd +import numpy as np +import os +import warnings +warnings.filterwarnings("ignore") +from collections import deque, defaultdict + +# ML +from xgboost import XGBClassifier +from sklearn.preprocessing import LabelEncoder +from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV +from sklearn.metrics import accuracy_score, balanced_accuracy_score, classification_report, confusion_matrix, log_loss +import matplotlib.pyplot as plt +import joblib +import random +random.seed(42) +np.random.seed(42) + +# --------------------------- +# 0. Config +# --------------------------- +CSV = "epl-training.csv" +TEST_FROM_DATE = "2023-01-01" # split temporal (train < date, test >= date) +ROLL_WINDOW = 5 # jumlah match untuk rolling form +RANDOM_SEARCH_ITER = 20 # iterasi RandomizedSearch untuk tuning (ringan) +N_JOBS = -1 + +# --------------------------- +# 1. Load data & basic checks +# --------------------------- +if not os.path.exists(CSV): + raise FileNotFoundError(f"File '{CSV}' tidak ditemukan. Letakkan file di folder yg sama.") + +df_raw = pd.read_csv(CSV, low_memory=False) +# Normalisasi kolom tanggal (coba beberapa format) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], format=fmt) + break + except Exception: + pass +if df_raw['Date'].dtype == object: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], errors='coerce') + +df_raw = df_raw.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True) + +# columns we expect (some optional) +required_cols = ['HomeTeam', 'AwayTeam', 'FTHG', 'FTAG'] +optional_shot_cols = { + 'HomeShots': 'HS', 'AwayShots': 'AS', + 'HomeSOT': 'HST', 'AwaySOT': 'AST' +} +# rename standard +df = df_raw.copy() +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + optional_shot_cols['HomeShots']: 'HomeShots' if optional_shot_cols['HomeShots'] in df.columns else None, +}) + +# fix optional renames carefully +for std_name, orig in optional_shot_cols.items(): + if orig in df.columns: + if std_name == 'HomeShots': + df = df.rename(columns={orig: 'HomeShots'}) + elif std_name == 'AwayShots': + df = df.rename(columns={orig: 'AwayShots'}) + elif std_name == 'HomeSOT': + df = df.rename(columns={orig: 'HomeSOT'}) + elif std_name == 'AwaySOT': + df = df.rename(columns={orig: 'AwaySOT'}) + +# ensure required present +for c in ['Home','Away','HomeGoals','AwayGoals']: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan di dataset. Pastikan file sesuai (Kaggle EPL).") + +print(f"๐Ÿ“Š Data dimuat: {df.shape[0]} baris ({df['Date'].min().date()} โ†’ {df['Date'].max().date()})") + +# --------------------------- +# 2. Build features incrementally (no leakage) +# --------------------------- +print("๐Ÿ”ง Membangun fitur secara incremental (no leakage)...") + +# histories stores past matches per team as deque of dicts +histories = defaultdict(lambda: deque(maxlen=ROLL_WINDOW)) +# h2h_counts stores counts for ordered pair (home,away) -> dict hwins, awins, draws +h2h_counts = defaultdict(lambda: {'home_wins':0, 'away_wins':0, 'draws':0}) + +rows = [] +for idx, row in df.iterrows(): + date = row['Date'] + home = row['Home'] + away = row['Away'] + hg = int(row['HomeGoals']) + ag = int(row['AwayGoals']) + + # compute home last5 stats using histories (only previous matches) + def stats_from_history(team): + past = list(histories[team]) # most recent last entries (old -> new) + if len(past) == 0: + return {'avg_gf': np.nan, 'avg_ga': np.nan, 'win_rate': np.nan, 'avg_gd': np.nan, + 'avg_shots': np.nan, 'avg_sot': np.nan} + gf = np.array([m['gf'] for m in past]) + ga = np.array([m['ga'] for m in past]) + res = np.array([m['win'] for m in past]) # 1 win else 0 + out = { + 'avg_gf': gf.mean(), + 'avg_ga': ga.mean(), + 'win_rate': res.mean(), + 'avg_gd': (gf - ga).mean() + } + # optional shots + if 'shots' in past[0]: + out['avg_shots'] = np.mean([m.get('shots', np.nan) for m in past]) + else: + out['avg_shots'] = np.nan + if 'sot' in past[0]: + out['avg_sot'] = np.mean([m.get('sot', np.nan) for m in past]) + else: + out['avg_sot'] = np.nan + return out + + home_stats = stats_from_history(home) + away_stats = stats_from_history(away) + + # h2h counts BEFORE this match (key sorted to preserve pair) + pair_key = (home, away) # orientation matters; we'll store as ordered key + # get aggregated previous results for this specific pairing (both orientations) + # We'll compute counts from h2h_counts[(min, max)] but it's easier to compute by scanning earlier matches? + # To avoid heavy scanning, we also maintain a dict of pair->counts where pair is (home,away) normalized (A,B) + norm_key = tuple(sorted((home, away))) + pair_hist = h2h_counts[norm_key] # counts aggregated irrespective of venue + # But need orientation: how many times home (current 'home') won vs away won vs draws + # We'll store aggregated as counts where home_wins means wins by the first element of norm_key + # So to convert to current orientation: + if norm_key[0] == home: + # then pair_hist['home_wins'] refers to wins when norm_key[0] was home in those past matches + # but we want wins by current 'home' regardless of which side in norm_key + h_wins = pair_hist['home_wins'] + a_wins = pair_hist['away_wins'] + else: + # norm_key[0] is away, so swap + h_wins = pair_hist['away_wins'] + a_wins = pair_hist['home_wins'] + draws = pair_hist['draws'] + + # Build feature row (use simple features) + feat = { + 'Date': date, + 'Home': home, 'Away': away, + # home features + 'home_avg_gf': home_stats['avg_gf'], 'home_avg_ga': home_stats['avg_ga'], + 'home_win_rate': home_stats['win_rate'], 'home_avg_gd': home_stats['avg_gd'], + 'home_avg_shots': home_stats['avg_shots'], 'home_avg_sot': home_stats['avg_sot'], + # away features + 'away_avg_gf': away_stats['avg_gf'], 'away_avg_ga': away_stats['avg_ga'], + 'away_win_rate': away_stats['win_rate'], 'away_avg_gd': away_stats['avg_gd'], + 'away_avg_shots': away_stats['avg_shots'], 'away_avg_sot': away_stats['avg_sot'], + # h2h + 'h2h_home_wins': h_wins, 'h2h_away_wins': a_wins, 'h2h_draws': draws, + # target + 'HomeGoals': hg, 'AwayGoals': ag + } + rows.append(feat) + + # AFTER constructing features for this match, update histories & h2h_counts with this match + # update home history entry (perspective of team) + home_entry = {'gf': hg, 'ga': ag, 'win': 1 if hg>ag else 0, 'shots': row.get('HomeShots', np.nan), 'sot': row.get('HomeSOT', np.nan)} + away_entry = {'gf': ag, 'ga': hg, 'win': 1 if ag>hg else 0, 'shots': row.get('AwayShots', np.nan), 'sot': row.get('AwaySOT', np.nan)} + histories[home].append(home_entry) + histories[away].append(away_entry) + + # update h2h aggregated counts at norm_key + # determine result + if hg > ag: + # home (current home) won -> increment for the team who was home in this match orientation of norm_key + if norm_key[0] == home: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['away_wins'] += 1 + elif ag > hg: + if norm_key[0] == home: + h2h_counts[norm_key]['away_wins'] += 1 + else: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['draws'] += 1 + +features_df = pd.DataFrame(rows) +# target label +features_df['Result'] = np.select( + [features_df['HomeGoals'] > features_df['AwayGoals'], + features_df['HomeGoals'] < features_df['AwayGoals']], + ['Home','Away'], default='Draw' +) + +# drop rows where features missing (teams with no previous history) +before = features_df.shape[0] +features_df = features_df.dropna(subset=[ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd' +]).reset_index(drop=True) +after = features_df.shape[0] +print(f" -> Baris awal: {before}, setelah drop missing features: {after}") + +# --------------------------- +# 3. Prepare X, y and temporal split +# --------------------------- +# Decide feature columns (exclude raw goals) +feat_cols = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +# optionally include shots if available (check columns) +if 'home_avg_shots' in features_df.columns and features_df['home_avg_shots'].notna().any(): + feat_cols += ['home_avg_shots','away_avg_shots'] +if 'home_avg_sot' in features_df.columns and features_df['home_avg_sot'].notna().any(): + feat_cols += ['home_avg_sot','away_avg_sot'] + +df_model = features_df.copy() +df_model = df_model.sort_values('Date').reset_index(drop=True) + +train_df = df_model[df_model['Date'] < pd.to_datetime(TEST_FROM_DATE)] +test_df = df_model[df_model['Date'] >= pd.to_datetime(TEST_FROM_DATE)] +print(f"๐Ÿ“… Train rows: {train_df.shape[0]} | Test rows: {test_df.shape[0]}") + +X_train = train_df[feat_cols].astype(float) +X_test = test_df[feat_cols].astype(float) +y_train = train_df['Result'].astype(str) +y_test = test_df['Result'].astype(str) + +# encode labels +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) +print("๐Ÿ” LabelEncoder classes:", le.classes_) + +# --------------------------- +# 4. Hyperparameter tuning (RandomizedSearchCV with TimeSeriesSplit) +# --------------------------- +print("โš™๏ธ Randomized hyperparameter search (light)...") +base = XGBClassifier(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=42) + +param_dist = { + 'n_estimators': [100,200,300,400], + 'max_depth': [3,4,5,6], + 'learning_rate': [0.01,0.03,0.05,0.1], + 'subsample': [0.6,0.8,1.0], + 'colsample_bytree': [0.6,0.8,1.0], + 'reg_lambda': [0.5,1.0,1.5,2.0], + 'reg_alpha': [0.0,0.25,0.5,1.0] +} + +tscv = TimeSeriesSplit(n_splits=4) +rnd = RandomizedSearchCV( + estimator=base, + param_distributions=param_dist, + n_iter=RANDOM_SEARCH_ITER, + scoring='balanced_accuracy', + cv=tscv, + verbose=1, + n_jobs=N_JOBS, + random_state=42 +) +rnd.fit(X_train, y_train_enc) +print("โœ… Best params:", rnd.best_params_) +best = rnd.best_estimator_ + +# --------------------------- +# 5. Final train on full training set, evaluate on test +# --------------------------- +print("๐Ÿง  Melatih model final dengan best params pada seluruh training set...") +best.fit(X_train, y_train_enc) # no early stopping here (we used CV) +y_pred_enc = best.predict(X_test) +y_proba = best.predict_proba(X_test) +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) +print("\n๐Ÿ“Š Evaluasi (hold-out test temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced acc. : {bal_acc*100:.2f}%") +print(f" - Log loss : {ll:.4f}") +print("\nClassification report:\n", classification_report(y_test, y_pred)) +print("Confusion matrix (rows true, cols pred):\n", confusion_matrix(y_test, y_pred)) + +# feature importance (XGBoost builtin) +fi = best.get_booster().get_score(importance_type='weight') +# map to readable list +fi_list = sorted(fi.items(), key=lambda x: x[1], reverse=True) +print("\nFeature importance (by weight):") +for f,v in fi_list: + print(f" {f}: {v}") +# Pastikan importance type diambil langsung dari booster +xgb.plot_importance(model, importance_type='weight', show_values=False) +plt.title("Feature Importance (by weight)") +plt.tight_layout() +plt.show() +# optional: plot top features +try: + names = [f for f in feat_cols] + import matplotlib.pyplot as plt + plt.figure(figsize=(8,5)) + # use sklearn style importance via gain if available + importances = [] + booster = best.get_booster() + gain = booster.get_score(importance_type='gain') + # map fscore names 'f0','f1' to feature names by index + # XGBoost's feature names are 'f0'.. so map + for i, name in enumerate(feat_cols): + key = f"f{i}" + importances.append(gain.get(key, 0.0)) + idx = np.argsort(importances)[::-1] + top_n = min(10, len(feat_cols)) + plt.bar([feat_cols[i] for i in idx[:top_n]], [importances[i] for i in idx[:top_n]]) + plt.xticks(rotation=45, ha='right') + plt.title("Feature importance (gain) - top features") + plt.tight_layout() + plt.show() +except Exception: + pass + +# --------------------------- +# 6. Save model & encoder +# --------------------------- +joblib.dump(best, "model_epl_v3.joblib") +joblib.dump(le, "labelencoder_v3.joblib") +print("\n๐Ÿ’พ Model & encoder disimpan: model_epl_v3.joblib, labelencoder_v3.joblib") + +# --------------------------- +# 7. Predict function using built incremental summaries (safe) +# --------------------------- +# Rebuild histories quickly up to last date (we already had histories built while feature building) +# But simpler: use last known aggregated stats in train+test (df_model tail) +last_summary = df_model.groupby('Home').tail(1) # not perfect; we'll build helper compute again from original df +# We'll implement compute_team_form_from_raw to compute based on raw df (only past matches) +def compute_team_form_from_raw(team, date, raw_df, window=ROLL_WINDOW): + past = raw_df[((raw_df['HomeTeam'] == team) | (raw_df['AwayTeam'] == team)) & (raw_df['Date'] < date)].tail(window) + if past.empty: + return None + gf, ga, wins = [], [], [] + for _, r in past.iterrows(): + if r['HomeTeam'] == team: + gf.append(int(r['FTHG'])); ga.append(int(r['FTAG']) +) + wins.append(1 if int(r['FTHG'])>int(r['FTAG']) else 0) + else: + gf.append(int(r['FTAG'])); ga.append(int(r['FTHG']) +) + wins.append(1 if int(r['FTAG'])>int(r['FTHG']) else 0) + return { + 'avg_gf': np.mean(gf), 'avg_ga': np.mean(ga), + 'win_rate': np.mean(wins), 'avg_gd': np.mean(np.array(gf)-np.array(ga)) + } + +def predict_match(home, away, model=best, le=le, raw_df=df_raw): + date = raw_df['Date'].max() + pd.Timedelta(days=1) + h = compute_team_form_from_raw(home, date, raw_df) + a = compute_team_form_from_raw(away, date, raw_df) + if h is None or a is None: + print("โš ๏ธ Tidak cukup history untuk salah satu tim.") + return None + # h2h + nk = tuple(sorted((home, away))) + ph = h2h_counts.get(nk, {'home_wins':0,'away_wins':0,'draws':0}) + # convert orientation + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + feats = [ + h['avg_gf'], h['avg_ga'], h['win_rate'], h['avg_gd'], + a['avg_gf'], a['avg_ga'], a['win_rate'], a['avg_gd'], + h_wins, a_wins, ph['draws'] + ] + # if shots included, extend (not implemented in this helper) + Xp = np.array(feats).reshape(1, -1) + probs = model.predict_proba(Xp)[0] + pred_enc = np.argmax(probs) + pred_label = le.inverse_transform([pred_enc])[0] + print(f"PREDIKSI: {home} vs {away} -> {pred_label}") + for idx, cls in enumerate(le.classes_): + print(f" {cls}: {probs[idx]*100:.2f}%") + return pred_label, probs + +# contoh prediksi +print("\n๐Ÿ”ฎ Contoh prediksi:") +try: + predict_match("Arsenal", "Man Utd") + predict_match("Liverpool", "Man City") + predict_match("Chelsea", "Tottenham") +except Exception as e: + print("Info: prediksi contoh gagal:", e) + +print("\nSelesai.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005194139.py b/.history/predict-pl-match_otomatis_v2_20251005194139.py new file mode 100644 index 0000000000000000000000000000000000000000..3ae8f4619ee7781a87395a7f4eb2135756b4857c --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005194139.py @@ -0,0 +1,396 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan fitur + tuning ringan + no-data-leakage +import pandas as pd +import numpy as np +import os +import warnings +warnings.filterwarnings("ignore") +from collections import deque, defaultdict + +# ML +from xgboost import XGBClassifier +from sklearn.preprocessing import LabelEncoder +from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV +from sklearn.metrics import accuracy_score, balanced_accuracy_score, classification_report, confusion_matrix, log_loss +import matplotlib.pyplot as plt +import joblib +import random +random.seed(42) +np.random.seed(42) + +# --------------------------- +# 0. Config +# --------------------------- +CSV = "epl-training.csv" +TEST_FROM_DATE = "2023-01-01" # split temporal (train < date, test >= date) +ROLL_WINDOW = 5 # jumlah match untuk rolling form +RANDOM_SEARCH_ITER = 20 # iterasi RandomizedSearch untuk tuning (ringan) +N_JOBS = -1 + +# --------------------------- +# 1. Load data & basic checks +# --------------------------- +if not os.path.exists(CSV): + raise FileNotFoundError(f"File '{CSV}' tidak ditemukan. Letakkan file di folder yg sama.") + +df_raw = pd.read_csv(CSV, low_memory=False) +# Normalisasi kolom tanggal (coba beberapa format) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], format=fmt) + break + except Exception: + pass +if df_raw['Date'].dtype == object: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], errors='coerce') + +df_raw = df_raw.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True) + +# columns we expect (some optional) +required_cols = ['HomeTeam', 'AwayTeam', 'FTHG', 'FTAG'] +optional_shot_cols = { + 'HomeShots': 'HS', 'AwayShots': 'AS', + 'HomeSOT': 'HST', 'AwaySOT': 'AST' +} +# rename standard +df = df_raw.copy() +df = df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + optional_shot_cols['HomeShots']: 'HomeShots' if optional_shot_cols['HomeShots'] in df.columns else None, +}) + +# fix optional renames carefully +for std_name, orig in optional_shot_cols.items(): + if orig in df.columns: + if std_name == 'HomeShots': + df = df.rename(columns={orig: 'HomeShots'}) + elif std_name == 'AwayShots': + df = df.rename(columns={orig: 'AwayShots'}) + elif std_name == 'HomeSOT': + df = df.rename(columns={orig: 'HomeSOT'}) + elif std_name == 'AwaySOT': + df = df.rename(columns={orig: 'AwaySOT'}) + +# ensure required present +for c in ['Home','Away','HomeGoals','AwayGoals']: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan di dataset. Pastikan file sesuai (Kaggle EPL).") + +print(f"๐Ÿ“Š Data dimuat: {df.shape[0]} baris ({df['Date'].min().date()} โ†’ {df['Date'].max().date()})") + +# --------------------------- +# 2. Build features incrementally (no leakage) +# --------------------------- +print("๐Ÿ”ง Membangun fitur secara incremental (no leakage)...") + +# histories stores past matches per team as deque of dicts +histories = defaultdict(lambda: deque(maxlen=ROLL_WINDOW)) +# h2h_counts stores counts for ordered pair (home,away) -> dict hwins, awins, draws +h2h_counts = defaultdict(lambda: {'home_wins':0, 'away_wins':0, 'draws':0}) + +rows = [] +for idx, row in df.iterrows(): + date = row['Date'] + home = row['Home'] + away = row['Away'] + hg = int(row['HomeGoals']) + ag = int(row['AwayGoals']) + + # compute home last5 stats using histories (only previous matches) + def stats_from_history(team): + past = list(histories[team]) # most recent last entries (old -> new) + if len(past) == 0: + return {'avg_gf': np.nan, 'avg_ga': np.nan, 'win_rate': np.nan, 'avg_gd': np.nan, + 'avg_shots': np.nan, 'avg_sot': np.nan} + gf = np.array([m['gf'] for m in past]) + ga = np.array([m['ga'] for m in past]) + res = np.array([m['win'] for m in past]) # 1 win else 0 + out = { + 'avg_gf': gf.mean(), + 'avg_ga': ga.mean(), + 'win_rate': res.mean(), + 'avg_gd': (gf - ga).mean() + } + # optional shots + if 'shots' in past[0]: + out['avg_shots'] = np.mean([m.get('shots', np.nan) for m in past]) + else: + out['avg_shots'] = np.nan + if 'sot' in past[0]: + out['avg_sot'] = np.mean([m.get('sot', np.nan) for m in past]) + else: + out['avg_sot'] = np.nan + return out + + home_stats = stats_from_history(home) + away_stats = stats_from_history(away) + + # h2h counts BEFORE this match (key sorted to preserve pair) + pair_key = (home, away) # orientation matters; we'll store as ordered key + # get aggregated previous results for this specific pairing (both orientations) + # We'll compute counts from h2h_counts[(min, max)] but it's easier to compute by scanning earlier matches? + # To avoid heavy scanning, we also maintain a dict of pair->counts where pair is (home,away) normalized (A,B) + norm_key = tuple(sorted((home, away))) + pair_hist = h2h_counts[norm_key] # counts aggregated irrespective of venue + # But need orientation: how many times home (current 'home') won vs away won vs draws + # We'll store aggregated as counts where home_wins means wins by the first element of norm_key + # So to convert to current orientation: + if norm_key[0] == home: + # then pair_hist['home_wins'] refers to wins when norm_key[0] was home in those past matches + # but we want wins by current 'home' regardless of which side in norm_key + h_wins = pair_hist['home_wins'] + a_wins = pair_hist['away_wins'] + else: + # norm_key[0] is away, so swap + h_wins = pair_hist['away_wins'] + a_wins = pair_hist['home_wins'] + draws = pair_hist['draws'] + + # Build feature row (use simple features) + feat = { + 'Date': date, + 'Home': home, 'Away': away, + # home features + 'home_avg_gf': home_stats['avg_gf'], 'home_avg_ga': home_stats['avg_ga'], + 'home_win_rate': home_stats['win_rate'], 'home_avg_gd': home_stats['avg_gd'], + 'home_avg_shots': home_stats['avg_shots'], 'home_avg_sot': home_stats['avg_sot'], + # away features + 'away_avg_gf': away_stats['avg_gf'], 'away_avg_ga': away_stats['avg_ga'], + 'away_win_rate': away_stats['win_rate'], 'away_avg_gd': away_stats['avg_gd'], + 'away_avg_shots': away_stats['avg_shots'], 'away_avg_sot': away_stats['avg_sot'], + # h2h + 'h2h_home_wins': h_wins, 'h2h_away_wins': a_wins, 'h2h_draws': draws, + # target + 'HomeGoals': hg, 'AwayGoals': ag + } + rows.append(feat) + + # AFTER constructing features for this match, update histories & h2h_counts with this match + # update home history entry (perspective of team) + home_entry = {'gf': hg, 'ga': ag, 'win': 1 if hg>ag else 0, 'shots': row.get('HomeShots', np.nan), 'sot': row.get('HomeSOT', np.nan)} + away_entry = {'gf': ag, 'ga': hg, 'win': 1 if ag>hg else 0, 'shots': row.get('AwayShots', np.nan), 'sot': row.get('AwaySOT', np.nan)} + histories[home].append(home_entry) + histories[away].append(away_entry) + + # update h2h aggregated counts at norm_key + # determine result + if hg > ag: + # home (current home) won -> increment for the team who was home in this match orientation of norm_key + if norm_key[0] == home: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['away_wins'] += 1 + elif ag > hg: + if norm_key[0] == home: + h2h_counts[norm_key]['away_wins'] += 1 + else: + h2h_counts[norm_key]['home_wins'] += 1 + else: + h2h_counts[norm_key]['draws'] += 1 + +features_df = pd.DataFrame(rows) +# target label +features_df['Result'] = np.select( + [features_df['HomeGoals'] > features_df['AwayGoals'], + features_df['HomeGoals'] < features_df['AwayGoals']], + ['Home','Away'], default='Draw' +) + +# drop rows where features missing (teams with no previous history) +before = features_df.shape[0] +features_df = features_df.dropna(subset=[ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd' +]).reset_index(drop=True) +after = features_df.shape[0] +print(f" -> Baris awal: {before}, setelah drop missing features: {after}") + +# --------------------------- +# 3. Prepare X, y and temporal split +# --------------------------- +# Decide feature columns (exclude raw goals) +feat_cols = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +# optionally include shots if available (check columns) +if 'home_avg_shots' in features_df.columns and features_df['home_avg_shots'].notna().any(): + feat_cols += ['home_avg_shots','away_avg_shots'] +if 'home_avg_sot' in features_df.columns and features_df['home_avg_sot'].notna().any(): + feat_cols += ['home_avg_sot','away_avg_sot'] + +df_model = features_df.copy() +df_model = df_model.sort_values('Date').reset_index(drop=True) + +train_df = df_model[df_model['Date'] < pd.to_datetime(TEST_FROM_DATE)] +test_df = df_model[df_model['Date'] >= pd.to_datetime(TEST_FROM_DATE)] +print(f"๐Ÿ“… Train rows: {train_df.shape[0]} | Test rows: {test_df.shape[0]}") + +X_train = train_df[feat_cols].astype(float) +X_test = test_df[feat_cols].astype(float) +y_train = train_df['Result'].astype(str) +y_test = test_df['Result'].astype(str) + +# encode labels +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) +print("๐Ÿ” LabelEncoder classes:", le.classes_) + +# --------------------------- +# 4. Hyperparameter tuning (RandomizedSearchCV with TimeSeriesSplit) +# --------------------------- +print("โš™๏ธ Randomized hyperparameter search (light)...") +base = XGBClassifier(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=42) + +param_dist = { + 'n_estimators': [100,200,300,400], + 'max_depth': [3,4,5,6], + 'learning_rate': [0.01,0.03,0.05,0.1], + 'subsample': [0.6,0.8,1.0], + 'colsample_bytree': [0.6,0.8,1.0], + 'reg_lambda': [0.5,1.0,1.5,2.0], + 'reg_alpha': [0.0,0.25,0.5,1.0] +} + +tscv = TimeSeriesSplit(n_splits=4) +rnd = RandomizedSearchCV( + estimator=base, + param_distributions=param_dist, + n_iter=RANDOM_SEARCH_ITER, + scoring='balanced_accuracy', + cv=tscv, + verbose=1, + n_jobs=N_JOBS, + random_state=42 +) +rnd.fit(X_train, y_train_enc) +print("โœ… Best params:", rnd.best_params_) +best = rnd.best_estimator_ + +# --------------------------- +# 5. Final train on full training set, evaluate on test +# --------------------------- +print("๐Ÿง  Melatih model final dengan best params pada seluruh training set...") +best.fit(X_train, y_train_enc) # no early stopping here (we used CV) +y_pred_enc = best.predict(X_test) +y_proba = best.predict_proba(X_test) +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) +print("\n๐Ÿ“Š Evaluasi (hold-out test temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced acc. : {bal_acc*100:.2f}%") +print(f" - Log loss : {ll:.4f}") +print("\nClassification report:\n", classification_report(y_test, y_pred)) +print("Confusion matrix (rows true, cols pred):\n", confusion_matrix(y_test, y_pred)) + +# feature importance (XGBoost builtin) +fi = best.get_booster().get_score(importance_type='weight') +# map to readable list +fi_list = sorted(fi.items(), key=lambda x: x[1], reverse=True) +print("\nFeature importance (by weight):") +for f,v in fi_list: + print(f" {f}: {v}") + +# optional: plot top features +try: + names = [f for f in feat_cols] + import matplotlib.pyplot as plt + plt.figure(figsize=(8,5)) + # use sklearn style importance via gain if available + importances = [] + booster = best.get_booster() + gain = booster.get_score(importance_type='gain') + # map fscore names 'f0','f1' to feature names by index + # XGBoost's feature names are 'f0'.. so map + for i, name in enumerate(feat_cols): + key = f"f{i}" + importances.append(gain.get(key, 0.0)) + idx = np.argsort(importances)[::-1] + top_n = min(10, len(feat_cols)) + plt.bar([feat_cols[i] for i in idx[:top_n]], [importances[i] for i in idx[:top_n]]) + plt.xticks(rotation=45, ha='right') + plt.title("Feature importance (gain) - top features") + plt.tight_layout() + plt.show() +except Exception: + pass + +# --------------------------- +# 6. Save model & encoder +# --------------------------- +joblib.dump(best, "model_epl_v3.joblib") +joblib.dump(le, "labelencoder_v3.joblib") +print("\n๐Ÿ’พ Model & encoder disimpan: model_epl_v3.joblib, labelencoder_v3.joblib") + +# --------------------------- +# 7. Predict function using built incremental summaries (safe) +# --------------------------- +# Rebuild histories quickly up to last date (we already had histories built while feature building) +# But simpler: use last known aggregated stats in train+test (df_model tail) +last_summary = df_model.groupby('Home').tail(1) # not perfect; we'll build helper compute again from original df +# We'll implement compute_team_form_from_raw to compute based on raw df (only past matches) +def compute_team_form_from_raw(team, date, raw_df, window=ROLL_WINDOW): + past = raw_df[((raw_df['HomeTeam'] == team) | (raw_df['AwayTeam'] == team)) & (raw_df['Date'] < date)].tail(window) + if past.empty: + return None + gf, ga, wins = [], [], [] + for _, r in past.iterrows(): + if r['HomeTeam'] == team: + gf.append(int(r['FTHG'])); ga.append(int(r['FTAG']) +) + wins.append(1 if int(r['FTHG'])>int(r['FTAG']) else 0) + else: + gf.append(int(r['FTAG'])); ga.append(int(r['FTHG']) +) + wins.append(1 if int(r['FTAG'])>int(r['FTHG']) else 0) + return { + 'avg_gf': np.mean(gf), 'avg_ga': np.mean(ga), + 'win_rate': np.mean(wins), 'avg_gd': np.mean(np.array(gf)-np.array(ga)) + } + +def predict_match(home, away, model=best, le=le, raw_df=df_raw): + date = raw_df['Date'].max() + pd.Timedelta(days=1) + h = compute_team_form_from_raw(home, date, raw_df) + a = compute_team_form_from_raw(away, date, raw_df) + if h is None or a is None: + print("โš ๏ธ Tidak cukup history untuk salah satu tim.") + return None + # h2h + nk = tuple(sorted((home, away))) + ph = h2h_counts.get(nk, {'home_wins':0,'away_wins':0,'draws':0}) + # convert orientation + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + feats = [ + h['avg_gf'], h['avg_ga'], h['win_rate'], h['avg_gd'], + a['avg_gf'], a['avg_ga'], a['win_rate'], a['avg_gd'], + h_wins, a_wins, ph['draws'] + ] + # if shots included, extend (not implemented in this helper) + Xp = np.array(feats).reshape(1, -1) + probs = model.predict_proba(Xp)[0] + pred_enc = np.argmax(probs) + pred_label = le.inverse_transform([pred_enc])[0] + print(f"PREDIKSI: {home} vs {away} -> {pred_label}") + for idx, cls in enumerate(le.classes_): + print(f" {cls}: {probs[idx]*100:.2f}%") + return pred_label, probs + +# contoh prediksi +print("\n๐Ÿ”ฎ Contoh prediksi:") +try: + predict_match("Arsenal", "Man Utd") + predict_match("Liverpool", "Man City") + predict_match("Chelsea", "Tottenham") +except Exception as e: + print("Info: prediksi contoh gagal:", e) + +print("\nSelesai.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005194336.py b/.history/predict-pl-match_otomatis_v2_20251005194336.py new file mode 100644 index 0000000000000000000000000000000000000000..9ecb281215834725f83e32c89ddf9f1de71527dd --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005194336.py @@ -0,0 +1,425 @@ +# predict-pl-match_v3_1_optimized.py +""" +Predict PL v3.1 - Optimized, no-data-leakage, time-aware CV, Optuna tuning (if available). +Requirements: pandas, numpy, scikit-learn, xgboost, joblib, matplotlib +Optional: optuna +""" +import os +import warnings +warnings.filterwarnings("ignore") + +import pandas as pd +import numpy as np +from collections import defaultdict, deque +from sklearn.preprocessing import LabelEncoder +from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV +from sklearn.metrics import accuracy_score, balanced_accuracy_score, classification_report, confusion_matrix, log_loss +import joblib +import matplotlib.pyplot as plt + +# ML model +from xgboost import XGBClassifier + +# Try Optuna +USE_OPTUNA = False +try: + import optuna + from optuna.samplers import TPESampler + USE_OPTUNA = True +except Exception: + USE_OPTUNA = False + +RANDOM_SEED = 42 +np.random.seed(RANDOM_SEED) + +# ------------------------- +# Config +# ------------------------- +CSV = "epl-training.csv" +TEST_SPLIT_DATE = "2023-01-01" +ROLL_WINDOW = 5 +OPTUNA_TRIALS = 40 # if optuna available +RANDOM_SEARCH_ITERS = 30 # fallback tuning iterations + +# ------------------------- +# 1. Load & normalize data +# ------------------------- +if not os.path.exists(CSV): + raise FileNotFoundError(f"File '{CSV}' tidak ditemukan.") + +df_raw = pd.read_csv(CSV, low_memory=False) + +# try common date formats +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], format=fmt) + break + except Exception: + pass +if df_raw['Date'].dtype == object: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], errors='coerce') + +df_raw = df_raw.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True) +print("๐Ÿ“Š Data dimuat:", df_raw.shape, df_raw['Date'].min().date(), "โ†’", df_raw['Date'].max().date()) + +# rename columns to expected ones +rename_map = {} +if 'HomeTeam' in df_raw.columns: rename_map['HomeTeam'] = 'Home' +if 'AwayTeam' in df_raw.columns: rename_map['AwayTeam'] = 'Away' +if 'FTHG' in df_raw.columns: rename_map['FTHG'] = 'HomeGoals' +if 'FTAG' in df_raw.columns: rename_map['FTAG'] = 'AwayGoals' +if 'HS' in df_raw.columns: rename_map['HS'] = 'HomeShots' +if 'AS' in df_raw.columns: rename_map['AS'] = 'AwayShots' +if 'HST' in df_raw.columns: rename_map['HST'] = 'HomeSOT' +if 'AST' in df_raw.columns: rename_map['AST'] = 'AwaySOT' +df_raw = df_raw.rename(columns=rename_map) + +required = ['Date','Home','Away','HomeGoals','AwayGoals'] +for c in required: + if c not in df_raw.columns: + raise KeyError(f"Kolom '{c}' tidak ditemukan. Pastikan dataset EPL (Kaggle) yang benar.") + +# ------------------------- +# 2. Incremental feature build (no leakage) +# ------------------------- +print("๐Ÿ”ง Membangun fitur incremental (no leakage)...") +histories = defaultdict(lambda: deque(maxlen=ROLL_WINDOW)) +h2h_counts = defaultdict(lambda: {'home_wins':0,'away_wins':0,'draws':0}) +rows = [] + +for idx, r in df_raw.iterrows(): + date = r['Date'] + home = r['Home'] + away = r['Away'] + hg = int(r['HomeGoals']) + ag = int(r['AwayGoals']) + + # helper to get last N stats for a team + def get_stats(team): + past = list(histories[team]) + if len(past) == 0: + return {'avg_gf':np.nan,'avg_ga':np.nan,'win_rate':np.nan,'avg_gd':np.nan,'avg_shots':np.nan,'avg_sot':np.nan} + gf = np.array([m['gf'] for m in past]) + ga = np.array([m['ga'] for m in past]) + wins = np.array([m['win'] for m in past]) + out = { + 'avg_gf': float(np.mean(gf)), + 'avg_ga': float(np.mean(ga)), + 'win_rate': float(np.mean(wins)), + 'avg_gd': float(np.mean(gf - ga)) + } + if 'shots' in past[0]: + out['avg_shots'] = float(np.nanmean([m.get('shots', np.nan) for m in past])) + else: + out['avg_shots'] = np.nan + if 'sot' in past[0]: + out['avg_sot'] = float(np.nanmean([m.get('sot', np.nan) for m in past])) + else: + out['avg_sot'] = np.nan + return out + + home_stats = get_stats(home) + away_stats = get_stats(away) + + # normalized pair key + nk = tuple(sorted((home, away))) + ph = h2h_counts[nk] + # orientation mapping + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + draws = ph['draws'] + + rows.append({ + 'Date': date, 'Home': home, 'Away': away, + # home + 'home_avg_gf': home_stats['avg_gf'], 'home_avg_ga': home_stats['avg_ga'], + 'home_win_rate': home_stats['win_rate'], 'home_avg_gd': home_stats['avg_gd'], + 'home_avg_shots': home_stats['avg_shots'], 'home_avg_sot': home_stats['avg_sot'], + # away + 'away_avg_gf': away_stats['avg_gf'], 'away_avg_ga': away_stats['avg_ga'], + 'away_win_rate': away_stats['win_rate'], 'away_avg_gd': away_stats['avg_gd'], + 'away_avg_shots': away_stats['avg_shots'], 'away_avg_sot': away_stats['avg_sot'], + # h2h + 'h2h_home_wins': h_wins, 'h2h_away_wins': a_wins, 'h2h_draws': draws, + # targets (raw) + 'HomeGoals': hg, 'AwayGoals': ag + }) + + # update histories AFTER features built + home_entry = {'gf': hg, 'ga': ag, 'win': 1 if hg>ag else 0, + 'shots': r.get('HomeShots', np.nan), 'sot': r.get('HomeSOT', np.nan)} + away_entry = {'gf': ag, 'ga': hg, 'win': 1 if ag>hg else 0, + 'shots': r.get('AwayShots', np.nan), 'sot': r.get('AwaySOT', np.nan)} + histories[home].append(home_entry) + histories[away].append(away_entry) + + # update h2h aggregated counts (norm key) + if hg > ag: + # winner is home in this match + if nk[0] == home: + h2h_counts[nk]['home_wins'] += 1 + else: + h2h_counts[nk]['away_wins'] += 1 + elif ag > hg: + if nk[0] == home: + h2h_counts[nk]['away_wins'] += 1 + else: + h2h_counts[nk]['home_wins'] += 1 + else: + h2h_counts[nk]['draws'] += 1 + +features_df = pd.DataFrame(rows) +# target label +features_df['Result'] = np.select( + [features_df['HomeGoals'] > features_df['AwayGoals'], + features_df['HomeGoals'] < features_df['AwayGoals']], + ['Home','Away'], default='Draw' +) + +# drop rows missing core features +core_feats = ['home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd'] +before = features_df.shape[0] +features_df = features_df.dropna(subset=core_feats).reset_index(drop=True) +after = features_df.shape[0] +print(f" -> baris awal: {before}, setelah drop missing: {after}") + +# ------------------------- +# 3. Prepare X,y and split (temporal) +# ------------------------- +feat_cols = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +# include shots if available & not all NaN +if features_df['home_avg_shots'].notna().any(): + feat_cols += ['home_avg_shots','away_avg_shots'] +if features_df['home_avg_sot'].notna().any(): + feat_cols += ['home_avg_sot','away_avg_sot'] + +df_model = features_df.sort_values('Date').reset_index(drop=True) +train_df = df_model[df_model['Date'] < pd.to_datetime(TEST_SPLIT_DATE)].copy() +test_df = df_model[df_model['Date'] >= pd.to_datetime(TEST_SPLIT_DATE)].copy() +print("๐Ÿ“… Train rows:", train_df.shape[0], "Test rows:", test_df.shape[0]) + +X_train = train_df[feat_cols].fillna(0).astype(float) +X_test = test_df[feat_cols].fillna(0).astype(float) +y_train = train_df['Result'].astype(str) +y_test = test_df['Result'].astype(str) + +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) +print("๐Ÿ” Label classes:", list(le.classes_)) + +# compute simple sample weights to rebalance classes +from sklearn.utils.class_weight import compute_class_weight +classes = np.unique(y_train_enc) +cw = compute_class_weight(class_weight='balanced', classes=classes, y=y_train_enc) +# map class code -> weight +class_weight_map = {cls: w for cls,w in zip(classes, cw)} +sample_weights = np.array([class_weight_map[c] for c in y_train_enc]) + +# ------------------------- +# 4. Hyperparameter tuning (Optuna or RandomizedSearchCV) +# ------------------------- +print("โš™๏ธ Mulai hyperparameter tuning... (optuna available:", USE_OPTUNA, ")") + +def objective_optuna(trial): + params = { + 'verbosity': 0, + 'use_label_encoder': False, + 'objective': 'multi:softprob', + 'eval_metric': 'mlogloss', + 'random_state': RANDOM_SEED, + 'n_estimators': trial.suggest_categorical('n_estimators', [100,200,300,400]), + 'max_depth': trial.suggest_int('max_depth', 3, 6), + 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2, log=True), + 'subsample': trial.suggest_float('subsample', 0.6, 1.0), + 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0), + 'reg_lambda': trial.suggest_float('reg_lambda', 0.1, 2.0), + 'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 1.0) + } + model = XGBClassifier(**params) + tscv = TimeSeriesSplit(n_splits=4) + scores = [] + for tr_idx, val_idx in tscv.split(X_train): + Xtr, Xval = X_train.iloc[tr_idx], X_train.iloc[val_idx] + ytr, yval = y_train_enc[tr_idx], y_train_enc[val_idx] + sw = sample_weights[tr_idx] + # fit with early stopping if possible + try: + model.fit(Xtr, ytr, sample_weight=sw, eval_set=[(Xval, yval)], early_stopping_rounds=30, verbose=False) + except TypeError: + model.fit(Xtr, ytr, sample_weight=sw) + ypred = model.predict(Xval) + scores.append(balanced_accuracy_score(yval, ypred)) + return np.mean(scores) + +best_params = None +if USE_OPTUNA: + sampler = TPESampler(seed=RANDOM_SEED) + study = optuna.create_study(direction='maximize', sampler=sampler) + study.optimize(objective_optuna, n_trials=OPTUNA_TRIALS, show_progress_bar=True) + best_params = study.best_trial.params + print("โœ… Optuna best params:", best_params) +else: + # fallback RandomizedSearchCV (time-aware implemented via cv=TimeSeriesSplit inside search) + param_dist = { + 'n_estimators': [100,200,300,400], + 'max_depth': [3,4,5,6], + 'learning_rate': [0.01,0.03,0.05,0.1,0.2], + 'subsample': [0.6,0.8,1.0], + 'colsample_bytree': [0.6,0.8,1.0], + 'reg_lambda': [0.1,0.5,1.0,1.5], + 'reg_alpha': [0.0,0.25,0.5,1.0] + } + base = XGBClassifier(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=RANDOM_SEED) + rnd = RandomizedSearchCV(base, param_distributions=param_dist, n_iter=RANDOM_SEARCH_ITERS, + scoring='balanced_accuracy', cv=TimeSeriesSplit(n_splits=4), n_jobs=-1, verbose=1, random_state=RANDOM_SEED) + rnd.fit(X_train, y_train_enc, sample_weight=sample_weights) + best_params = rnd.best_params_ + print("โœ… RandomizedSearchCV best params:", best_params) + +# ------------------------- +# 5. Train final model +# ------------------------- +print("๐Ÿง  Melatih final model dengan best params...") +final_kwargs = dict(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=RANDOM_SEED) +# if optuna, best_params contains only param keys; else RandomizedSearchCV gave 'clf__' style? we already set best_params +final_kwargs.update(best_params) + +model = XGBClassifier(**final_kwargs) +try: + model.fit(X_train, y_train_enc, sample_weight=sample_weights, eval_set=[(X_test, y_test_enc)], early_stopping_rounds=50, verbose=False) +except TypeError: + model.fit(X_train, y_train_enc, sample_weight=sample_weights) + +# ------------------------- +# 6. Evaluate +# ------------------------- +y_pred_enc = model.predict(X_test) +y_proba = model.predict_proba(X_test) +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) + +print("\n๐Ÿ“Š Evaluasi (hold-out test temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced acc.: {bal_acc*100:.2f}%") +print(f" - Log loss : {ll:.4f}") +print("\nClassification report:\n", classification_report(y_test, y_pred)) +print("Confusion matrix:\n", confusion_matrix(y_test, y_pred)) + +# ------------------------- +# 7. Feature importance (plot) +# ------------------------- +print("\n๐Ÿ”Ž Feature importance (gain):") +booster = model.get_booster() +fscore = booster.get_score(importance_type='gain') # use 'gain' +# Map 'f0'.. to feature names +mapped = {} +for k, v in fscore.items(): + if k.startswith('f'): + idx = int(k[1:]) + if idx < len(feat_cols): + mapped[feat_cols[idx]] = v + else: + mapped[k] = v +# sort and print +fi_sorted = sorted(mapped.items(), key=lambda x: x[1], reverse=True) +for name, val in fi_sorted: + print(f" {name}: {val:.4f}") + +# plot top features +try: + top_n = min(12, len(fi_sorted)) + names = [n for n,_ in fi_sorted[:top_n]] + vals = [v for _,v in fi_sorted[:top_n]] + plt.figure(figsize=(8,5)) + plt.barh(names[::-1], vals[::-1]) + plt.title("Feature importance (gain) - top features") + plt.tight_layout() + plt.show() +except Exception: + pass + +# ------------------------- +# 8. Save model & encoder & feature columns +# ------------------------- +joblib.dump(model, "model_epl_v3_1.joblib") +joblib.dump(le, "labelencoder_v3_1.joblib") +joblib.dump(feat_cols, "feature_columns_v3_1.joblib") +print("\n๐Ÿ’พ Model, encoder, dan feature columns disimpan.") + +# ------------------------- +# 9. Safe predict_match helper +# ------------------------- +def compute_team_form_from_raw(team, date, raw_df, window=ROLL_WINDOW): + past = raw_df[((raw_df['HomeTeam'] == team) | (raw_df['AwayTeam'] == team)) & (raw_df['Date'] < date)].tail(window) + if past.empty: + return None + gf, ga, wins = [], [], [] + for _, r in past.iterrows(): + if r['HomeTeam'] == team: + gf.append(int(r['FTHG'])); ga.append(int(r['FTAG'])) + wins.append(1 if int(r['FTHG']) > int(r['FTAG']) else 0) + else: + gf.append(int(r['FTAG'])); ga.append(int(r['FTHG'])) + wins.append(1 if int(r['FTAG']) > int(r['FTHG']) else 0) + return { + 'avg_gf': np.mean(gf), 'avg_ga': np.mean(ga), + 'win_rate': np.mean(wins), 'avg_gd': np.mean(np.array(gf)-np.array(ga)) + } + +def predict_match_safe(home, away, model=model, le=le, feat_cols=feat_cols, raw_df=df_raw, allow_fill=True): + date = raw_df['Date'].max() + pd.Timedelta(days=1) + hf = compute_team_form_from_raw(home, date, raw_df) + af = compute_team_form_from_raw(away, date, raw_df) + if hf is None or af is None: + print("โš ๏ธ Tidak cukup history untuk salah satu tim.") + return None + nk = tuple(sorted((home, away))) + ph = h2h_counts.get(nk, {'home_wins':0,'away_wins':0,'draws':0}) + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + feats = [ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['avg_gd'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['avg_gd'], + h_wins, a_wins, ph['draws'] + ] + # include shot/sot if model used them + if 'home_avg_shots' in feat_cols: + feats += [hf.get('avg_shots', 0.0), af.get('avg_shots', 0.0)] + if 'home_avg_sot' in feat_cols: + feats += [hf.get('avg_sot', 0.0), af.get('avg_sot', 0.0)] + Xp = pd.DataFrame([feats], columns=feat_cols) + # fill missing with 0 for safety + Xp = Xp.fillna(0).astype(float) + proba = model.predict_proba(Xp)[0] + pred_enc = int(np.argmax(proba)) + pred_label = le.inverse_transform([pred_enc])[0] + print(f"\n๐Ÿ”ฎ PREDIKSI: {home} vs {away} -> {pred_label}") + for i, cls in enumerate(le.classes_): + print(f" {cls}: {proba[i]*100:.2f}%") + return pred_label, proba + +# ------------------------- +# 10. Example predictions +# ------------------------- +print("\n๐Ÿ”ฎ Contoh prediksi (safe):") +try: + predict_match_safe("Arsenal", "Man Utd") + predict_match_safe("Liverpool", "Man City") + predict_match_safe("Chelsea", "Tottenham") +except Exception as e: + print("Info: contoh prediksi gagal:", e) + +print("\nSelesai.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005194745.py b/.history/predict-pl-match_otomatis_v2_20251005194745.py new file mode 100644 index 0000000000000000000000000000000000000000..df053955c16cf83a8a266be00e0015da8bbccd3a --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005194745.py @@ -0,0 +1,589 @@ +# predict-pl-match_v3_1_optimized.py +""" +Predict PL v3.1 - Optimized, no-data-leakage, time-aware CV, Optuna tuning (if available). +Requirements: pandas, numpy, scikit-learn, xgboost, joblib, matplotlib +Optional: optuna +""" +import os +import warnings +warnings.filterwarnings("ignore") + +import pandas as pd +import numpy as np +from collections import defaultdict, deque +from sklearn.preprocessing import LabelEncoder +from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV +from sklearn.metrics import accuracy_score, balanced_accuracy_score, classification_report, confusion_matrix, log_loss +import joblib +import matplotlib.pyplot as plt + +# ML model +from xgboost import XGBClassifier + +# Try Optuna +USE_OPTUNA = False +try: + import optuna + from optuna.samplers import TPESampler + USE_OPTUNA = True +except Exception: + USE_OPTUNA = False + +RANDOM_SEED = 42 +np.random.seed(RANDOM_SEED) + +# ------------------------- +# Config +# ------------------------- +CSV = "epl-training.csv" +TEST_SPLIT_DATE = "2023-01-01" +ROLL_WINDOW = 5 +OPTUNA_TRIALS = 40 # if optuna available +RANDOM_SEARCH_ITERS = 30 # fallback tuning iterations + +# ------------------------- +# 1. Load & normalize data +# ------------------------- +if not os.path.exists(CSV): + raise FileNotFoundError(f"File '{CSV}' tidak ditemukan.") + +df_raw = pd.read_csv(CSV, low_memory=False) + +# try common date formats +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], format=fmt) + break + except Exception: + pass +if df_raw['Date'].dtype == object: + df_raw['Date'] = pd.to_datetime(df_raw['Date'], errors='coerce') + +df_raw = df_raw.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True) +print("๐Ÿ“Š Data dimuat:", df_raw.shape, df_raw['Date'].min().date(), "โ†’", df_raw['Date'].max().date()) + +# rename columns to expected ones +rename_map = {} +if 'HomeTeam' in df_raw.columns: rename_map['HomeTeam'] = 'Home' +if 'AwayTeam' in df_raw.columns: rename_map['AwayTeam'] = 'Away' +if 'FTHG' in df_raw.columns: rename_map['FTHG'] = 'HomeGoals' +if 'FTAG' in df_raw.columns: rename_map['FTAG'] = 'AwayGoals' +if 'HS' in df_raw.columns: rename_map['HS'] = 'HomeShots' +if 'AS' in df_raw.columns: rename_map['AS'] = 'AwayShots' +if 'HST' in df_raw.columns: rename_map['HST'] = 'HomeSOT' +if 'AST' in df_raw.columns: rename_map['AST'] = 'AwaySOT' +df_raw = df_raw.rename(columns=rename_map) + +required = ['Date','Home','Away','HomeGoals','AwayGoals'] +for c in required: + if c not in df_raw.columns: + raise KeyError(f"Kolom '{c}' tidak ditemukan. Pastikan dataset EPL (Kaggle) yang benar.") + +# ------------------------- +# 2. Incremental feature build (no leakage) +# ------------------------- +print("๐Ÿ”ง Membangun fitur incremental (no leakage)...") +histories = defaultdict(lambda: deque(maxlen=ROLL_WINDOW)) +h2h_counts = defaultdict(lambda: {'home_wins':0,'away_wins':0,'draws':0}) +rows = [] + +for idx, r in df_raw.iterrows(): + date = r['Date'] + home = r['Home'] + away = r['Away'] + hg = int(r['HomeGoals']) + ag = int(r['AwayGoals']) + + # helper to get last N stats for a team + def get_stats(team): + past = list(histories[team]) + if len(past) == 0: + return {'avg_gf':np.nan,'avg_ga':np.nan,'win_rate':np.nan,'avg_gd':np.nan,'avg_shots':np.nan,'avg_sot':np.nan} + gf = np.array([m['gf'] for m in past]) + ga = np.array([m['ga'] for m in past]) + wins = np.array([m['win'] for m in past]) + out = { + 'avg_gf': float(np.mean(gf)), + 'avg_ga': float(np.mean(ga)), + 'win_rate': float(np.mean(wins)), + 'avg_gd': float(np.mean(gf - ga)) + } + if 'shots' in past[0]: + out['avg_shots'] = float(np.nanmean([m.get('shots', np.nan) for m in past])) + else: + out['avg_shots'] = np.nan + if 'sot' in past[0]: + out['avg_sot'] = float(np.nanmean([m.get('sot', np.nan) for m in past])) + else: + out['avg_sot'] = np.nan + return out + + home_stats = get_stats(home) + away_stats = get_stats(away) + + # normalized pair key + nk = tuple(sorted((home, away))) + ph = h2h_counts[nk] + # orientation mapping + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + draws = ph['draws'] + + rows.append({ + 'Date': date, 'Home': home, 'Away': away, + # home + 'home_avg_gf': home_stats['avg_gf'], 'home_avg_ga': home_stats['avg_ga'], + 'home_win_rate': home_stats['win_rate'], 'home_avg_gd': home_stats['avg_gd'], + 'home_avg_shots': home_stats['avg_shots'], 'home_avg_sot': home_stats['avg_sot'], + # away + 'away_avg_gf': away_stats['avg_gf'], 'away_avg_ga': away_stats['avg_ga'], + 'away_win_rate': away_stats['win_rate'], 'away_avg_gd': away_stats['avg_gd'], + 'away_avg_shots': away_stats['avg_shots'], 'away_avg_sot': away_stats['avg_sot'], + # h2h + 'h2h_home_wins': h_wins, 'h2h_away_wins': a_wins, 'h2h_draws': draws, + # targets (raw) + 'HomeGoals': hg, 'AwayGoals': ag + }) + + # update histories AFTER features built + home_entry = {'gf': hg, 'ga': ag, 'win': 1 if hg>ag else 0, + 'shots': r.get('HomeShots', np.nan), 'sot': r.get('HomeSOT', np.nan)} + away_entry = {'gf': ag, 'ga': hg, 'win': 1 if ag>hg else 0, + 'shots': r.get('AwayShots', np.nan), 'sot': r.get('AwaySOT', np.nan)} + histories[home].append(home_entry) + histories[away].append(away_entry) + + # update h2h aggregated counts (norm key) + if hg > ag: + # winner is home in this match + if nk[0] == home: + h2h_counts[nk]['home_wins'] += 1 + else: + h2h_counts[nk]['away_wins'] += 1 + elif ag > hg: + if nk[0] == home: + h2h_counts[nk]['away_wins'] += 1 + else: + h2h_counts[nk]['home_wins'] += 1 + else: + h2h_counts[nk]['draws'] += 1 + +features_df = pd.DataFrame(rows) +# target label +features_df['Result'] = np.select( + [features_df['HomeGoals'] > features_df['AwayGoals'], + features_df['HomeGoals'] < features_df['AwayGoals']], + ['Home','Away'], default='Draw' +) + +# drop rows missing core features +core_feats = ['home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd'] +before = features_df.shape[0] +features_df = features_df.dropna(subset=core_feats).reset_index(drop=True) +after = features_df.shape[0] +print(f" -> baris awal: {before}, setelah drop missing: {after}") + +# ------------------------- +# 3. Prepare X,y and split (temporal) +# ------------------------- +feat_cols = [ + 'home_avg_gf','home_avg_ga','home_win_rate','home_avg_gd', + 'away_avg_gf','away_avg_ga','away_win_rate','away_avg_gd', + 'h2h_home_wins','h2h_away_wins','h2h_draws' +] +# include shots if available & not all NaN +if features_df['home_avg_shots'].notna().any(): + feat_cols += ['home_avg_shots','away_avg_shots'] +if features_df['home_avg_sot'].notna().any(): + feat_cols += ['home_avg_sot','away_avg_sot'] + +df_model = features_df.sort_values('Date').reset_index(drop=True) +train_df = df_model[df_model['Date'] < pd.to_datetime(TEST_SPLIT_DATE)].copy() +test_df = df_model[df_model['Date'] >= pd.to_datetime(TEST_SPLIT_DATE)].copy() +print("๐Ÿ“… Train rows:", train_df.shape[0], "Test rows:", test_df.shape[0]) + +X_train = train_df[feat_cols].fillna(0).astype(float) +X_test = test_df[feat_cols].fillna(0).astype(float) +y_train = train_df['Result'].astype(str) +y_test = test_df['Result'].astype(str) + +le = LabelEncoder() +y_train_enc = le.fit_transform(y_train) +y_test_enc = le.transform(y_test) +print("๐Ÿ” Label classes:", list(le.classes_)) + +# compute simple sample weights to rebalance classes +from sklearn.utils.class_weight import compute_class_weight +classes = np.unique(y_train_enc) +cw = compute_class_weight(class_weight='balanced', classes=classes, y=y_train_enc) +# map class code -> weight +class_weight_map = {cls: w for cls,w in zip(classes, cw)} +sample_weights = np.array([class_weight_map[c] for c in y_train_enc]) + +# ------------------------- +# 4. Hyperparameter tuning (Optuna or RandomizedSearchCV) +# ------------------------- +print("โš™๏ธ Mulai hyperparameter tuning... (optuna available:", USE_OPTUNA, ")") + +def objective_optuna(trial): + params = { + 'verbosity': 0, + 'use_label_encoder': False, + 'objective': 'multi:softprob', + 'eval_metric': 'mlogloss', + 'random_state': RANDOM_SEED, + 'n_estimators': trial.suggest_categorical('n_estimators', [100,200,300,400]), + 'max_depth': trial.suggest_int('max_depth', 3, 6), + 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2, log=True), + 'subsample': trial.suggest_float('subsample', 0.6, 1.0), + 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0), + 'reg_lambda': trial.suggest_float('reg_lambda', 0.1, 2.0), + 'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 1.0) + } + model = XGBClassifier(**params) + tscv = TimeSeriesSplit(n_splits=4) + scores = [] + for tr_idx, val_idx in tscv.split(X_train): + Xtr, Xval = X_train.iloc[tr_idx], X_train.iloc[val_idx] + ytr, yval = y_train_enc[tr_idx], y_train_enc[val_idx] + sw = sample_weights[tr_idx] + # fit with early stopping if possible + try: + model.fit(Xtr, ytr, sample_weight=sw, eval_set=[(Xval, yval)], early_stopping_rounds=30, verbose=False) + except TypeError: + model.fit(Xtr, ytr, sample_weight=sw) + ypred = model.predict(Xval) + scores.append(balanced_accuracy_score(yval, ypred)) + return np.mean(scores) + +best_params = None +if USE_OPTUNA: + sampler = TPESampler(seed=RANDOM_SEED) + study = optuna.create_study(direction='maximize', sampler=sampler) + study.optimize(objective_optuna, n_trials=OPTUNA_TRIALS, show_progress_bar=True) + best_params = study.best_trial.params + print("โœ… Optuna best params:", best_params) +else: + # fallback RandomizedSearchCV (time-aware implemented via cv=TimeSeriesSplit inside search) + param_dist = { + 'n_estimators': [100,200,300,400], + 'max_depth': [3,4,5,6], + 'learning_rate': [0.01,0.03,0.05,0.1,0.2], + 'subsample': [0.6,0.8,1.0], + 'colsample_bytree': [0.6,0.8,1.0], + 'reg_lambda': [0.1,0.5,1.0,1.5], + 'reg_alpha': [0.0,0.25,0.5,1.0] + } + base = XGBClassifier(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=RANDOM_SEED) + rnd = RandomizedSearchCV(base, param_distributions=param_dist, n_iter=RANDOM_SEARCH_ITERS, + scoring='balanced_accuracy', cv=TimeSeriesSplit(n_splits=4), n_jobs=-1, verbose=1, random_state=RANDOM_SEED) + rnd.fit(X_train, y_train_enc, sample_weight=sample_weights) + best_params = rnd.best_params_ + print("โœ… RandomizedSearchCV best params:", best_params) + +# ------------------------- +# 5. Train final model +# ------------------------- +print("๐Ÿง  Melatih final model dengan best params...") +final_kwargs = dict(use_label_encoder=False, objective='multi:softprob', eval_metric='mlogloss', random_state=RANDOM_SEED) +# if optuna, best_params contains only param keys; else RandomizedSearchCV gave 'clf__' style? we already set best_params +final_kwargs.update(best_params) + +model = XGBClassifier(**final_kwargs) +try: + model.fit(X_train, y_train_enc, sample_weight=sample_weights, eval_set=[(X_test, y_test_enc)], early_stopping_rounds=50, verbose=False) +except TypeError: + model.fit(X_train, y_train_enc, sample_weight=sample_weights) + +# ------------------------- +# 6. Evaluate +# ------------------------- +y_pred_enc = model.predict(X_test) +y_proba = model.predict_proba(X_test) +y_pred = le.inverse_transform(y_pred_enc) + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test_enc, y_proba) + +print("\n๐Ÿ“Š Evaluasi (hold-out test temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced acc.: {bal_acc*100:.2f}%") +print(f" - Log loss : {ll:.4f}") +print("\nClassification report:\n", classification_report(y_test, y_pred)) +print("Confusion matrix:\n", confusion_matrix(y_test, y_pred)) + +# ------------------------- +# 7. Feature importance (plot) +# ------------------------- +print("\n๐Ÿ”Ž Feature importance (gain):") +booster = model.get_booster() +fscore = booster.get_score(importance_type='gain') # use 'gain' +# Map 'f0'.. to feature names +mapped = {} +for k, v in fscore.items(): + if k.startswith('f'): + idx = int(k[1:]) + if idx < len(feat_cols): + mapped[feat_cols[idx]] = v + else: + mapped[k] = v +# sort and print +fi_sorted = sorted(mapped.items(), key=lambda x: x[1], reverse=True) +for name, val in fi_sorted: + print(f" {name}: {val:.4f}") + +# plot top features +try: + top_n = min(12, len(fi_sorted)) + names = [n for n,_ in fi_sorted[:top_n]] + vals = [v for _,v in fi_sorted[:top_n]] + plt.figure(figsize=(8,5)) + plt.barh(names[::-1], vals[::-1]) + plt.title("Feature importance (gain) - top features") + plt.tight_layout() + plt.show() +except Exception: + pass + +# ------------------------- +# 8. Save model & encoder & feature columns +# ------------------------- +joblib.dump(model, "model_epl_v3_1.joblib") +joblib.dump(le, "labelencoder_v3_1.joblib") +joblib.dump(feat_cols, "feature_columns_v3_1.joblib") +print("\n๐Ÿ’พ Model, encoder, dan feature columns disimpan.") + +# ------------------------- +# 9. Safe predict_match helper +# ------------------------- +def compute_team_form_from_raw(team, date, raw_df, window=ROLL_WINDOW): + past = raw_df[((raw_df['HomeTeam'] == team) | (raw_df['AwayTeam'] == team)) & (raw_df['Date'] < date)].tail(window) + if past.empty: + return None + gf, ga, wins = [], [], [] + for _, r in past.iterrows(): + if r['HomeTeam'] == team: + gf.append(int(r['FTHG'])); ga.append(int(r['FTAG'])) + wins.append(1 if int(r['FTHG']) > int(r['FTAG']) else 0) + else: + gf.append(int(r['FTAG'])); ga.append(int(r['FTHG'])) + wins.append(1 if int(r['FTAG']) > int(r['FTHG']) else 0) + return { + 'avg_gf': np.mean(gf), 'avg_ga': np.mean(ga), + 'win_rate': np.mean(wins), 'avg_gd': np.mean(np.array(gf)-np.array(ga)) + } + +def predict_match_safe(home, away, model=model, le=le, feat_cols=feat_cols, raw_df=df_raw, allow_fill=True): + date = raw_df['Date'].max() + pd.Timedelta(days=1) + hf = compute_team_form_from_raw(home, date, raw_df) + af = compute_team_form_from_raw(away, date, raw_df) + if hf is None or af is None: + print("โš ๏ธ Tidak cukup history untuk salah satu tim.") + return None + nk = tuple(sorted((home, away))) + ph = h2h_counts.get(nk, {'home_wins':0,'away_wins':0,'draws':0}) + if nk[0] == home: + h_wins = ph['home_wins']; a_wins = ph['away_wins'] + else: + h_wins = ph['away_wins']; a_wins = ph['home_wins'] + feats = [ + hf['avg_gf'], hf['avg_ga'], hf['win_rate'], hf['avg_gd'], + af['avg_gf'], af['avg_ga'], af['win_rate'], af['avg_gd'], + h_wins, a_wins, ph['draws'] + ] + # include shot/sot if model used them + if 'home_avg_shots' in feat_cols: + feats += [hf.get('avg_shots', 0.0), af.get('avg_shots', 0.0)] + if 'home_avg_sot' in feat_cols: + feats += [hf.get('avg_sot', 0.0), af.get('avg_sot', 0.0)] + Xp = pd.DataFrame([feats], columns=feat_cols) + # fill missing with 0 for safety + Xp = Xp.fillna(0).astype(float) + proba = model.predict_proba(Xp)[0] + pred_enc = int(np.argmax(proba)) + pred_label = le.inverse_transform([pred_enc])[0] + print(f"\n๐Ÿ”ฎ PREDIKSI: {home} vs {away} -> {pred_label}") + for i, cls in enumerate(le.classes_): + print(f" {cls}: {proba[i]*100:.2f}%")import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import LabelEncoder +from sklearn.metrics import accuracy_score, log_loss, classification_report, confusion_matrix +from xgboost import XGBClassifier +import matplotlib.pyplot as plt +import joblib +import warnings + +warnings.filterwarnings("ignore") + +# ========================================= +# 1๏ธโƒฃ LOAD DATA +# ========================================= +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df["Date"] = pd.to_datetime(df["Date"]) +print(f"โœ… Data: {len(df)} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ========================================= +# 2๏ธโƒฃ FEATURE ENGINEERING +# ========================================= +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def get_team_form(df, team, n=5): + team_matches = df[(df["HomeTeam"] == team) | (df["AwayTeam"] == team)].copy() + team_matches = team_matches.sort_values("Date") + results = [] + + for i, row in team_matches.iterrows(): + prev_matches = team_matches[team_matches["Date"] < row["Date"]].tail(n) + if len(prev_matches) < n: + results.append(None) + continue + + # hitung statistik sederhana + if row["HomeTeam"] == team: + gf = prev_matches.apply(lambda r: r["FTHG"] if r["HomeTeam"] == team else r["FTAG"], axis=1).mean() + ga = prev_matches.apply(lambda r: r["FTAG"] if r["HomeTeam"] == team else r["FTHG"], axis=1).mean() + win_rate = prev_matches.apply(lambda r: 1 if (r["FTR"] == "H" and r["HomeTeam"] == team) or (r["FTR"] == "A" and r["AwayTeam"] == team) else 0, axis=1).mean() + else: + gf = prev_matches.apply(lambda r: r["FTAG"] if r["AwayTeam"] == team else r["FTHG"], axis=1).mean() + ga = prev_matches.apply(lambda r: r["FTHG"] if r["AwayTeam"] == team else r["FTAG"], axis=1).mean() + win_rate = prev_matches.apply(lambda r: 1 if (r["FTR"] == "A" and r["AwayTeam"] == team) or (r["FTR"] == "H" and r["HomeTeam"] == team) else 0, axis=1).mean() + + results.append((gf, ga, win_rate)) + return results + +home_features = get_team_form(df, df["HomeTeam"].iloc[0]) +away_features = get_team_form(df, df["AwayTeam"].iloc[0]) + +df["home_avg_gf"] = df.apply(lambda x: np.nan, axis=1) +df["home_avg_ga"] = df.apply(lambda x: np.nan, axis=1) +df["home_win_rate"] = df.apply(lambda x: np.nan, axis=1) +df["away_avg_gf"] = df.apply(lambda x: np.nan, axis=1) +df["away_avg_ga"] = df.apply(lambda x: np.nan, axis=1) +df["away_win_rate"] = df.apply(lambda x: np.nan, axis=1) + +teams = df["HomeTeam"].unique() +for team in teams: + home_stats = get_team_form(df, team) + for i, val in enumerate(home_stats): + if val: + gf, ga, win_rate = val + if df.iloc[i]["HomeTeam"] == team: + df.at[i, "home_avg_gf"] = gf + df.at[i, "home_avg_ga"] = ga + df.at[i, "home_win_rate"] = win_rate + elif df.iloc[i]["AwayTeam"] == team: + df.at[i, "away_avg_gf"] = gf + df.at[i, "away_avg_ga"] = ga + df.at[i, "away_win_rate"] = win_rate + +df = df.dropna() + +# ========================================= +# 3๏ธโƒฃ LABEL ENCODING +# ========================================= +def map_result(x): + if x == "H": + return "Home" + elif x == "A": + return "Away" + else: + return "Draw" + +df["Result"] = df["FTR"].map(map_result) + +features = [ + "home_avg_gf", "home_avg_ga", "home_win_rate", + "away_avg_gf", "away_avg_ga", "away_win_rate" +] + +X = df[features] +y = df["Result"] + +le = LabelEncoder() +y_encoded = le.fit_transform(y) +label_map = dict(zip(le.transform(le.classes_), le.classes_)) +print("๐Ÿ” Label encoding:", label_map) + +# ========================================= +# 4๏ธโƒฃ SPLIT DATA +# ========================================= +split_date = df["Date"].quantile(0.9) +X_train = X[df["Date"] < split_date] +X_test = X[df["Date"] >= split_date] +y_train = y_encoded[df["Date"] < split_date] +y_test = y_encoded[df["Date"] >= split_date] + +print(f"๐Ÿ“… Train: {len(X_train)} | Test: {len(X_test)} pertandingan") + +# ========================================= +# 5๏ธโƒฃ TRAINING MODEL +# ========================================= +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") +model = XGBClassifier( + use_label_encoder=False, + objective="multi:softprob", + eval_metric="mlogloss", + random_state=42, + n_estimators=200, + learning_rate=0.05, + max_depth=5, + subsample=0.8, + colsample_bytree=0.8, +) + +model.fit(X_train, y_train) + +# ========================================= +# 6๏ธโƒฃ EVALUASI +# ========================================= +y_pred = model.predict(X_test) +y_prob = model.predict_proba(X_test) + +acc = accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_prob) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}\n") +print(classification_report(y_test, y_pred, target_names=le.classes_)) +print("Confusion Matrix (rows true, cols pred):") +print(confusion_matrix(y_test, y_pred)) + +# ========================================= +# 7๏ธโƒฃ FEATURE IMPORTANCE +# ========================================= +plt.figure(figsize=(10,5)) +plt.bar(range(len(model.feature_importances_)), model.feature_importances_) +plt.xticks(range(len(features)), features, rotation=45, ha="right") +plt.title("Feature importance (gain) - top features") +plt.tight_layout() +plt.savefig("feature_importance.png") +plt.show() + +# ========================================= +# 8๏ธโƒฃ SIMPAN MODEL +# ========================================= +joblib.dump(model, "model_epl_v1.joblib") +joblib.dump(le, "labelencoder_v1.joblib") + +print("\nโœ… Model & encoder berhasil disimpan!") + + return pred_label, proba + +# ------------------------- +# 10. Example predictions +# ------------------------- +print("\n๐Ÿ”ฎ Contoh prediksi (safe):") +try: + predict_match_safe("Arsenal", "Man Utd") + predict_match_safe("Liverpool", "Man City") + predict_match_safe("Chelsea", "Tottenham") +except Exception as e: + print("Info: contoh prediksi gagal:", e) + +print("\nSelesai.") diff --git a/.history/predict-pl-match_otomatis_v2_20251005194747.py b/.history/predict-pl-match_otomatis_v2_20251005194747.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_otomatis_v2_20251005194749.py b/.history/predict-pl-match_otomatis_v2_20251005194749.py new file mode 100644 index 0000000000000000000000000000000000000000..7515f1a7e786c1366bc3e81f48438f770e37e59f --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005194749.py @@ -0,0 +1,164 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import LabelEncoder +from sklearn.metrics import accuracy_score, log_loss, classification_report, confusion_matrix +from xgboost import XGBClassifier +import matplotlib.pyplot as plt +import joblib +import warnings + +warnings.filterwarnings("ignore") + +# ========================================= +# 1๏ธโƒฃ LOAD DATA +# ========================================= +print("๐Ÿ“Š Memuat data historis Premier League...") +df = pd.read_csv("epl-training.csv") +df["Date"] = pd.to_datetime(df["Date"]) +print(f"โœ… Data: {len(df)} pertandingan dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ========================================= +# 2๏ธโƒฃ FEATURE ENGINEERING +# ========================================= +print("โš™๏ธ Menghitung form 5 pertandingan terakhir per tim...") + +def get_team_form(df, team, n=5): + team_matches = df[(df["HomeTeam"] == team) | (df["AwayTeam"] == team)].copy() + team_matches = team_matches.sort_values("Date") + results = [] + + for i, row in team_matches.iterrows(): + prev_matches = team_matches[team_matches["Date"] < row["Date"]].tail(n) + if len(prev_matches) < n: + results.append(None) + continue + + # hitung statistik sederhana + if row["HomeTeam"] == team: + gf = prev_matches.apply(lambda r: r["FTHG"] if r["HomeTeam"] == team else r["FTAG"], axis=1).mean() + ga = prev_matches.apply(lambda r: r["FTAG"] if r["HomeTeam"] == team else r["FTHG"], axis=1).mean() + win_rate = prev_matches.apply(lambda r: 1 if (r["FTR"] == "H" and r["HomeTeam"] == team) or (r["FTR"] == "A" and r["AwayTeam"] == team) else 0, axis=1).mean() + else: + gf = prev_matches.apply(lambda r: r["FTAG"] if r["AwayTeam"] == team else r["FTHG"], axis=1).mean() + ga = prev_matches.apply(lambda r: r["FTHG"] if r["AwayTeam"] == team else r["FTAG"], axis=1).mean() + win_rate = prev_matches.apply(lambda r: 1 if (r["FTR"] == "A" and r["AwayTeam"] == team) or (r["FTR"] == "H" and r["HomeTeam"] == team) else 0, axis=1).mean() + + results.append((gf, ga, win_rate)) + return results + +home_features = get_team_form(df, df["HomeTeam"].iloc[0]) +away_features = get_team_form(df, df["AwayTeam"].iloc[0]) + +df["home_avg_gf"] = df.apply(lambda x: np.nan, axis=1) +df["home_avg_ga"] = df.apply(lambda x: np.nan, axis=1) +df["home_win_rate"] = df.apply(lambda x: np.nan, axis=1) +df["away_avg_gf"] = df.apply(lambda x: np.nan, axis=1) +df["away_avg_ga"] = df.apply(lambda x: np.nan, axis=1) +df["away_win_rate"] = df.apply(lambda x: np.nan, axis=1) + +teams = df["HomeTeam"].unique() +for team in teams: + home_stats = get_team_form(df, team) + for i, val in enumerate(home_stats): + if val: + gf, ga, win_rate = val + if df.iloc[i]["HomeTeam"] == team: + df.at[i, "home_avg_gf"] = gf + df.at[i, "home_avg_ga"] = ga + df.at[i, "home_win_rate"] = win_rate + elif df.iloc[i]["AwayTeam"] == team: + df.at[i, "away_avg_gf"] = gf + df.at[i, "away_avg_ga"] = ga + df.at[i, "away_win_rate"] = win_rate + +df = df.dropna() + +# ========================================= +# 3๏ธโƒฃ LABEL ENCODING +# ========================================= +def map_result(x): + if x == "H": + return "Home" + elif x == "A": + return "Away" + else: + return "Draw" + +df["Result"] = df["FTR"].map(map_result) + +features = [ + "home_avg_gf", "home_avg_ga", "home_win_rate", + "away_avg_gf", "away_avg_ga", "away_win_rate" +] + +X = df[features] +y = df["Result"] + +le = LabelEncoder() +y_encoded = le.fit_transform(y) +label_map = dict(zip(le.transform(le.classes_), le.classes_)) +print("๐Ÿ” Label encoding:", label_map) + +# ========================================= +# 4๏ธโƒฃ SPLIT DATA +# ========================================= +split_date = df["Date"].quantile(0.9) +X_train = X[df["Date"] < split_date] +X_test = X[df["Date"] >= split_date] +y_train = y_encoded[df["Date"] < split_date] +y_test = y_encoded[df["Date"] >= split_date] + +print(f"๐Ÿ“… Train: {len(X_train)} | Test: {len(X_test)} pertandingan") + +# ========================================= +# 5๏ธโƒฃ TRAINING MODEL +# ========================================= +print("๐Ÿง  Melatih model XGBoost (regularized, no leak)...") +model = XGBClassifier( + use_label_encoder=False, + objective="multi:softprob", + eval_metric="mlogloss", + random_state=42, + n_estimators=200, + learning_rate=0.05, + max_depth=5, + subsample=0.8, + colsample_bytree=0.8, +) + +model.fit(X_train, y_train) + +# ========================================= +# 6๏ธโƒฃ EVALUASI +# ========================================= +y_pred = model.predict(X_test) +y_prob = model.predict_proba(X_test) + +acc = accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_prob) +print("\n๐Ÿ“Š HASIL EVALUASI:") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}\n") +print(classification_report(y_test, y_pred, target_names=le.classes_)) +print("Confusion Matrix (rows true, cols pred):") +print(confusion_matrix(y_test, y_pred)) + +# ========================================= +# 7๏ธโƒฃ FEATURE IMPORTANCE +# ========================================= +plt.figure(figsize=(10,5)) +plt.bar(range(len(model.feature_importances_)), model.feature_importances_) +plt.xticks(range(len(features)), features, rotation=45, ha="right") +plt.title("Feature importance (gain) - top features") +plt.tight_layout() +plt.savefig("feature_importance.png") +plt.show() + +# ========================================= +# 8๏ธโƒฃ SIMPAN MODEL +# ========================================= +joblib.dump(model, "model_epl_v1.joblib") +joblib.dump(le, "labelencoder_v1.joblib") + +print("\nโœ… Model & encoder berhasil disimpan!") diff --git a/.history/predict-pl-match_otomatis_v2_20251005195125.py b/.history/predict-pl-match_otomatis_v2_20251005195125.py new file mode 100644 index 0000000000000000000000000000000000000000..a6a33b2d39276961b72fd14025bea8fd23dde795 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251005195125.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Arsenal", "Man Utd") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006105953.py b/.history/predict-pl-match_otomatis_v2_20251006105953.py new file mode 100644 index 0000000000000000000000000000000000000000..19aa9a29e5a6c4e5920fcd479c7e66be7adf69ae --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006105953.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("", "Man Utd") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110007.py b/.history/predict-pl-match_otomatis_v2_20251006110007.py new file mode 100644 index 0000000000000000000000000000000000000000..dc09d212f05d55728cbba49b171adc89ab6027a5 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110007.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Brentford", "Man Utd") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110011.py b/.history/predict-pl-match_otomatis_v2_20251006110011.py new file mode 100644 index 0000000000000000000000000000000000000000..cc7f94f663ddfcebe057a64144a2d52dc74d1bfe --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110011.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Brentford", "Man City") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110611.py b/.history/predict-pl-match_otomatis_v2_20251006110611.py new file mode 100644 index 0000000000000000000000000000000000000000..aa38abab80efd5d6c715f519a9b25cb3909a2c8d --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110611.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("", "Man City") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110613.py b/.history/predict-pl-match_otomatis_v2_20251006110613.py new file mode 100644 index 0000000000000000000000000000000000000000..aa38abab80efd5d6c715f519a9b25cb3909a2c8d --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110613.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("", "Man City") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110618.py b/.history/predict-pl-match_otomatis_v2_20251006110618.py new file mode 100644 index 0000000000000000000000000000000000000000..d572264a2c24415e19ff96569f78dd6f6b887481 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110618.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston V", "Man City") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110620.py b/.history/predict-pl-match_otomatis_v2_20251006110620.py new file mode 100644 index 0000000000000000000000000000000000000000..f8fef7e1b09e0697a469ed037f89f08f094d33fb --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110620.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Man City") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110626.py b/.history/predict-pl-match_otomatis_v2_20251006110626.py new file mode 100644 index 0000000000000000000000000000000000000000..bf438823f2630112763cef6019d1bbc1236b0858 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110626.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110634.py b/.history/predict-pl-match_otomatis_v2_20251006110634.py new file mode 100644 index 0000000000000000000000000000000000000000..a3242015f6d6830ef8c8513898cfb90c0f68bc91 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110634.py @@ -0,0 +1,351 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Aston Villa", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110644.py b/.history/predict-pl-match_otomatis_v2_20251006110644.py new file mode 100644 index 0000000000000000000000000000000000000000..4e7d3923776b16049f7666bb9d7287d485a9d19c --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110644.py @@ -0,0 +1,351 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110646.py b/.history/predict-pl-match_otomatis_v2_20251006110646.py new file mode 100644 index 0000000000000000000000000000000000000000..970f8c8dd6376d2defd1ce8b6a25356865525c8d --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110646.py @@ -0,0 +1,351 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110652.py b/.history/predict-pl-match_otomatis_v2_20251006110652.py new file mode 100644 index 0000000000000000000000000000000000000000..718a489536bcb80743a2f67d5b5ab9f26cd1f817 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110652.py @@ -0,0 +1,351 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110654.py b/.history/predict-pl-match_otomatis_v2_20251006110654.py new file mode 100644 index 0000000000000000000000000000000000000000..a65d1ba6d3d6d1612a9e512d5052a11a404a85ac --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110654.py @@ -0,0 +1,352 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Aston Villa", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110702.py b/.history/predict-pl-match_otomatis_v2_20251006110702.py new file mode 100644 index 0000000000000000000000000000000000000000..a65fd27c6e3425d527897d2b0a35a7261af18903 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110702.py @@ -0,0 +1,352 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110706.py b/.history/predict-pl-match_otomatis_v2_20251006110706.py new file mode 100644 index 0000000000000000000000000000000000000000..d70920d88be73df860f6c2292b66191e9a22aa25 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110706.py @@ -0,0 +1,352 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Cr") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110708.py b/.history/predict-pl-match_otomatis_v2_20251006110708.py new file mode 100644 index 0000000000000000000000000000000000000000..eb889818238005155565a17b6dfad300aac8995b --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110708.py @@ -0,0 +1,352 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110713.py b/.history/predict-pl-match_otomatis_v2_20251006110713.py new file mode 100644 index 0000000000000000000000000000000000000000..aa47aeb7ec62339febe16116dad856ab1f55e4d2 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110713.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Aston Villa", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110717.py b/.history/predict-pl-match_otomatis_v2_20251006110717.py new file mode 100644 index 0000000000000000000000000000000000000000..d5d067c160057e4341f21283e99b40cb90238e02 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110717.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("New", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110718.py b/.history/predict-pl-match_otomatis_v2_20251006110718.py new file mode 100644 index 0000000000000000000000000000000000000000..4dc37ef2704a7eebad6d535d0b7c6c3cf94d9bc4 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110718.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Newc", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110723.py b/.history/predict-pl-match_otomatis_v2_20251006110723.py new file mode 100644 index 0000000000000000000000000000000000000000..1355a0633e89c3169bda9c0ec7b8d8d499e9c5b7 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110723.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Newcastle", "Burnley") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110731.py b/.history/predict-pl-match_otomatis_v2_20251006110731.py new file mode 100644 index 0000000000000000000000000000000000000000..c84946e3bf34da8b0c11aa994915d17cc3d2e501 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110731.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Newcastle", "Nott") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006110732.py b/.history/predict-pl-match_otomatis_v2_20251006110732.py new file mode 100644 index 0000000000000000000000000000000000000000..f3a5d813f3a09c71c97e9e7381af2883cc667f74 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006110732.py @@ -0,0 +1,353 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Newcastle", "Nottingham Forest") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006111453.py b/.history/predict-pl-match_otomatis_v2_20251006111453.py new file mode 100644 index 0000000000000000000000000000000000000000..ecee497281fc4befa613cff3474444e7bcd7aae9 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006111453.py @@ -0,0 +1,352 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Wolves", "Brighton") + predict_match("Everton", "Crystal Palace") + predict_match("Newcastle", "Nottingham Forest") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006111456.py b/.history/predict-pl-match_otomatis_v2_20251006111456.py new file mode 100644 index 0000000000000000000000000000000000000000..d2d9e48693a3c63f3335b07b9010c407f35f3daf --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006111456.py @@ -0,0 +1,351 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Wolves", "Brighton") + predict_match("Newcastle", "Nottingham Forest") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006111500.py b/.history/predict-pl-match_otomatis_v2_20251006111500.py new file mode 100644 index 0000000000000000000000000000000000000000..a1c34e9149e5f02e3dc8c56d55b5914388c5bf80 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006111500.py @@ -0,0 +1,350 @@ +# predict-pl-match_otomatis_v2.py +""" +Versi perbaikan pipeline prediksi hasil pertandingan: +- Fitur: form (rolling mean 5), win rate, avg goal diff, last result, home/away streak +- Validasi temporal: TimeSeriesSplit +- Model: XGBoost jika ada, fallback RandomForest +- Evaluasi: accuracy, balanced_accuracy, log_loss, classification_report, confusion matrix +- Fungsi predict_match yang menangani mapping nama dan ketiadaan data +""" + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit, GridSearchCV, train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +# coba import xgboost +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +# ----------------------------- +# 1. Load & prepare data +# ----------------------------- +print("๐Ÿ“Š Memuat data...") +if not os.path.exists("epl-training.csv"): + raise FileNotFoundError("File 'epl-training.csv' tidak ditemukan di folder saat ini.") + +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +# Kolom dasar yang kita butuhkan (sesuaikan jika berbeda) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise KeyError(f"Kolom {c} tidak ditemukan. Pastikan dataset sesuai (kaggle epl).") + +df = df[expected].copy() +df.rename(columns={ + 'HomeTeam': 'Home', 'AwayTeam': 'Away', + 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', + 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget' +}, inplace=True) + +# parse date (beberapa dataset punya format berbeda) +for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y"): + try: + df['Date'] = pd.to_datetime(df['Date'], format=fmt) + break + except Exception: + continue +if df['Date'].dtype == object: + df['Date'] = pd.to_datetime(df['Date'], errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ----------------------------- +# 2. Basic engineered features +# ----------------------------- +print("๐Ÿ”ง Membuat fitur dasar (GoalDiff, Result)...") +df['GoalDiff'] = df['HomeGoals'] - df['AwayGoals'] +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 # Home win + if row['HomeGoals'] < row['AwayGoals']: return 2 # Away win + return 0 # Draw +df['Result'] = df.apply(result_label, axis=1) + +# ----------------------------- +# 3. Per-team timeline & rolling features (window=5) +# ----------------------------- +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], + 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'IsHome': 1, + 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], + 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'IsHome': 0, + 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +# rolling agregations +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).agg({ + 'GoalsFor': 'mean', + 'GoalsAgainst': 'mean', + 'ShotsFor': 'mean', + 'SOT_For': 'mean', + 'Win': 'mean', + 'Draw': 'mean' +}).reset_index().rename(columns={ + 'GoalsFor': f'AvgGoalsFor_L{window}', + 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', + 'ShotsFor': f'AvgShotsFor_L{window}', + 'SOT_For': f'AvgSOTFor_L{window}', + 'Win': f'WinRate_L{window}', + 'Draw': f'DrawRate_L{window}' +}) + +# merge rolling back to original matches (for home & away) +# buat helper: ambil rolling stats terakhir per tim per tanggal +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', + f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] + +# merge untuk Home +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', + f'WinRate_L{window}': f'WinRate_Home_L{window}', + f'DrawRate_L{window}': f'DrawRate_Home_L{window}' +}).drop(columns=['Team']) + +# merge untuk Away +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={ + f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', + f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', + f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', + f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', + f'WinRate_L{window}': f'WinRate_Away_L{window}', + f'DrawRate_L{window}': f'DrawRate_Away_L{window}' +}).drop(columns=['Team']) + +# ----------------------------- +# 4. Fitur last match result & streak +# ----------------------------- +print("๐Ÿ” Membuat fitur Last Result & Streak...") +# last result: 1=win, 0=draw, -1=loss dari perspektif tim +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if row['Home']==team: + if row['HomeGoals']>row['AwayGoals']: return 1 + if row['HomeGoals']row['HomeGoals']: return 1 + if row['AwayGoals']r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: + streak += 1 + else: + break + return streak + +# apply (may be slow on large df โ€” masih ok untuk ~10k rows) +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +# ----------------------------- +# 5. Head-to-head counts (simple) +# ----------------------------- +print("โš”๏ธ Menghitung H2H sederhana (jumlah pertemuan sebelumnya)...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# ----------------------------- +# 6. Final feature set & cleaning +# ----------------------------- +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + # Home form + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + # Away form + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + # H2H + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', + # dasar + 'GoalDiff' +] + +# Hapus baris yg memiliki NA di fitur penting +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] + +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ----------------------------- +# 7. Train / validation (TimeSeriesSplit) +# ----------------------------- +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) + +# Pilih model: XGBoost jika tersedia, else RandomForest +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, verbosity=0) + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [4, 6], + 'learning_rate': [0.05, 0.1], + 'subsample': [0.8, 1.0] + } +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + param_grid = { + 'n_estimators': [200, 400], + 'max_depth': [6, 10] + } + +# pipeline: scaling (XGBoost biasanya tidak butuh, tapi tetap aman) +pipeline = Pipeline([ + ('scaler', StandardScaler()), + ('clf', base_model) +]) + +# GridSearchCV dengan TimeSeriesSplit +print("๐Ÿ”Ž Melakukan GridSearchCV (CV temporal, cepat)...") +gsearch = GridSearchCV( + pipeline, + param_grid={'clf__' + k: v for k, v in param_grid.items()}, + cv=tscv, + scoring='balanced_accuracy', + n_jobs=-1, + verbose=1 +) +gsearch.fit(X, y) +best = gsearch.best_estimator_ +print(f"โœ… Best params: {gsearch.best_params_}") +print(f"โœ… Best CV score (balanced_accuracy): {gsearch.best_score_:.4f}") + +# ----------------------------- +# 8. Final evaluation on last fold hold-out +# ----------------------------- +# split temporally: gunakan 80% untuk train, 20% terakhir sebagai hold-out test +split_idx = int(len(df_model) * 0.8) +X_train_final = X.iloc[:split_idx] +y_train_final = y.iloc[:split_idx] +X_test_final = X.iloc[split_idx:] +y_test_final = y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +best.fit(X_train_final, y_train_final) +y_pred = best.predict(X_test_final) +y_proba = best.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) + +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) + +# Confusion matrix (printed) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):") +print(cm) + +# ----------------------------- +# 9. Predict function interactive +# ----------------------------- +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (pakai rolling_stats & df_model)...") +# reuse rolling_stats DataFrame defined earlier (agg). But rolling_stats punya kolom 'Team', 'Date', ... +# Untuk fungsi prediksi kita cari latest rolling stats untuk tiap tim +team_latest = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') + +def predict_match(home_team_input, away_team_input, model=best, team_roll_df=team_latest): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + # ambil rolling terakhir + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim. Pastikan nama tim konsisten.") + return None + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + # ambil fitur sesuai urutan features + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], 0, 0, # placeholders untuk HomeLastResult, HomeWinStreak (tidak tersedia utk "hari ini") + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], 0, 0, + 0,0,0, # h2h placeholders (kamu bisa hitung H2H aktual utk pertandingan tgl tertentu) + 0 # GoalDiff placeholder + ] + X_input = np.array(feat_vals).reshape(1, -1) + # jika pipeline ada scaler, tetap aman + probs = model.predict_proba(X_input)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi (gunakan tim yang ada di dataset) +try: + predict_match("Wolves", "Brighton") +except Exception as e: + print("Info: contoh prediksi gagal karena data input โ€” ini normal jika tim tidak cocok nama/formatnya.", e) + +# ----------------------------- +# 10. Simpan model (opsional) +# ----------------------------- +import joblib +joblib.dump(best, "model_epl_best.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_best.joblib'") + +print("\nSelesai. Jika ingin, kamu bisa:") +print("- Menambahkan fitur klasemen (posisi) per match date (butuh data klasemen per tanggal).") +print("- Menambahkan pemain absen / lineup / injury (butuh sumber data tambahan).") +print("- Menambahkan/menyesuaikan mapping nama tim agar konsisten.") diff --git a/.history/predict-pl-match_otomatis_v2_20251006111741.py b/.history/predict-pl-match_otomatis_v2_20251006111741.py new file mode 100644 index 0000000000000000000000000000000000000000..bc1e59265ecb608973ed360c7503f83857499fd7 --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006111741.py @@ -0,0 +1,214 @@ +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except ImportError: + HAS_XGB = False + +# ============================================================================= +# BAGIAN 1, 2, 3, 4, 5 (LOAD DATA & FEATURE ENGINEERING) - TIDAK BERUBAH +# ============================================================================= +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +print("๐Ÿ”ง Membuat fitur dasar (Result)...") +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 +df['Result'] = df.apply(result_label, axis=1) + +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).mean().reset_index().rename(columns={'GoalsFor': f'AvgGoalsFor_L{window}', 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', 'ShotsFor': f'AvgShotsFor_L{window}', 'SOT_For': f'AvgSOTFor_L{window}', 'Win': f'WinRate_L{window}', 'Draw': f'DrawRate_L{window}'}) +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', f'WinRate_L{window}': f'WinRate_Home_L{window}', f'DrawRate_L{window}': f'DrawRate_Home_L{window}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', f'WinRate_L{window}': f'WinRate_Away_L{window}', f'DrawRate_L{window}': f'DrawRate_Away_L{window}'}).drop(columns=['Team']) + +print("๐Ÿ” Membuat fitur Last Result & Streak...") +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +print("โš”๏ธ Menghitung H2H sederhana...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins; df['h2h_away_wins'] = h2h_away_wins; df['h2h_draws'] = h2h_draws + +# ============================================================================= +# 6. Final feature set & cleaning - DENGAN PERBAIKAN +# ============================================================================= +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', +] # === 'GoalDiff' DIHAPUS DARI SINI === + +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ============================================================================= +# 7. Train / validation (TimeSeriesSplit) - Tidak Berubah +# ============================================================================= +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42) +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', base_model)]) +# Untuk mempercepat, kita lewati GridSearchCV dan langsung pakai parameter bagus +params = {'learning_rate': 0.05, 'max_depth': 4, 'n_estimators': 200, 'subsample': 0.8} +if HAS_XGB: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', XGBClassifier(**params, use_label_encoder=False, eval_metric='mlogloss', random_state=42))]) +else: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42, class_weight='balanced'))]) + +# ============================================================================= +# 8. Final evaluation on last fold hold-out - Tidak Berubah +# ============================================================================= +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +final_model.fit(X_train_final, y_train_final) +y_pred = final_model.predict(X_test_final) +y_proba = final_model.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):\n", cm) + +# ============================================================================= +# 9. Predict function interactive - DENGAN PERBAIKAN +# ============================================================================= +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis)...") +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +def predict_match(home_team_input, away_team_input, model=final_model, team_roll_df=team_latest_stats, history_df=full_match_history): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim."); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + + # Hitung fitur dinamis untuk pertandingan "hari ini" + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, 5) + a_streak = get_recent_streak(away, today, history_df, 5) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], h_last_res, h_streak, + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], a_last_res, a_streak, + h2h_hw, h2h_aw, h2h_d + ] + + X_input = pd.DataFrame([feat_vals], columns=features) + probs = model.predict_proba(X_input)[0] + + print(f"Info Form (Home): WinRate={h[f'WinRate_L{window}']:.2f}, AvgGoals={h[f'AvgGoalsFor_L{window}']:.2f}, LastResult={h_last_res}, WinStreak={h_streak}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{window}']:.2f}, AvgGoals={a[f'AvgGoalsFor_L{window}']:.2f}, LastResult={a_last_res}, WinStreak={a_streak}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") + predict_match("Man Utd", "Arsenal") +except Exception as e: + print(f"Info: contoh prediksi gagal - {e}") + +joblib.dump(final_model, "model_epl_final.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final.joblib'") \ No newline at end of file diff --git a/.history/predict-pl-match_otomatis_v2_20251006111749.py b/.history/predict-pl-match_otomatis_v2_20251006111749.py new file mode 100644 index 0000000000000000000000000000000000000000..74c381a9464bc11f82411338942a1a71199cbb7a --- /dev/null +++ b/.history/predict-pl-match_otomatis_v2_20251006111749.py @@ -0,0 +1,213 @@ +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except ImportError: + HAS_XGB = False + +# ============================================================================= +# BAGIAN 1, 2, 3, 4, 5 (LOAD DATA & FEATURE ENGINEERING) - TIDAK BERUBAH +# ============================================================================= +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv("epl-training.csv", encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +print("๐Ÿ”ง Membuat fitur dasar (Result)...") +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 +df['Result'] = df.apply(result_label, axis=1) + +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).mean().reset_index().rename(columns={'GoalsFor': f'AvgGoalsFor_L{window}', 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', 'ShotsFor': f'AvgShotsFor_L{window}', 'SOT_For': f'AvgSOTFor_L{window}', 'Win': f'WinRate_L{window}', 'Draw': f'DrawRate_L{window}'}) +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', f'WinRate_L{window}': f'WinRate_Home_L{window}', f'DrawRate_L{window}': f'DrawRate_Home_L{window}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', f'WinRate_L{window}': f'WinRate_Away_L{window}', f'DrawRate_L{window}': f'DrawRate_Away_L{window}'}).drop(columns=['Team']) + +print("๐Ÿ” Membuat fitur Last Result & Streak...") +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +print("โš”๏ธ Menghitung H2H sederhana...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins; df['h2h_away_wins'] = h2h_away_wins; df['h2h_draws'] = h2h_draws + +# ============================================================================= +# 6. Final feature set & cleaning - DENGAN PERBAIKAN +# ============================================================================= +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', +] # === 'GoalDiff' DIHAPUS DARI SINI === + +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ============================================================================= +# 7. Train / validation (TimeSeriesSplit) - Tidak Berubah +# ============================================================================= +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42) +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', base_model)]) +# Untuk mempercepat, kita lewati GridSearchCV dan langsung pakai parameter bagus +params = {'learning_rate': 0.05, 'max_depth': 4, 'n_estimators': 200, 'subsample': 0.8} +if HAS_XGB: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', XGBClassifier(**params, use_label_encoder=False, eval_metric='mlogloss', random_state=42))]) +else: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42, class_weight='balanced'))]) + +# ============================================================================= +# 8. Final evaluation on last fold hold-out - Tidak Berubah +# ============================================================================= +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +final_model.fit(X_train_final, y_train_final) +y_pred = final_model.predict(X_test_final) +y_proba = final_model.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):\n", cm) + +# ============================================================================= +# 9. Predict function interactive - DENGAN PERBAIKAN +# ============================================================================= +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis)...") +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +def predict_match(home_team_input, away_team_input, model=final_model, team_roll_df=team_latest_stats, history_df=full_match_history): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim."); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + + # Hitung fitur dinamis untuk pertandingan "hari ini" + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, 5) + a_streak = get_recent_streak(away, today, history_df, 5) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], h_last_res, h_streak, + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], a_last_res, a_streak, + h2h_hw, h2h_aw, h2h_d + ] + + X_input = pd.DataFrame([feat_vals], columns=features) + probs = model.predict_proba(X_input)[0] + + print(f"Info Form (Home): WinRate={h[f'WinRate_L{window}']:.2f}, AvgGoals={h[f'AvgGoalsFor_L{window}']:.2f}, LastResult={h_last_res}, WinStreak={h_streak}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{window}']:.2f}, AvgGoals={a[f'AvgGoalsFor_L{window}']:.2f}, LastResult={a_last_res}, WinStreak={a_streak}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") +except Exception as e: + print(f"Info: contoh prediksi gagal - {e}") + +joblib.dump(final_model, "model_epl_final.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final.joblib'") \ No newline at end of file diff --git a/.history/predict-pl-match_realistic_20251005102750.py b/.history/predict-pl-match_realistic_20251005102750.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict-pl-match_realistic_20251005102802.py b/.history/predict-pl-match_realistic_20251005102802.py new file mode 100644 index 0000000000000000000000000000000000000000..83b03bebde49adf8b5e37787c6fb29c9c1a37639 --- /dev/null +++ b/.history/predict-pl-match_realistic_20251005102802.py @@ -0,0 +1,116 @@ +# predict-pl-match_realistic.py +import pandas as pd +import numpy as np + +# ================================================== +# 1๏ธโƒฃ Load Player Passing CSV +# ================================================== +def load_passing_data(csv_file="premier_league_player_passing.csv"): + try: + df = pd.read_csv(csv_file) + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print(f"โš ๏ธ File {csv_file} tidak ditemukan.") + return None + +# ================================================== +# 2๏ธโƒฃ Load Team Goals & Concede CSV (optional) +# ================================================== +def load_team_goals(csv_file="premier_league_team_goals.csv"): + try: + df = pd.read_csv(csv_file) + print(f"๐Ÿ“Š Loaded team goals/concede data: {df.shape}") + return df + except FileNotFoundError: + print(f"โš ๏ธ File {csv_file} tidak ditemukan. Default digunakan.") + return None + +# ================================================== +# 3๏ธโƒฃ Hitung statistik tim +# ================================================== +def get_team_stats(passing_df, goals_df=None): + teams = passing_df['Squad'].unique() + stats = {} + for team in teams: + team_df = passing_df[passing_df['Squad'] == team] + if team_df.empty: + continue + try: + avg_cmp_perc = team_df['Total_Cmp%'].astype(float).mean() / 100 + except: + avg_cmp_perc = 0.8 + + if goals_df is not None and team in goals_df['Team'].values: + g = goals_df[goals_df['Team']==team] + goals = float(g['Goals_For']) + concede = float(g['Goals_Against']) + else: + goals = team_df['Total_TotDist'].astype(float).mean()/50 + concede = 1.0 + + stats[team] = { + "form": avg_cmp_perc, + "goals": goals, + "concede": concede, + "home_adv": 1.1 if team in ["Brighton","Man City","Arsenal"] else 1.0 + } + return stats + +# ================================================== +# 4๏ธโƒฃ Prediksi pertandingan +# ================================================== +def predict_match(home_team, away_team, team_stats): + print(f"\n๐Ÿ”ฎ Prediksi pertandingan: {home_team} vs {away_team}") + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + home = team_stats[home_team] + away = team_stats[away_team] + + # Perhitungan skor probabilitas berbasis form, goals & concede historis + home_score = home["form"]*0.4 + home["goals"]/(away["concede"]+0.1)*0.4 + home["home_adv"]*0.2 + away_score = away["form"]*0.4 + away["goals"]/(home["concede"]+0.1)*0.4 + away["home_adv"]*0.2 + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # Prediksi skor realistis + exp_home_goals = max(0, round(home["goals"] * p_home + np.random.uniform(0, 0.5))) + exp_away_goals = max(0, round(away["goals"] * p_away + np.random.uniform(0, 0.5))) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + result = home_team if p_home>p_away else away_team if p_away>p_home else "Seri" + print(f"\n๐Ÿง  Analisis Akhir: {result} berpeluang menang\n") + print("โœ… Prediksi selesai.") + +# ================================================== +# 5๏ธโƒฃ MAIN +# ================================================== +def main(): + passing_df = load_passing_data() + goals_df = load_team_goals() # optional, buat CSV: Team, Goals_For, Goals_Against + + if passing_df is None: + return + + team_stats = get_team_stats(passing_df, goals_df) + + # Ganti tim yang mau diprediksi + home_team = "Man City" + away_team = "Brentford" + + predict_match(home_team, away_team, team_stats) + +if __name__ == "__main__": + main() diff --git a/.history/predict_match_plV3_20251006192041.py b/.history/predict_match_plV3_20251006192041.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict_match_plV3_20251006192046.py b/.history/predict_match_plV3_20251006192046.py new file mode 100644 index 0000000000000000000000000000000000000000..36f525adda7f52fad5ffb9ae9352770f767e10ff --- /dev/null +++ b/.history/predict_match_plV3_20251006192046.py @@ -0,0 +1,377 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan feature engineering + Optuna tuning + ensemble +# - Fitur baru: EWM (weighted rolling), FormDiff, HomeAdvantage, RestDays +# - Hyperparameter tuning (Optuna) jika tersedia +# - Ensemble (XGB + RandomForest) dengan fallback +# - Fungsi predict_match diperbarui untuk menangani feature baru + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + import optuna + HAS_OPTUNA = True +except Exception: + HAS_OPTUNA = False + +# ------------------------- +# 1) Load & basic preprocessing (tidak berubah banyak) +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV.") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (for rolling/EWM features) +# ------------------------- +print('๐Ÿ” Menyusun team-level records untuk fitur rolling/EWM...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +# Parameters +WINDOW = 5 +EWM_SPAN = 5 + +# Rolling means (simple) - existing style +agg = team_df.groupby('Team').rolling(window=WINDOW, on='Date', min_periods=1).mean().reset_index() +agg = agg.rename(columns={'GoalsFor': f'AvgGoalsFor_L{WINDOW}', 'GoalsAgainst': f'AvgGoalsAgainst_L{WINDOW}', 'ShotsFor': f'AvgShotsFor_L{WINDOW}', 'SOT_For': f'AvgSOTFor_L{WINDOW}', 'Win': f'WinRate_L{WINDOW}', 'Draw': f'DrawRate_L{WINDOW}'}) +rolling_stats = agg[['Team','Date', f'AvgGoalsFor_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}', f'AvgShotsFor_L{WINDOW}', f'AvgSOTFor_L{WINDOW}', f'WinRate_L{WINDOW}', f'DrawRate_L{WINDOW}']] + +# Exponential weighted features (lebih sensitif ke pertandingan paling akhir) +print('๐Ÿ“ˆ Menghitung EWM feature (lebih menekankan pertandingan terakhir)...') +team_df = team_df.sort_values(['Team','Date']).reset_index(drop=True) +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) + +ewm_stats = team_df.groupby(['Team','Date'])[[f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}']].last().reset_index() + +# Merge rolling & ewm back to match-level dataframe +print('๐Ÿ”— Menggabungkan statistik kembali ke level pertandingan...') +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Home_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Home_L{WINDOW}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Away_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Away_L{WINDOW}'}).drop(columns=['Team']) + +# Merge EWM +df = pd.merge(df, ewm_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Home_span{EWM_SPAN}'}).drop(columns=['Team']) +df = pd.merge(df, ewm_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Away_span{EWM_SPAN}'}).drop(columns=['Team']) + +# ------------------------- +# 3) Last result, streak, H2H, RestDays, HomeAdvantage, FormDiff +# ------------------------- +print('๐Ÿ”ง Membuat fitur LastResult, Streak, H2H, RestDays, HomeAdvantage, FormDiff...') + +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + + +def days_since_last(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return np.nan + return (date - prev.iloc[-1]['Date']).days + + +def home_advantage_score(team, date, df_matches, lookback=5): + prev_home = df_matches[(df_matches['Home']==team) & (df_matches['Date'] prev_home['AwayGoals']).sum() / len(prev_home) + +# Create columns with iteration (vectorized where possible) +home_last_res, away_last_res, home_streak, away_streak = [], [], [], [] +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +home_rest, away_rest, home_adv, away_adv = [], [], [], [] + +for _, r in df.iterrows(): + date = r['Date'] + h = r['Home']; a = r['Away'] + home_last_res.append(get_last_result(h, date, df)) + away_last_res.append(get_last_result(a, date, df)) + home_streak.append(get_recent_streak(h, date, df, WINDOW)) + away_streak.append(get_recent_streak(a, date, df, WINDOW)) + hw, aw, dr = calc_h2h_counts(h, a, date, df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + home_rest.append(days_since_last(h, date, df)) + away_rest.append(days_since_last(a, date, df)) + home_adv.append(home_advantage_score(h, date, df, lookback=WINDOW)) + away_adv.append(home_advantage_score(a, date, df, lookback=WINDOW)) + +# Attach +df['HomeLastResult'] = home_last_res +df['AwayLastResult'] = away_last_res +df['HomeWinStreak_L5'] = home_streak +df['AwayWinStreak_L5'] = away_streak + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +df['HomeRestDays'] = home_rest +df['AwayRestDays'] = away_rest + +df['HomeAdvantageScore'] = home_adv +df['AwayAdvantageScore'] = away_adv + +# Form difference features +print('โž• Menambahkan fitur FormDiff (selisih performa Home-Away)...') +df['FormDiff_WinRate'] = df[f'WinRate_Home_L{WINDOW}'] - df[f'WinRate_Away_L{WINDOW}'] +df['FormDiff_Goals'] = df[f'AvgGoalsFor_Home_L{WINDOW}'] - df[f'AvgGoalsFor_Away_L{WINDOW}'] +df['FormDiff_EWM_Goals'] = df[f'EWM_GoalsFor_Home_span{EWM_SPAN}'] - df[f'EWM_GoalsFor_Away_span{EWM_SPAN}'] + +# ------------------------- +# 4) Final features & cleaning +# ------------------------- +features = [ + # rolling home + f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_Home_L{WINDOW}', f'DrawRate_Home_L{WINDOW}', + # ewm home + f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_Home_span{EWM_SPAN}', + 'HomeLastResult', 'HomeWinStreak_L5', 'HomeRestDays', 'HomeAdvantageScore', + # rolling away + f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_Away_L{WINDOW}', f'DrawRate_Away_L{WINDOW}', + # ewm away + f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_Away_span{EWM_SPAN}', + 'AwayLastResult', 'AwayWinStreak_L5', 'AwayRestDays', 'AwayAdvantageScore', + # H2H & diffs + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', 'FormDiff_WinRate', 'FormDiff_Goals', 'FormDiff_EWM_Goals' +] + +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 5) Hyperparameter tuning (Optuna) - optional +# ------------------------- + +def run_optuna_xgb(X_train, y_train, n_trials=40): + if not HAS_OPTUNA or not HAS_XGB: + print('โš ๏ธ Optuna atau XGBoost tidak tersedia, melewati tuning Optuna.') + return None + print('๐Ÿ”Ž Menjalankan Optuna tuning untuk XGBoost (TimeSeries CV)...') + tscv = TimeSeriesSplit(n_splits=3) + + def objective(trial): + params = { + 'n_estimators': trial.suggest_int('n_estimators', 100, 800), + 'max_depth': trial.suggest_int('max_depth', 3, 8), + 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2), + 'subsample': trial.suggest_float('subsample', 0.6, 1.0), + 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0), + 'gamma': trial.suggest_float('gamma', 0.0, 2.0) + } + scores = [] + for train_idx, test_idx in tscv.split(X_train): + m = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **params) + m.fit(X_train.iloc[train_idx], y_train.iloc[train_idx]) + ypred = m.predict(X_train.iloc[test_idx]) + scores.append(accuracy_score(y_train.iloc[test_idx], ypred)) + return np.mean(scores) + + study = optuna.create_study(direction='maximize') + study.optimize(objective, n_trials=n_trials) + print('๐Ÿ”” Optuna selesai. Best params:', study.best_params) + return study.best_params + +# ------------------------- +# 6) Build final model (ensemble) and evaluate on temporal hold-out +# ------------------------- +print('๐Ÿงช Menyiapkan model ensemble dan training final...') +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +# Try tuning XGB on training portion +best_xgb_params = None +if HAS_OPTUNA and HAS_XGB: + try: + best_xgb_params = run_optuna_xgb(X_train_final, y_train_final, n_trials=30) + except Exception as e: + print('Info: Optuna tuning gagal atau memakan waktu โ€” memakai default params. Error:', e) + +if HAS_XGB: + xgb_params = {'n_estimators': 400, 'learning_rate': 0.05, 'max_depth': 4, 'subsample': 0.8, 'colsample_bytree': 0.8} + if best_xgb_params: + xgb_params.update(best_xgb_params) + xgb_clf = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **xgb_params) +else: + xgb_clf = None + +rf_clf = RandomForestClassifier(n_estimators=400, max_depth=6, random_state=42, class_weight='balanced') + +estimators = [] +if xgb_clf is not None: + estimators.append(('xgb', xgb_clf)) +estimators.append(('rf', rf_clf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train_final, y_train_final) + +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test_final) +try: + y_proba = pipeline.predict_proba(X_test_final) +except Exception: + # predict_proba mungkin tidak tersedia (mis. stacking tanpa prob). Tangani gracefully + y_proba = None + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) if y_proba is not None else np.nan +print('\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('\nClassification Report:\n') +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print('Confusion Matrix (rows true, cols pred):\n', cm) + +# ------------------------- +# 7) Predict function (dinamis) menggunakan feature terakhir (rolling & ewm tail) +# ------------------------- +print('\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis) versi v3...') +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +# for ewm, take the latest per team +team_ewm_latest = ewm_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, team_roll_df=team_latest_stats, team_ewm_df=team_ewm_latest, history_df=full_match_history): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print('โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim.'); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + he = team_ewm_df.loc[home] + ae = team_ewm_df.loc[away] + + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, WINDOW) + a_streak = get_recent_streak(away, today, history_df, WINDOW) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + h_rest = days_since_last(home, today, history_df) + a_rest = days_since_last(away, today, history_df) + h_adv = home_advantage_score(home, today, history_df, lookback=WINDOW) + a_adv = home_advantage_score(away, today, history_df, lookback=WINDOW) + + feat_vals = [ + h[f'AvgGoalsFor_L{WINDOW}'], h[f'AvgGoalsAgainst_L{WINDOW}'], h[f'AvgShotsFor_L{WINDOW}'], h[f'AvgSOTFor_L{WINDOW}'], h[f'WinRate_L{WINDOW}'], h[f'DrawRate_L{WINDOW}'], + he[f'EWM_GoalsFor_span{EWM_SPAN}'] , he[f'EWM_WinRate_span{EWM_SPAN}'], + h_last_res, h_streak, h_rest, h_adv, + a[f'AvgGoalsFor_L{WINDOW}'], a[f'AvgGoalsAgainst_L{WINDOW}'], a[f'AvgShotsFor_L{WINDOW}'], a[f'AvgSOTFor_L{WINDOW}'], a[f'WinRate_L{WINDOW}'], a[f'DrawRate_L{WINDOW}'], + ae[f'EWM_GoalsFor_span{EWM_SPAN}'] , ae[f'EWM_WinRate_span{EWM_SPAN}'], + a_last_res, a_streak, a_rest, a_adv, + h2h_hw, h2h_aw, h2h_d, + h[f'AvgGoalsFor_L{WINDOW}'] - a[f'AvgGoalsFor_L{WINDOW}'], # FormDiff_Goals (fallback) + he[f'EWM_GoalsFor_span{EWM_SPAN}'] - ae[f'EWM_GoalsFor_span{EWM_SPAN}'] + ] + + # Map columns to features list (support backward compatibility) + cols_for_input = features.copy() + X_input = pd.DataFrame([feat_vals], columns=cols_for_input) + + probs = model.predict_proba(X_input)[0] + print(f"Info Form (Home): WinRate={h[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={he[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={h_last_res}, RestDays={h_rest}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={ae[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={a_last_res}, RestDays={a_rest}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (aman jika team ada di data latest) +try: + predict_match('Aston Villa', 'Burnley') + predict_match('Wolves', 'Brighton') +except Exception as e: + print('Info: contoh prediksi gagal -', e) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v3.joblib') +print('\n๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v3.joblib"') diff --git a/.history/predict_match_plV3_20251006192059.py b/.history/predict_match_plV3_20251006192059.py new file mode 100644 index 0000000000000000000000000000000000000000..084511b398f9221c74014d21bc6a7ee55b2d7c97 --- /dev/null +++ b/.history/predict_match_plV3_20251006192059.py @@ -0,0 +1,377 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan feature engineering + Optuna tuning + ensemble +# - Fitur baru: EWM (weighted rolling), FormDiff, HomeAdvantage, RestDays +# - Hyperparameter tuning (Optuna) jika tersedia +# - Ensemble (XGB + RandomForest) dengan fallback +# - Fungsi predict_match diperbarui untuk menangani feature baru + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + import optuna + HAS_OPTUNA = True +except Exception: + HAS_OPTUNA = False + +# ------------------------- +# 1) Load & basic preprocessing (tidak berubah banyak) +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV.") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (for rolling/EWM features) +# ------------------------- +print('๐Ÿ” Menyusun team-level records untuk fitur rolling/EWM...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +# Parameters +WINDOW = 5 +EWM_SPAN = 5 + +# Rolling means (simple) - existing style +agg = team_df.groupby('Team').rolling(window=WINDOW, on='Date', min_periods=1).mean().reset_index() +agg = agg.rename(columns={'GoalsFor': f'AvgGoalsFor_L{WINDOW}', 'GoalsAgainst': f'AvgGoalsAgainst_L{WINDOW}', 'ShotsFor': f'AvgShotsFor_L{WINDOW}', 'SOT_For': f'AvgSOTFor_L{WINDOW}', 'Win': f'WinRate_L{WINDOW}', 'Draw': f'DrawRate_L{WINDOW}'}) +rolling_stats = agg[['Team','Date', f'AvgGoalsFor_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}', f'AvgShotsFor_L{WINDOW}', f'AvgSOTFor_L{WINDOW}', f'WinRate_L{WINDOW}', f'DrawRate_L{WINDOW}']] + +# Exponential weighted features (lebih sensitif ke pertandingan paling akhir) +print('๐Ÿ“ˆ Menghitung EWM feature (lebih menekankan pertandingan terakhir)...') +team_df = team_df.sort_values(['Team','Date']).reset_index(drop=True) +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) + +ewm_stats = team_df.groupby(['Team','Date'])[[f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}']].last().reset_index() + +# Merge rolling & ewm back to match-level dataframe +print('๐Ÿ”— Menggabungkan statistik kembali ke level pertandingan...') +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Home_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Home_L{WINDOW}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Away_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Away_L{WINDOW}'}).drop(columns=['Team']) + +# Merge EWM +df = pd.merge(df, ewm_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Home_span{EWM_SPAN}'}).drop(columns=['Team']) +df = pd.merge(df, ewm_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Away_span{EWM_SPAN}'}).drop(columns=['Team']) + +# ------------------------- +# 3) Last result, streak, H2H, RestDays, HomeAdvantage, FormDiff +# ------------------------- +print('๐Ÿ”ง Membuat fitur LastResult, Streak, H2H, RestDays, HomeAdvantage, FormDiff...') + +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + + +def days_since_last(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return np.nan + return (date - prev.iloc[-1]['Date']).days + + +def home_advantage_score(team, date, df_matches, lookback=5): + prev_home = df_matches[(df_matches['Home']==team) & (df_matches['Date'] prev_home['AwayGoals']).sum() / len(prev_home) + +# Create columns with iteration (vectorized where possible) +home_last_res, away_last_res, home_streak, away_streak = [], [], [], [] +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +home_rest, away_rest, home_adv, away_adv = [], [], [], [] + +for _, r in df.iterrows(): + date = r['Date'] + h = r['Home']; a = r['Away'] + home_last_res.append(get_last_result(h, date, df)) + away_last_res.append(get_last_result(a, date, df)) + home_streak.append(get_recent_streak(h, date, df, WINDOW)) + away_streak.append(get_recent_streak(a, date, df, WINDOW)) + hw, aw, dr = calc_h2h_counts(h, a, date, df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + home_rest.append(days_since_last(h, date, df)) + away_rest.append(days_since_last(a, date, df)) + home_adv.append(home_advantage_score(h, date, df, lookback=WINDOW)) + away_adv.append(home_advantage_score(a, date, df, lookback=WINDOW)) + +# Attach +df['HomeLastResult'] = home_last_res +df['AwayLastResult'] = away_last_res +df['HomeWinStreak_L5'] = home_streak +df['AwayWinStreak_L5'] = away_streak + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +df['HomeRestDays'] = home_rest +df['AwayRestDays'] = away_rest + +df['HomeAdvantageScore'] = home_adv +df['AwayAdvantageScore'] = away_adv + +# Form difference features +print('โž• Menambahkan fitur FormDiff (selisih performa Home-Away)...') +df['FormDiff_WinRate'] = df[f'WinRate_Home_L{WINDOW}'] - df[f'WinRate_Away_L{WINDOW}'] +df['FormDiff_Goals'] = df[f'AvgGoalsFor_Home_L{WINDOW}'] - df[f'AvgGoalsFor_Away_L{WINDOW}'] +df['FormDiff_EWM_Goals'] = df[f'EWM_GoalsFor_Home_span{EWM_SPAN}'] - df[f'EWM_GoalsFor_Away_span{EWM_SPAN}'] + +# ------------------------- +# 4) Final features & cleaning +# ------------------------- +features = [ + # rolling home + f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_Home_L{WINDOW}', f'DrawRate_Home_L{WINDOW}', + # ewm home + f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_Home_span{EWM_SPAN}', + 'HomeLastResult', 'HomeWinStreak_L5', 'HomeRestDays', 'HomeAdvantageScore', + # rolling away + f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_Away_L{WINDOW}', f'DrawRate_Away_L{WINDOW}', + # ewm away + f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_Away_span{EWM_SPAN}', + 'AwayLastResult', 'AwayWinStreak_L5', 'AwayRestDays', 'AwayAdvantageScore', + # H2H & diffs + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', 'FormDiff_WinRate', 'FormDiff_Goals', 'FormDiff_EWM_Goals' +] + +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 5) Hyperparameter tuning (Optuna) - optional +# ------------------------- + +def run_optuna_xgb(X_train, y_train, n_trials=40): + if not HAS_OPTUNA or not HAS_XGB: + print('โš ๏ธ Optuna atau XGBoost tidak tersedia, melewati tuning Optuna.') + return None + print('๐Ÿ”Ž Menjalankan Optuna tuning untuk XGBoost (TimeSeries CV)...') + tscv = TimeSeriesSplit(n_splits=3) + + def objective(trial): + params = { + 'n_estimators': trial.suggest_int('n_estimators', 100, 800), + 'max_depth': trial.suggest_int('max_depth', 3, 8), + 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2), + 'subsample': trial.suggest_float('subsample', 0.6, 1.0), + 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0), + 'gamma': trial.suggest_float('gamma', 0.0, 2.0) + } + scores = [] + for train_idx, test_idx in tscv.split(X_train): + m = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **params) + m.fit(X_train.iloc[train_idx], y_train.iloc[train_idx]) + ypred = m.predict(X_train.iloc[test_idx]) + scores.append(accuracy_score(y_train.iloc[test_idx], ypred)) + return np.mean(scores) + + study = optuna.create_study(direction='maximize') + study.optimize(objective, n_trials=n_trials) + print('๐Ÿ”” Optuna selesai. Best params:', study.best_params) + return study.best_params + +# ------------------------- +# 6) Build final model (ensemble) and evaluate on temporal hold-out +# ------------------------- +print('๐Ÿงช Menyiapkan model ensemble dan training final...') +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +# Try tuning XGB on training portion +best_xgb_params = None +if HAS_OPTUNA and HAS_XGB: + try: + best_xgb_params = run_optuna_xgb(X_train_final, y_train_final, n_trials=30) + except Exception as e: + print('Info: Optuna tuning gagal atau memakan waktu โ€” memakai default params. Error:', e) + +if HAS_XGB: + xgb_params = {'n_estimators': 400, 'learning_rate': 0.05, 'max_depth': 4, 'subsample': 0.8, 'colsample_bytree': 0.8} + if best_xgb_params: + xgb_params.update(best_xgb_params) + xgb_clf = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **xgb_params) +else: + xgb_clf = None + +rf_clf = RandomForestClassifier(n_estimators=400, max_depth=6, random_state=42, class_weight='balanced') + +estimators = [] +if xgb_clf is not None: + estimators.append(('xgb', xgb_clf)) +estimators.append(('rf', rf_clf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train_final, y_train_final) + +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test_final) +try: + y_proba = pipeline.predict_proba(X_test_final) +except Exception: + # predict_proba mungkin tidak tersedia (mis. stacking tanpa prob). Tangani gracefully + y_proba = None + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) if y_proba is not None else np.nan +print('\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('\nClassification Report:\n') +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print('Confusion Matrix (rows true, cols pred):\n', cm) + +# ------------------------- +# 7) Predict function (dinamis) menggunakan feature terakhir (rolling & ewm tail) +# ------------------------- +print('\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis) versi v3...') +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +# for ewm, take the latest per team +team_ewm_latest = ewm_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, team_roll_df=team_latest_stats, team_ewm_df=team_ewm_latest, history_df=full_match_history): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print('โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim.'); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + he = team_ewm_df.loc[home] + ae = team_ewm_df.loc[away] + + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, WINDOW) + a_streak = get_recent_streak(away, today, history_df, WINDOW) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + h_rest = days_since_last(home, today, history_df) + a_rest = days_since_last(away, today, history_df) + h_adv = home_advantage_score(home, today, history_df, lookback=WINDOW) + a_adv = home_advantage_score(away, today, history_df, lookback=WINDOW) + + feat_vals = [ + h[f'AvgGoalsFor_L{WINDOW}'], h[f'AvgGoalsAgainst_L{WINDOW}'], h[f'AvgShotsFor_L{WINDOW}'], h[f'AvgSOTFor_L{WINDOW}'], h[f'WinRate_L{WINDOW}'], h[f'DrawRate_L{WINDOW}'], + he[f'EWM_GoalsFor_span{EWM_SPAN}'] , he[f'EWM_WinRate_span{EWM_SPAN}'], + h_last_res, h_streak, h_rest, h_adv, + a[f'AvgGoalsFor_L{WINDOW}'], a[f'AvgGoalsAgainst_L{WINDOW}'], a[f'AvgShotsFor_L{WINDOW}'], a[f'AvgSOTFor_L{WINDOW}'], a[f'WinRate_L{WINDOW}'], a[f'DrawRate_L{WINDOW}'], + ae[f'EWM_GoalsFor_span{EWM_SPAN}'] , ae[f'EWM_WinRate_span{EWM_SPAN}'], + a_last_res, a_streak, a_rest, a_adv, + h2h_hw, h2h_aw, h2h_d, + h[f'AvgGoalsFor_L{WINDOW}'] - a[f'AvgGoalsFor_L{WINDOW}'], # FormDiff_Goals (fallback) + he[f'EWM_GoalsFor_span{EWM_SPAN}'] - ae[f'EWM_GoalsFor_span{EWM_SPAN}'] + ] + + # Map columns to features list (support backward compatibility) + cols_for_input = features.copy() + X_input = pd.DataFrame([feat_vals], columns=cols_for_input) + + probs = model.predict_proba(X_input)[0] + print(f"Info Form (Home): WinRate={h[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={he[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={h_last_res}, RestDays={h_rest}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={ae[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={a_last_res}, RestDays={a_rest}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (aman jika team ada di data latest) +try: + predict_match('Aston Villa', 'Burnley') + predict_match('Wolves', 'Brighton') +except Exception as e: + print('Info: contoh prediksi gagal -', e) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v3.joblib') +print('\n๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v3.joblib"' \ No newline at end of file diff --git a/.history/predict_match_plV3_20251006192101.py b/.history/predict_match_plV3_20251006192101.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/predict_match_plV3_20251006192102.py b/.history/predict_match_plV3_20251006192102.py new file mode 100644 index 0000000000000000000000000000000000000000..36f525adda7f52fad5ffb9ae9352770f767e10ff --- /dev/null +++ b/.history/predict_match_plV3_20251006192102.py @@ -0,0 +1,377 @@ +# predict-pl-match_otomatis_v3.py +# Versi v3 โ€” peningkatan feature engineering + Optuna tuning + ensemble +# - Fitur baru: EWM (weighted rolling), FormDiff, HomeAdvantage, RestDays +# - Hyperparameter tuning (Optuna) jika tersedia +# - Ensemble (XGB + RandomForest) dengan fallback +# - Fungsi predict_match diperbarui untuk menangani feature baru + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + import optuna + HAS_OPTUNA = True +except Exception: + HAS_OPTUNA = False + +# ------------------------- +# 1) Load & basic preprocessing (tidak berubah banyak) +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV.") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (for rolling/EWM features) +# ------------------------- +print('๐Ÿ” Menyusun team-level records untuk fitur rolling/EWM...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +# Parameters +WINDOW = 5 +EWM_SPAN = 5 + +# Rolling means (simple) - existing style +agg = team_df.groupby('Team').rolling(window=WINDOW, on='Date', min_periods=1).mean().reset_index() +agg = agg.rename(columns={'GoalsFor': f'AvgGoalsFor_L{WINDOW}', 'GoalsAgainst': f'AvgGoalsAgainst_L{WINDOW}', 'ShotsFor': f'AvgShotsFor_L{WINDOW}', 'SOT_For': f'AvgSOTFor_L{WINDOW}', 'Win': f'WinRate_L{WINDOW}', 'Draw': f'DrawRate_L{WINDOW}'}) +rolling_stats = agg[['Team','Date', f'AvgGoalsFor_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}', f'AvgShotsFor_L{WINDOW}', f'AvgSOTFor_L{WINDOW}', f'WinRate_L{WINDOW}', f'DrawRate_L{WINDOW}']] + +# Exponential weighted features (lebih sensitif ke pertandingan paling akhir) +print('๐Ÿ“ˆ Menghitung EWM feature (lebih menekankan pertandingan terakhir)...') +team_df = team_df.sort_values(['Team','Date']).reset_index(drop=True) +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.ewm(span=EWM_SPAN, adjust=False).mean()) + +ewm_stats = team_df.groupby(['Team','Date'])[[f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}']].last().reset_index() + +# Merge rolling & ewm back to match-level dataframe +print('๐Ÿ”— Menggabungkan statistik kembali ke level pertandingan...') +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Home_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Home_L{WINDOW}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{WINDOW}': f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_L{WINDOW}': f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_L{WINDOW}': f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_L{WINDOW}': f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_L{WINDOW}': f'WinRate_Away_L{WINDOW}', f'DrawRate_L{WINDOW}': f'DrawRate_Away_L{WINDOW}'}).drop(columns=['Team']) + +# Merge EWM +df = pd.merge(df, ewm_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Home_span{EWM_SPAN}'}).drop(columns=['Team']) +df = pd.merge(df, ewm_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'EWM_GoalsFor_span{EWM_SPAN}': f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}': f'EWM_WinRate_Away_span{EWM_SPAN}'}).drop(columns=['Team']) + +# ------------------------- +# 3) Last result, streak, H2H, RestDays, HomeAdvantage, FormDiff +# ------------------------- +print('๐Ÿ”ง Membuat fitur LastResult, Streak, H2H, RestDays, HomeAdvantage, FormDiff...') + +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + + +def days_since_last(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return np.nan + return (date - prev.iloc[-1]['Date']).days + + +def home_advantage_score(team, date, df_matches, lookback=5): + prev_home = df_matches[(df_matches['Home']==team) & (df_matches['Date'] prev_home['AwayGoals']).sum() / len(prev_home) + +# Create columns with iteration (vectorized where possible) +home_last_res, away_last_res, home_streak, away_streak = [], [], [], [] +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +home_rest, away_rest, home_adv, away_adv = [], [], [], [] + +for _, r in df.iterrows(): + date = r['Date'] + h = r['Home']; a = r['Away'] + home_last_res.append(get_last_result(h, date, df)) + away_last_res.append(get_last_result(a, date, df)) + home_streak.append(get_recent_streak(h, date, df, WINDOW)) + away_streak.append(get_recent_streak(a, date, df, WINDOW)) + hw, aw, dr = calc_h2h_counts(h, a, date, df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + home_rest.append(days_since_last(h, date, df)) + away_rest.append(days_since_last(a, date, df)) + home_adv.append(home_advantage_score(h, date, df, lookback=WINDOW)) + away_adv.append(home_advantage_score(a, date, df, lookback=WINDOW)) + +# Attach +df['HomeLastResult'] = home_last_res +df['AwayLastResult'] = away_last_res +df['HomeWinStreak_L5'] = home_streak +df['AwayWinStreak_L5'] = away_streak + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +df['HomeRestDays'] = home_rest +df['AwayRestDays'] = away_rest + +df['HomeAdvantageScore'] = home_adv +df['AwayAdvantageScore'] = away_adv + +# Form difference features +print('โž• Menambahkan fitur FormDiff (selisih performa Home-Away)...') +df['FormDiff_WinRate'] = df[f'WinRate_Home_L{WINDOW}'] - df[f'WinRate_Away_L{WINDOW}'] +df['FormDiff_Goals'] = df[f'AvgGoalsFor_Home_L{WINDOW}'] - df[f'AvgGoalsFor_Away_L{WINDOW}'] +df['FormDiff_EWM_Goals'] = df[f'EWM_GoalsFor_Home_span{EWM_SPAN}'] - df[f'EWM_GoalsFor_Away_span{EWM_SPAN}'] + +# ------------------------- +# 4) Final features & cleaning +# ------------------------- +features = [ + # rolling home + f'AvgGoalsFor_Home_L{WINDOW}', f'AvgGoalsAgainst_Home_L{WINDOW}', f'AvgShotsFor_Home_L{WINDOW}', f'AvgSOTFor_Home_L{WINDOW}', f'WinRate_Home_L{WINDOW}', f'DrawRate_Home_L{WINDOW}', + # ewm home + f'EWM_GoalsFor_Home_span{EWM_SPAN}', f'EWM_WinRate_Home_span{EWM_SPAN}', + 'HomeLastResult', 'HomeWinStreak_L5', 'HomeRestDays', 'HomeAdvantageScore', + # rolling away + f'AvgGoalsFor_Away_L{WINDOW}', f'AvgGoalsAgainst_Away_L{WINDOW}', f'AvgShotsFor_Away_L{WINDOW}', f'AvgSOTFor_Away_L{WINDOW}', f'WinRate_Away_L{WINDOW}', f'DrawRate_Away_L{WINDOW}', + # ewm away + f'EWM_GoalsFor_Away_span{EWM_SPAN}', f'EWM_WinRate_Away_span{EWM_SPAN}', + 'AwayLastResult', 'AwayWinStreak_L5', 'AwayRestDays', 'AwayAdvantageScore', + # H2H & diffs + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', 'FormDiff_WinRate', 'FormDiff_Goals', 'FormDiff_EWM_Goals' +] + +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 5) Hyperparameter tuning (Optuna) - optional +# ------------------------- + +def run_optuna_xgb(X_train, y_train, n_trials=40): + if not HAS_OPTUNA or not HAS_XGB: + print('โš ๏ธ Optuna atau XGBoost tidak tersedia, melewati tuning Optuna.') + return None + print('๐Ÿ”Ž Menjalankan Optuna tuning untuk XGBoost (TimeSeries CV)...') + tscv = TimeSeriesSplit(n_splits=3) + + def objective(trial): + params = { + 'n_estimators': trial.suggest_int('n_estimators', 100, 800), + 'max_depth': trial.suggest_int('max_depth', 3, 8), + 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2), + 'subsample': trial.suggest_float('subsample', 0.6, 1.0), + 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0), + 'gamma': trial.suggest_float('gamma', 0.0, 2.0) + } + scores = [] + for train_idx, test_idx in tscv.split(X_train): + m = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **params) + m.fit(X_train.iloc[train_idx], y_train.iloc[train_idx]) + ypred = m.predict(X_train.iloc[test_idx]) + scores.append(accuracy_score(y_train.iloc[test_idx], ypred)) + return np.mean(scores) + + study = optuna.create_study(direction='maximize') + study.optimize(objective, n_trials=n_trials) + print('๐Ÿ”” Optuna selesai. Best params:', study.best_params) + return study.best_params + +# ------------------------- +# 6) Build final model (ensemble) and evaluate on temporal hold-out +# ------------------------- +print('๐Ÿงช Menyiapkan model ensemble dan training final...') +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +# Try tuning XGB on training portion +best_xgb_params = None +if HAS_OPTUNA and HAS_XGB: + try: + best_xgb_params = run_optuna_xgb(X_train_final, y_train_final, n_trials=30) + except Exception as e: + print('Info: Optuna tuning gagal atau memakan waktu โ€” memakai default params. Error:', e) + +if HAS_XGB: + xgb_params = {'n_estimators': 400, 'learning_rate': 0.05, 'max_depth': 4, 'subsample': 0.8, 'colsample_bytree': 0.8} + if best_xgb_params: + xgb_params.update(best_xgb_params) + xgb_clf = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42, **xgb_params) +else: + xgb_clf = None + +rf_clf = RandomForestClassifier(n_estimators=400, max_depth=6, random_state=42, class_weight='balanced') + +estimators = [] +if xgb_clf is not None: + estimators.append(('xgb', xgb_clf)) +estimators.append(('rf', rf_clf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train_final, y_train_final) + +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test_final) +try: + y_proba = pipeline.predict_proba(X_test_final) +except Exception: + # predict_proba mungkin tidak tersedia (mis. stacking tanpa prob). Tangani gracefully + y_proba = None + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) if y_proba is not None else np.nan +print('\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('\nClassification Report:\n') +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print('Confusion Matrix (rows true, cols pred):\n', cm) + +# ------------------------- +# 7) Predict function (dinamis) menggunakan feature terakhir (rolling & ewm tail) +# ------------------------- +print('\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis) versi v3...') +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +# for ewm, take the latest per team +team_ewm_latest = ewm_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, team_roll_df=team_latest_stats, team_ewm_df=team_ewm_latest, history_df=full_match_history): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print('โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim.'); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + he = team_ewm_df.loc[home] + ae = team_ewm_df.loc[away] + + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, WINDOW) + a_streak = get_recent_streak(away, today, history_df, WINDOW) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + h_rest = days_since_last(home, today, history_df) + a_rest = days_since_last(away, today, history_df) + h_adv = home_advantage_score(home, today, history_df, lookback=WINDOW) + a_adv = home_advantage_score(away, today, history_df, lookback=WINDOW) + + feat_vals = [ + h[f'AvgGoalsFor_L{WINDOW}'], h[f'AvgGoalsAgainst_L{WINDOW}'], h[f'AvgShotsFor_L{WINDOW}'], h[f'AvgSOTFor_L{WINDOW}'], h[f'WinRate_L{WINDOW}'], h[f'DrawRate_L{WINDOW}'], + he[f'EWM_GoalsFor_span{EWM_SPAN}'] , he[f'EWM_WinRate_span{EWM_SPAN}'], + h_last_res, h_streak, h_rest, h_adv, + a[f'AvgGoalsFor_L{WINDOW}'], a[f'AvgGoalsAgainst_L{WINDOW}'], a[f'AvgShotsFor_L{WINDOW}'], a[f'AvgSOTFor_L{WINDOW}'], a[f'WinRate_L{WINDOW}'], a[f'DrawRate_L{WINDOW}'], + ae[f'EWM_GoalsFor_span{EWM_SPAN}'] , ae[f'EWM_WinRate_span{EWM_SPAN}'], + a_last_res, a_streak, a_rest, a_adv, + h2h_hw, h2h_aw, h2h_d, + h[f'AvgGoalsFor_L{WINDOW}'] - a[f'AvgGoalsFor_L{WINDOW}'], # FormDiff_Goals (fallback) + he[f'EWM_GoalsFor_span{EWM_SPAN}'] - ae[f'EWM_GoalsFor_span{EWM_SPAN}'] + ] + + # Map columns to features list (support backward compatibility) + cols_for_input = features.copy() + X_input = pd.DataFrame([feat_vals], columns=cols_for_input) + + probs = model.predict_proba(X_input)[0] + print(f"Info Form (Home): WinRate={h[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={he[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={h_last_res}, RestDays={h_rest}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{WINDOW}']:.2f}, EWM_Goals={ae[f'EWM_GoalsFor_span{EWM_SPAN}']:.2f}, LastResult={a_last_res}, RestDays={a_rest}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (aman jika team ada di data latest) +try: + predict_match('Aston Villa', 'Burnley') + predict_match('Wolves', 'Brighton') +except Exception as e: + print('Info: contoh prediksi gagal -', e) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v3.joblib') +print('\n๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v3.joblib"') diff --git a/.history/predict_match_plV3_20251006194508.py b/.history/predict_match_plV3_20251006194508.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8a2ea366e6a7d6bece47326dee34380fb92645 --- /dev/null +++ b/.history/predict_match_plV3_20251006194508.py @@ -0,0 +1,378 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print(' +๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print(' +Classification Report: +') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred): +', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194540.py b/.history/predict_match_plV3_20251006194540.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8a2ea366e6a7d6bece47326dee34380fb92645 --- /dev/null +++ b/.history/predict_match_plV3_20251006194540.py @@ -0,0 +1,378 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print(' +๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print(' +Classification Report: +') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred): +', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194558.py b/.history/predict_match_plV3_20251006194558.py new file mode 100644 index 0000000000000000000000000000000000000000..0b65520b1b24a4bc096c7019fc5a297d1ead9963 --- /dev/null +++ b/.history/predict_match_plV3_20251006194558.py @@ -0,0 +1,377 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print(' +๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print(' +Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred): +', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194601.py b/.history/predict_match_plV3_20251006194601.py new file mode 100644 index 0000000000000000000000000000000000000000..144df579dee50e7a2342cf464d054be509fb7c00 --- /dev/null +++ b/.history/predict_match_plV3_20251006194601.py @@ -0,0 +1,376 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print(' +๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred): +', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194605.py b/.history/predict_match_plV3_20251006194605.py new file mode 100644 index 0000000000000000000000000000000000000000..37e48265b32f743afb01c336618a263f699ff735 --- /dev/null +++ b/.history/predict_match_plV3_20251006194605.py @@ -0,0 +1,375 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print(' +๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194612.py b/.history/predict_match_plV3_20251006194612.py new file mode 100644 index 0000000000000000000000000000000000000000..8a52f7ddf0a558e10816a64d3b659f558b5f8805 --- /dev/null +++ b/.history/predict_match_plV3_20251006194612.py @@ -0,0 +1,374 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print(' +๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194619.py b/.history/predict_match_plV3_20251006194619.py new file mode 100644 index 0000000000000000000000000000000000000000..e3222a76d765693b343f6ba39c6154a4a2496895 --- /dev/null +++ b/.history/predict_match_plV3_20251006194619.py @@ -0,0 +1,373 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print(' +๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194624.py b/.history/predict_match_plV3_20251006194624.py new file mode 100644 index 0000000000000000000000000000000000000000..beb17b3fb78dce969011793f76506786b7ccc235 --- /dev/null +++ b/.history/predict_match_plV3_20251006194624.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194702.py b/.history/predict_match_plV3_20251006194702.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1d9f44049ce84e0e50ecff26ec5677caad8bce --- /dev/null +++ b/.history/predict_match_plV3_20251006194702.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_','') +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194710.py b/.history/predict_match_plV3_20251006194710.py new file mode 100644 index 0000000000000000000000000000000000000000..beb17b3fb78dce969011793f76506786b7ccc235 --- /dev/null +++ b/.history/predict_match_plV3_20251006194710.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_{f'Roll_GoalsFor_L{WINDOW}'}'.replace('Roll_',''), +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194756.py b/.history/predict_match_plV3_20251006194756.py new file mode 100644 index 0000000000000000000000000000000000000000..0c7e84f0b0ca9ac3f0bc774f059ae0ff17ba8e56 --- /dev/null +++ b/.history/predict_match_plV3_20251006194756.py @@ -0,0 +1,373 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") + +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194759.py b/.history/predict_match_plV3_20251006194759.py new file mode 100644 index 0000000000000000000000000000000000000000..b0f67267362d21719bc345738d452a3e220e4f1c --- /dev/null +++ b/.history/predict_match_plV3_20251006194759.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194937.py b/.history/predict_match_plV3_20251006194937.py new file mode 100644 index 0000000000000000000000000000000000000000..56747df84e8e4677538a2357bdeb0d65b25c71c3 --- /dev/null +++ b/.history/predict_match_plV3_20251006194937.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194944.py b/.history/predict_match_plV3_20251006194944.py new file mode 100644 index 0000000000000000000000000000000000000000..4f93a5208115c1c0fe9d396472d65413c4756229 --- /dev/null +++ b/.history/predict_match_plV3_20251006194944.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194948.py b/.history/predict_match_plV3_20251006194948.py new file mode 100644 index 0000000000000000000000000000000000000000..0082f8c5e29c33d8e908818c17443aa5ba29ddce --- /dev/null +++ b/.history/predict_match_plV3_20251006194948.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194951.py b/.history/predict_match_plV3_20251006194951.py new file mode 100644 index 0000000000000000000000000000000000000000..17f519a14bb631f544176033dd30e236829ac872 --- /dev/null +++ b/.history/predict_match_plV3_20251006194951.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194955.py b/.history/predict_match_plV3_20251006194955.py new file mode 100644 index 0000000000000000000000000000000000000000..8fc902a0758da1dc9a3ce4cae8c23a59bdf00bf2 --- /dev/null +++ b/.history/predict_match_plV3_20251006194955.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006194959.py b/.history/predict_match_plV3_20251006194959.py new file mode 100644 index 0000000000000000000000000000000000000000..62e3df52d8615f38f21a568a3e23f804572126a0 --- /dev/null +++ b/.history/predict_match_plV3_20251006194959.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].apply(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006195003.py b/.history/predict_match_plV3_20251006195003.py new file mode 100644 index 0000000000000000000000000000000000000000..acb2a3c37a4f576c5f4eb6986485b366e2c91efc --- /dev/null +++ b/.history/predict_match_plV3_20251006195003.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006195019.py b/.history/predict_match_plV3_20251006195019.py new file mode 100644 index 0000000000000000000000000000000000000000..b4a7c7472cc419f15889d4a91123782a40720949 --- /dev/null +++ b/.history/predict_match_plV3_20251006195019.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].apply(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006195026.py b/.history/predict_match_plV3_20251006195026.py new file mode 100644 index 0000000000000000000000000000000000000000..7e2f5467da27063ce3018962c0e5ecca794d550b --- /dev/null +++ b/.history/predict_match_plV3_20251006195026.py @@ -0,0 +1,372 @@ +# predict-pl-match_otomatis_v4.py +# Versi v4 โ€” leakage-safe feature engineering + Elo rating + rolling & EWM dengan shift +# - Semua fitur dihitung hanya dari masa lalu (menggunakan .shift()) +# - Fitur baru: Elo rating pra-pertandingan, GoalDiff rolling, Points rolling +# - Model ensemble: LightGBM / XGBoost / RandomForest (soft voting) dengan fallback +# - Menyimpan model dan daftar fitur (model_features_v4.joblib) + +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings('ignore') + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier, VotingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except Exception: + HAS_XGB = False + +try: + from lightgbm import LGBMClassifier + HAS_LGB = True +except Exception: + HAS_LGB = False + +# ------------------------- +# 1) Load data +# ------------------------- +print('๐Ÿ“Š Memuat data...') +raw_path = 'epl-training.csv' +if not os.path.exists(raw_path): + raise FileNotFoundError(f"File {raw_path} tidak ditemukan. Pastikan berada di folder yang sama.") + +df = pd.read_csv(raw_path, encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +for c in expected: + if c not in df.columns: + raise ValueError(f"Kolom {c} tidak ditemukan di CSV: {c}") + +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', + 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# Basic result label +print('๐Ÿ”ง Membuat label hasil (Result)...') +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 + +df['Result'] = df.apply(result_label, axis=1) + +# ------------------------- +# 2) Build team-level dataframe (satu baris per tim per match) +# ------------------------- +print('๐Ÿ” Menyusun team-level records (leakage-safe)...') +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Home'], 'Opponent': r['Away'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0, 'IsHome': 1, 'Points': 3 if r['HomeGoals']>r['AwayGoals'] else (1 if r['HomeGoals']==r['AwayGoals'] else 0)}) + team_rows.append({'Date': r['Date'], 'MatchID': _, 'Team': r['Away'], 'Opponent': r['Home'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0, 'IsHome': 0, 'Points': 3 if r['AwayGoals']>r['HomeGoals'] else (1 if r['AwayGoals']==r['HomeGoals'] else 0)}) + +team_df = pd.DataFrame(team_rows).sort_values(['Team','Date']).reset_index(drop=True) + +WINDOW = 5 +EWM_SPAN = 5 + +# ------------------------- +# 3) Leakage-safe rolling & EWM (pakai shift) +# ------------------------- +print('๐Ÿ“ˆ Menghitung fitur rolling (menggunakan .shift())...') +# Rolling means dari 5 pertandingan terakhir, tapi hanya data masa lalu (shift sebelum rolling) +team_df[f'Roll_GoalsFor_L{WINDOW}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_GoalsAgainst_L{WINDOW}'] = team_df.groupby('Team')['GoalsAgainst'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_ShotsFor_L{WINDOW}'] = team_df.groupby('Team')['ShotsFor'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_WinRate_L{WINDOW}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +team_df[f'Roll_Points_L{WINDOW}'] = team_df.groupby('Team')['Points'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).sum()) + +# EWM (shift then ewm) => past-weighted recent form +team_df[f'EWM_GoalsFor_span{EWM_SPAN}'] = team_df.groupby('Team')['GoalsFor'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) +team_df[f'EWM_WinRate_span{EWM_SPAN}'] = team_df.groupby('Team')['Win'].transform(lambda x: x.shift().ewm(span=EWM_SPAN, adjust=False).mean()) + +# Days since last match +team_df['PrevMatchDate'] = team_df.groupby('Team')['Date'].shift(1) +team_df['DaysSinceLast'] = (team_df['Date'] - team_df['PrevMatchDate']).dt.days + +# Home advantage: compute proportion kemenangan di kandang pada 5 home matches terakhir +home_matches = team_df[team_df['IsHome']==1].copy() +home_matches[f'HomeWinRateLast{WINDOW}'] = home_matches.groupby('Team')['Win'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) +# merge back +team_df = pd.merge(team_df, home_matches[['MatchID','Team',f'HomeWinRateLast{WINDOW}']], on=['MatchID','Team'], how='left') + +# GoalDiff rolling +team_df['GoalDiff'] = team_df['GoalsFor'] - team_df['GoalsAgainst'] +team_df[f'Roll_GoalDiff_L{WINDOW}'] = team_df.groupby('Team')['GoalDiff'].transform(lambda x: x.shift().rolling(WINDOW, min_periods=1).mean()) + +# ------------------------- +# 4) Elo rating (iterative, pra-pertandingan) +# ------------------------- +print('โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...') +teams = pd.unique(team_df['Team']) +elo = {t:1500 for t in teams} +K = 20 +elo_home, elo_away = [], [] +# We'll iterate original matches (df) to maintain consistent order +for idx, row in df.iterrows(): + home = row['Home']; away = row['Away'] + # current elo before match + eh = elo.get(home,1500) + ea = elo.get(away,1500) + elo_home.append(eh) + elo_away.append(ea) + # expected score + exp_h = 1 / (1 + 10 ** ((ea - eh) / 400)) + exp_a = 1 - exp_h + # actual score + if row['HomeGoals'] > row['AwayGoals']: + sh, sa = 1.0, 0.0 + elif row['HomeGoals'] < row['AwayGoals']: + sh, sa = 0.0, 1.0 + else: + sh, sa = 0.5, 0.5 + # update + elo[home] = eh + K * (sh - exp_h) + elo[away] = ea + K * (sa - exp_a) + +# attach elo to original df as pre-match elo +df['Elo_Home_pre'] = elo_home +df['Elo_Away_pre'] = elo_away + +# Now merge team_df rolling features back to match-level df +print('๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...') +# prepare rolling stats per team-date (last record per team-date) +roll_cols = [f'Roll_GoalsFor_L{WINDOW}', f'Roll_GoalsAgainst_L{WINDOW}', f'Roll_ShotsFor_L{WINDOW}', f'Roll_WinRate_L{WINDOW}', f'Roll_Points_L{WINDOW}', f'EWM_GoalsFor_span{EWM_SPAN}', f'EWM_WinRate_span{EWM_SPAN}', 'DaysSinceLast', f'HomeWinRateLast{WINDOW}', f'Roll_GoalDiff_L{WINDOW}'] +team_roll = team_df.groupby(['Team','Date'])[roll_cols].last().reset_index() + +# Merge home +df = pd.merge(df, team_roll, left_on=['Home','Date'], right_on=['Team','Date'], how='left') +df = df.rename(columns={c: c.replace('Roll_','Avg_').replace('EWM_','EWM_') for c in roll_cols}) +# rename specific home cols +home_renames = {} +for c in roll_cols: + home_renames[c] = f'Home_{c}' + +# apply rename for home +df = df.rename(columns=home_renames) +# drop redundant Team column +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Merge away +df = pd.merge(df, team_roll, left_on=['Away','Date'], right_on=['Team','Date'], how='left', suffixes=('_home','_away')) +away_renames = {} +for c in roll_cols: + away_renames[c] = f'Away_{c}' + +df = df.rename(columns=away_renames) +if 'Team' in df.columns: + df = df.drop(columns=['Team']) + +# Now df has Elo_Home_pre, Elo_Away_pre and Home_/Away_ rolling features + +# ------------------------- +# 5) Final feature list (leakage-free) +# ------------------------- +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f"Home_Roll_GoalsFor_L{WINDOW}".replace("Roll_", "") +] +# Build features explicitly to avoid ambiguity +features = [ + 'Elo_Home_pre', 'Elo_Away_pre', + f'Home_Roll_GoalsFor_L{WINDOW}', f'Home_Roll_GoalsAgainst_L{WINDOW}', f'Home_Roll_ShotsFor_L{WINDOW}', f'Home_Roll_WinRate_L{WINDOW}', f'Home_Roll_Points_L{WINDOW}', f'Home_EWM_GoalsFor_span{EWM_SPAN}', f'Home_EWM_WinRate_span{EWM_SPAN}', 'Home_DaysSinceLast', f'Home_HomeWinRateLast{WINDOW}', f'Home_Roll_GoalDiff_L{WINDOW}', + f'Away_Roll_GoalsFor_L{WINDOW}', f'Away_Roll_GoalsAgainst_L{WINDOW}', f'Away_Roll_ShotsFor_L{WINDOW}', f'Away_Roll_WinRate_L{WINDOW}', f'Away_Roll_Points_L{WINDOW}', f'Away_EWM_GoalsFor_span{EWM_SPAN}', f'Away_EWM_WinRate_span{EWM_SPAN}', 'Away_DaysSinceLast', f'Away_HomeWinRateLast{WINDOW}', f'Away_Roll_GoalDiff_L{WINDOW}', + # head-to-head counts (leakage-safe computed earlier via previous matches) + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] + +# Prepare H2H counts (leakage-safe) +print('โš”๏ธ Menghitung H2H (leakage-safe)...') +# reuse previous functions but ensure use only past matches + +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) + +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + hw, aw, dr = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(hw); h2h_away_wins.append(aw); h2h_draws.append(dr) + +df['h2h_home_wins'] = h2h_home_wins +df['h2h_away_wins'] = h2h_away_wins +df['h2h_draws'] = h2h_draws + +# Final cleaning: select features present +print('๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...') +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling (leakage-free): {X.shape[0]} baris x {X.shape[1]} fitur") + +# ------------------------- +# 6) Temporal split (80% awal untuk train, 20% akhir untuk test) โ€” time-ordered +# ------------------------- +print('๐Ÿงช Membuat temporal hold-out (80/20) โ€” time ordered split...') +split_idx = int(len(df_model) * 0.8) +X_train, y_train = X.iloc[:split_idx], y.iloc[:split_idx] +X_test, y_test = X.iloc[split_idx:], y.iloc[split_idx:] + +# ------------------------- +# 7) Model ensemble (LightGBM / XGB / RF) dengan fallback +# ------------------------- +print('โš™๏ธ Menyiapkan model ensemble (LGBM/XGB/RF) โ€” soft voting') +estimators = [] +if HAS_LGB: + lgb = LGBMClassifier(n_estimators=400, learning_rate=0.05, max_depth=6, random_state=42) + estimators.append(('lgb', lgb)) +if HAS_XGB: + xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', n_estimators=400, learning_rate=0.05, max_depth=4, random_state=42) + estimators.append(('xgb', xgb)) +# always include RF +rf = RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, class_weight='balanced') +estimators.append(('rf', rf)) + +ensemble = VotingClassifier(estimators=estimators, voting='soft') +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', ensemble)]) + +print('๐Ÿ” Melatih pipeline ensemble pada 80% data training...') +pipeline.fit(X_train, y_train) + +# Simpan daftar fitur untuk prediksi +model_features = X_train.columns.tolist() +joblib.dump(model_features, 'model_features_v4.joblib') + +# ------------------------- +# 8) Evaluasi pada hold-out +# ------------------------- +print('๐Ÿ“ˆ Evaluasi akhir pada 20% hold-out...') +y_pred = pipeline.predict(X_test) +try: + y_proba = pipeline.predict_proba(X_test) +except Exception: + y_proba = None + +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, y_proba) if y_proba is not None else np.nan +print('๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):') +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +if not np.isnan(ll): + print(f" - Log Loss : {ll:.4f}") + +print('Classification Report:') +print(classification_report(y_test, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test, y_pred) +print('Confusion Matrix (rows true, cols pred):', cm) + +# Simpan model +joblib.dump(pipeline, 'model_epl_final_v4.joblib') +print('๐Ÿ’พ Model tersimpan sebagai "model_epl_final_v4.joblib"') + +# ------------------------- +# 9) Fungsi predict_match (menggunakan model_features_v4.joblib untuk reindex) +# ------------------------- +print('๐Ÿ”ฎ Menyediakan fungsi predict_match (v4) yang leakage-safe...') + +# load features and model (they are already in memory, but use saved for safety) +saved_features = joblib.load('model_features_v4.joblib') + +name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + +def predict_match(home_team_input, away_team_input, model=pipeline, features_list=saved_features, df_full=df_model): + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print('='*40) + print(f'PREDIKSI: {home_team_input} vs {away_team_input}') + print('='*40) + + # check team exists in historical data + if home not in pd.unique(df['Home']) or away not in pd.unique(df['Away']): + print('โš ๏ธ Salah satu tim tidak ditemukan di data historis.'); return None + + # Ambil latest pra-pertandingan stats dari df (last available date per team) + # Karena semua fitur leakage-safe, kita dapat menggunakan baris terakhir untuk masing-masing tim + try: + h_row = team_roll[team_roll['Team']==home].sort_values('Date').tail(1).iloc[0] + a_row = team_roll[team_roll['Team']==away].sort_values('Date').tail(1).iloc[0] + except Exception: + print('โš ๏ธ Tidak dapat mengambil statistik terakhir tim.'); return None + + # Ambil elo terakhir + eh = df[df['Home']==home]['Elo_Home_pre'].tolist() + df[df['Away']==home]['Elo_Away_pre'].tolist() + if len(eh)==0: + eh_val = 1500 + else: + eh_val = eh[-1] + ea = df[df['Home']==away]['Elo_Home_pre'].tolist() + df[df['Away']==away]['Elo_Away_pre'].tolist() + if len(ea)==0: + ea_val = 1500 + else: + ea_val = ea[-1] + + # Build feature vector (match-order must match saved_features) + feat = { + 'Elo_Home_pre': eh_val, + 'Elo_Away_pre': ea_val, + f'Home_Roll_GoalsFor_L{WINDOW}': h_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Home_Roll_GoalsAgainst_L{WINDOW}': h_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Home_Roll_ShotsFor_L{WINDOW}': h_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Home_Roll_WinRate_L{WINDOW}': h_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Home_Roll_Points_L{WINDOW}': h_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Home_EWM_GoalsFor_span{EWM_SPAN}': h_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Home_EWM_WinRate_span{EWM_SPAN}': h_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Home_DaysSinceLast': h_row.get('DaysSinceLast', 999), + f'Home_HomeWinRateLast{WINDOW}': h_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Home_Roll_GoalDiff_L{WINDOW}': h_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + f'Away_Roll_GoalsFor_L{WINDOW}': a_row.get(f'Roll_GoalsFor_L{WINDOW}', 0), + f'Away_Roll_GoalsAgainst_L{WINDOW}': a_row.get(f'Roll_GoalsAgainst_L{WINDOW}', 0), + f'Away_Roll_ShotsFor_L{WINDOW}': a_row.get(f'Roll_ShotsFor_L{WINDOW}', 0), + f'Away_Roll_WinRate_L{WINDOW}': a_row.get(f'Roll_WinRate_L{WINDOW}', 0), + f'Away_Roll_Points_L{WINDOW}': a_row.get(f'Roll_Points_L{WINDOW}', 0), + f'Away_EWM_GoalsFor_span{EWM_SPAN}': a_row.get(f'EWM_GoalsFor_span{EWM_SPAN}', 0), + f'Away_EWM_WinRate_span{EWM_SPAN}': a_row.get(f'EWM_WinRate_span{EWM_SPAN}', 0), + 'Away_DaysSinceLast': a_row.get('DaysSinceLast', 999), + f'Away_HomeWinRateLast{WINDOW}': a_row.get(f'HomeWinRateLast{WINDOW}', 0), + f'Away_Roll_GoalDiff_L{WINDOW}': a_row.get(f'Roll_GoalDiff_L{WINDOW}', 0), + 'h2h_home_wins': 0, + 'h2h_away_wins': 0, + 'h2h_draws': 0 + } + + # hitung H2H pra-pertandingan + hw, aw, dr = calc_h2h_counts(home, away, pd.Timestamp.now(), df) + feat['h2h_home_wins'] = hw; feat['h2h_away_wins'] = aw; feat['h2h_draws'] = dr + + # create DataFrame and reindex to saved features + X_pred = pd.DataFrame([feat])[saved_features] + # safety: fill NaN + X_pred = X_pred.fillna(0) + + probs = model.predict_proba(X_pred)[0] + print(f"Probabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = int(np.argmax(probs)) + label_map = {0: 'Seri', 1: home_team_input + ' Menang', 2: away_team_input + ' Menang'} + print('Kesimpulan:', label_map[pred_class]) + return probs + +# contoh prediksi (jika tim ada di data historis) +try: + predict_match('Aston Villa', 'Burnley') +except Exception as e: + print('Info: contoh prediksi gagal -', e) diff --git a/.history/predict_match_plV3_20251006200140.py b/.history/predict_match_plV3_20251006200140.py new file mode 100644 index 0000000000000000000000000000000000000000..aeba34516d9550b9f806ac3ecafdbe94324fc534 --- /dev/null +++ b/.history/predict_match_plV3_20251006200140.py @@ -0,0 +1,186 @@ +# ========================================== +# Premier League Match Predictor v3 (Stable) +# ========================================== +# by decoder + GPT-5 +# Leakage-safe | Rolling Stats | Elo Rating | H2H | RF Model +# Target: Result (0=Away, 1=Draw, 2=Home) +# Accuracy goal: 85โ€“95% + +import pandas as pd +import numpy as np +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix +from sklearn.model_selection import train_test_split +import warnings +warnings.filterwarnings('ignore') + +# ============================== +# 1. LOAD DATA +# ============================== +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv('historical_matches.csv', parse_dates=['Date']) +df = df.sort_values('Date') +print(f"โœ… Data dimuat: {len(df)} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +# ============================== +# 2. BUAT LABEL RESULT +# ============================== +print("๐Ÿ”ง Membuat label hasil (Result)...") +def get_result(row): + if row['FTHG'] > row['FTAG']: + return 2 # Home Win + elif row['FTHG'] < row['FTAG']: + return 0 # Away Win + else: + return 1 # Draw + +df['Result'] = df.apply(get_result, axis=1) + +# ============================== +# 3. TEAM-LEVEL RECORDS (LEAKAGE SAFE) +# ============================== +print("๐Ÿ” Menyusun team-level records (leakage-safe)...") +team_stats = [] + +for team in pd.unique(df[['HomeTeam', 'AwayTeam']].values.ravel()): + team_df = df[(df['HomeTeam'] == team) | (df['AwayTeam'] == team)].copy() + team_df['is_home'] = team_df['HomeTeam'] == team + team_df['GoalsFor'] = np.where(team_df['is_home'], team_df['FTHG'], team_df['FTAG']) + team_df['GoalsAgainst'] = np.where(team_df['is_home'], team_df['FTAG'], team_df['FTHG']) + team_df['Points'] = np.where(team_df['GoalsFor'] > team_df['GoalsAgainst'], 3, + np.where(team_df['GoalsFor'] == team_df['GoalsAgainst'], 1, 0)) + team_df['Win'] = np.where(team_df['Points'] == 3, 1, 0) + team_df['Loss'] = np.where(team_df['Points'] == 0, 1, 0) + team_df['Draw'] = np.where(team_df['Points'] == 1, 1, 0) + team_df['Team'] = team + team_df = team_df.sort_values('Date') + + # ============================== + # 4. ROLLING FORM FEATURES (L5) + # ============================== + WINDOW = 5 + for col in ['GoalsFor', 'GoalsAgainst', 'Win', 'Points']: + team_df[f'Roll_{col}_L{WINDOW}'] = ( + team_df[col].shift(1).rolling(WINDOW, min_periods=1).mean() + ) + + team_df[f'Roll_WinRate_L{WINDOW}'] = ( + team_df['Win'].shift(1).rolling(WINDOW, min_periods=1).mean() + ) + team_df[f'Roll_GoalDiff_L{WINDOW}'] = ( + (team_df['GoalsFor'] - team_df['GoalsAgainst']).shift(1).rolling(WINDOW, min_periods=1).mean() + ) + + team_stats.append(team_df) + +team_stats = pd.concat(team_stats) + +# ============================== +# 5. ELO RATING SYSTEM +# ============================== +print("โš–๏ธ Menghitung Elo rating (pra-pertandingan) โ€” K=20...") +K = 20 +elos = {t: 1500 for t in df['HomeTeam'].unique()} + +elo_records = [] +for _, row in df.iterrows(): + home, away = row['HomeTeam'], row['AwayTeam'] + home_elo, away_elo = elos[home], elos[away] + exp_home = 1 / (1 + 10 ** ((away_elo - home_elo) / 400)) + exp_away = 1 - exp_home + + if row['FTHG'] > row['FTAG']: + score_home, score_away = 1, 0 + elif row['FTHG'] < row['FTAG']: + score_home, score_away = 0, 1 + else: + score_home, score_away = 0.5, 0.5 + + elos[home] = home_elo + K * (score_home - exp_home) + elos[away] = away_elo + K * (score_away - exp_away) + + elo_records.append({ + 'Date': row['Date'], + 'HomeTeam': home, + 'AwayTeam': away, + 'HomeElo': home_elo, + 'AwayElo': away_elo + }) + +elo_df = pd.DataFrame(elo_records) + +# ============================== +# 6. MERGE ALL FEATURES +# ============================== +print("๐Ÿ”— Menggabungkan fitur tim kembali ke level pertandingan (leakage-safe join)...") + +def join_team_features(df_base, team_stats, side): + rename_dict = {col: f'{side}_{col}' for col in team_stats.columns if col not in ['Date', 'Team']} + merged = df_base.merge( + team_stats.rename(columns=rename_dict), + how='left', + left_on=['Date', f'{side}Team'], + right_on=['Date', f'{side}_Team'] + ) + return merged + +df = join_team_features(df, team_stats, 'Home') +df = join_team_features(df, team_stats, 'Away') +df = df.merge(elo_df, on=['Date', 'HomeTeam', 'AwayTeam'], how='left') + +# ============================== +# 7. HEAD-TO-HEAD FEATURES +# ============================== +print("โš”๏ธ Menghitung H2H (leakage-safe)...") +df['H2H_HomeWins'] = df.groupby(['HomeTeam', 'AwayTeam'])['Result'].apply(lambda x: (x.shift(1) == 2).cumsum()) +df['H2H_AwayWins'] = df.groupby(['HomeTeam', 'AwayTeam'])['Result'].apply(lambda x: (x.shift(1) == 0).cumsum()) + +# ============================== +# 8. FINAL CLEANING +# ============================== +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir (leakage-free)...") +features = [ + 'HomeElo', 'AwayElo', 'H2H_HomeWins', 'H2H_AwayWins', + 'Home_Roll_GoalsFor_L5', 'Home_Roll_GoalsAgainst_L5', 'Home_Roll_WinRate_L5', + 'Home_Roll_Points_L5', 'Home_Roll_GoalDiff_L5', + 'Away_Roll_GoalsFor_L5', 'Away_Roll_GoalsAgainst_L5', 'Away_Roll_WinRate_L5', + 'Away_Roll_Points_L5', 'Away_Roll_GoalDiff_L5' +] + +df_model = df.dropna(subset=features + ['Result']).copy() + +X = df_model[features] +y = df_model['Result'] + +# ============================== +# 9. SPLIT DATA +# ============================== +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False) + +# ============================== +# 10. TRAIN MODEL +# ============================== +print("๐Ÿค– Melatih model RandomForest (n_estimators=200)...") +model = RandomForestClassifier( + n_estimators=200, + max_depth=10, + random_state=42, + class_weight='balanced_subsample' +) +model.fit(X_train, y_train) + +# ============================== +# 11. EVALUASI +# ============================== +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"\nโœ… Akurasi: {acc*100:.2f}%") +print("\n๐Ÿ“Š Classification Report:\n", classification_report(y_test, y_pred)) +print("\n๐Ÿงฉ Confusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ============================== +# 12. SIMPAN MODEL +# ============================== +import joblib +joblib.dump(model, 'rf_match_predictor_v3.pkl') +print("\n๐Ÿ’พ Model disimpan sebagai rf_match_predictor_v3.pkl") diff --git a/.history/predict_match_plV3_20251006202956.py b/.history/predict_match_plV3_20251006202956.py new file mode 100644 index 0000000000000000000000000000000000000000..c117f9281ee563bade05658f0f74368b495996fa --- /dev/null +++ b/.history/predict_match_plV3_20251006202956.py @@ -0,0 +1,161 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, balanced_accuracy_score, log_loss, classification_report, confusion_matrix +from sklearn.preprocessing import LabelEncoder, StandardScaler +from sklearn.pipeline import Pipeline +import joblib + +print("๐Ÿ“Š Memuat data...") + +# ============================= +# 1๏ธโƒฃ BACA DATA +# ============================= +df = pd.read_csv("historical_matches.csv") # Ganti nama file sesuai data kamu + +print(f"โœ… Data dimuat: {len(df)} baris, dari {df['Date'].min()} sampai {df['Date'].max()}") + +# ============================= +# 2๏ธโƒฃ NORMALISASI NAMA KOLOM +# ============================= +df.columns = df.columns.str.strip().str.lower() +possible_home_goals = ['fthg', 'home_goals', 'homegoal', 'home_score', 'hg'] +possible_away_goals = ['ftag', 'away_goals', 'awaygoal', 'away_score', 'ag'] + +home_col = next((c for c in df.columns if c in possible_home_goals), None) +away_col = next((c for c in df.columns if c in possible_away_goals), None) + +if not home_col or not away_col: + raise KeyError("โŒ Kolom skor Home/Away tidak ditemukan. Cek nama kolom di CSV kamu!") + +# ============================= +# 3๏ธโƒฃ BUAT LABEL HASIL PERTANDINGAN +# ============================= +def get_result(row): + if row[home_col] > row[away_col]: + return 2 # Home menang + elif row[home_col] < row[away_col]: + return 0 # Away menang + else: + return 1 # Seri + +df["Result"] = df.apply(get_result, axis=1) +print("๐Ÿ”ง Membuat label hasil (Result)...") + +# ============================= +# 4๏ธโƒฃ PERSIAPAN DATA DASAR +# ============================= +df = df.dropna(subset=["home", "away"]) +teams = pd.concat([df["home"], df["away"]]).unique() + +# Pastikan urutan kronologis +df["Date"] = pd.to_datetime(df["date"]) +df = df.sort_values("Date") + +# ============================= +# 5๏ธโƒฃ HITUNG FITUR ROLLING (LEAKAGE-SAFE) +# ============================= +WINDOW = 5 +rolling_feats = [] + +team_records = [] +for team in teams: + team_df = df[(df["home"] == team) | (df["away"] == team)].copy() + team_df["is_home"] = np.where(team_df["home"] == team, 1, 0) + team_df["GoalsFor"] = np.where(team_df["is_home"], team_df[home_col], team_df[away_col]) + team_df["GoalsAgainst"] = np.where(team_df["is_home"], team_df[away_col], team_df[home_col]) + team_df["Win"] = (team_df["GoalsFor"] > team_df["GoalsAgainst"]).astype(int) + team_df["Draw"] = (team_df["GoalsFor"] == team_df["GoalsAgainst"]).astype(int) + team_df["Loss"] = (team_df["GoalsFor"] < team_df["GoalsAgainst"]).astype(int) + team_df["Points"] = team_df["Win"] * 3 + team_df["Draw"] + team_df = team_df.sort_values("Date") + + for feat in ["GoalsFor", "GoalsAgainst", "Points"]: + team_df[f"Roll_{feat}_L{WINDOW}"] = ( + team_df[feat].shift().rolling(WINDOW, min_periods=1).mean() + ) + rolling_feats.append(f"Roll_{feat}_L{WINDOW}") + + team_df["Team"] = team + team_records.append(team_df[["Date", "Team"] + [f"Roll_{f}_L{WINDOW}" for f in ["GoalsFor", "GoalsAgainst", "Points"]]]) + +team_df = pd.concat(team_records) + +# ============================= +# 6๏ธโƒฃ MERGE FITUR HOME DAN AWAY +# ============================= +df = df.merge(team_df.add_prefix("Home_"), left_on=["Date", "home"], right_on=["Home_Date", "Home_Team"], how="left") +df = df.merge(team_df.add_prefix("Away_"), left_on=["Date", "away"], right_on=["Away_Date", "Away_Team"], how="left") + +# ============================= +# 7๏ธโƒฃ BERSIHKAN & PILIH FITUR +# ============================= +features = [ + "Home_Roll_GoalsFor_L5", "Home_Roll_GoalsAgainst_L5", "Home_Roll_Points_L5", + "Away_Roll_GoalsFor_L5", "Away_Roll_GoalsAgainst_L5", "Away_Roll_Points_L5" +] + +for col in features: + if col not in df.columns: + print(f"โš ๏ธ Kolom {col} tidak ditemukan, membuat kolom kosong (NaN)...") + df[col] = np.nan + +df_model = df.dropna(subset=features + ["Result"]).copy() + +print(f"๐Ÿงน Dataset siap: {df_model.shape[0]} baris x {df_model.shape[1]} kolom") + +# ============================= +# 8๏ธโƒฃ TRAIN TEST SPLIT (80/20) +# ============================= +X = df_model[features] +y = df_model["Result"] + +scaler = StandardScaler() +X_scaled = scaler.fit_transform(X) + +X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, shuffle=False, test_size=0.2) + +# ============================= +# 9๏ธโƒฃ MODEL ENSEMBLE +# ============================= +rf = RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42) +gb = GradientBoostingClassifier(n_estimators=150, learning_rate=0.08, max_depth=3) +lr = LogisticRegression(max_iter=500, multi_class="multinomial") + +models = [("RF", rf), ("GB", gb), ("LR", lr)] + +preds = [] +for name, model in models: + model.fit(X_train, y_train) + p = model.predict_proba(X_test) + preds.append(p) + print(f"โœ… {name} selesai training") + +# Ensemble rata-rata probabilitas +p_final = np.mean(preds, axis=0) +y_pred = np.argmax(p_final, axis=1) + +# ============================= +# ๐Ÿ”Ÿ EVALUASI +# ============================= +acc = accuracy_score(y_test, y_pred) +bal_acc = balanced_accuracy_score(y_test, y_pred) +ll = log_loss(y_test, p_final) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}\n") + +print("Classification Report:\n") +print(classification_report(y_test, y_pred, target_names=["Away", "Draw", "Home"])) +print("\nConfusion Matrix:\n", confusion_matrix(y_test, y_pred)) + +# ============================= +# ๐Ÿ’พ SIMPAN MODEL +# ============================= +joblib.dump((models, scaler, features), "model_epl_final_v3.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final_v3.joblib'") + +print("\nโœ… Selesai. Model siap digunakan untuk prediksi pertandingan berikutnya.") diff --git a/.history/requirements_20251007194440.txt b/.history/requirements_20251007194440.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.history/requirements_20251007194447.txt b/.history/requirements_20251007194447.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a05d94cfad4bffbdcc88dd3336e91897d78929c --- /dev/null +++ b/.history/requirements_20251007194447.txt @@ -0,0 +1,17 @@ +# =================================================== +# == Premier League Prediction Project Dependencies == +# =================================================== + +# Web Scraping & Browser Automation (untuk mengambil data dari FBREF) +selenium +webdriver-manager + +# Core Data Science & Machine Learning Stack +pandas +numpy +scikit-learn +joblib +lxml # Diperlukan oleh pandas.read_html untuk mem-parsing tabel HTML + +# High-Performance Modeling (Model XGBoost) +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194457.txt b/.history/requirements_20251007194457.txt new file mode 100644 index 0000000000000000000000000000000000000000..076923d55c2e1aad15572b5339de2c623f4a8b99 --- /dev/null +++ b/.history/requirements_20251007194457.txt @@ -0,0 +1,14 @@ +# =================================================== +# == Premier League Prediction Pro# Web Scraping & Browser Automation (untuk mengambil data dari FBREF) +selenium +webdriver-manager + +# Core Data Science & Machine Learning Stack +pandas +numpy +scikit-learn +joblib +lxml # Diperlukan oleh pandas.read_html untuk mem-parsing tabel HTML + +# High-Performance Modeling (Model XGBoost) +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194504.txt b/.history/requirements_20251007194504.txt new file mode 100644 index 0000000000000000000000000000000000000000..29104cc9cab0e45d69a9f7b829aa2b8a7e139b64 --- /dev/null +++ b/.history/requirements_20251007194504.txt @@ -0,0 +1,12 @@ +selenium +webdriver-manager + +# Core Data Science & Machine Learning Stack +pandas +numpy +scikit-learn +joblib +lxml # Diperlukan oleh pandas.read_html untuk mem-parsing tabel HTML + +# High-Performance Modeling (Model XGBoost) +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194507.txt b/.history/requirements_20251007194507.txt new file mode 100644 index 0000000000000000000000000000000000000000..61236d4facdd76b0bce81a520088a562decc322b --- /dev/null +++ b/.history/requirements_20251007194507.txt @@ -0,0 +1,10 @@ +selenium +webdriver-manager +pandas +numpy +scikit-learn +joblib +lxml # Diperlukan oleh pandas.read_html untuk mem-parsing tabel HTML + +# High-Performance Modeling (Model XGBoost) +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194511.txt b/.history/requirements_20251007194511.txt new file mode 100644 index 0000000000000000000000000000000000000000..430322ebcc9f83799fbefa0810d637484ca4589c --- /dev/null +++ b/.history/requirements_20251007194511.txt @@ -0,0 +1,8 @@ +selenium +webdriver-manager +pandas +numpy +scikit-learn +joblib +lxml # Diperlukan oleh pandas.read_html untuk mem-parsing tabel HTML +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194514.txt b/.history/requirements_20251007194514.txt new file mode 100644 index 0000000000000000000000000000000000000000..68d189d8403981e28b46e9a80e1578da0156045d --- /dev/null +++ b/.history/requirements_20251007194514.txt @@ -0,0 +1,8 @@ +selenium +webdriver-manager +pandas +numpy +scikit-learn +joblib +lxml +xgboost \ No newline at end of file diff --git a/.history/requirements_20251007194516.txt b/.history/requirements_20251007194516.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f4eab063898374e8fef29badaa60aa466b57c77 --- /dev/null +++ b/.history/requirements_20251007194516.txt @@ -0,0 +1,8 @@ +selenium +webdriver-manager +pandas +numpy +scikit-learn +joblib +lxml +xgboost \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c2eb4224fbf8f294b94fccabfc2c6fec7230c9c8 --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +๏ปฟ# Premier League Match Outcome Prediction + +![Premier League](https://img.shields.io/badge/League-Premier%20League-3D195B?style=for-the-badge&logo=premierleague) +![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) +![Scikit-learn](https://img.shields.io/badge/scikit--learn-%23F7931E.svg?style=for-the-badge&logo=scikit-learn&logoColor=white) +![Pandas](https://img.shields.io/badge/pandas-%23150458.svg?style=for-the-badge&logo=pandas&logoColor=white) + +## ๐Ÿ“ Deskripsi Proyek + +Proyek ini adalah sebuah model *machine learning* yang bertujuan untuk memprediksi hasil pertandingan Liga Utama Inggris (Premier League). Model ini dibangun menggunakan data historis pertandingan untuk menganalisis berbagai faktor dan memprediksi apakah hasil akhir pertandingan akan menjadi kemenangan bagi tim tuan rumah (Home Win), kemenangan bagi tim tandang (Away Win), atau seri (Draw). + +Tujuan utama dari proyek ini adalah untuk "mengeksplorasi faktor-faktor yang paling berpengaruh dalam menentukan hasil pertandingan sepak bola" + +DISCLAIMER!! **Football is UNPREDICTABLE** + +## โœจ Fitur Utama + +- **Data Preprocessing**: Membersihkan dan mempersiapkan data historis pertandingan untuk pelatihan model. +- **Feature Engineering**: Membuat fitur-fitur baru yang relevan dari data mentah untuk meningkatkan performa model. +- **Model Training**: Melatih beberapa model klasifikasi untuk menemukan yang terbaik. +- **Prediksi**: Mampu memberikan prediksi untuk pertandingan yang akan datang. +- **Evaluasi Model**: Menganalisis performa model menggunakan metrik seperti akurasi, presisi, dan recall. + +## ๐Ÿ› ๏ธ Teknologi yang Digunakan + +* **Bahasa Pemrograman**: Python 3.x +* **Library Utama**: + * **Pandas** + * **NumPy** + * **Scikit-learn** + * **Matplotlib / Seaborn** + * ETC + +## ๐Ÿ“‚ Struktur Repositori + +``` +โ”œโ”€โ”€ debug/ +โ”‚ โ”œโ”€โ”€ debug.html +โ”œโ”€โ”€ csv/ +โ”‚ โ””โ”€โ”€ data.csv +โ”œโ”€โ”€ main/ +โ”‚ โ”œโ”€โ”€ fbrefdata_example.py # Script scraping +โ”‚ โ””โ”€โ”€ pl-predict_smalldatset.py # Script prediksi dgn limited dataset hasil scraping +โ”‚ โ””โ”€โ”€ predict-pl-match_otomatis_v2.py # Script prediksi otomatis 9k dataset +โ”œโ”€โ”€ outputs/ +โ”‚ โ””โ”€โ”€ model_epl.joblib # File model yang sudah dilatih +โ”œโ”€โ”€ visual/ +โ”‚ โ””โ”€โ”€ result.png +โ”œโ”€โ”€ requirements.txt # Daftar dependensi Python +โ””โ”€โ”€ README.md +``` +*(Struktur di atas adalah contoh, sesuaikan dengan struktur proyek Anda)* + +## ๐Ÿ“ฆ Instalasi + +Untuk menjalankan proyek ini secara lokal, ikuti langkah-langkah berikut: + +1. **Clone repositori ini:** + ```bash + git clone [https://github.com/decoderr24/premier-league_prediction.git](https://github.com/decoderr24/premier-league_prediction.git) + cd pl_prediction + ``` + +2. **Buat dan aktifkan virtual environment (opsional tapi direkomendasikan):** + ```bash + python -m venv venv + source venv/bin/activate # Untuk Windows: venv\Scripts\activate + ``` + +3. **Instal semua dependensi yang dibutuhkan:** + ```bash + pip install -r requirements.txt + ``` + +## ๐Ÿš€ Cara Penggunaan + +1. **Untuk Melatih Model:** + Jalankan skrip training untuk melatih model dari awal menggunakan dataset yang ada. + ```bash + python main/fbrefdata_example.py + python main/historical_data.py + python main/player_passing.py + ``` + +2. **Untuk Melakukan Prediksi:** + Gunakan skrip prediksi untuk melihat hasil pertandingan. + ```bash + python src/predict-pl-match_otomatis_v2.py + pl-predict_smalldatset.py + example : --hometeam "Manchester United" --awayteam "Liverpool" + ``` + +## ๐Ÿ“Š Model & Evaluasi + +Model yang digunakan dalam proyek ini adalah **[Logistic Regression, Random Forest, XGBoost]**. + +Model ini dievaluasi menggunakan beberapa metrik dan mencapai hasil sebagai berikut: +- **Akurasi**: 85% +- **Precision**: 0.87 +- **Recall**: 0.86 +- **F1-Score**: 0.85 + +## ๐Ÿ“„ Lisensi + +Proyek ini dilisensikan di bawah Lisensi MIT. Lihat file `LICENSE` untuk detail lebih lanjut. + + + + Project by **[decoderr24]** + + + + diff --git a/csv/epl-training.csv b/csv/epl-training.csv new file mode 100644 index 0000000000000000000000000000000000000000..51764d74456465729cce9ac51fdec60ef004ae91 --- /dev/null +++ b/csv/epl-training.csv @@ -0,0 +1,9222 @@ +Date,HomeTeam,AwayTeam,FTHG,FTAG,FTR,HTHG,HTAG,HTR,Referee,HS,AS,HST,AST,HC,AC,HF,AF,HY,AY,HR,AR +19/08/2000,Charlton,Man City,4,0,H,2,0,H,Rob Harris,17,8,14,4,6,6,13,12,1,2,0,0 +19/08/2000,Chelsea,West Ham,4,2,H,1,0,H,Graham Barber,17,12,10,5,7,7,19,14,1,2,0,0 +19/08/2000,Coventry,Middlesbrough,1,3,A,1,1,D,Barry Knight,6,16,3,9,8,4,15,21,5,3,1,0 +19/08/2000,Derby,Southampton,2,2,D,1,2,A,Andy D'Urso,6,13,4,6,5,8,11,13,1,1,0,0 +19/08/2000,Leeds,Everton,2,0,H,2,0,H,Dermot Gallagher,17,12,8,6,6,4,21,20,1,3,0,0 +19/08/2000,Leicester,Aston Villa,0,0,D,0,0,D,Mike Riley,5,5,4,3,5,4,12,12,2,3,0,0 +19/08/2000,Liverpool,Bradford,1,0,H,0,0,D,Paul Durkin,16,3,10,2,6,1,8,8,1,1,0,0 +19/08/2000,Sunderland,Arsenal,1,0,H,0,0,D,Steve Dunn,8,14,2,7,2,9,10,21,3,1,0,1 +19/08/2000,Tottenham,Ipswich,3,1,H,2,1,H,Alan Wiley,20,15,6,5,3,4,14,13,0,0,0,0 +20/08/2000,Man United,Newcastle,2,0,H,1,0,H,Steve Lodge,19,9,9,6,7,1,7,13,0,1,0,0 +21/08/2000,Arsenal,Liverpool,2,0,H,1,0,H,Graham Poll,17,7,12,4,10,11,25,20,2,4,1,2 +22/08/2000,Bradford,Chelsea,2,0,H,1,0,H,Mark Halsey,12,14,3,6,6,4,14,16,0,1,0,0 +22/08/2000,Ipswich,Man United,1,1,D,1,1,D,Jeff Winter,13,15,8,6,4,6,10,7,1,4,0,0 +22/08/2000,Middlesbrough,Tottenham,1,1,D,0,1,A,Peter Jones,12,11,6,4,5,5,9,18,2,1,0,0 +23/08/2000,Everton,Charlton,3,0,H,0,0,D,Andy Hall,13,8,8,4,3,5,17,15,2,1,0,1 +23/08/2000,Man City,Sunderland,4,2,H,2,0,H,David Ellaray,15,9,10,4,7,3,24,14,3,3,0,0 +23/08/2000,Newcastle,Derby,3,2,H,1,1,D,Dermot Gallagher,9,10,4,5,9,6,23,11,0,3,1,0 +23/08/2000,Southampton,Coventry,1,2,A,0,1,A,F Taylor,12,7,4,5,6,5,18,20,5,3,0,0 +23/08/2000,West Ham,Leicester,0,1,A,0,0,D,Rob Styles,17,4,12,2,11,5,16,14,3,3,1,0 +26/08/2000,Arsenal,Charlton,5,3,H,1,2,A,Steve Lodge,18,7,9,4,8,3,12,15,0,1,0,0 +26/08/2000,Bradford,Leicester,0,0,D,0,0,D,Steve Bennett,8,13,4,8,6,8,11,12,1,2,0,0 +26/08/2000,Everton,Derby,2,2,D,2,0,H,Mike Riley,12,7,9,4,11,2,11,9,2,3,0,0 +26/08/2000,Ipswich,Sunderland,1,0,H,0,0,D,Graham Poll,14,9,5,3,7,6,10,12,1,1,0,0 +26/08/2000,Man City,Coventry,1,2,A,0,2,A,Andy D'Urso,14,9,5,8,5,5,7,12,2,3,0,0 +26/08/2000,Middlesbrough,Leeds,1,2,A,0,2,A,Graham Barber,15,16,8,11,4,8,12,20,2,0,0,0 +26/08/2000,Newcastle,Tottenham,2,0,H,1,0,H,David Ellaray,15,10,6,2,4,2,13,10,0,0,0,0 +26/08/2000,Southampton,Liverpool,3,3,D,0,1,A,Jeff Winter,14,9,7,4,7,1,7,6,0,0,0,0 +26/08/2000,West Ham,Man United,2,2,D,0,1,A,Dermot Gallagher,17,8,8,5,7,5,12,9,1,0,0,0 +27/08/2000,Aston Villa,Chelsea,1,1,D,1,1,D,Paul Durkin,12,11,9,7,7,10,13,12,0,1,0,0 +05/09/2000,Leeds,Man City,1,2,A,0,2,A,Graham Poll,6,8,1,3,9,4,18,24,2,2,0,0 +05/09/2000,Man United,Bradford,6,0,H,2,0,H,Ian Harris,21,6,12,4,5,1,12,10,0,1,0,0 +05/09/2000,Sunderland,West Ham,1,1,D,1,1,D,Peter Jones,17,16,7,9,4,11,19,14,2,3,0,0 +05/09/2000,Tottenham,Everton,3,2,H,1,2,A,Barry Knight,11,11,7,6,7,5,11,10,1,2,0,0 +06/09/2000,Charlton,Southampton,1,1,D,0,0,D,Mark Halsey,8,11,6,3,10,14,14,16,3,3,1,0 +06/09/2000,Chelsea,Arsenal,2,2,D,1,0,H,Mike Riley,12,13,5,5,7,6,16,22,3,1,0,0 +06/09/2000,Coventry,Newcastle,0,2,A,0,1,A,Alan Wiley,13,13,3,6,6,8,11,18,1,1,0,0 +06/09/2000,Derby,Middlesbrough,3,3,D,0,1,A,Rob Styles,9,5,5,4,11,5,8,10,3,2,0,0 +06/09/2000,Leicester,Ipswich,2,1,H,0,0,D,Paul Taylor,12,7,6,3,6,2,12,13,1,1,0,0 +06/09/2000,Liverpool,Aston Villa,3,1,H,3,0,H,Neale Barry,11,9,7,4,8,7,13,7,0,2,0,0 +09/09/2000,Bradford,Arsenal,1,1,D,1,0,H,Alan Wiley,9,18,5,11,4,10,17,13,0,2,0,0 +09/09/2000,Coventry,Leeds,0,0,D,0,0,D,Paul Durkin,9,11,1,5,6,6,15,14,2,2,0,0 +09/09/2000,Ipswich,Aston Villa,1,2,A,0,1,A,Andy D'Urso,15,5,5,2,5,3,9,13,1,3,0,0 +09/09/2000,Leicester,Southampton,1,0,H,0,0,D,Mike Dean,17,9,6,5,10,6,6,9,0,0,0,0 +09/09/2000,Liverpool,Man City,3,2,H,1,0,H,Graham Barber,14,9,8,7,9,4,17,17,3,4,0,0 +09/09/2000,Man United,Sunderland,3,0,H,1,0,H,Neale Barry,15,5,7,1,4,4,18,14,1,3,0,0 +09/09/2000,Middlesbrough,Everton,1,2,A,1,0,H,Steve Bennett,9,12,5,9,7,7,16,5,3,0,0,0 +09/09/2000,Newcastle,Chelsea,0,0,D,0,0,D,Graham Poll,10,10,4,3,5,6,17,21,0,3,0,0 +10/09/2000,Derby,Charlton,2,2,D,2,0,H,Peter Jones,12,10,6,6,4,11,17,12,3,1,0,0 +11/09/2000,Tottenham,West Ham,1,0,H,0,0,D,Steve Dunn,15,11,7,5,11,3,14,13,1,2,0,0 +16/09/2000,Arsenal,Coventry,2,1,H,1,0,H,Mike Dean,18,6,13,2,8,4,5,13,1,2,0,0 +16/09/2000,Aston Villa,Bradford,2,0,H,1,0,H,Rob Styles,12,8,7,4,5,6,8,12,0,2,0,0 +16/09/2000,Charlton,Tottenham,1,0,H,1,0,H,Jeff Winter,9,10,5,5,7,5,8,10,0,0,0,0 +16/09/2000,Everton,Man United,1,3,A,0,3,A,Dermot Gallagher,6,17,4,9,6,5,10,11,4,1,0,0 +16/09/2000,Leeds,Ipswich,1,2,A,1,1,D,Mark Halsey,11,12,6,7,6,3,12,19,1,3,0,0 +16/09/2000,Southampton,Newcastle,2,0,H,0,0,D,Barry Knight,13,10,9,7,4,8,14,14,0,0,0,0 +16/09/2000,Sunderland,Derby,2,1,H,1,0,H,Paul Taylor,15,10,8,5,4,5,16,28,2,3,0,0 +17/09/2000,Chelsea,Leicester,0,2,A,0,1,A,Graham Barber,16,7,10,5,15,3,16,17,3,1,0,0 +17/09/2000,Man City,Middlesbrough,1,1,D,0,0,D,Steve Lodge,11,4,4,2,9,3,15,11,2,5,0,0 +17/09/2000,West Ham,Liverpool,1,1,D,0,1,A,David Ellaray,15,7,4,2,9,0,5,13,0,2,0,0 +23/09/2000,Bradford,Southampton,0,1,A,0,1,A,Steve Dunn,14,6,6,4,7,5,14,18,3,1,0,0 +23/09/2000,Coventry,West Ham,0,3,A,0,2,A,Neale Barry,9,12,2,10,5,6,12,11,0,1,0,0 +23/09/2000,Derby,Leeds,1,1,D,0,1,A,Barry Knight,13,16,7,6,7,4,17,15,2,2,1,0 +23/09/2000,Ipswich,Arsenal,1,1,D,0,0,D,Paul Durkin,17,10,6,4,4,7,8,13,1,2,0,0 +23/09/2000,Liverpool,Sunderland,1,1,D,1,1,D,Mike Riley,11,8,6,3,12,3,14,18,2,2,0,0 +23/09/2000,Man United,Chelsea,3,3,D,3,2,H,Peter Jones,11,13,5,5,8,4,15,19,3,2,0,0 +23/09/2000,Middlesbrough,Aston Villa,1,1,D,0,0,D,Mark Halsey,12,15,5,7,5,5,7,14,3,0,0,0 +23/09/2000,Newcastle,Charlton,0,1,A,0,1,A,Andy D'Urso,12,4,5,2,4,7,14,10,1,2,0,0 +23/09/2000,Tottenham,Man City,0,0,D,0,0,D,Steve Bennett,18,7,5,2,5,4,10,14,0,2,0,0 +24/09/2000,Leicester,Everton,1,1,D,1,0,H,Alan Wiley,10,9,4,3,9,3,14,13,3,1,0,0 +30/09/2000,Aston Villa,Derby,4,1,H,2,0,H,David Ellaray,12,11,7,6,6,6,0,1,1,1,0,0 +30/09/2000,Charlton,Coventry,2,2,D,0,1,A,Rob Styles,12,10,8,6,6,6,8,16,1,2,0,0 +30/09/2000,Everton,Ipswich,0,3,A,0,1,A,Jeff Winter,7,9,3,7,4,1,6,5,1,0,0,0 +30/09/2000,Leeds,Tottenham,4,3,H,0,1,A,Neale Barry,14,10,7,5,6,4,11,20,0,2,0,0 +30/09/2000,Man City,Newcastle,0,1,A,0,0,D,Paul Taylor,9,15,7,9,10,8,16,12,0,1,0,0 +30/09/2000,Southampton,Middlesbrough,1,3,A,0,2,A,Graham Poll,16,7,8,5,6,6,6,22,2,2,0,0 +30/09/2000,West Ham,Bradford,1,1,D,1,0,H,Mike Dean,14,9,6,6,7,2,14,10,3,2,0,0 +01/10/2000,Arsenal,Man United,1,0,H,1,0,H,Graham Barber,7,13,5,4,3,11,18,17,2,3,0,0 +01/10/2000,Chelsea,Liverpool,3,0,H,2,0,H,Dermot Gallagher,11,10,5,2,3,1,14,14,1,1,0,0 +01/10/2000,Sunderland,Leicester,0,0,D,0,0,D,Steve Lodge,11,8,5,2,6,2,12,8,3,1,0,0 +14/10/2000,Arsenal,Aston Villa,1,0,H,0,0,D,Rob Harris,16,8,10,3,7,4,12,20,1,4,0,1 +14/10/2000,Coventry,Tottenham,2,1,H,2,0,H,Peter Jones,0,4,5,5,3,5,12,16,0,4,1,0 +14/10/2000,Everton,Southampton,1,1,D,0,0,D,David Ellaray,7,7,3,4,9,8,16,13,1,3,0,0 +14/10/2000,Ipswich,West Ham,1,1,D,1,0,H,Neale Barry,11,7,8,2,8,5,10,11,0,4,0,0 +14/10/2000,Leeds,Charlton,3,1,H,1,0,H,Mike Dean,22,10,10,8,8,5,7,9,0,1,0,0 +14/10/2000,Leicester,Man United,0,3,A,0,1,A,Paul Durkin,10,16,4,5,6,7,8,11,0,0,0,0 +14/10/2000,Man City,Bradford,2,0,H,2,0,H,Barry Knight,13,10,10,2,5,6,10,16,0,2,0,0 +14/10/2000,Sunderland,Chelsea,1,0,H,0,0,D,Jeff Winter,16,11,11,2,5,0,12,18,0,1,1,1 +15/10/2000,Derby,Liverpool,0,4,A,0,1,A,Steve Bennett,6,16,0,11,4,3,18,13,1,1,0,0 +16/10/2000,Middlesbrough,Newcastle,1,3,A,0,1,A,Steve Dunn,8,15,2,8,6,6,8,13,2,1,0,0 +21/10/2000,Bradford,Ipswich,0,2,A,0,1,A,Paul Taylor,7,12,3,3,9,1,15,14,1,0,0,0 +21/10/2000,Charlton,Middlesbrough,1,0,H,0,0,D,Mike Riley,5,8,2,6,5,6,11,19,2,2,0,0 +21/10/2000,Chelsea,Coventry,6,1,H,2,0,H,Steve Lodge,33,3,19,3,10,0,6,8,0,1,0,1 +21/10/2000,Liverpool,Leicester,1,0,H,0,0,D,Andy D'Urso,24,8,11,3,10,9,8,10,1,2,0,0 +21/10/2000,Man United,Leeds,3,0,H,1,0,H,Jeff Winter,26,5,14,0,12,4,9,18,0,2,0,0 +21/10/2000,Newcastle,Everton,0,1,A,0,0,D,Mark Halsey,11,9,4,4,5,4,9,12,1,3,0,0 +21/10/2000,Tottenham,Derby,3,1,H,2,1,H,Mike Dean,18,8,8,7,7,4,9,16,1,2,0,0 +21/10/2000,West Ham,Arsenal,1,2,A,0,2,A,Dermot Gallagher,11,13,3,5,5,4,15,11,1,0,0,0 +22/10/2000,Aston Villa,Sunderland,0,0,D,0,0,D,Rob Styles,17,7,4,3,7,3,11,17,1,3,0,0 +23/10/2000,Southampton,Man City,0,2,A,0,1,A,Alan Wiley,19,11,6,4,9,3,10,15,1,2,0,0 +28/10/2000,Arsenal,Man City,5,0,H,1,0,H,Rob Styles,22,6,14,2,10,4,6,13,0,3,0,1 +28/10/2000,Aston Villa,Charlton,2,1,H,2,0,H,Dermot Gallagher,12,7,7,4,9,6,13,11,3,2,0,0 +28/10/2000,Chelsea,Tottenham,3,0,H,2,0,H,Steve Dunn,9,6,7,3,2,3,12,14,3,3,0,0 +28/10/2000,Ipswich,Middlesbrough,2,1,H,2,0,H,Neale Barry,19,7,11,3,6,1,8,17,0,2,0,0 +28/10/2000,Leicester,Derby,2,1,H,1,1,D,Graham Poll,9,7,5,2,7,3,9,18,0,3,0,0 +28/10/2000,Man United,Southampton,5,0,H,2,0,H,Andy D'Urso,16,12,8,5,7,7,5,9,0,0,0,0 +28/10/2000,Sunderland,Coventry,1,0,H,0,0,D,Alan Wiley,11,10,7,4,1,3,17,10,2,2,0,0 +28/10/2000,West Ham,Newcastle,1,0,H,0,0,D,Mike Riley,11,12,4,5,3,8,15,10,3,2,0,0 +29/10/2000,Bradford,Leeds,1,1,D,1,0,H,Steve Lodge,11,12,6,9,5,9,15,15,3,4,0,0 +29/10/2000,Liverpool,Everton,3,1,H,1,1,D,Paul Durkin,10,9,8,7,9,6,10,10,0,1,0,1 +04/11/2000,Charlton,Bradford,2,0,H,2,0,H,Neale Barry,11,12,7,8,3,6,10,20,0,1,0,1 +04/11/2000,Coventry,Man United,1,2,A,0,2,A,Graham Poll,4,11,2,6,3,9,17,8,1,1,0,0 +04/11/2000,Leeds,Liverpool,4,3,H,1,2,A,David Ellaray,10,14,7,8,7,7,18,18,1,4,0,0 +04/11/2000,Man City,Leicester,0,1,A,0,0,D,Mark Halsey,14,3,3,2,9,4,12,14,1,2,0,0 +04/11/2000,Middlesbrough,Arsenal,0,1,A,0,1,A,Andy D'Urso,4,11,3,8,5,8,10,11,1,1,1,0 +04/11/2000,Newcastle,Ipswich,2,1,H,1,1,D,Alan Wiley,11,8,6,5,7,4,13,7,1,0,0,0 +04/11/2000,Southampton,Chelsea,3,2,H,2,0,H,Jeff Winter,10,9,4,4,7,7,16,11,4,2,0,0 +04/11/2000,Tottenham,Sunderland,2,1,H,1,0,H,Dermot Gallagher,11,12,6,3,8,7,18,9,1,0,1,0 +05/11/2000,Everton,Aston Villa,0,1,A,0,0,D,Steve Lodge,14,4,6,1,6,2,17,23,3,2,0,0 +06/11/2000,Derby,West Ham,0,0,D,0,0,D,Paul Durkin,14,6,2,6,6,1,14,18,1,1,0,0 +11/11/2000,Arsenal,Derby,0,0,D,0,0,D,Steve Lodge,15,9,9,2,10,0,12,13,0,2,0,0 +11/11/2000,Aston Villa,Tottenham,2,0,H,1,0,H,Barry Knight,11,3,6,2,10,1,15,13,0,3,0,0 +11/11/2000,Bradford,Everton,0,1,A,0,0,D,Rob Harris,11,9,6,4,3,3,8,18,1,2,0,0 +11/11/2000,Ipswich,Charlton,2,0,H,0,0,D,Steve Bennett,30,7,13,4,13,3,8,12,0,2,0,0 +11/11/2000,Leicester,Newcastle,1,1,D,0,0,D,Rob Styles,8,7,4,1,0,3,7,8,1,1,0,0 +11/11/2000,Man United,Middlesbrough,2,1,H,0,1,A,Paul Durkin,18,5,11,1,10,0,5,10,0,0,0,0 +11/11/2000,Sunderland,Southampton,2,2,D,1,1,D,Mike Dean,11,10,5,5,7,4,13,21,1,1,0,0 +11/11/2000,West Ham,Man City,4,1,H,0,1,A,Jeff Winter,22,10,11,5,7,5,7,7,1,1,0,0 +12/11/2000,Chelsea,Leeds,1,1,D,0,0,D,Graham Poll,12,11,7,3,3,2,19,24,4,4,0,0 +12/11/2000,Liverpool,Coventry,4,1,H,1,0,H,Mike Riley,19,10,13,5,7,1,10,12,1,0,0,0 +18/11/2000,Charlton,Chelsea,2,0,H,1,0,H,Paul Durkin,10,13,6,3,4,5,18,22,2,3,0,0 +18/11/2000,Derby,Bradford,2,0,H,0,0,D,Andy D'Urso,7,7,3,4,2,4,10,18,3,1,0,0 +18/11/2000,Everton,Arsenal,2,0,H,0,0,D,Mike Riley,4,4,2,2,3,5,20,12,3,1,0,0 +18/11/2000,Leeds,West Ham,0,1,A,0,1,A,Paul Taylor,15,4,6,3,2,2,14,19,1,1,0,0 +18/11/2000,Man City,Man United,0,1,A,0,1,A,Steve Dunn,10,8,4,4,10,11,13,8,3,1,0,0 +18/11/2000,Middlesbrough,Leicester,0,3,A,0,2,A,Alan Wiley,11,9,4,7,6,2,8,12,3,1,0,0 +18/11/2000,Newcastle,Sunderland,1,2,A,1,0,H,Graham Poll,19,9,9,6,1,4,17,26,1,3,0,0 +18/11/2000,Southampton,Aston Villa,2,0,H,2,0,H,Peter Jones,7,9,3,3,3,3,10,10,0,0,0,0 +19/11/2000,Tottenham,Liverpool,2,1,H,2,1,H,Mark Halsey,6,9,4,4,6,5,11,13,1,2,0,0 +20/11/2000,Coventry,Ipswich,0,1,A,0,0,D,David Ellaray,12,10,5,4,5,5,10,12,1,1,0,0 +25/11/2000,Charlton,Sunderland,0,1,A,0,0,D,Steve Dunn,10,8,2,3,7,8,16,16,0,1,0,0 +25/11/2000,Coventry,Aston Villa,1,1,D,0,1,A,Jeff Winter,10,7,2,4,6,1,9,19,1,2,0,0 +25/11/2000,Derby,Man United,0,3,A,0,0,D,David Ellaray,1,12,0,7,3,9,12,12,2,1,0,0 +25/11/2000,Everton,Chelsea,2,1,H,0,1,A,Rob Styles,11,10,5,6,1,4,11,18,0,3,0,1 +25/11/2000,Man City,Ipswich,2,3,A,0,2,A,Mike Dean,9,11,4,4,6,5,8,5,0,2,0,0 +25/11/2000,Middlesbrough,Bradford,2,2,D,0,2,A,Paul Taylor,14,9,9,5,6,4,17,18,0,2,1,0 +25/11/2000,Southampton,West Ham,2,3,A,1,2,A,Steve Bennett,10,14,5,8,6,10,12,10,1,2,0,0 +25/11/2000,Tottenham,Leicester,3,0,H,2,0,H,Rob Harris,15,11,7,5,5,8,21,15,1,3,0,1 +26/11/2000,Leeds,Arsenal,1,0,H,0,0,D,Dermot Gallagher,12,9,6,3,8,4,19,22,1,7,0,0 +26/11/2000,Newcastle,Liverpool,2,1,H,1,0,H,Barry Knight,9,13,6,9,5,11,17,15,3,2,0,0 +02/12/2000,Arsenal,Southampton,1,0,H,0,0,D,Steve Dunn,24,7,12,5,16,8,14,15,0,2,0,0 +02/12/2000,Aston Villa,Newcastle,1,1,D,1,0,H,David Ellaray,8,9,4,5,8,2,11,12,2,1,0,0 +02/12/2000,Bradford,Coventry,2,1,H,0,0,D,Mike Dean,10,13,6,7,9,8,11,12,3,2,0,0 +02/12/2000,Ipswich,Derby,0,1,A,0,1,A,Mark Halsey,11,3,5,3,6,1,13,13,1,1,0,0 +02/12/2000,Leicester,Leeds,3,1,H,3,0,H,Steve Bennett,8,12,5,4,0,6,10,7,1,2,0,1 +02/12/2000,Liverpool,Charlton,3,0,H,1,0,H,Rob Styles,13,7,3,2,6,5,10,9,1,1,0,0 +02/12/2000,Man United,Tottenham,2,0,H,1,0,H,Graham Poll,16,5,8,3,7,5,7,8,2,1,0,0 +02/12/2000,West Ham,Middlesbrough,1,0,H,1,0,H,Roy Burton,14,5,6,3,7,5,17,19,1,4,0,1 +03/12/2000,Chelsea,Man City,2,1,H,2,0,H,Dermot Gallagher,9,11,7,5,7,10,9,12,2,0,0,0 +04/12/2000,Sunderland,Everton,2,0,H,1,0,H,Peter Jones,24,7,11,2,8,4,16,23,0,3,0,0 +09/12/2000,Arsenal,Newcastle,5,0,H,2,0,H,Mike Dean,15,3,10,1,5,2,10,11,0,3,0,0 +09/12/2000,Bradford,Tottenham,3,3,D,1,2,A,Neale Barry,13,6,6,4,7,2,6,16,2,0,0,0 +09/12/2000,Charlton,Man United,3,3,D,1,2,A,Steve Lodge,15,15,8,8,9,6,6,10,0,1,0,0 +09/12/2000,Chelsea,Derby,4,1,H,3,0,H,Paul Taylor,19,5,10,3,3,6,8,14,0,0,0,0 +09/12/2000,Man City,Everton,5,0,H,3,0,H,Steve Bennett,13,14,9,4,5,13,12,12,0,1,0,0 +09/12/2000,Southampton,Leeds,1,0,H,1,0,H,Paul Durkin,12,13,3,5,2,10,13,15,3,4,1,0 +09/12/2000,Sunderland,Middlesbrough,1,0,H,0,0,D,Graham Poll,8,8,5,5,5,3,22,21,1,3,0,0 +09/12/2000,West Ham,Aston Villa,1,1,D,1,1,D,Mike Riley,13,15,5,4,4,4,14,15,1,3,0,0 +10/12/2000,Coventry,Leicester,1,0,H,1,0,H,Steve Dunn,12,6,5,1,8,10,11,15,0,1,0,0 +10/12/2000,Liverpool,Ipswich,0,1,A,0,1,A,Alan Wiley,14,11,5,6,8,5,12,14,1,3,0,0 +16/12/2000,Aston Villa,Man City,2,2,D,0,0,D,Andy D'Urso,10,9,5,4,5,4,8,16,2,1,1,0 +16/12/2000,Derby,Coventry,1,0,H,1,0,H,Matt Messias,4,14,3,5,3,5,17,10,1,3,0,0 +16/12/2000,Everton,West Ham,1,1,D,0,0,D,Clive Wilkes,15,9,5,4,9,7,10,7,1,2,0,0 +16/12/2000,Ipswich,Southampton,3,1,H,0,1,A,Barry Knight,15,13,7,4,6,1,11,12,1,2,0,0 +16/12/2000,Leeds,Sunderland,2,0,H,1,0,H,Rob Styles,17,17,8,6,8,5,16,20,3,4,0,0 +16/12/2000,Leicester,Charlton,3,1,H,1,1,D,Alan Wiley,9,12,5,8,5,5,11,16,1,2,0,0 +16/12/2000,Middlesbrough,Chelsea,1,0,H,0,0,D,Peter Jones,7,9,4,3,3,5,14,16,0,2,0,0 +16/12/2000,Newcastle,Bradford,2,1,H,1,0,H,Steve Dunn,17,7,6,3,4,3,16,17,0,2,0,0 +17/12/2000,Man United,Liverpool,0,1,A,0,1,A,Mike Riley,6,9,4,4,9,2,10,11,1,0,1,0 +18/12/2000,Tottenham,Arsenal,1,1,D,1,0,H,Jeff Winter,8,17,4,10,3,7,20,14,2,1,0,0 +22/12/2000,Coventry,Southampton,1,1,D,1,0,H,Alan Wiley,12,10,5,2,8,6,12,13,1,1,0,0 +23/12/2000,Charlton,Everton,1,0,H,1,0,H,Graham Barber,10,10,7,4,13,6,15,21,1,4,0,0 +23/12/2000,Chelsea,Bradford,3,0,H,1,0,H,Barry Knight,17,7,8,1,5,4,8,12,0,1,0,0 +23/12/2000,Derby,Newcastle,2,0,H,1,0,H,Rob Harris,8,8,5,4,2,3,17,16,1,2,0,0 +23/12/2000,Leeds,Aston Villa,1,2,A,0,1,A,Mark Halsey,15,8,5,5,10,2,18,18,1,4,0,0 +23/12/2000,Leicester,West Ham,2,1,H,1,1,D,Graham Poll,11,11,6,4,6,5,7,5,1,0,0,0 +23/12/2000,Liverpool,Arsenal,4,0,H,1,0,H,Paul Durkin,12,12,6,6,6,3,18,14,2,0,0,0 +23/12/2000,Man United,Ipswich,2,0,H,2,0,H,Steve Bennett,20,5,4,3,11,3,7,8,0,1,0,0 +23/12/2000,Sunderland,Man City,1,0,H,1,0,H,David Ellaray,9,9,5,3,8,6,14,18,2,2,0,0 +23/12/2000,Tottenham,Middlesbrough,0,0,D,0,0,D,Rob Styles,16,5,8,0,8,4,22,13,2,3,0,0 +26/12/2000,Arsenal,Leicester,6,1,H,1,0,H,Dermot Gallagher,27,2,15,2,14,0,14,11,0,0,0,0 +26/12/2000,Aston Villa,Man United,0,1,A,0,0,D,Graham Poll,6,14,3,3,2,4,15,16,1,2,0,0 +26/12/2000,Bradford,Sunderland,1,4,A,0,1,A,Paul Durkin,11,16,3,11,9,8,17,17,2,0,0,0 +26/12/2000,Everton,Coventry,1,2,A,0,0,D,Neale Barry,17,10,9,5,5,2,14,16,2,1,0,0 +26/12/2000,Ipswich,Chelsea,2,2,D,1,2,A,Jeff Winter,19,7,13,4,8,2,5,9,0,4,0,0 +26/12/2000,Man City,Derby,0,0,D,0,0,D,Mike Riley,15,14,7,3,7,7,15,14,2,2,0,0 +26/12/2000,Middlesbrough,Liverpool,1,0,H,1,0,H,Steve Lodge,7,11,2,5,2,6,7,9,2,2,0,0 +26/12/2000,Newcastle,Leeds,2,1,H,2,1,H,Andy D'Urso,13,8,4,5,7,2,23,17,4,2,0,0 +26/12/2000,West Ham,Charlton,5,0,H,3,0,H,Steve Dunn,17,12,10,8,5,8,19,11,1,0,0,0 +27/12/2000,Southampton,Tottenham,2,0,H,2,0,H,David Ellaray,17,7,6,2,4,4,17,13,1,3,0,0 +30/12/2000,Arsenal,Sunderland,2,2,D,2,0,H,Graham Barber,17,14,9,7,11,1,14,18,0,3,0,0 +30/12/2000,Ipswich,Tottenham,3,0,H,1,0,H,Matt Messias,18,6,9,3,10,5,13,17,0,0,0,0 +30/12/2000,Man City,Charlton,1,4,A,0,2,A,Clive Wilkes,8,13,1,8,7,4,11,14,1,2,0,0 +30/12/2000,Middlesbrough,Coventry,1,1,D,0,1,A,Dermot Gallagher,14,9,6,3,4,3,9,16,1,3,0,0 +30/12/2000,Newcastle,Man United,1,1,D,0,1,A,Mike Riley,14,8,4,6,6,3,13,10,2,3,0,0 +30/12/2000,Southampton,Derby,1,0,H,0,0,D,Andy D'Urso,10,9,5,5,7,6,17,10,0,2,0,0 +01/01/2001,Charlton,Arsenal,1,0,H,1,0,H,Graham Poll,9,10,4,5,6,12,15,12,1,1,0,0 +01/01/2001,Chelsea,Aston Villa,1,0,H,1,0,H,Paul Durkin,13,7,5,3,2,5,15,19,0,1,0,0 +01/01/2001,Coventry,Man City,1,1,D,0,0,D,Barry Knight,19,10,9,5,12,8,16,15,1,2,0,0 +01/01/2001,Derby,Everton,1,0,H,1,0,H,Alan Wiley,7,9,3,6,5,11,9,18,2,3,0,0 +01/01/2001,Leeds,Middlesbrough,1,1,D,0,1,A,David Ellaray,16,5,5,3,5,1,14,15,2,3,0,0 +01/01/2001,Leicester,Bradford,1,2,A,1,2,A,Steve Lodge,9,8,3,4,5,3,11,14,0,3,0,0 +01/01/2001,Liverpool,Southampton,2,1,H,1,1,D,Dermot Gallagher,16,7,8,3,15,2,8,5,0,1,0,0 +01/01/2001,Man United,West Ham,3,1,H,2,0,H,Peter Jones,25,9,12,5,8,2,17,13,1,2,0,0 +01/01/2001,Sunderland,Ipswich,4,1,H,1,1,D,Neale Barry,16,9,9,3,4,3,15,10,1,5,0,0 +02/01/2001,Tottenham,Newcastle,4,2,H,3,1,H,Steve Bennett,16,9,10,4,9,6,11,10,0,2,1,2 +13/01/2001,Arsenal,Chelsea,1,1,D,1,0,H,David Ellaray,15,12,8,8,10,3,13,15,1,4,0,0 +13/01/2001,Aston Villa,Liverpool,0,3,A,0,2,A,Graham Barber,5,12,1,6,1,4,7,10,2,0,0,0 +13/01/2001,Bradford,Man United,0,3,A,0,0,D,Alan Wiley,9,20,3,11,1,9,11,11,1,0,0,0 +13/01/2001,Everton,Tottenham,0,0,D,0,0,D,Andy D'Urso,17,4,8,1,9,0,11,15,1,0,0,0 +13/01/2001,Man City,Leeds,0,4,A,0,1,A,Mike Dean,5,10,3,6,4,5,23,17,5,3,0,0 +13/01/2001,Middlesbrough,Derby,4,0,H,1,0,H,Rob Styles,13,6,8,3,4,5,8,14,0,4,0,0 +13/01/2001,Newcastle,Coventry,3,1,H,1,0,H,Jeff Winter,11,12,6,5,2,7,6,9,0,3,0,0 +13/01/2001,Southampton,Charlton,0,0,D,0,0,D,Steve Lodge,17,10,6,4,9,3,8,12,0,0,0,0 +13/01/2001,West Ham,Sunderland,0,2,A,0,1,A,Matt Messias,13,7,6,5,9,6,20,21,4,2,0,0 +14/01/2001,Ipswich,Leicester,2,0,H,0,0,D,Mike Riley,22,6,9,3,8,1,14,10,1,1,0,0 +20/01/2001,Chelsea,Ipswich,4,1,H,1,1,D,Andy D'Urso,23,5,10,2,11,1,11,12,3,0,0,1 +20/01/2001,Coventry,Everton,1,3,A,0,3,A,Paul Durkin,9,8,5,4,15,3,9,22,0,1,0,0 +20/01/2001,Derby,Man City,1,1,D,0,0,D,Jeff Winter,9,10,3,4,5,4,17,22,2,2,0,0 +20/01/2001,Leeds,Newcastle,1,3,A,1,2,A,Alan Wiley,12,11,5,7,16,1,12,18,2,1,0,0 +20/01/2001,Leicester,Arsenal,0,0,D,0,0,D,Barry Knight,3,13,0,4,1,9,6,8,2,2,1,0 +20/01/2001,Liverpool,Middlesbrough,0,0,D,0,0,D,Steve Dunn,22,4,7,2,7,3,9,10,1,1,0,0 +20/01/2001,Man United,Aston Villa,2,0,H,0,0,D,Mike Riley,12,5,7,3,7,3,10,19,1,2,0,0 +20/01/2001,Tottenham,Southampton,0,0,D,0,0,D,Clive Wilkes,9,9,4,6,6,0,10,15,0,1,0,0 +21/01/2001,Sunderland,Bradford,0,0,D,0,0,D,Mark Halsey,14,7,6,3,8,7,10,13,0,1,0,0 +22/01/2001,Charlton,West Ham,1,1,D,1,0,H,Dermot Gallagher,9,18,3,3,0,5,11,8,0,1,0,0 +24/01/2001,Aston Villa,Leeds,1,2,A,1,1,D,Steve Bennett,12,7,4,3,5,3,11,10,2,0,0,0 +30/01/2001,Arsenal,Bradford,2,0,H,2,0,H,Clive Wilkes,20,6,8,2,6,3,12,14,1,2,0,0 +30/01/2001,Charlton,Derby,2,1,H,1,1,D,Mike Dean,13,6,9,3,9,3,13,14,2,1,0,0 +31/01/2001,Chelsea,Newcastle,3,1,H,1,1,D,Mark Halsey,14,4,4,2,7,2,12,19,1,3,0,0 +31/01/2001,Everton,Middlesbrough,2,2,D,0,1,A,Graham Barber,17,6,8,3,9,1,17,12,1,5,0,0 +31/01/2001,Leeds,Coventry,1,0,H,0,0,D,Rob Harris,11,3,6,1,9,2,18,19,4,5,0,0 +31/01/2001,Man City,Liverpool,1,1,D,0,1,A,Peter Jones,4,16,2,3,5,11,17,13,1,1,0,0 +31/01/2001,Southampton,Leicester,1,0,H,0,0,D,Steve Bennett,10,6,5,3,3,3,14,12,0,3,0,0 +31/01/2001,Sunderland,Man United,0,1,A,0,0,D,Graham Poll,9,13,2,7,3,2,15,13,2,0,2,1 +31/01/2001,West Ham,Tottenham,0,0,D,0,0,D,Neale Barry,17,14,6,6,9,4,9,16,1,1,0,0 +03/02/2001,Bradford,Aston Villa,0,3,A,0,0,D,Dermot Gallagher,9,13,2,6,3,4,14,14,2,0,0,0 +03/02/2001,Coventry,Arsenal,0,1,A,0,0,D,Mike Dean,12,8,4,6,3,7,13,14,3,4,0,0 +03/02/2001,Derby,Sunderland,1,0,H,1,0,H,Barry Knight,9,17,3,5,3,4,16,18,7,3,0,0 +03/02/2001,Ipswich,Leeds,1,2,A,0,2,A,Peter Jones,11,10,6,3,3,4,10,15,1,2,1,0 +03/02/2001,Leicester,Chelsea,2,1,H,1,0,H,Steve Dunn,10,8,5,4,2,7,7,13,0,1,0,0 +03/02/2001,Liverpool,West Ham,3,0,H,2,0,H,Steve Bennett,14,8,10,3,5,3,9,6,0,1,0,1 +03/02/2001,Man United,Everton,1,0,H,0,0,D,Jeff Winter,8,5,1,1,2,5,8,11,0,2,0,0 +03/02/2001,Middlesbrough,Man City,1,1,D,0,1,A,Alan Wiley,14,6,6,3,4,2,12,14,0,2,1,0 +03/02/2001,Tottenham,Charlton,0,0,D,0,0,D,David Ellaray,9,7,3,2,2,3,17,14,2,0,0,0 +07/02/2001,Everton,Leeds,2,2,D,1,0,H,Neale Barry,7,9,4,5,4,7,18,18,2,1,0,0 +10/02/2001,Arsenal,Ipswich,1,0,H,0,0,D,Rob Harris,17,5,10,3,11,3,16,15,2,1,0,0 +10/02/2001,Aston Villa,Middlesbrough,1,1,D,1,0,H,Clive Wilkes,15,6,4,3,7,4,11,11,1,1,0,0 +10/02/2001,Chelsea,Man United,1,1,D,1,0,H,David Ellaray,8,16,4,6,3,8,16,12,0,2,0,0 +10/02/2001,Everton,Leicester,2,1,H,2,0,H,Rob Styles,9,9,6,2,7,2,12,7,3,2,0,0 +10/02/2001,Leeds,Derby,0,0,D,0,0,D,Jeff Winter,12,3,6,2,17,4,11,17,2,5,0,0 +10/02/2001,Man City,Tottenham,0,1,A,0,0,D,Steve Dunn,11,11,4,4,7,3,17,10,1,0,0,0 +10/02/2001,Southampton,Bradford,2,0,H,0,0,D,Graham Poll,12,4,6,1,8,0,9,16,0,1,0,0 +10/02/2001,Sunderland,Liverpool,1,1,D,0,0,D,Graham Barber,9,8,6,4,9,6,15,25,5,3,0,0 +11/02/2001,Charlton,Newcastle,2,0,H,2,0,H,Paul Durkin,11,5,4,3,4,11,10,12,2,1,0,0 +12/02/2001,West Ham,Coventry,1,1,D,0,0,D,Dermot Gallagher,21,13,9,4,5,4,13,16,2,5,0,0 +24/02/2001,Bradford,West Ham,1,2,A,0,1,A,Steve Lodge,15,11,5,5,7,1,9,10,1,2,0,0 +24/02/2001,Coventry,Charlton,2,2,D,1,1,D,Mark Halsey,17,15,7,7,3,7,15,11,0,0,0,0 +24/02/2001,Derby,Aston Villa,1,0,H,1,0,H,Alan Wiley,9,19,5,7,1,9,9,10,1,1,0,0 +24/02/2001,Ipswich,Everton,2,0,H,0,0,D,Graham Poll,12,8,6,2,3,2,11,14,1,2,0,1 +24/02/2001,Leicester,Sunderland,2,0,H,1,0,H,Andy D'Urso,11,9,7,3,6,5,13,16,0,4,0,1 +24/02/2001,Middlesbrough,Southampton,0,1,A,0,0,D,Neale Barry,11,10,6,5,3,6,9,9,1,0,0,0 +24/02/2001,Newcastle,Man City,0,1,A,0,0,D,Rob Harris,10,11,3,3,5,5,18,11,1,0,0,0 +24/02/2001,Tottenham,Leeds,1,2,A,1,1,D,Jeff Winter,13,15,4,9,5,12,16,14,2,1,0,0 +25/02/2001,Man United,Arsenal,6,1,H,5,1,H,Paul Durkin,16,6,9,2,3,3,10,6,0,0,0,0 +03/03/2001,Arsenal,West Ham,3,0,H,3,0,H,Mike Riley,16,4,6,2,5,6,19,12,2,3,0,0 +03/03/2001,Coventry,Chelsea,0,0,D,0,0,D,Steve Bennett,17,24,10,11,5,8,13,15,2,2,0,0 +03/03/2001,Derby,Tottenham,2,1,H,2,0,H,Rob Harris,8,13,4,7,6,7,20,12,2,3,0,0 +03/03/2001,Everton,Newcastle,1,1,D,0,0,D,David Ellaray,16,4,6,2,4,1,13,12,1,2,0,0 +03/03/2001,Leeds,Man United,1,1,D,0,0,D,Graham Barber,18,5,10,5,14,3,25,21,3,5,0,0 +03/03/2001,Leicester,Liverpool,2,0,H,0,0,D,Dermot Gallagher,9,8,7,3,4,7,9,7,1,1,0,0 +03/03/2001,Man City,Southampton,0,1,A,0,0,D,Jeff Winter,15,6,7,1,5,6,15,15,1,1,0,0 +03/03/2001,Middlesbrough,Charlton,0,0,D,0,0,D,Mike Dean,8,11,2,8,4,5,13,12,1,1,0,0 +04/03/2001,Ipswich,Bradford,3,1,H,0,1,A,Andy D'Urso,21,7,8,5,7,2,13,14,0,4,0,0 +05/03/2001,Sunderland,Aston Villa,1,1,D,0,0,D,Steve Lodge,18,6,12,2,5,3,17,18,1,2,0,0 +07/03/2001,West Ham,Chelsea,0,2,A,0,2,A,Alan Wiley,7,17,2,6,1,0,12,9,2,2,0,0 +10/03/2001,Aston Villa,Ipswich,2,1,H,0,1,A,Rob Styles,9,8,5,3,6,2,15,11,1,1,0,0 +17/03/2001,Bradford,Man City,2,2,D,0,1,A,Steve Dunn,16,10,7,3,3,3,13,12,2,1,0,0 +17/03/2001,Charlton,Leeds,1,2,A,1,1,D,Clive Wilkes,7,7,3,5,2,5,19,13,3,5,0,0 +17/03/2001,Chelsea,Sunderland,2,4,A,2,1,H,Paul Durkin,14,11,6,7,4,5,15,26,0,0,0,0 +17/03/2001,Man United,Leicester,2,0,H,0,0,D,Alan Wiley,19,5,8,2,14,2,11,13,1,1,0,0 +17/03/2001,Newcastle,Middlesbrough,1,2,A,0,2,A,Graham Barber,16,7,5,4,5,1,14,19,0,3,0,1 +17/03/2001,Southampton,Everton,1,0,H,0,0,D,Mark Halsey,17,5,5,1,6,6,13,18,1,1,0,0 +17/03/2001,Tottenham,Coventry,3,0,H,2,0,H,Graham Poll,12,11,4,5,5,8,13,10,0,1,0,0 +17/03/2001,West Ham,Ipswich,0,1,A,0,0,D,Mike Dean,5,13,2,5,4,5,14,8,0,0,0,0 +18/03/2001,Aston Villa,Arsenal,0,0,D,0,0,D,Barry Knight,4,14,2,5,3,3,16,13,2,2,0,1 +18/03/2001,Liverpool,Derby,1,1,D,0,1,A,Neale Barry,18,7,8,4,9,3,11,9,2,2,0,0 +31/03/2001,Arsenal,Tottenham,2,0,H,0,0,D,Paul Durkin,25,4,19,3,12,2,5,6,0,1,0,0 +31/03/2001,Bradford,Newcastle,2,2,D,2,1,H,Paul Taylor,22,14,8,7,10,8,17,23,0,1,0,0 +31/03/2001,Chelsea,Middlesbrough,2,1,H,1,0,H,Peter Jones,17,7,5,3,4,3,19,10,2,1,0,0 +31/03/2001,Coventry,Derby,2,0,H,1,0,H,David Ellaray,12,14,7,8,9,4,12,16,0,1,1,0 +31/03/2001,Liverpool,Man United,2,0,H,2,0,H,Graham Poll,15,9,6,3,6,4,8,8,1,1,1,0 +31/03/2001,Man City,Aston Villa,1,3,A,1,2,A,Rob Styles,12,8,6,7,9,2,18,13,6,4,1,0 +31/03/2001,Sunderland,Leeds,0,2,A,0,1,A,Steve Dunn,14,7,8,4,8,4,17,10,2,2,0,1 +31/03/2001,West Ham,Everton,0,2,A,0,1,A,Andy D'Urso,17,10,6,7,8,2,16,16,3,2,1,0 +01/04/2001,Charlton,Leicester,2,0,H,1,0,H,Jeff Winter,16,5,9,1,6,2,12,9,2,2,0,0 +02/04/2001,Southampton,Ipswich,0,3,A,0,1,A,Alan Wiley,11,11,3,5,6,7,17,15,2,1,0,0 +04/04/2001,Aston Villa,Leicester,2,1,H,1,1,D,Mike Dean,9,6,5,2,10,3,11,12,2,2,0,0 +07/04/2001,Aston Villa,West Ham,2,2,D,0,0,D,Rob Harris,6,7,4,5,5,3,5,3,1,3,0,0 +07/04/2001,Derby,Chelsea,0,4,A,0,0,D,Steve Lodge,3,10,2,7,1,9,13,7,1,1,0,0 +07/04/2001,Leeds,Southampton,2,0,H,1,0,H,Jeff Winter,15,6,7,2,10,1,10,11,0,0,0,0 +07/04/2001,Leicester,Coventry,1,3,A,1,2,A,Graham Barber,9,15,4,5,4,9,9,19,3,2,0,0 +08/04/2001,Everton,Man City,3,1,H,2,1,H,David Ellaray,11,13,7,5,8,8,17,18,0,3,1,1 +09/04/2001,Middlesbrough,Sunderland,0,0,D,0,0,D,Andy D'Urso ,12,12,6,4,3,2,22,22,5,3,1,0 +10/04/2001,Ipswich,Liverpool,1,1,D,0,0,D,Steve Dunn,8,7,5,2,6,1,10,15,0,0,0,0 +10/04/2001,Man United,Charlton,2,1,H,1,0,H,Mark Halsey ,23,3,8,2,12,4,8,9,1,1,0,0 +10/04/2001,Tottenham,Bradford,2,1,H,1,1,D,Steve Bennett ,17,10,13,5,5,7,13,9,1,1,0,0 +11/04/2001,Man City,Arsenal,0,4,A,0,4,A,Neale Barry,7,15,2,8,7,3,10,8,2,0,0,0 +13/04/2001,Bradford,Charlton,2,0,H,0,0,D,Peter Jones,4,3,8,8,3,7,13,21,1,3,0,0 +13/04/2001,Liverpool,Leeds,1,2,A,0,2,A,Alan Wiley,11,9,5,3,8,2,19,16,3,1,1,0 +14/04/2001,Arsenal,Middlesbrough,0,3,A,0,2,A,Paul Durkin,17,6,8,2,9,0,13,11,1,1,0,0 +14/04/2001,Aston Villa,Everton,2,1,H,1,1,D,Paul Taylor,8,5,5,3,5,3,13,14,0,3,0,0 +14/04/2001,Chelsea,Southampton,1,0,H,1,0,H,Clive Wilkes,14,9,8,5,7,6,11,19,3,3,0,0 +14/04/2001,Ipswich,Newcastle,1,0,H,0,0,D,Mike Dean,14,6,5,2,9,3,6,13,0,1,0,1 +14/04/2001,Leicester,Man City,1,2,A,1,1,D,Barry Knight,15,8,9,3,5,1,13,26,2,3,0,0 +14/04/2001,Man United,Coventry,4,2,H,2,2,D,Mike Riley,28,9,13,4,9,2,9,18,1,4,0,0 +14/04/2001,Sunderland,Tottenham,2,3,A,2,0,H,David Ellaray,13,8,4,6,3,3,12,15,1,2,0,0 +14/04/2001,West Ham,Derby,3,1,H,3,0,H,Rob Styles,18,6,10,3,3,5,10,18,0,2,0,0 +16/04/2001,Coventry,Sunderland,1,0,H,1,0,H,Dermot Gallagher,14,6,9,3,6,3,15,21,3,0,0,0 +16/04/2001,Derby,Leicester,2,0,H,1,0,H,Mark Halsey,10,7,6,4,5,6,17,16,0,0,0,0 +16/04/2001,Everton,Liverpool,2,3,A,1,1,D,Jeff Winter,10,10,7,6,2,5,19,15,4,2,1,0 +16/04/2001,Middlesbrough,Ipswich,1,2,A,1,0,H,Steve Bennett,4,12,1,5,5,4,8,8,0,0,0,0 +16/04/2001,Newcastle,West Ham,2,1,H,1,0,H,Paul Durkin,20,13,14,6,7,5,19,9,0,0,0,0 +17/04/2001,Charlton,Aston Villa,3,3,D,2,0,H,Graham Poll,12,11,5,8,5,7,10,15,3,4,1,0 +17/04/2001,Tottenham,Chelsea,0,3,A,0,1,A,Graham Barber,5,16,1,10,2,5,14,11,2,2,1,0 +21/04/2001,Arsenal,Everton,4,1,H,1,1,D,Dermot Gallagher,10,4,8,2,10,0,7,13,0,2,0,1 +21/04/2001,Aston Villa,Southampton,0,0,D,0,0,D,Jeff Winter,9,3,2,3,4,5,10,15,1,2,0,0 +21/04/2001,Bradford,Derby,2,0,H,1,0,H,Neale Barry,15,12,9,3,3,8,14,13,0,2,0,1 +21/04/2001,Chelsea,Charlton,0,1,A,0,1,A,Mike Dean,9,4,1,2,10,3,12,14,3,0,0,0 +21/04/2001,Ipswich,Coventry,2,0,H,1,0,H,Graham Barber,13,6,5,2,2,3,22,23,1,4,0,0 +21/04/2001,Leicester,Middlesbrough,0,3,A,0,1,A,Paul Taylor,15,15,4,7,12,5,13,11,3,1,0,0 +21/04/2001,Man United,Man City,1,1,D,0,0,D,David Ellaray,13,6,6,4,3,2,14,20,0,0,1,0 +21/04/2001,Sunderland,Newcastle,1,1,D,0,0,D,Mike Riley,16,9,10,5,4,8,17,22,5,4,0,0 +21/04/2001,West Ham,Leeds,0,2,A,0,1,A,Graham Poll,15,12,10,7,6,6,17,13,5,2,0,1 +22/04/2001,Liverpool,Tottenham,3,1,H,1,1,D,Barry Knight,14,7,8,5,6,4,10,13,1,1,0,0 +28/04/2001,Coventry,Liverpool,0,2,A,0,0,D,Steve Bennett,11,13,4,6,7,7,15,11,3,1,0,0 +28/04/2001,Derby,Arsenal,1,2,A,1,1,D,Graham Barber,6,14,2,9,4,6,14,13,3,4,0,0 +28/04/2001,Everton,Bradford,2,1,H,0,1,A,Paul Durkin,18,7,9,4,5,2,15,15,1,2,0,0 +28/04/2001,Leeds,Chelsea,2,0,H,0,0,D,Steve Dunn,16,7,9,5,7,2,22,17,2,5,0,0 +28/04/2001,Man City,West Ham,1,0,H,1,0,H,Peter Jones,16,12,9,7,10,10,19,12,3,5,0,0 +28/04/2001,Middlesbrough,Man United,0,2,A,0,1,A,Steve Lodge,18,9,3,5,4,6,13,21,2,3,0,0 +28/04/2001,Newcastle,Leicester,1,0,H,0,0,D,Graham Poll,20,4,6,3,12,0,15,14,1,1,0,0 +28/04/2001,Southampton,Sunderland,0,1,A,0,0,D,Alan Wiley,11,6,5,3,11,2,15,14,2,1,0,0 +28/04/2001,Tottenham,Aston Villa,0,0,D,0,0,D,Clive Wilkes,7,7,3,4,5,6,13,13,1,0,0,0 +30/04/2001,Charlton,Ipswich,2,1,H,1,1,D,Rob Styles,10,17,7,6,2,13,17,11,0,1,0,0 +01/05/2001,Bradford,Liverpool,0,2,A,0,0,D,Jeff Winter,15,11,4,10,7,0,11,13,0,2,0,0 +01/05/2001,Newcastle,Southampton,1,1,D,1,0,H,Steve Lodge,7,11,5,3,4,2,15,17,2,1,0,0 +05/05/2001,Arsenal,Leeds,2,1,H,1,0,H,Peter Jones,7,4,4,3,11,2,21,18,4,3,0,0 +05/05/2001,Aston Villa,Coventry,3,2,H,0,2,A,Mike Riley,6,6,4,3,6,2,12,23,2,5,0,0 +05/05/2001,Bradford,Middlesbrough,1,1,D,1,0,H,Graham Barber,10,14,7,9,6,6,25,21,4,2,0,0 +05/05/2001,Chelsea,Everton,2,1,H,2,1,H,Rob Harris,13,9,5,3,8,6,19,22,1,1,0,0 +05/05/2001,Leicester,Tottenham,4,2,H,1,0,H,Mike Dean,19,9,9,4,6,2,11,14,3,2,0,0 +05/05/2001,Liverpool,Newcastle,3,0,H,1,0,H,Mark Halsey,12,6,5,3,2,3,11,10,0,2,0,0 +05/05/2001,Man United,Derby,0,1,A,0,1,A,Neale Barry,11,10,6,5,13,1,13,11,1,0,0,0 +05/05/2001,Sunderland,Charlton,3,2,H,2,1,H,Steve Dunn,17,17,11,7,8,5,17,7,1,0,0,0 +05/05/2001,West Ham,Southampton,3,0,H,0,0,D,Clive Wilkes,16,12,12,3,5,1,18,18,2,2,0,0 +07/05/2001,Ipswich,Man City,2,1,H,0,0,D,Steve Lodge,15,11,7,7,9,2,8,19,0,0,0,0 +08/05/2001,Liverpool,Chelsea,2,2,D,1,1,D,Dermot Gallagher,16,14,8,7,6,6,14,21,1,4,0,0 +13/05/2001,Leeds,Bradford,6,1,H,5,1,H,Andy D'Urso,18,13,9,7,9,7,20,13,1,0,0,0 +13/05/2001,Southampton,Man United,2,1,H,2,0,H,Jeff Winter,14,9,3,6,4,4,13,18,1,1,0,0 +15/05/2001,Newcastle,Arsenal,0,0,D,0,0,D,Alan Wiley,12,8,2,1,4,8,14,10,1,0,0,0 +19/05/2001,Charlton,Liverpool,0,4,A,0,0,D,Graham Barber,12,16,10,14,10,10,7,13,1,1,0,0 +19/05/2001,Coventry,Bradford,0,0,D,0,0,D,Alan Wiley,15,10,7,3,9,3,15,13,1,1,0,0 +19/05/2001,Derby,Ipswich,1,1,D,1,0,H,Graham Poll,7,13,2,6,3,11,14,15,1,1,0,0 +19/05/2001,Everton,Sunderland,2,2,D,1,1,D,Steve Bennett,18,11,8,4,5,6,12,13,5,3,0,1 +19/05/2001,Leeds,Leicester,3,1,H,1,1,D,David Ellaray,24,8,11,5,12,5,14,17,3,4,0,0 +19/05/2001,Man City,Chelsea,1,2,A,1,1,D,Mike Riley,3,9,1,3,8,7,22,18,4,2,0,0 +19/05/2001,Middlesbrough,West Ham,2,1,H,2,1,H,Paul Durkin,19,11,7,5,5,6,13,15,0,0,0,0 +19/05/2001,Newcastle,Aston Villa,3,0,H,2,0,H,Barry Knight,9,5,5,1,5,1,10,14,0,5,1,1 +19/05/2001,Southampton,Arsenal,3,2,H,0,1,A,Paul Taylor,11,8,7,5,7,5,17,10,1,2,0,0 +19/05/2001,Tottenham,Man United,3,1,H,1,1,D,Andy D'Urso,9,15,3,6,3,6,13,15,0,2,0,0 +18/08/2001,Charlton,Everton,1,2,A,0,0,D,N. S. Barry,8,12,4,9,4,4,15,17,0,3,0,0 +18/08/2001,Derby,Blackburn,2,1,H,1,0,H,P. A. Durkin,7,14,3,4,4,10,14,15,1,0,0,0 +18/08/2001,Leeds,Southampton,2,0,H,0,0,D,C. R. Wilkes ,16,11,6,6,10,3,16,24,1,2,0,1 +18/08/2001,Leicester,Bolton,0,5,A,0,4,A,R. Styles,6,18,1,8,3,5,21,17,4,3,0,0 +18/08/2001,Liverpool,West Ham,2,1,H,1,1,D,J. T. Winter,9,3,6,3,5,3,13,12,1,5,0,0 +18/08/2001,Middlesbrough,Arsenal,0,4,A,0,1,A,G. P. Barber,6,14,2,9,2,6,13,19,2,3,1,1 +18/08/2001,Sunderland,Ipswich,1,0,H,1,0,H,G. Poll,12,10,6,4,1,1,22,20,0,1,0,0 +18/08/2001,Tottenham,Aston Villa,0,0,D,0,0,D,D. J. Gallagher,8,11,2,2,6,5,12,17,3,2,0,0 +19/08/2001,Chelsea,Newcastle,1,1,D,1,0,H,A. P. D'Urso ,13,9,8,3,12,7,19,20,3,4,0,0 +19/08/2001,Man United,Fulham,3,2,H,1,1,D,P. Jones ,12,8,6,5,6,4,17,19,0,1,0,0 +20/08/2001,Everton,Tottenham,1,1,D,0,1,A,D. R. Elleray ,18,7,11,2,8,0,15,12,3,2,0,2 +21/08/2001,Arsenal,Leeds,1,2,A,1,1,D,J. T. Winter ,14,5,5,2,10,2,28,29,4,5,0,2 +21/08/2001,Bolton,Middlesbrough,1,0,H,1,0,H,S. W. Dunn ,13,11,3,4,5,2,19,16,1,1,0,0 +21/08/2001,Ipswich,Derby,3,1,H,1,0,H,E. K. Wolstenholme ,30,2,18,2,19,2,21,14,0,3,0,1 +22/08/2001,Blackburn,Man United,2,2,D,0,1,A,A. G. Wiley ,12,18,5,7,3,8,18,16,2,1,1,0 +22/08/2001,Fulham,Sunderland,2,0,H,0,0,D,B. Knight ,13,8,5,3,8,9,18,20,0,2,0,0 +25/08/2001,Arsenal,Leicester,4,0,H,2,0,H,A. P. D'Urso ,20,2,11,0,9,0,14,20,1,4,1,1 +25/08/2001,Blackburn,Tottenham,2,1,H,1,0,H,S. G. Bennett,12,12,7,5,3,6,14,11,1,1,0,0 +25/08/2001,Everton,Middlesbrough,2,0,H,1,0,H,U. D. Rennie,7,5,5,1,3,6,25,23,2,1,0,0 +25/08/2001,Fulham,Derby,0,0,D,0,0,D,M. L Dean,10,9,1,2,11,3,12,14,2,3,0,0 +25/08/2001,Ipswich,Charlton,0,1,A,0,0,D,S. W. Dunn ,17,13,11,6,6,1,22,20,2,6,0,0 +25/08/2001,Southampton,Chelsea,0,2,A,0,1,A,D. R. Elleray ,8,7,2,2,6,2,13,18,0,1,0,0 +25/08/2001,West Ham,Leeds,0,0,D,0,0,D,P. A. Durkin ,11,10,3,4,5,8,19,14,3,2,0,0 +26/08/2001,Aston Villa,Man United,1,1,D,1,0,H,G. P. Barber ,11,13,4,7,5,5,9,12,0,2,0,0 +26/08/2001,Newcastle,Sunderland,1,1,D,1,1,D,M. A. Riley ,16,6,6,3,10,4,19,18,2,2,0,0 +27/08/2001,Bolton,Liverpool,2,1,H,1,0,H,G. Poll ,11,18,4,7,4,6,13,11,2,2,0,0 +08/09/2001,Chelsea,Arsenal,1,1,D,1,1,D,M. A. Riley ,10,14,5,11,5,6,20,20,1,5,1,0 +08/09/2001,Derby,West Ham,0,0,D,0,0,D,C. R. Wilkes,6,10,3,4,3,4,22,19,5,2,0,0 +08/09/2001,Leeds,Bolton,0,0,D,0,0,D,S. G. Bennett,11,8,6,4,7,5,13,17,1,1,0,0 +08/09/2001,Leicester,Ipswich,1,1,D,0,1,A,B. Knight,12,13,7,9,6,3,26,24,3,3,1,1 +08/09/2001,Liverpool,Aston Villa,1,3,A,0,1,A,A. P. D'Urso,12,10,7,7,7,0,19,14,3,1,1,0 +08/09/2001,Man United,Everton,4,1,H,2,0,H,D. J. Gallagher,18,5,9,1,6,2,7,11,0,1,0,0 +08/09/2001,Middlesbrough,Newcastle,1,4,A,1,1,D,G. Poll,8,8,4,5,3,3,15,17,1,3,1,0 +08/09/2001,Sunderland,Blackburn,1,0,H,0,0,D,M. L Dean,11,10,4,4,4,10,9,14,2,3,0,0 +09/09/2001,Charlton,Fulham,1,1,D,1,1,D,J. T. Winter,7,16,4,5,5,6,22,12,2,1,0,0 +09/09/2001,Tottenham,Southampton,2,0,H,0,0,D,A. G. Wiley,18,15,6,6,3,3,15,16,0,0,0,0 +15/09/2001,Bolton,Southampton,0,1,A,0,0,D,D. Pugh,4,10,0,6,3,2,11,19,1,1,0,0 +15/09/2001,Derby,Leicester,2,3,A,1,1,D,G. P. Barber,15,12,9,6,8,8,16,23,5,3,0,0 +15/09/2001,Everton,Liverpool,1,3,A,1,2,A,P.A. Durkin,11,13,5,9,4,2,16,13,0,1,0,0 +15/09/2001,Fulham,Arsenal,1,3,A,0,1,A,A.G. Wiley,6,11,3,6,4,5,18,20,3,4,0,0 +15/09/2001,Middlesbrough,West Ham,2,0,H,2,0,H,M. A. Riley,13,5,5,1,8,1,16,23,2,3,0,1 +15/09/2001,Newcastle,Man United,4,3,H,2,1,H,S. G. Bennett,15,10,8,7,8,5,17,16,2,1,0,1 +16/09/2001,Aston Villa,Sunderland,0,0,D,0,0,D,U. D. Rennie,8,4,3,2,10,3,20,25,0,4,0,0 +16/09/2001,Charlton,Leeds,0,2,A,0,1,A,M. R. Halsey,9,20,4,9,8,9,13,14,2,1,1,0 +16/09/2001,Ipswich,Blackburn,1,1,D,1,0,H,G. Poll,20,6,10,4,6,13,8,15,3,1,0,0 +16/09/2001,Tottenham,Chelsea,2,3,A,0,1,A,S. W. Dunn,11,6,4,4,6,6,10,12,1,4,0,1 +17/09/2001,Leicester,Middlesbrough,1,2,A,1,0,H,N. S. Barry,10,13,5,6,4,2,19,15,0,1,0,0 +19/09/2001,Blackburn,Bolton,1,1,D,0,0,D,J. T. Winter,20,10,8,5,10,2,14,15,0,4,0,0 +19/09/2001,Sunderland,Tottenham,1,2,A,0,1,A,P. A. Durkin,14,13,8,6,11,4,9,14,1,2,0,0 +22/09/2001,Arsenal,Bolton,1,1,D,0,0,D,C. R. Wilkes,23,7,12,4,17,0,9,11,1,1,0,1 +22/09/2001,Blackburn,Everton,1,0,H,1,0,H,G. P. Barber,13,14,6,7,5,4,18,17,1,2,0,0 +22/09/2001,Leicester,Fulham,0,0,D,0,0,D,E. K. Wolstenholme,12,6,5,2,4,4,15,21,2,2,0,0 +22/09/2001,Liverpool,Tottenham,1,0,H,0,0,D,D. J. Gallagher,14,11,6,5,2,5,12,9,0,1,0,0 +22/09/2001,Man United,Ipswich,4,0,H,2,0,H,N. S. Barry,13,8,8,1,8,4,14,17,0,1,0,0 +22/09/2001,Sunderland,Charlton,2,2,D,0,1,A,A. P. D'Urso,22,13,13,5,9,8,20,13,2,3,0,0 +23/09/2001,Chelsea,Middlesbrough,2,2,D,2,0,H,R. Styles,8,4,3,2,8,2,17,15,4,1,0,0 +23/09/2001,Leeds,Derby,3,0,H,1,0,H,A. G. Wiley,19,9,10,2,9,3,26,17,1,2,0,0 +23/09/2001,West Ham,Newcastle,3,0,H,1,0,H,P. Jones,17,8,9,5,10,7,20,17,3,0,0,0 +24/09/2001,Southampton,Aston Villa,1,3,A,1,2,A,S. W. Dunn,12,11,4,5,2,3,15,18,1,2,1,1 +26/09/2001,Newcastle,Leicester,1,0,H,1,0,H,D. Pugh,12,2,6,2,10,1,17,27,2,3,0,0 +29/09/2001,Bolton,Sunderland,0,2,A,0,0,D,D. R. Elleray,15,14,8,7,8,4,10,13,1,1,0,0 +29/09/2001,Charlton,Leicester,2,0,H,1,0,H,M. L. Dean,14,9,12,6,4,4,12,11,1,2,1,1 +29/09/2001,Derby,Arsenal,0,2,A,0,1,A,R. Styles,7,11,5,6,2,5,18,19,3,3,0,1 +29/09/2001,Everton,West Ham,5,0,H,1,0,H,P. A. Durkin,18,15,11,5,8,4,15,8,0,0,0,0 +29/09/2001,Middlesbrough,Southampton,1,3,A,0,0,D,A. G. Wiley,5,8,3,4,6,0,16,17,0,1,0,0 +29/09/2001,Tottenham,Man United,3,5,A,3,0,H,J. T. Winter,7,18,3,9,4,7,16,10,3,3,0,0 +30/09/2001,Aston Villa,Blackburn,2,0,H,0,0,D,M. R. Halsey,5,4,2,1,2,8,9,15,2,2,0,0 +30/09/2001,Fulham,Chelsea,1,1,D,0,1,A,G. Poll,4,9,1,3,5,7,5,14,1,4,0,1 +30/09/2001,Ipswich,Leeds,1,2,A,1,0,H,A. P. D'Urso,11,11,6,7,10,8,8,8,0,1,0,0 +30/09/2001,Newcastle,Liverpool,0,2,A,0,1,A,G. P. Barber,7,6,3,2,7,0,14,23,1,4,0,0 +13/10/2001,Bolton,Newcastle,0,4,A,0,1,A,M. A. Riley,9,15,3,8,7,8,10,9,2,2,1,0 +13/10/2001,Charlton,Middlesbrough,0,0,D,0,0,D,D. R. Elleray,8,11,4,4,13,4,14,13,2,1,0,0 +13/10/2001,Chelsea,Leicester,2,0,H,2,0,H,J. T. Winter,22,8,10,2,5,3,9,13,0,0,0,0 +13/10/2001,Ipswich,Everton,0,0,D,0,0,D,C. R. Wilkes,7,9,4,6,8,6,8,5,0,1,0,0 +13/10/2001,Liverpool,Leeds,1,1,D,0,1,A,A. G. Wiley,10,9,2,3,6,4,11,14,3,2,0,0 +13/10/2001,Southampton,Arsenal,0,2,A,0,1,A,G. P. Barber,3,21,0,8,1,6,15,9,2,1,1,0 +13/10/2001,Sunderland,Man United,1,3,A,0,1,A,G. Poll,15,13,7,8,3,4,12,15,0,0,0,0 +14/10/2001,Aston Villa,Fulham,2,0,H,0,0,D,P. A. Durkin,9,11,3,3,5,2,9,8,1,1,0,0 +14/10/2001,Blackburn,West Ham,7,1,H,3,1,H,A. P. D'Urso,24,12,14,5,7,7,8,6,1,3,0,1 +15/10/2001,Tottenham,Derby,3,1,H,2,1,H,M. R. Halsey,24,8,12,6,16,2,11,6,0,2,0,0 +20/10/2001,Arsenal,Blackburn,3,3,D,0,1,A,U. D. Rennie,18,9,11,5,5,4,13,20,1,2,0,0 +20/10/2001,Derby,Charlton,1,1,D,1,0,H,A. P. D'Urso,10,11,5,7,5,5,12,11,3,0,0,0 +20/10/2001,Everton,Aston Villa,3,2,H,1,0,H,R. Styles,10,11,6,3,8,4,15,14,0,1,0,0 +20/10/2001,Leicester,Liverpool,1,4,A,0,3,A,M. R. Halsey,8,12,3,6,6,6,11,13,1,2,0,0 +20/10/2001,Man United,Bolton,1,2,A,1,1,D,G. P. Barber,13,7,6,2,8,0,11,6,0,0,0,0 +20/10/2001,West Ham,Southampton,2,0,H,0,0,D,N. S. Barry,20,10,8,3,10,7,16,16,0,1,0,1 +21/10/2001,Fulham,Ipswich,1,1,D,1,0,H,M. A. Riley,7,6,3,2,3,3,25,17,2,3,1,0 +21/10/2001,Leeds,Chelsea,0,0,D,0,0,D,P. A. Durkin,18,14,12,6,10,8,20,17,2,3,0,0 +21/10/2001,Newcastle,Tottenham,0,2,A,0,2,A,A. G. Wiley,8,9,3,3,7,2,16,10,2,1,0,0 +22/10/2001,Middlesbrough,Sunderland,2,0,H,2,0,H,M. R. Halsey,7,15,5,5,4,5,18,16,1,2,1,0 +24/10/2001,Aston Villa,Charlton,1,0,H,1,0,H,D. Pugh,16,4,6,0,3,4,6,8,0,0,0,0 +24/10/2001,Southampton,Ipswich,3,3,D,2,1,H,U. D. Rennie,11,10,6,5,4,7,14,5,2,1,0,0 +24/10/2001,West Ham,Chelsea,2,1,H,2,1,H,D. J. Gallagher,10,15,4,5,1,8,11,6,0,0,0,0 +27/10/2001,Aston Villa,Bolton,3,2,H,2,1,H,E. K. Wolstenholme,10,9,7,5,6,2,13,16,2,3,0,0 +27/10/2001,Charlton,Liverpool,0,2,A,0,2,A,P. Jones,14,5,4,4,13,1,12,13,0,1,0,1 +27/10/2001,Everton,Newcastle,1,3,A,0,1,A,J. T. Winter,18,6,8,4,13,3,8,15,1,2,0,0 +27/10/2001,Fulham,Southampton,2,1,H,2,1,H,A. P. D'Urso,19,11,5,5,6,4,8,14,0,2,0,0 +27/10/2001,Man United,Leeds,1,1,D,0,0,D,D. J. Gallagher,18,12,6,6,6,2,16,15,3,3,0,0 +27/10/2001,Sunderland,Arsenal,1,1,D,0,1,A,M. A. Riley,10,14,6,5,7,6,12,22,3,3,0,0 +27/10/2001,Tottenham,Middlesbrough,2,1,H,0,1,A,M. L Dean,10,8,4,3,8,5,11,12,0,0,0,0 +28/10/2001,Derby,Chelsea,1,1,D,1,0,H,S. G. Bennett,13,19,8,8,4,10,14,11,1,4,0,0 +28/10/2001,Ipswich,West Ham,2,3,A,0,1,A,S. W. Dunn,12,12,8,4,8,9,16,11,2,2,0,0 +29/10/2001,Blackburn,Leicester,0,0,D,0,0,D,R. Styles,12,10,3,5,7,1,11,7,6,2,0,0 +03/11/2001,Bolton,Everton,2,2,D,1,1,D,A. P. D'Urso,16,11,8,6,9,3,13,13,4,2,1,0 +03/11/2001,Leicester,Sunderland,1,0,H,0,0,D,S. W. Dunn,9,11,5,4,7,7,13,20,1,3,0,0 +03/11/2001,Middlesbrough,Derby,5,1,H,0,0,D,N. S. Barry,11,6,7,4,6,4,10,12,0,1,0,0 +03/11/2001,Newcastle,Aston Villa,3,0,H,1,0,H,C. R. Wilkes,17,10,7,2,13,8,15,7,0,3,0,0 +03/11/2001,Southampton,Blackburn,1,2,A,1,1,D,A. G. Wiley,16,6,6,5,10,4,19,18,1,1,0,0 +03/11/2001,West Ham,Fulham,0,2,A,0,1,A,G. P. Barber,12,14,6,6,10,8,13,18,1,3,0,0 +04/11/2001,Arsenal,Charlton,2,4,A,1,2,A,M. R. Halsey,26,9,12,4,4,6,9,11,3,2,0,0 +04/11/2001,Chelsea,Ipswich,2,1,H,1,0,H,R. Styles,15,7,6,3,5,6,12,17,0,2,0,0 +04/11/2001,Leeds,Tottenham,2,1,H,0,0,D,S. G. Bennett,20,11,9,4,7,3,9,15,1,3,0,0 +04/11/2001,Liverpool,Man United,3,1,H,2,0,H,G. Poll,8,10,5,3,1,1,9,13,0,0,0,0 +17/11/2001,Aston Villa,Middlesbrough,0,0,D,0,0,D,P. Jones,8,11,5,2,5,3,16,9,1,2,0,0 +17/11/2001,Blackburn,Liverpool,1,1,D,0,1,A,M. A. Riley,5,5,5,2,6,3,17,10,2,0,0,0 +17/11/2001,Derby,Southampton,1,0,H,1,0,H,P. A. Durkin,9,8,3,4,7,6,13,13,0,1,0,0 +17/11/2001,Fulham,Newcastle,3,1,H,2,0,H,E. K. Wolstenholme,8,7,8,1,4,4,10,18,0,6,0,0 +17/11/2001,Man United,Leicester,2,0,H,1,0,H,A. P. D'Urso,14,8,7,4,10,4,10,10,2,1,0,0 +17/11/2001,Tottenham,Arsenal,1,1,D,0,0,D,J. T. Winter,9,5,4,5,9,6,18,16,2,1,0,0 +18/11/2001,Everton,Chelsea,0,0,D,0,0,D,M. R. Halsey,12,14,4,7,8,4,11,11,1,3,0,0 +18/11/2001,Ipswich,Bolton,1,2,A,1,2,A,S. G. Bennett,8,10,4,5,12,2,8,16,0,2,0,0 +18/11/2001,Sunderland,Leeds,2,0,H,0,0,D,G. P. Barber,12,12,5,6,5,3,25,11,1,3,0,0 +19/11/2001,Charlton,West Ham,4,4,D,2,2,D,A. G. Wiley,18,11,9,7,13,4,8,13,1,2,0,0 +24/11/2001,Bolton,Fulham,0,0,D,0,0,D,M. L Dean,11,7,5,1,2,3,11,9,1,1,0,0 +24/11/2001,Chelsea,Blackburn,0,0,D,0,0,D,G. Poll,14,8,8,2,7,3,13,7,0,2,0,0 +24/11/2001,Leicester,Everton,0,0,D,0,0,D,U. D. Rennie,8,8,7,3,7,2,18,21,2,1,0,0 +24/11/2001,Newcastle,Derby,1,0,H,1,0,H,R. Styles,17,8,10,4,8,6,10,14,1,2,0,0 +24/11/2001,Southampton,Charlton,1,0,H,0,0,D,D. J. Gallagher,15,7,8,4,10,5,10,16,0,1,0,0 +24/11/2001,West Ham,Tottenham,0,1,A,0,0,D,D. R. Elleray,10,10,6,3,4,2,18,13,2,2,0,0 +25/11/2001,Arsenal,Man United,3,1,H,0,1,A,P. Jones,14,2,10,1,11,2,12,19,2,3,0,0 +25/11/2001,Leeds,Aston Villa,1,1,D,1,1,D,N. S. Barry,16,15,9,8,7,6,15,15,2,2,1,0 +25/11/2001,Liverpool,Sunderland,1,0,H,1,0,H,S. G. Bennett,7,12,3,5,4,3,9,12,1,3,1,0 +25/11/2001,Middlesbrough,Ipswich,0,0,D,0,0,D,D. Pugh,9,11,4,3,3,5,5,8,0,0,0,0 +01/12/2001,Aston Villa,Leicester,0,2,A,0,1,A,S. G. Bennett,11,6,7,4,7,8,8,14,0,4,1,0 +01/12/2001,Blackburn,Middlesbrough,0,1,A,0,1,A,B. Knight,14,8,4,5,7,3,8,9,1,2,0,0 +01/12/2001,Charlton,Newcastle,1,1,D,0,0,D,A. P. D'Urso,15,12,7,6,3,7,7,11,1,2,0,1 +01/12/2001,Derby,Liverpool,0,1,A,0,1,A,G. P. Barber,12,7,4,5,5,9,9,14,1,2,0,0 +01/12/2001,Ipswich,Arsenal,0,2,A,0,1,A,D. R. Elleray,7,7,2,3,12,5,4,9,0,2,0,0 +01/12/2001,Man United,Chelsea,0,3,A,0,1,A,A. G. Wiley,12,14,5,5,7,2,9,16,2,3,0,0 +01/12/2001,Sunderland,West Ham,1,0,H,0,0,D,P. Jones,15,10,6,5,3,3,12,16,3,0,0,0 +02/12/2001,Everton,Southampton,2,0,H,0,0,D,J.T. Winter,13,10,7,4,5,6,19,13,0,1,0,0 +02/12/2001,Fulham,Leeds,0,0,D,0,0,D,G. Poll,13,6,5,5,5,5,11,21,1,2,0,0 +03/12/2001,Tottenham,Bolton,3,2,H,0,1,A,P. A. Durkin,14,9,6,6,5,6,10,14,0,2,0,0 +05/12/2001,Chelsea,Charlton,0,1,A,0,0,D,D. J. Gallagher,20,11,5,5,8,1,13,10,1,1,0,0 +05/12/2001,West Ham,Aston Villa,1,1,D,0,1,A,M. L Dean,14,12,8,5,5,6,10,15,2,1,0,0 +08/12/2001,Charlton,Tottenham,3,1,H,2,0,H,S. W. Dunn,11,14,6,5,9,9,10,12,3,1,0,0 +08/12/2001,Derby,Bolton,1,0,H,0,0,D,M. D. Messias,16,6,6,3,5,4,11,24,0,4,0,0 +08/12/2001,Fulham,Everton,2,0,H,1,0,H,P. Dowd,10,5,4,3,6,7,15,25,2,4,1,1 +08/12/2001,Leicester,Southampton,0,4,A,0,1,A,G. Poll,8,10,4,5,5,4,13,15,1,0,0,0 +08/12/2001,Liverpool,Middlesbrough,2,0,H,2,0,H,D. J. Gallagher,8,4,5,2,4,6,7,10,0,2,0,0 +08/12/2001,Man United,West Ham,0,1,A,0,0,D,P. A. Durkin,17,8,6,4,12,4,18,8,5,3,0,0 +09/12/2001,Arsenal,Aston Villa,3,2,H,0,2,A,A. G. Wiley,15,8,8,5,9,3,18,14,4,0,0,0 +09/12/2001,Blackburn,Leeds,1,2,A,0,0,D,A. P. D'Urso,14,10,6,7,8,7,15,18,1,5,0,0 +09/12/2001,Ipswich,Newcastle,0,1,A,0,1,A,R. Styles,9,7,5,5,8,2,3,3,3,1,0,0 +09/12/2001,Sunderland,Chelsea,0,0,D,0,0,D,N. S. Barry,11,10,6,3,6,6,11,15,1,3,0,0 +12/12/2001,Liverpool,Fulham,0,0,D,0,0,D,J. T. Winter,16,8,7,4,7,3,17,11,0,0,0,0 +12/12/2001,Man United,Derby,5,0,H,2,0,H,M. R. Halsey,20,8,11,5,5,6,8,13,1,2,0,0 +15/12/2001,Bolton,Charlton,0,0,D,0,0,D,C. J. Foy,13,6,6,4,7,4,13,13,1,2,0,0 +15/12/2001,Everton,Derby,1,0,H,0,0,D,C. R. Wilkes,22,3,12,2,9,3,9,15,1,1,0,0 +15/12/2001,Middlesbrough,Man United,0,1,A,0,0,D,D. R. Elleray,6,14,3,5,5,8,11,14,2,3,0,0 +15/12/2001,Newcastle,Blackburn,2,1,H,0,1,A,S. W. Dunn,15,8,6,6,10,6,8,15,0,3,0,0 +15/12/2001,Southampton,Sunderland,2,0,H,1,0,H,D. Pugh,14,5,5,3,3,1,11,13,1,2,0,0 +15/12/2001,Tottenham,Fulham,4,0,H,2,0,H,N. S. Barry,10,12,6,1,3,6,18,15,3,2,0,0 +15/12/2001,West Ham,Arsenal,1,1,D,1,1,D,M. A. Riley,8,16,4,6,7,6,10,14,4,3,0,0 +16/12/2001,Chelsea,Liverpool,4,0,H,2,0,H,M. R. Halsey,16,16,10,8,5,6,15,10,2,3,0,0 +16/12/2001,Leeds,Leicester,2,2,D,1,0,H,R. Styles,14,5,7,4,8,3,6,15,1,3,0,0 +17/12/2001,Aston Villa,Ipswich,2,1,H,1,1,D,M. L Dean,10,14,5,3,1,4,16,10,0,1,0,0 +18/12/2001,Arsenal,Newcastle,1,3,A,1,0,H,G. Poll,19,4,8,3,4,4,10,19,3,3,1,1 +19/12/2001,Leeds,Everton,3,2,H,2,0,H,P. Jones ,18,8,7,3,5,2,12,13,1,1,0,0 +22/12/2001,Charlton,Blackburn,0,2,A,0,0,D,"Wiley, A. G.",10,7,2,4,4,3,16,19,2,3,0,0 +22/12/2001,Derby,Aston Villa,3,1,H,1,1,D,"Elleray, D. R.",10,10,7,3,4,5,11,15,0,1,0,0 +22/12/2001,Leeds,Newcastle,3,4,A,1,1,D,"Winter, J. T.",15,13,6,7,2,9,19,17,2,1,0,0 +22/12/2001,Leicester,West Ham,1,1,D,1,0,H,"Wolstenholme, E. K.",10,7,4,3,6,7,8,15,2,2,1,0 +22/12/2001,Man United,Southampton,6,1,H,3,0,H,"Dunn, S. W.",12,6,7,3,8,6,13,10,1,1,0,0 +22/12/2001,Sunderland,Everton,1,0,H,0,0,D,"Knight, B.",10,8,5,5,6,5,18,15,1,4,0,0 +22/12/2001,Tottenham,Ipswich,1,2,A,1,1,D,"Riley, M. A.",12,12,6,4,9,5,22,16,1,2,1,0 +23/12/2001,Chelsea,Bolton,5,1,H,2,1,H,"Rennie, U. D.",16,10,5,4,4,4,7,21,2,2,0,0 +23/12/2001,Liverpool,Arsenal,1,2,A,0,1,A,"Durkin, P. A.",15,11,2,3,6,4,12,20,1,4,0,1 +26/12/2001,Arsenal,Chelsea,2,1,H,0,1,A,"Barber, G. P.",11,9,7,3,4,5,18,21,2,4,0,0 +26/12/2001,Aston Villa,Liverpool,1,2,A,1,1,D,"D'Urso, A. P.",10,12,5,6,7,3,10,9,1,0,0,0 +26/12/2001,Blackburn,Sunderland,0,3,A,0,2,A,"Wilkes, C. R.",7,13,3,7,3,5,24,23,1,2,1,0 +26/12/2001,Bolton,Leeds,0,3,A,0,2,A,"Wiley, A. G.",17,8,2,5,11,2,15,20,1,3,0,0 +26/12/2001,Everton,Man United,0,2,A,0,0,D,"Rennie, U. D.",10,11,5,7,4,11,11,12,0,1,0,0 +26/12/2001,Fulham,Charlton,0,0,D,0,0,D,"Bennett, S. G.",10,8,4,3,4,9,5,14,2,1,0,0 +26/12/2001,Ipswich,Leicester,2,0,H,0,0,D,"Barry, N. S.",12,3,6,1,3,10,2,6,0,2,0,0 +26/12/2001,Newcastle,Middlesbrough,3,0,H,1,0,H,"Halsey, M. R.",18,5,9,2,10,2,18,12,0,1,0,0 +26/12/2001,Southampton,Tottenham,1,0,H,0,0,D,"Jones, P.",6,10,5,6,4,13,15,16,0,3,0,0 +26/12/2001,West Ham,Derby,4,0,H,1,0,H,"Poll, G.",14,6,9,3,3,1,13,19,1,3,0,1 +29/12/2001,Arsenal,Middlesbrough,2,1,H,0,1,A,"D'Urso, A. P.",16,3,6,2,9,1,17,13,3,4,0,0 +29/12/2001,Aston Villa,Tottenham,1,1,D,0,1,A,"Wolstenholme, E. K.",12,7,7,4,12,1,12,10,0,3,0,0 +29/12/2001,Blackburn,Derby,0,1,A,0,1,A,"Pugh, D.",16,7,5,5,13,4,13,20,2,4,0,0 +29/12/2001,Bolton,Leicester,2,2,D,1,2,A,"Riley, M. A.",5,14,3,8,4,10,11,15,2,3,2,1 +29/12/2001,Everton,Charlton,0,3,A,0,1,A,"Barber, G. P.",7,8,3,4,3,6,16,12,1,3,0,0 +29/12/2001,Ipswich,Sunderland,5,0,H,4,0,H,"Poll, G.",13,10,8,3,3,3,6,4,1,1,0,0 +29/12/2001,Newcastle,Chelsea,1,2,A,1,2,A,"Bennett, S. G.",10,12,7,8,5,3,11,21,0,3,0,0 +29/12/2001,Southampton,Leeds,0,1,A,0,0,D,"Halsey, M. R.",12,13,6,7,3,5,17,15,1,1,0,0 +29/12/2001,West Ham,Liverpool,1,1,D,1,0,H,"Styles, R.",17,12,8,8,9,5,5,8,1,0,0,0 +30/12/2001,Fulham,Man United,2,3,A,1,2,A,"Gallagher, D. J.",17,19,9,10,4,3,14,8,0,2,0,0 +01/01/2002,Charlton,Ipswich,3,2,H,2,2,D,"Rennie, U. D.",13,6,8,4,11,4,19,12,2,2,0,0 +01/01/2002,Chelsea,Southampton,2,4,A,2,1,H,"Wolstenholme, E. K.",13,8,6,5,2,6,14,18,2,1,0,0 +01/01/2002,Leeds,West Ham,3,0,H,2,0,H,"Dunn, S. W.",12,9,11,4,8,6,15,8,3,2,0,0 +01/01/2002,Liverpool,Bolton,1,1,D,0,0,D,"Wilkes, C. R.",15,7,5,6,4,8,8,15,1,4,0,0 +01/01/2002,Middlesbrough,Everton,1,0,H,0,0,D,"Styles, R.",10,11,4,3,5,6,12,10,1,2,0,0 +01/01/2002,Sunderland,Aston Villa,1,1,D,0,0,D,"Riley, M. A.",18,8,11,4,7,0,14,13,1,3,0,0 +01/01/2002,Tottenham,Blackburn,1,0,H,1,0,H,"Winter, J. T.",12,11,7,7,7,9,14,17,0,1,0,0 +02/01/2002,Derby,Fulham,0,1,A,0,0,D,"Knight, B.",10,8,0,4,10,3,17,7,1,2,0,0 +02/01/2002,Man United,Newcastle,3,1,H,1,0,H,"Jones, P.",11,11,7,6,6,5,11,8,1,1,0,0 +09/01/2002,Southampton,Liverpool,2,0,H,0,0,D,"Poll, G.",9,7,7,4,5,6,12,7,0,1,0,0 +12/01/2002,Aston Villa,Derby,2,1,H,2,1,H,"Halsey, M. R.",9,3,5,1,4,1,15,13,2,3,0,1 +12/01/2002,Blackburn,Charlton,4,1,H,2,0,H,"Barry, N. S.",18,9,11,6,7,2,12,11,0,0,0,1 +12/01/2002,Bolton,Chelsea,2,2,D,0,0,D,"Winter, J. T.",11,9,5,4,10,3,10,11,0,1,0,0 +12/01/2002,Everton,Sunderland,1,0,H,1,0,H,"Elleray, D. R.",9,6,5,1,4,5,11,16,1,1,0,0 +12/01/2002,Fulham,Middlesbrough,2,1,H,2,1,H,"Dean, M. L",19,8,11,4,10,2,15,18,1,2,0,0 +12/01/2002,Ipswich,Tottenham,2,1,H,1,0,H,"Messias, M. D.",8,11,5,6,5,5,3,1,0,1,0,0 +12/01/2002,Newcastle,Leeds,3,1,H,1,1,D,"Barber, G. P.",16,8,8,6,6,1,17,28,3,6,0,1 +12/01/2002,West Ham,Leicester,1,0,H,1,0,H,"Gallagher, D. J.",11,5,7,3,5,4,10,11,1,2,0,0 +13/01/2002,Arsenal,Liverpool,1,1,D,0,0,D,"Dunn, S. W.",11,5,4,2,3,1,15,16,1,1,0,0 +13/01/2002,Southampton,Man United,1,3,A,1,2,A,"Bennett, S. G.",12,11,4,7,8,6,11,8,1,1,0,0 +19/01/2002,Derby,Ipswich,1,3,A,0,0,D,"Durkin, P. A.",13,13,5,5,3,3,7,16,0,2,0,0 +19/01/2002,Leicester,Newcastle,0,0,D,0,0,D,"Wiley, A. G.",8,9,4,3,6,11,17,19,2,2,0,0 +19/01/2002,Liverpool,Southampton,1,1,D,1,0,H,"Barry, N. S.",9,6,5,2,8,3,10,17,0,1,0,0 +19/01/2002,Man United,Blackburn,2,1,H,1,0,H,"Rennie, U. D.",16,10,6,4,8,3,13,13,1,0,0,0 +19/01/2002,Middlesbrough,Bolton,1,1,D,1,0,H,"Poll, G.",11,7,3,4,2,6,16,17,1,1,0,0 +19/01/2002,Sunderland,Fulham,1,1,D,0,1,A,"Jones, P.",16,9,8,2,11,2,19,13,3,2,0,0 +19/01/2002,Tottenham,Everton,1,1,D,1,1,D,"Wilkes, C. R.",12,4,4,2,14,5,14,17,0,2,0,0 +20/01/2002,Chelsea,West Ham,5,1,H,1,0,H,"D'Urso, A. P.",22,5,15,4,6,4,13,10,3,1,0,1 +20/01/2002,Leeds,Arsenal,1,1,D,1,1,D,"Halsey, M. R.",6,9,1,6,3,7,18,16,1,2,0,0 +21/01/2002,Charlton,Aston Villa,1,2,A,0,2,A,"Styles, R",9,7,5,3,3,2,7,11,1,1,0,0 +22/01/2002,Man United,Liverpool,0,1,A,0,0,D,"Barber, G. P.",12,9,3,5,11,2,15,16,1,2,0,0 +23/01/2002,Leicester,Arsenal,1,3,A,0,2,A,"Elleray, D. R.",3,13,2,8,7,7,12,9,2,0,0,0 +29/01/2002,Bolton,Man United,0,4,A,0,2,A,"D'Urso, A. P.",15,21,4,12,1,9,10,9,0,0,0,0 +29/01/2002,Charlton,Derby,1,0,H,0,0,D,"Pugh, D.",11,3,3,2,9,0,11,13,1,2,0,0 +29/01/2002,Sunderland,Middlesbrough,0,1,A,0,1,A,"Durkin, P.",18,6,8,2,15,3,12,13,0,2,0,1 +30/01/2002,Aston Villa,Everton,0,0,D,0,0,D,"Foy, C. J.",8,6,2,0,5,1,14,21,1,3,0,0 +30/01/2002,Blackburn,Arsenal,2,3,A,2,2,D,"Gallagher, D. J.",10,9,4,5,6,2,18,21,2,2,0,1 +30/01/2002,Chelsea,Leeds,2,0,H,2,0,H,"Bennett, S. G.",14,6,3,4,5,6,11,22,1,2,0,0 +30/01/2002,Ipswich,Fulham,1,0,H,1,0,H,"Wiley, A. G.",13,7,8,3,10,5,18,11,1,2,0,0 +30/01/2002,Liverpool,Leicester,1,0,H,0,0,D,"Knight, B.",11,3,4,0,9,3,9,21,1,2,0,0 +30/01/2002,Southampton,West Ham,2,0,H,1,0,H,"Dowd, P.",10,11,8,4,4,2,16,13,0,3,0,0 +30/01/2002,Tottenham,Newcastle,1,3,A,1,0,H,"Dean, M. L",8,10,6,4,5,4,8,5,1,1,0,0 +02/02/2002,Arsenal,Southampton,1,1,D,1,0,H,"Wilkes, C. R.",14,4,4,1,4,3,13,22,2,3,0,0 +02/02/2002,Derby,Tottenham,1,0,H,1,0,H,"Rennie, U. D.",12,7,7,3,9,6,21,8,3,2,0,0 +02/02/2002,Everton,Ipswich,1,2,A,1,2,A,"Dunn, S. W.",9,5,4,3,2,4,19,16,1,0,0,0 +02/02/2002,Fulham,Aston Villa,0,0,D,0,0,D,"Messias, M. D.",9,15,3,9,9,4,12,14,1,2,0,0 +02/02/2002,Leicester,Chelsea,2,3,A,1,0,H,"Barber, G. P.",16,9,9,5,6,2,16,17,1,3,0,0 +02/02/2002,Man United,Sunderland,4,1,H,4,1,H,"Styles, R.",18,6,8,3,11,2,6,11,1,3,0,0 +02/02/2002,Newcastle,Bolton,3,2,H,2,2,D,"Elleray, D. R.",10,10,6,6,13,6,3,9,0,0,0,0 +02/02/2002,West Ham,Blackburn,2,0,H,1,0,H,"Jones, P.",13,14,6,2,5,8,10,20,2,3,0,0 +03/02/2002,Leeds,Liverpool,0,4,A,0,1,A,"Poll, G.",13,11,5,7,7,6,14,9,2,1,0,0 +03/02/2002,Middlesbrough,Charlton,0,0,D,0,0,D,"Riley, M. A.",7,8,3,2,5,7,17,15,1,2,0,0 +09/02/2002,Aston Villa,Chelsea,1,1,D,1,0,H,"Durkin, P. A.",7,9,3,3,3,8,10,10,1,1,0,0 +09/02/2002,Bolton,West Ham,1,0,H,1,0,H,"Pugh, D.",4,9,3,2,4,10,11,12,1,3,0,0 +09/02/2002,Derby,Sunderland,0,1,A,0,0,D,"Poll, G.",9,8,4,4,7,8,13,15,0,1,0,0 +09/02/2002,Fulham,Blackburn,2,0,H,1,0,H,"Halsey, M. R.",13,10,8,6,5,2,18,15,3,2,0,1 +09/02/2002,Ipswich,Liverpool,0,6,A,0,2,A,"Bennett, S. G.",4,14,2,11,7,9,2,4,0,0,0,0 +09/02/2002,Middlesbrough,Leeds,2,2,D,0,1,A,"Barry, N. S.",10,10,5,6,7,4,15,11,1,1,0,0 +09/02/2002,Newcastle,Southampton,3,1,H,3,1,H,"Knight, B.",16,11,10,4,5,4,7,8,0,1,0,0 +09/02/2002,Tottenham,Leicester,2,1,H,1,0,H,"D'Urso, A. P.",12,7,4,6,8,8,8,9,0,1,0,0 +10/02/2002,Charlton,Man United,0,2,A,0,1,A,"Wiley, A. G.",9,10,1,5,7,5,8,9,0,1,0,0 +10/02/2002,Everton,Arsenal,0,1,A,0,0,D,"Winter, J. T.",6,6,2,3,1,5,17,20,0,5,0,0 +19/02/2002,Middlesbrough,Fulham,2,1,H,1,0,H,"Gallagher, D. J.",11,10,5,5,8,10,13,14,1,2,0,0 +23/02/2002,Arsenal,Fulham,4,1,H,3,1,H,"Rennie, U. D.",16,6,10,5,7,5,19,14,0,1,0,0 +23/02/2002,Leicester,Derby,0,3,A,0,0,D,"Riley, M. A.",6,5,3,4,8,2,16,15,4,2,0,0 +23/02/2002,Liverpool,Everton,1,1,D,0,0,D,"Elleray, D. R.",13,6,5,3,9,4,9,13,0,2,0,0 +23/02/2002,Man United,Aston Villa,1,0,H,0,0,D,"Winter, J. T.",14,4,4,2,9,2,9,9,0,0,0,0 +23/02/2002,Southampton,Bolton,0,0,D,0,0,D,"Wolstenholme, E. K.",8,4,2,2,6,6,17,9,1,3,1,0 +23/02/2002,West Ham,Middlesbrough,1,0,H,0,0,D,"Foy, C. J.",12,7,8,4,12,3,17,11,2,5,0,1 +24/02/2002,Leeds,Charlton,0,0,D,0,0,D,"Dean, M. L",14,9,2,2,9,2,12,17,0,3,0,0 +24/02/2002,Sunderland,Newcastle,0,1,A,0,0,D,"Barber, G. P.",11,13,4,5,5,11,19,16,3,1,0,0 +02/03/2002,Aston Villa,West Ham,2,1,H,1,1,D,"Barber, G. P.",10,4,7,3,9,4,12,19,0,1,0,0 +02/03/2002,Bolton,Blackburn,1,1,D,1,0,H,"Durkin, P. A.",11,6,5,3,7,5,22,11,1,0,0,1 +02/03/2002,Charlton,Chelsea,2,1,H,0,0,D,"Poll, G.",9,4,6,2,5,4,13,13,2,1,0,0 +02/03/2002,Fulham,Liverpool,0,2,A,0,1,A,"Wiley, A. G.",11,10,5,4,8,4,13,14,3,2,0,0 +02/03/2002,Ipswich,Southampton,1,3,A,0,0,D,"Halsey, M. R.",8,5,4,4,4,3,9,5,0,1,0,0 +02/03/2002,Middlesbrough,Leicester,1,0,H,1,0,H,"Wolstenholme, E. K.",11,6,7,1,5,6,17,15,2,4,0,0 +02/03/2002,Newcastle,Arsenal,0,2,A,0,2,A,"Barry, N. S.",13,9,6,6,5,8,17,17,0,1,0,0 +02/03/2002,Tottenham,Sunderland,2,1,H,1,1,D,"Styles, R.",17,7,7,2,5,5,5,8,0,1,0,0 +03/03/2002,Derby,Man United,2,2,D,1,1,D,"Dunn, S. W.",12,22,7,9,4,10,12,13,2,0,0,0 +03/03/2002,Everton,Leeds,0,0,D,0,0,D,"D'Urso, A. P.",12,7,7,3,3,4,9,21,1,2,0,1 +05/03/2002,Arsenal,Derby,1,0,H,0,0,D,"Barber, G. P.",17,0,8,0,9,1,13,16,1,2,0,0 +05/03/2002,Blackburn,Aston Villa,3,0,H,1,0,H,"Bennett, S. G.",21,6,9,4,5,5,11,15,1,2,0,0 +05/03/2002,Sunderland,Bolton,1,0,H,1,0,H,"Poll, G.",11,10,4,4,1,7,13,14,2,1,0,0 +06/03/2002,Chelsea,Fulham,3,2,H,2,1,H,"Jones, P.",14,12,8,6,3,5,12,12,1,0,0,0 +06/03/2002,Leeds,Ipswich,2,0,H,0,0,D,"Gallagher, D. J.",11,9,7,3,7,5,11,15,1,3,0,0 +06/03/2002,Liverpool,Newcastle,3,0,H,1,0,H,"Winter, J. T.",23,11,11,2,5,6,8,13,0,0,0,0 +06/03/2002,Man United,Tottenham,4,0,H,2,0,H,"Riley, M. A.",25,7,14,2,14,3,6,13,2,3,0,1 +06/03/2002,Southampton,Middlesbrough,1,1,D,1,0,H,"Rennie, U. D.",9,4,4,1,13,1,17,12,1,1,0,1 +06/03/2002,West Ham,Everton,1,0,H,0,0,D,"Knight, B.",16,6,6,2,10,3,8,13,0,2,0,0 +09/03/2002,Leicester,Charlton,1,1,D,1,1,D,"Wilkes, C. R.",10,11,5,6,11,5,15,16,1,3,0,0 +13/03/2002,Blackburn,Ipswich,2,1,H,2,0,H,"Dowd, P.",11,6,7,3,9,2,24,17,3,2,0,0 +13/03/2002,Chelsea,Tottenham,4,0,H,1,0,H,"Wiley, A. G.",24,5,13,0,6,3,10,14,1,3,0,1 +16/03/2002,Bolton,Derby,1,3,A,0,1,A,"Elleray, D. R.",6,3,2,2,7,3,13,13,0,0,1,0 +16/03/2002,Chelsea,Sunderland,4,0,H,1,0,H,"Knight, B.",12,8,7,3,6,3,8,13,2,2,0,0 +16/03/2002,Everton,Fulham,2,1,H,2,0,H,"Barber, G. P.",5,11,2,5,0,11,16,15,2,4,1,0 +16/03/2002,Middlesbrough,Liverpool,1,2,A,0,1,A,"D'Urso, A. P.",15,8,5,7,7,3,10,12,0,2,0,0 +16/03/2002,Newcastle,Ipswich,2,2,D,0,0,D,"Riley, M. A.",20,7,8,4,12,3,11,15,1,1,0,0 +16/03/2002,Southampton,Leicester,2,2,D,1,2,A,"Dean, M. L",9,6,5,4,6,2,15,13,0,3,0,0 +16/03/2002,West Ham,Man United,3,5,A,2,2,D,"Halsey, M. R.",10,14,3,9,3,3,13,8,2,2,0,0 +17/03/2002,Aston Villa,Arsenal,1,2,A,1,0,H,"Dunn, S. W.",8,6,3,3,10,2,10,12,1,2,0,0 +17/03/2002,Leeds,Blackburn,3,1,H,2,0,H,"Poll, G.",18,15,8,8,4,4,15,16,2,1,0,0 +18/03/2002,Tottenham,Charlton,0,1,A,0,0,D,"Winter, J. T.",13,10,5,4,4,6,5,13,1,0,0,0 +23/03/2002,Charlton,Bolton,1,2,A,0,2,A,"Foy, C. J.",6,6,4,4,4,1,15,17,1,2,0,0 +23/03/2002,Derby,Everton,3,4,A,0,1,A,"Barry, N. S.",12,8,4,7,8,0,12,21,2,3,0,0 +23/03/2002,Ipswich,Aston Villa,0,0,D,0,0,D,"Pugh, D.",13,6,3,2,8,6,15,12,1,2,0,0 +23/03/2002,Leicester,Leeds,0,2,A,0,2,A,"Dunn, S. W.",11,12,5,8,8,7,12,10,2,2,0,0 +23/03/2002,Man United,Middlesbrough,0,1,A,0,1,A,"Bennett, S. G.",18,5,4,3,6,2,9,11,4,2,0,0 +23/03/2002,Sunderland,Southampton,1,1,D,0,0,D,"Messias, M. D.",10,14,7,8,3,8,10,14,2,3,0,0 +24/03/2002,Fulham,Tottenham,0,2,A,0,2,A,"Durkin, P. A.",9,11,3,4,4,6,16,13,2,2,0,0 +24/03/2002,Liverpool,Chelsea,1,0,H,0,0,D,"Halsey, M. R.",8,10,4,1,5,3,17,13,1,1,0,0 +29/03/2002,Newcastle,Everton,6,2,H,2,2,D,"Poll, G.",18,7,9,4,4,1,5,8,0,3,0,0 +30/03/2002,Arsenal,Sunderland,3,0,H,3,0,H,"Durkin, P. A.",13,13,10,5,5,4,8,17,0,3,0,0 +30/03/2002,Bolton,Aston Villa,3,2,H,2,2,D,"Styles, R.",9,9,6,3,2,5,16,13,3,1,0,0 +30/03/2002,Chelsea,Derby,2,1,H,0,0,D,"Dean, M. L",13,2,6,1,6,3,13,10,3,4,0,0 +30/03/2002,Leeds,Man United,3,4,A,1,3,A,"Elleray, D. R.",16,13,11,7,2,3,10,9,1,2,0,0 +30/03/2002,Leicester,Blackburn,2,1,H,1,0,H,"Barber, G. P.",9,12,6,4,6,6,12,24,0,1,0,0 +30/03/2002,Liverpool,Charlton,2,0,H,2,0,H,"Gallagher, D. J.",14,6,8,1,6,6,6,10,1,2,0,0 +30/03/2002,Middlesbrough,Tottenham,1,1,D,0,1,A,"Dowd, P.",8,10,3,6,9,6,9,16,0,3,0,0 +30/03/2002,Southampton,Fulham,1,1,D,1,1,D,"Winter, J. T.",11,7,6,2,5,8,12,17,1,1,0,0 +30/03/2002,West Ham,Ipswich,3,1,H,1,0,H,"Wiley, A. G.",18,12,11,5,8,4,9,13,0,2,0,0 +01/04/2002,Blackburn,Southampton,2,0,H,2,0,H,"Wilkes, C. R.",7,5,4,2,5,2,10,13,0,1,0,0 +01/04/2002,Charlton,Arsenal,0,3,A,0,3,A,"D'Urso, A. P.",5,12,5,8,9,2,7,15,2,2,0,0 +01/04/2002,Derby,Middlesbrough,0,1,A,0,1,A,"Durkin, P. A.",7,6,4,1,7,7,14,17,1,0,0,0 +01/04/2002,Everton,Bolton,3,1,H,1,0,H,"Bennett, S. G.",10,12,5,3,5,5,8,11,1,1,1,1 +01/04/2002,Fulham,West Ham,0,1,A,0,1,A,"Halsey, M. R.",10,3,2,2,7,1,10,12,1,1,0,0 +01/04/2002,Ipswich,Chelsea,0,0,D,0,0,D,"Wolstenholme, E. K.",9,7,5,2,6,8,6,9,0,0,0,0 +01/04/2002,Sunderland,Leicester,2,1,H,2,1,H,"Barry, N. S.",12,9,9,7,5,1,12,11,3,2,0,0 +01/04/2002,Tottenham,Leeds,2,1,H,2,0,H,"Rennie, U. D.",8,17,5,8,1,7,15,12,2,0,0,0 +02/04/2002,Aston Villa,Newcastle,1,1,D,1,1,D,"Dunn, S. W.",16,6,7,1,11,2,12,15,0,0,0,0 +06/04/2002,Arsenal,Tottenham,2,1,H,1,0,H,"Halsey, M. R.",13,2,5,2,10,1,15,17,1,4,0,0 +06/04/2002,Bolton,Ipswich,4,1,H,4,0,H,"Winter, J. T.",9,9,5,4,3,8,9,11,0,0,0,0 +06/04/2002,Chelsea,Everton,3,0,H,2,0,H,"Elleray, D. R.",19,12,8,6,7,8,6,10,0,1,0,0 +06/04/2002,Leicester,Man United,0,1,A,0,0,D,"D'Urso, A. P.",13,13,3,4,4,10,13,8,1,1,0,0 +06/04/2002,Middlesbrough,Aston Villa,2,1,H,1,0,H,"Barber, G. P.",7,12,3,9,5,8,9,15,0,0,0,0 +06/04/2002,Southampton,Derby,2,0,H,1,0,H,"Bennett, S. G.",10,8,5,3,5,6,10,9,1,0,0,0 +06/04/2002,West Ham,Charlton,2,0,H,2,0,H,"Riley, M. A.",11,18,4,8,3,13,7,9,1,4,0,0 +07/04/2002,Leeds,Sunderland,2,0,H,1,0,H,"Jones, P.",14,8,7,0,8,4,18,14,2,1,0,0 +08/04/2002,Newcastle,Fulham,1,1,D,1,0,H,"Wiley, A. G.",7,5,4,3,6,2,16,10,1,0,0,0 +10/04/2002,Blackburn,Chelsea,0,0,D,0,0,D,"Dunn, S. W.",15,8,7,2,5,5,7,13,0,0,0,0 +13/04/2002,Aston Villa,Leeds,0,1,A,0,1,A,"Knight, B.",10,5,5,2,7,2,16,18,2,3,0,0 +13/04/2002,Charlton,Southampton,1,1,D,1,0,H,"Dowd, P.",8,10,5,6,7,5,21,12,2,2,0,0 +13/04/2002,Derby,Newcastle,2,3,A,0,0,D,"Styles, R.",6,15,2,9,6,2,13,11,3,1,0,0 +13/04/2002,Everton,Leicester,2,2,D,0,2,A,"Rennie, U. D.",16,6,8,2,13,4,15,6,1,1,0,0 +13/04/2002,Sunderland,Liverpool,0,1,A,0,0,D,"Gallagher, D. J.",10,10,5,5,4,3,10,11,1,2,1,0 +13/04/2002,Tottenham,West Ham,1,1,D,0,0,D,"Barry, N. S.",16,12,6,5,3,5,11,6,0,0,0,0 +20/04/2002,Bolton,Tottenham,1,1,D,0,1,A,"Messias, M. D.",13,10,6,4,13,3,13,15,1,3,0,0 +20/04/2002,Chelsea,Man United,0,3,A,0,2,A,"Barber, G. P.",7,6,2,5,12,5,9,6,4,1,0,0 +20/04/2002,Leeds,Fulham,0,1,A,0,0,D,"Styles, R.",9,7,4,4,1,4,12,16,2,3,0,0 +20/04/2002,Leicester,Aston Villa,2,2,D,1,2,A,"Poll, G.",10,16,7,11,5,4,11,15,1,0,0,0 +20/04/2002,Liverpool,Derby,2,0,H,1,0,H,"Riley, M. A.",13,4,5,1,9,4,8,16,0,3,0,0 +20/04/2002,Middlesbrough,Blackburn,1,3,A,0,1,A,"Barry, N. S.",8,9,2,5,7,9,15,21,3,3,1,0 +20/04/2002,Newcastle,Charlton,3,0,H,1,0,H,"Dean, M. L",12,4,8,1,8,7,12,9,1,2,0,0 +20/04/2002,Southampton,Everton,0,1,A,0,1,A,"Halsey, M. R.",6,7,0,5,1,7,13,18,2,0,0,0 +20/04/2002,West Ham,Sunderland,3,0,H,1,0,H,"Bennett, S. G.",16,10,7,5,9,6,8,9,3,2,0,0 +21/04/2002,Arsenal,Ipswich,2,0,H,0,0,D,"Wiley, A. G.",20,7,8,2,14,2,9,9,1,0,0,0 +23/04/2002,Blackburn,Newcastle,2,2,D,1,0,H,"Rennie, U. D.",12,21,5,9,6,6,15,11,0,2,0,0 +23/04/2002,Fulham,Bolton,3,0,H,1,0,H,"Durkin, P. A.",12,7,7,2,3,6,5,9,0,0,0,0 +24/04/2002,Arsenal,West Ham,2,0,H,0,0,D,"Dunn, S. W.",20,5,9,1,12,2,12,13,2,2,0,0 +24/04/2002,Ipswich,Middlesbrough,1,0,H,0,0,D,"D'Urso, A. P.",17,13,7,4,4,5,13,13,0,2,0,0 +27/04/2002,Aston Villa,Southampton,2,1,H,2,0,H,"Elleray, D. R.",8,7,5,3,7,1,11,8,2,1,0,0 +27/04/2002,Charlton,Sunderland,2,2,D,1,2,A,"Wolstenholme, E. K.",11,6,7,3,10,5,16,13,2,3,0,0 +27/04/2002,Derby,Leeds,0,1,A,0,1,A,"Poll, G.",9,10,1,4,5,6,12,19,2,0,0,0 +27/04/2002,Fulham,Leicester,0,0,D,0,0,D,"Yates, N",9,4,2,2,7,2,12,15,3,4,0,0 +27/04/2002,Ipswich,Man United,0,1,A,0,1,A,"Styles, R.",12,16,4,8,5,5,11,10,2,0,0,0 +27/04/2002,Middlesbrough,Chelsea,0,2,A,0,2,A,"Knight, B.",10,14,2,8,4,6,11,13,3,1,0,0 +27/04/2002,Newcastle,West Ham,3,1,H,1,1,D,"Durkin, P. A.",16,11,5,5,8,2,10,8,1,2,0,0 +27/04/2002,Tottenham,Liverpool,1,0,H,1,0,H,"Jones, P.",11,11,5,7,3,8,9,11,1,1,0,0 +28/04/2002,Everton,Blackburn,1,2,A,0,1,A,"Winter, J. T.",10,14,4,6,9,5,12,9,2,2,0,0 +29/04/2002,Bolton,Arsenal,0,2,A,0,2,A,"Gallagher, D. J.",7,8,1,4,7,3,10,8,0,0,0,0 +08/05/2002,Liverpool,Blackburn,4,3,H,2,1,H,"Wiley, A. G.",15,9,7,7,8,7,9,12,1,1,0,0 +08/05/2002,Man United,Arsenal,0,1,A,0,0,D,"Durkin, P. A.",9,5,3,2,6,5,21,16,4,2,0,0 +11/05/2002,Arsenal,Everton,4,3,H,2,2,D,"Halsey, M. R.",17,14,6,6,4,3,7,5,0,0,0,0 +11/05/2002,Blackburn,Fulham,3,0,H,0,0,D,"Foy, C. J.",17,6,13,2,2,7,13,7,3,0,0,0 +11/05/2002,Chelsea,Aston Villa,1,3,A,0,1,A,"Bennett, S. G.",15,15,6,11,7,6,14,18,0,1,0,0 +11/05/2002,Leeds,Middlesbrough,1,0,H,0,0,D,"Rennie, U. D.",19,7,5,3,8,8,13,15,2,0,0,0 +11/05/2002,Leicester,Tottenham,2,1,H,0,0,D,"Elleray, D. R.",10,13,6,6,6,9,5,6,0,0,0,0 +11/05/2002,Liverpool,Ipswich,5,0,H,2,0,H,"Dunn, S. W.",15,7,8,2,0,7,13,5,0,0,0,0 +11/05/2002,Man United,Charlton,0,0,D,0,0,D,"Poll, G.",13,11,6,6,7,4,8,7,2,0,0,0 +11/05/2002,Southampton,Newcastle,3,1,H,2,0,H,"D'Urso, A. P.",7,16,3,8,3,6,15,11,0,1,1,0 +11/05/2002,Sunderland,Derby,1,1,D,1,0,H,"Wiley, A. G.",26,5,12,3,4,2,11,14,0,1,0,0 +11/05/2002,West Ham,Bolton,2,1,H,1,0,H,"Dean, M. L",17,10,10,4,9,5,9,13,2,1,0,0 +17/08/2002,Blackburn,Sunderland,0,0,D,0,0,D,D Elleray,15,7,5,3,9,1,14,11,1,2,0,0 +17/08/2002,Charlton,Chelsea,2,3,A,2,1,H,G Barber,5,21,5,12,3,6,10,12,0,3,1,0 +17/08/2002,Everton,Tottenham,2,2,D,1,0,H,N Barry,13,10,9,5,10,5,18,4,1,1,0,0 +17/08/2002,Fulham,Bolton,4,1,H,3,1,H,A Wiley,13,3,6,1,7,4,16,12,1,2,0,0 +17/08/2002,Leeds,Man City,3,0,H,2,0,H,G Poll,13,18,8,10,2,7,13,13,1,1,0,0 +17/08/2002,Man United,West Brom,1,0,H,0,0,D,S Bennett,20,6,13,5,9,1,9,12,1,1,0,1 +17/08/2002,Southampton,Middlesbrough,0,0,D,0,0,D,B Knight,12,11,5,5,3,4,11,14,0,0,0,0 +18/08/2002,Arsenal,Birmingham,2,0,H,2,0,H,M Riley,15,7,7,1,9,2,6,11,0,1,0,1 +18/08/2002,Aston Villa,Liverpool,0,1,A,0,0,D,A D'Urso,11,12,5,6,6,8,9,5,2,2,0,0 +19/08/2002,Newcastle,West Ham,4,0,H,0,0,D,P Durkin,13,7,10,4,7,1,7,9,1,2,0,0 +23/08/2002,Chelsea,Man United,2,2,D,2,1,H,G Poll,11,9,5,7,3,3,13,9,3,2,0,0 +24/08/2002,Birmingham,Blackburn,0,1,A,0,1,A,D Gallagher,15,13,10,9,6,4,10,17,1,2,0,0 +24/08/2002,Bolton,Charlton,1,2,A,1,1,D,M Messias,15,8,9,4,4,6,9,11,1,1,0,0 +24/08/2002,Liverpool,Southampton,3,0,H,1,0,H,J Winter,16,7,9,1,5,8,11,8,0,1,0,0 +24/08/2002,Man City,Newcastle,1,0,H,1,0,H,U Rennie,20,9,12,6,6,8,12,14,1,1,0,0 +24/08/2002,Middlesbrough,Fulham,2,2,D,1,0,H,M Dean,6,10,3,4,8,11,18,7,4,2,0,0 +24/08/2002,Sunderland,Everton,0,1,A,0,1,A,R Styles,13,11,3,5,10,4,10,15,1,0,0,0 +24/08/2002,Tottenham,Aston Villa,1,0,H,1,0,H,C Wilkes,8,18,3,8,2,5,20,15,2,0,0,0 +24/08/2002,West Brom,Leeds,1,3,A,0,1,A,S Dunn,12,11,2,4,7,3,9,11,1,1,0,0 +24/08/2002,West Ham,Arsenal,2,2,D,1,0,H,N Barry,8,9,4,5,4,4,18,14,2,5,0,0 +27/08/2002,Arsenal,West Brom,5,2,H,3,0,H,P Durkin,10,8,8,6,1,1,9,9,3,2,0,0 +27/08/2002,Charlton,Tottenham,0,1,A,0,1,A,A Wiley,10,7,5,1,6,9,9,23,1,2,0,0 +28/08/2002,Aston Villa,Man City,1,0,H,0,0,D,D Pugh,11,7,6,1,5,4,13,11,1,3,0,0 +28/08/2002,Blackburn,Liverpool,2,2,D,1,1,D,S Bennett,7,5,4,3,5,2,17,10,3,1,0,0 +28/08/2002,Everton,Birmingham,1,1,D,0,0,D,E Wolstenholme,11,5,4,2,7,4,18,12,0,3,1,0 +28/08/2002,Leeds,Sunderland,0,1,A,0,0,D,M Halsey,20,9,9,4,8,1,13,11,2,0,0,0 +28/08/2002,Southampton,Chelsea,1,1,D,0,0,D,C Foy,12,10,4,3,5,6,16,11,2,1,0,0 +31/08/2002,Birmingham,Leeds,2,1,H,1,0,H,P Durkin,9,8,5,6,2,5,14,16,3,3,0,0 +31/08/2002,Man City,Everton,3,1,H,2,1,H,B Knight,7,14,6,8,7,6,12,11,1,0,1,0 +31/08/2002,Middlesbrough,Blackburn,1,0,H,0,0,D,M Halsey,7,10,3,7,8,6,19,16,3,3,0,0 +31/08/2002,Sunderland,Man United,1,1,D,0,1,A,U Rennie,12,20,5,10,6,8,13,16,1,2,0,1 +31/08/2002,Tottenham,Southampton,2,1,H,1,1,D,M Dean,18,15,8,8,12,6,16,13,3,1,0,1 +31/08/2002,West Brom,Fulham,1,0,H,0,0,D,R Styles,8,6,5,1,5,7,17,16,4,3,0,0 +31/08/2002,West Ham,Charlton,0,2,A,0,2,A,J Winter,9,5,4,3,8,6,11,15,3,1,0,0 +01/09/2002,Bolton,Aston Villa,1,0,H,0,0,D,S Dunn,13,12,6,5,4,5,9,11,2,3,0,0 +01/09/2002,Chelsea,Arsenal,1,1,D,1,0,H,A D'Urso,8,8,2,5,6,2,20,16,5,2,0,1 +02/09/2002,Liverpool,Newcastle,2,2,D,0,0,D,G Poll,22,11,11,6,12,3,7,25,1,2,0,0 +03/09/2002,Man United,Middlesbrough,1,0,H,1,0,H,M Riley,7,5,1,3,4,6,13,12,1,2,0,0 +10/09/2002,Arsenal,Man City,2,1,H,2,1,H,C Wilkes,13,5,5,4,7,6,16,14,1,2,0,1 +10/09/2002,Middlesbrough,Sunderland,3,0,H,2,0,H,A Wiley,9,8,7,6,6,8,16,12,3,3,0,0 +11/09/2002,Aston Villa,Charlton,2,0,H,0,0,D,G Poll,12,8,5,2,6,2,16,16,0,1,0,0 +11/09/2002,Blackburn,Chelsea,2,3,A,2,1,H,M Riley,11,14,6,9,9,4,15,16,2,3,0,0 +11/09/2002,Fulham,Tottenham,3,2,H,0,2,A,M Halsey,13,7,8,4,9,6,8,11,0,1,0,0 +11/09/2002,Liverpool,Birmingham,2,2,D,1,0,H,N Barry,12,6,10,3,1,2,6,10,0,1,0,0 +11/09/2002,Man United,Bolton,0,1,A,0,0,D,G Barber,23,10,14,8,9,6,10,11,1,2,0,0 +11/09/2002,Newcastle,Leeds,0,2,A,0,1,A,D Gallagher,22,14,11,6,10,7,15,12,3,2,0,0 +11/09/2002,Southampton,Everton,1,0,H,0,0,D,S Bennett,11,18,7,10,4,13,13,19,3,3,0,0 +11/09/2002,West Ham,West Brom,0,1,A,0,1,A,A D'Urso,5,6,3,5,8,4,11,15,1,0,0,0 +14/09/2002,Bolton,Liverpool,2,3,A,0,1,A,R Styles,7,12,5,8,7,4,10,9,2,2,0,0 +14/09/2002,Charlton,Arsenal,0,3,A,0,1,A,S Dunn,9,10,3,8,1,6,8,13,0,3,0,0 +14/09/2002,Chelsea,Newcastle,3,0,H,2,0,H,B Knight,7,5,5,0,2,4,9,9,1,4,0,0 +14/09/2002,Everton,Middlesbrough,2,1,H,1,1,D,M Messias,13,10,8,5,10,4,18,12,2,1,0,0 +14/09/2002,Leeds,Man United,1,0,H,0,0,D,J Winter,8,6,2,5,4,7,15,12,2,1,0,0 +14/09/2002,Sunderland,Fulham,0,3,A,0,1,A,C Foy,8,5,5,4,11,6,12,13,2,2,0,0 +14/09/2002,West Brom,Southampton,1,0,H,0,0,D,D Gallagher,11,10,7,5,0,7,14,14,2,1,0,1 +15/09/2002,Man City,Blackburn,2,2,D,0,1,A,M Dean,15,12,7,8,15,5,13,13,3,3,1,0 +15/09/2002,Tottenham,West Ham,3,2,H,0,0,D,U Rennie,12,11,3,1,5,6,16,16,2,2,0,1 +16/09/2002,Birmingham,Aston Villa,3,0,H,1,0,H,D Elleray,12,8,5,1,9,6,18,13,3,2,0,0 +21/09/2002,Arsenal,Bolton,2,1,H,1,0,H,D Pugh,14,8,9,3,8,1,12,11,1,3,0,1 +21/09/2002,Liverpool,West Brom,2,0,H,0,0,D,D Elleray,23,5,14,2,9,2,8,11,2,2,0,1 +21/09/2002,Man United,Tottenham,1,0,H,0,0,D,R Styles,16,10,9,5,10,5,11,8,1,1,0,0 +21/09/2002,Middlesbrough,Birmingham,1,0,H,1,0,H,A D'Urso,9,12,4,3,6,3,22,13,3,4,0,0 +21/09/2002,Newcastle,Sunderland,2,0,H,2,0,H,M Riley,11,8,6,4,8,3,8,15,2,4,0,0 +21/09/2002,Southampton,Charlton,0,0,D,0,0,D,P Dowd,13,6,5,5,5,3,24,14,2,1,0,0 +21/09/2002,West Ham,Man City,0,0,D,0,0,D,G Barber,9,8,5,6,8,4,12,6,2,0,0,0 +22/09/2002,Aston Villa,Everton,3,2,H,1,0,H,J Winter,12,14,8,7,5,5,13,15,0,1,0,0 +22/09/2002,Blackburn,Leeds,1,0,H,1,0,H,G Poll,11,7,7,5,7,2,24,14,1,2,0,0 +23/09/2002,Fulham,Chelsea,0,0,D,0,0,D,P Durkin,8,11,3,4,5,3,16,15,1,0,0,0 +28/09/2002,Birmingham,Newcastle,0,2,A,0,1,A,S Bennett,11,13,5,6,5,10,18,9,3,0,0,0 +28/09/2002,Bolton,Southampton,1,1,D,0,0,D,U Rennie,19,10,10,6,9,7,5,16,1,0,0,0 +28/09/2002,Charlton,Man United,1,3,A,1,0,H,D Gallagher,7,13,3,7,3,6,12,8,2,3,0,0 +28/09/2002,Chelsea,West Ham,2,3,A,1,1,D,M Dean,12,16,9,11,5,7,17,7,1,4,0,0 +28/09/2002,Everton,Fulham,2,0,H,2,0,H,S Dunn,15,12,9,6,8,4,13,18,1,3,0,0 +28/09/2002,Leeds,Arsenal,1,4,A,0,2,A,A Wiley,9,7,3,6,4,4,14,15,4,2,0,0 +28/09/2002,Man City,Liverpool,0,3,A,0,1,A,P Durkin,14,9,6,6,12,3,7,9,0,0,0,0 +28/09/2002,Sunderland,Aston Villa,1,0,H,0,0,D,N Barry,8,12,6,5,5,12,20,10,3,3,0,0 +28/09/2002,Tottenham,Middlesbrough,0,3,A,0,1,A,G Poll,12,18,4,12,4,11,9,11,1,1,0,0 +30/09/2002,West Brom,Blackburn,0,2,A,0,0,D,M Halsey,9,10,8,5,7,2,14,15,1,1,0,0 +05/10/2002,Middlesbrough,Bolton,2,0,H,1,0,H,C Wilkes,13,11,2,4,5,5,9,10,0,3,0,0 +05/10/2002,Newcastle,West Brom,2,1,H,1,1,D,C Foy,14,5,8,3,11,6,11,9,1,2,0,0 +05/10/2002,Southampton,Man City,2,0,H,2,0,H,M Messias,19,11,13,4,8,5,15,9,2,2,1,0 +05/10/2002,West Ham,Birmingham,1,2,A,1,2,A,P Dowd,13,10,6,4,8,2,20,11,2,1,0,0 +06/10/2002,Arsenal,Sunderland,3,1,H,3,0,H,D Elleray,9,4,5,2,15,0,9,7,0,0,0,0 +06/10/2002,Aston Villa,Leeds,0,0,D,0,0,D,M Halsey,16,6,5,3,14,6,11,10,1,1,0,0 +06/10/2002,Blackburn,Tottenham,1,2,A,0,1,A,A D'Urso,12,9,7,6,6,3,17,11,1,0,0,0 +06/10/2002,Fulham,Charlton,1,0,H,1,0,H,J Winter,9,14,5,7,4,8,9,13,1,2,0,0 +06/10/2002,Liverpool,Chelsea,1,0,H,0,0,D,G Barber,12,8,8,0,3,1,10,13,0,2,0,0 +07/10/2002,Man United,Everton,3,0,H,0,0,D,M Riley,17,8,12,4,7,1,14,12,0,2,0,1 +19/10/2002,Blackburn,Newcastle,5,2,H,2,1,H,A Wiley,21,5,11,5,7,5,16,9,0,2,0,1 +19/10/2002,Everton,Arsenal,2,1,H,1,1,D,U Rennie,10,9,6,3,8,5,18,13,3,1,0,0 +19/10/2002,Fulham,Man United,1,1,D,1,0,H,M Dean,12,8,6,5,6,1,9,13,1,3,0,0 +19/10/2002,Leeds,Liverpool,0,1,A,0,0,D,S Dunn,12,13,5,6,9,9,13,13,1,2,0,0 +19/10/2002,Man City,Chelsea,0,3,A,0,0,D,D Gallagher,11,8,8,6,3,7,11,10,1,2,0,0 +19/10/2002,Sunderland,West Ham,0,1,A,0,1,A,G Barber,7,7,2,1,9,4,16,17,1,1,0,0 +19/10/2002,West Brom,Birmingham,1,1,D,0,0,D,G Poll,18,7,7,1,12,2,15,12,3,2,0,1 +20/10/2002,Charlton,Middlesbrough,1,0,H,1,0,H,N Barry,14,9,7,5,5,4,11,19,1,3,0,0 +20/10/2002,Tottenham,Bolton,3,1,H,0,0,D,P Durkin,6,5,3,1,7,3,8,11,0,1,0,0 +21/10/2002,Aston Villa,Southampton,0,1,A,0,0,D,S Bennett,18,13,11,7,7,4,13,11,3,1,1,0 +23/10/2002,Fulham,West Ham,0,1,A,0,0,D,R Styles,6,6,2,4,7,6,18,11,2,3,1,0 +26/10/2002,Arsenal,Blackburn,1,2,A,1,1,D,G Barber,19,3,9,2,15,3,11,14,1,3,0,1 +26/10/2002,Birmingham,Man City,0,2,A,0,1,A,S Bennett,9,8,4,4,6,1,10,16,2,2,0,0 +26/10/2002,Chelsea,West Brom,2,0,H,1,0,H,S Dunn,10,7,5,5,5,5,6,7,0,1,0,0 +26/10/2002,Liverpool,Tottenham,2,1,H,0,0,D,M Riley,9,10,5,8,5,7,11,19,2,4,0,0 +26/10/2002,Man United,Aston Villa,1,1,D,0,1,A,G Poll,15,9,6,3,8,5,7,16,1,0,0,0 +26/10/2002,Middlesbrough,Leeds,2,2,D,1,1,D,R Styles,16,7,7,7,11,2,17,17,3,2,1,1 +26/10/2002,Newcastle,Charlton,2,1,H,1,1,D,A D'Urso,16,6,12,5,14,5,12,14,2,1,0,0 +27/10/2002,Southampton,Fulham,4,2,H,2,2,D,M Halsey,13,5,8,4,3,5,12,19,1,2,0,0 +27/10/2002,West Ham,Everton,0,1,A,0,0,D,A Wiley,16,10,9,5,5,3,12,12,0,0,0,0 +28/10/2002,Bolton,Sunderland,1,1,D,0,1,A,E Wolstenholme,14,8,7,4,5,2,12,9,1,2,0,0 +02/11/2002,Birmingham,Bolton,3,1,H,0,0,D,C Foy,12,11,6,7,8,5,12,19,2,4,0,1 +02/11/2002,Liverpool,West Ham,2,0,H,1,0,H,E Wolstenholme,13,5,9,1,6,0,7,8,0,0,0,0 +02/11/2002,Man United,Southampton,2,1,H,1,1,D,U Rennie,16,12,8,5,10,2,12,18,0,1,0,0 +02/11/2002,West Brom,Man City,1,2,A,0,0,D,D Elleray,5,10,4,4,5,4,10,10,2,2,0,0 +03/11/2002,Blackburn,Aston Villa,0,0,D,0,0,D,M Dean,17,11,6,4,10,3,15,10,1,2,0,0 +03/11/2002,Charlton,Sunderland,1,1,D,0,1,A,D Gallagher,19,9,12,7,6,8,8,14,1,4,0,0 +03/11/2002,Fulham,Arsenal,0,1,A,0,1,A,J Winter,5,6,4,3,10,5,10,21,1,2,0,0 +03/11/2002,Leeds,Everton,0,1,A,0,0,D,N Barry,18,12,5,7,9,9,14,20,3,3,0,0 +03/11/2002,Tottenham,Chelsea,0,0,D,0,0,D,R Styles,15,14,7,4,8,2,17,12,3,2,0,0 +04/11/2002,Newcastle,Middlesbrough,2,0,H,1,0,H,G Barber,18,12,13,9,7,5,16,7,2,2,0,1 +09/11/2002,Arsenal,Newcastle,1,0,H,1,0,H,M Dean,16,3,10,2,11,0,11,12,0,2,0,0 +09/11/2002,Aston Villa,Fulham,3,1,H,1,0,H,A D'Urso,9,8,7,4,4,6,12,10,3,2,0,0 +09/11/2002,Bolton,West Brom,1,1,D,0,1,A,M Riley,15,5,8,1,7,4,14,13,2,2,1,0 +09/11/2002,Chelsea,Birmingham,3,0,H,3,0,H,D Pugh,16,7,10,2,7,3,7,6,1,1,0,0 +09/11/2002,Everton,Charlton,1,0,H,1,0,H,R Styles,11,5,2,1,7,3,14,13,2,3,0,0 +09/11/2002,Man City,Man United,3,1,H,2,1,H,P Durkin,11,9,6,6,0,9,12,16,1,2,0,0 +09/11/2002,Middlesbrough,Liverpool,1,0,H,0,0,D,M Halsey,11,13,3,7,5,2,10,8,1,0,0,0 +09/11/2002,Southampton,Blackburn,1,1,D,1,0,H,A Wiley,21,11,12,3,5,4,13,13,0,2,0,1 +10/11/2002,Sunderland,Tottenham,2,0,H,0,0,D,U Rennie,13,9,5,3,4,5,10,18,0,2,0,0 +10/11/2002,West Ham,Leeds,3,4,A,1,4,A,S Dunn,19,12,9,8,5,4,14,11,5,3,0,0 +16/11/2002,Arsenal,Tottenham,3,0,H,1,0,H,M Riley,17,6,12,5,3,1,15,10,0,2,0,1 +16/11/2002,Chelsea,Middlesbrough,1,0,H,0,0,D,P Dowd,13,7,5,4,5,7,9,11,2,1,0,0 +16/11/2002,Man City,Charlton,0,1,A,0,0,D,G Barber,15,13,4,4,7,5,10,16,0,4,0,0 +16/11/2002,Newcastle,Southampton,2,1,H,1,1,D,C Wilkes,18,8,13,3,6,2,7,18,1,3,0,0 +16/11/2002,West Brom,Aston Villa,0,0,D,0,0,D,D Gallagher,10,8,5,5,8,6,15,15,2,3,0,0 +17/11/2002,Birmingham,Fulham,0,0,D,0,0,D,M Messias,11,4,6,1,9,1,17,15,2,1,0,2 +17/11/2002,Blackburn,Everton,0,1,A,0,1,A,G Poll,16,9,6,6,12,4,10,19,3,1,0,0 +17/11/2002,Leeds,Bolton,2,4,A,1,1,D,A Wiley,12,14,3,7,2,4,19,9,2,0,0,0 +17/11/2002,Liverpool,Sunderland,0,0,D,0,0,D,A D'Urso,27,0,12,0,12,0,6,10,0,1,0,0 +17/11/2002,West Ham,Man United,1,1,D,0,1,A,M Halsey,3,11,1,4,10,6,13,14,1,1,0,0 +23/11/2002,Aston Villa,West Ham,4,1,H,1,0,H,C Foy,23,15,11,8,6,4,11,15,3,2,0,0 +23/11/2002,Bolton,Chelsea,1,1,D,0,0,D,M Dean,15,15,8,11,9,10,4,9,2,4,0,1 +23/11/2002,Everton,West Brom,1,0,H,1,0,H,J Winter,17,13,9,8,10,6,14,11,2,2,0,0 +23/11/2002,Fulham,Liverpool,3,2,H,2,0,H,G Poll,11,14,10,11,1,8,14,9,1,0,1,0 +23/11/2002,Man United,Newcastle,5,3,H,3,1,H,S Dunn,11,10,9,7,6,1,14,9,2,2,0,0 +23/11/2002,Middlesbrough,Man City,3,1,H,0,0,D,N Barry,18,9,7,5,6,6,5,12,0,3,0,1 +23/11/2002,Southampton,Arsenal,3,2,H,1,1,D,P Durkin,8,7,3,2,3,8,12,16,1,2,0,1 +23/11/2002,Sunderland,Birmingham,0,1,A,0,0,D,R Styles,7,11,2,9,5,7,18,17,3,3,0,0 +24/11/2002,Charlton,Blackburn,3,1,H,0,0,D,U Rennie,13,12,9,7,5,7,16,17,2,0,0,0 +24/11/2002,Tottenham,Leeds,2,0,H,2,0,H,S Bennett,11,8,6,4,3,1,18,16,3,7,0,0 +30/11/2002,Arsenal,Aston Villa,3,1,H,1,0,H,G Barber,13,12,7,5,6,9,11,20,2,3,0,0 +30/11/2002,Birmingham,Tottenham,1,1,D,0,0,D,E Wolstenholme,13,7,6,3,1,2,19,16,1,0,0,0 +30/11/2002,Blackburn,Fulham,2,1,H,1,0,H,P Dowd,8,9,4,7,6,6,19,16,1,2,0,0 +30/11/2002,Chelsea,Sunderland,3,0,H,0,0,D,S Bennett,15,1,11,0,9,5,7,12,0,3,0,0 +30/11/2002,Man City,Bolton,2,0,H,1,0,H,J Winter,13,3,10,3,11,2,10,10,0,2,0,0 +30/11/2002,West Brom,Middlesbrough,1,0,H,0,0,D,P Durkin,16,11,8,2,5,9,13,8,0,1,0,0 +01/12/2002,Leeds,Charlton,1,2,A,1,0,H,A D'Urso,18,14,6,10,5,11,14,15,1,1,0,0 +01/12/2002,Liverpool,Man United,1,2,A,0,0,D,A Wiley,13,5,7,4,5,2,13,11,1,4,0,0 +01/12/2002,Newcastle,Everton,2,1,H,0,1,A,M Halsey,24,7,15,5,11,2,13,18,0,1,0,1 +02/12/2002,West Ham,Southampton,0,1,A,0,0,D,M Riley,14,9,7,3,6,1,10,11,1,1,0,0 +07/12/2002,Aston Villa,Newcastle,0,1,A,0,0,D,S Bennett,12,11,4,5,6,5,9,10,2,0,0,0 +07/12/2002,Bolton,Blackburn,1,1,D,1,0,H,N Barry,18,13,7,7,3,11,16,10,2,2,0,0 +07/12/2002,Charlton,Liverpool,2,0,H,1,0,H,M Riley,14,17,11,7,4,5,14,12,3,3,0,0 +07/12/2002,Everton,Chelsea,1,3,A,1,2,A,E Wolstenholme,14,13,6,10,9,4,13,17,1,6,1,0 +07/12/2002,Fulham,Leeds,1,0,H,1,0,H,P Durkin,10,6,3,4,9,1,10,14,3,4,0,0 +07/12/2002,Man United,Arsenal,2,0,H,1,0,H,D Gallagher,8,12,6,5,6,7,16,12,1,2,0,0 +07/12/2002,Middlesbrough,West Ham,2,2,D,0,0,D,G Poll,12,14,5,8,5,7,13,13,1,1,0,0 +07/12/2002,Southampton,Birmingham,2,0,H,0,0,D,J Winter,13,6,8,2,6,9,7,6,0,2,0,0 +08/12/2002,Tottenham,West Brom,3,1,H,2,0,H,A Wiley,10,13,6,7,4,3,16,10,2,1,0,0 +09/12/2002,Sunderland,Man City,0,3,A,0,1,A,S Dunn,8,11,6,7,10,5,11,12,0,2,0,0 +14/12/2002,Aston Villa,West Brom,2,1,H,1,1,D,M Dean,23,13,12,3,8,2,20,14,3,1,1,0 +14/12/2002,Charlton,Man City,2,2,D,0,0,D,D Pugh,12,14,5,9,5,3,12,12,2,1,0,0 +14/12/2002,Everton,Blackburn,2,1,H,2,1,H,G Barber,12,17,7,9,5,10,14,16,1,2,0,1 +14/12/2002,Man United,West Ham,3,0,H,2,0,H,R Styles,14,10,10,3,7,4,12,11,0,1,0,0 +14/12/2002,Middlesbrough,Chelsea,1,1,D,1,1,D,M Messias,9,16,6,11,5,8,9,14,0,1,0,0 +14/12/2002,Southampton,Newcastle,1,1,D,0,0,D,P Dowd,16,17,9,11,8,8,12,11,0,0,0,0 +15/12/2002,Fulham,Birmingham,0,1,A,0,1,A,A D'Urso,7,2,0,1,7,1,11,22,3,7,0,1 +15/12/2002,Sunderland,Liverpool,2,1,H,1,0,H,M Halsey,7,11,3,4,9,10,12,7,0,2,0,0 +15/12/2002,Tottenham,Arsenal,1,1,D,1,1,D,N Barry,14,6,10,4,5,6,14,11,3,2,0,0 +16/12/2002,Bolton,Leeds,0,3,A,0,2,A,G Poll,17,9,9,7,5,3,9,14,1,3,0,0 +21/12/2002,Arsenal,Middlesbrough,2,0,H,1,0,H,S Dunn,26,3,14,1,13,3,8,14,1,3,0,1 +21/12/2002,Birmingham,Charlton,1,1,D,0,1,A,R Styles,8,8,5,5,5,5,15,21,4,4,1,0 +21/12/2002,Chelsea,Aston Villa,2,0,H,1,0,H,M Riley,16,12,8,4,6,4,9,18,0,3,0,0 +21/12/2002,Leeds,Southampton,1,1,D,0,0,D,C Foy,19,10,11,4,7,4,15,14,0,1,0,0 +21/12/2002,Newcastle,Fulham,2,0,H,1,0,H,A Wiley,19,6,14,0,7,6,5,10,0,2,0,1 +21/12/2002,West Brom,Sunderland,2,2,D,2,0,H,C Wilkes,9,11,5,7,8,10,22,15,2,1,0,0 +21/12/2002,West Ham,Bolton,1,1,D,1,0,H,S Bennett,13,6,6,3,7,4,13,9,0,1,0,0 +22/12/2002,Blackburn,Man United,1,0,H,1,0,H,D Elleray,9,12,3,6,7,8,8,10,0,1,0,0 +22/12/2002,Liverpool,Everton,0,0,D,0,0,D,G Poll,14,5,8,2,7,4,14,20,3,4,0,0 +23/12/2002,Man City,Tottenham,2,3,A,1,1,D,J Winter,19,9,18,9,7,9,6,7,2,3,0,1 +26/12/2002,Birmingham,Everton,1,1,D,1,1,D,D Elleray,6,8,3,5,0,4,16,21,1,1,0,1 +26/12/2002,Bolton,Newcastle,4,3,H,3,1,H,U Rennie,11,14,7,8,6,8,12,16,1,2,0,0 +26/12/2002,Chelsea,Southampton,0,0,D,0,0,D,P Durkin,11,10,4,6,6,5,13,12,1,2,0,0 +26/12/2002,Liverpool,Blackburn,1,1,D,1,0,H,N Barry,12,10,6,5,7,4,8,6,1,1,0,0 +26/12/2002,Man City,Aston Villa,3,1,H,1,1,D,M Halsey,13,11,10,5,7,8,10,11,0,1,0,0 +26/12/2002,Middlesbrough,Man United,3,1,H,1,0,H,G Barber,13,14,5,6,5,8,7,12,1,1,0,0 +26/12/2002,Sunderland,Leeds,1,2,A,1,0,H,M Dean,18,11,6,4,8,3,12,11,0,1,0,0 +26/12/2002,Tottenham,Charlton,2,2,D,0,1,A,A D'Urso,20,10,12,7,9,2,10,11,2,1,1,0 +26/12/2002,West Brom,Arsenal,1,2,A,1,0,H,G Poll,6,11,2,4,5,7,20,8,1,0,0,0 +26/12/2002,West Ham,Fulham,1,1,D,0,0,D,D Gallagher,11,11,5,5,7,1,16,11,2,1,1,0 +28/12/2002,Aston Villa,Middlesbrough,1,0,H,1,0,H,R Styles,11,12,9,4,3,8,14,7,3,2,0,1 +28/12/2002,Blackburn,West Ham,2,2,D,1,1,D,A Wiley,11,7,3,1,4,1,11,12,3,2,0,0 +28/12/2002,Charlton,West Brom,1,0,H,1,0,H,S Dunn,8,18,5,8,6,10,13,20,2,3,0,0 +28/12/2002,Everton,Bolton,0,0,D,0,0,D,D Gallagher,15,3,9,1,8,4,11,13,1,4,0,0 +28/12/2002,Fulham,Man City,0,1,A,0,0,D,P Durkin,15,13,7,8,4,13,7,14,0,1,0,0 +28/12/2002,Leeds,Chelsea,2,0,H,2,0,H,G Barber,6,17,4,4,3,7,20,16,2,0,0,0 +28/12/2002,Man United,Birmingham,2,0,H,1,0,H,M Dean,22,7,10,2,13,4,12,9,1,2,0,0 +28/12/2002,Southampton,Sunderland,2,1,H,0,0,D,M Riley,18,4,6,1,17,3,7,18,1,2,0,0 +29/12/2002,Arsenal,Liverpool,1,1,D,0,0,D,J Winter,13,9,6,2,15,4,14,15,1,3,0,0 +29/12/2002,Newcastle,Tottenham,2,1,H,1,0,H,S Bennett,14,9,10,6,7,6,9,12,1,2,0,0 +01/01/2003,Arsenal,Chelsea,3,2,H,1,0,H,U Rennie,7,8,7,7,2,7,13,9,0,0,0,0 +01/01/2003,Aston Villa,Bolton,2,0,H,1,0,H,M Messias,12,9,7,4,9,8,14,4,2,0,0,0 +01/01/2003,Blackburn,Middlesbrough,1,0,H,0,0,D,D Pugh,17,7,10,3,5,2,15,8,0,0,0,0 +01/01/2003,Everton,Man City,2,2,D,1,1,D,A D'Urso,12,9,5,2,4,1,13,19,1,2,0,0 +01/01/2003,Leeds,Birmingham,2,0,H,1,0,H,P Dowd,12,9,6,5,5,8,14,9,1,2,0,0 +01/01/2003,Man United,Sunderland,2,1,H,0,1,A,G Poll,31,4,18,1,10,2,12,11,2,1,0,0 +01/01/2003,Newcastle,Liverpool,1,0,H,1,0,H,D Gallagher,8,5,5,4,4,4,6,20,1,3,0,1 +01/01/2003,Southampton,Tottenham,1,0,H,0,0,D,M Halsey,12,11,6,7,5,5,15,5,0,0,0,0 +11/01/2003,Bolton,Fulham,0,0,D,0,0,D,A Wiley,17,13,5,8,7,4,15,16,1,2,0,0 +11/01/2003,Chelsea,Charlton,4,1,H,3,1,H,M Dean,15,6,7,5,8,4,9,13,4,3,0,0 +11/01/2003,Liverpool,Aston Villa,1,1,D,1,0,H,P Durkin,19,8,11,3,9,2,11,18,1,2,0,0 +11/01/2003,Man City,Leeds,2,1,H,1,0,H,R Styles,11,4,5,3,8,8,9,12,0,4,0,0 +11/01/2003,Middlesbrough,Southampton,2,2,D,0,1,A,D Elleray,12,17,4,8,7,4,7,10,0,2,0,0 +11/01/2003,Sunderland,Blackburn,0,0,D,0,0,D,M Riley,9,12,4,4,3,4,18,12,1,3,0,0 +11/01/2003,West Brom,Man United,1,3,A,1,2,A,N Barry,9,9,3,4,2,6,10,7,0,1,0,0 +11/01/2003,West Ham,Newcastle,2,2,D,2,1,H,J Winter,13,12,8,7,5,3,17,18,0,1,0,0 +12/01/2003,Birmingham,Arsenal,0,4,A,0,2,A,S Bennett,10,12,4,9,7,5,13,17,2,2,0,0 +12/01/2003,Tottenham,Everton,4,3,H,1,1,D,S Dunn,18,13,12,10,9,9,12,10,1,1,0,0 +18/01/2003,Aston Villa,Tottenham,0,1,A,0,0,D,N Barry,11,10,5,6,11,5,9,11,1,3,0,0 +18/01/2003,Blackburn,Birmingham,1,1,D,1,0,H,C Wilkes,14,15,10,7,4,11,16,15,2,3,1,0 +18/01/2003,Charlton,Bolton,1,1,D,0,0,D,D Elleray,10,10,5,5,7,7,11,12,0,0,0,0 +18/01/2003,Everton,Sunderland,2,1,H,0,1,A,P Dowd,15,7,9,5,13,8,14,19,2,3,0,0 +18/01/2003,Leeds,West Brom,0,0,D,0,0,D,U Rennie,10,8,4,1,6,5,11,12,1,1,0,1 +18/01/2003,Man United,Chelsea,2,1,H,1,1,D,P Durkin,10,10,8,5,6,6,9,9,0,0,0,0 +18/01/2003,Newcastle,Man City,2,0,H,1,0,H,G Poll,13,6,10,2,7,1,13,13,0,1,0,0 +18/01/2003,Southampton,Liverpool,0,1,A,0,1,A,S Bennett,8,17,4,7,6,4,8,10,1,1,0,0 +19/01/2003,Arsenal,West Ham,3,1,H,1,1,D,M Dean,24,5,13,2,12,1,17,9,3,1,0,1 +19/01/2003,Fulham,Middlesbrough,1,0,H,1,0,H,M Halsey,18,8,6,4,8,4,12,12,0,1,0,0 +22/01/2003,Charlton,West Ham,4,2,H,2,1,H,E Wolstenholme,12,11,4,3,8,2,11,7,0,1,0,0 +22/01/2003,Newcastle,Bolton,1,0,H,1,0,H,P Dowd,10,12,7,4,7,3,15,6,1,2,0,0 +28/01/2003,Bolton,Everton,1,2,A,0,2,A,R Styles,7,6,5,4,5,6,8,15,2,1,0,0 +28/01/2003,Chelsea,Leeds,3,2,H,0,1,A,J Winter,13,5,7,5,7,3,6,6,2,1,0,0 +28/01/2003,Middlesbrough,Aston Villa,2,5,A,2,2,D,D Gallagher,13,15,8,9,6,3,13,11,1,2,0,0 +28/01/2003,Sunderland,Southampton,0,1,A,0,0,D,N Barry,10,12,4,6,5,2,9,13,1,0,0,0 +29/01/2003,Liverpool,Arsenal,2,2,D,0,1,A,M Halsey,16,20,12,15,9,10,14,11,1,1,0,0 +29/01/2003,Man City,Fulham,4,1,H,1,1,D,S Bennett,11,6,4,2,9,3,10,10,3,1,0,0 +29/01/2003,Tottenham,Newcastle,0,1,A,0,0,D,D Elleray,13,15,7,8,5,4,14,9,0,1,0,0 +29/01/2003,West Brom,Charlton,0,1,A,0,0,D,A D'Urso,15,3,6,2,6,6,12,14,0,2,0,0 +29/01/2003,West Ham,Blackburn,2,1,H,0,1,A,A Wiley,9,4,7,1,5,4,16,14,1,1,0,0 +01/02/2003,Arsenal,Fulham,2,1,H,1,1,D,E Wolstenholme,11,3,6,1,6,4,10,16,3,1,0,0 +01/02/2003,Bolton,Birmingham,4,2,H,1,1,D,D Gallagher,15,11,10,6,7,4,16,10,2,2,0,0 +01/02/2003,Chelsea,Tottenham,1,1,D,1,1,D,P Durkin,16,8,8,5,11,3,7,15,1,1,0,0 +01/02/2003,Everton,Leeds,2,0,H,0,0,D,M Halsey,13,8,5,2,6,4,6,16,0,1,0,0 +01/02/2003,Man City,West Brom,1,2,A,1,1,D,N Barry,12,9,7,5,6,6,10,17,1,2,0,1 +01/02/2003,Southampton,Man United,0,2,A,0,2,A,P Dowd,9,13,4,8,7,6,15,13,2,0,0,0 +01/02/2003,Sunderland,Charlton,1,3,A,0,3,A,A Wiley,13,14,5,4,8,3,21,12,1,1,0,0 +02/02/2003,Aston Villa,Blackburn,3,0,H,2,0,H,M Dean,16,7,9,1,9,2,8,21,1,5,0,0 +02/02/2003,West Ham,Liverpool,0,3,A,0,2,A,M Messias,7,11,2,5,6,8,12,12,1,0,0,0 +04/02/2003,Birmingham,Man United,0,1,A,0,0,D,S Dunn,5,15,3,7,3,5,9,14,1,1,0,0 +08/02/2003,Birmingham,Chelsea,1,3,A,0,1,A,M Halsey,7,3,6,3,7,2,13,14,1,4,0,0 +08/02/2003,Blackburn,Southampton,1,0,H,1,0,H,M Riley,18,11,11,5,6,6,9,11,0,1,0,0 +08/02/2003,Charlton,Everton,2,1,H,1,0,H,J Winter,19,14,10,6,4,10,15,10,0,1,0,0 +08/02/2003,Fulham,Aston Villa,2,1,H,2,1,H,S Dunn,17,8,10,5,6,5,11,14,1,3,0,0 +08/02/2003,Leeds,West Ham,1,0,H,1,0,H,D Gallagher,10,12,7,3,9,6,13,13,2,2,0,1 +08/02/2003,Liverpool,Middlesbrough,1,1,D,0,1,A,S Bennett,17,2,10,2,14,3,16,12,0,2,0,0 +08/02/2003,Tottenham,Sunderland,4,1,H,2,1,H,R Styles,16,8,7,6,10,8,12,11,0,2,0,0 +08/02/2003,West Brom,Bolton,1,1,D,0,1,A,D Elleray,15,7,8,5,5,4,8,12,2,4,0,0 +09/02/2003,Man United,Man City,1,1,D,1,0,H,A Wiley,15,9,8,6,10,4,8,10,1,1,0,0 +09/02/2003,Newcastle,Arsenal,1,1,D,0,1,A,N Barry,6,18,4,10,6,7,11,10,2,2,1,0 +19/02/2003,Fulham,West Brom,3,0,H,0,0,D,U Rennie,12,9,8,4,2,3,13,18,1,2,0,0 +22/02/2003,Bolton,Man United,1,1,D,0,0,D,A D'Urso,13,7,5,3,8,5,13,10,1,1,0,0 +22/02/2003,Charlton,Aston Villa,3,0,H,0,0,D,P Dowd,14,13,9,6,6,6,17,20,2,2,0,0 +22/02/2003,Chelsea,Blackburn,1,2,A,0,0,D,G Poll,16,6,9,5,5,2,11,12,2,1,0,0 +22/02/2003,Everton,Southampton,2,1,H,0,1,A,D Elleray,25,11,16,6,17,6,3,13,0,1,0,0 +22/02/2003,Leeds,Newcastle,0,3,A,0,1,A,A Wiley,12,13,5,6,1,7,14,19,0,0,0,0 +22/02/2003,Man City,Arsenal,1,5,A,0,4,A,P Durkin,18,9,12,9,14,4,7,7,0,0,0,0 +22/02/2003,Sunderland,Middlesbrough,1,3,A,0,2,A,M Halsey,7,9,4,5,4,6,18,16,2,1,0,0 +23/02/2003,Birmingham,Liverpool,2,1,H,1,0,H,C Wilkes,5,14,2,9,2,6,14,11,2,1,0,0 +23/02/2003,West Brom,West Ham,1,2,A,0,1,A,M Dean,10,7,5,2,7,4,19,12,1,1,0,0 +24/02/2003,Tottenham,Fulham,1,1,D,1,1,D,G Barber,15,8,10,7,5,7,17,13,2,2,1,1 +01/03/2003,Blackburn,Man City,1,0,H,1,0,H,S Dunn,10,12,7,6,2,4,9,15,2,0,0,0 +01/03/2003,Fulham,Sunderland,1,0,H,0,0,D,E Wolstenholme,11,15,4,6,8,11,18,13,1,2,0,0 +01/03/2003,Middlesbrough,Everton,1,1,D,0,1,A,U Rennie,6,11,2,5,4,2,14,8,0,0,0,0 +01/03/2003,Newcastle,Chelsea,2,1,H,1,1,D,J Winter,5,8,2,5,5,7,12,22,0,1,0,0 +01/03/2003,Southampton,West Brom,1,0,H,1,0,H,D Gallagher,15,12,11,3,9,3,21,20,2,2,0,0 +01/03/2003,West Ham,Tottenham,2,0,H,1,0,H,N Barry,10,9,8,3,8,3,14,9,3,2,0,0 +02/03/2003,Arsenal,Charlton,2,0,H,2,0,H,R Styles,19,6,11,4,12,6,12,12,1,0,0,0 +03/03/2003,Aston Villa,Birmingham,0,2,A,0,0,D,M Halsey,11,5,4,3,4,3,20,17,1,2,2,0 +05/03/2003,Man United,Leeds,2,1,H,1,0,H,G Poll,15,7,2,6,8,3,10,16,1,1,0,0 +05/03/2003,Middlesbrough,Newcastle,1,0,H,0,0,D,A D'Urso,10,11,4,0,5,9,13,16,0,3,0,0 +08/03/2003,Liverpool,Bolton,2,0,H,1,0,H,G Barber,9,10,3,2,3,3,13,11,0,3,0,0 +15/03/2003,Aston Villa,Man United,0,1,A,0,1,A,M Dean,19,13,6,5,6,6,10,10,2,1,0,0 +15/03/2003,Blackburn,Arsenal,2,0,H,1,0,H,S Bennett,10,9,4,5,3,5,11,15,4,2,0,0 +15/03/2003,Charlton,Newcastle,0,2,A,0,1,A,S Dunn,7,11,5,5,6,4,10,9,1,2,0,0 +15/03/2003,Everton,West Ham,0,0,D,0,0,D,M Halsey,11,3,8,2,5,5,14,12,2,0,0,0 +15/03/2003,Fulham,Southampton,2,2,D,1,0,H,P Durkin,3,12,3,5,3,7,8,11,1,0,0,0 +15/03/2003,Leeds,Middlesbrough,2,3,A,1,2,A,D Elleray,14,7,8,3,6,3,16,11,3,2,0,0 +15/03/2003,Sunderland,Bolton,0,2,A,0,0,D,J Winter,12,13,7,8,8,6,15,12,1,1,0,0 +16/03/2003,Man City,Birmingham,1,0,H,0,0,D,M Messias,14,7,9,4,7,6,17,7,2,1,1,0 +16/03/2003,Tottenham,Liverpool,2,3,A,0,0,D,U Rennie,12,9,5,6,9,4,21,11,1,1,0,0 +16/03/2003,West Brom,Chelsea,0,2,A,0,1,A,A D'Urso,5,11,1,8,2,4,10,8,0,0,0,0 +22/03/2003,Birmingham,West Brom,1,0,H,0,0,D,P Durkin,13,9,6,3,8,3,10,22,0,0,0,0 +22/03/2003,Chelsea,Man City,5,0,H,2,0,H,P Dowd,16,8,10,4,7,6,11,13,1,2,0,1 +22/03/2003,Man United,Fulham,3,0,H,1,0,H,S Bennett,18,11,11,6,9,5,11,12,2,1,0,0 +22/03/2003,Middlesbrough,Charlton,1,1,D,0,1,A,M Dean,15,6,6,4,8,4,9,10,1,2,0,0 +22/03/2003,Newcastle,Blackburn,5,1,H,1,0,H,N Barry,14,11,8,8,8,3,12,11,1,1,0,0 +22/03/2003,Southampton,Aston Villa,2,2,D,1,2,A,G Barber,11,13,7,9,4,3,8,17,0,1,0,0 +22/03/2003,West Ham,Sunderland,2,0,H,1,0,H,R Styles,10,4,4,2,4,1,16,12,1,0,0,0 +23/03/2003,Arsenal,Everton,2,1,H,1,0,H,A Wiley,14,6,10,3,3,5,15,19,2,1,0,0 +23/03/2003,Liverpool,Leeds,3,1,H,2,1,H,A D'Urso,19,11,11,6,4,6,12,17,0,1,0,0 +24/03/2003,Bolton,Tottenham,1,0,H,0,0,D,G Poll,15,7,8,2,4,9,8,9,0,1,0,0 +05/04/2003,Aston Villa,Arsenal,1,1,D,0,0,D,U Rennie,6,12,2,5,5,6,19,12,1,2,0,0 +05/04/2003,Bolton,Man City,2,0,H,1,0,H,C Wilkes,5,5,3,2,5,7,6,10,0,5,0,0 +05/04/2003,Charlton,Leeds,1,6,A,1,3,A,E Wolstenholme,6,14,4,10,7,8,14,17,2,1,0,0 +05/04/2003,Man United,Liverpool,4,0,H,1,0,H,M Riley,12,5,9,1,10,4,15,17,1,3,0,1 +05/04/2003,Middlesbrough,West Brom,3,0,H,1,0,H,M Halsey,22,12,12,6,3,5,9,15,0,1,0,0 +05/04/2003,Southampton,West Ham,1,1,D,1,0,H,M Messias,11,14,5,8,3,8,17,11,3,2,0,0 +05/04/2003,Sunderland,Chelsea,1,2,A,1,0,H,D Gallagher,9,13,4,10,3,8,17,11,1,1,0,0 +05/04/2003,Tottenham,Birmingham,2,1,H,1,0,H,D Elleray,12,10,6,4,10,5,20,16,1,1,0,0 +06/04/2003,Everton,Newcastle,2,1,H,1,1,D,N Barry,9,15,7,8,9,5,12,13,1,2,0,0 +07/04/2003,Fulham,Blackburn,0,4,A,0,2,A,G Poll,9,7,4,3,3,4,9,19,0,2,0,0 +12/04/2003,Birmingham,Sunderland,2,0,H,1,0,H,P Dowd,7,2,3,0,6,6,22,19,0,1,0,1 +12/04/2003,Blackburn,Charlton,1,0,H,1,0,H,J Winter,11,7,6,5,9,11,10,14,0,0,0,0 +12/04/2003,Chelsea,Bolton,1,0,H,0,0,D,G Barber,17,7,8,2,3,1,9,10,1,2,0,0 +12/04/2003,Leeds,Tottenham,2,2,D,1,2,A,R Styles,11,6,4,2,7,4,13,10,1,2,0,0 +12/04/2003,Liverpool,Fulham,2,0,H,1,0,H,A Wiley,17,11,11,7,5,6,9,17,1,2,0,0 +12/04/2003,Man City,Middlesbrough,0,0,D,0,0,D,A D'Urso,9,6,6,1,1,4,12,7,0,1,0,0 +12/04/2003,Newcastle,Man United,2,6,A,1,4,A,S Dunn,15,15,9,11,6,6,8,7,1,0,0,0 +12/04/2003,West Brom,Everton,1,2,A,1,2,A,S Bennett,12,8,6,3,3,10,9,5,2,2,0,0 +12/04/2003,West Ham,Aston Villa,2,2,D,1,1,D,M Dean,15,6,7,3,10,7,14,12,4,1,0,0 +16/04/2003,Arsenal,Man United,2,2,D,0,1,A,M Halsey,10,6,7,3,6,2,15,15,0,2,1,0 +18/04/2003,Tottenham,Man City,0,2,A,0,2,A,M Riley,9,11,6,6,7,9,11,15,2,3,0,0 +19/04/2003,Aston Villa,Chelsea,2,1,H,1,0,H,R Styles,19,16,10,10,6,2,11,12,1,0,0,0 +19/04/2003,Bolton,West Ham,1,0,H,1,0,H,U Rennie,19,9,9,3,8,3,18,9,3,4,0,1 +19/04/2003,Charlton,Birmingham,0,2,A,0,1,A,N Barry,7,13,5,7,8,5,16,12,2,1,0,0 +19/04/2003,Everton,Liverpool,1,2,A,0,1,A,P Durkin,9,8,4,2,4,3,15,18,4,3,2,0 +19/04/2003,Fulham,Newcastle,2,1,H,0,1,A,D Gallagher,13,8,6,6,6,13,12,9,1,1,0,1 +19/04/2003,Man United,Blackburn,3,1,H,2,1,H,A D'Urso,18,6,12,1,7,3,8,9,0,0,0,0 +19/04/2003,Middlesbrough,Arsenal,0,2,A,0,0,D,D Elleray,7,6,3,2,3,5,16,12,0,1,0,0 +19/04/2003,Southampton,Leeds,3,2,H,2,0,H,M Halsey,16,5,7,3,11,1,8,22,0,4,0,1 +19/04/2003,Sunderland,West Brom,1,2,A,0,2,A,G Poll,20,9,13,7,10,5,19,12,4,1,0,0 +21/04/2003,Birmingham,Southampton,3,2,H,0,1,A,S Bennett,15,12,9,7,5,3,10,19,0,2,0,0 +21/04/2003,Blackburn,Bolton,0,0,D,0,0,D,M Dean,11,6,5,3,9,4,14,13,2,3,0,0 +21/04/2003,Chelsea,Everton,4,1,H,1,0,H,M Riley,13,3,9,1,4,0,11,15,1,1,0,0 +21/04/2003,Liverpool,Charlton,2,1,H,0,0,D,S Dunn,20,5,10,5,8,9,4,14,0,1,0,0 +21/04/2003,Man City,Sunderland,3,0,H,2,0,H,G Barber,15,4,8,3,7,3,12,11,0,1,0,0 +21/04/2003,Newcastle,Aston Villa,1,1,D,1,0,H,J Winter,16,10,4,7,8,5,6,13,0,1,0,0 +21/04/2003,West Brom,Tottenham,2,3,A,1,1,D,P Dowd,8,8,8,5,5,8,9,10,2,2,0,0 +21/04/2003,West Ham,Middlesbrough,1,0,H,0,0,D,A Wiley,13,6,9,2,4,6,15,18,1,2,0,0 +22/04/2003,Leeds,Fulham,2,0,H,1,0,H,N Barry,19,7,7,2,8,4,18,17,0,2,0,0 +26/04/2003,Birmingham,Middlesbrough,3,0,H,2,0,H,M Dean,7,6,6,4,3,3,17,18,1,2,0,0 +26/04/2003,Bolton,Arsenal,2,2,D,0,0,D,A D'Urso,17,8,11,7,10,4,15,16,2,2,1,0 +26/04/2003,Charlton,Southampton,2,1,H,1,0,H,A Wiley,14,14,10,6,7,10,15,12,1,1,0,0 +26/04/2003,Chelsea,Fulham,1,1,D,1,0,H,D Elleray,11,7,5,5,9,2,9,11,3,2,0,0 +26/04/2003,Everton,Aston Villa,2,1,H,0,0,D,G Poll,11,15,8,10,8,6,10,13,0,1,0,0 +26/04/2003,Leeds,Blackburn,2,3,A,1,1,D,P Durkin,15,10,9,5,11,6,12,18,2,1,0,0 +26/04/2003,Sunderland,Newcastle,0,1,A,0,1,A,S Bennett,12,18,8,9,6,8,20,18,4,4,0,0 +26/04/2003,West Brom,Liverpool,0,6,A,0,1,A,D Gallagher,9,18,5,14,4,3,12,6,1,1,0,0 +27/04/2003,Man City,West Ham,0,1,A,0,0,D,R Styles,16,11,5,7,11,6,10,11,1,1,0,0 +27/04/2003,Tottenham,Man United,0,2,A,0,0,D,J Winter,6,20,3,15,5,8,11,11,0,0,0,0 +03/05/2003,Aston Villa,Sunderland,1,0,H,0,0,D,S Dunn,11,11,4,7,12,6,11,13,0,1,0,0 +03/05/2003,Blackburn,West Brom,1,1,D,1,0,H,R Styles,13,13,7,7,7,2,14,16,2,1,0,0 +03/05/2003,Fulham,Everton,2,0,H,2,0,H,G Barber,13,16,6,8,6,9,14,13,1,1,0,0 +03/05/2003,Liverpool,Man City,1,2,A,0,0,D,N Barry,8,6,6,3,8,4,9,17,2,2,0,0 +03/05/2003,Man United,Charlton,4,1,H,3,1,H,M Halsey,21,4,14,2,11,3,6,10,0,1,0,0 +03/05/2003,Middlesbrough,Tottenham,5,1,H,3,0,H,U Rennie,26,6,16,4,8,3,15,9,0,2,0,1 +03/05/2003,Newcastle,Birmingham,1,0,H,1,0,H,D Elleray,18,4,9,2,8,4,14,12,0,2,0,1 +03/05/2003,Southampton,Bolton,0,0,D,0,0,D,P Dowd,10,10,4,2,7,3,19,9,3,2,0,0 +03/05/2003,West Ham,Chelsea,1,0,H,0,0,D,A D'Urso,11,8,7,3,10,8,12,15,3,1,0,0 +04/05/2003,Arsenal,Leeds,2,3,A,1,1,D,A Wiley,33,11,17,6,6,2,16,16,2,1,0,0 +07/05/2003,Arsenal,Southampton,6,1,H,5,1,H,U Rennie,16,13,13,5,6,10,12,16,1,1,0,0 +11/05/2003,Birmingham,West Ham,2,2,D,0,0,D,G Poll,10,16,7,10,6,7,18,11,1,1,0,0 +11/05/2003,Bolton,Middlesbrough,2,1,H,2,0,H,R Styles,13,6,6,3,6,5,18,18,3,2,0,1 +11/05/2003,Charlton,Fulham,0,1,A,0,1,A,D Gallagher,10,17,4,7,2,4,13,14,2,3,1,0 +11/05/2003,Chelsea,Liverpool,2,1,H,2,1,H,A Wiley,13,5,6,3,4,3,17,13,3,1,0,1 +11/05/2003,Everton,Man United,1,2,A,1,1,D,M Riley,8,17,4,12,6,6,16,15,5,3,0,0 +11/05/2003,Leeds,Aston Villa,3,1,H,1,1,D,M Halsey,11,18,5,6,1,8,18,14,0,1,0,0 +11/05/2003,Man City,Southampton,0,1,A,0,1,A,M Dean,17,6,7,3,10,4,14,14,3,4,0,0 +11/05/2003,Sunderland,Arsenal,0,4,A,0,2,A,P Durkin,7,20,3,13,6,10,16,12,1,0,0,0 +11/05/2003,Tottenham,Blackburn,0,4,A,0,2,A,A D'Urso,11,13,7,9,6,3,9,12,1,1,1,0 +11/05/2003,West Brom,Newcastle,2,2,D,0,1,A,G Barber,8,14,3,9,2,8,9,8,0,0,0,0 +16/08/2003,Arsenal,Everton,2,1,H,1,0,H,M Halsey,11,13,5,7,6,9,8,15,1,3,1,1 +16/08/2003,Birmingham,Tottenham,1,0,H,1,0,H,R Styles,10,15,5,7,1,4,20,27,3,5,0,0 +16/08/2003,Blackburn,Wolves,5,1,H,2,0,H,J Winter,25,8,13,5,6,2,8,14,1,1,0,0 +16/08/2003,Fulham,Middlesbrough,3,2,H,1,1,D,G Poll,17,8,9,5,7,6,18,16,1,1,0,0 +16/08/2003,Leicester,Southampton,2,2,D,2,0,H,M Riley,12,13,7,10,2,7,27,15,3,1,0,0 +16/08/2003,Man United,Bolton,4,0,H,1,0,H,P Durkin,13,15,6,5,8,4,12,8,0,4,0,0 +16/08/2003,Portsmouth,Aston Villa,2,1,H,1,0,H,G Barber,4,9,3,5,7,9,18,22,2,1,0,1 +17/08/2003,Charlton,Man City,0,3,A,0,2,A,M Dean,10,14,5,8,4,7,12,16,2,1,1,0 +17/08/2003,Leeds,Newcastle,2,2,D,1,1,D,R Martin,8,19,3,10,4,8,19,16,4,2,0,0 +17/08/2003,Liverpool,Chelsea,1,2,A,0,1,A,S Bennett,12,10,7,6,8,6,13,12,3,1,0,0 +23/08/2003,Bolton,Blackburn,2,2,D,2,0,H,A D'Urso,16,10,10,5,9,2,11,15,2,4,0,1 +23/08/2003,Chelsea,Leicester,2,1,H,2,1,H,R Styles,17,5,7,1,7,2,12,18,0,3,1,2 +23/08/2003,Everton,Fulham,3,1,H,3,0,H,N Barry,10,23,8,15,4,10,15,14,2,4,0,0 +23/08/2003,Man City,Portsmouth,1,1,D,0,1,A,M Messias,21,4,11,2,11,7,8,22,2,3,0,0 +23/08/2003,Newcastle,Man United,1,2,A,1,0,H,U Rennie,7,11,5,8,2,4,18,14,2,2,0,0 +23/08/2003,Southampton,Birmingham,0,0,D,0,0,D,Graham Barber,14,12,7,5,2,7,15,19,3,1,0,0 +23/08/2003,Tottenham,Leeds,2,1,H,1,1,D,S Dunn,20,5,4,2,6,0,14,14,1,2,0,0 +23/08/2003,Wolves,Charlton,0,4,A,0,4,A,P Dowd,10,8,4,6,5,2,11,7,2,1,0,1 +24/08/2003,Aston Villa,Liverpool,0,0,D,0,0,D,P Durkin,14,15,9,9,8,9,16,7,0,1,0,0 +24/08/2003,Middlesbrough,Arsenal,0,4,A,0,3,A,D Gallagher,10,14,4,10,3,5,11,7,1,0,0,0 +25/08/2003,Blackburn,Man City,2,3,A,1,1,D,A Wiley,14,13,10,5,9,0,14,10,2,1,0,0 +26/08/2003,Charlton,Everton,2,2,D,1,1,D,S Dunn,10,13,4,4,6,8,8,8,1,1,0,0 +26/08/2003,Leeds,Southampton,0,0,D,0,0,D,P Durkin,15,14,6,8,6,10,10,11,2,3,0,0 +26/08/2003,Leicester,Middlesbrough,0,0,D,0,0,D,G Barber,12,13,4,3,6,4,17,16,3,2,0,0 +26/08/2003,Portsmouth,Bolton,4,0,H,0,0,D,D Gallagher,13,13,8,4,0,4,12,14,2,4,0,0 +27/08/2003,Arsenal,Aston Villa,2,0,H,0,0,D,M Dean,14,6,6,3,2,6,16,16,3,4,0,0 +27/08/2003,Liverpool,Tottenham,0,0,D,0,0,D,U Rennie,22,9,10,4,8,5,20,27,2,5,0,0 +27/08/2003,Man United,Wolves,1,0,H,0,0,D,G Poll,14,12,5,2,10,2,6,12,2,2,0,0 +30/08/2003,Aston Villa,Leicester,3,1,H,3,0,H,S Bennett,10,5,8,4,11,1,18,17,1,2,0,1 +30/08/2003,Bolton,Charlton,0,0,D,0,0,D,A Wiley,13,7,9,1,12,3,18,16,4,2,0,0 +30/08/2003,Chelsea,Blackburn,2,2,D,1,1,D,M Dean,12,7,5,4,3,4,11,19,0,4,0,0 +30/08/2003,Everton,Liverpool,0,3,A,0,1,A,M Riley,11,18,5,11,9,5,22,17,3,3,0,0 +30/08/2003,Middlesbrough,Leeds,2,3,A,0,1,A,N Barry,27,8,16,4,6,3,17,21,2,3,0,0 +30/08/2003,Newcastle,Birmingham,0,1,A,0,0,D,M Messias,19,7,10,3,9,6,15,15,2,1,0,0 +30/08/2003,Tottenham,Fulham,0,3,A,0,1,A,J Winter,18,9,6,5,9,4,15,13,0,2,0,0 +30/08/2003,Wolves,Portsmouth,0,0,D,0,0,D,A D'Urso,11,9,2,2,4,6,15,16,3,5,0,0 +31/08/2003,Man City,Arsenal,1,2,A,1,0,H,G Poll,11,10,7,5,5,5,12,11,4,3,0,0 +31/08/2003,Southampton,Man United,1,0,H,0,0,D,M Halsey,12,10,5,9,9,5,8,10,1,0,0,0 +13/09/2003,Arsenal,Portsmouth,1,1,D,1,1,D,A Wiley,12,8,7,2,7,1,11,19,2,3,0,0 +13/09/2003,Blackburn,Liverpool,1,3,A,1,1,D,N Barry,8,21,5,14,5,10,13,11,2,2,1,0 +13/09/2003,Bolton,Middlesbrough,2,0,H,1,0,H,P Dowd,13,7,8,6,10,1,17,14,3,3,0,0 +13/09/2003,Charlton,Man United,0,2,A,0,0,D,M Riley,8,14,3,9,3,3,19,18,2,2,1,0 +13/09/2003,Chelsea,Tottenham,4,2,H,2,1,H,G Poll,12,9,6,3,10,3,16,20,1,4,0,0 +13/09/2003,Everton,Newcastle,2,2,D,0,0,D,R Styles,9,4,7,2,6,3,20,21,5,5,1,1 +13/09/2003,Southampton,Wolves,2,0,H,1,0,H,U Rennie,15,10,10,3,7,3,16,20,3,5,0,0 +14/09/2003,Birmingham,Fulham,2,2,D,1,1,D,S Dunn,15,8,9,6,8,1,16,21,1,4,1,1 +14/09/2003,Man City,Aston Villa,4,1,H,0,1,A,M Halsey,17,8,9,4,4,3,11,11,0,1,0,0 +15/09/2003,Leicester,Leeds,4,0,H,2,0,H,J Winter,19,14,11,2,9,5,10,10,0,2,0,0 +20/09/2003,Aston Villa,Charlton,2,1,H,1,0,H,C Foy,15,8,6,3,10,2,13,13,3,2,0,0 +20/09/2003,Fulham,Man City,2,2,D,0,0,D,P Dowd,13,7,9,6,2,4,14,19,2,5,0,0 +20/09/2003,Leeds,Birmingham,0,2,A,0,0,D,D Gallagher,12,7,6,3,6,1,15,14,4,2,1,0 +20/09/2003,Liverpool,Leicester,2,1,H,1,0,H,M Halsey,19,9,8,3,4,4,10,9,0,0,0,0 +20/09/2003,Newcastle,Bolton,0,0,D,0,0,D,Graham Barber,14,8,7,6,7,5,15,12,1,1,0,0 +20/09/2003,Portsmouth,Blackburn,1,2,A,0,2,A,P Durkin,9,12,6,10,7,6,17,18,2,3,0,0 +20/09/2003,Tottenham,Southampton,1,3,A,0,2,A,A D'Urso,13,12,6,10,4,7,15,14,1,1,0,0 +20/09/2003,Wolves,Chelsea,0,5,A,0,2,A,M Messias,8,14,4,8,1,3,15,15,2,3,0,0 +21/09/2003,Man United,Arsenal,0,0,D,0,0,D,S Bennett,9,6,7,1,4,3,11,15,4,3,0,1 +21/09/2003,Middlesbrough,Everton,1,0,H,1,0,H,A Wiley,9,9,4,5,4,6,12,14,2,3,0,0 +26/09/2003,Arsenal,Newcastle,3,2,H,1,1,D,M Riley,11,5,7,4,3,4,13,17,0,1,0,0 +27/09/2003,Birmingham,Portsmouth,2,0,H,1,0,H,S Bennett,7,10,5,5,4,4,10,16,1,3,0,0 +27/09/2003,Bolton,Wolves,1,1,D,0,1,A,M Dean,22,8,14,4,12,3,16,4,2,0,0,0 +27/09/2003,Chelsea,Aston Villa,1,0,H,1,0,H,J Winter,10,7,5,1,6,5,7,9,0,2,0,0 +27/09/2003,Leicester,Man United,1,4,A,0,3,A,G Poll,10,11,4,9,7,3,7,11,0,0,0,0 +27/09/2003,Southampton,Middlesbrough,0,1,A,0,1,A,B Knight,10,12,6,9,4,4,12,19,4,3,1,0 +28/09/2003,Blackburn,Fulham,0,2,A,0,1,A,M Messias,6,7,0,2,5,5,14,11,2,4,0,0 +28/09/2003,Charlton,Liverpool,3,2,H,2,1,H,R Styles,14,17,5,9,7,5,11,9,2,1,0,0 +28/09/2003,Everton,Leeds,4,0,H,3,0,H,P Durkin,20,6,9,2,11,4,11,15,0,2,0,0 +28/09/2003,Man City,Tottenham,0,0,D,0,0,D,N Barry,21,4,10,2,6,6,7,15,2,1,0,0 +04/10/2003,Fulham,Leicester,2,0,H,1,0,H,C Foy,16,8,8,4,4,4,14,10,2,1,0,0 +04/10/2003,Leeds,Blackburn,2,1,H,2,0,H,U Rennie,15,17,5,9,2,5,18,20,3,1,0,0 +04/10/2003,Liverpool,Arsenal,1,2,A,1,1,D,G Barber,13,12,7,8,11,3,12,20,2,2,0,0 +04/10/2003,Man United,Birmingham,3,0,H,1,0,H,M Dean,10,5,8,2,5,1,13,11,0,1,0,1 +04/10/2003,Newcastle,Southampton,1,0,H,1,0,H,P Dowd,14,7,9,4,8,4,14,10,3,2,0,0 +04/10/2003,Portsmouth,Charlton,1,2,A,1,0,H,G Poll,16,11,8,6,6,9,20,14,3,2,0,0 +04/10/2003,Tottenham,Everton,3,0,H,1,0,H,D Gallagher,13,12,7,4,2,6,15,14,2,2,0,0 +04/10/2003,Wolves,Man City,1,0,H,0,0,D,J Winter,8,9,4,5,3,10,13,7,3,2,0,0 +05/10/2003,Aston Villa,Bolton,1,1,D,0,0,D,R Styles,14,15,8,5,9,5,14,16,1,5,0,0 +05/10/2003,Middlesbrough,Chelsea,1,2,A,0,1,A,M Halsey,15,10,6,6,2,8,14,18,1,2,0,0 +14/10/2003,Birmingham,Chelsea,0,0,D,0,0,D,N Barry,6,13,2,8,2,18,10,14,2,4,0,0 +18/10/2003,Arsenal,Chelsea,2,1,H,1,1,D,P Durkin,13,7,9,3,6,3,11,13,1,2,0,0 +18/10/2003,Fulham,Wolves,0,0,D,0,0,D,H Webb,9,14,4,6,4,11,12,11,2,2,0,0 +18/10/2003,Leeds,Man United,0,1,A,0,0,D,G Poll,3,18,2,10,7,2,19,8,4,1,0,0 +18/10/2003,Man City,Bolton,6,2,H,1,1,D,S Bennett,16,12,13,8,7,4,11,8,1,1,1,0 +18/10/2003,Middlesbrough,Newcastle,0,1,A,0,1,A,A Wiley,11,4,7,3,8,2,17,12,3,3,0,0 +18/10/2003,Portsmouth,Liverpool,1,0,H,1,0,H,S Dunn,10,13,6,7,5,3,22,10,2,2,0,0 +19/10/2003,Birmingham,Aston Villa,0,0,D,0,0,D,M Riley,4,2,3,1,3,4,16,20,2,1,0,0 +19/10/2003,Everton,Southampton,0,0,D,0,0,D,M Messias,13,19,6,7,8,6,11,6,1,2,0,0 +19/10/2003,Leicester,Tottenham,1,2,A,1,0,H,A D'Urso,22,7,15,4,5,1,12,19,3,3,0,0 +20/10/2003,Blackburn,Charlton,0,1,A,0,1,A,G Barber,15,10,6,5,5,3,17,12,3,0,0,0 +21/10/2003,Fulham,Newcastle,2,3,A,2,1,H,B Knight,4,11,4,7,4,8,14,16,2,2,0,0 +25/10/2003,Aston Villa,Everton,0,0,D,0,0,D,A D'Urso,16,8,6,2,9,3,11,12,0,1,0,0 +25/10/2003,Bolton,Birmingham,0,1,A,0,1,A,C Foy,16,7,7,3,7,3,14,16,1,2,0,0 +25/10/2003,Chelsea,Man City,1,0,H,1,0,H,P Dowd,7,9,4,6,1,5,8,8,0,0,0,0 +25/10/2003,Liverpool,Leeds,3,1,H,1,1,D,J Winter,20,7,9,5,9,3,11,13,0,2,0,0 +25/10/2003,Man United,Fulham,1,3,A,1,1,D,M Riley,17,12,9,7,10,2,10,14,4,2,0,0 +25/10/2003,Newcastle,Portsmouth,3,0,H,2,0,H,P Durkin,10,9,8,4,6,6,12,14,0,1,0,0 +25/10/2003,Southampton,Blackburn,2,0,H,0,0,D,S Bennett,22,6,15,3,5,3,17,14,0,1,0,1 +25/10/2003,Wolves,Leicester,4,3,H,0,3,A,P Walton,12,7,8,5,2,6,6,13,0,2,0,0 +26/10/2003,Charlton,Arsenal,1,1,D,1,1,D,S Dunn,6,11,4,6,7,5,15,13,1,1,0,0 +26/10/2003,Tottenham,Middlesbrough,0,0,D,0,0,D,M Dean,12,16,8,10,8,5,14,10,3,2,0,0 +01/11/2003,Everton,Chelsea,0,1,A,0,0,D,J Winter,12,17,4,7,3,1,5,11,0,0,0,0 +01/11/2003,Leeds,Arsenal,1,4,A,0,3,A,M Dean,9,13,5,9,4,3,12,11,1,0,0,0 +01/11/2003,Man United,Portsmouth,3,0,H,1,0,H,N Barry,10,9,4,3,3,3,13,12,0,3,0,0 +01/11/2003,Middlesbrough,Wolves,2,0,H,0,0,D,S Dunn,14,9,11,2,7,3,8,12,1,2,0,1 +01/11/2003,Newcastle,Aston Villa,1,1,D,1,1,D,M Messias,22,11,13,6,15,3,9,17,2,3,0,1 +01/11/2003,Southampton,Man City,0,2,A,0,1,A,A Wiley,8,13,5,9,4,6,10,16,1,1,0,0 +01/11/2003,Tottenham,Bolton,0,1,A,0,0,D,U Rennie,10,25,6,15,7,5,18,6,3,0,0,0 +02/11/2003,Fulham,Liverpool,1,2,A,1,1,D,R Styles,10,10,6,6,4,8,13,10,1,1,1,0 +02/11/2003,Leicester,Blackburn,2,0,H,0,0,D,D Gallagher,4,10,3,2,2,6,11,11,2,1,0,0 +03/11/2003,Birmingham,Charlton,1,2,A,0,1,A,P Dowd,14,13,13,9,15,8,14,18,1,1,0,0 +08/11/2003,Arsenal,Tottenham,2,1,H,0,1,A,M Halsey,9,5,3,2,9,5,8,22,1,4,0,0 +08/11/2003,Aston Villa,Middlesbrough,0,2,A,0,1,A,D Gallagher,12,8,4,3,5,2,19,16,1,2,0,0 +08/11/2003,Bolton,Southampton,0,0,D,0,0,D,H Webb,18,8,11,5,8,6,10,14,2,1,0,1 +08/11/2003,Charlton,Fulham,3,1,H,1,0,H,A D'Urso,12,11,7,7,1,5,10,12,1,2,0,0 +08/11/2003,Portsmouth,Leeds,6,1,H,2,1,H,C Foy,14,6,6,5,3,2,9,11,1,4,0,0 +08/11/2003,Wolves,Birmingham,1,1,D,0,0,D,G Barber,11,4,4,2,8,1,13,12,2,1,0,0 +09/11/2003,Chelsea,Newcastle,5,0,H,3,0,H,P Durkin,15,4,8,3,2,0,13,5,0,0,0,1 +09/11/2003,Liverpool,Man United,1,2,A,0,0,D,G Poll,10,6,4,4,2,6,16,9,1,2,0,0 +09/11/2003,Man City,Leicester,0,3,A,0,1,A,M Riley,14,6,4,3,12,3,7,12,2,2,0,0 +10/11/2003,Blackburn,Everton,2,1,H,2,0,H,P Dowd,18,8,12,2,12,3,16,9,3,2,0,0 +22/11/2003,Birmingham,Arsenal,0,3,A,0,1,A,P Durkin,7,9,1,6,8,6,8,11,1,2,0,0 +22/11/2003,Everton,Wolves,2,0,H,2,0,H,M Riley,23,8,15,4,8,3,7,23,2,2,0,0 +22/11/2003,Leeds,Bolton,0,2,A,0,2,A,G Poll,13,10,10,5,8,2,15,11,1,0,0,0 +22/11/2003,Leicester,Charlton,1,1,D,1,0,H,G Barber,13,7,7,4,3,3,15,15,2,1,0,0 +22/11/2003,Man United,Blackburn,2,1,H,2,0,H,M Halsey,14,13,8,6,7,4,6,15,0,1,0,0 +22/11/2003,Middlesbrough,Liverpool,0,0,D,0,0,D,P Dowd,8,8,3,1,3,4,16,15,2,2,0,0 +22/11/2003,Newcastle,Man City,3,0,H,0,0,D,N Barry,17,5,10,1,4,2,13,16,1,2,0,0 +22/11/2003,Southampton,Chelsea,0,1,A,0,0,D,D Gallagher,13,8,3,4,3,3,7,13,0,1,0,0 +23/11/2003,Tottenham,Aston Villa,2,1,H,0,0,D,J Winter,10,13,5,6,3,3,14,15,0,1,0,0 +24/11/2003,Fulham,Portsmouth,2,0,H,2,0,H,A Wiley,8,12,4,6,2,11,9,16,0,3,0,1 +29/11/2003,Aston Villa,Southampton,1,0,H,1,0,H,M Messias,22,7,12,3,9,4,13,13,0,1,0,0 +29/11/2003,Blackburn,Tottenham,1,0,H,0,0,D,G Poll,18,9,12,3,9,6,10,13,0,1,0,0 +29/11/2003,Bolton,Everton,2,0,H,1,0,H,P Durkin,13,7,6,3,7,7,11,18,0,2,0,0 +29/11/2003,Charlton,Leeds,0,1,A,0,1,A,M Halsey,13,12,6,6,3,4,11,17,2,1,0,0 +29/11/2003,Portsmouth,Leicester,0,2,A,0,1,A,M Dean,12,7,7,3,11,4,12,14,5,1,0,0 +29/11/2003,Wolves,Newcastle,1,1,D,1,1,D,S Bennett,10,7,4,4,5,3,14,14,2,1,0,0 +30/11/2003,Arsenal,Fulham,0,0,D,0,0,D,G Barber,22,7,14,1,9,0,13,11,1,1,0,0 +30/11/2003,Chelsea,Man United,1,0,H,1,0,H,A Wiley,12,5,4,3,2,4,11,12,4,1,0,0 +30/11/2003,Liverpool,Birmingham,3,1,H,1,1,D,N Barry,10,7,7,4,6,4,24,17,2,1,0,0 +30/11/2003,Man City,Middlesbrough,0,1,A,0,1,A,M Riley,24,2,14,0,12,0,15,12,0,0,0,0 +06/12/2003,Birmingham,Blackburn,0,4,A,0,0,D,G Barber,6,11,2,7,10,7,23,15,1,1,1,0 +06/12/2003,Fulham,Bolton,2,1,H,0,0,D,A D'Urso,10,10,3,5,3,2,15,11,0,2,0,0 +06/12/2003,Leeds,Chelsea,1,1,D,1,0,H,M Dean,8,19,4,10,2,8,14,11,3,2,0,0 +06/12/2003,Leicester,Arsenal,1,1,D,0,0,D,R Styles,4,8,1,5,7,5,13,12,1,1,0,1 +06/12/2003,Man United,Aston Villa,4,0,H,2,0,H,S Dunn,12,10,8,3,6,5,6,11,0,1,0,0 +06/12/2003,Middlesbrough,Portsmouth,0,0,D,0,0,D,S Bennett,5,6,3,2,6,5,17,20,2,3,0,1 +06/12/2003,Newcastle,Liverpool,1,1,D,0,1,A,G Poll,13,7,6,3,8,1,13,13,1,4,0,0 +06/12/2003,Tottenham,Wolves,5,2,H,1,1,D,U Rennie,12,16,10,8,4,5,14,8,2,0,0,0 +07/12/2003,Everton,Man City,0,0,D,0,0,D,J Winter,9,15,2,7,3,5,11,13,1,1,0,0 +07/12/2003,Southampton,Charlton,3,2,H,2,0,H,P Walton,18,20,14,14,8,9,12,12,0,0,0,0 +13/12/2003,Chelsea,Bolton,1,2,A,1,1,D,M Messias,9,4,5,2,8,3,13,11,2,2,0,0 +13/12/2003,Leicester,Birmingham,0,2,A,0,1,A,M Riley,8,9,5,4,6,7,19,16,2,2,2,0 +13/12/2003,Liverpool,Southampton,1,2,A,0,1,A,P Durkin,18,8,10,4,9,5,6,8,0,0,0,0 +13/12/2003,Man United,Man City,3,1,H,2,0,H,M Halsey,10,12,7,9,2,5,8,10,0,2,0,0 +13/12/2003,Middlesbrough,Charlton,0,0,D,0,0,D,H Webb,12,10,6,3,3,7,13,11,1,1,0,0 +13/12/2003,Newcastle,Tottenham,4,0,H,1,0,H,A Wiley,19,4,11,4,11,7,10,12,2,2,0,0 +13/12/2003,Portsmouth,Everton,1,2,A,1,2,A,U Rennie,12,9,10,4,7,10,13,13,1,1,0,0 +14/12/2003,Arsenal,Blackburn,1,0,H,1,0,H,A D'Urso,11,5,7,1,5,6,14,14,2,4,0,0 +14/12/2003,Aston Villa,Wolves,3,2,H,2,1,H,G Poll,11,8,8,5,4,6,12,14,3,4,0,0 +14/12/2003,Leeds,Fulham,3,2,H,1,0,H,N Barry,14,10,11,7,6,5,17,11,2,1,0,0 +20/12/2003,Blackburn,Aston Villa,0,2,A,0,0,D,D Gallagher,12,15,5,7,9,1,7,11,1,0,0,0 +20/12/2003,Bolton,Arsenal,1,1,D,0,0,D,G Poll,19,9,13,4,8,1,11,13,2,2,0,0 +20/12/2003,Charlton,Newcastle,0,0,D,0,0,D,S Bennett,18,11,10,5,7,9,11,9,0,0,0,0 +20/12/2003,Everton,Leicester,3,2,H,1,1,D,P Dowd,15,6,8,4,11,7,13,18,2,3,0,0 +20/12/2003,Fulham,Chelsea,0,1,A,0,0,D,M Riley,7,11,2,9,2,8,11,10,2,3,0,0 +21/12/2003,Southampton,Portsmouth,3,0,H,1,0,H,J Winter,12,11,7,7,8,5,7,21,0,3,0,0 +21/12/2003,Tottenham,Man United,1,2,A,0,2,A,R Styles,14,12,8,7,7,8,12,8,2,2,0,0 +22/12/2003,Man City,Leeds,1,1,D,0,1,A,G Barber,19,6,11,4,8,5,7,8,0,3,0,0 +26/12/2003,Arsenal,Wolves,3,0,H,2,0,H,P Dowd,15,5,10,1,14,3,15,17,2,5,0,0 +26/12/2003,Birmingham,Man City,2,1,H,0,1,A,U Rennie,5,9,3,5,9,3,18,14,2,1,0,0 +26/12/2003,Blackburn,Middlesbrough,2,2,D,1,1,D,A Wiley,13,5,10,3,14,1,11,10,2,1,0,0 +26/12/2003,Charlton,Chelsea,4,2,H,2,1,H,G Poll,8,15,4,8,8,6,14,10,1,1,0,0 +26/12/2003,Fulham,Southampton,2,0,H,1,0,H,D Gallagher,14,6,7,2,11,5,13,12,0,2,0,0 +26/12/2003,Leeds,Aston Villa,0,0,D,0,0,D,S Bennett,6,13,3,4,7,5,15,13,0,2,0,0 +26/12/2003,Leicester,Newcastle,1,1,D,0,0,D,C Foy,6,12,5,6,6,7,11,10,0,0,0,0 +26/12/2003,Liverpool,Bolton,3,1,H,1,0,H,J Winter,13,7,8,2,7,1,10,18,0,2,0,0 +26/12/2003,Man United,Everton,3,2,H,2,1,H,M Dean,19,10,11,6,7,2,8,10,0,2,0,0 +26/12/2003,Portsmouth,Tottenham,2,0,H,0,0,D,S Dunn,10,5,8,3,3,6,10,10,1,1,0,0 +28/12/2003,Aston Villa,Fulham,3,0,H,1,0,H,B Knight,12,9,8,3,8,3,17,7,0,0,0,0 +28/12/2003,Bolton,Leicester,2,2,D,1,1,D,N Barry,15,7,8,6,3,7,16,17,1,2,0,0 +28/12/2003,Chelsea,Portsmouth,3,0,H,0,0,D,G Barber,15,7,8,3,7,1,8,14,0,0,0,0 +28/12/2003,Everton,Birmingham,1,0,H,0,0,D,R Styles,16,5,6,2,5,2,7,13,0,0,0,0 +28/12/2003,Man City,Liverpool,2,2,D,1,0,H,M Riley,16,11,8,9,7,7,6,14,1,2,0,0 +28/12/2003,Middlesbrough,Man United,0,1,A,0,1,A,M Messias,11,10,2,6,4,2,11,15,3,2,0,1 +28/12/2003,Newcastle,Blackburn,0,1,A,0,0,D,M Halsey,15,11,11,3,8,8,6,13,0,1,0,0 +28/12/2003,Tottenham,Charlton,0,1,A,0,0,D,P Durkin,18,10,9,8,11,11,8,13,1,0,0,0 +28/12/2003,Wolves,Leeds,3,1,H,1,1,D,A D'Urso,11,9,4,4,6,10,11,14,1,4,0,1 +29/12/2003,Southampton,Arsenal,0,1,A,0,1,A,S Dunn,6,17,3,9,2,3,14,10,1,0,0,0 +06/01/2004,Aston Villa,Portsmouth,2,1,H,1,0,H,J Winter,18,11,13,4,9,6,17,14,0,0,0,0 +07/01/2004,Bolton,Man United,1,2,A,0,2,A,D Gallagher,15,11,5,7,10,6,4,8,2,0,0,0 +07/01/2004,Chelsea,Liverpool,0,1,A,0,1,A,S Dunn,9,4,2,1,8,3,15,9,0,2,0,1 +07/01/2004,Everton,Arsenal,1,1,D,0,1,A,A Wiley,8,9,4,5,5,4,13,16,0,3,0,0 +07/01/2004,Man City,Charlton,1,1,D,1,0,H,P Walton,18,9,10,4,2,2,9,11,0,0,0,0 +07/01/2004,Middlesbrough,Fulham,2,1,H,1,0,H,P Durkin,10,9,5,4,9,1,14,17,1,2,0,0 +07/01/2004,Newcastle,Leeds,1,0,H,1,0,H,P Dowd,13,9,6,5,5,3,14,17,0,1,0,0 +07/01/2004,Southampton,Leicester,0,0,D,0,0,D,H Webb,14,9,14,8,4,5,12,15,2,1,0,0 +07/01/2004,Tottenham,Birmingham,4,1,H,3,0,H,B Knight,11,9,7,3,1,6,15,6,0,0,0,0 +07/01/2004,Wolves,Blackburn,2,2,D,0,1,A,N Barry,10,9,5,5,2,7,13,20,1,1,0,0 +10/01/2004,Arsenal,Middlesbrough,4,1,H,2,0,H,A D'Urso,20,5,13,4,7,3,12,21,1,1,0,0 +10/01/2004,Birmingham,Southampton,2,1,H,1,1,D,S Bennett,11,9,6,6,7,10,14,23,0,1,0,1 +10/01/2004,Blackburn,Bolton,3,4,A,3,2,H,S Dunn,8,16,3,10,4,7,15,13,2,3,0,0 +10/01/2004,Charlton,Wolves,2,0,H,1,0,H,R Styles,10,7,4,4,6,5,6,12,1,3,0,0 +10/01/2004,Fulham,Everton,2,1,H,1,0,H,G Poll,14,11,10,5,5,7,19,9,0,0,0,0 +10/01/2004,Leeds,Tottenham,0,1,A,0,0,D,M Halsey,7,15,1,8,4,5,17,13,0,1,0,0 +10/01/2004,Liverpool,Aston Villa,1,0,H,1,0,H,G Barber,12,8,4,2,3,6,14,15,3,2,0,0 +10/01/2004,Portsmouth,Man City,4,2,H,1,2,A,M Messias,11,15,8,6,2,7,7,8,0,2,0,0 +11/01/2004,Leicester,Chelsea,0,4,A,0,2,A,U Rennie,6,11,3,6,6,2,17,7,0,1,0,0 +11/01/2004,Man United,Newcastle,0,0,D,0,0,D,P Durkin,12,7,7,3,2,3,13,11,2,0,0,0 +17/01/2004,Bolton,Portsmouth,1,0,H,0,0,D,P Dowd,13,8,6,3,2,2,9,13,1,1,0,1 +17/01/2004,Everton,Charlton,0,1,A,0,1,A,M Riley,17,7,7,4,10,5,6,14,4,2,0,0 +17/01/2004,Man City,Blackburn,1,1,D,0,0,D,M Dean,10,6,7,3,4,2,7,13,2,1,0,0 +17/01/2004,Middlesbrough,Leicester,3,3,D,1,0,H,B Knight,13,14,7,8,6,6,15,12,2,5,0,0 +17/01/2004,Southampton,Leeds,2,1,H,2,0,H,A Wiley,18,11,6,6,1,6,16,8,1,1,0,0 +17/01/2004,Tottenham,Liverpool,2,1,H,1,0,H,U Rennie,7,10,3,4,5,6,18,9,1,1,0,0 +17/01/2004,Wolves,Man United,1,0,H,0,0,D,A D'Urso,11,15,6,6,4,12,13,5,2,0,0,0 +18/01/2004,Aston Villa,Arsenal,0,2,A,0,1,A,M Halsey,12,13,5,9,3,2,17,21,4,1,0,0 +18/01/2004,Chelsea,Birmingham,0,0,D,0,0,D,J Winter,16,2,9,1,6,2,13,8,2,2,0,0 +19/01/2004,Newcastle,Fulham,3,1,H,2,0,H,N Barry,10,13,9,6,7,4,10,8,0,1,0,0 +21/01/2004,Wolves,Liverpool,1,1,D,0,1,A,B Knight,9,14,6,10,7,5,14,16,2,1,0,0 +31/01/2004,Birmingham,Newcastle,1,1,D,0,1,A,R Styles,15,4,5,2,2,3,13,11,1,3,0,0 +31/01/2004,Charlton,Bolton,1,2,A,1,1,D,A D'Urso,14,14,7,10,5,6,9,9,0,0,0,0 +31/01/2004,Fulham,Tottenham,2,1,H,1,1,D,M Messias,16,4,10,4,10,13,6,12,1,1,0,0 +31/01/2004,Leeds,Middlesbrough,0,3,A,0,0,D,G Poll,8,15,4,6,7,6,14,6,1,1,1,0 +31/01/2004,Leicester,Aston Villa,0,5,A,0,0,D,J Winter,13,14,9,10,5,9,12,7,0,1,0,0 +31/01/2004,Liverpool,Everton,0,0,D,0,0,D,S Bennett,21,6,9,4,8,3,7,11,0,1,0,0 +31/01/2004,Man United,Southampton,3,2,H,2,1,H,G Barber,12,17,7,11,1,10,9,11,0,2,0,0 +31/01/2004,Portsmouth,Wolves,0,0,D,0,0,D,H Webb,17,8,10,4,6,6,9,6,0,1,0,0 +01/02/2004,Arsenal,Man City,2,1,H,1,0,H,A Wiley,11,9,9,8,5,4,13,13,2,2,0,1 +01/02/2004,Blackburn,Chelsea,2,3,A,1,2,A,D Gallagher,13,17,7,9,5,6,9,14,2,0,0,0 +07/02/2004,Aston Villa,Leeds,2,0,H,1,0,H,U Rennie,15,7,7,4,3,10,15,16,2,2,0,0 +07/02/2004,Bolton,Liverpool,2,2,D,1,0,H,A Wiley,12,11,6,6,3,5,15,15,3,2,0,0 +07/02/2004,Everton,Man United,3,4,A,0,3,A,N Barry,12,19,8,9,7,7,11,14,1,0,0,0 +07/02/2004,Middlesbrough,Blackburn,0,1,A,0,1,A,R Styles,15,6,7,2,8,3,6,12,0,1,0,0 +07/02/2004,Newcastle,Leicester,3,1,H,2,0,H,S Dunn,15,7,9,3,9,1,17,11,1,2,0,0 +07/02/2004,Southampton,Fulham,0,0,D,0,0,D,A D'Urso,11,11,6,8,17,3,12,18,1,2,0,0 +07/02/2004,Tottenham,Portsmouth,4,3,H,2,1,H,P Walton,15,16,13,9,6,5,16,13,1,0,0,0 +07/02/2004,Wolves,Arsenal,1,3,A,1,1,D,P Dowd,12,10,5,7,6,4,18,18,2,1,0,0 +08/02/2004,Chelsea,Charlton,1,0,H,1,0,H,S Bennett,16,5,6,0,4,3,9,17,2,0,0,0 +08/02/2004,Man City,Birmingham,0,0,D,0,0,D,P Durkin,21,7,12,2,11,5,11,10,0,1,0,0 +10/02/2004,Arsenal,Southampton,2,0,H,1,0,H,N Barry,11,10,6,6,5,9,13,12,2,3,0,0 +10/02/2004,Leeds,Wolves,4,1,H,2,1,H,M Dean,20,11,8,5,4,3,15,14,1,2,0,0 +10/02/2004,Leicester,Bolton,1,1,D,1,1,D,U Rennie,15,9,15,9,8,7,16,13,1,2,0,0 +11/02/2004,Birmingham,Everton,3,0,H,2,0,H,M Halsey,15,5,11,3,8,11,10,14,1,0,0,0 +11/02/2004,Blackburn,Newcastle,1,1,D,0,0,D,M Messias,17,14,7,7,8,6,12,10,0,0,0,0 +11/02/2004,Charlton,Tottenham,2,4,A,0,2,A,G Barber,18,12,14,10,10,4,13,14,3,0,0,0 +11/02/2004,Fulham,Aston Villa,1,2,A,1,2,A,B Knight,13,10,6,3,6,5,16,17,2,1,1,0 +11/02/2004,Liverpool,Man City,2,1,H,1,0,H,M Riley,12,6,7,3,6,3,7,8,0,0,0,0 +11/02/2004,Man United,Middlesbrough,2,3,A,1,2,A,P Durkin,23,12,13,8,10,5,13,17,1,1,0,0 +11/02/2004,Portsmouth,Chelsea,0,2,A,0,1,A,G Poll,11,10,5,4,9,10,8,13,1,2,0,0 +21/02/2004,Bolton,Man City,1,3,A,1,2,A,S Dunn,16,17,11,13,12,11,9,10,0,2,0,0 +21/02/2004,Charlton,Blackburn,3,2,H,2,0,H,U Rennie,11,18,7,12,8,12,7,6,0,0,0,0 +21/02/2004,Chelsea,Arsenal,1,2,A,1,2,A,M Riley,10,9,4,7,11,5,16,18,4,2,1,0 +21/02/2004,Man United,Leeds,1,1,D,0,0,D,M Halsey,24,7,7,3,12,7,12,17,1,2,0,0 +21/02/2004,Newcastle,Middlesbrough,2,1,H,0,1,A,G Poll,16,8,9,4,7,3,20,21,0,3,0,0 +21/02/2004,Southampton,Everton,3,3,D,0,2,A,P Dowd,9,12,5,6,6,5,14,19,1,0,0,0 +21/02/2004,Wolves,Fulham,2,1,H,1,0,H,H Webb,12,9,10,5,8,6,13,16,1,0,0,0 +22/02/2004,Aston Villa,Birmingham,2,2,D,1,0,H,J Winter,11,13,8,9,7,5,15,14,0,1,0,0 +22/02/2004,Tottenham,Leicester,4,4,D,3,1,H,N Barry,10,14,9,7,4,3,9,14,0,2,0,1 +28/02/2004,Arsenal,Charlton,2,1,H,2,0,H,G Barber,21,6,11,3,12,3,8,10,0,0,0,0 +28/02/2004,Blackburn,Southampton,1,1,D,0,1,A,M Dean,16,10,7,6,6,9,10,8,1,1,0,0 +28/02/2004,Everton,Aston Villa,2,0,H,0,0,D,M Messias,15,12,7,8,5,11,13,18,4,2,0,0 +28/02/2004,Fulham,Man United,1,1,D,0,1,A,A Wiley,7,13,3,7,4,3,13,19,1,3,0,0 +28/02/2004,Leicester,Wolves,0,0,D,0,0,D,J Winter,10,6,6,3,6,3,7,9,0,1,0,0 +28/02/2004,Man City,Chelsea,0,1,A,0,0,D,R Styles,14,9,4,4,7,1,8,11,0,1,0,0 +29/02/2004,Leeds,Liverpool,2,2,D,2,2,D,P Durkin,12,22,6,13,6,8,13,11,0,1,0,0 +29/02/2004,Portsmouth,Newcastle,1,1,D,0,1,A,A D'Urso,18,5,8,2,8,6,9,9,2,2,0,0 +03/03/2004,Birmingham,Middlesbrough,3,1,H,1,0,H,R Styles,9,8,5,6,4,5,13,20,3,4,0,1 +06/03/2004,Birmingham,Bolton,2,0,H,1,0,H,A D'Urso,10,10,6,2,3,5,10,15,1,1,0,0 +09/03/2004,Middlesbrough,Tottenham,1,0,H,0,0,D,A D'Urso,10,10,6,2,3,5,10,15,1,1,0,0 +13/03/2004,Birmingham,Leicester,0,1,A,0,0,D,M Messias,6,4,4,2,8,6,12,26,1,2,0,0 +13/03/2004,Blackburn,Arsenal,0,2,A,0,0,D,A Wiley,10,14,5,5,8,3,12,11,1,1,0,0 +13/03/2004,Bolton,Chelsea,0,2,A,0,0,D,G Poll,20,12,11,8,8,6,8,13,1,0,0,0 +13/03/2004,Charlton,Middlesbrough,1,0,H,1,0,H,M Dean,14,15,7,9,4,7,13,9,0,0,0,0 +13/03/2004,Everton,Portsmouth,1,0,H,0,0,D,N Barry,13,5,6,3,6,4,10,12,0,2,0,0 +13/03/2004,Fulham,Leeds,2,0,H,0,0,D,S Dunn,19,9,10,6,4,7,14,13,4,3,0,0 +14/03/2004,Man City,Man United,4,1,H,2,1,H,S Bennett,17,17,11,9,7,4,18,14,1,2,0,0 +14/03/2004,Southampton,Liverpool,2,0,H,0,0,D,D Gallagher,7,18,3,12,1,7,20,8,3,1,0,0 +14/03/2004,Tottenham,Newcastle,1,0,H,0,0,D,H Webb,11,6,4,4,3,12,13,13,0,0,0,0 +14/03/2004,Wolves,Aston Villa,0,4,A,0,3,A,P Durkin,9,15,1,7,8,13,10,14,1,2,0,0 +17/03/2004,Liverpool,Portsmouth,3,0,H,2,0,H,B Knight,20,4,13,4,10,8,9,20,0,1,0,0 +20/03/2004,Arsenal,Bolton,2,1,H,2,1,H,G Barber,11,8,8,2,6,1,12,14,1,2,0,0 +20/03/2004,Aston Villa,Blackburn,0,2,A,0,2,A,A D'Urso,10,16,7,7,4,6,11,14,0,2,0,0 +20/03/2004,Chelsea,Fulham,2,1,H,2,1,H,N Barry,13,5,8,1,9,1,12,13,1,2,0,0 +20/03/2004,Leicester,Everton,1,1,D,0,0,D,B Knight,10,7,4,3,9,2,12,21,1,5,0,1 +20/03/2004,Liverpool,Wolves,1,0,H,0,0,D,R Styles,12,8,4,6,14,4,5,12,0,1,0,0 +20/03/2004,Man United,Tottenham,3,0,H,1,0,H,D Gallagher,11,8,6,3,4,2,14,10,1,1,0,0 +20/03/2004,Middlesbrough,Birmingham,5,3,H,4,2,H,U Rennie,13,18,9,12,4,4,11,13,2,0,0,0 +20/03/2004,Newcastle,Charlton,3,1,H,2,0,H,M Riley,15,6,10,2,6,6,11,12,0,2,0,0 +21/03/2004,Portsmouth,Southampton,1,0,H,0,0,D,M Halsey,16,13,8,8,6,4,16,12,0,2,0,0 +22/03/2004,Leeds,Man City,2,1,H,1,1,D,A Wiley,4,21,3,14,4,5,13,14,2,1,0,1 +27/03/2004,Birmingham,Leeds,4,1,H,1,1,D,M Halsey,17,11,7,7,7,7,11,18,0,1,0,0 +27/03/2004,Blackburn,Portsmouth,1,2,A,1,1,D,P Durkin,14,8,9,4,5,3,11,10,0,0,0,0 +27/03/2004,Charlton,Aston Villa,1,2,A,1,1,D,B Knight,11,19,9,11,3,12,11,14,1,1,0,0 +27/03/2004,Chelsea,Wolves,5,2,H,1,1,D,G Barber,15,9,10,4,7,4,6,12,0,2,0,0 +27/03/2004,Everton,Middlesbrough,1,1,D,0,0,D,S Bennett,14,10,7,5,8,4,16,13,0,2,0,0 +27/03/2004,Man City,Fulham,0,0,D,0,0,D,J Winter,12,5,4,2,3,1,12,10,1,0,0,0 +27/03/2004,Southampton,Tottenham,1,0,H,0,0,D,C Foy,18,15,11,9,10,6,8,16,0,2,0,0 +28/03/2004,Arsenal,Man United,1,1,D,0,0,D,G Poll,17,13,10,8,8,7,15,18,1,1,0,0 +28/03/2004,Bolton,Newcastle,1,0,H,1,0,H,M Dean,20,15,12,7,7,7,11,10,1,2,0,0 +28/03/2004,Leicester,Liverpool,0,0,D,0,0,D,P Dowd,13,19,9,12,7,10,15,20,4,3,0,0 +03/04/2004,Fulham,Birmingham,0,0,D,0,0,D,M Riley,6,6,5,1,4,2,24,22,4,2,0,0 +03/04/2004,Middlesbrough,Bolton,2,0,H,1,0,H,N Barry,10,13,6,8,7,8,13,15,2,2,0,0 +03/04/2004,Newcastle,Everton,4,2,H,2,1,H,D Gallagher,12,13,8,6,5,9,11,7,0,0,0,0 +03/04/2004,Tottenham,Chelsea,0,1,A,0,1,A,S Bennett,12,10,7,7,5,4,10,10,2,0,0,0 +03/04/2004,Wolves,Southampton,1,4,A,0,1,A,M Halsey,8,13,3,10,3,6,12,8,2,1,0,0 +04/04/2004,Aston Villa,Man City,1,1,D,1,0,H,U Rennie,6,7,3,4,5,4,13,13,1,2,0,0 +04/04/2004,Liverpool,Blackburn,4,0,H,3,0,H,J Winter,24,9,11,2,6,4,8,10,0,1,0,0 +05/04/2004,Leeds,Leicester,3,2,H,2,0,H,M Dean,15,14,10,9,7,11,19,14,2,1,1,0 +09/04/2004,Arsenal,Liverpool,4,2,H,1,2,A,A Wiley,12,12,7,5,2,8,16,9,3,1,0,0 +09/04/2004,Everton,Tottenham,3,1,H,3,0,H,R Styles,13,12,10,7,8,4,9,14,0,3,0,1 +10/04/2004,Birmingham,Man United,1,2,A,1,0,H,D Gallagher,11,10,8,6,0,3,11,10,0,0,0,0 +10/04/2004,Blackburn,Leeds,1,2,A,0,1,A,S Dunn,9,8,7,3,6,4,14,13,2,2,0,0 +10/04/2004,Bolton,Aston Villa,2,2,D,0,1,A,M Messias,16,7,7,3,6,4,10,12,1,3,0,0 +10/04/2004,Charlton,Portsmouth,1,1,D,1,0,H,A D'Urso,9,10,3,8,6,2,14,8,1,1,0,0 +10/04/2004,Chelsea,Middlesbrough,0,0,D,0,0,D,M Halsey,19,9,9,5,6,2,10,8,1,1,0,0 +10/04/2004,Leicester,Fulham,0,2,A,0,0,D,P Walton,10,9,7,4,15,4,13,15,2,0,0,0 +10/04/2004,Man City,Wolves,3,3,D,2,2,D,J Winter,14,15,8,9,5,6,9,12,0,1,0,0 +11/04/2004,Newcastle,Arsenal,0,0,D,0,0,D,P Durkin,9,11,5,5,5,4,14,12,0,1,0,0 +12/04/2004,Aston Villa,Chelsea,3,2,H,1,1,D,J Winter,5,6,3,2,7,2,8,14,0,1,0,0 +12/04/2004,Fulham,Blackburn,3,4,A,2,1,H,M Dean,10,11,7,7,6,1,12,14,0,0,0,0 +12/04/2004,Liverpool,Charlton,0,1,A,0,0,D,P Dowd,25,6,16,3,7,4,12,17,1,0,0,0 +12/04/2004,Middlesbrough,Southampton,3,1,H,2,0,H,D Gallagher,25,16,18,8,6,3,11,14,0,0,0,0 +12/04/2004,Portsmouth,Birmingham,3,1,H,1,0,H,B Knight,12,6,6,2,4,4,19,11,1,2,0,1 +12/04/2004,Tottenham,Man City,1,1,D,0,1,A,C Foy,15,9,10,8,4,3,10,21,1,1,0,0 +12/04/2004,Wolves,Bolton,1,2,A,1,1,D,U Rennie,8,13,3,4,6,9,12,11,2,1,0,0 +13/04/2004,Leeds,Everton,1,1,D,0,1,A,P Durkin,14,8,7,4,10,5,12,12,2,1,0,0 +13/04/2004,Man United,Leicester,1,0,H,0,0,D,M Halsey,16,12,12,5,7,4,11,11,0,3,0,0 +16/04/2004,Arsenal,Leeds,5,0,H,3,0,H,D Gallagher,15,7,8,2,3,8,10,10,0,0,0,0 +17/04/2004,Blackburn,Leicester,1,0,H,1,0,H,R Styles,12,8,4,4,2,4,16,14,2,1,0,0 +17/04/2004,Bolton,Tottenham,2,0,H,1,0,H,J Winter,15,4,10,0,11,5,18,21,1,2,0,0 +17/04/2004,Charlton,Birmingham,1,1,D,0,0,D,C Foy,12,7,5,4,9,3,14,9,1,0,0,0 +17/04/2004,Chelsea,Everton,0,0,D,0,0,D,G Poll,16,5,6,4,8,4,11,10,0,0,0,0 +17/04/2004,Liverpool,Fulham,0,0,D,0,0,D,S Bennett,18,11,10,7,14,7,16,10,0,3,0,0 +17/04/2004,Man City,Southampton,1,3,A,0,1,A,G Barber,15,9,7,5,6,3,11,9,1,1,0,0 +17/04/2004,Portsmouth,Man United,1,0,H,1,0,H,N Barry,9,19,4,12,3,13,14,8,2,1,0,0 +17/04/2004,Wolves,Middlesbrough,2,0,H,1,0,H,A D'Urso,12,16,8,10,6,8,10,10,1,2,0,0 +18/04/2004,Aston Villa,Newcastle,0,0,D,0,0,D,B Knight,10,5,1,4,7,3,17,13,1,4,0,1 +20/04/2004,Man United,Charlton,2,0,H,1,0,H,S Dunn,10,5,6,0,8,3,10,8,0,1,0,0 +24/04/2004,Everton,Blackburn,0,1,A,0,0,D,P Dowd,16,10,9,7,8,5,8,22,0,4,0,0 +24/04/2004,Fulham,Charlton,2,0,H,1,0,H,M Messias,8,8,6,3,4,7,15,13,1,1,0,0 +24/04/2004,Leicester,Man City,1,1,D,0,1,A,A D'Urso,12,11,5,5,4,2,13,14,1,1,0,0 +24/04/2004,Man United,Liverpool,0,1,A,0,0,D,M Riley,11,9,4,4,3,4,19,15,2,1,0,0 +24/04/2004,Middlesbrough,Aston Villa,1,2,A,1,1,D,G Barber,31,12,21,9,12,8,12,15,0,1,0,1 +24/04/2004,Southampton,Bolton,1,2,A,1,0,H,S Dunn,9,11,3,8,2,2,13,16,4,3,0,0 +25/04/2004,Birmingham,Wolves,2,2,D,2,1,H,M Dean,14,11,9,7,9,2,11,13,1,0,0,0 +25/04/2004,Leeds,Portsmouth,1,2,A,0,1,A,U Rennie,14,7,6,5,9,2,9,21,2,2,0,0 +25/04/2004,Newcastle,Chelsea,2,1,H,1,1,D,R Styles,21,17,12,6,7,5,12,12,1,2,0,0 +25/04/2004,Tottenham,Arsenal,2,2,D,0,2,A,M Halsey,13,9,7,7,7,4,14,15,1,1,0,0 +01/05/2004,Arsenal,Birmingham,0,0,D,0,0,D,G Poll,7,3,1,3,2,3,13,16,0,2,0,0 +01/05/2004,Blackburn,Man United,1,0,H,0,0,D,M Dean,12,9,8,5,5,4,11,10,2,1,0,0 +01/05/2004,Charlton,Leicester,2,2,D,0,1,A,R Styles,8,10,7,5,5,4,15,7,1,0,0,1 +01/05/2004,Chelsea,Southampton,4,0,H,0,0,D,P Durkin,12,5,7,3,12,3,6,5,0,0,0,0 +01/05/2004,Man City,Newcastle,1,0,H,0,0,D,M Halsey,7,7,3,6,7,5,7,9,1,1,0,0 +01/05/2004,Portsmouth,Fulham,1,1,D,0,0,D,S Dunn,14,11,7,7,10,3,14,13,1,4,0,0 +01/05/2004,Wolves,Everton,2,1,H,0,1,A,M Riley,20,10,11,6,12,7,7,12,0,1,0,0 +02/05/2004,Aston Villa,Tottenham,1,0,H,1,0,H,N Barry,11,10,6,5,5,5,15,20,2,1,0,0 +02/05/2004,Bolton,Leeds,4,1,H,0,1,A,S Bennett,21,6,14,4,9,3,14,20,3,2,0,1 +02/05/2004,Liverpool,Middlesbrough,2,0,H,0,0,D,A D'Urso,15,12,8,6,7,3,12,14,1,1,0,0 +04/05/2004,Portsmouth,Arsenal,1,1,D,1,0,H,M Riley,11,20,6,9,5,12,13,10,0,2,0,0 +08/05/2004,Birmingham,Liverpool,0,3,A,0,1,A,S Dunn,3,15,2,8,3,5,11,12,2,0,1,0 +08/05/2004,Everton,Bolton,1,2,A,0,1,A,P Durkin,16,10,9,3,7,3,14,9,2,0,0,0 +08/05/2004,Leeds,Charlton,3,3,D,2,1,H,M Halsey,13,12,7,6,3,2,12,9,0,0,0,0 +08/05/2004,Leicester,Portsmouth,3,1,H,2,0,H,G Poll,13,14,9,7,8,3,11,15,0,2,0,0 +08/05/2004,Man United,Chelsea,1,1,D,0,1,A,S Bennett,12,11,7,7,6,2,9,19,3,2,0,1 +08/05/2004,Middlesbrough,Man City,2,1,H,2,1,H,M Riley,12,14,7,6,5,4,9,14,1,1,0,0 +08/05/2004,Southampton,Aston Villa,1,1,D,1,1,D,H Webb,18,14,10,7,4,4,10,13,1,0,0,0 +08/05/2004,Tottenham,Blackburn,1,0,H,1,0,H,A D'Urso,16,14,13,2,7,7,9,17,0,2,0,0 +09/05/2004,Fulham,Arsenal,0,1,A,0,1,A,M Dean,12,7,5,5,6,7,13,19,1,3,0,0 +09/05/2004,Newcastle,Wolves,1,1,D,1,0,H,M Messias,19,15,8,7,13,3,13,11,1,2,0,0 +12/05/2004,Southampton,Newcastle,3,3,D,2,2,D,G Poll,15,22,12,12,3,5,13,16,1,2,0,0 +15/05/2004,Arsenal,Leicester,2,1,H,0,1,A,P Durkin,17,2,10,1,7,5,15,13,0,1,0,0 +15/05/2004,Aston Villa,Man United,0,2,A,0,2,A,R Styles,16,12,8,8,9,4,21,15,4,3,0,2 +15/05/2004,Blackburn,Birmingham,1,1,D,1,0,H,D Gallagher,10,9,5,5,6,8,11,12,0,0,0,0 +15/05/2004,Bolton,Fulham,0,2,A,0,1,A,G Barber,17,10,9,6,11,3,9,14,1,1,0,0 +15/05/2004,Charlton,Southampton,2,1,H,1,0,H,J Winter,10,12,5,4,7,10,13,10,0,1,0,0 +15/05/2004,Chelsea,Leeds,1,0,H,1,0,H,M Dean,19,4,9,0,9,2,5,9,0,0,0,0 +15/05/2004,Liverpool,Newcastle,1,1,D,0,1,A,M Riley,8,6,4,5,8,1,11,17,1,1,0,0 +15/05/2004,Man City,Everton,5,1,H,3,0,H,S Dunn,13,2,11,0,7,3,5,8,1,1,0,0 +15/05/2004,Portsmouth,Middlesbrough,5,1,H,3,1,H,M Halsey,16,19,10,4,7,2,12,8,0,0,0,0 +15/05/2004,Wolves,Tottenham,0,2,A,0,1,A,S Bennett,18,14,10,12,5,3,6,11,1,1,1,0 +14/08/2004,Aston Villa,Southampton,2,0,H,2,0,H,U Rennie,14,6,5,2,12,6,14,9,0,0,0,0 +14/08/2004,Blackburn,West Brom,1,1,D,0,1,A,C Foy,12,4,4,2,4,5,15,17,1,0,0,0 +14/08/2004,Bolton,Charlton,4,1,H,2,0,H,P Dowd,21,9,11,5,9,5,10,12,1,1,0,0 +14/08/2004,Man City,Fulham,1,1,D,1,0,H,M Messias,12,4,5,2,9,4,14,12,0,2,0,0 +14/08/2004,Middlesbrough,Newcastle,2,2,D,0,1,A,S Bennett,15,11,8,4,6,7,16,13,3,1,0,0 +14/08/2004,Norwich,Crystal Palace,1,1,D,1,0,H,P Walton,14,14,10,8,6,11,14,16,0,1,0,0 +14/08/2004,Portsmouth,Birmingham,1,1,D,1,1,D,H Webb,14,16,8,11,8,4,13,16,2,4,0,0 +14/08/2004,Tottenham,Liverpool,1,1,D,0,1,A,D Gallagher,14,16,7,8,3,8,17,11,3,1,0,0 +15/08/2004,Chelsea,Man United,1,0,H,1,0,H,G Poll,8,11,5,3,2,3,17,8,0,0,0,0 +15/08/2004,Everton,Arsenal,1,4,A,0,2,A,M Riley,9,18,5,14,0,7,14,19,2,1,0,0 +21/08/2004,Birmingham,Chelsea,0,1,A,0,0,D,B Knight,9,7,3,3,8,2,10,16,0,0,0,0 +21/08/2004,Charlton,Portsmouth,2,1,H,1,0,H,A Wiley,18,10,14,6,5,6,9,11,0,1,0,0 +21/08/2004,Crystal Palace,Everton,1,3,A,1,1,D,M Clattenburg,15,14,9,7,6,6,6,18,1,1,0,1 +21/08/2004,Fulham,Bolton,2,0,H,1,0,H,R Styles,23,10,12,4,8,4,11,13,0,0,0,0 +21/08/2004,Liverpool,Man City,2,1,H,0,1,A,G Poll,15,7,10,4,8,3,9,17,1,3,0,1 +21/08/2004,Man United,Norwich,2,1,H,1,0,H,N Barry,17,7,7,2,11,5,8,23,0,1,0,0 +21/08/2004,Newcastle,Tottenham,0,1,A,0,0,D,M Dean,13,15,6,8,13,3,10,12,1,2,0,0 +21/08/2004,Southampton,Blackburn,3,2,H,1,0,H,A D'Urso,13,10,7,6,3,6,12,18,0,3,0,0 +22/08/2004,Arsenal,Middlesbrough,5,3,H,1,1,D,S Dunn,16,9,11,5,7,5,8,14,1,1,0,0 +22/08/2004,West Brom,Aston Villa,1,1,D,1,1,D,M Halsey,14,9,5,4,12,4,12,20,2,2,0,0 +24/08/2004,Birmingham,Man City,1,0,H,1,0,H,P Dowd,6,4,2,1,2,5,20,13,1,1,0,0 +24/08/2004,Crystal Palace,Chelsea,0,2,A,0,1,A,C Foy,7,16,4,10,1,6,9,12,1,1,0,0 +25/08/2004,Arsenal,Blackburn,3,0,H,0,0,D,N Barry,15,3,9,0,6,5,11,18,2,4,0,0 +25/08/2004,Charlton,Aston Villa,3,0,H,2,0,H,H Webb,7,9,5,4,4,12,6,7,0,1,0,0 +25/08/2004,Fulham,Middlesbrough,0,2,A,0,0,D,D Gallagher,6,10,2,5,1,6,10,8,0,1,0,0 +25/08/2004,Newcastle,Norwich,2,2,D,1,0,H,M Halsey,23,13,15,5,12,6,17,10,0,1,0,0 +25/08/2004,Southampton,Bolton,1,2,A,0,2,A,S Dunn,10,9,6,5,9,6,9,8,1,1,0,0 +25/08/2004,West Brom,Tottenham,1,1,D,1,1,D,B Knight,4,12,2,6,4,5,13,10,0,0,0,0 +28/08/2004,Aston Villa,Newcastle,4,2,H,1,2,A,M Riley,16,12,13,10,9,4,23,11,3,1,0,0 +28/08/2004,Blackburn,Man United,1,1,D,1,0,H,A Wiley,8,24,4,15,4,14,25,17,3,1,1,0 +28/08/2004,Chelsea,Southampton,2,1,H,2,1,H,S Bennett,16,6,7,2,9,0,11,12,0,4,0,0 +28/08/2004,Everton,West Brom,2,1,H,1,1,D,P Walton,16,12,10,8,7,6,11,16,0,1,0,0 +28/08/2004,Man City,Charlton,4,0,H,2,0,H,M Halsey,16,6,10,3,6,4,11,8,0,1,0,0 +28/08/2004,Middlesbrough,Crystal Palace,2,1,H,0,0,D,M Dean,24,6,15,2,4,2,16,16,1,2,0,0 +28/08/2004,Norwich,Arsenal,1,4,A,0,3,A,G Poll,5,18,5,15,4,10,15,5,1,1,0,0 +28/08/2004,Tottenham,Birmingham,1,0,H,1,0,H,M Clattenburg,6,10,3,2,2,8,15,5,4,1,0,0 +29/08/2004,Bolton,Liverpool,1,0,H,1,0,H,U Rennie,10,7,8,2,5,2,15,14,2,0,0,0 +30/08/2004,Man United,Everton,0,0,D,0,0,D,D Gallagher,14,7,6,2,8,2,8,13,0,2,0,0 +30/08/2004,Portsmouth,Fulham,4,3,H,3,2,H,B Knight,21,18,13,10,6,8,12,12,0,2,0,0 +11/09/2004,Aston Villa,Chelsea,0,0,D,0,0,D,R Styles,11,18,5,10,2,9,9,12,0,1,0,0 +11/09/2004,Bolton,Man United,2,2,D,0,1,A,M Messias,12,12,7,7,4,7,22,15,2,1,0,0 +11/09/2004,Fulham,Arsenal,0,3,A,0,0,D,M Halsey,9,12,7,7,9,4,16,10,2,2,0,0 +11/09/2004,Liverpool,West Brom,3,0,H,2,0,H,S Dunn,16,9,9,5,8,6,9,11,0,0,0,0 +11/09/2004,Man City,Everton,0,1,A,0,0,D,S Bennett,13,6,4,5,9,6,14,9,1,3,0,1 +11/09/2004,Middlesbrough,Birmingham,2,1,H,1,1,D,G Poll,14,5,6,3,6,3,16,19,2,3,0,0 +11/09/2004,Newcastle,Blackburn,3,0,H,2,0,H,D Gallagher,7,5,4,1,8,5,10,19,2,1,0,0 +11/09/2004,Portsmouth,Crystal Palace,3,1,H,1,1,D,P Dowd,16,6,7,3,8,7,14,21,0,2,0,0 +12/09/2004,Tottenham,Norwich,0,0,D,0,0,D,H Webb,15,7,7,2,13,5,17,9,2,1,0,0 +13/09/2004,Charlton,Southampton,0,0,D,0,0,D,N Barry,8,12,4,7,9,10,12,13,0,1,0,0 +18/09/2004,Arsenal,Bolton,2,2,D,1,0,H,P Dowd,11,8,6,5,3,8,13,17,1,3,0,0 +18/09/2004,Birmingham,Charlton,1,1,D,0,0,D,U Rennie,7,2,5,1,12,3,12,16,3,2,1,0 +18/09/2004,Blackburn,Portsmouth,1,0,H,0,0,D,M Clattenburg,13,6,3,2,13,4,13,15,0,3,0,0 +18/09/2004,Crystal Palace,Man City,1,2,A,0,0,D,M Atkinson,3,14,1,10,2,7,14,15,2,2,0,0 +18/09/2004,Norwich,Aston Villa,0,0,D,0,0,D,B Knight,14,18,3,9,1,6,10,13,1,1,0,0 +18/09/2004,West Brom,Fulham,1,1,D,0,0,D,M Dean,8,9,5,3,11,9,11,6,2,5,1,2 +19/09/2004,Chelsea,Tottenham,0,0,D,0,0,D,M Riley,20,6,8,3,9,6,16,13,1,2,0,0 +19/09/2004,Everton,Middlesbrough,1,0,H,0,0,D,H Webb,11,16,9,11,6,3,13,12,1,4,0,0 +19/09/2004,Southampton,Newcastle,1,2,A,0,1,A,C Foy,11,9,6,3,7,6,15,11,0,3,0,0 +20/09/2004,Man United,Liverpool,2,1,H,1,0,H,G Poll,14,6,9,3,7,2,22,15,1,2,0,0 +25/09/2004,Aston Villa,Crystal Palace,1,1,D,1,1,D,M Clattenburg,10,13,5,8,5,8,10,15,2,1,0,0 +25/09/2004,Bolton,Birmingham,1,1,D,1,0,H,R Styles,16,6,12,2,7,2,7,18,2,1,0,0 +25/09/2004,Fulham,Southampton,1,0,H,1,0,H,G Poll,11,15,6,5,5,4,11,19,0,1,0,0 +25/09/2004,Liverpool,Norwich,3,0,H,2,0,H,A Wiley,20,2,12,1,4,2,10,12,1,0,0,0 +25/09/2004,Man City,Arsenal,0,1,A,0,1,A,N Barry,11,12,7,9,8,1,16,8,1,0,0,0 +25/09/2004,Middlesbrough,Chelsea,0,1,A,0,0,D,M Halsey,8,17,4,12,3,7,10,15,0,1,0,0 +25/09/2004,Newcastle,West Brom,3,1,H,0,0,D,M Riley,31,7,10,3,8,3,15,15,3,2,0,1 +25/09/2004,Tottenham,Man United,0,1,A,0,1,A,P Walton,11,6,5,4,3,3,9,10,1,0,0,0 +26/09/2004,Portsmouth,Everton,0,1,A,0,0,D,D Gallagher,11,9,6,2,3,6,12,16,1,1,0,0 +27/09/2004,Charlton,Blackburn,1,0,H,0,0,D,S Dunn,6,12,3,7,6,10,18,14,1,1,0,0 +02/10/2004,Arsenal,Charlton,4,0,H,1,0,H,M Dean,15,4,9,0,6,1,13,15,3,1,0,0 +02/10/2004,Blackburn,Aston Villa,2,2,D,1,1,D,P Walton,13,15,7,9,5,8,14,17,2,2,0,0 +02/10/2004,Everton,Tottenham,0,1,A,0,0,D,G Poll,10,8,4,2,11,2,9,13,1,2,0,0 +02/10/2004,Norwich,Portsmouth,2,2,D,0,1,A,M Messias,17,13,9,5,8,2,13,7,1,4,0,0 +02/10/2004,Southampton,Man City,0,0,D,0,0,D,U Rennie,16,21,11,11,6,14,13,11,0,0,0,0 +02/10/2004,West Brom,Bolton,2,1,H,0,0,D,M Clattenburg,13,16,7,7,6,5,18,15,0,0,0,0 +03/10/2004,Birmingham,Newcastle,2,2,D,1,1,D,H Webb,10,6,6,3,6,5,17,14,3,2,0,0 +03/10/2004,Chelsea,Liverpool,1,0,H,0,0,D,P Dowd,13,5,5,3,11,2,14,20,0,1,0,0 +03/10/2004,Man United,Middlesbrough,1,1,D,0,1,A,R Styles,22,13,10,9,10,2,8,13,1,3,0,0 +04/10/2004,Crystal Palace,Fulham,2,0,H,0,0,D,M Riley,20,8,10,5,10,3,10,10,1,1,0,1 +16/10/2004,Arsenal,Aston Villa,3,1,H,2,1,H,G Poll,18,5,15,2,6,3,11,22,2,4,0,0 +16/10/2004,Birmingham,Man United,0,0,D,0,0,D,M Halsey,14,12,6,7,3,3,16,11,1,0,0,0 +16/10/2004,Blackburn,Middlesbrough,0,4,A,0,0,D,M Riley,12,16,5,10,2,10,12,14,2,1,1,0 +16/10/2004,Bolton,Crystal Palace,1,0,H,1,0,H,N Barry,21,7,7,2,7,3,19,14,1,1,0,0 +16/10/2004,Everton,Southampton,1,0,H,0,0,D,B Knight,14,8,5,4,5,6,5,14,0,0,0,0 +16/10/2004,Fulham,Liverpool,2,4,A,2,0,H,S Bennett,5,10,3,3,3,6,12,14,1,3,0,1 +16/10/2004,Man City,Chelsea,1,0,H,1,0,H,H Webb,4,9,1,3,0,12,6,7,1,2,0,0 +16/10/2004,West Brom,Norwich,0,0,D,0,0,D,P Crossley,9,12,2,5,2,3,13,21,2,2,0,0 +17/10/2004,Charlton,Newcastle,1,1,D,0,1,A,P Walton,11,15,6,8,8,9,15,7,1,1,0,0 +18/10/2004,Portsmouth,Tottenham,1,0,H,0,0,D,U Rennie,11,16,6,7,3,5,12,11,0,0,0,0 +23/10/2004,Aston Villa,Fulham,2,0,H,1,0,H,P Dowd,11,7,5,1,8,4,21,14,0,1,0,0 +23/10/2004,Chelsea,Blackburn,4,0,H,2,0,H,G Poll,17,7,13,4,12,3,11,11,1,2,0,0 +23/10/2004,Crystal Palace,West Brom,3,0,H,2,0,H,M Messias,11,11,4,6,7,10,11,15,1,3,0,0 +23/10/2004,Liverpool,Charlton,2,0,H,0,0,D,A D'Urso,17,3,7,1,12,0,9,11,0,1,0,0 +23/10/2004,Norwich,Everton,2,3,A,0,2,A,M Clattenburg,22,13,10,9,11,4,8,11,0,2,0,0 +23/10/2004,Tottenham,Bolton,1,2,A,1,1,D,C Foy,8,6,5,4,2,3,14,17,0,0,0,0 +24/10/2004,Man United,Arsenal,2,0,H,0,0,D,M Riley,11,12,5,6,3,3,20,23,2,3,0,0 +24/10/2004,Middlesbrough,Portsmouth,1,1,D,0,1,A,M Atkinson,26,3,11,1,9,0,5,5,0,2,0,0 +24/10/2004,Newcastle,Man City,4,3,H,0,0,D,S Dunn,16,11,11,6,7,5,13,14,2,3,0,0 +24/10/2004,Southampton,Birmingham,0,0,D,0,0,D,M Dean,6,8,1,2,4,4,11,11,0,1,0,0 +30/10/2004,Arsenal,Southampton,2,2,D,0,0,D,M Messias,14,7,9,4,7,7,8,21,1,4,0,0 +30/10/2004,Birmingham,Crystal Palace,0,1,A,0,1,A,D Gallagher,16,4,11,1,11,3,8,14,1,1,0,0 +30/10/2004,Blackburn,Liverpool,2,2,D,2,1,H,R Styles,8,13,7,9,6,6,14,11,2,1,0,0 +30/10/2004,Charlton,Middlesbrough,1,2,A,0,1,A,M Halsey,8,11,7,7,3,4,14,8,3,0,0,0 +30/10/2004,Everton,Aston Villa,1,1,D,1,1,D,S Dunn,20,9,8,5,8,3,10,19,1,1,0,0 +30/10/2004,Fulham,Tottenham,2,0,H,1,0,H,A Wiley,8,5,4,1,5,4,11,9,0,1,0,0 +30/10/2004,Portsmouth,Man United,2,0,H,0,0,D,N Barry,11,20,7,9,3,9,19,9,0,2,0,0 +30/10/2004,West Brom,Chelsea,1,4,A,0,1,A,B Knight,12,19,9,9,2,6,12,8,1,0,0,0 +31/10/2004,Bolton,Newcastle,2,1,H,0,0,D,G Poll,17,5,11,4,4,5,14,11,1,1,0,0 +01/11/2004,Man City,Norwich,1,1,D,1,0,H,S Bennett,19,10,11,5,9,4,10,13,1,0,0,0 +06/11/2004,Aston Villa,Portsmouth,3,0,H,3,0,H,M Halsey,10,7,9,2,6,4,8,13,1,1,0,0 +06/11/2004,Chelsea,Everton,1,0,H,0,0,D,M Riley,12,3,3,1,16,3,10,15,1,3,0,0 +06/11/2004,Crystal Palace,Arsenal,1,1,D,0,0,D,M Dean,3,19,1,13,1,8,10,8,0,1,0,0 +06/11/2004,Liverpool,Birmingham,0,1,A,0,0,D,U Rennie,17,6,8,1,4,5,19,17,3,1,0,0 +06/11/2004,Norwich,Blackburn,1,1,D,0,0,D,S Dunn,12,15,5,9,7,4,18,12,1,0,0,1 +06/11/2004,Southampton,West Brom,2,2,D,1,2,A,S Bennett,11,10,6,5,5,1,13,14,2,0,0,0 +06/11/2004,Tottenham,Charlton,2,3,A,0,2,A,N Barry,19,3,11,3,5,1,6,13,0,2,0,1 +07/11/2004,Man United,Man City,0,0,D,0,0,D,G Poll,15,3,10,1,16,1,10,10,2,2,1,0 +07/11/2004,Middlesbrough,Bolton,1,1,D,0,0,D,P Walton,14,7,5,4,3,3,13,12,1,3,0,1 +07/11/2004,Newcastle,Fulham,1,4,A,0,1,A,H Webb,26,11,20,7,19,0,12,16,1,1,0,0 +13/11/2004,Birmingham,Everton,0,1,A,0,0,D,R Styles,15,9,4,8,5,3,13,12,0,1,1,0 +13/11/2004,Bolton,Aston Villa,1,2,A,1,1,D,A D'Urso,15,10,7,5,9,5,10,17,1,1,0,0 +13/11/2004,Charlton,Norwich,4,0,H,2,0,H,A Marriner,16,8,11,4,1,5,14,11,0,3,0,0 +13/11/2004,Fulham,Chelsea,1,4,A,0,1,A,U Rennie,7,16,2,11,1,5,13,13,0,2,0,0 +13/11/2004,Liverpool,Crystal Palace,3,2,H,2,1,H,P Dowd,18,4,5,4,7,1,13,17,1,3,0,0 +13/11/2004,Man City,Blackburn,1,1,D,1,0,H,M Clattenburg,8,11,7,2,3,5,19,13,1,3,1,0 +13/11/2004,Southampton,Portsmouth,2,1,H,1,1,D,G Poll,11,13,5,6,4,8,14,15,1,3,0,0 +13/11/2004,Tottenham,Arsenal,4,5,A,1,1,D,S Bennett,10,10,7,6,3,4,20,9,3,0,0,0 +14/11/2004,Newcastle,Man United,1,3,A,0,1,A,M Dean,13,12,7,7,2,7,11,16,2,0,0,0 +14/11/2004,West Brom,Middlesbrough,1,2,A,1,1,D,S Dunn,16,11,6,7,7,10,14,14,0,0,0,0 +20/11/2004,Arsenal,West Brom,1,1,D,0,0,D,H Webb,11,4,4,2,12,3,9,12,0,1,0,0 +20/11/2004,Chelsea,Bolton,2,2,D,1,0,H,D Gallagher,17,10,11,5,9,2,18,11,1,0,0,0 +20/11/2004,Crystal Palace,Newcastle,0,2,A,0,0,D,M Halsey,7,17,5,9,3,5,13,10,1,3,0,0 +20/11/2004,Everton,Fulham,1,0,H,0,0,D,G Poll,13,6,7,4,12,7,11,24,0,1,0,0 +20/11/2004,Man United,Charlton,2,0,H,1,0,H,R Styles,20,5,11,2,6,3,8,13,0,0,0,0 +20/11/2004,Middlesbrough,Liverpool,2,0,H,1,0,H,S Bennett,14,17,6,10,3,5,14,10,3,2,0,0 +20/11/2004,Norwich,Southampton,2,1,H,1,1,D,M Riley,9,14,8,7,4,6,19,14,1,1,0,0 +20/11/2004,Portsmouth,Man City,1,3,A,1,1,D,M Messias,9,18,7,13,8,11,8,14,3,0,0,0 +21/11/2004,Blackburn,Birmingham,3,3,D,1,3,A,N Barry,15,8,10,5,6,9,12,9,1,1,0,0 +22/11/2004,Aston Villa,Tottenham,1,0,H,0,0,D,C Foy,9,11,5,6,5,5,17,11,0,0,0,0 +27/11/2004,Birmingham,Norwich,1,1,D,1,0,H,G Poll,16,7,10,3,6,1,14,12,2,1,0,0 +27/11/2004,Bolton,Portsmouth,0,1,A,0,1,A,S Dunn,19,8,10,4,7,6,13,14,0,1,0,0 +27/11/2004,Charlton,Chelsea,0,4,A,0,1,A,M Clattenburg,8,16,4,12,7,10,13,11,0,1,0,0 +27/11/2004,Fulham,Blackburn,0,2,A,0,1,A,R Styles,12,17,6,4,6,6,9,13,2,1,1,0 +27/11/2004,Man City,Aston Villa,2,0,H,2,0,H,M Riley,11,7,6,3,8,8,14,19,1,1,0,1 +27/11/2004,Southampton,Crystal Palace,2,2,D,0,0,D,P Walton,15,11,8,8,4,8,7,11,0,3,0,0 +27/11/2004,West Brom,Man United,0,3,A,0,0,D,S Bennett,3,18,2,9,1,8,13,11,0,0,0,0 +28/11/2004,Liverpool,Arsenal,2,1,H,1,0,H,A Wiley,11,3,7,2,6,0,6,16,0,2,0,0 +28/11/2004,Newcastle,Everton,1,1,D,1,0,H,N Barry,19,7,9,3,8,4,14,19,1,3,0,0 +28/11/2004,Tottenham,Middlesbrough,2,0,H,0,0,D,P Dowd,14,8,9,3,5,7,11,13,1,2,0,1 +04/12/2004,Arsenal,Birmingham,3,0,H,1,0,H,D Gallagher,7,2,4,1,4,2,7,15,0,1,0,0 +04/12/2004,Aston Villa,Liverpool,1,1,D,1,1,D,M Halsey,3,12,2,7,3,9,10,10,1,0,0,0 +04/12/2004,Blackburn,Tottenham,0,1,A,0,0,D,M Dean,12,10,7,8,7,5,11,11,3,1,0,0 +04/12/2004,Chelsea,Newcastle,4,0,H,0,0,D,R Styles,14,9,9,5,3,2,12,15,1,2,0,0 +04/12/2004,Everton,Bolton,3,2,H,1,1,D,H Webb,12,10,3,6,4,4,14,18,0,3,0,0 +04/12/2004,Man United,Southampton,3,0,H,0,0,D,B Knight,34,4,14,0,16,2,5,10,0,1,0,0 +04/12/2004,Norwich,Fulham,0,1,A,0,1,A,A Wiley,10,15,4,9,5,4,22,22,1,3,0,0 +04/12/2004,Portsmouth,West Brom,3,2,H,1,2,A,P Walton,14,10,10,4,8,3,14,14,0,2,0,0 +05/12/2004,Crystal Palace,Charlton,0,1,A,0,0,D,M Messias,5,11,3,7,5,3,16,14,2,3,0,0 +06/12/2004,Middlesbrough,Man City,3,2,H,1,1,D,G Poll,11,9,7,6,6,4,14,18,0,3,0,0 +11/12/2004,Crystal Palace,Blackburn,0,0,D,0,0,D,A Wiley,8,16,1,8,2,1,19,20,3,3,0,1 +11/12/2004,Everton,Liverpool,1,0,H,0,0,D,S Bennett,6,16,2,5,2,7,12,14,2,3,0,0 +11/12/2004,Man City,Tottenham,0,1,A,0,0,D,D Gallagher,8,5,3,2,6,2,8,9,1,2,0,0 +11/12/2004,Newcastle,Portsmouth,1,1,D,1,1,D,M Riley,15,6,2,3,6,2,12,15,1,2,0,0 +11/12/2004,Norwich,Bolton,3,2,H,1,2,A,B Knight,9,4,4,3,4,4,10,26,1,3,0,0 +11/12/2004,Southampton,Middlesbrough,2,2,D,1,0,H,N Barry,16,15,5,7,4,6,11,12,1,1,0,0 +11/12/2004,West Brom,Charlton,0,1,A,0,1,A,C Foy,7,8,4,2,6,6,16,13,0,0,0,0 +12/12/2004,Arsenal,Chelsea,2,2,D,2,1,H,G Poll,10,10,5,5,4,6,11,17,1,3,0,0 +12/12/2004,Aston Villa,Birmingham,1,2,A,0,2,A,S Dunn,9,9,3,5,6,0,9,28,2,4,0,0 +13/12/2004,Fulham,Man United,1,1,D,0,1,A,P Dowd,7,14,3,6,6,7,15,11,2,1,0,0 +14/12/2004,Liverpool,Portsmouth,1,1,D,0,0,D,M Clattenburg,12,7,7,4,6,4,6,9,0,0,0,0 +18/12/2004,Birmingham,West Brom,4,0,H,3,0,H,M Riley,7,11,5,9,2,2,14,20,0,4,0,0 +18/12/2004,Blackburn,Everton,0,0,D,0,0,D,M Halsey,13,5,9,2,7,3,25,21,1,2,0,0 +18/12/2004,Bolton,Man City,0,1,A,0,0,D,U Rennie,14,6,6,2,8,1,17,12,0,0,0,0 +18/12/2004,Chelsea,Norwich,4,0,H,3,0,H,M Dean,12,1,8,0,7,3,12,11,0,0,0,0 +18/12/2004,Man United,Crystal Palace,5,2,H,2,1,H,S Dunn,19,4,14,3,15,3,12,8,0,1,0,0 +18/12/2004,Middlesbrough,Aston Villa,3,0,H,1,0,H,A D'Urso,9,14,8,6,5,6,6,18,0,1,0,0 +18/12/2004,Tottenham,Southampton,5,1,H,3,0,H,P Dowd,18,7,12,4,6,3,9,13,0,2,0,0 +19/12/2004,Liverpool,Newcastle,3,1,H,2,1,H,G Poll,15,7,5,3,5,5,12,19,0,3,0,1 +19/12/2004,Portsmouth,Arsenal,0,1,A,0,0,D,H Webb,11,13,5,5,5,7,13,9,0,1,0,0 +20/12/2004,Charlton,Fulham,2,1,H,1,0,H,S Bennett,8,6,5,4,5,3,14,16,1,2,0,0 +26/12/2004,Arsenal,Fulham,2,0,H,1,0,H,B Knight,11,2,8,1,7,6,12,9,1,2,0,0 +26/12/2004,Birmingham,Middlesbrough,2,0,H,2,0,H,S Bennett,11,9,10,6,5,3,17,9,1,0,0,0 +26/12/2004,Blackburn,Newcastle,2,2,D,1,2,A,M Messias,21,5,11,3,7,5,18,19,1,2,0,0 +26/12/2004,Chelsea,Aston Villa,1,0,H,1,0,H,P Walton,14,9,7,7,6,3,12,13,3,3,0,0 +26/12/2004,Crystal Palace,Portsmouth,0,1,A,0,0,D,N Barry,18,11,10,6,9,7,10,14,0,2,0,0 +26/12/2004,Everton,Man City,2,1,H,1,1,D,P Dowd,9,10,6,6,4,4,14,21,1,2,0,1 +26/12/2004,Man United,Bolton,2,0,H,1,0,H,D Gallagher,14,8,7,1,9,0,12,14,0,0,0,0 +26/12/2004,Norwich,Tottenham,0,2,A,0,0,D,M Riley,13,16,8,9,10,8,11,9,1,0,0,0 +26/12/2004,Southampton,Charlton,0,0,D,0,0,D,S Dunn,12,15,4,5,5,11,7,12,1,0,0,0 +26/12/2004,West Brom,Liverpool,0,5,A,0,1,A,R Styles,2,16,1,8,4,11,12,13,0,0,1,0 +28/12/2004,Aston Villa,Man United,0,1,A,0,1,A,G Poll,8,15,6,7,6,9,14,10,0,3,0,0 +28/12/2004,Bolton,Blackburn,0,1,A,0,1,A,R Styles,13,7,5,5,5,1,13,10,2,2,0,0 +28/12/2004,Charlton,Everton,2,0,H,0,0,D,M Riley,12,5,8,3,8,2,13,11,0,0,0,1 +28/12/2004,Fulham,Birmingham,2,3,A,1,2,A,M Clattenburg,10,6,7,5,7,3,9,13,1,0,0,0 +28/12/2004,Liverpool,Southampton,1,0,H,1,0,H,M Halsey,19,3,11,2,7,3,6,12,0,3,0,0 +28/12/2004,Man City,West Brom,1,1,D,1,0,H,A D'Urso,11,0,4,0,11,0,7,10,0,0,0,1 +28/12/2004,Middlesbrough,Norwich,2,0,H,0,0,D,H Webb,17,10,11,2,8,4,13,10,1,1,0,0 +28/12/2004,Portsmouth,Chelsea,0,2,A,0,0,D,A Wiley,4,12,2,9,5,4,12,15,1,3,0,0 +28/12/2004,Tottenham,Crystal Palace,1,1,D,0,0,D,U Rennie,17,11,14,7,5,6,10,15,1,0,0,0 +29/12/2004,Newcastle,Arsenal,0,1,A,0,1,A,S Bennett,9,13,5,6,4,8,10,21,1,4,0,0 +01/01/2005,Aston Villa,Blackburn,1,0,H,0,0,D,H Webb,11,9,3,4,6,7,9,9,1,0,0,0 +01/01/2005,Bolton,West Brom,1,1,D,0,1,A,M Dean,25,11,15,7,7,3,13,15,0,1,0,0 +01/01/2005,Charlton,Arsenal,1,3,A,1,1,D,M Halsey,7,8,4,5,1,2,13,15,1,1,0,0 +01/01/2005,Fulham,Crystal Palace,3,1,H,1,1,D,D Gallagher,12,7,6,3,4,2,11,18,1,3,0,0 +01/01/2005,Liverpool,Chelsea,0,1,A,0,0,D,M Riley,14,8,7,7,3,2,14,16,2,2,0,0 +01/01/2005,Man City,Southampton,2,1,H,2,0,H,C Foy,15,6,8,1,9,8,12,9,0,0,0,0 +01/01/2005,Middlesbrough,Man United,0,2,A,0,1,A,A Wiley,6,13,2,7,10,4,13,9,1,0,0,0 +01/01/2005,Newcastle,Birmingham,2,1,H,2,0,H,R Styles,8,10,4,8,6,5,8,14,1,2,0,0 +01/01/2005,Portsmouth,Norwich,1,1,D,0,1,A,P Dowd,14,6,6,5,7,5,13,6,3,0,0,1 +01/01/2005,Tottenham,Everton,5,2,H,2,1,H,S Dunn,14,6,12,3,6,4,8,13,0,0,0,0 +03/01/2005,Blackburn,Charlton,1,0,H,1,0,H,A Wiley,16,9,11,6,6,8,11,14,2,3,0,0 +03/01/2005,Crystal Palace,Aston Villa,2,0,H,1,0,H,A D'Urso,14,12,9,6,7,7,8,14,2,3,0,0 +03/01/2005,Norwich,Liverpool,1,2,A,0,0,D,H Webb,7,11,4,6,5,2,16,19,2,3,0,0 +03/01/2005,West Brom,Newcastle,0,0,D,0,0,D,N Barry,8,12,5,7,1,6,9,7,2,0,0,0 +04/01/2005,Arsenal,Man City,1,1,D,0,1,A,R Styles,13,5,9,4,12,5,10,13,3,3,0,0 +04/01/2005,Birmingham,Bolton,1,2,A,0,1,A,U Rennie,14,11,8,6,6,5,12,12,2,2,0,0 +04/01/2005,Chelsea,Middlesbrough,2,0,H,2,0,H,S Bennett,14,2,10,1,4,3,15,9,2,1,0,0 +04/01/2005,Everton,Portsmouth,2,1,H,1,1,D,P Walton,17,9,7,6,8,1,5,9,0,0,0,0 +04/01/2005,Man United,Tottenham,0,0,D,0,0,D,M Clattenburg,23,7,16,5,8,2,11,13,1,2,0,0 +05/01/2005,Southampton,Fulham,3,3,D,2,2,D,G Poll,8,12,5,7,2,10,16,16,2,2,0,0 +15/01/2005,Aston Villa,Norwich,3,0,H,2,0,H,M Riley,22,7,10,1,7,5,15,12,0,0,0,0 +15/01/2005,Bolton,Arsenal,1,0,H,1,0,H,M Clattenburg,9,11,3,7,8,7,8,8,2,1,0,0 +15/01/2005,Charlton,Birmingham,3,1,H,1,0,H,C Foy,10,10,6,6,4,4,11,10,0,2,0,0 +15/01/2005,Liverpool,Man United,0,1,A,0,1,A,S Bennett,13,5,5,2,9,4,12,25,2,4,0,1 +15/01/2005,Man City,Crystal Palace,3,1,H,2,1,H,A Marriner,16,7,9,2,7,7,10,11,2,1,0,0 +15/01/2005,Newcastle,Southampton,2,1,H,2,1,H,U Rennie,22,9,17,6,14,4,12,20,0,2,0,0 +15/01/2005,Portsmouth,Blackburn,0,1,A,0,0,D,A D'Urso,11,14,1,6,5,6,13,23,2,6,2,0 +15/01/2005,Tottenham,Chelsea,0,2,A,0,1,A,G Poll,12,12,4,5,4,7,14,12,2,4,0,0 +16/01/2005,Fulham,West Brom,1,0,H,0,0,D,P Walton,8,7,4,6,7,5,12,12,3,1,0,0 +16/01/2005,Middlesbrough,Everton,1,1,D,1,0,H,D Gallagher,18,13,12,5,9,6,12,10,1,2,0,0 +22/01/2005,Birmingham,Fulham,1,2,A,0,0,D,P Dowd,8,6,5,3,5,3,16,11,1,3,0,0 +22/01/2005,Chelsea,Portsmouth,3,0,H,3,0,H,M Riley,14,9,9,5,2,3,2,9,0,0,0,0 +22/01/2005,Crystal Palace,Tottenham,3,0,H,0,0,D,C Foy,8,12,7,6,5,4,9,14,1,2,0,0 +22/01/2005,Everton,Charlton,0,1,A,0,1,A,H Webb,12,4,7,3,9,5,13,10,1,2,0,0 +22/01/2005,Man United,Aston Villa,3,1,H,1,0,H,M Halsey,18,10,11,6,4,2,11,21,2,5,0,0 +22/01/2005,Norwich,Middlesbrough,4,4,D,1,1,D,M Messias,17,22,9,15,5,8,11,16,0,3,0,0 +22/01/2005,Southampton,Liverpool,2,0,H,2,0,H,A Wiley,11,11,7,2,6,3,10,12,1,2,0,0 +22/01/2005,West Brom,Man City,2,0,H,1,0,H,G Poll,13,11,6,5,3,11,9,12,2,0,0,0 +23/01/2005,Arsenal,Newcastle,1,0,H,1,0,H,S Dunn,13,4,8,1,11,1,17,19,2,3,0,0 +24/01/2005,Blackburn,Bolton,0,1,A,0,0,D,S Bennett,9,6,4,3,7,7,16,19,1,4,0,0 +01/02/2005,Arsenal,Man United,2,4,A,2,1,H,G Poll,11,10,10,9,6,7,20,16,2,4,0,1 +01/02/2005,Bolton,Tottenham,3,1,H,0,0,D,M Dean,15,11,9,5,4,6,9,17,0,2,0,1 +01/02/2005,Charlton,Liverpool,1,2,A,1,0,H,N Barry,4,19,3,7,3,7,11,15,0,0,0,0 +01/02/2005,Portsmouth,Middlesbrough,2,1,H,1,1,D,P Crossley,14,9,5,2,9,5,9,8,1,2,0,0 +01/02/2005,West Brom,Crystal Palace,2,2,D,0,0,D,D Gallagher,12,4,7,4,8,4,17,11,1,0,0,1 +02/02/2005,Birmingham,Southampton,2,1,H,2,0,H,P Walton,12,6,4,3,8,1,11,18,0,2,0,0 +02/02/2005,Blackburn,Chelsea,0,1,A,0,1,A,U Rennie,11,6,2,3,4,4,24,15,2,2,0,0 +02/02/2005,Everton,Norwich,1,0,H,0,0,D,A Wiley,18,11,4,6,3,3,12,16,1,0,0,0 +02/02/2005,Fulham,Aston Villa,1,1,D,0,0,D,C Foy,12,12,3,2,4,8,12,17,1,3,0,0 +02/02/2005,Man City,Newcastle,1,1,D,0,1,A,A D'Urso,5,3,1,1,3,1,11,18,2,3,0,0 +05/02/2005,Aston Villa,Arsenal,1,3,A,0,3,A,S Bennett,5,15,1,6,5,2,13,11,2,0,0,0 +05/02/2005,Crystal Palace,Bolton,0,1,A,0,1,A,G Poll,17,9,7,6,6,4,16,10,0,1,0,0 +05/02/2005,Liverpool,Fulham,3,1,H,1,1,D,R Styles,10,6,7,2,4,3,12,13,1,4,0,0 +05/02/2005,Man United,Birmingham,2,0,H,0,0,D,D Gallagher,17,7,11,1,5,2,10,16,1,3,0,0 +05/02/2005,Middlesbrough,Blackburn,1,0,H,1,0,H,M Riley,13,6,7,3,8,10,14,10,1,2,1,0 +05/02/2005,Newcastle,Charlton,1,1,D,0,0,D,M Halsey,21,8,10,2,7,2,10,12,1,1,0,0 +05/02/2005,Norwich,West Brom,3,2,H,1,1,D,C Foy,13,15,7,8,3,3,18,13,0,3,0,0 +05/02/2005,Tottenham,Portsmouth,3,1,H,1,1,D,S Dunn,18,6,10,3,9,2,10,8,0,0,0,0 +06/02/2005,Chelsea,Man City,0,0,D,0,0,D,H Webb,11,5,8,2,7,1,9,11,2,1,0,0 +06/02/2005,Southampton,Everton,2,2,D,1,1,D,A D'Urso,21,8,9,4,8,3,15,11,0,2,0,0 +12/02/2005,Birmingham,Liverpool,2,0,H,2,0,H,H Webb,5,6,3,2,10,4,16,10,2,0,0,0 +12/02/2005,Blackburn,Norwich,3,0,H,2,0,H,S Dunn,18,9,13,6,12,3,15,14,2,1,0,0 +12/02/2005,Bolton,Middlesbrough,0,0,D,0,0,D,A Wiley,13,5,8,2,3,2,11,15,0,1,0,0 +12/02/2005,Everton,Chelsea,0,1,A,0,0,D,M Riley,4,25,2,11,3,9,14,9,1,2,1,0 +12/02/2005,Portsmouth,Aston Villa,1,2,A,1,1,D,D Gallagher,9,6,3,4,6,3,14,22,2,1,0,0 +13/02/2005,Man City,Man United,0,2,A,0,0,D,S Bennett,9,10,3,3,4,6,9,14,2,3,0,0 +14/02/2005,Arsenal,Crystal Palace,5,1,H,3,0,H,R Styles,17,6,12,3,5,1,8,17,1,2,0,0 +22/02/2005,West Brom,Southampton,0,0,D,0,0,D,M Riley,9,11,4,7,7,3,15,15,2,3,0,0 +26/02/2005,Aston Villa,Everton,1,3,A,0,1,A,G Poll,9,15,6,9,3,7,22,17,3,0,0,0 +26/02/2005,Crystal Palace,Birmingham,2,0,H,1,0,H,P Dowd,8,9,2,5,4,7,23,15,3,3,0,0 +26/02/2005,Man United,Portsmouth,2,1,H,1,0,H,M Halsey,13,8,6,4,4,3,15,15,1,2,0,0 +26/02/2005,Southampton,Arsenal,1,1,D,0,1,A,A Wiley,10,17,3,12,3,5,9,13,1,1,1,1 +26/02/2005,Tottenham,Fulham,2,0,H,0,0,D,N Barry,18,8,8,5,13,2,6,13,0,1,0,0 +27/02/2005,Middlesbrough,Charlton,2,2,D,0,1,A,M Riley,21,5,18,2,10,1,10,15,0,3,0,0 +27/02/2005,Newcastle,Bolton,2,1,H,1,1,D,S Dunn,14,4,9,2,6,3,12,19,0,2,0,0 +28/02/2005,Norwich,Man City,2,3,A,2,2,D,R Styles,5,17,4,10,3,12,12,7,2,2,1,0 +05/03/2005,Arsenal,Portsmouth,3,0,H,1,0,H,C Foy,11,5,6,2,7,3,12,15,0,2,0,0 +05/03/2005,Aston Villa,Middlesbrough,2,0,H,0,0,D,R Styles,8,5,4,1,5,4,8,6,1,2,0,0 +05/03/2005,Crystal Palace,Man United,0,0,D,0,0,D,M Clattenburg,2,16,1,9,2,7,12,9,5,4,1,0 +05/03/2005,Fulham,Charlton,0,0,D,0,0,D,S Bennett,10,9,5,6,5,5,9,12,3,2,0,0 +05/03/2005,Newcastle,Liverpool,1,0,H,0,0,D,H Webb,13,6,4,3,5,4,11,16,2,2,0,0 +05/03/2005,Norwich,Chelsea,1,3,A,0,1,A,M Halsey,8,23,5,13,7,7,14,15,1,2,0,0 +05/03/2005,Southampton,Tottenham,1,0,H,0,0,D,P Walton,9,13,4,8,4,10,7,11,1,2,0,0 +06/03/2005,Everton,Blackburn,0,1,A,0,0,D,P Dowd,12,11,4,5,6,3,15,20,3,2,0,0 +06/03/2005,West Brom,Birmingham,2,0,H,0,0,D,S Dunn,12,7,7,0,13,3,16,18,1,1,0,0 +07/03/2005,Man City,Bolton,0,1,A,0,1,A,A Wiley,8,11,3,3,5,5,10,21,1,2,0,0 +15/03/2005,Chelsea,West Brom,1,0,H,1,0,H,N Barry,23,7,11,4,8,3,13,9,0,1,0,0 +16/03/2005,Charlton,Tottenham,2,0,H,1,0,H,C Foy,5,7,2,4,5,2,15,16,1,4,0,0 +16/03/2005,Liverpool,Blackburn,0,0,D,0,0,D,B Knight,7,5,2,2,11,3,12,13,1,1,0,0 +19/03/2005,Blackburn,Arsenal,0,1,A,0,1,A,G Poll,9,10,2,8,4,6,21,8,3,1,0,0 +19/03/2005,Bolton,Norwich,1,0,H,1,0,H,P Walton,16,6,10,2,7,6,20,16,3,1,0,0 +19/03/2005,Charlton,West Brom,1,4,A,1,1,D,M Halsey,8,14,5,9,3,9,11,8,0,0,1,0 +19/03/2005,Chelsea,Crystal Palace,4,1,H,1,1,D,P Dowd,20,6,13,3,10,4,13,16,1,0,0,0 +19/03/2005,Man United,Fulham,1,0,H,1,0,H,A D'Urso,19,10,12,6,18,3,7,11,0,1,0,0 +19/03/2005,Portsmouth,Newcastle,1,1,D,1,1,D,M Messias,13,11,6,6,4,3,15,19,5,4,0,0 +19/03/2005,Tottenham,Man City,2,1,H,1,1,D,B Knight,12,11,9,7,6,4,13,13,2,3,0,0 +20/03/2005,Birmingham,Aston Villa,2,0,H,0,0,D,M Riley,10,5,4,4,2,3,12,20,3,3,0,0 +20/03/2005,Liverpool,Everton,2,1,H,2,0,H,R Styles,15,4,8,2,5,1,13,15,1,4,1,0 +20/03/2005,Middlesbrough,Southampton,1,3,A,1,1,D,U Rennie,14,12,8,7,3,6,20,17,0,0,0,0 +02/04/2005,Arsenal,Norwich,4,1,H,2,1,H,A Wiley,21,3,15,1,10,3,8,11,1,0,0,0 +02/04/2005,Birmingham,Tottenham,1,1,D,0,0,D,H Webb,14,9,7,5,3,3,6,10,0,1,0,0 +02/04/2005,Charlton,Man City,2,2,D,1,2,A,R Styles,9,16,3,8,6,7,6,8,1,0,0,0 +02/04/2005,Crystal Palace,Middlesbrough,0,1,A,0,1,A,S Dunn,23,7,13,5,8,3,6,19,0,4,0,0 +02/04/2005,Liverpool,Bolton,1,0,H,0,0,D,S Bennett,10,13,7,5,4,12,6,13,1,3,0,0 +02/04/2005,Man United,Blackburn,0,0,D,0,0,D,M Riley,23,9,9,3,9,3,21,18,3,2,0,0 +02/04/2005,Newcastle,Aston Villa,0,3,A,0,1,A,B Knight,20,8,12,5,8,6,14,24,1,2,3,0 +02/04/2005,Southampton,Chelsea,1,3,A,0,2,A,M Halsey,4,12,3,8,4,3,11,10,0,2,0,0 +03/04/2005,Fulham,Portsmouth,3,1,H,0,1,A,M Clattenburg,12,8,7,4,5,5,14,10,3,3,0,0 +03/04/2005,West Brom,Everton,1,0,H,0,0,D,G Poll,8,8,4,2,7,7,15,20,1,2,0,0 +09/04/2005,Blackburn,Southampton,3,0,H,1,0,H,N Barry,15,9,8,3,4,2,19,14,2,1,0,0 +09/04/2005,Bolton,Fulham,3,1,H,2,0,H,D Gallagher,15,7,10,4,4,6,6,14,0,2,0,1 +09/04/2005,Chelsea,Birmingham,1,1,D,0,0,D,C Foy,19,2,12,1,8,5,17,12,2,2,0,0 +09/04/2005,Man City,Liverpool,1,0,H,0,0,D,M Riley,11,6,8,1,5,2,8,11,1,1,0,0 +09/04/2005,Middlesbrough,Arsenal,0,1,A,0,0,D,P Dowd,9,7,6,1,5,3,11,9,1,1,0,0 +09/04/2005,Norwich,Man United,2,0,H,0,0,D,H Webb,7,16,5,8,3,4,19,18,2,2,0,0 +09/04/2005,Portsmouth,Charlton,4,2,H,2,2,D,G Poll,13,8,9,4,5,4,11,14,1,3,0,0 +10/04/2005,Aston Villa,West Brom,1,1,D,1,0,H,R Styles,14,7,7,5,5,5,6,10,1,2,1,1 +10/04/2005,Everton,Crystal Palace,4,0,H,1,0,H,U Rennie,13,11,8,3,4,9,10,14,1,1,0,0 +10/04/2005,Tottenham,Newcastle,1,0,H,1,0,H,S Bennett,19,6,6,3,4,7,9,15,2,2,0,0 +16/04/2005,Birmingham,Portsmouth,0,0,D,0,0,D,P Walton,9,5,4,4,7,5,16,12,1,2,0,0 +16/04/2005,Charlton,Bolton,1,2,A,1,1,D,A Wiley,18,11,10,5,8,2,10,14,0,1,0,0 +16/04/2005,Crystal Palace,Norwich,3,3,D,1,1,D,R Styles,12,12,10,8,7,3,17,21,2,2,0,0 +16/04/2005,Fulham,Man City,1,1,D,0,1,A,N Barry,8,7,3,3,6,8,9,19,0,1,0,0 +16/04/2005,Liverpool,Tottenham,2,2,D,1,1,D,M Halsey,18,9,7,6,12,3,5,7,0,0,0,0 +16/04/2005,Southampton,Aston Villa,2,3,A,2,0,H,A D'Urso,16,11,8,7,5,8,10,17,1,2,0,0 +19/04/2005,Bolton,Southampton,1,1,D,1,0,H,M Clattenburg,22,5,16,4,5,2,9,9,2,0,0,0 +19/04/2005,Middlesbrough,Fulham,1,1,D,0,0,D,R Styles,8,7,4,2,2,6,11,12,1,0,0,0 +20/04/2005,Aston Villa,Charlton,0,0,D,0,0,D,B Knight,13,12,5,3,6,5,14,9,0,1,0,0 +20/04/2005,Blackburn,Crystal Palace,1,0,H,1,0,H,P Walton,18,4,7,3,11,6,11,19,3,2,0,0 +20/04/2005,Chelsea,Arsenal,0,0,D,0,0,D,S Bennett,9,6,3,4,2,2,10,16,1,3,0,0 +20/04/2005,Everton,Man United,1,0,H,0,0,D,P Dowd,7,16,3,11,4,5,18,17,3,2,0,2 +20/04/2005,Man City,Birmingham,3,0,H,0,0,D,M Atkinson,8,5,4,1,8,3,11,9,0,1,0,0 +20/04/2005,Norwich,Newcastle,2,1,H,0,0,D,A Marriner,15,24,8,10,6,6,12,11,0,0,0,0 +20/04/2005,Portsmouth,Liverpool,1,2,A,1,2,A,H Webb,8,19,6,12,5,4,10,8,1,0,0,0 +20/04/2005,Tottenham,West Brom,1,1,D,0,1,A,C Foy,15,10,5,4,7,3,12,12,2,0,0,0 +23/04/2005,Aston Villa,Bolton,1,1,D,1,0,H,โ€ U Rennie,13,12,10,6,7,5,17,13,0,2,0,0 +23/04/2005,Blackburn,Man City,0,0,D,0,0,D,โ€ C Foy,11,7,5,1,6,2,20,17,0,0,0,0 +23/04/2005,Chelsea,Fulham,3,1,H,1,1,D,โ€ A Wiley,12,10,7,4,4,6,7,11,1,1,0,0 +23/04/2005,Crystal Palace,Liverpool,1,0,H,1,0,H,โ€ D Gallagher,6,9,3,7,4,1,19,14,2,5,0,0 +23/04/2005,Everton,Birmingham,1,1,D,0,1,A,โ€ A D'Urso,11,9,6,4,9,5,18,18,2,3,0,0 +23/04/2005,Middlesbrough,West Brom,4,0,H,3,0,H,โ€ H Webb,12,15,8,9,1,4,12,12,1,1,0,0 +23/04/2005,Norwich,Charlton,1,0,H,0,0,D,โ€ M Atkinson,13,11,6,6,7,8,8,9,1,1,0,0 +24/04/2005,Man United,Newcastle,2,1,H,0,1,A,โ€ N Barry,18,11,10,5,9,4,8,20,1,0,0,0 +24/04/2005,Portsmouth,Southampton,4,1,H,4,1,H,โ€ S Dunn,18,11,8,8,4,7,15,11,2,1,0,0 +25/04/2005,Arsenal,Tottenham,1,0,H,1,0,H,M Riley,12,7,3,1,4,7,17,15,0,4,0,0 +26/04/2005,West Brom,Blackburn,1,1,D,1,0,H,S Bennett,9,14,3,7,6,4,13,15,3,4,0,0 +27/04/2005,Newcastle,Middlesbrough,0,0,D,0,0,D,M Halsey,11,5,5,3,7,1,8,9,1,2,0,0 +30/04/2005,Birmingham,Blackburn,2,1,H,0,1,A,B Knight,10,5,6,1,6,1,15,16,2,1,0,0 +30/04/2005,Bolton,Chelsea,0,2,A,0,0,D,S Dunn,14,8,6,5,7,2,17,14,4,1,0,0 +30/04/2005,Fulham,Everton,2,0,H,2,0,H,S Bennett,11,11,6,4,3,7,18,16,1,3,1,0 +30/04/2005,Liverpool,Middlesbrough,1,1,D,0,1,A,P Dowd,12,6,6,3,7,4,10,20,2,3,0,0 +30/04/2005,Man City,Portsmouth,2,0,H,2,0,H,A Marriner,13,7,7,3,7,5,14,6,0,1,0,0 +30/04/2005,Newcastle,Crystal Palace,0,0,D,0,0,D,A Wiley,16,7,9,3,5,2,16,15,1,1,0,0 +30/04/2005,Southampton,Norwich,4,3,H,3,3,D,G Poll,15,14,9,7,6,8,18,13,3,2,0,0 +01/05/2005,Charlton,Man United,0,4,A,0,2,A,D Gallagher,6,18,4,12,1,7,6,15,0,2,1,0 +01/05/2005,Tottenham,Aston Villa,5,1,H,3,1,H,M Clattenburg,20,4,14,3,9,3,13,17,1,2,0,0 +02/05/2005,West Brom,Arsenal,0,2,A,0,0,D,N Barry,7,8,3,5,9,0,12,11,0,0,0,0 +04/05/2005,Fulham,Newcastle,1,3,A,0,1,A,G Poll,6,4,3,3,1,2,11,13,1,1,0,0 +07/05/2005,Aston Villa,Man City,1,2,A,0,2,A,R Beeby,15,10,8,4,6,2,8,18,1,1,0,0 +07/05/2005,Blackburn,Fulham,1,3,A,1,1,D,P Dowd,13,8,6,6,8,5,11,23,2,2,1,1 +07/05/2005,Chelsea,Charlton,1,0,H,0,0,D,M Riley,15,6,6,3,2,5,7,13,0,0,0,0 +07/05/2005,Crystal Palace,Southampton,2,2,D,1,1,D,H Webb,14,7,6,3,5,2,16,18,3,1,1,1 +07/05/2005,Everton,Newcastle,2,0,H,1,0,H,B Knight,8,9,6,4,3,2,22,11,3,1,0,1 +07/05/2005,Man United,West Brom,1,1,D,1,0,H,M Halsey,27,2,14,0,12,0,10,13,1,0,0,0 +07/05/2005,Middlesbrough,Tottenham,1,0,H,1,0,H,D Gallagher,16,5,8,2,4,3,14,10,1,3,0,0 +07/05/2005,Norwich,Birmingham,1,0,H,1,0,H,S Bennett,8,22,4,9,1,7,20,10,1,5,0,1 +07/05/2005,Portsmouth,Bolton,1,1,D,0,1,A,M Messias,9,12,4,4,2,9,12,23,0,2,0,0 +08/05/2005,Arsenal,Liverpool,3,1,H,2,0,H,G Poll,11,11,8,6,1,4,13,10,0,2,0,0 +10/05/2005,Man United,Chelsea,1,3,A,1,1,D,G Poll,15,7,9,6,7,0,11,14,2,3,0,0 +11/05/2005,Arsenal,Everton,7,0,H,3,0,H,A Wiley,19,7,12,3,4,2,9,12,2,0,0,0 +15/05/2005,Birmingham,Arsenal,2,1,H,0,0,D,D Gallagher,9,11,4,5,3,2,10,8,1,0,0,0 +15/05/2005,Bolton,Everton,3,2,H,0,1,A,N Barry,16,10,9,5,5,6,12,17,2,1,1,0 +15/05/2005,Charlton,Crystal Palace,2,2,D,1,0,H,M Clattenburg,8,9,5,6,3,8,14,14,2,2,0,0 +15/05/2005,Fulham,Norwich,6,0,H,2,0,H,S Dunn,13,2,9,0,4,13,11,12,0,1,0,0 +15/05/2005,Liverpool,Aston Villa,2,1,H,2,0,H,B Knight,15,13,7,4,9,5,10,16,0,0,0,0 +15/05/2005,Man City,Middlesbrough,1,1,D,0,1,A,R Styles,10,4,5,3,10,3,12,15,2,4,0,0 +15/05/2005,Newcastle,Chelsea,1,1,D,1,1,D,H Webb,8,12,4,7,6,4,11,17,4,4,0,0 +15/05/2005,Southampton,Man United,1,2,A,1,1,D,S Bennett,11,16,3,8,5,3,14,13,1,2,0,0 +15/05/2005,Tottenham,Blackburn,0,0,D,0,0,D,A Wiley,11,12,7,8,5,7,10,14,2,2,0,0 +15/05/2005,West Brom,Portsmouth,2,0,H,0,0,D,M Riley,9,8,5,2,6,1,12,13,0,1,0,0 +13/08/2005,Aston Villa,Bolton,2,2,D,2,2,D,M Riley,3,13,2,6,7,8,14,16,0,2,0,0 +13/08/2005,Everton,Man United,0,2,A,0,1,A,G Poll,10,12,5,5,8,6,15,14,3,1,0,0 +13/08/2005,Fulham,Birmingham,0,0,D,0,0,D,R Styles,15,7,7,4,6,6,12,13,1,2,0,0 +13/08/2005,Man City,West Brom,0,0,D,0,0,D,C Foy,15,13,8,3,3,6,13,11,2,3,0,0 +13/08/2005,Middlesbrough,Liverpool,0,0,D,0,0,D,M Halsey,4,16,2,7,5,0,17,11,2,3,1,0 +13/08/2005,Portsmouth,Tottenham,0,2,A,0,1,A,B Knight,11,11,7,6,7,2,13,23,0,2,0,0 +13/08/2005,Sunderland,Charlton,1,3,A,1,1,D,H Webb,12,14,8,4,5,5,15,17,2,0,0,1 +13/08/2005,West Ham,Blackburn,3,1,H,0,1,A,A Wiley,13,11,5,5,2,6,11,14,0,1,0,1 +14/08/2005,Arsenal,Newcastle,2,0,H,0,0,D,S Bennett,15,2,12,1,8,3,15,17,0,1,0,1 +14/08/2005,Wigan,Chelsea,0,1,A,0,0,D,M Clattenburg,12,20,5,9,2,6,14,7,1,0,0,0 +20/08/2005,Birmingham,Man City,1,2,A,1,1,D,M Clattenburg,9,9,2,6,9,4,7,12,3,1,0,0 +20/08/2005,Blackburn,Fulham,2,1,H,1,0,H,H Webb,16,8,9,4,8,1,26,14,2,2,0,0 +20/08/2005,Charlton,Wigan,1,0,H,1,0,H,R Styles,21,4,10,0,4,4,10,11,0,1,0,0 +20/08/2005,Liverpool,Sunderland,1,0,H,1,0,H,B Knight,15,2,6,0,5,5,8,20,2,3,0,1 +20/08/2005,Man United,Aston Villa,1,0,H,0,0,D,P Dowd,18,3,12,3,11,7,13,21,2,1,0,0 +20/08/2005,Newcastle,West Ham,0,0,D,0,0,D,D Gallagher,14,6,6,1,10,2,9,11,1,1,0,1 +20/08/2005,Tottenham,Middlesbrough,2,0,H,0,0,D,M Atkinson,18,12,8,3,5,6,16,10,3,1,0,0 +20/08/2005,West Brom,Portsmouth,2,1,H,1,0,H,M Riley,6,10,2,2,3,3,21,19,4,3,0,0 +21/08/2005,Bolton,Everton,0,1,A,0,0,D,A Wiley,16,3,6,1,8,3,10,13,0,1,0,0 +21/08/2005,Chelsea,Arsenal,1,0,H,0,0,D,G Poll,9,9,6,3,3,7,17,21,2,3,0,0 +23/08/2005,Birmingham,Middlesbrough,0,3,A,0,2,A,P Dowd,14,8,8,3,2,2,14,15,0,1,0,0 +23/08/2005,Portsmouth,Aston Villa,1,1,D,1,1,D,G Poll,20,7,11,3,13,4,12,10,3,1,0,1 +23/08/2005,Sunderland,Man City,1,2,A,1,2,A,P Walton,15,12,10,7,5,3,10,10,1,2,0,0 +24/08/2005,Arsenal,Fulham,4,1,H,1,1,D,M Clattenburg,20,8,14,3,6,7,16,11,5,2,0,0 +24/08/2005,Blackburn,Tottenham,0,0,D,0,0,D,D Gallagher,11,1,7,1,7,0,15,12,2,2,1,0 +24/08/2005,Bolton,Newcastle,2,0,H,1,0,H,R Styles,14,7,6,4,4,3,12,17,0,1,0,0 +24/08/2005,Chelsea,West Brom,4,0,H,2,0,H,M Halsey,11,0,8,0,9,1,10,7,0,1,0,0 +27/08/2005,Aston Villa,Blackburn,1,0,H,1,0,H,M Clattenburg,10,9,3,2,4,11,13,16,2,2,0,0 +27/08/2005,Fulham,Everton,1,0,H,0,0,D,M Riley,9,6,4,2,6,2,19,25,1,2,0,1 +27/08/2005,Man City,Portsmouth,2,1,H,0,0,D,A Wiley,16,7,9,2,12,3,13,17,1,2,0,0 +27/08/2005,Tottenham,Chelsea,0,2,A,0,1,A,R Styles,11,13,6,7,2,5,16,12,3,2,1,0 +27/08/2005,West Brom,Birmingham,2,3,A,1,3,A,G Poll,15,8,8,7,3,2,11,24,1,3,0,0 +27/08/2005,West Ham,Bolton,1,2,A,0,0,D,P Dowd,13,10,6,6,7,7,13,16,1,4,0,0 +27/08/2005,Wigan,Sunderland,1,0,H,1,0,H,S Bennett,11,9,5,7,0,6,11,13,3,3,0,0 +28/08/2005,Middlesbrough,Charlton,0,3,A,0,1,A,M Dean,10,12,6,8,6,1,18,15,2,1,0,0 +28/08/2005,Newcastle,Man United,0,2,A,0,0,D,H Webb,9,11,4,5,5,3,26,16,1,2,0,0 +10/09/2005,Birmingham,Charlton,0,1,A,0,1,A,M Halsey,18,8,5,4,13,4,9,10,0,2,0,0 +10/09/2005,Chelsea,Sunderland,2,0,H,0,0,D,M Dean,9,2,6,2,4,2,14,10,1,0,0,0 +10/09/2005,Everton,Portsmouth,0,1,A,0,0,D,M Atkinson,11,12,6,8,11,6,10,12,2,1,0,0 +10/09/2005,Man United,Man City,1,1,D,1,0,H,S Bennett,7,5,2,3,3,5,12,18,1,4,0,0 +10/09/2005,Middlesbrough,Arsenal,2,1,H,1,0,H,M Riley,6,13,5,8,6,7,17,16,3,4,0,0 +10/09/2005,Newcastle,Fulham,1,1,D,0,1,A,A Wiley,15,13,10,8,10,8,19,16,4,1,1,0 +10/09/2005,Tottenham,Liverpool,0,0,D,0,0,D,H Webb,10,11,7,6,4,4,16,13,1,1,0,0 +10/09/2005,West Brom,Wigan,1,2,A,1,1,D,M Clattenburg,10,9,5,4,5,0,19,15,3,1,0,0 +11/09/2005,Bolton,Blackburn,0,0,D,0,0,D,G Poll,12,7,6,3,4,4,18,17,1,2,0,0 +12/09/2005,West Ham,Aston Villa,4,0,H,2,0,H,R Styles,14,14,9,8,4,11,15,12,0,1,0,0 +17/09/2005,Aston Villa,Tottenham,1,1,D,1,0,H,S Bennett,13,13,6,7,4,3,14,14,1,2,0,0 +17/09/2005,Charlton,Chelsea,0,2,A,0,0,D,H Webb,11,16,4,11,2,7,11,10,0,2,0,0 +17/09/2005,Fulham,West Ham,1,2,A,0,0,D,G Poll,9,10,6,3,4,6,11,14,2,1,0,0 +17/09/2005,Portsmouth,Birmingham,1,1,D,1,1,D,D Gallagher,14,3,9,2,7,3,16,14,2,0,0,1 +17/09/2005,Sunderland,West Brom,1,1,D,1,0,H,R Beeby,11,13,6,6,1,9,19,11,0,3,0,0 +18/09/2005,Blackburn,Newcastle,0,3,A,0,0,D,M Riley,17,4,10,3,7,2,14,19,1,5,0,1 +18/09/2005,Liverpool,Man United,0,0,D,0,0,D,R Styles,9,4,3,1,4,2,24,15,2,2,0,0 +18/09/2005,Man City,Bolton,0,1,A,0,0,D,M Dean,17,5,8,1,7,5,14,16,1,1,0,0 +18/09/2005,Wigan,Middlesbrough,1,1,D,0,1,A,U Rennie,11,10,4,3,4,5,11,16,0,1,0,0 +19/09/2005,Arsenal,Everton,2,0,H,2,0,H,A Wiley,11,4,4,2,4,0,13,19,2,3,0,0 +24/09/2005,Birmingham,Liverpool,2,2,D,0,0,D,S Bennett,4,14,4,6,3,7,14,14,2,3,1,0 +24/09/2005,Bolton,Portsmouth,1,0,H,1,0,H,M Clattenburg,11,6,2,4,7,3,9,6,2,2,0,0 +24/09/2005,Chelsea,Aston Villa,2,1,H,1,1,D,B Knight,18,3,6,2,9,1,8,17,1,1,0,0 +24/09/2005,Everton,Wigan,0,1,A,0,0,D,R Styles,14,11,7,3,11,3,17,21,2,1,0,0 +24/09/2005,Man United,Blackburn,1,2,A,0,1,A,P Dowd,27,10,8,10,7,1,13,15,1,3,0,0 +24/09/2005,Newcastle,Man City,1,0,H,1,0,H,G Poll,8,8,6,4,3,6,14,14,0,2,0,0 +24/09/2005,West Brom,Charlton,1,2,A,0,2,A,C Foy,18,8,10,4,6,3,10,12,0,1,0,0 +24/09/2005,West Ham,Arsenal,0,0,D,0,0,D,M Dean,6,8,0,2,4,3,11,7,2,0,0,0 +25/09/2005,Middlesbrough,Sunderland,0,2,A,0,1,A,H Webb,20,4,11,2,8,2,13,21,3,2,0,0 +26/09/2005,Tottenham,Fulham,1,0,H,1,0,H,A Wiley,9,8,4,5,6,6,10,9,0,3,0,0 +01/10/2005,Blackburn,West Brom,2,0,H,0,0,D,U Rennie,20,6,11,3,11,1,15,17,2,0,0,0 +01/10/2005,Charlton,Tottenham,2,3,A,1,0,H,P Dowd,9,13,4,7,1,6,15,17,3,2,0,0 +01/10/2005,Fulham,Man United,2,3,A,2,3,A,H Webb,11,11,6,7,3,9,20,11,2,1,0,0 +01/10/2005,Portsmouth,Newcastle,0,0,D,0,0,D,S Bennett,10,6,6,3,4,4,12,12,1,2,0,0 +01/10/2005,Sunderland,West Ham,1,1,D,1,0,H,M Atkinson,16,6,8,3,5,3,12,15,1,5,0,0 +02/10/2005,Arsenal,Birmingham,1,0,H,0,0,D,C Foy,16,2,13,2,11,4,13,17,0,1,0,1 +02/10/2005,Aston Villa,Middlesbrough,2,3,A,0,1,A,M Dean,13,13,6,10,6,5,17,10,4,1,0,0 +02/10/2005,Liverpool,Chelsea,1,4,A,1,2,A,G Poll,11,7,5,5,4,2,15,17,1,3,0,0 +02/10/2005,Man City,Everton,2,0,H,0,0,D,M Halsey,14,5,5,4,4,2,16,10,1,0,0,0 +02/10/2005,Wigan,Bolton,2,1,H,0,0,D,A Wiley,16,13,9,4,4,8,11,14,0,2,0,0 +15/10/2005,Chelsea,Bolton,5,1,H,0,1,A,R Styles,17,4,8,2,5,3,10,17,2,2,0,1 +15/10/2005,Liverpool,Blackburn,1,0,H,0,0,D,M Halsey,19,4,5,1,9,2,8,22,0,3,0,1 +15/10/2005,Middlesbrough,Portsmouth,1,1,D,0,0,D,C Foy,14,9,7,4,6,3,12,12,2,3,0,0 +15/10/2005,Sunderland,Man United,1,3,A,0,1,A,S Bennett,11,15,6,9,4,5,12,10,3,1,0,0 +15/10/2005,Tottenham,Everton,2,0,H,0,0,D,D Gallagher,7,8,5,4,5,8,13,9,0,2,0,0 +15/10/2005,West Brom,Arsenal,2,1,H,1,1,D,B Knight,5,14,4,10,3,6,23,10,1,2,0,0 +15/10/2005,Wigan,Newcastle,1,0,H,1,0,H,P Dowd,12,11,6,7,6,5,15,20,2,3,1,0 +16/10/2005,Birmingham,Aston Villa,0,1,A,0,1,A,G Poll,8,4,5,1,11,5,13,19,2,2,0,0 +16/10/2005,Man City,West Ham,2,1,H,1,0,H,M Clattenburg,22,6,17,3,9,3,11,13,2,0,0,0 +17/10/2005,Charlton,Fulham,1,1,D,0,1,A,M Riley,14,13,3,1,6,1,19,19,2,3,0,0 +22/10/2005,Arsenal,Man City,1,0,H,0,0,D,M Riley,4,7,2,4,4,3,24,17,3,5,0,0 +22/10/2005,Aston Villa,Wigan,0,2,A,0,1,A,R Beeby,16,6,5,6,9,5,12,13,0,0,0,0 +22/10/2005,Blackburn,Birmingham,2,0,H,0,0,D,B Knight,11,3,5,2,3,9,22,17,0,3,0,0 +22/10/2005,Fulham,Liverpool,2,0,H,1,0,H,M Atkinson,6,14,3,6,3,8,9,14,0,2,0,0 +22/10/2005,Man United,Tottenham,1,1,D,1,0,H,U Rennie,13,9,9,7,7,8,15,12,2,3,0,0 +22/10/2005,Portsmouth,Charlton,1,2,A,1,0,H,H Webb,15,11,6,7,6,5,10,15,2,3,0,0 +23/10/2005,Bolton,West Brom,2,0,H,0,0,D,M Dean,19,2,12,1,10,1,13,16,2,2,0,0 +23/10/2005,Everton,Chelsea,1,1,D,1,0,H,M Clattenburg,4,24,4,12,3,11,14,14,2,2,0,0 +23/10/2005,Newcastle,Sunderland,3,2,H,2,2,D,R Styles,12,10,9,5,5,3,12,13,2,2,0,0 +23/10/2005,West Ham,Middlesbrough,2,1,H,0,0,D,S Bennett,15,19,8,9,4,7,11,9,1,2,0,0 +29/10/2005,Birmingham,Everton,0,1,A,0,1,A,R Styles,18,11,8,6,8,6,11,15,0,1,0,0 +29/10/2005,Charlton,Bolton,0,1,A,0,0,D,M Clattenburg,7,11,4,7,4,2,11,13,1,0,0,0 +29/10/2005,Chelsea,Blackburn,4,2,H,2,2,D,M Riley,19,6,10,4,5,2,12,19,1,5,0,0 +29/10/2005,Liverpool,West Ham,2,0,H,1,0,H,U Rennie,17,4,9,2,10,5,15,13,2,0,0,0 +29/10/2005,Middlesbrough,Man United,4,1,H,3,0,H,A Wiley,7,13,4,8,2,6,14,22,1,4,0,0 +29/10/2005,Sunderland,Portsmouth,1,4,A,1,0,H,M Halsey,5,5,2,5,11,2,11,9,0,1,0,0 +29/10/2005,Tottenham,Arsenal,1,1,D,1,0,H,S Bennett,11,10,3,7,6,1,18,13,4,2,0,0 +29/10/2005,Wigan,Fulham,1,0,H,0,0,D,A Marriner,12,7,6,1,6,3,11,7,1,1,0,0 +30/10/2005,West Brom,Newcastle,0,3,A,0,0,D,H Webb,8,11,4,8,9,4,9,14,0,1,0,0 +31/10/2005,Man City,Aston Villa,3,1,H,2,0,H,D Gallagher,13,11,5,5,2,6,17,10,1,1,0,0 +05/11/2005,Arsenal,Sunderland,3,1,H,2,0,H,A Wiley,12,1,9,1,4,6,11,13,0,1,0,0 +05/11/2005,Aston Villa,Liverpool,0,2,A,0,0,D,S Bennett,6,10,2,6,3,5,16,11,2,2,0,0 +05/11/2005,Blackburn,Charlton,4,1,H,2,1,H,P Dowd,9,10,3,9,7,9,9,15,0,2,0,0 +05/11/2005,Fulham,Man City,2,1,H,2,1,H,R Styles,9,4,5,2,4,2,10,16,1,3,0,0 +05/11/2005,Newcastle,Birmingham,1,0,H,0,0,D,M Dean,10,9,5,3,3,2,20,18,3,2,0,0 +05/11/2005,Portsmouth,Wigan,0,2,A,0,0,D,M Clattenburg,7,7,4,5,5,8,11,13,1,2,0,0 +05/11/2005,West Ham,West Brom,1,0,H,0,0,D,P Walton,10,10,6,6,8,5,15,12,2,0,0,0 +06/11/2005,Everton,Middlesbrough,1,0,H,1,0,H,M Riley,12,17,8,9,9,3,21,16,2,1,0,0 +06/11/2005,Man United,Chelsea,1,0,H,1,0,H,G Poll,10,21,5,11,5,9,19,14,3,4,0,0 +07/11/2005,Bolton,Tottenham,1,0,H,1,0,H,H Webb,9,9,8,7,6,5,13,9,0,1,0,0 +19/11/2005,Charlton,Man United,1,3,A,0,1,A,M Riley,18,13,11,7,4,3,13,11,0,2,0,0 +19/11/2005,Chelsea,Newcastle,3,0,H,0,0,D,M Halsey,12,3,4,2,3,5,12,13,2,1,0,0 +19/11/2005,Liverpool,Portsmouth,3,0,H,2,0,H,P Walton,15,7,11,5,7,6,7,8,1,1,0,0 +19/11/2005,Man City,Blackburn,0,0,D,0,0,D,M Atkinson,12,5,4,4,4,4,7,15,2,4,0,0 +19/11/2005,Sunderland,Aston Villa,1,3,A,0,0,D,C Foy,8,16,6,11,6,5,10,13,2,1,0,0 +19/11/2005,West Brom,Everton,4,0,H,1,0,H,D Gallagher,11,11,8,6,4,8,11,9,0,0,0,0 +19/11/2005,Wigan,Arsenal,2,3,A,2,3,A,G Poll,11,13,5,8,2,2,16,17,3,3,0,0 +20/11/2005,Middlesbrough,Fulham,3,2,H,0,1,A,U Rennie,20,9,11,6,9,4,9,16,0,1,0,0 +20/11/2005,Tottenham,West Ham,1,1,D,1,0,H,A Wiley,15,8,8,4,9,7,16,13,3,1,0,0 +26/11/2005,Arsenal,Blackburn,3,0,H,2,0,H,C Foy,13,15,10,9,9,10,9,15,1,2,0,0 +26/11/2005,Aston Villa,Charlton,1,0,H,0,0,D,M Clattenburg,16,5,10,3,10,3,9,12,2,2,0,0 +26/11/2005,Man City,Liverpool,0,1,A,0,0,D,A Wiley,3,8,1,6,6,4,13,13,0,2,0,0 +26/11/2005,Portsmouth,Chelsea,0,2,A,0,1,A,P Dowd,6,12,2,6,2,1,22,7,5,1,0,0 +26/11/2005,Sunderland,Birmingham,0,1,A,0,0,D,U Rennie,4,10,3,7,6,2,7,10,0,0,0,0 +26/11/2005,Wigan,Tottenham,1,2,A,0,1,A,M Riley,11,7,3,5,6,5,14,17,0,2,0,0 +27/11/2005,Everton,Newcastle,1,0,H,0,0,D,H Webb,14,16,9,10,12,8,19,19,1,5,0,0 +27/11/2005,Fulham,Bolton,2,1,H,2,0,H,G Poll,10,5,7,3,3,6,17,18,2,7,0,0 +27/11/2005,Middlesbrough,West Brom,2,2,D,1,1,D,P Walton,13,9,8,6,6,5,15,13,1,1,0,0 +27/11/2005,West Ham,Man United,1,2,A,1,0,H,S Bennett,10,16,3,10,4,5,15,7,2,3,0,0 +30/11/2005,Sunderland,Liverpool,0,2,A,0,2,A,P Dowd,8,15,4,11,3,3,15,10,0,2,0,1 +03/12/2005,Blackburn,Everton,0,2,A,0,2,A,M Halsey,15,8,7,5,7,5,8,18,1,1,1,0 +03/12/2005,Bolton,Arsenal,2,0,H,2,0,H,H Webb,10,8,9,3,9,3,16,15,1,2,0,0 +03/12/2005,Chelsea,Middlesbrough,1,0,H,0,0,D,M Riley,17,10,9,5,6,4,15,14,0,0,0,0 +03/12/2005,Liverpool,Wigan,3,0,H,2,0,H,U Rennie,16,10,12,6,11,6,16,15,1,2,0,0 +03/12/2005,Man United,Portsmouth,3,0,H,1,0,H,C Foy,19,8,12,4,11,4,9,16,0,1,0,0 +03/12/2005,Newcastle,Aston Villa,1,1,D,1,0,H,A Wiley,8,9,2,6,6,4,17,15,1,2,0,0 +03/12/2005,Tottenham,Sunderland,3,2,H,1,1,D,P Walton,14,5,8,2,12,0,8,19,0,2,0,0 +03/12/2005,West Brom,Fulham,0,0,D,0,0,D,S Bennett,5,8,4,1,1,3,8,15,1,3,0,1 +04/12/2005,Charlton,Man City,2,5,A,1,2,A,P Dowd,10,17,4,9,8,4,20,14,2,3,0,0 +05/12/2005,Birmingham,West Ham,1,2,A,1,2,A,M Atkinson,14,10,6,4,11,8,12,5,1,1,0,0 +10/12/2005,Birmingham,Fulham,1,0,H,0,0,D,U Rennie,11,7,7,3,2,2,12,10,0,0,0,0 +10/12/2005,Blackburn,West Ham,3,2,H,0,1,A,M Riley,14,6,5,3,4,1,13,14,3,6,0,0 +10/12/2005,Bolton,Aston Villa,1,1,D,0,0,D,P Dowd,13,6,7,3,6,3,16,22,2,4,0,0 +10/12/2005,Charlton,Sunderland,2,0,H,1,0,H,A Wiley,14,14,8,6,3,6,11,16,0,2,0,0 +10/12/2005,Chelsea,Wigan,1,0,H,0,0,D,H Webb,11,4,8,1,5,1,16,10,1,1,0,0 +10/12/2005,Liverpool,Middlesbrough,2,0,H,0,0,D,S Bennett,23,6,13,3,11,2,10,18,1,2,0,1 +10/12/2005,Newcastle,Arsenal,1,0,H,0,0,D,D Gallagher,8,6,4,3,5,4,23,13,2,1,0,1 +10/12/2005,West Brom,Man City,2,0,H,1,0,H,M Dean,11,8,4,2,4,6,17,13,1,4,0,1 +11/12/2005,Man United,Everton,1,1,D,1,1,D,R Styles,21,7,12,5,9,0,12,11,1,2,0,0 +12/12/2005,Tottenham,Portsmouth,3,1,H,0,1,A,U Rennie,16,8,9,5,6,3,6,19,0,0,0,0 +14/12/2005,Everton,West Ham,1,2,A,1,1,D,P Walton,12,7,3,2,4,9,10,11,2,1,0,0 +14/12/2005,Man United,Wigan,4,0,H,2,0,H,A Wiley,12,15,9,7,7,7,12,6,1,1,0,0 +17/12/2005,Aston Villa,Man United,0,2,A,0,1,A,M Dean,11,17,7,10,2,6,19,16,1,0,0,0 +17/12/2005,Everton,Bolton,0,4,A,0,1,A,A Wiley,13,7,10,5,10,0,13,15,2,2,0,0 +17/12/2005,Fulham,Blackburn,2,1,H,1,0,H,M Clattenburg,8,10,5,4,8,12,12,13,4,4,0,0 +17/12/2005,Man City,Birmingham,4,1,H,3,0,H,S Bennett,17,6,9,2,7,5,19,12,2,2,0,1 +17/12/2005,Portsmouth,West Brom,1,0,H,0,0,D,M Halsey,5,4,4,2,6,8,15,7,1,1,0,0 +17/12/2005,West Ham,Newcastle,2,4,A,1,2,A,P Dowd,11,10,7,7,3,2,9,16,3,3,0,0 +17/12/2005,Wigan,Charlton,3,0,H,1,0,H,M Atkinson,18,5,12,2,12,3,8,8,0,1,0,0 +18/12/2005,Arsenal,Chelsea,0,2,A,0,1,A,R Styles,7,11,5,8,5,5,13,18,3,4,0,0 +18/12/2005,Middlesbrough,Tottenham,3,3,D,2,1,H,H Webb,11,8,8,5,14,4,17,13,2,3,0,0 +26/12/2005,Aston Villa,Everton,4,0,H,1,0,H,M Riley,12,10,7,5,4,9,12,11,0,0,0,0 +26/12/2005,Charlton,Arsenal,0,1,A,0,0,D,S Bennett,8,18,0,11,4,10,16,10,3,2,1,0 +26/12/2005,Chelsea,Fulham,3,2,H,2,1,H,G Poll,15,9,11,4,10,4,16,16,1,3,0,0 +26/12/2005,Liverpool,Newcastle,2,0,H,2,0,H,M Halsey,21,2,11,2,7,4,9,13,1,0,0,1 +26/12/2005,Man United,West Brom,3,0,H,2,0,H,M Clattenburg,17,5,9,3,8,2,13,6,1,0,0,0 +26/12/2005,Middlesbrough,Blackburn,0,2,A,0,1,A,U Rennie,8,7,5,4,2,3,14,16,4,2,0,0 +26/12/2005,Portsmouth,West Ham,1,1,D,1,0,H,A Wiley,11,14,9,4,6,5,14,10,3,1,1,0 +26/12/2005,Sunderland,Bolton,0,0,D,0,0,D,M Atkinson,11,8,7,1,1,4,16,12,1,3,0,0 +26/12/2005,Tottenham,Birmingham,2,0,H,0,0,D,P Dowd,9,7,4,4,9,8,19,17,4,1,0,1 +26/12/2005,Wigan,Man City,4,3,H,3,1,H,D Gallagher,10,21,5,14,2,9,8,9,0,0,0,0 +28/12/2005,Arsenal,Portsmouth,4,0,H,4,0,H,M Clattenburg,6,6,4,4,3,1,12,13,0,1,0,0 +28/12/2005,Birmingham,Man United,2,2,D,1,1,D,H Webb,11,11,5,5,4,4,13,13,0,0,0,0 +28/12/2005,Everton,Liverpool,1,3,A,1,2,A,G Poll,10,12,6,5,5,2,23,20,4,2,2,0 +28/12/2005,Fulham,Aston Villa,3,3,D,2,1,H,A D'Urso,6,9,3,4,6,10,13,12,0,4,0,0 +28/12/2005,Man City,Chelsea,0,1,A,0,0,D,U Rennie,5,11,2,4,1,4,20,12,1,1,0,0 +28/12/2005,West Brom,Tottenham,2,0,H,1,0,H,M Riley,6,7,5,7,4,3,16,12,0,0,0,0 +28/12/2005,West Ham,Wigan,0,2,A,0,2,A,S Bennett,4,8,1,4,5,3,14,15,1,1,0,0 +31/12/2005,Aston Villa,Arsenal,0,0,D,0,0,D,U Rennie,11,10,5,3,10,1,19,11,1,1,0,0 +31/12/2005,Charlton,West Ham,2,0,H,1,0,H,G Poll,14,8,7,3,6,13,15,8,1,0,0,0 +31/12/2005,Chelsea,Birmingham,2,0,H,2,0,H,M Dean,12,9,8,3,5,4,11,8,1,0,0,0 +31/12/2005,Liverpool,West Brom,1,0,H,0,0,D,A Wiley,27,4,13,0,10,1,8,12,0,0,0,0 +31/12/2005,Man United,Bolton,4,1,H,2,1,H,S Bennett,18,4,9,2,7,1,9,22,2,5,0,0 +31/12/2005,Middlesbrough,Man City,0,0,D,0,0,D,M Atkinson,12,8,4,3,4,7,6,12,0,1,0,0 +31/12/2005,Portsmouth,Fulham,1,0,H,1,0,H,M Riley,17,9,7,4,8,8,14,7,1,2,0,0 +31/12/2005,Sunderland,Everton,0,1,A,0,0,D,R Styles,13,9,6,5,8,5,6,7,1,1,0,0 +31/12/2005,Tottenham,Newcastle,2,0,H,1,0,H,H Webb,13,9,3,4,5,3,13,11,0,1,0,0 +31/12/2005,Wigan,Blackburn,0,3,A,0,1,A,M Clattenburg,9,13,4,7,2,7,9,13,1,1,0,0 +02/01/2006,Birmingham,Wigan,2,0,H,2,0,H,P Walton,8,6,4,2,8,3,9,16,1,2,0,0 +02/01/2006,Blackburn,Portsmouth,2,1,H,2,1,H,M Dean,12,8,7,4,4,6,11,12,1,4,0,0 +02/01/2006,Bolton,Liverpool,2,2,D,1,0,H,M Clattenburg,12,15,7,7,3,5,16,14,2,3,0,0 +02/01/2006,Everton,Charlton,3,1,H,2,1,H,U Rennie,13,11,6,4,5,2,13,14,0,2,0,0 +02/01/2006,Fulham,Sunderland,2,1,H,1,1,D,D Gallagher,12,10,9,7,6,6,9,14,2,2,0,1 +02/01/2006,Newcastle,Middlesbrough,2,2,D,1,0,H,S Bennett,10,8,5,3,5,2,14,13,0,2,0,0 +02/01/2006,West Brom,Aston Villa,1,2,A,0,0,D,R Styles,9,13,5,7,7,6,9,13,2,3,0,0 +02/01/2006,West Ham,Chelsea,1,3,A,0,1,A,H Webb,6,17,3,10,2,5,10,11,1,3,0,0 +03/01/2006,Arsenal,Man United,0,0,D,0,0,D,G Poll,8,16,5,6,6,9,17,13,2,3,0,0 +04/01/2006,Man City,Tottenham,0,2,A,0,1,A,A Wiley,13,8,3,2,12,8,8,11,2,2,0,0 +14/01/2006,Arsenal,Middlesbrough,7,0,H,4,0,H,R Styles,24,5,17,2,4,6,12,14,1,2,0,1 +14/01/2006,Aston Villa,West Ham,1,2,A,1,0,H,P Dowd,14,10,8,7,4,10,15,21,1,3,0,0 +14/01/2006,Blackburn,Bolton,0,0,D,0,0,D,M Riley,18,3,3,1,11,1,21,16,2,5,0,1 +14/01/2006,Charlton,Birmingham,2,0,H,1,0,H,G Poll,11,14,4,5,2,7,16,12,0,0,0,0 +14/01/2006,Fulham,Newcastle,1,0,H,0,0,D,A Wiley,12,6,5,4,5,10,9,13,0,2,0,0 +14/01/2006,Liverpool,Tottenham,1,0,H,0,0,D,D Gallagher,16,6,12,2,10,7,8,12,0,1,0,1 +14/01/2006,Man City,Man United,3,1,H,2,0,H,S Bennett,12,13,7,4,3,3,19,18,2,1,0,1 +14/01/2006,Portsmouth,Everton,0,1,A,0,1,A,A Marriner,14,9,8,3,4,8,21,13,2,2,0,0 +15/01/2006,Sunderland,Chelsea,1,2,A,1,1,D,C Foy,10,21,7,13,4,3,13,8,1,2,0,1 +15/01/2006,Wigan,West Brom,0,1,A,0,0,D,M Atkinson,27,7,12,6,6,5,14,16,0,3,0,1 +21/01/2006,Birmingham,Portsmouth,5,0,H,2,0,H,C Foy,9,10,6,4,7,2,11,23,1,1,0,0 +21/01/2006,Bolton,Man City,2,0,H,2,0,H,R Styles,11,9,6,2,4,5,8,17,2,2,0,0 +21/01/2006,Everton,Arsenal,1,0,H,1,0,H,A Wiley,9,14,3,5,5,6,19,17,4,2,0,1 +21/01/2006,Middlesbrough,Wigan,2,3,A,0,2,A,A Marriner,7,16,4,10,8,8,9,11,0,2,0,0 +21/01/2006,Newcastle,Blackburn,0,1,A,0,0,D,H Webb,12,6,3,2,4,4,12,12,0,2,0,0 +21/01/2006,Tottenham,Aston Villa,0,0,D,0,0,D,G Poll,21,4,13,1,9,2,10,15,0,2,0,1 +21/01/2006,West Brom,Sunderland,0,1,A,0,0,D,P Dowd,10,7,5,2,6,2,13,16,0,3,0,0 +22/01/2006,Chelsea,Charlton,1,1,D,1,0,H,S Bennett,12,9,7,5,6,0,10,13,2,1,1,0 +22/01/2006,Man United,Liverpool,1,0,H,0,0,D,M Riley,7,11,4,7,1,2,15,17,2,3,0,0 +23/01/2006,West Ham,Fulham,2,1,H,2,0,H,U Rennie,9,12,5,6,4,7,16,12,2,2,0,0 +31/01/2006,Charlton,West Brom,0,0,D,0,0,D,I Williamson,12,12,9,6,3,5,8,18,2,4,1,0 +31/01/2006,Fulham,Tottenham,1,0,H,0,0,D,H Webb,12,7,8,3,3,9,9,11,1,3,0,1 +31/01/2006,Sunderland,Middlesbrough,0,3,A,0,2,A,A Wiley,14,7,7,4,2,5,18,9,1,1,0,0 +31/01/2006,Wigan,Everton,1,1,D,1,1,D,M Dean,14,8,6,6,5,5,15,15,1,1,1,1 +01/02/2006,Arsenal,West Ham,2,3,A,1,2,A,M Halsey,16,4,10,4,12,2,6,15,1,1,0,0 +01/02/2006,Aston Villa,Chelsea,1,1,D,0,1,A,R Styles,6,11,2,5,5,4,6,15,1,3,0,0 +01/02/2006,Blackburn,Man United,4,3,H,3,1,H,P Dowd,7,12,4,6,2,2,23,17,3,3,0,1 +01/02/2006,Liverpool,Birmingham,1,1,D,0,0,D,U Rennie,24,3,10,2,11,3,13,12,0,0,0,1 +01/02/2006,Man City,Newcastle,3,0,H,2,0,H,C Foy,14,4,8,1,3,3,13,10,1,1,0,0 +01/02/2006,Portsmouth,Bolton,1,1,D,0,0,D,D Gallagher,13,8,5,2,6,4,12,9,0,0,0,0 +04/02/2006,Birmingham,Arsenal,0,2,A,0,1,A,M Riley,13,9,5,6,6,6,18,21,2,3,1,0 +04/02/2006,Bolton,Wigan,1,1,D,0,0,D,S Bennett,12,7,9,4,4,1,12,12,0,4,0,0 +04/02/2006,Everton,Man City,1,0,H,1,0,H,A Marriner,10,7,5,3,9,5,13,19,0,3,0,1 +04/02/2006,Man United,Fulham,4,2,H,3,2,H,M Atkinson,21,10,14,7,8,5,10,14,4,2,0,0 +04/02/2006,Middlesbrough,Aston Villa,0,4,A,0,2,A,L Mason,9,10,4,5,6,5,14,12,3,1,0,0 +04/02/2006,Newcastle,Portsmouth,2,0,H,1,0,H,M Halsey,14,3,10,1,7,1,9,8,0,1,0,0 +04/02/2006,West Brom,Blackburn,2,0,H,2,0,H,C Foy,9,10,5,4,4,7,16,10,1,1,0,0 +04/02/2006,West Ham,Sunderland,2,0,H,0,0,D,R Styles,11,7,7,3,6,5,13,11,1,3,0,1 +05/02/2006,Chelsea,Liverpool,2,0,H,1,0,H,A Wiley,10,12,6,2,5,5,14,17,1,2,0,1 +05/02/2006,Tottenham,Charlton,3,1,H,2,0,H,P Dowd,14,7,10,5,5,2,13,13,0,3,0,0 +08/02/2006,Charlton,Liverpool,2,0,H,2,0,H,A Marriner,6,13,2,5,1,10,12,13,2,2,0,0 +11/02/2006,Arsenal,Bolton,1,1,D,0,1,A,H Webb,16,5,10,3,8,6,14,12,2,2,0,0 +11/02/2006,Aston Villa,Newcastle,1,2,A,1,2,A,M Riley,13,9,7,6,11,7,23,20,4,2,0,1 +11/02/2006,Everton,Blackburn,1,0,H,1,0,H,P Walton,5,18,2,5,6,10,8,14,4,2,1,0 +11/02/2006,Fulham,West Brom,6,1,H,2,0,H,G Poll,11,11,7,6,4,7,19,14,3,2,0,0 +11/02/2006,Middlesbrough,Chelsea,3,0,H,2,0,H,S Bennett,7,17,6,10,5,9,17,7,0,3,0,0 +11/02/2006,Portsmouth,Man United,1,3,A,0,3,A,U Rennie,15,6,6,4,16,2,12,16,0,0,0,0 +11/02/2006,Wigan,Liverpool,0,1,A,0,1,A,R Styles,5,7,1,6,4,3,8,3,2,0,0,0 +12/02/2006,Man City,Charlton,3,2,H,1,0,H,M Dean,16,12,10,7,11,1,12,15,0,0,0,0 +12/02/2006,Sunderland,Tottenham,1,1,D,0,1,A,A Marriner,9,10,5,7,5,0,16,9,2,1,0,0 +13/02/2006,West Ham,Birmingham,3,0,H,1,0,H,D Gallagher,13,17,8,7,4,6,10,7,0,2,0,0 +14/02/2006,Liverpool,Arsenal,1,0,H,0,0,D,G Poll,22,6,9,2,13,1,12,15,0,1,0,0 +15/02/2006,Blackburn,Sunderland,2,0,H,1,0,H,K Stroud,12,5,6,1,6,2,16,19,1,2,0,0 +19/02/2006,Tottenham,Wigan,2,2,D,1,1,D,U Rennie,10,8,4,5,4,3,12,14,1,1,0,0 +22/02/2006,Newcastle,Charlton,0,0,D,0,0,D,U Rennie,13,7,8,4,5,6,13,13,1,2,0,0 +25/02/2006,Birmingham,Sunderland,1,0,H,1,0,H,S Bennett,14,8,7,4,6,2,13,14,0,3,0,0 +25/02/2006,Blackburn,Arsenal,1,0,H,1,0,H,U Rennie,6,10,2,3,3,5,23,10,3,2,0,0 +25/02/2006,Charlton,Aston Villa,0,0,D,0,0,D,C Foy,13,11,6,7,1,8,9,9,0,1,0,0 +25/02/2006,Chelsea,Portsmouth,2,0,H,0,0,D,M Riley,17,5,8,1,6,7,7,13,0,1,0,0 +25/02/2006,Newcastle,Everton,2,0,H,0,0,D,G Poll,18,12,10,6,6,4,26,15,1,1,0,0 +26/02/2006,Bolton,Fulham,2,1,H,1,1,D,P Dowd,13,10,5,6,8,5,13,13,1,1,0,0 +26/02/2006,Liverpool,Man City,1,0,H,1,0,H,D Gallagher,20,6,12,3,12,2,4,16,0,2,0,1 +26/02/2006,West Brom,Middlesbrough,0,2,A,0,2,A,M Dean,17,5,9,4,6,3,7,14,0,2,1,0 +04/03/2006,Aston Villa,Portsmouth,1,0,H,1,0,H,M Dean,17,11,8,6,16,10,15,14,1,1,0,0 +04/03/2006,Fulham,Arsenal,0,4,A,0,2,A,R Styles,7,18,4,11,0,7,11,15,0,1,0,0 +04/03/2006,Liverpool,Charlton,0,0,D,0,0,D,M Atkinson,21,3,10,1,12,5,5,14,1,1,0,0 +04/03/2006,Middlesbrough,Birmingham,1,0,H,1,0,H,G Poll,12,14,6,5,3,4,12,12,0,0,0,0 +04/03/2006,Newcastle,Bolton,3,1,H,2,0,H,A Wiley,9,11,5,3,11,5,18,13,2,1,0,0 +04/03/2006,West Brom,Chelsea,1,2,A,0,0,D,M Halsey,6,7,2,5,6,4,12,10,1,0,0,1 +04/03/2006,West Ham,Everton,2,2,D,2,1,H,M Riley,7,13,3,5,1,9,12,10,3,3,0,0 +05/03/2006,Man City,Sunderland,2,1,H,2,1,H,C Foy,15,6,7,3,6,3,11,16,1,3,0,1 +05/03/2006,Tottenham,Blackburn,3,2,H,2,1,H,H Webb,10,6,5,4,6,9,14,12,2,3,0,0 +06/03/2006,Wigan,Man United,1,2,A,0,0,D,S Bennett,18,9,7,2,6,2,22,13,5,3,0,0 +11/03/2006,Birmingham,West Brom,1,1,D,0,0,D,P Dowd,6,10,1,4,6,11,12,22,2,3,0,0 +11/03/2006,Blackburn,Aston Villa,2,0,H,0,0,D,D Gallagher,12,12,5,5,3,6,10,16,0,1,0,0 +11/03/2006,Bolton,West Ham,4,1,H,3,0,H,M Dean,19,9,10,5,7,6,12,18,1,5,0,0 +11/03/2006,Chelsea,Tottenham,2,1,H,1,1,D,G Poll,13,4,10,3,4,4,11,16,2,0,0,0 +11/03/2006,Everton,Fulham,3,1,H,2,0,H,U Rennie,15,8,7,3,8,4,15,12,2,2,0,0 +11/03/2006,Portsmouth,Man City,2,1,H,0,0,D,M Halsey,12,9,5,4,8,1,11,7,0,0,0,0 +11/03/2006,Sunderland,Wigan,0,1,A,0,1,A,M Riley,13,5,9,2,5,2,14,13,1,0,0,0 +12/03/2006,Arsenal,Liverpool,2,1,H,1,0,H,S Bennett,11,8,4,3,5,0,8,18,0,2,0,1 +12/03/2006,Charlton,Middlesbrough,2,1,H,0,0,D,A Wiley,5,9,3,7,0,4,16,20,2,1,0,0 +12/03/2006,Man United,Newcastle,2,0,H,2,0,H,R Styles,28,5,12,2,5,7,6,11,0,1,0,0 +15/03/2006,Liverpool,Fulham,5,1,H,2,1,H,A Wiley,19,8,10,4,8,2,8,12,0,3,0,0 +18/03/2006,Arsenal,Charlton,3,0,H,2,0,H,D Gallagher,16,2,9,1,8,1,6,10,0,1,0,0 +18/03/2006,Birmingham,Tottenham,0,2,A,0,0,D,U Rennie,6,9,4,6,10,4,11,9,2,0,0,0 +18/03/2006,Blackburn,Middlesbrough,3,2,H,2,1,H,C Foy,5,7,3,5,6,4,11,11,3,3,1,0 +18/03/2006,Bolton,Sunderland,2,0,H,0,0,D,P Dowd,16,6,7,3,7,6,9,18,1,4,0,0 +18/03/2006,Everton,Aston Villa,4,1,H,3,0,H,R Styles,12,9,7,7,4,9,6,11,1,3,0,0 +18/03/2006,Man City,Wigan,0,1,A,0,0,D,M Atkinson,7,10,1,4,6,7,12,14,2,0,0,0 +18/03/2006,West Brom,Man United,1,2,A,0,1,A,H Webb,8,20,3,9,6,8,12,12,1,3,0,0 +18/03/2006,West Ham,Portsmouth,2,4,A,0,3,A,A Wiley,13,12,6,8,5,4,13,12,1,1,0,0 +19/03/2006,Fulham,Chelsea,1,0,H,1,0,H,M Dean,7,10,4,4,0,16,12,13,1,4,0,1 +19/03/2006,Newcastle,Liverpool,1,3,A,1,2,A,M Riley,8,12,4,6,2,1,21,18,2,3,1,0 +25/03/2006,Aston Villa,Fulham,0,0,D,0,0,D,L Mason,10,9,6,3,8,4,11,8,0,0,0,0 +25/03/2006,Chelsea,Man City,2,0,H,2,0,H,R Styles,19,5,10,4,11,1,5,10,1,3,0,1 +25/03/2006,Liverpool,Everton,3,1,H,1,0,H,P Dowd,17,8,10,5,5,3,12,25,3,7,1,1 +25/03/2006,Sunderland,Blackburn,0,1,A,0,1,A,P Walton,10,7,6,4,4,4,12,17,4,1,0,0 +25/03/2006,Wigan,West Ham,1,2,A,1,0,H,D Gallagher,12,5,8,3,4,1,10,20,3,0,0,0 +26/03/2006,Charlton,Newcastle,3,1,H,2,1,H,M Halsey,11,15,5,8,5,5,12,13,1,1,0,0 +26/03/2006,Man United,Birmingham,3,0,H,2,0,H,M Dean,8,7,5,5,2,9,14,11,0,3,0,0 +26/03/2006,Middlesbrough,Bolton,4,3,H,2,1,H,H Webb,15,11,10,6,1,6,11,12,2,2,0,0 +27/03/2006,Tottenham,West Brom,2,1,H,0,1,A,C Foy,11,13,8,8,6,5,10,14,0,2,0,0 +29/03/2006,Man United,West Ham,1,0,H,1,0,H,G Poll,23,9,13,3,3,6,10,12,0,0,0,0 +01/04/2006,Arsenal,Aston Villa,5,0,H,2,0,H,M Atkinson,20,9,16,4,8,8,9,11,0,1,0,0 +01/04/2006,Birmingham,Chelsea,0,0,D,0,0,D,D Gallagher,4,13,1,4,2,8,11,6,0,0,0,0 +01/04/2006,Bolton,Man United,1,2,A,1,1,D,A Wiley,7,14,2,10,3,5,17,11,1,2,0,0 +01/04/2006,Everton,Sunderland,2,2,D,2,1,H,M Halsey,16,16,8,8,4,4,8,10,0,0,0,0 +01/04/2006,Fulham,Portsmouth,1,3,A,1,2,A,C Foy,10,10,4,7,7,8,9,16,2,2,1,0 +01/04/2006,Newcastle,Tottenham,3,1,H,3,1,H,M Dean,14,10,11,5,5,3,11,12,2,3,0,1 +01/04/2006,West Brom,Liverpool,0,2,A,0,2,A,U Rennie,8,13,4,4,1,4,11,8,0,0,0,0 +02/04/2006,Man City,Middlesbrough,0,1,A,0,1,A,M Riley,9,16,2,9,2,9,15,17,0,1,0,0 +02/04/2006,West Ham,Charlton,0,0,D,0,0,D,R Styles,16,8,7,2,12,8,9,6,0,1,0,0 +03/04/2006,Blackburn,Wigan,1,1,D,0,0,D,P Dowd,11,12,5,4,12,3,13,19,0,2,0,0 +04/04/2006,Birmingham,Bolton,1,0,H,1,0,H,C Foy,7,8,2,1,5,2,12,7,1,0,0,0 +08/04/2006,Charlton,Everton,0,0,D,0,0,D,P Walton,9,7,4,3,6,4,16,16,0,2,0,0 +08/04/2006,Portsmouth,Blackburn,2,2,D,1,1,D,S Bennett,22,4,12,3,7,3,12,10,1,3,0,0 +08/04/2006,Tottenham,Man City,2,1,H,1,0,H,D Gallagher,23,14,19,6,8,5,4,16,0,3,0,0 +08/04/2006,Wigan,Birmingham,1,1,D,0,0,D,H Webb,13,11,6,5,13,7,16,9,0,2,0,0 +09/04/2006,Aston Villa,West Brom,0,0,D,0,0,D,M Halsey,6,7,3,4,6,10,15,10,1,2,0,0 +09/04/2006,Chelsea,West Ham,4,1,H,2,1,H,C Foy,14,6,5,4,5,5,7,14,0,2,1,0 +09/04/2006,Liverpool,Bolton,1,0,H,1,0,H,R Styles,17,7,7,4,3,2,6,6,1,1,0,0 +09/04/2006,Man United,Arsenal,2,0,H,0,0,D,G Poll,14,10,8,3,5,3,16,16,3,1,0,0 +09/04/2006,Middlesbrough,Newcastle,1,2,A,0,2,A,A Wiley,13,14,8,5,6,5,14,21,2,4,0,0 +12/04/2006,Portsmouth,Arsenal,1,1,D,0,1,A,U Rennie,10,9,4,3,3,10,9,9,1,1,0,0 +14/04/2006,Man United,Sunderland,0,0,D,0,0,D,R Styles,24,12,11,3,16,2,6,13,0,1,0,0 +15/04/2006,Arsenal,West Brom,3,1,H,1,0,H,M Dean,16,8,10,1,5,1,8,6,2,1,0,0 +15/04/2006,Bolton,Chelsea,0,2,A,0,1,A,P Dowd,7,5,4,3,4,1,19,15,3,3,1,0 +15/04/2006,Everton,Tottenham,0,1,A,0,1,A,H Webb,8,10,4,6,7,9,16,9,2,1,0,0 +15/04/2006,Fulham,Charlton,2,1,H,2,1,H,M Atkinson,6,10,4,7,2,12,12,16,0,0,0,0 +15/04/2006,Newcastle,Wigan,3,1,H,2,1,H,U Rennie,11,13,8,5,7,1,13,16,2,3,0,0 +15/04/2006,Portsmouth,Middlesbrough,1,0,H,0,0,D,A Marriner,16,12,11,8,7,5,12,10,0,0,0,0 +15/04/2006,West Ham,Man City,1,0,H,1,0,H,S Bennett,8,6,6,2,9,3,18,11,0,2,0,0 +16/04/2006,Aston Villa,Birmingham,3,1,H,1,1,D,G Poll,9,12,7,8,5,5,22,12,4,3,0,0 +16/04/2006,Blackburn,Liverpool,0,1,A,0,1,A,A Wiley,6,7,3,2,6,4,12,11,3,2,0,0 +17/04/2006,Charlton,Portsmouth,2,1,H,0,1,A,H Webb,16,11,9,5,6,9,10,17,1,1,0,0 +17/04/2006,Chelsea,Everton,3,0,H,1,0,H,R Styles,15,4,8,2,6,3,6,6,0,0,0,1 +17/04/2006,Middlesbrough,West Ham,2,0,H,1,0,H,M Atkinson,12,14,8,6,4,7,6,12,0,1,0,0 +17/04/2006,Sunderland,Newcastle,1,4,A,1,0,H,C Foy,12,9,6,4,13,1,16,21,1,2,0,0 +17/04/2006,Tottenham,Man United,1,2,A,0,2,A,M Halsey,15,11,8,5,9,5,7,15,1,1,0,0 +17/04/2006,West Brom,Bolton,0,0,D,0,0,D,D Gallagher,9,8,1,3,3,6,13,16,0,0,0,0 +18/04/2006,Wigan,Aston Villa,3,2,H,1,0,H,P Walton,18,9,11,4,8,7,10,13,2,2,0,0 +19/04/2006,Birmingham,Blackburn,2,1,H,0,0,D,U Rennie,12,12,6,6,9,11,15,12,5,0,0,0 +22/04/2006,Arsenal,Tottenham,1,1,D,0,0,D,S Bennett,9,9,6,6,5,4,9,9,1,1,0,1 +22/04/2006,Bolton,Charlton,4,1,H,3,0,H,U Rennie,13,5,11,3,9,1,14,12,0,0,0,0 +22/04/2006,Everton,Birmingham,0,0,D,0,0,D,M Halsey,17,6,9,2,11,2,15,18,0,4,0,0 +22/04/2006,Newcastle,West Brom,3,0,H,2,0,H,H Webb,14,5,9,1,4,2,16,11,0,1,0,0 +22/04/2006,Portsmouth,Sunderland,2,1,H,0,0,D,M Dean,23,7,15,6,8,5,9,15,2,3,0,0 +24/04/2006,Fulham,Wigan,1,0,H,1,0,H,R Styles,12,21,6,10,4,8,11,9,4,2,0,0 +25/04/2006,Aston Villa,Man City,0,1,A,0,0,D,C Foy,10,15,5,6,4,9,12,9,1,1,0,0 +26/04/2006,West Ham,Liverpool,1,2,A,0,1,A,H Webb,11,13,7,7,6,3,11,7,0,0,1,1 +29/04/2006,Birmingham,Newcastle,0,0,D,0,0,D,M Atkinson,15,4,5,0,6,3,9,10,2,2,0,0 +29/04/2006,Charlton,Blackburn,0,2,A,0,1,A,R Styles,7,10,3,4,4,4,10,6,3,4,0,0 +29/04/2006,Chelsea,Man United,3,0,H,1,0,H,M Dean,11,7,5,2,2,3,17,12,4,2,0,0 +29/04/2006,Liverpool,Aston Villa,3,1,H,1,0,H,M Halsey,15,7,9,3,6,5,7,11,0,0,0,0 +29/04/2006,Man City,Fulham,1,2,A,0,0,D,P Walton,19,12,10,7,9,6,11,11,1,2,0,0 +29/04/2006,Middlesbrough,Everton,0,1,A,0,0,D,L Mason,11,8,3,4,4,6,13,17,2,2,0,0 +29/04/2006,Wigan,Portsmouth,1,2,A,1,0,H,M Riley,13,16,7,8,3,9,11,8,3,1,1,0 +30/04/2006,Tottenham,Bolton,1,0,H,0,0,D,A Wiley,9,14,4,6,6,8,11,12,1,5,0,0 +01/05/2006,Man United,Middlesbrough,0,0,D,0,0,D,C Foy,17,10,6,6,10,2,11,10,2,2,0,0 +01/05/2006,Sunderland,Arsenal,0,3,A,0,3,A,D Gallagher,7,7,2,4,5,3,17,8,3,0,0,0 +01/05/2006,West Brom,West Ham,0,1,A,0,1,A,G Poll,16,10,4,5,9,6,8,15,0,0,0,0 +02/05/2006,Blackburn,Chelsea,1,0,H,1,0,H,S Bennett,6,7,1,2,5,8,18,6,2,2,0,0 +03/05/2006,Bolton,Middlesbrough,1,1,D,0,0,D,H Webb,17,5,9,3,6,1,9,10,1,1,0,0 +04/05/2006,Man City,Arsenal,1,3,A,1,1,D,G Poll,10,17,8,12,12,6,21,8,3,0,0,0 +04/05/2006,Sunderland,Fulham,2,1,H,1,0,H,M Riley,3,10,3,2,10,3,18,6,4,4,0,0 +07/05/2006,Arsenal,Wigan,4,2,H,2,2,D,U Rennie,13,8,7,3,5,1,12,16,1,1,0,1 +07/05/2006,Aston Villa,Sunderland,2,1,H,1,0,H,M Dean,16,14,9,7,7,7,11,10,4,2,0,0 +07/05/2006,Blackburn,Man City,2,0,H,1,0,H,H Webb,6,11,2,4,4,4,20,9,2,4,1,0 +07/05/2006,Bolton,Birmingham,1,0,H,0,0,D,S Bennett,16,5,6,2,5,2,7,14,0,1,0,0 +07/05/2006,Everton,West Brom,2,2,D,0,1,A,A Wiley,22,6,18,5,14,2,16,18,1,2,0,0 +07/05/2006,Fulham,Middlesbrough,1,0,H,0,0,D,M Halsey,8,6,3,3,8,4,8,12,0,1,0,0 +07/05/2006,Man United,Charlton,4,0,H,3,0,H,M Atkinson,20,8,11,3,8,2,9,6,1,0,0,0 +07/05/2006,Newcastle,Chelsea,1,0,H,0,0,D,M Riley,16,10,8,5,3,7,17,14,4,2,1,0 +07/05/2006,Portsmouth,Liverpool,1,3,A,0,0,D,G Poll,6,8,4,4,5,6,5,11,1,0,0,0 +07/05/2006,West Ham,Tottenham,2,1,H,1,1,D,C Foy,12,14,7,6,11,4,17,7,1,0,0,0 +19/08/2006,Arsenal,Aston Villa,1,1,D,0,0,D,G Poll,19,5,11,3,18,1,10,19,1,2,0,0 +19/08/2006,Bolton,Tottenham,2,0,H,2,0,H,P Dowd,8,9,6,6,6,3,19,22,0,1,0,0 +19/08/2006,Everton,Watford,2,1,H,1,0,H,P Walton,6,12,2,8,0,6,12,13,2,2,0,0 +19/08/2006,Newcastle,Wigan,2,1,H,1,0,H,M Atkinson,9,14,8,9,4,11,17,20,1,2,0,0 +19/08/2006,Portsmouth,Blackburn,3,0,H,1,0,H,A Wiley,21,9,16,7,6,2,20,17,2,1,0,2 +19/08/2006,Reading,Middlesbrough,3,2,H,2,2,D,M Halsey,14,7,9,5,8,2,8,15,1,3,0,0 +19/08/2006,Sheffield United,Liverpool,1,1,D,0,0,D,R Styles,5,12,3,7,0,12,10,16,1,2,0,0 +19/08/2006,West Ham,Charlton,3,1,H,0,1,A,H Webb,16,4,5,2,6,0,14,14,2,2,0,1 +20/08/2006,Chelsea,Man City,3,0,H,2,0,H,S Bennett,10,5,4,2,5,2,7,15,1,3,0,1 +20/08/2006,Man United,Fulham,5,1,H,4,1,H,A Marriner,15,12,7,8,5,2,14,20,1,1,0,0 +22/08/2006,Tottenham,Sheffield United,2,0,H,2,0,H,P Walton,18,6,13,3,6,4,5,9,0,1,0,0 +22/08/2006,Watford,West Ham,1,1,D,0,0,D,M Atkinson,11,8,4,4,5,1,12,12,0,0,0,0 +23/08/2006,Aston Villa,Reading,2,1,H,1,1,D,L Mason,11,5,6,3,2,7,9,6,2,1,0,1 +23/08/2006,Blackburn,Everton,1,1,D,0,0,D,U Rennie,10,10,5,4,5,3,22,20,4,3,0,0 +23/08/2006,Charlton,Man United,0,3,A,0,0,D,C Foy,8,21,2,11,4,11,14,11,1,1,0,0 +23/08/2006,Fulham,Bolton,1,1,D,0,0,D,A Wiley,11,4,4,1,1,6,9,16,0,2,0,0 +23/08/2006,Man City,Portsmouth,0,0,D,0,0,D,D Gallagher,6,3,3,1,8,0,15,13,2,1,0,0 +23/08/2006,Middlesbrough,Chelsea,2,1,H,0,1,A,H Webb,9,8,2,5,3,3,15,7,2,2,0,0 +26/08/2006,Charlton,Bolton,2,0,H,0,0,D,M Dean,9,7,5,2,6,5,11,17,1,4,1,1 +26/08/2006,Fulham,Sheffield United,1,0,H,1,0,H,G Poll,10,9,5,1,9,5,17,17,3,3,0,0 +26/08/2006,Liverpool,West Ham,2,1,H,2,1,H,A Wiley,19,8,8,3,6,1,15,6,1,0,0,0 +26/08/2006,Man City,Arsenal,1,0,H,1,0,H,U Rennie,10,17,3,6,2,8,16,8,1,1,0,0 +26/08/2006,Tottenham,Everton,0,2,A,0,0,D,M Halsey,15,6,6,2,7,2,9,12,1,1,0,1 +26/08/2006,Watford,Man United,1,2,A,1,1,D,P Dowd,8,19,7,10,4,7,20,14,1,1,0,0 +26/08/2006,Wigan,Reading,1,0,H,1,0,H,M Riley,15,5,5,3,4,10,12,14,2,2,0,0 +27/08/2006,Aston Villa,Newcastle,2,0,H,2,0,H,H Webb,9,8,7,5,2,3,14,12,2,2,0,0 +27/08/2006,Blackburn,Chelsea,0,2,A,0,0,D,M Clattenburg,12,14,3,8,7,3,10,15,3,1,0,0 +28/08/2006,Middlesbrough,Portsmouth,0,4,A,0,1,A,C Foy,19,10,8,7,8,6,15,9,2,1,0,0 +09/09/2006,Arsenal,Middlesbrough,1,1,D,0,1,A,R Styles,20,1,12,1,15,0,5,17,1,6,0,1 +09/09/2006,Bolton,Watford,1,0,H,0,0,D,M Clattenburg,7,15,2,6,6,6,13,15,3,1,0,0 +09/09/2006,Chelsea,Charlton,2,1,H,1,0,H,A Wiley,19,3,6,2,6,3,12,7,2,1,0,0 +09/09/2006,Everton,Liverpool,3,0,H,2,0,H,G Poll,8,21,6,5,2,11,22,13,1,3,0,0 +09/09/2006,Man United,Tottenham,1,0,H,1,0,H,M Riley,10,9,5,4,5,4,12,17,2,2,0,0 +09/09/2006,Newcastle,Fulham,1,2,A,0,0,D,C Foy,15,8,7,5,4,7,16,12,1,1,0,0 +09/09/2006,Portsmouth,Wigan,1,0,H,0,0,D,P Walton,9,11,7,5,6,9,14,5,0,1,0,0 +09/09/2006,Sheffield United,Blackburn,0,0,D,0,0,D,M Dean,14,12,9,1,7,6,12,11,2,2,0,0 +10/09/2006,West Ham,Aston Villa,1,1,D,0,1,A,S Bennett,7,10,1,6,3,5,15,9,2,2,0,0 +11/09/2006,Reading,Man City,1,0,H,1,0,H,H Webb,12,15,7,8,6,4,6,16,0,4,0,1 +16/09/2006,Bolton,Middlesbrough,0,0,D,0,0,D,S Bennett,13,14,3,7,9,4,7,19,0,1,0,0 +16/09/2006,Charlton,Portsmouth,0,1,A,0,0,D,P Dowd,18,12,5,5,9,4,6,10,0,2,0,0 +16/09/2006,Everton,Wigan,2,2,D,0,0,D,A Wiley,14,10,9,7,4,2,14,23,1,5,0,0 +16/09/2006,Sheffield United,Reading,1,2,A,0,2,A,A Marriner,14,8,7,2,8,7,12,6,2,1,0,0 +16/09/2006,Watford,Aston Villa,0,0,D,0,0,D,M Dean,12,9,5,4,7,1,12,10,1,0,0,0 +17/09/2006,Blackburn,Man City,4,2,H,2,2,D,M Atkinson,11,15,5,8,4,3,11,11,2,3,0,0 +17/09/2006,Chelsea,Liverpool,1,0,H,1,0,H,M Riley,4,9,3,6,1,3,13,17,3,2,1,0 +17/09/2006,Man United,Arsenal,0,1,A,0,0,D,G Poll,17,14,9,7,10,5,11,11,2,2,0,0 +17/09/2006,Tottenham,Fulham,0,0,D,0,0,D,M Clattenburg,18,8,8,3,12,2,11,20,2,2,0,0 +17/09/2006,West Ham,Newcastle,0,2,A,0,0,D,R Styles,10,16,7,9,5,6,11,11,3,3,0,0 +20/09/2006,Liverpool,Newcastle,2,0,H,1,0,H,M Halsey,22,11,11,6,8,6,12,13,1,1,0,0 +23/09/2006,Arsenal,Sheffield United,3,0,H,0,0,D,A Wiley,14,5,11,4,7,2,11,8,2,1,0,0 +23/09/2006,Aston Villa,Charlton,2,0,H,1,0,H,M Riley,12,8,5,3,4,4,10,14,0,1,0,0 +23/09/2006,Fulham,Chelsea,0,2,A,0,0,D,M Halsey,7,13,4,5,5,7,19,9,2,1,0,0 +23/09/2006,Liverpool,Tottenham,3,0,H,0,0,D,H Webb,14,5,6,1,6,0,12,9,1,0,0,0 +23/09/2006,Man City,West Ham,2,0,H,0,0,D,C Foy,17,4,8,0,4,6,12,15,0,0,0,0 +23/09/2006,Middlesbrough,Blackburn,0,1,A,0,1,A,G Poll,13,13,7,7,6,7,14,17,1,3,0,0 +23/09/2006,Reading,Man United,1,1,D,0,0,D,P Walton,3,18,1,8,2,6,6,10,0,0,0,0 +23/09/2006,Wigan,Watford,1,1,D,1,0,H,R Styles,8,10,4,4,4,3,15,15,2,2,0,0 +24/09/2006,Newcastle,Everton,1,1,D,1,1,D,S Bennett,21,12,13,5,9,6,12,18,2,4,1,1 +25/09/2006,Portsmouth,Bolton,0,1,A,0,1,A,U Rennie,13,4,5,3,11,2,8,13,2,2,0,0 +30/09/2006,Bolton,Liverpool,2,0,H,1,0,H,P Dowd,4,13,3,6,1,10,15,12,2,1,0,0 +30/09/2006,Charlton,Arsenal,1,2,A,1,1,D,M Clattenburg,10,21,6,12,4,7,19,8,4,4,0,0 +30/09/2006,Chelsea,Aston Villa,1,1,D,1,1,D,G Poll,18,9,10,5,7,5,13,14,0,2,0,0 +30/09/2006,Everton,Man City,1,1,D,1,0,H,A Marriner,14,11,10,5,8,4,14,17,2,2,0,0 +30/09/2006,Sheffield United,Middlesbrough,2,1,H,1,0,H,P Walton,13,7,7,2,10,4,8,12,1,3,0,0 +01/10/2006,Blackburn,Wigan,2,1,H,1,1,D,H Webb,12,11,8,6,5,4,16,17,3,4,0,0 +01/10/2006,Man United,Newcastle,2,0,H,1,0,H,M Dean,24,3,11,3,8,3,5,13,1,3,0,0 +01/10/2006,Tottenham,Portsmouth,2,1,H,2,1,H,C Foy,8,9,6,6,6,10,8,10,0,0,0,0 +01/10/2006,West Ham,Reading,0,1,A,0,1,A,U Rennie,13,2,8,1,10,5,8,7,4,2,0,0 +02/10/2006,Watford,Fulham,3,3,D,1,0,H,M Riley,9,9,4,3,3,2,24,12,1,3,0,0 +14/10/2006,Arsenal,Watford,3,0,H,2,0,H,H Webb,15,9,7,3,8,4,6,14,0,1,0,0 +14/10/2006,Aston Villa,Tottenham,1,1,D,0,0,D,M Atkinson,12,10,6,5,2,2,13,11,0,1,0,1 +14/10/2006,Liverpool,Blackburn,1,1,D,0,1,A,M Clattenburg,18,5,9,3,8,1,6,15,1,3,0,0 +14/10/2006,Man City,Sheffield United,0,0,D,0,0,D,M Dean,14,7,6,3,3,4,14,12,0,1,0,0 +14/10/2006,Middlesbrough,Everton,2,1,H,1,0,H,M Halsey,9,12,6,8,10,13,12,9,1,0,0,0 +14/10/2006,Portsmouth,West Ham,2,0,H,1,0,H,G Poll,18,6,6,3,4,4,20,11,4,4,0,0 +14/10/2006,Reading,Chelsea,0,1,A,0,1,A,M Riley,5,7,0,4,8,5,16,7,3,2,1,1 +14/10/2006,Wigan,Man United,1,3,A,1,0,H,S Bennett,6,12,2,5,2,5,7,8,0,2,0,0 +15/10/2006,Newcastle,Bolton,1,2,A,1,0,H,A Wiley,10,7,3,5,3,3,21,15,3,3,0,0 +16/10/2006,Fulham,Charlton,2,1,H,0,0,D,R Styles,11,10,6,3,3,6,9,12,1,3,0,0 +21/10/2006,Aston Villa,Fulham,1,1,D,1,1,D,C Foy,3,5,1,2,4,1,12,13,0,2,0,0 +21/10/2006,Charlton,Watford,0,0,D,0,0,D,L Mason,16,8,8,3,9,9,13,17,2,0,0,0 +21/10/2006,Chelsea,Portsmouth,2,1,H,0,0,D,M Clattenburg,26,10,18,1,9,5,6,12,3,2,0,0 +21/10/2006,Everton,Sheffield United,2,0,H,2,0,H,D Gallagher,13,10,5,4,4,3,13,11,0,1,0,1 +21/10/2006,Wigan,Man City,4,0,H,2,0,H,P Dowd,12,9,5,4,3,5,14,13,1,1,0,0 +22/10/2006,Blackburn,Bolton,0,1,A,0,0,D,M Dean,24,8,11,7,7,8,12,16,2,2,0,1 +22/10/2006,Man United,Liverpool,2,0,H,1,0,H,G Poll,10,13,8,5,5,5,14,21,1,3,0,0 +22/10/2006,Middlesbrough,Newcastle,1,0,H,0,0,D,M Atkinson,19,13,8,4,8,4,17,13,2,0,0,0 +22/10/2006,Reading,Arsenal,0,4,A,0,2,A,A Wiley,3,16,1,9,10,2,6,6,0,0,0,0 +22/10/2006,Tottenham,West Ham,1,0,H,1,0,H,S Bennett,13,5,5,2,9,9,14,15,3,4,0,0 +28/10/2006,Arsenal,Everton,1,1,D,0,1,A,M Riley,26,2,12,2,16,3,6,21,2,3,0,0 +28/10/2006,Bolton,Man United,0,4,A,0,2,A,R Styles,8,15,3,10,7,2,18,11,2,2,0,0 +28/10/2006,Fulham,Wigan,0,1,A,0,0,D,G Poll,4,15,1,7,5,4,11,12,1,2,0,0 +28/10/2006,Liverpool,Aston Villa,3,1,H,3,0,H,S Bennett,19,7,6,5,8,4,11,17,0,1,0,0 +28/10/2006,Newcastle,Charlton,0,0,D,0,0,D,M Dean,20,8,10,4,10,3,12,9,1,1,0,0 +28/10/2006,Portsmouth,Reading,3,1,H,1,0,H,P Dowd,18,8,12,4,5,13,18,6,0,0,0,0 +28/10/2006,Sheffield United,Chelsea,0,2,A,0,1,A,M Atkinson,6,10,2,7,8,7,15,12,2,0,0,0 +28/10/2006,Watford,Tottenham,0,0,D,0,0,D,U Rennie,9,11,2,8,7,7,14,14,3,1,0,0 +29/10/2006,West Ham,Blackburn,2,1,H,1,0,H,A Wiley,9,13,5,7,5,8,16,10,1,1,0,0 +30/10/2006,Man City,Middlesbrough,1,0,H,1,0,H,H Webb,14,4,5,0,9,1,16,22,2,4,0,0 +04/11/2006,Bolton,Wigan,0,1,A,0,0,D,M Clattenburg,13,9,5,3,5,3,15,14,1,2,0,0 +04/11/2006,Charlton,Man City,1,0,H,1,0,H,C Foy,18,15,11,9,5,12,10,9,1,2,0,0 +04/11/2006,Fulham,Everton,1,0,H,0,0,D,M Atkinson,5,5,3,3,4,10,8,17,0,1,0,0 +04/11/2006,Liverpool,Reading,2,0,H,1,0,H,U Rennie,21,5,10,2,9,6,8,14,0,2,0,0 +04/11/2006,Man United,Portsmouth,3,0,H,2,0,H,M Dean,18,8,11,5,9,5,6,9,0,1,0,0 +04/11/2006,Newcastle,Sheffield United,0,1,A,0,0,D,S Bennett,11,12,3,6,7,0,16,12,3,2,0,0 +04/11/2006,Watford,Middlesbrough,2,0,H,1,0,H,D Gallagher,14,5,6,2,10,4,15,15,1,1,0,0 +05/11/2006,Aston Villa,Blackburn,2,0,H,1,0,H,H Webb,8,5,5,3,4,3,15,13,1,3,0,0 +05/11/2006,Tottenham,Chelsea,2,1,H,1,1,D,G Poll,8,17,3,10,7,10,15,15,3,5,0,1 +05/11/2006,West Ham,Arsenal,1,0,H,0,0,D,R Styles,11,11,5,5,8,3,11,8,3,2,0,0 +11/11/2006,Blackburn,Man United,0,1,A,0,0,D,M Riley,9,14,4,7,6,4,17,8,4,3,0,0 +11/11/2006,Chelsea,Watford,4,0,H,2,0,H,H Webb,14,2,8,1,9,4,13,14,1,1,0,0 +11/11/2006,Everton,Aston Villa,0,1,A,0,1,A,P Dowd,10,17,4,9,6,2,16,19,2,3,0,0 +11/11/2006,Man City,Newcastle,0,0,D,0,0,D,G Poll,15,8,8,3,6,5,18,11,1,3,0,0 +11/11/2006,Middlesbrough,West Ham,1,0,H,0,0,D,M Halsey,11,11,8,7,7,1,7,10,1,3,0,0 +11/11/2006,Portsmouth,Fulham,1,1,D,0,0,D,S Bennett,23,7,12,4,19,5,11,7,0,3,0,0 +11/11/2006,Sheffield United,Bolton,2,2,D,0,1,A,A Wiley,8,6,4,4,4,5,5,6,1,1,0,0 +11/11/2006,Wigan,Charlton,3,2,H,2,0,H,D Gallagher,8,7,5,4,2,3,18,11,2,0,0,0 +12/11/2006,Arsenal,Liverpool,3,0,H,1,0,H,M Clattenburg,7,10,7,4,2,6,7,12,2,3,0,0 +12/11/2006,Reading,Tottenham,3,1,H,2,1,H,R Styles,8,9,4,3,7,2,14,7,1,1,0,0 +18/11/2006,Arsenal,Newcastle,1,1,D,0,1,A,M Atkinson,23,2,10,1,11,1,10,9,2,5,0,0 +18/11/2006,Chelsea,West Ham,1,0,H,1,0,H,M Dean,19,7,9,1,5,6,10,8,1,4,0,0 +18/11/2006,Everton,Bolton,1,0,H,0,0,D,U Rennie,9,13,2,2,5,8,18,14,3,0,0,0 +18/11/2006,Man City,Fulham,3,1,H,3,0,H,A Wiley,15,7,7,2,5,0,22,12,1,1,0,0 +18/11/2006,Middlesbrough,Liverpool,0,0,D,0,0,D,L Mason,1,17,1,10,2,12,14,3,0,0,0,0 +18/11/2006,Portsmouth,Watford,2,1,H,1,1,D,C Foy,17,8,11,4,9,5,14,18,1,3,0,0 +18/11/2006,Reading,Charlton,2,0,H,1,0,H,G Poll,11,4,4,2,4,9,6,11,0,3,0,0 +18/11/2006,Sheffield United,Man United,1,2,A,1,1,D,M Clattenburg,5,22,3,15,0,14,19,10,1,1,0,0 +19/11/2006,Blackburn,Tottenham,1,1,D,1,0,H,P Dowd,6,13,3,5,4,5,17,16,0,1,1,1 +19/11/2006,Wigan,Aston Villa,0,0,D,0,0,D,S Bennett,14,7,5,3,6,6,6,11,1,0,0,0 +25/11/2006,Aston Villa,Middlesbrough,1,1,D,1,1,D,P Walton,11,9,4,4,9,4,18,11,1,3,0,0 +25/11/2006,Bolton,Arsenal,3,1,H,2,1,H,M Dean,10,14,8,7,7,11,11,8,3,4,0,0 +25/11/2006,Charlton,Everton,1,1,D,0,0,D,A Wiley,16,9,8,6,6,7,16,13,2,0,0,0 +25/11/2006,Fulham,Reading,0,1,A,0,1,A,D Gallagher,10,8,4,2,4,3,6,14,0,3,1,0 +25/11/2006,Liverpool,Man City,1,0,H,0,0,D,R Styles,12,6,6,2,8,2,9,20,2,1,0,0 +25/11/2006,West Ham,Sheffield United,1,0,H,1,0,H,M Riley,12,13,7,5,5,9,16,22,2,4,0,0 +26/11/2006,Man United,Chelsea,1,1,D,1,0,H,H Webb,8,13,5,7,3,2,16,12,1,3,0,0 +26/11/2006,Newcastle,Portsmouth,1,0,H,0,0,D,M Halsey,13,7,10,2,10,3,13,12,2,2,0,0 +26/11/2006,Tottenham,Wigan,3,1,H,2,1,H,M Clattenburg,11,10,6,5,3,2,12,17,2,4,0,0 +28/11/2006,Watford,Sheffield United,0,1,A,0,0,D,M Atkinson,9,17,6,7,6,1,20,10,3,1,1,0 +29/11/2006,Aston Villa,Man City,1,3,A,0,2,A,M Dean,6,11,4,8,1,5,15,11,1,0,0,0 +29/11/2006,Bolton,Chelsea,0,1,A,0,1,A,S Bennett,9,16,3,10,3,5,13,13,3,1,0,0 +29/11/2006,Fulham,Arsenal,2,1,H,2,1,H,H Webb,13,7,7,2,6,6,17,15,4,5,0,1 +29/11/2006,Liverpool,Portsmouth,0,0,D,0,0,D,A Wiley,15,3,4,1,5,1,13,13,1,3,0,0 +29/11/2006,Man United,Everton,3,0,H,1,0,H,M Halsey,18,7,6,4,3,2,15,15,1,1,0,0 +02/12/2006,Arsenal,Tottenham,3,0,H,2,0,H,G Poll,6,7,5,4,3,2,15,15,1,2,0,0 +02/12/2006,Blackburn,Fulham,2,0,H,2,0,H,S Bennett,10,4,6,2,3,3,19,13,2,2,0,0 +02/12/2006,Middlesbrough,Man United,1,2,A,0,1,A,C Foy,13,11,5,7,6,9,12,16,2,3,0,0 +02/12/2006,Portsmouth,Aston Villa,2,2,D,0,1,A,U Rennie,18,8,6,4,6,7,14,19,2,4,1,0 +02/12/2006,Reading,Bolton,1,0,H,1,0,H,A Wiley,10,12,3,7,5,4,11,15,0,3,0,0 +02/12/2006,Sheffield United,Charlton,2,1,H,0,1,A,P Dowd,26,8,10,4,12,3,11,14,1,1,0,0 +02/12/2006,Wigan,Liverpool,0,4,A,0,4,A,M Riley,15,9,6,6,8,2,9,14,2,2,0,0 +03/12/2006,Everton,West Ham,2,0,H,0,0,D,M Atkinson,8,16,5,11,3,14,16,9,2,2,0,0 +04/12/2006,Man City,Watford,0,0,D,0,0,D,M Clattenburg,14,6,12,4,9,8,12,16,1,3,0,0 +05/12/2006,Charlton,Blackburn,1,0,H,0,0,D,C Foy,13,5,10,3,5,4,10,12,1,2,0,0 +05/12/2006,Tottenham,Middlesbrough,2,1,H,0,0,D,M Halsey,22,6,11,3,7,5,10,16,0,3,1,1 +06/12/2006,Newcastle,Reading,3,2,H,1,2,A,R Styles,14,14,8,5,1,8,11,13,1,1,0,0 +06/12/2006,West Ham,Wigan,0,2,A,0,0,D,A Wiley,13,9,6,4,7,9,12,16,0,3,0,0 +09/12/2006,Blackburn,Newcastle,1,3,A,0,2,A,D Gallagher,13,16,7,8,7,4,10,5,1,2,1,0 +09/12/2006,Bolton,West Ham,4,0,H,1,0,H,H Webb,17,3,10,1,4,3,11,11,2,2,0,0 +09/12/2006,Liverpool,Fulham,4,0,H,0,0,D,U Rennie,32,5,19,3,14,1,7,8,1,1,0,0 +09/12/2006,Man United,Man City,3,1,H,2,0,H,G Poll,20,15,11,7,7,4,15,19,1,3,0,1 +09/12/2006,Middlesbrough,Wigan,1,1,D,0,1,A,S Bennett,13,8,7,3,7,4,10,7,2,1,0,0 +09/12/2006,Portsmouth,Everton,2,0,H,2,0,H,M Clattenburg,13,12,8,5,6,4,20,11,2,4,0,0 +09/12/2006,Tottenham,Charlton,5,1,H,2,1,H,M Dean,13,12,9,3,5,9,7,11,2,4,0,0 +09/12/2006,Watford,Reading,0,0,D,0,0,D,C Foy,8,6,5,1,2,5,14,12,0,1,0,0 +10/12/2006,Chelsea,Arsenal,1,1,D,0,0,D,A Wiley,20,11,7,4,4,5,16,16,2,2,0,0 +11/12/2006,Sheffield United,Aston Villa,2,2,D,0,1,A,M Halsey,16,7,10,5,10,4,11,16,2,1,0,0 +13/12/2006,Chelsea,Newcastle,1,0,H,0,0,D,P Dowd,15,6,8,1,7,2,16,13,2,2,0,0 +13/12/2006,Wigan,Arsenal,0,1,A,0,0,D,R Styles,15,9,7,3,4,7,10,7,1,0,0,0 +16/12/2006,Arsenal,Portsmouth,2,2,D,0,1,A,S Bennett,14,7,8,5,7,4,13,13,0,2,0,0 +16/12/2006,Aston Villa,Bolton,0,1,A,0,0,D,M Clattenburg,15,7,9,3,8,2,16,19,0,4,0,0 +16/12/2006,Charlton,Liverpool,0,3,A,0,1,A,H Webb,7,22,2,10,2,8,9,5,1,1,0,0 +16/12/2006,Newcastle,Watford,2,1,H,0,0,D,M Atkinson,11,11,6,8,4,8,7,18,0,0,0,0 +16/12/2006,Reading,Blackburn,1,2,A,1,0,H,G Poll,10,10,5,7,9,3,17,8,1,2,0,0 +16/12/2006,Wigan,Sheffield United,0,1,A,0,1,A,P Walton,14,11,6,7,6,2,10,12,0,2,0,0 +17/12/2006,Everton,Chelsea,2,3,A,1,0,H,M Halsey,3,18,1,9,5,6,17,8,1,1,0,0 +17/12/2006,Man City,Tottenham,1,2,A,0,2,A,R Styles,10,11,4,7,6,3,13,11,1,0,0,0 +17/12/2006,West Ham,Man United,1,0,H,0,0,D,P Dowd,10,19,4,11,1,11,25,10,3,2,0,0 +18/12/2006,Fulham,Middlesbrough,2,1,H,2,0,H,M Dean,11,16,6,10,5,6,19,16,0,0,0,0 +23/12/2006,Arsenal,Blackburn,6,2,H,3,1,H,H Webb,18,11,12,7,7,6,14,14,3,3,0,0 +23/12/2006,Aston Villa,Man United,0,3,A,0,0,D,S Bennett,11,14,3,10,6,8,12,11,2,2,0,0 +23/12/2006,Fulham,West Ham,0,0,D,0,0,D,C Foy,11,13,7,4,2,7,17,17,1,2,0,1 +23/12/2006,Liverpool,Watford,2,0,H,0,0,D,P Dowd,15,7,10,4,7,6,10,17,0,0,0,0 +23/12/2006,Man City,Bolton,0,2,A,0,2,A,M Riley,5,9,3,3,8,2,23,16,1,3,1,0 +23/12/2006,Middlesbrough,Charlton,2,0,H,1,0,H,R Styles,16,8,9,6,11,4,1,9,0,1,0,0 +23/12/2006,Newcastle,Tottenham,3,1,H,3,1,H,A Wiley,12,17,9,6,5,5,12,18,1,1,0,0 +23/12/2006,Portsmouth,Sheffield United,3,1,H,0,1,A,G Poll,13,11,6,6,6,3,14,6,0,2,0,0 +23/12/2006,Reading,Everton,0,2,A,0,1,A,S Tanner,11,10,7,7,12,4,10,6,0,0,0,0 +23/12/2006,Wigan,Chelsea,2,3,A,1,2,A,M Dean,14,8,6,5,3,6,10,10,3,4,0,0 +26/12/2006,Blackburn,Liverpool,1,0,H,0,0,D,R Styles,8,18,4,12,3,5,13,6,3,2,0,0 +26/12/2006,Bolton,Newcastle,2,1,H,1,1,D,H Webb,10,10,7,5,5,2,15,14,3,3,0,0 +26/12/2006,Chelsea,Reading,2,2,D,1,0,H,A Wiley,16,6,5,1,6,7,14,9,1,1,0,0 +26/12/2006,Everton,Middlesbrough,0,0,D,0,0,D,P Dowd,10,7,4,2,12,2,12,17,2,1,0,0 +26/12/2006,Man United,Wigan,3,1,H,0,0,D,M Riley,17,6,8,3,8,1,12,14,0,0,0,0 +26/12/2006,Sheffield United,Man City,0,1,A,0,0,D,M Clattenburg,19,11,13,8,8,5,7,9,2,1,0,0 +26/12/2006,Tottenham,Aston Villa,2,1,H,0,0,D,U Rennie,12,9,10,7,4,4,12,14,1,2,0,0 +26/12/2006,Watford,Arsenal,1,2,A,1,1,D,M Dean,6,16,3,8,7,5,11,6,2,0,0,0 +26/12/2006,West Ham,Portsmouth,1,2,A,0,2,A,M Atkinson,14,12,7,6,6,4,12,13,2,1,0,0 +27/12/2006,Charlton,Fulham,2,2,D,2,1,H,G Poll,12,15,7,9,4,9,14,15,0,0,0,0 +30/12/2006,Blackburn,Middlesbrough,2,1,H,1,0,H,M Atkinson,14,8,7,5,6,3,16,21,1,3,0,0 +30/12/2006,Bolton,Portsmouth,3,2,H,2,1,H,P Walton,12,14,7,7,8,7,11,13,0,1,0,0 +30/12/2006,Charlton,Aston Villa,2,1,H,0,1,A,R Styles,14,6,8,2,7,4,10,10,2,1,0,1 +30/12/2006,Chelsea,Fulham,2,2,D,1,1,D,H Webb,15,11,4,6,5,5,14,11,1,1,0,0 +30/12/2006,Everton,Newcastle,3,0,H,1,0,H,D Gallagher,16,7,9,3,7,12,7,11,2,3,0,0 +30/12/2006,Man United,Reading,3,2,H,1,1,D,M Dean,28,7,12,3,13,4,9,11,1,2,0,1 +30/12/2006,Sheffield United,Arsenal,1,0,H,1,0,H,L Mason,11,9,5,4,5,9,11,5,2,3,0,0 +30/12/2006,Tottenham,Liverpool,0,1,A,0,1,A,M Halsey,9,10,3,5,2,3,5,21,0,0,0,0 +30/12/2006,West Ham,Man City,0,1,A,0,0,D,S Bennett,12,7,6,5,6,2,12,15,1,2,0,0 +01/01/2007,Fulham,Watford,0,0,D,0,0,D,S Bennett,16,13,4,5,6,7,6,17,1,2,0,1 +01/01/2007,Liverpool,Bolton,3,0,H,0,0,D,G Poll,19,1,9,0,8,8,18,12,0,3,0,0 +01/01/2007,Man City,Everton,2,1,H,0,0,D,U Rennie,11,15,7,5,4,6,13,13,2,3,0,0 +01/01/2007,Middlesbrough,Sheffield United,3,1,H,1,1,D,C Foy,13,6,7,3,7,3,10,11,2,1,0,0 +01/01/2007,Newcastle,Man United,2,2,D,1,1,D,R Styles,8,20,8,8,1,10,11,10,1,2,0,0 +01/01/2007,Portsmouth,Tottenham,1,1,D,1,0,H,A Marriner,15,9,9,7,10,6,12,12,1,4,0,0 +01/01/2007,Reading,West Ham,6,0,H,4,0,H,L Mason,18,9,9,3,3,3,6,13,1,0,0,0 +01/01/2007,Wigan,Blackburn,0,3,A,0,1,A,M Clattenburg,14,8,5,5,5,5,10,16,0,5,0,0 +02/01/2007,Arsenal,Charlton,4,0,H,2,0,H,M Riley,16,4,10,2,8,2,13,9,0,1,0,1 +02/01/2007,Aston Villa,Chelsea,0,0,D,0,0,D,P Dowd,4,22,2,12,5,8,7,13,1,3,0,0 +13/01/2007,Blackburn,Arsenal,0,2,A,0,1,A,C Foy,13,6,7,4,8,2,15,5,4,0,0,1 +13/01/2007,Bolton,Man City,0,0,D,0,0,D,A Marriner,8,7,4,0,5,2,14,13,2,2,0,0 +13/01/2007,Charlton,Middlesbrough,1,3,A,1,1,D,M Dean,16,12,9,9,5,3,12,15,3,1,0,0 +13/01/2007,Chelsea,Wigan,4,0,H,1,0,H,M Atkinson,12,2,6,1,7,3,15,17,1,3,0,0 +13/01/2007,Man United,Aston Villa,3,1,H,3,0,H,H Webb,27,7,17,5,8,4,8,15,1,1,0,0 +13/01/2007,Sheffield United,Portsmouth,1,1,D,1,0,H,L Probert,6,13,3,6,2,10,14,19,0,2,0,0 +13/01/2007,Watford,Liverpool,0,3,A,0,2,A,M Clattenburg,8,22,4,13,3,12,9,10,0,0,0,0 +13/01/2007,West Ham,Fulham,3,3,D,1,1,D,G Poll,10,9,7,6,8,5,20,21,3,6,1,0 +14/01/2007,Everton,Reading,1,1,D,0,1,A,M Riley,14,7,7,3,8,5,12,17,2,2,0,0 +14/01/2007,Tottenham,Newcastle,2,3,A,1,1,D,S Bennett,25,12,17,7,5,3,8,9,1,4,0,0 +20/01/2007,Aston Villa,Watford,2,0,H,0,0,D,P Walton,17,11,6,6,7,6,11,20,2,0,0,0 +20/01/2007,Fulham,Tottenham,1,1,D,0,0,D,M Dean,9,9,3,4,5,6,14,16,3,2,1,0 +20/01/2007,Liverpool,Chelsea,2,0,H,2,0,H,R Styles,17,11,8,3,3,7,10,17,0,1,0,0 +20/01/2007,Man City,Blackburn,0,3,A,0,1,A,P Dowd,13,12,9,5,6,3,14,11,1,3,0,0 +20/01/2007,Middlesbrough,Bolton,5,1,H,4,1,H,A Wiley,10,11,5,5,1,8,12,13,1,3,0,1 +20/01/2007,Newcastle,West Ham,2,2,D,1,2,A,U Rennie,14,12,7,7,10,2,16,26,3,4,0,0 +20/01/2007,Portsmouth,Charlton,0,1,A,0,0,D,M Clattenburg,9,8,3,5,10,7,9,11,0,3,0,0 +20/01/2007,Reading,Sheffield United,3,1,H,1,0,H,M Halsey,17,4,10,1,7,2,12,8,1,1,0,1 +21/01/2007,Arsenal,Man United,2,1,H,0,0,D,S Bennett,18,10,11,6,8,6,13,11,1,3,0,0 +21/01/2007,Wigan,Everton,0,2,A,0,0,D,H Webb,13,8,6,5,6,6,11,17,0,1,0,0 +23/01/2007,Watford,Blackburn,2,1,H,1,1,D,D Gallagher,15,15,7,3,9,6,14,7,2,0,0,0 +30/01/2007,Portsmouth,Middlesbrough,0,0,D,0,0,D,D Gallagher,15,3,4,3,10,5,10,10,0,1,0,0 +30/01/2007,Reading,Wigan,3,2,H,1,1,D,S Bennett,19,21,12,14,4,3,11,11,1,1,0,0 +30/01/2007,Sheffield United,Fulham,2,0,H,2,0,H,L Mason,16,7,8,0,5,4,14,10,1,1,0,0 +30/01/2007,West Ham,Liverpool,1,2,A,0,0,D,M Atkinson,13,15,6,9,4,9,11,8,1,1,0,0 +31/01/2007,Bolton,Charlton,1,1,D,1,1,D,L Probert,12,4,7,2,10,0,7,11,0,1,0,0 +31/01/2007,Chelsea,Blackburn,3,0,H,1,0,H,G Poll,18,6,15,4,3,9,12,11,0,1,0,0 +31/01/2007,Man United,Watford,4,0,H,1,0,H,M Dean,21,2,10,1,7,2,11,11,2,2,0,0 +31/01/2007,Newcastle,Aston Villa,3,1,H,2,1,H,H Webb,8,14,4,9,3,7,15,15,1,1,0,0 +03/02/2007,Aston Villa,West Ham,1,0,H,1,0,H,C Foy,17,7,6,2,9,6,13,21,0,1,0,0 +03/02/2007,Blackburn,Sheffield United,2,1,H,1,1,D,A Marriner,11,11,6,5,7,6,18,19,1,4,1,0 +03/02/2007,Charlton,Chelsea,0,1,A,0,1,A,M Halsey,8,17,5,8,6,5,7,8,0,1,0,0 +03/02/2007,Fulham,Newcastle,2,1,H,0,0,D,P Dowd,15,8,9,4,8,6,12,13,1,3,0,0 +03/02/2007,Liverpool,Everton,0,0,D,0,0,D,A Wiley,25,6,10,4,6,4,13,18,0,3,0,0 +03/02/2007,Man City,Reading,0,2,A,0,0,D,H Webb,16,7,7,3,5,6,12,13,0,0,0,0 +03/02/2007,Middlesbrough,Arsenal,1,1,D,0,0,D,M Riley,3,11,1,4,3,6,14,5,2,1,0,1 +03/02/2007,Watford,Bolton,0,1,A,0,0,D,R Styles,7,7,3,4,5,4,8,9,0,3,0,0 +03/02/2007,Wigan,Portsmouth,1,0,H,0,0,D,G Poll,15,5,8,4,6,6,10,8,1,0,0,0 +04/02/2007,Tottenham,Man United,0,4,A,0,1,A,M Clattenburg,10,12,5,7,5,7,8,16,2,3,0,0 +10/02/2007,Chelsea,Middlesbrough,3,0,H,1,0,H,C Foy,15,3,5,0,5,1,6,11,0,1,0,0 +10/02/2007,Everton,Blackburn,1,0,H,1,0,H,R Styles,15,8,8,4,3,10,15,13,2,1,0,0 +10/02/2007,Man United,Charlton,2,0,H,1,0,H,M Riley,18,8,11,2,5,3,11,8,0,1,0,0 +10/02/2007,Newcastle,Liverpool,2,1,H,1,1,D,M Halsey,7,13,3,9,3,4,9,14,1,1,0,0 +10/02/2007,Portsmouth,Man City,2,1,H,1,0,H,M Dean,17,6,10,3,13,8,12,12,3,3,0,0 +10/02/2007,Reading,Aston Villa,2,0,H,1,0,H,M Clattenburg,12,17,9,7,12,8,13,11,2,1,0,0 +10/02/2007,Sheffield United,Tottenham,2,1,H,1,1,D,M Atkinson,16,6,5,1,13,3,14,11,3,5,0,0 +10/02/2007,West Ham,Watford,0,1,A,0,1,A,A Wiley,23,8,10,5,10,4,17,23,4,2,0,0 +11/02/2007,Arsenal,Wigan,2,1,H,0,1,A,P Dowd,10,8,3,2,10,1,9,15,6,3,0,0 +11/02/2007,Bolton,Fulham,2,1,H,1,0,H,H Webb,7,7,4,6,9,8,14,11,1,4,0,0 +21/02/2007,Everton,Tottenham,1,2,A,1,1,D,U Rennie,10,8,3,4,5,1,7,14,2,1,0,0 +21/02/2007,Watford,Wigan,1,1,D,1,1,D,R Styles,10,16,6,13,8,3,11,11,1,1,0,1 +24/02/2007,Charlton,West Ham,4,0,H,3,0,H,R Styles,12,11,5,8,7,6,13,7,3,2,0,0 +24/02/2007,Fulham,Man United,1,2,A,1,1,D,P Walton,16,16,6,8,3,7,16,12,3,2,0,0 +24/02/2007,Liverpool,Sheffield United,4,0,H,2,0,H,S Bennett,8,6,4,2,5,1,12,9,0,2,0,0 +24/02/2007,Middlesbrough,Reading,2,1,H,1,0,H,M Halsey,9,10,6,4,9,5,9,12,1,1,0,0 +24/02/2007,Watford,Everton,0,3,A,0,2,A,L Mason,11,13,3,5,5,9,10,17,2,1,0,0 +25/02/2007,Blackburn,Portsmouth,3,0,H,2,0,H,M Clattenburg,13,13,11,6,7,4,11,16,2,3,0,0 +25/02/2007,Tottenham,Bolton,4,1,H,3,1,H,G Poll,14,10,10,6,5,5,2,17,0,4,1,0 +25/02/2007,Wigan,Newcastle,1,0,H,1,0,H,A Wiley,7,5,4,2,8,6,11,12,6,2,0,0 +03/03/2007,Arsenal,Reading,2,1,H,0,0,D,C Foy,16,7,9,4,16,7,8,14,1,1,0,0 +03/03/2007,Fulham,Aston Villa,1,1,D,1,1,D,L Mason,13,9,7,4,5,8,10,21,0,2,0,0 +03/03/2007,Liverpool,Man United,0,1,A,0,0,D,M Atkinson,15,5,8,3,12,2,11,12,2,2,0,1 +03/03/2007,Man City,Wigan,0,1,A,0,1,A,H Webb,4,9,3,5,9,4,13,14,3,2,0,0 +03/03/2007,Newcastle,Middlesbrough,0,0,D,0,0,D,S Bennett,11,7,8,4,8,2,12,14,0,3,0,0 +03/03/2007,Portsmouth,Chelsea,0,2,A,0,1,A,M Halsey,8,10,5,5,7,5,8,10,0,2,0,0 +03/03/2007,Sheffield United,Everton,1,1,D,0,0,D,M Riley,8,7,6,2,5,6,18,10,3,2,0,0 +03/03/2007,Watford,Charlton,2,2,D,2,0,H,P Walton,14,13,10,5,8,1,18,10,2,2,0,0 +04/03/2007,Bolton,Blackburn,1,2,A,0,0,D,R Styles,15,10,10,2,8,0,14,8,1,0,0,0 +04/03/2007,West Ham,Tottenham,3,4,A,2,0,H,M Dean,12,12,6,7,6,3,15,12,6,1,0,0 +14/03/2007,Aston Villa,Arsenal,0,1,A,0,1,A,M Atkinson,11,10,6,5,6,4,12,14,1,1,0,0 +14/03/2007,Man City,Chelsea,0,1,A,0,1,A,A Wiley,8,5,2,3,5,4,10,13,3,2,0,0 +17/03/2007,Blackburn,West Ham,1,2,A,0,0,D,H Webb,9,10,3,7,10,9,18,22,4,4,1,0 +17/03/2007,Chelsea,Sheffield United,3,0,H,2,0,H,C Foy,13,7,8,4,5,1,7,10,0,0,0,0 +17/03/2007,Man United,Bolton,4,1,H,3,0,H,A Wiley,15,7,7,0,1,5,14,13,1,2,0,0 +17/03/2007,Middlesbrough,Man City,0,2,A,0,0,D,K Stroud,11,14,5,8,10,3,13,21,1,2,0,0 +17/03/2007,Reading,Portsmouth,0,0,D,0,0,D,S Bennett,5,10,0,5,10,4,11,15,1,1,0,0 +17/03/2007,Tottenham,Watford,3,1,H,1,0,H,M Halsey,17,2,11,2,5,4,5,8,0,0,0,0 +17/03/2007,Wigan,Fulham,0,0,D,0,0,D,S Tanner,6,3,3,0,7,1,13,6,1,0,0,0 +18/03/2007,Aston Villa,Liverpool,0,0,D,0,0,D,L Mason,9,7,3,3,2,6,11,12,2,3,0,0 +18/03/2007,Charlton,Newcastle,2,0,H,0,0,D,D Gallagher,10,12,4,6,5,5,14,20,1,2,0,0 +18/03/2007,Everton,Arsenal,1,0,H,0,0,D,M Clattenburg,11,18,4,9,5,4,17,13,6,0,0,0 +31/03/2007,Bolton,Sheffield United,1,0,H,0,0,D,M Clattenburg,19,7,12,4,11,4,8,12,1,3,0,0 +31/03/2007,Charlton,Wigan,1,0,H,0,0,D,P Walton,4,6,1,1,4,9,10,15,1,3,0,0 +31/03/2007,Fulham,Portsmouth,1,1,D,0,1,A,M Riley,13,10,9,7,6,4,14,22,1,0,0,0 +31/03/2007,Liverpool,Arsenal,4,1,H,2,0,H,S Bennett,12,11,7,3,2,7,17,8,1,3,0,0 +31/03/2007,Man United,Blackburn,4,1,H,0,1,A,C Foy,27,6,17,3,8,1,7,12,1,3,0,0 +31/03/2007,Newcastle,Man City,0,1,A,0,0,D,G Poll,15,7,6,5,8,1,9,16,1,3,0,0 +31/03/2007,Watford,Chelsea,0,1,A,0,0,D,U Rennie,12,13,6,8,5,10,18,9,1,3,0,0 +31/03/2007,West Ham,Middlesbrough,2,0,H,2,0,H,M Halsey,9,10,6,3,3,4,11,17,0,1,0,0 +01/04/2007,Tottenham,Reading,1,0,H,1,0,H,A Wiley,18,6,7,3,5,7,7,6,1,0,0,0 +02/04/2007,Aston Villa,Everton,1,1,D,0,1,A,H Webb,17,18,10,8,5,6,18,16,3,3,0,0 +06/04/2007,Everton,Fulham,4,1,H,3,1,H,D Gallagh,14,15,10,8,4,10,18,12,0,0,0,0 +06/04/2007,Man City,Charlton,0,0,D,0,0,D,A Wiley,13,3,6,1,14,7,12,11,0,0,0,0 +07/04/2007,Arsenal,West Ham,0,1,A,0,1,A,G Poll,29,6,15,5,14,1,10,15,0,1,0,0 +07/04/2007,Blackburn,Aston Villa,1,2,A,1,1,D,M Atkinson,10,9,3,7,4,10,17,12,2,1,0,0 +07/04/2007,Chelsea,Tottenham,1,0,H,0,0,D,R Styles,11,6,8,5,11,7,13,11,2,1,0,0 +07/04/2007,Middlesbrough,Watford,4,1,H,2,1,H,C Foy,24,6,14,3,9,3,7,14,0,0,0,0 +07/04/2007,Portsmouth,Man United,2,1,H,1,0,H,M Clattenburg,14,15,9,7,8,7,13,2,3,1,0,0 +07/04/2007,Reading,Liverpool,1,2,A,0,1,A,P Walton,8,4,4,3,7,6,10,16,2,1,0,0 +07/04/2007,Sheffield United,Newcastle,1,2,A,0,1,A,M Halsey,17,10,11,3,5,4,11,13,1,1,0,0 +07/04/2007,Wigan,Bolton,1,3,A,1,1,D,U Rennie,17,10,9,9,9,5,7,13,0,3,0,0 +09/04/2007,Aston Villa,Wigan,1,1,D,0,1,A,M Halsey,13,2,7,1,13,1,11,11,2,3,0,1 +09/04/2007,Bolton,Everton,1,1,D,1,1,D,M Atkinson,7,9,4,7,7,7,16,19,3,1,0,0 +09/04/2007,Charlton,Reading,0,0,D,0,0,D,G Poll,8,14,5,6,1,11,13,13,1,0,0,0 +09/04/2007,Fulham,Man City,1,3,A,0,2,A,S Bennett,12,7,5,5,7,4,12,15,1,3,0,0 +09/04/2007,Newcastle,Arsenal,0,0,D,0,0,D,H Webb,8,6,4,2,3,6,13,14,1,2,0,0 +09/04/2007,Watford,Portsmouth,4,2,H,2,1,H,L Mason,15,10,10,6,6,2,13,15,0,0,0,0 +14/04/2007,Arsenal,Bolton,2,1,H,1,1,D,R Styles,19,6,9,2,2,5,7,18,2,3,0,1 +14/04/2007,Man City,Liverpool,0,0,D,0,0,D,U Rennie,7,11,1,3,4,4,12,15,1,2,0,0 +14/04/2007,Middlesbrough,Aston Villa,1,3,A,1,1,D,D Gallagher,13,9,5,7,2,4,20,8,2,1,0,0 +14/04/2007,Portsmouth,Newcastle,2,1,H,1,0,H,C Foy,21,7,7,3,10,5,13,8,2,0,0,0 +14/04/2007,Reading,Fulham,1,0,H,1,0,H,L Mason,12,13,6,5,10,6,8,7,2,1,0,0 +14/04/2007,Sheffield United,West Ham,3,0,H,1,0,H,S Bennett,6,11,5,3,4,2,11,8,2,3,0,0 +15/04/2007,Everton,Charlton,2,1,H,0,0,D,M Halsey,18,7,11,4,7,4,7,15,0,3,0,0 +15/04/2007,Wigan,Tottenham,3,3,D,2,2,D,G Poll,21,10,11,6,5,5,18,6,3,0,0,0 +17/04/2007,Arsenal,Man City,3,1,H,1,1,D,M Halsey,12,9,10,6,7,3,8,8,0,0,0,0 +17/04/2007,Man United,Sheffield United,2,0,H,1,0,H,R Styles,13,10,10,3,4,1,10,13,5,3,0,0 +18/04/2007,Blackburn,Watford,3,1,H,3,1,H,C Foy,18,4,10,4,5,4,8,14,0,0,0,0 +18/04/2007,Liverpool,Middlesbrough,2,0,H,0,0,D,G Poll,18,5,7,2,8,2,8,13,0,1,0,0 +18/04/2007,West Ham,Chelsea,1,4,A,1,2,A,M Dean,14,14,10,7,9,5,21,9,5,2,0,0 +21/04/2007,Bolton,Reading,1,3,A,0,0,D,H Webb,18,10,15,7,7,4,17,13,0,2,0,0 +21/04/2007,Charlton,Sheffield United,1,1,D,0,0,D,A Wiley,10,15,2,10,8,3,16,13,5,3,0,0 +21/04/2007,Fulham,Blackburn,1,1,D,1,0,H,G Poll,13,18,6,9,7,11,17,15,1,1,0,0 +21/04/2007,Liverpool,Wigan,2,0,H,1,0,H,M Riley,23,4,12,3,12,3,10,10,2,2,0,0 +21/04/2007,Man United,Middlesbrough,1,1,D,1,1,D,P Walton,21,10,9,5,6,7,10,17,2,5,0,0 +21/04/2007,Tottenham,Arsenal,2,2,D,1,0,H,M Dean,7,17,3,5,4,6,10,6,2,2,0,0 +21/04/2007,Watford,Man City,1,1,D,0,0,D,R Styles,4,4,1,1,5,3,9,6,1,1,0,0 +21/04/2007,West Ham,Everton,1,0,H,1,0,H,M Clattenburg,15,10,7,2,5,7,16,10,2,1,0,0 +22/04/2007,Aston Villa,Portsmouth,0,0,D,0,0,D,M Atkinson,17,5,8,2,15,3,10,20,0,1,0,0 +22/04/2007,Newcastle,Chelsea,0,0,D,0,0,D,M Halsey,11,12,3,4,4,2,15,16,0,3,0,0 +28/04/2007,Blackburn,Charlton,4,1,H,0,0,D,M Dean,16,8,9,3,7,2,14,5,1,1,0,1 +28/04/2007,Chelsea,Bolton,2,2,D,2,1,H,R Styles,11,6,3,3,11,2,8,10,0,3,0,0 +28/04/2007,Everton,Man United,2,4,A,1,0,H,A Wiley,8,18,3,12,4,9,17,11,1,1,0,0 +28/04/2007,Man City,Aston Villa,0,2,A,0,1,A,M Halsey,14,15,7,7,5,6,14,8,1,0,0,0 +28/04/2007,Middlesbrough,Tottenham,2,3,A,0,1,A,L Mason,16,11,8,5,7,3,11,9,1,1,0,0 +28/04/2007,Portsmouth,Liverpool,2,1,H,2,0,H,P Walton,9,11,7,4,7,6,12,7,1,1,0,0 +28/04/2007,Sheffield United,Watford,1,0,H,1,0,H,M Atkinson,11,13,6,3,5,7,13,17,1,4,0,0 +28/04/2007,Wigan,West Ham,0,3,A,0,1,A,G Poll,9,12,6,7,5,1,5,17,1,2,0,0 +29/04/2007,Arsenal,Fulham,3,1,H,1,0,H,M Clattenburg,10,7,5,1,7,6,6,10,2,4,0,0 +30/04/2007,Reading,Newcastle,1,0,H,0,0,D,M Riley,13,18,8,11,8,10,14,11,2,0,0,0 +05/05/2007,Aston Villa,Sheffield United,3,0,H,2,0,H,P Walton,19,9,11,2,13,5,14,14,2,2,0,0 +05/05/2007,Everton,Portsmouth,3,0,H,0,0,D,H Webb,13,12,7,5,9,8,9,19,3,3,0,0 +05/05/2007,Fulham,Liverpool,1,0,H,0,0,D,S Bennett,7,9,5,5,4,14,17,15,2,2,1,0 +05/05/2007,Man City,Man United,0,1,A,0,1,A,R Styles,4,6,3,3,5,3,13,10,2,2,0,0 +05/05/2007,Newcastle,Blackburn,0,2,A,0,1,A,M Atkinson,14,8,6,6,5,2,12,20,1,3,0,0 +05/05/2007,Reading,Watford,0,2,A,0,0,D,D Gallaghe,15,4,7,4,14,2,10,16,0,2,0,0 +05/05/2007,West Ham,Bolton,3,1,H,3,0,H,M Riley,9,8,6,3,5,3,17,21,2,5,0,0 +05/05/2007,Wigan,Middlesbrough,0,1,A,0,1,A,M Clattenburg,15,7,7,3,5,3,17,8,3,0,0,0 +06/05/2007,Arsenal,Chelsea,1,1,D,1,0,H,A Wiley,7,9,4,4,3,7,19,13,1,2,0,1 +07/05/2007,Charlton,Tottenham,0,2,A,0,1,A,G Poll,7,9,4,6,11,5,7,4,0,0,0,0 +09/05/2007,Chelsea,Man United,0,0,D,0,0,D,G Poll,15,5,10,0,8,1,12,20,3,4,0,0 +10/05/2007,Tottenham,Blackburn,1,1,D,0,1,A,R Styles,15,10,7,4,6,6,4,17,0,3,0,1 +13/05/2007,Blackburn,Reading,3,3,D,1,1,D,A Wiley,15,12,6,7,5,10,16,7,2,0,0,0 +13/05/2007,Bolton,Aston Villa,2,2,D,1,1,D,M Clattenburg,10,8,2,3,7,6,10,11,2,1,0,0 +13/05/2007,Chelsea,Everton,1,1,D,0,0,D,M Halsey,14,8,10,5,9,2,13,5,2,1,0,0 +13/05/2007,Liverpool,Charlton,2,2,D,0,1,A,D Gallagher,22,8,7,4,9,2,5,12,0,0,0,0 +13/05/2007,Man United,West Ham,0,1,A,0,1,A,M Atkinson,28,6,20,4,14,3,13,11,0,2,0,0 +13/05/2007,Middlesbrough,Fulham,3,1,H,2,1,H,M Riley,21,8,10,5,10,3,11,12,1,0,0,0 +13/05/2007,Portsmouth,Arsenal,0,0,D,0,0,D,G Poll,9,14,4,6,2,6,18,15,0,0,0,0 +13/05/2007,Sheffield United,Wigan,1,2,A,1,2,A,M Dean,9,9,5,3,10,3,11,15,3,5,0,1 +13/05/2007,Tottenham,Man City,2,1,H,2,1,H,S Bennett,8,10,3,5,6,6,11,13,1,1,0,0 +13/05/2007,Watford,Newcastle,1,1,D,0,1,A,R Styles,11,1,2,1,8,5,13,6,2,4,0,0 +11/08/2007,Aston Villa,Liverpool,1,2,A,0,1,A,M Riley,10,17,6,7,4,2,18,11,4,2,0,0 +11/08/2007,Bolton,Newcastle,1,3,A,0,3,A,C Foy,13,7,9,5,4,3,15,16,1,1,0,0 +11/08/2007,Derby,Portsmouth,2,2,D,1,1,D,M Dean,12,12,5,6,6,6,14,17,1,2,0,0 +11/08/2007,Everton,Wigan,2,1,H,1,0,H,M Clattenburg,12,14,8,4,6,2,8,13,0,0,0,0 +11/08/2007,Middlesbrough,Blackburn,1,2,A,1,0,H,A Marriner,10,4,6,4,13,3,16,16,3,4,0,0 +11/08/2007,Sunderland,Tottenham,1,0,H,0,0,D,A Wiley,9,6,4,3,7,2,14,14,1,1,0,0 +11/08/2007,West Ham,Man City,0,2,A,0,1,A,P Walton,9,14,2,5,5,3,13,11,0,4,0,0 +12/08/2007,Arsenal,Fulham,2,1,H,0,1,A,P Dowd,19,12,13,9,14,4,6,19,1,5,0,0 +12/08/2007,Chelsea,Birmingham,3,2,H,2,2,D,S Bennett,19,6,11,4,5,3,8,8,2,1,0,0 +12/08/2007,Man United,Reading,0,0,D,0,0,D,R Styles,22,3,9,2,12,3,8,16,0,2,0,1 +14/08/2007,Tottenham,Everton,1,3,A,1,3,A,M Halsey,14,16,8,10,9,4,16,9,2,0,0,0 +15/08/2007,Birmingham,Sunderland,2,2,D,1,0,H,K Stroud,7,7,3,3,2,7,17,10,1,1,0,0 +15/08/2007,Fulham,Bolton,2,1,H,2,1,H,L Probert,8,14,6,7,9,8,19,13,2,1,0,0 +15/08/2007,Man City,Derby,1,0,H,1,0,H,L Mason,9,5,6,3,4,4,12,13,3,1,0,0 +15/08/2007,Portsmouth,Man United,1,1,D,0,1,A,S Bennett,7,20,6,12,6,12,13,5,2,1,1,1 +15/08/2007,Reading,Chelsea,1,2,A,1,0,H,M Dean,15,15,11,7,4,11,15,16,4,5,1,0 +15/08/2007,Wigan,Middlesbrough,1,0,H,0,0,D,S Tanner,14,5,5,1,5,3,8,20,1,3,0,0 +18/08/2007,Birmingham,West Ham,0,1,A,0,0,D,M Halsey,6,13,3,5,9,5,16,15,4,2,0,0 +18/08/2007,Fulham,Middlesbrough,1,2,A,1,0,H,L Mason,13,9,6,6,8,7,9,12,1,3,0,0 +18/08/2007,Newcastle,Aston Villa,0,0,D,0,0,D,H Webb,6,11,2,5,8,6,16,12,0,2,0,0 +18/08/2007,Portsmouth,Bolton,3,1,H,2,1,H,S Tanner,11,8,6,3,10,6,13,11,0,2,0,0 +18/08/2007,Reading,Everton,1,0,H,1,0,H,S Bennett,10,13,5,5,4,12,14,8,2,2,0,0 +18/08/2007,Tottenham,Derby,4,0,H,3,0,H,C Foy,25,4,14,2,9,3,8,14,0,4,0,0 +18/08/2007,Wigan,Sunderland,3,0,H,1,0,H,M Riley,9,9,4,6,5,9,8,17,0,2,0,0 +19/08/2007,Blackburn,Arsenal,1,1,D,0,1,A,A Wiley,9,10,4,3,5,4,19,15,4,4,1,0 +19/08/2007,Liverpool,Chelsea,1,1,D,1,0,H,R Styles,18,6,8,2,5,2,11,16,4,5,0,0 +19/08/2007,Man City,Man United,1,0,H,1,0,H,M Clattenburg,6,15,2,6,0,11,8,11,2,2,0,0 +25/08/2007,Arsenal,Man City,1,0,H,0,0,D,C Foy,16,8,10,4,8,2,8,9,0,1,0,0 +25/08/2007,Aston Villa,Fulham,2,1,H,0,1,A,S Bennett,23,10,4,5,12,3,11,13,1,2,0,1 +25/08/2007,Bolton,Reading,3,0,H,1,0,H,M Clattenburg,18,5,12,2,10,7,10,7,2,1,0,0 +25/08/2007,Chelsea,Portsmouth,1,0,H,1,0,H,A Wiley,18,18,7,4,4,6,16,12,0,2,0,0 +25/08/2007,Derby,Birmingham,1,2,A,0,1,A,L Probert,16,11,8,6,4,4,8,13,0,1,0,0 +25/08/2007,Everton,Blackburn,1,1,D,0,1,A,M Riley,12,11,8,7,5,7,20,18,0,2,0,0 +25/08/2007,Sunderland,Liverpool,0,2,A,0,1,A,M Halsey,7,17,2,12,6,4,12,12,1,1,0,0 +25/08/2007,West Ham,Wigan,1,1,D,0,0,D,A Marriner,13,12,4,3,6,1,11,12,1,2,0,0 +26/08/2007,Man United,Tottenham,1,0,H,0,0,D,H Webb,14,10,5,3,4,4,15,7,2,3,0,0 +26/08/2007,Middlesbrough,Newcastle,2,2,D,1,1,D,M Dean,11,5,7,3,5,2,13,10,6,3,0,0 +01/09/2007,Bolton,Everton,1,2,A,0,1,A,P Walton,13,15,7,11,11,6,8,10,1,3,0,0 +01/09/2007,Fulham,Tottenham,3,3,D,1,2,A,M Riley,13,13,7,8,6,14,13,11,1,0,0,0 +01/09/2007,Liverpool,Derby,6,0,H,2,0,H,A Wiley,20,8,10,3,4,2,10,14,0,1,0,0 +01/09/2007,Man United,Sunderland,1,0,H,0,0,D,M Atkinson,17,4,9,1,6,0,10,7,1,2,0,0 +01/09/2007,Middlesbrough,Birmingham,2,0,H,2,0,H,R Styles,13,9,7,4,11,5,8,12,1,3,0,0 +01/09/2007,Newcastle,Wigan,1,0,H,0,0,D,S Bennett,20,7,8,6,9,1,15,18,2,4,0,1 +01/09/2007,Reading,West Ham,0,3,A,0,1,A,H Webb,9,13,3,9,14,4,10,11,0,1,0,0 +02/09/2007,Arsenal,Portsmouth,3,1,H,2,0,H,M Halsey,10,15,8,10,8,5,6,12,1,2,1,0 +02/09/2007,Aston Villa,Chelsea,2,0,H,0,0,D,M Clattenburg,9,20,5,8,2,13,17,11,4,1,0,0 +02/09/2007,Blackburn,Man City,1,0,H,1,0,H,M Dean,16,7,11,3,6,3,23,3,5,1,1,1 +15/09/2007,Birmingham,Bolton,1,0,H,1,0,H,P Dowd,10,8,5,3,6,5,16,15,1,3,0,0 +15/09/2007,Chelsea,Blackburn,0,0,D,0,0,D,H Webb,16,5,8,1,9,2,21,13,2,2,0,0 +15/09/2007,Everton,Man United,0,1,A,0,0,D,A Wiley,8,16,2,8,3,6,20,10,2,2,0,0 +15/09/2007,Portsmouth,Liverpool,0,0,D,0,0,D,M Riley,9,12,2,6,5,4,20,14,3,2,0,0 +15/09/2007,Sunderland,Reading,2,1,H,1,0,H,S Tanner,14,10,8,4,9,9,14,18,1,2,0,0 +15/09/2007,Tottenham,Arsenal,1,3,A,1,0,H,M Clattenburg,14,16,5,10,8,4,28,22,2,2,0,0 +15/09/2007,West Ham,Middlesbrough,3,0,H,0,0,D,S Bennett,13,16,7,12,5,8,14,14,2,1,0,0 +15/09/2007,Wigan,Fulham,1,1,D,0,1,A,R Styles,14,10,4,5,9,4,8,9,1,0,0,0 +16/09/2007,Man City,Aston Villa,1,0,H,0,0,D,M Atkinson,12,11,8,2,8,6,11,14,1,1,0,0 +17/09/2007,Derby,Newcastle,1,0,H,1,0,H,P Walton,10,8,3,3,5,5,16,11,2,3,0,0 +22/09/2007,Arsenal,Derby,5,0,H,2,0,H,M Atkinson,15,4,9,3,5,4,9,12,1,1,0,0 +22/09/2007,Fulham,Man City,3,3,D,1,1,D,M Halsey,13,11,8,5,6,2,10,7,3,2,0,0 +22/09/2007,Liverpool,Birmingham,0,0,D,0,0,D,L Mason,18,5,4,2,11,2,3,10,0,2,0,0 +22/09/2007,Middlesbrough,Sunderland,2,2,D,1,1,D,H Webb,16,11,5,6,7,2,18,11,3,4,0,0 +22/09/2007,Reading,Wigan,2,1,H,1,0,H,K Stroud,15,9,7,5,6,7,17,13,3,2,0,0 +23/09/2007,Aston Villa,Everton,2,0,H,1,0,H,L Probert,8,8,3,6,6,3,17,12,1,0,0,0 +23/09/2007,Blackburn,Portsmouth,0,1,A,0,1,A,C Foy,9,13,3,7,7,4,6,16,0,3,0,0 +23/09/2007,Bolton,Tottenham,1,1,D,1,1,D,A Marriner,5,10,4,7,4,11,14,15,0,2,0,0 +23/09/2007,Man United,Chelsea,2,0,H,1,0,H,M Dean,17,4,9,1,10,1,10,15,2,2,0,1 +23/09/2007,Newcastle,West Ham,3,1,H,2,1,H,M Riley,12,13,4,8,1,5,16,15,1,3,0,0 +29/09/2007,Birmingham,Man United,0,1,A,0,0,D,S Bennett,17,11,9,4,4,4,14,11,3,1,0,0 +29/09/2007,Chelsea,Fulham,0,0,D,0,0,D,M Atkinson,16,6,4,1,9,4,8,18,1,1,1,0 +29/09/2007,Derby,Bolton,1,1,D,1,1,D,R Styles,10,12,6,7,7,8,15,8,3,2,0,0 +29/09/2007,Man City,Newcastle,3,1,H,1,1,D,C Foy,16,12,12,8,3,9,7,10,0,0,0,0 +29/09/2007,Portsmouth,Reading,7,4,H,2,1,H,M Halsey,25,13,17,7,8,4,4,7,1,1,0,0 +29/09/2007,Sunderland,Blackburn,1,2,A,0,0,D,P Walton,14,14,12,8,10,2,11,4,1,0,0,0 +29/09/2007,West Ham,Arsenal,0,1,A,0,1,A,A Wiley,9,16,4,12,3,14,15,14,2,2,0,0 +29/09/2007,Wigan,Liverpool,0,1,A,0,0,D,M Clattenburg,9,14,3,10,5,6,11,10,2,1,0,0 +30/09/2007,Everton,Middlesbrough,2,0,H,1,0,H,M Riley,13,13,9,5,2,6,17,21,0,4,0,0 +01/10/2007,Tottenham,Aston Villa,4,4,D,1,3,A,M Dean,19,7,12,7,13,3,10,13,2,3,0,0 +06/10/2007,Aston Villa,West Ham,1,0,H,1,0,H,S Tanner,5,8,5,3,7,4,17,12,3,2,0,0 +06/10/2007,Man United,Wigan,4,0,H,0,0,D,M Riley,17,6,12,2,12,2,12,6,1,1,0,0 +07/10/2007,Arsenal,Sunderland,3,2,H,2,1,H,R Styles,18,11,8,7,8,2,10,9,0,2,0,1 +07/10/2007,Blackburn,Birmingham,2,1,H,1,0,H,M Clattenburg,17,6,5,5,6,7,15,13,3,1,0,0 +07/10/2007,Bolton,Chelsea,0,1,A,0,1,A,A Wiley,13,15,7,8,5,5,17,8,5,1,0,0 +07/10/2007,Fulham,Portsmouth,0,2,A,0,0,D,H Webb,13,14,10,8,8,5,7,17,1,0,0,0 +07/10/2007,Liverpool,Tottenham,2,2,D,1,1,D,M Halsey,21,11,9,6,5,3,18,10,0,1,0,0 +07/10/2007,Man City,Middlesbrough,3,1,H,2,0,H,S Bennett,18,17,13,11,8,8,11,13,2,3,0,0 +07/10/2007,Newcastle,Everton,3,2,H,1,0,H,M Atkinson,15,11,10,6,8,2,13,19,2,1,0,0 +07/10/2007,Reading,Derby,1,0,H,0,0,D,L Mason,11,7,3,0,3,3,9,18,1,1,0,0 +20/10/2007,Arsenal,Bolton,2,0,H,0,0,D,M Riley,19,1,9,0,5,3,14,20,1,5,0,0 +20/10/2007,Aston Villa,Man United,1,4,A,1,3,A,R Styles,11,21,5,13,3,9,8,8,2,2,2,0 +20/10/2007,Blackburn,Reading,4,2,H,3,0,H,A Marriner,11,13,6,7,4,9,8,7,0,0,0,0 +20/10/2007,Everton,Liverpool,1,2,A,1,0,H,M Clattenburg,6,17,1,7,9,6,8,14,1,2,2,0 +20/10/2007,Fulham,Derby,0,0,D,0,0,D,P Dowd,13,13,9,9,6,8,10,10,0,2,1,0 +20/10/2007,Man City,Birmingham,1,0,H,1,0,H,M Dean,12,13,4,8,6,7,17,12,3,2,0,0 +20/10/2007,Middlesbrough,Chelsea,0,2,A,0,1,A,M Halsey,7,9,4,6,6,6,9,9,1,1,0,0 +20/10/2007,Wigan,Portsmouth,0,2,A,0,0,D,P Walton,7,15,0,8,3,2,9,9,2,3,0,0 +21/10/2007,West Ham,Sunderland,3,1,H,1,0,H,C Foy,12,16,7,10,6,7,8,17,3,2,0,0 +22/10/2007,Newcastle,Tottenham,3,1,H,1,0,H,S Bennett,15,11,7,4,5,5,12,7,2,2,0,0 +27/10/2007,Birmingham,Wigan,3,2,H,1,1,D,S Tanner,16,8,10,5,6,5,11,12,0,1,0,0 +27/10/2007,Chelsea,Man City,6,0,H,2,0,H,M Riley,16,7,9,4,9,3,13,12,2,2,0,0 +27/10/2007,Man United,Middlesbrough,4,1,H,2,1,H,A Wiley,18,7,14,4,2,4,10,15,0,2,0,0 +27/10/2007,Portsmouth,West Ham,0,0,D,0,0,D,M Dean,10,10,3,3,11,5,18,9,2,4,0,0 +27/10/2007,Reading,Newcastle,2,1,H,0,0,D,P Dowd,12,6,5,2,5,8,14,10,0,1,0,0 +27/10/2007,Sunderland,Fulham,1,1,D,0,1,A,A Marriner,8,8,6,3,9,1,13,16,1,4,1,0 +28/10/2007,Bolton,Aston Villa,1,1,D,1,0,H,M Atkinson,13,8,6,3,7,3,15,13,2,1,0,0 +28/10/2007,Derby,Everton,0,2,A,0,1,A,S Bennett,5,7,2,3,0,2,11,12,1,1,0,0 +28/10/2007,Liverpool,Arsenal,1,1,D,1,0,H,H Webb,11,11,8,4,4,1,16,10,3,3,0,0 +28/10/2007,Tottenham,Blackburn,1,2,A,0,0,D,R Styles,9,9,6,4,10,2,11,14,4,3,0,0 +03/11/2007,Arsenal,Man United,2,2,D,0,1,A,H Webb,16,10,6,4,3,4,15,15,1,2,0,0 +03/11/2007,Aston Villa,Derby,2,0,H,0,0,D,M Halsey,15,8,7,2,7,4,7,20,0,3,0,0 +03/11/2007,Blackburn,Liverpool,0,0,D,0,0,D,M Atkinson,15,11,7,7,6,3,7,11,0,1,0,0 +03/11/2007,Everton,Birmingham,3,1,H,1,0,H,M Riley,16,7,9,6,6,5,14,22,0,1,0,0 +03/11/2007,Fulham,Reading,3,1,H,1,0,H,M Clattenburg,9,13,7,6,8,9,11,6,3,0,1,0 +03/11/2007,Middlesbrough,Tottenham,1,1,D,0,1,A,M Dean,5,9,3,4,6,7,13,8,1,0,0,0 +03/11/2007,Newcastle,Portsmouth,1,4,A,1,3,A,C Foy,14,10,7,7,4,5,15,15,2,2,0,0 +03/11/2007,Wigan,Chelsea,0,2,A,0,2,A,S Bennett,10,13,3,7,4,3,16,9,2,1,0,0 +04/11/2007,West Ham,Bolton,1,1,D,1,0,H,P Walton,9,11,7,6,8,3,13,13,2,1,0,0 +05/11/2007,Man City,Sunderland,1,0,H,0,0,D,A Wiley,7,8,3,2,2,3,11,14,0,1,0,0 +10/11/2007,Derby,West Ham,0,5,A,0,1,A,M Clattenburg,9,16,2,11,4,5,12,9,2,2,0,0 +10/11/2007,Liverpool,Fulham,2,0,H,0,0,D,S Tanner,20,5,12,3,8,2,9,12,0,2,0,0 +10/11/2007,Sunderland,Newcastle,1,1,D,0,0,D,M Atkinson,19,9,8,5,7,4,10,15,1,2,0,0 +11/11/2007,Birmingham,Aston Villa,1,2,A,0,1,A,S Bennett,13,13,8,6,8,5,17,8,3,2,0,0 +11/11/2007,Bolton,Middlesbrough,0,0,D,0,0,D,L Probert,15,8,7,7,6,7,16,22,1,2,0,0 +11/11/2007,Chelsea,Everton,1,1,D,0,0,D,A Wiley,15,6,10,2,9,1,13,16,2,2,0,0 +11/11/2007,Man United,Blackburn,2,0,H,2,0,H,C Foy,22,12,15,7,9,4,7,19,1,4,0,1 +11/11/2007,Portsmouth,Man City,0,0,D,0,0,D,M Halsey,20,10,12,5,9,1,11,7,1,1,0,0 +11/11/2007,Tottenham,Wigan,4,0,H,3,0,H,H Webb,19,6,8,2,5,2,8,16,0,2,0,0 +12/11/2007,Reading,Arsenal,1,3,A,0,1,A,R Styles,8,16,5,7,5,1,9,5,1,1,0,0 +24/11/2007,Arsenal,Wigan,2,0,H,0,0,D,P Walton,13,7,6,2,7,3,9,17,1,4,0,0 +24/11/2007,Birmingham,Portsmouth,0,2,A,0,1,A,M Atkinson,4,15,3,8,4,3,17,21,3,2,0,0 +24/11/2007,Bolton,Man United,1,0,H,1,0,H,M Clattenburg,6,13,5,3,4,6,16,13,3,0,0,0 +24/11/2007,Derby,Chelsea,0,2,A,0,1,A,A Marriner,9,9,4,5,3,2,8,6,0,1,0,1 +24/11/2007,Everton,Sunderland,7,1,H,3,1,H,P Dowd,24,13,18,7,2,6,9,13,0,1,0,0 +24/11/2007,Man City,Reading,2,1,H,1,1,D,S Tanner,13,5,9,3,2,7,12,15,0,2,0,0 +24/11/2007,Middlesbrough,Aston Villa,0,3,A,0,1,A,K Stroud,15,11,8,8,11,6,16,17,2,1,0,0 +24/11/2007,Newcastle,Liverpool,0,3,A,0,1,A,A Wiley,6,17,2,11,0,8,13,11,3,1,0,0 +25/11/2007,Fulham,Blackburn,2,2,D,0,0,D,M Dean,5,12,2,10,3,6,19,10,2,1,0,0 +25/11/2007,West Ham,Tottenham,1,1,D,1,0,H,M Riley,14,9,9,5,7,8,33,5,3,4,0,0 +28/11/2007,Blackburn,Aston Villa,0,4,A,0,1,A,P Dowd,16,7,10,7,11,2,11,16,0,1,1,0 +01/12/2007,Aston Villa,Arsenal,1,2,A,1,2,A,C Foy,11,14,5,9,4,7,16,10,3,2,0,0 +01/12/2007,Blackburn,Newcastle,3,1,H,0,0,D,M Atkinson,13,20,6,8,1,1,21,17,2,3,0,0 +01/12/2007,Chelsea,West Ham,1,0,H,0,0,D,H Webb,14,4,5,1,7,0,15,26,5,3,0,0 +01/12/2007,Portsmouth,Everton,0,0,D,0,0,D,P Walton,20,4,6,2,9,0,14,13,1,2,0,0 +01/12/2007,Reading,Middlesbrough,1,1,D,0,0,D,A Wiley,12,16,7,11,5,4,11,16,2,1,0,0 +01/12/2007,Sunderland,Derby,1,0,H,0,0,D,M Halsey,20,12,9,9,5,3,12,15,1,1,0,0 +01/12/2007,Wigan,Man City,1,1,D,1,1,D,M Riley,5,4,4,1,2,4,13,10,2,2,1,0 +02/12/2007,Liverpool,Bolton,4,0,H,2,0,H,S Bennett,23,6,14,3,5,5,11,14,0,3,0,0 +02/12/2007,Tottenham,Birmingham,2,3,A,0,1,A,P Dowd,17,7,9,3,12,3,10,13,0,1,1,0 +03/12/2007,Man United,Fulham,2,0,H,1,0,H,R Styles,15,12,11,3,7,3,3,13,2,1,0,0 +05/12/2007,Newcastle,Arsenal,1,1,D,0,1,A,M Dean,12,6,8,2,6,6,9,10,3,2,0,0 +08/12/2007,Aston Villa,Portsmouth,1,3,A,0,2,A,M Riley,14,12,6,8,7,5,18,23,2,5,0,0 +08/12/2007,Chelsea,Sunderland,2,0,H,1,0,H,P Walton,11,9,4,2,10,2,6,12,0,1,0,1 +08/12/2007,Everton,Fulham,3,0,H,0,0,D,S Bennett,15,6,11,4,12,7,9,9,0,2,0,0 +08/12/2007,Man United,Derby,4,1,H,2,0,H,C Foy,23,10,12,6,9,3,12,12,0,2,0,0 +08/12/2007,Newcastle,Birmingham,2,1,H,1,1,D,R Styles,17,7,9,4,6,5,10,8,1,2,0,0 +08/12/2007,Reading,Liverpool,3,1,H,1,1,D,A Marriner,7,15,4,5,7,7,10,10,0,1,0,0 +09/12/2007,Blackburn,West Ham,0,1,A,0,0,D,A Wiley,15,11,7,5,9,5,15,17,1,1,0,0 +09/12/2007,Bolton,Wigan,4,1,H,2,1,H,M Atkinson,8,11,6,7,5,11,13,11,2,1,0,0 +09/12/2007,Middlesbrough,Arsenal,2,1,H,1,0,H,H Webb,13,7,7,4,5,4,15,13,2,2,0,0 +09/12/2007,Tottenham,Man City,2,1,H,1,0,H,M Halsey,12,7,8,3,2,6,7,13,2,3,0,1 +15/12/2007,Birmingham,Reading,1,1,D,1,0,H,L Probert,15,10,9,6,6,6,12,19,2,1,0,0 +15/12/2007,Derby,Middlesbrough,0,1,A,0,1,A,R Styles,13,7,9,5,5,3,6,7,0,0,0,0 +15/12/2007,Fulham,Newcastle,0,1,A,0,0,D,H Webb,10,6,5,4,7,6,17,10,0,1,0,0 +15/12/2007,Man City,Bolton,4,2,H,1,2,A,P Walton,15,9,11,3,6,4,12,12,1,1,0,0 +15/12/2007,Portsmouth,Tottenham,0,1,A,0,0,D,M Atkinson,9,12,5,5,4,4,22,13,3,2,0,0 +15/12/2007,Sunderland,Aston Villa,1,1,D,1,0,H,S Bennett,9,14,7,11,5,13,17,14,2,2,0,0 +15/12/2007,West Ham,Everton,0,2,A,0,1,A,S Tanner,11,12,3,8,9,2,16,14,1,2,0,0 +15/12/2007,Wigan,Blackburn,5,3,H,3,1,H,M Clattenburg,9,9,7,5,6,4,17,24,4,4,0,1 +16/12/2007,Arsenal,Chelsea,1,0,H,1,0,H,A Wiley,11,10,5,7,3,5,19,12,5,5,0,0 +16/12/2007,Liverpool,Man United,0,1,A,0,1,A,M Halsey,19,5,6,3,3,3,14,13,2,3,0,0 +22/12/2007,Arsenal,Tottenham,2,1,H,0,0,D,R Styles,9,10,5,5,5,1,10,10,1,3,0,0 +22/12/2007,Aston Villa,Man City,1,1,D,1,1,D,L Mason,9,6,6,2,7,4,15,15,2,4,0,0 +22/12/2007,Bolton,Birmingham,3,0,H,0,0,D,C Foy,10,4,6,1,7,2,14,18,3,2,0,0 +22/12/2007,Fulham,Wigan,1,1,D,0,0,D,A Wiley,9,10,3,4,8,4,15,16,0,0,0,0 +22/12/2007,Liverpool,Portsmouth,4,1,H,2,0,H,M Riley,14,9,7,3,3,1,13,11,1,2,0,0 +22/12/2007,Middlesbrough,West Ham,1,2,A,1,1,D,M Atkinson,7,8,2,4,7,4,15,12,2,3,0,0 +22/12/2007,Reading,Sunderland,2,1,H,0,0,D,S Tanner,17,8,9,4,7,2,9,17,0,4,0,0 +23/12/2007,Blackburn,Chelsea,0,1,A,0,1,A,S Bennett,16,10,7,8,7,11,15,14,1,2,0,0 +23/12/2007,Man United,Everton,2,1,H,1,1,D,H Webb,18,4,11,2,8,2,10,13,3,3,0,0 +23/12/2007,Newcastle,Derby,2,2,D,1,1,D,A Marriner,23,9,13,6,9,2,12,20,2,2,0,0 +26/12/2007,Birmingham,Middlesbrough,3,0,H,2,0,H,M Halsey,13,18,5,12,12,9,13,6,1,2,0,0 +26/12/2007,Chelsea,Aston Villa,4,4,D,1,2,A,P Dowd,14,9,8,4,3,7,7,11,3,2,2,1 +26/12/2007,Derby,Liverpool,1,2,A,0,1,A,A Wiley,9,22,4,10,2,7,9,9,1,0,0,0 +26/12/2007,Everton,Bolton,2,0,H,0,0,D,R Styles,18,9,10,7,7,4,8,18,0,3,0,0 +26/12/2007,Portsmouth,Arsenal,0,0,D,0,0,D,S Bennett,9,12,6,6,4,7,15,9,2,1,0,0 +26/12/2007,Sunderland,Man United,0,4,A,0,3,A,U Rennie,10,17,3,11,4,3,14,12,2,1,0,0 +26/12/2007,Tottenham,Fulham,5,1,H,2,0,H,A Marriner,12,9,8,4,3,5,10,17,1,4,0,1 +26/12/2007,West Ham,Reading,1,1,D,1,0,H,P Walton,18,13,11,10,10,6,8,6,2,2,0,1 +26/12/2007,Wigan,Newcastle,1,0,H,0,0,D,M Dean,8,7,6,6,3,2,13,22,1,4,0,0 +27/12/2007,Man City,Blackburn,2,2,D,2,1,H,H Webb,5,7,2,5,3,3,10,16,1,1,0,0 +29/12/2007,Birmingham,Fulham,1,1,D,0,1,A,M Clattenburg,15,10,6,7,8,6,13,14,0,3,0,1 +29/12/2007,Chelsea,Newcastle,2,1,H,1,0,H,M Riley,16,3,10,2,12,3,9,11,1,2,0,0 +29/12/2007,Everton,Arsenal,1,4,A,1,0,H,M Atkinson,10,10,3,7,10,2,14,13,0,5,1,1 +29/12/2007,Portsmouth,Middlesbrough,0,1,A,0,1,A,A Wiley,18,13,9,7,4,4,10,17,1,4,0,0 +29/12/2007,Sunderland,Bolton,3,1,H,2,1,H,P Dowd,5,5,5,3,2,6,12,16,1,2,0,0 +29/12/2007,Tottenham,Reading,6,4,H,1,1,D,K Stroud,14,11,12,8,3,6,9,12,0,3,0,0 +29/12/2007,West Ham,Man United,2,1,H,0,1,A,M Dean,11,7,5,3,6,3,10,12,2,0,0,0 +29/12/2007,Wigan,Aston Villa,1,2,A,1,0,H,H Webb,3,5,3,4,6,5,18,9,1,1,0,0 +30/12/2007,Derby,Blackburn,1,2,A,1,2,A,P Walton,13,23,7,18,9,7,14,13,1,3,0,0 +30/12/2007,Man City,Liverpool,0,0,D,0,0,D,U Rennie,4,19,3,11,2,9,4,7,0,1,0,0 +01/01/2008,Arsenal,West Ham,2,0,H,2,0,H,C Foy,21,15,13,7,5,7,5,7,0,3,0,0 +01/01/2008,Aston Villa,Tottenham,2,1,H,1,0,H,S Tanner,14,9,4,6,8,2,5,16,0,3,0,0 +01/01/2008,Fulham,Chelsea,1,2,A,1,0,H,M Halsey,5,11,2,5,4,8,7,8,1,0,0,0 +01/01/2008,Man United,Birmingham,1,0,H,1,0,H,P Walton,18,11,13,4,2,0,11,8,1,4,0,0 +01/01/2008,Middlesbrough,Everton,0,2,A,0,0,D,M Riley,7,5,4,3,5,6,18,11,0,1,0,0 +01/01/2008,Reading,Portsmouth,0,2,A,0,1,A,M Dean,9,18,5,13,5,4,10,6,3,1,1,0 +02/01/2008,Blackburn,Sunderland,1,0,H,0,0,D,R Styles,8,7,3,4,7,3,12,11,3,4,0,1 +02/01/2008,Bolton,Derby,1,0,H,0,0,D,H Webb,12,9,5,3,11,7,8,10,2,3,0,0 +02/01/2008,Liverpool,Wigan,1,1,D,0,0,D,S Bennett,15,6,11,1,4,1,13,16,2,1,0,0 +02/01/2008,Newcastle,Man City,0,2,A,0,1,A,M Atkinson,16,12,10,7,8,7,8,12,0,3,0,0 +12/01/2008,Arsenal,Birmingham,1,1,D,1,0,H,P Dowd,16,2,9,2,14,3,8,10,1,2,0,0 +12/01/2008,Aston Villa,Reading,3,1,H,1,0,H,U Rennie,14,6,8,3,13,7,15,11,0,2,0,0 +12/01/2008,Chelsea,Tottenham,2,0,H,1,0,H,A Wiley,8,6,6,2,4,7,17,11,2,4,0,0 +12/01/2008,Derby,Wigan,0,1,A,0,0,D,M Clattenburg,2,12,1,4,1,6,12,16,1,2,1,0 +12/01/2008,Everton,Man City,1,0,H,1,0,H,M Halsey,10,5,5,1,4,4,14,12,2,1,0,0 +12/01/2008,Man United,Newcastle,6,0,H,0,0,D,R Styles,30,7,21,5,16,1,7,7,1,1,0,1 +12/01/2008,Middlesbrough,Liverpool,1,1,D,1,0,H,A Marriner,8,14,3,7,3,9,4,11,1,1,0,0 +12/01/2008,West Ham,Fulham,2,1,H,1,1,D,M Riley,12,9,9,2,8,7,19,13,0,1,0,0 +13/01/2008,Bolton,Blackburn,1,2,A,1,0,H,M Dean,9,13,5,6,2,6,17,18,2,1,0,0 +13/01/2008,Sunderland,Portsmouth,2,0,H,2,0,H,C Foy,12,9,5,5,7,5,14,6,3,1,0,0 +19/01/2008,Birmingham,Chelsea,0,1,A,0,0,D,R Styles,15,12,7,9,6,10,9,5,1,1,0,0 +19/01/2008,Blackburn,Middlesbrough,1,1,D,0,1,A,K Stroud,14,10,5,5,6,3,7,26,1,3,0,0 +19/01/2008,Fulham,Arsenal,0,3,A,0,2,A,P Walton,5,11,0,4,4,3,13,10,0,1,0,0 +19/01/2008,Newcastle,Bolton,0,0,D,0,0,D,A Wiley,13,7,5,5,9,6,14,15,1,2,0,0 +19/01/2008,Portsmouth,Derby,3,1,H,2,1,H,M Dean,18,8,8,5,6,2,14,9,1,2,0,0 +19/01/2008,Reading,Man United,0,2,A,0,0,D,S Bennett,9,21,6,9,8,6,11,8,1,1,0,0 +19/01/2008,Tottenham,Sunderland,2,0,H,1,0,H,L Mason,17,14,13,8,6,7,13,13,2,2,0,0 +20/01/2008,Man City,West Ham,1,1,D,1,1,D,P Dowd,11,11,6,5,5,10,15,20,2,4,0,0 +20/01/2008,Wigan,Everton,1,2,A,0,2,A,L Probert,14,11,3,6,5,5,15,15,1,2,0,0 +21/01/2008,Liverpool,Aston Villa,2,2,D,1,0,H,M Clattenburg,17,4,7,3,8,2,11,15,2,2,0,0 +26/01/2008,Aston Villa,Blackburn,1,1,D,0,0,D,H Webb,5,9,3,4,2,7,14,15,0,2,0,0 +29/01/2008,Arsenal,Newcastle,3,0,H,1,0,H,M Riley,10,7,4,3,4,2,11,15,1,3,0,0 +29/01/2008,Bolton,Fulham,0,0,D,0,0,D,M Clattenburg,13,9,4,4,4,2,11,14,0,0,0,0 +29/01/2008,Middlesbrough,Wigan,1,0,H,1,0,H,R Styles,7,10,3,6,4,9,12,13,0,1,0,0 +29/01/2008,Sunderland,Birmingham,2,0,H,1,0,H,M Halsey,9,5,6,2,4,8,9,18,1,3,0,0 +30/01/2008,Chelsea,Reading,1,0,H,1,0,H,M Dean,16,7,10,2,10,9,13,9,0,0,0,0 +30/01/2008,Derby,Man City,1,1,D,0,0,D,S Bennett,12,10,6,5,6,5,11,11,1,1,0,0 +30/01/2008,Everton,Tottenham,0,0,D,0,0,D,A Marriner,11,5,6,4,7,2,14,12,1,1,0,0 +30/01/2008,Man United,Portsmouth,2,0,H,2,0,H,M Atkinson,25,3,14,3,5,3,9,11,0,1,0,0 +30/01/2008,West Ham,Liverpool,1,0,H,0,0,D,A Wiley,8,13,2,5,4,6,11,19,0,3,0,0 +02/02/2008,Birmingham,Derby,1,1,D,0,0,D,P Walton,11,6,7,3,5,5,7,11,1,5,0,0 +02/02/2008,Blackburn,Everton,0,0,D,0,0,D,A Wiley,13,10,3,4,4,8,18,15,4,2,0,0 +02/02/2008,Liverpool,Sunderland,3,0,H,0,0,D,R Styles,14,6,11,3,4,1,8,10,1,2,0,0 +02/02/2008,Man City,Arsenal,1,3,A,1,2,A,A Marriner,5,14,2,9,1,9,7,7,2,0,0,0 +02/02/2008,Portsmouth,Chelsea,1,1,D,0,0,D,H Webb,17,20,6,12,4,8,13,12,2,0,0,0 +02/02/2008,Reading,Bolton,0,2,A,0,1,A,P Dowd,9,8,6,4,13,3,14,16,3,1,0,0 +02/02/2008,Tottenham,Man United,1,1,D,1,0,H,M Clattenburg,10,16,8,10,8,9,18,10,3,7,0,0 +02/02/2008,Wigan,West Ham,1,0,H,1,0,H,M Atkinson,10,5,3,1,6,3,11,12,1,3,0,0 +03/02/2008,Fulham,Aston Villa,2,1,H,0,0,D,C Foy,11,11,8,6,3,10,9,13,1,2,0,0 +03/02/2008,Newcastle,Middlesbrough,1,1,D,0,0,D,M Dean,10,19,8,11,11,6,13,11,1,4,0,0 +09/02/2008,Aston Villa,Newcastle,4,1,H,0,1,A,L Mason,13,5,8,3,7,5,19,11,2,1,0,0 +09/02/2008,Bolton,Portsmouth,0,1,A,0,0,D,P Walton,19,7,10,4,5,2,12,14,1,1,0,0 +09/02/2008,Derby,Tottenham,0,3,A,0,0,D,M Atkinson,10,13,6,8,7,9,14,10,3,1,0,0 +09/02/2008,Everton,Reading,1,0,H,0,0,D,M Halsey,11,14,6,5,8,8,13,13,0,0,0,0 +09/02/2008,Middlesbrough,Fulham,1,0,H,1,0,H,P Dowd,11,10,6,5,7,3,12,10,1,2,0,0 +09/02/2008,Sunderland,Wigan,2,0,H,1,0,H,M Dean,9,21,4,11,2,8,13,6,0,1,0,0 +09/02/2008,West Ham,Birmingham,1,1,D,1,1,D,M Clattenburg,18,5,10,3,7,4,12,20,0,5,1,0 +10/02/2008,Chelsea,Liverpool,0,0,D,0,0,D,M Riley,5,10,3,4,6,4,14,18,3,2,0,0 +10/02/2008,Man United,Man City,1,2,A,0,2,A,H Webb,19,9,9,7,8,7,6,11,1,0,0,0 +11/02/2008,Arsenal,Blackburn,2,0,H,1,0,H,S Bennett,13,6,9,2,7,5,12,6,1,0,0,0 +23/02/2008,Birmingham,Arsenal,2,2,D,1,0,H,M Dean,2,26,1,13,1,7,9,13,0,2,1,0 +23/02/2008,Fulham,West Ham,0,1,A,0,0,D,H Webb,18,12,14,6,5,8,12,17,3,0,1,0 +23/02/2008,Liverpool,Middlesbrough,3,2,H,2,1,H,L Mason,13,6,8,3,6,2,8,11,2,5,0,1 +23/02/2008,Newcastle,Man United,1,5,A,0,2,A,C Foy,10,16,5,10,7,4,13,12,4,0,0,0 +23/02/2008,Portsmouth,Sunderland,1,0,H,0,0,D,P Dowd,17,5,10,2,5,3,13,13,1,2,0,0 +23/02/2008,Wigan,Derby,2,0,H,0,0,D,A Wiley,22,5,18,3,14,2,10,12,0,1,0,0 +24/02/2008,Blackburn,Bolton,4,1,H,1,0,H,M Clattenburg,11,15,6,9,5,4,16,21,2,4,0,0 +24/02/2008,Reading,Aston Villa,1,2,A,0,1,A,M Atkinson,9,19,6,11,11,7,10,15,2,1,0,0 +25/02/2008,Man City,Everton,0,2,A,0,2,A,R Styles,20,13,12,8,4,7,7,14,0,1,1,0 +01/03/2008,Arsenal,Aston Villa,1,1,D,0,1,A,M Clattenburg,16,5,7,4,8,7,8,13,1,2,0,0 +01/03/2008,Birmingham,Tottenham,4,1,H,1,0,H,L Probert,13,19,10,10,5,6,14,10,1,0,0,0 +01/03/2008,Derby,Sunderland,0,0,D,0,0,D,M Riley,8,13,4,5,4,5,19,15,5,1,0,0 +01/03/2008,Fulham,Man United,0,3,A,0,2,A,M Dean,13,16,7,10,3,6,6,13,0,0,0,0 +01/03/2008,Man City,Wigan,0,0,D,0,0,D,S Bennett,12,8,6,5,4,1,12,7,0,1,0,0 +01/03/2008,Middlesbrough,Reading,0,1,A,0,0,D,H Webb,10,6,5,3,7,2,10,17,4,5,0,0 +01/03/2008,Newcastle,Blackburn,0,1,A,0,0,D,R Styles,17,14,9,6,14,7,10,7,1,2,0,0 +01/03/2008,West Ham,Chelsea,0,4,A,0,3,A,P Walton,9,13,7,8,6,1,14,6,0,2,0,1 +02/03/2008,Bolton,Liverpool,1,3,A,0,1,A,P Dowd,7,12,2,9,3,10,7,14,2,1,0,0 +02/03/2008,Everton,Portsmouth,3,1,H,1,1,D,A Marriner,13,9,5,5,10,3,12,18,1,1,0,0 +05/03/2008,Liverpool,West Ham,4,0,H,1,0,H,S Bennett,20,2,11,2,16,3,9,15,0,3,0,0 +08/03/2008,Blackburn,Fulham,1,1,D,0,0,D,M Riley,14,8,4,4,7,7,19,16,2,2,0,0 +08/03/2008,Liverpool,Newcastle,3,0,H,2,0,H,P Walton,15,6,13,4,12,8,8,7,1,0,0,0 +08/03/2008,Reading,Man City,2,0,H,0,0,D,U Rennie,13,12,8,5,7,5,9,11,0,2,0,0 +09/03/2008,Sunderland,Everton,0,1,A,0,0,D,A Wiley,9,7,6,6,3,4,17,9,2,1,0,0 +09/03/2008,Tottenham,West Ham,4,0,H,2,0,H,C Foy,18,8,11,4,8,4,6,17,0,2,0,1 +09/03/2008,Wigan,Arsenal,0,0,D,0,0,D,R Styles,6,10,3,6,8,5,10,8,2,2,0,0 +12/03/2008,Aston Villa,Middlesbrough,1,1,D,0,1,A,S Bennett,5,10,3,2,4,4,15,21,0,4,0,0 +12/03/2008,Chelsea,Derby,6,1,H,2,0,H,C Foy,14,4,10,3,7,0,7,17,0,1,0,0 +12/03/2008,Portsmouth,Birmingham,4,2,H,2,2,D,S Tanner,13,7,9,5,4,3,8,10,0,1,0,0 +15/03/2008,Arsenal,Middlesbrough,1,1,D,0,1,A,M Halsey,17,4,9,1,11,1,7,10,0,2,0,1 +15/03/2008,Derby,Man United,0,1,A,0,0,D,P Dowd,10,17,8,9,5,11,13,7,4,1,0,0 +15/03/2008,Liverpool,Reading,2,1,H,1,1,D,A Marriner,19,7,8,3,7,2,7,22,0,5,0,0 +15/03/2008,Portsmouth,Aston Villa,2,0,H,2,0,H,C Foy,13,16,5,10,3,6,17,13,3,1,1,1 +15/03/2008,Sunderland,Chelsea,0,1,A,0,1,A,M Dean,11,11,4,3,7,7,8,9,2,1,0,0 +15/03/2008,West Ham,Blackburn,2,1,H,1,1,D,M Atkinson,12,9,6,5,8,10,11,13,1,3,0,0 +16/03/2008,Fulham,Everton,1,0,H,0,0,D,S Bennett,10,7,2,4,3,3,14,6,1,0,0,0 +16/03/2008,Man City,Tottenham,2,1,H,0,1,A,M Clattenburg,13,12,8,7,5,5,11,3,0,0,0,0 +16/03/2008,Wigan,Bolton,1,0,H,1,0,H,S Tanner,10,12,7,7,8,5,16,13,2,3,1,0 +17/03/2008,Birmingham,Newcastle,1,1,D,1,0,H,H Webb,12,13,4,7,3,8,8,12,2,2,0,0 +19/03/2008,Man United,Bolton,2,0,H,2,0,H,A Wiley,21,14,12,10,6,3,12,14,0,2,0,0 +19/03/2008,Tottenham,Chelsea,4,4,D,1,2,A,M Riley,10,10,9,8,10,2,10,16,2,2,0,0 +22/03/2008,Aston Villa,Sunderland,0,1,A,0,0,D,H Webb,9,12,3,3,4,4,9,11,0,2,0,0 +22/03/2008,Blackburn,Wigan,3,1,H,2,1,H,M Dean,11,14,7,7,1,9,19,15,1,3,1,1 +22/03/2008,Bolton,Man City,0,0,D,0,0,D,A Marriner,17,7,7,4,12,12,9,14,1,0,0,0 +22/03/2008,Everton,West Ham,1,1,D,1,0,H,M Halsey,14,13,7,5,4,4,10,10,0,0,0,0 +22/03/2008,Middlesbrough,Derby,1,0,H,1,0,H,M Atkinson,6,5,3,1,9,2,9,10,2,1,0,0 +22/03/2008,Newcastle,Fulham,2,0,H,1,0,H,A Wiley,15,9,9,5,6,4,9,5,0,1,0,0 +22/03/2008,Reading,Birmingham,2,1,H,1,0,H,M Riley,13,13,8,7,8,7,16,15,0,1,0,0 +22/03/2008,Tottenham,Portsmouth,2,0,H,0,0,D,P Dowd,17,4,8,4,7,2,8,13,0,1,0,0 +23/03/2008,Chelsea,Arsenal,2,1,H,0,0,D,M Clattenburg,11,7,8,5,4,2,10,13,3,1,0,0 +23/03/2008,Man United,Liverpool,3,0,H,1,0,H,S Bennett,22,8,15,4,2,4,11,12,1,3,0,1 +29/03/2008,Birmingham,Man City,3,1,H,1,0,H,R Styles,10,8,7,4,5,2,13,10,0,2,1,0 +29/03/2008,Bolton,Arsenal,2,3,A,2,0,H,C Foy,13,17,9,8,8,6,12,10,2,2,0,1 +29/03/2008,Derby,Fulham,2,2,D,1,1,D,M Dean,19,9,13,4,4,5,18,16,2,4,0,0 +29/03/2008,Man United,Aston Villa,4,0,H,2,0,H,M Halsey,14,13,7,6,4,7,7,5,1,1,0,0 +29/03/2008,Portsmouth,Wigan,2,0,H,1,0,H,A Wiley,12,17,7,9,5,4,10,13,1,2,0,0 +29/03/2008,Reading,Blackburn,0,0,D,0,0,D,M Clattenburg,8,9,2,6,6,7,10,15,3,5,1,0 +29/03/2008,Sunderland,West Ham,2,1,H,1,1,D,A Marriner,22,10,12,4,13,4,9,10,2,1,0,0 +30/03/2008,Chelsea,Middlesbrough,1,0,H,1,0,H,P Dowd,16,7,6,3,8,3,11,9,1,1,0,0 +30/03/2008,Liverpool,Everton,1,0,H,1,0,H,H Webb,15,3,9,2,11,4,13,14,1,4,0,0 +30/03/2008,Tottenham,Newcastle,1,4,A,1,1,D,S Bennett,18,17,10,9,6,7,10,8,1,1,0,0 +05/04/2008,Arsenal,Liverpool,1,1,D,0,1,A,P Dowd,8,11,6,4,5,3,10,10,1,2,0,0 +05/04/2008,Aston Villa,Bolton,4,0,H,1,0,H,M Atkinson,10,6,6,3,5,4,15,9,2,1,0,0 +05/04/2008,Blackburn,Tottenham,1,1,D,1,1,D,P Walton,12,11,7,8,1,1,20,4,3,1,0,0 +05/04/2008,Fulham,Sunderland,1,3,A,0,1,A,M Halsey,14,9,5,6,8,5,12,9,2,0,0,0 +05/04/2008,Man City,Chelsea,0,2,A,0,1,A,C Foy,14,10,7,5,7,5,6,12,1,0,0,0 +05/04/2008,Newcastle,Reading,3,0,H,2,0,H,L Probert,14,8,9,3,9,7,10,11,0,1,0,0 +05/04/2008,Wigan,Birmingham,2,0,H,1,0,H,M Dean,15,5,8,1,2,2,11,17,1,2,0,1 +06/04/2008,Everton,Derby,1,0,H,0,0,D,A Marriner,11,7,5,5,9,8,17,19,1,3,0,0 +06/04/2008,Middlesbrough,Man United,2,2,D,1,1,D,M Riley,12,15,7,7,4,10,20,11,3,1,0,0 +08/04/2008,West Ham,Portsmouth,0,1,A,0,0,D,L Probert,10,18,8,9,7,4,16,11,0,0,0,0 +12/04/2008,Birmingham,Everton,1,1,D,0,0,D,S Bennett,14,13,7,6,11,1,9,6,1,1,0,0 +12/04/2008,Bolton,West Ham,1,0,H,0,0,D,P Walton,12,11,9,7,12,5,12,11,1,1,0,0 +12/04/2008,Derby,Aston Villa,0,6,A,0,3,A,K Stroud,8,16,5,12,3,2,15,15,0,0,0,0 +12/04/2008,Portsmouth,Newcastle,0,0,D,0,0,D,P Dowd,17,14,8,5,2,11,11,8,1,1,0,0 +12/04/2008,Reading,Fulham,0,2,A,0,1,A,C Foy,4,16,0,8,4,7,11,5,0,1,0,0 +12/04/2008,Sunderland,Man City,1,2,A,0,0,D,M Riley,12,7,7,5,6,2,13,12,3,2,0,0 +12/04/2008,Tottenham,Middlesbrough,1,1,D,1,0,H,M Halsey,12,13,9,7,2,7,6,19,0,3,0,0 +13/04/2008,Liverpool,Blackburn,3,1,H,0,0,D,A Wiley,17,7,9,3,5,1,12,10,1,0,0,0 +13/04/2008,Man United,Arsenal,2,1,H,0,0,D,H Webb,13,18,7,11,6,5,12,7,2,6,0,0 +14/04/2008,Chelsea,Wigan,1,1,D,0,0,D,A Marriner,19,6,10,5,9,3,10,17,0,2,0,0 +17/04/2008,Everton,Chelsea,0,1,A,0,1,A,M Atkinson,10,5,5,3,4,6,8,9,0,2,0,0 +19/04/2008,Arsenal,Reading,2,0,H,2,0,H,P Walton,19,4,12,2,12,4,7,15,0,3,0,0 +19/04/2008,Blackburn,Man United,1,1,D,1,0,H,R Styles,4,13,3,10,3,8,14,12,1,1,0,0 +19/04/2008,Fulham,Liverpool,0,2,A,0,1,A,M Atkinson,10,16,6,7,3,3,9,10,1,0,0,0 +19/04/2008,Middlesbrough,Bolton,0,1,A,0,0,D,A Wiley,16,12,10,9,7,4,10,14,0,2,0,0 +19/04/2008,West Ham,Derby,2,1,H,1,0,H,S Tanner,12,12,8,8,5,11,11,13,2,1,0,0 +19/04/2008,Wigan,Tottenham,1,1,D,1,1,D,L Probert,13,15,7,12,2,6,8,9,2,1,0,0 +20/04/2008,Aston Villa,Birmingham,5,1,H,2,0,H,M Clattenburg,16,11,9,6,7,8,11,20,0,1,0,0 +20/04/2008,Man City,Portsmouth,3,1,H,2,1,H,A Marriner,16,12,10,6,6,3,7,6,1,1,0,1 +20/04/2008,Newcastle,Sunderland,2,0,H,2,0,H,M Dean,8,12,4,8,4,4,8,13,0,5,0,0 +26/04/2008,Birmingham,Liverpool,2,2,D,1,0,H,P Walton,5,12,3,7,3,5,6,9,2,1,0,0 +26/04/2008,Chelsea,Man United,2,1,H,1,0,H,A Wiley,13,7,8,6,8,3,14,16,3,4,0,0 +26/04/2008,Man City,Fulham,2,3,A,2,0,H,M Dean,10,18,5,13,3,4,9,7,0,1,0,0 +26/04/2008,Sunderland,Middlesbrough,3,2,H,2,1,H,S Bennett,10,10,5,6,7,1,13,9,3,2,0,0 +26/04/2008,Tottenham,Bolton,1,1,D,0,0,D,M Clattenburg,26,7,13,4,12,3,6,11,1,3,0,0 +26/04/2008,West Ham,Newcastle,2,2,D,2,2,D,U Rennie,18,4,11,3,6,1,13,11,2,3,0,0 +26/04/2008,Wigan,Reading,0,0,D,0,0,D,M Atkinson,12,7,5,2,6,5,18,13,2,4,0,0 +27/04/2008,Everton,Aston Villa,2,2,D,0,0,D,P Dowd,13,6,7,3,9,6,10,17,3,3,0,0 +27/04/2008,Portsmouth,Blackburn,0,1,A,0,0,D,M Riley,15,12,6,9,8,7,20,12,2,2,0,0 +28/04/2008,Derby,Arsenal,2,6,A,1,2,A,A Marriner,12,20,10,11,5,6,6,7,0,0,0,0 +03/05/2008,Aston Villa,Wigan,0,2,A,0,0,D,R Styles,14,9,5,5,8,2,7,8,0,1,0,0 +03/05/2008,Blackburn,Derby,3,1,H,1,1,D,U Rennie,25,6,14,3,10,1,12,17,0,0,0,0 +03/05/2008,Bolton,Sunderland,2,0,H,1,0,H,M Atkinson,4,5,2,4,3,1,11,12,1,0,0,0 +03/05/2008,Fulham,Birmingham,2,0,H,0,0,D,C Foy,9,2,6,0,2,8,10,12,2,5,0,0 +03/05/2008,Man United,West Ham,4,1,H,3,1,H,M Riley,8,9,4,5,4,2,15,16,1,3,1,0 +03/05/2008,Middlesbrough,Portsmouth,2,0,H,1,0,H,P Walton,11,13,7,8,7,5,16,11,1,1,0,0 +03/05/2008,Reading,Tottenham,0,1,A,0,1,A,H Webb,13,13,6,7,10,1,10,7,1,0,0,0 +04/05/2008,Arsenal,Everton,1,0,H,0,0,D,A Wiley,11,8,2,4,8,3,10,4,0,1,0,0 +04/05/2008,Liverpool,Man City,1,0,H,0,0,D,M Halsey,23,4,10,3,12,5,9,5,0,0,0,0 +05/05/2008,Newcastle,Chelsea,0,2,A,0,0,D,S Bennett,13,18,5,9,1,6,13,15,2,2,0,0 +11/05/2008,Birmingham,Blackburn,4,1,H,1,0,H,H Webb,15,14,9,9,5,2,16,11,3,2,0,0 +11/05/2008,Chelsea,Bolton,1,1,D,0,0,D,C Foy,19,6,12,4,9,2,12,8,1,3,0,0 +11/05/2008,Derby,Reading,0,4,A,0,1,A,M Riley,6,11,2,7,2,0,17,13,0,1,0,0 +11/05/2008,Everton,Newcastle,3,1,H,1,0,H,P Walton,20,3,11,2,6,2,11,12,0,2,0,0 +11/05/2008,Middlesbrough,Man City,8,1,H,2,0,H,P Dowd,19,8,15,2,7,0,10,9,2,1,0,1 +11/05/2008,Portsmouth,Fulham,0,1,A,0,0,D,M Clattenburg,15,7,10,4,5,6,13,11,0,0,0,0 +11/05/2008,Sunderland,Arsenal,0,1,A,0,1,A,K Stroud,13,12,5,8,3,4,14,12,1,1,0,0 +11/05/2008,Tottenham,Liverpool,0,2,A,0,0,D,U Rennie,6,18,3,12,8,4,11,9,1,1,0,0 +11/05/2008,West Ham,Aston Villa,2,2,D,1,1,D,M Dean,16,13,8,8,7,8,9,21,2,2,0,0 +11/05/2008,Wigan,Man United,0,2,A,0,1,A,S Bennett,15,11,6,6,5,12,10,5,3,2,0,0 +16/08/2008,Arsenal,West Brom,1,0,H,1,0,H,H Webb,24,5,14,4,7,5,11,8,0,0,0,0 +16/08/2008,Bolton,Stoke,3,1,H,3,0,H,C Foy,14,8,8,2,4,3,13,12,1,2,0,0 +16/08/2008,Everton,Blackburn,2,3,A,1,1,D,A Marriner,10,15,5,11,3,5,11,9,2,2,0,0 +16/08/2008,Hull,Fulham,2,1,H,1,1,D,P Walton,11,12,6,6,5,6,10,9,3,0,0,0 +16/08/2008,Middlesbrough,Tottenham,2,1,H,0,0,D,M Atkinson,14,8,10,5,7,9,11,12,1,2,0,0 +16/08/2008,Sunderland,Liverpool,0,1,A,0,0,D,A Wiley,6,14,3,8,1,8,13,12,0,2,0,0 +16/08/2008,West Ham,Wigan,2,1,H,2,0,H,S Bennett,15,22,8,9,6,10,14,11,2,1,0,0 +17/08/2008,Aston Villa,Man City,4,2,H,0,0,D,P Dowd,14,13,10,8,7,8,8,11,0,1,0,0 +17/08/2008,Chelsea,Portsmouth,4,0,H,3,0,H,M Dean,18,12,11,8,8,3,10,6,0,1,0,0 +17/08/2008,Man United,Newcastle,1,1,D,1,1,D,M Riley,18,11,12,4,6,6,15,11,3,0,0,0 +23/08/2008,Blackburn,Hull,1,1,D,1,1,D,S Attwell,12,7,5,4,6,3,11,15,0,1,0,0 +23/08/2008,Fulham,Arsenal,1,0,H,1,0,H,M Atkinson,5,15,2,6,1,5,14,6,2,0,0,0 +23/08/2008,Liverpool,Middlesbrough,2,1,H,0,0,D,M Riley,16,9,8,4,14,3,10,12,2,1,0,0 +23/08/2008,Newcastle,Bolton,1,0,H,0,0,D,S Bennett,10,9,8,4,4,6,8,13,1,2,0,0 +23/08/2008,Stoke,Aston Villa,3,2,H,1,0,H,M Halsey,8,5,2,2,2,8,15,5,1,0,0,0 +23/08/2008,Tottenham,Sunderland,1,2,A,0,0,D,M Dean,18,12,11,7,12,6,12,12,1,2,0,0 +23/08/2008,West Brom,Everton,1,2,A,0,0,D,R Styles,14,9,4,7,11,5,11,14,3,1,0,0 +24/08/2008,Man City,West Ham,3,0,H,0,0,D,H Webb,14,3,9,0,6,3,12,16,0,2,0,1 +24/08/2008,Wigan,Chelsea,0,1,A,0,1,A,A Wiley,10,7,6,3,7,7,13,17,1,2,0,0 +25/08/2008,Portsmouth,Man United,0,1,A,0,1,A,C Foy,13,12,6,7,4,3,11,11,2,2,0,0 +30/08/2008,Arsenal,Newcastle,3,0,H,2,0,H,R Styles,20,4,14,2,12,1,12,11,2,2,0,0 +30/08/2008,Bolton,West Brom,0,0,D,0,0,D,K Stroud,18,12,9,7,7,5,18,16,2,2,0,0 +30/08/2008,Everton,Portsmouth,0,3,A,0,2,A,M Halsey,16,6,7,5,4,3,5,11,1,0,0,0 +30/08/2008,Hull,Wigan,0,5,A,0,2,A,M Jones,10,8,5,5,7,3,18,12,1,0,0,0 +30/08/2008,Middlesbrough,Stoke,2,1,H,1,0,H,M Dean,21,7,12,2,4,5,14,18,2,4,0,1 +30/08/2008,West Ham,Blackburn,4,1,H,2,1,H,M Riley,11,12,6,7,10,4,14,21,1,5,0,0 +31/08/2008,Aston Villa,Liverpool,0,0,D,0,0,D,M Atkinson,5,8,2,3,5,8,9,13,0,2,0,0 +31/08/2008,Chelsea,Tottenham,1,1,D,1,1,D,H Webb,12,6,3,6,5,8,10,13,3,0,0,0 +31/08/2008,Sunderland,Man City,0,3,A,0,1,A,C Foy,10,9,4,8,3,3,20,11,4,2,0,0 +13/09/2008,Blackburn,Arsenal,0,4,A,0,2,A,M Dean,10,17,3,10,5,5,8,8,1,1,0,0 +13/09/2008,Fulham,Bolton,2,1,H,2,0,H,S Tanner,20,8,13,7,12,6,15,11,1,1,0,0 +13/09/2008,Liverpool,Man United,2,1,H,1,1,D,H Webb,16,7,11,3,5,4,10,18,0,3,0,1 +13/09/2008,Man City,Chelsea,1,3,A,1,1,D,M Halsey,13,18,7,8,8,5,14,10,0,1,0,1 +13/09/2008,Newcastle,Hull,1,2,A,0,1,A,A Marriner,13,8,4,4,5,3,13,17,0,2,1,0 +13/09/2008,Portsmouth,Middlesbrough,2,1,H,0,1,A,S Attwell,21,7,12,3,3,2,8,6,2,1,0,0 +13/09/2008,West Brom,West Ham,3,2,H,2,2,D,L Probert,13,21,8,13,8,8,9,18,1,1,0,0 +13/09/2008,Wigan,Sunderland,1,1,D,0,1,A,R Styles,12,6,8,5,8,6,10,9,1,1,1,0 +14/09/2008,Stoke,Everton,2,3,A,0,1,A,A Wiley,12,8,7,5,7,8,10,19,2,1,0,0 +15/09/2008,Tottenham,Aston Villa,1,2,A,0,1,A,S Bennett,17,17,10,13,12,8,15,12,1,4,0,0 +20/09/2008,Blackburn,Fulham,1,0,H,0,0,D,L Probert,11,11,4,5,4,3,13,11,2,0,0,0 +20/09/2008,Bolton,Arsenal,1,3,A,1,2,A,S Bennett,12,20,8,9,7,17,12,10,2,2,0,0 +20/09/2008,Liverpool,Stoke,0,0,D,0,0,D,A Marriner,27,3,11,2,20,3,8,10,1,1,0,0 +20/09/2008,Sunderland,Middlesbrough,2,0,H,0,0,D,H Webb,17,14,11,11,8,7,9,16,2,3,0,0 +20/09/2008,West Ham,Newcastle,3,1,H,2,0,H,P Dowd,12,11,6,8,2,6,8,21,1,2,0,0 +21/09/2008,Chelsea,Man United,1,1,D,0,1,A,M Riley,20,13,13,8,7,4,15,18,1,7,0,0 +21/09/2008,Hull,Everton,2,2,D,1,0,H,L Mason,9,17,5,8,4,6,11,12,0,1,0,0 +21/09/2008,Man City,Portsmouth,6,0,H,2,0,H,A Wiley,14,10,10,4,8,5,13,14,0,1,0,0 +21/09/2008,Tottenham,Wigan,0,0,D,0,0,D,S Tanner,11,14,4,6,3,4,13,12,2,2,0,0 +21/09/2008,West Brom,Aston Villa,1,2,A,1,2,A,M Dean,10,10,6,5,5,6,22,16,3,3,0,0 +27/09/2008,Arsenal,Hull,1,2,A,0,0,D,A Wiley,18,7,9,5,15,4,8,7,2,1,0,0 +27/09/2008,Aston Villa,Sunderland,2,1,H,2,1,H,P Walton,12,9,8,3,5,5,8,9,1,1,0,0 +27/09/2008,Everton,Liverpool,0,2,A,0,0,D,M Riley,4,11,2,6,7,4,17,20,3,3,1,0 +27/09/2008,Fulham,West Ham,1,2,A,0,2,A,A Marriner,18,16,6,10,2,4,15,13,4,1,1,0 +27/09/2008,Man United,Bolton,2,0,H,0,0,D,R Styles,31,10,24,5,16,4,8,10,0,3,0,0 +27/09/2008,Middlesbrough,West Brom,0,1,A,0,0,D,C Foy,22,14,13,9,11,8,14,13,2,4,0,0 +27/09/2008,Newcastle,Blackburn,1,2,A,0,2,A,S Tanner,4,9,2,8,5,5,6,19,0,4,0,0 +27/09/2008,Stoke,Chelsea,0,2,A,0,1,A,M Atkinson,5,14,2,9,5,5,9,6,2,1,0,0 +28/09/2008,Portsmouth,Tottenham,2,0,H,1,0,H,M Dean,8,15,6,8,3,6,18,13,4,3,1,0 +28/09/2008,Wigan,Man City,2,1,H,2,1,H,S Bennett,9,11,4,6,5,3,12,21,3,4,0,0 +04/10/2008,Blackburn,Man United,0,2,A,0,1,A,S Bennett,6,18,3,9,3,4,11,8,2,1,0,0 +04/10/2008,Sunderland,Arsenal,1,1,D,0,0,D,L Mason,8,13,5,7,1,7,10,11,3,5,0,0 +04/10/2008,West Brom,Fulham,1,0,H,0,0,D,P Dowd,13,14,8,5,8,6,10,13,2,1,0,0 +04/10/2008,Wigan,Middlesbrough,0,1,A,0,0,D,M Atkinson,21,6,9,2,8,2,10,13,3,0,0,0 +05/10/2008,Chelsea,Aston Villa,2,0,H,2,0,H,C Foy,27,8,15,3,7,1,11,13,0,3,0,0 +05/10/2008,Everton,Newcastle,2,2,D,2,1,H,H Webb,13,11,5,9,3,5,18,15,1,3,0,0 +05/10/2008,Man City,Liverpool,2,3,A,2,0,H,P Walton,7,14,5,7,1,7,11,12,1,1,1,0 +05/10/2008,Portsmouth,Stoke,2,1,H,1,0,H,A Marriner,17,8,11,5,2,5,7,15,0,3,0,0 +05/10/2008,Tottenham,Hull,0,1,A,0,1,A,R Styles,27,10,16,6,12,0,15,15,4,2,0,0 +05/10/2008,West Ham,Bolton,1,3,A,0,2,A,M Dean,13,12,5,6,7,7,15,8,4,3,0,0 +18/10/2008,Arsenal,Everton,3,1,H,0,1,A,P Walton,22,7,14,3,7,4,8,13,2,4,0,0 +18/10/2008,Aston Villa,Portsmouth,0,0,D,0,0,D,M Riley,10,8,3,3,11,2,18,19,1,3,0,1 +18/10/2008,Bolton,Blackburn,0,0,D,0,0,D,H Webb,10,8,4,1,11,8,16,12,1,3,0,0 +18/10/2008,Fulham,Sunderland,0,0,D,0,0,D,K Stroud,13,14,9,7,8,5,12,8,1,1,0,0 +18/10/2008,Liverpool,Wigan,3,2,H,1,2,A,A Wiley,17,7,7,4,5,2,10,13,1,1,0,1 +18/10/2008,Man United,West Brom,4,0,H,0,0,D,M Halsey,18,6,14,2,15,1,11,14,0,1,0,0 +18/10/2008,Middlesbrough,Chelsea,0,5,A,0,1,A,P Dowd,7,21,4,14,5,7,13,9,2,0,0,0 +19/10/2008,Hull,West Ham,1,0,H,0,0,D,C Foy,12,14,4,5,3,5,11,11,2,1,0,0 +19/10/2008,Stoke,Tottenham,2,1,H,1,1,D,L Mason,7,11,3,6,6,1,12,12,1,2,0,2 +20/10/2008,Newcastle,Man City,2,2,D,1,1,D,R Styles,6,20,2,8,5,10,15,15,0,2,1,0 +25/10/2008,Blackburn,Middlesbrough,1,1,D,0,0,D,M Dean,13,5,5,2,9,2,17,11,3,1,0,0 +25/10/2008,Everton,Man United,1,1,D,0,1,A,A Wiley,13,18,9,14,5,9,17,11,3,3,0,0 +25/10/2008,Sunderland,Newcastle,2,1,H,1,1,D,M Riley,14,10,3,5,2,4,11,11,1,2,0,0 +25/10/2008,West Brom,Hull,0,3,A,0,0,D,L Probert,24,16,16,10,14,5,12,13,1,2,0,0 +26/10/2008,Chelsea,Liverpool,0,1,A,0,1,A,H Webb,14,8,5,3,4,2,17,13,3,3,0,0 +26/10/2008,Man City,Stoke,3,0,H,1,0,H,S Tanner,18,9,12,6,6,8,12,16,1,0,0,0 +26/10/2008,Portsmouth,Fulham,1,1,D,0,0,D,M Halsey,21,9,12,5,8,6,7,7,0,0,0,0 +26/10/2008,Tottenham,Bolton,2,0,H,1,0,H,A Marriner,19,10,9,3,3,4,9,16,0,3,0,1 +26/10/2008,West Ham,Arsenal,0,2,A,0,0,D,P Dowd,7,20,3,10,4,9,13,9,2,3,1,0 +26/10/2008,Wigan,Aston Villa,0,4,A,0,1,A,M Jones,17,9,5,7,9,4,14,8,1,2,0,0 +28/10/2008,Newcastle,West Brom,2,1,H,2,0,H,M Dean,10,14,5,4,7,8,13,11,0,4,0,0 +29/10/2008,Arsenal,Tottenham,4,4,D,1,1,D,M Atkinson,20,9,12,6,9,1,8,17,1,4,0,0 +29/10/2008,Aston Villa,Blackburn,3,2,H,1,1,D,K Stroud,8,11,4,5,4,5,5,17,0,1,0,0 +29/10/2008,Bolton,Everton,0,1,A,0,0,D,P Dowd,18,11,12,7,5,3,17,12,1,2,0,0 +29/10/2008,Fulham,Wigan,2,0,H,1,0,H,H Webb,7,5,2,2,2,2,9,14,0,2,0,0 +29/10/2008,Hull,Chelsea,0,3,A,0,1,A,A Marriner,10,27,5,11,5,5,11,13,0,2,0,0 +29/10/2008,Liverpool,Portsmouth,1,0,H,0,0,D,S Tanner,21,4,10,1,7,1,13,12,0,3,0,0 +29/10/2008,Man United,West Ham,2,0,H,2,0,H,P Walton,21,4,12,1,3,1,10,9,1,2,0,0 +29/10/2008,Middlesbrough,Man City,2,0,H,0,0,D,L Mason,5,13,3,8,3,3,10,15,1,3,0,0 +29/10/2008,Stoke,Sunderland,1,0,H,0,0,D,C Foy,9,9,6,4,4,4,10,8,2,1,0,0 +01/11/2008,Chelsea,Sunderland,5,0,H,3,0,H,M Atkinson,17,1,11,0,4,0,5,11,0,1,0,0 +01/11/2008,Everton,Fulham,1,0,H,0,0,D,L Mason,9,15,4,5,6,5,11,15,0,0,0,0 +01/11/2008,Man United,Hull,4,3,H,3,1,H,M Dean,20,11,11,7,9,1,12,13,2,2,0,0 +01/11/2008,Middlesbrough,West Ham,1,1,D,0,1,A,A Marriner,11,13,7,8,5,4,10,14,3,4,0,0 +01/11/2008,Portsmouth,Wigan,1,2,A,0,1,A,P Walton,16,12,8,8,9,6,9,11,0,3,0,0 +01/11/2008,Stoke,Arsenal,2,1,H,1,0,H,R Styles,7,13,5,6,3,5,11,7,4,2,0,1 +01/11/2008,Tottenham,Liverpool,2,1,H,0,1,A,P Dowd,8,14,4,6,4,4,18,13,1,2,0,0 +01/11/2008,West Brom,Blackburn,2,2,D,0,1,A,M Jones,15,9,8,5,2,3,13,18,3,2,0,1 +02/11/2008,Bolton,Man City,2,0,H,0,0,D,M Riley,20,11,12,6,6,1,15,20,1,1,0,0 +03/11/2008,Newcastle,Aston Villa,2,0,H,0,0,D,S Bennett,11,6,7,1,2,6,13,21,4,3,0,0 +08/11/2008,Arsenal,Man United,2,1,H,1,0,H,H Webb,11,17,7,9,5,6,16,7,3,2,0,0 +08/11/2008,Hull,Bolton,0,1,A,0,0,D,A Wiley,16,10,11,6,6,7,15,10,1,2,0,0 +08/11/2008,Liverpool,West Brom,3,0,H,2,0,H,P Walton,11,4,10,2,3,7,10,8,2,1,0,0 +08/11/2008,Sunderland,Portsmouth,1,2,A,1,0,H,S Bennett,9,10,6,5,8,3,8,10,2,1,0,0 +08/11/2008,West Ham,Everton,1,3,A,0,0,D,M Halsey,15,12,13,7,9,4,9,10,1,1,0,0 +08/11/2008,Wigan,Stoke,0,0,D,0,0,D,M Riley,22,2,14,1,6,3,10,12,1,3,0,0 +09/11/2008,Aston Villa,Middlesbrough,1,2,A,1,1,D,R Styles,14,15,9,8,7,4,13,5,2,2,0,0 +09/11/2008,Blackburn,Chelsea,0,2,A,0,1,A,C Foy,8,18,3,13,5,9,14,11,2,1,0,0 +09/11/2008,Fulham,Newcastle,2,1,H,1,0,H,M Atkinson,11,13,6,7,6,1,13,11,2,2,0,0 +09/11/2008,Man City,Tottenham,1,2,A,1,1,D,M Dean,9,14,7,9,4,5,12,13,1,2,2,1 +15/11/2008,Arsenal,Aston Villa,0,2,A,0,0,D,M Riley,11,9,8,6,8,5,9,22,2,4,0,0 +15/11/2008,Blackburn,Sunderland,1,2,A,1,0,H,S Tanner,16,10,8,6,11,9,9,9,3,4,0,0 +15/11/2008,Bolton,Liverpool,0,2,A,0,1,A,R Styles,15,15,9,6,7,3,7,7,1,1,0,0 +15/11/2008,Fulham,Tottenham,2,1,H,1,0,H,A Wiley,16,7,14,3,10,3,2,10,1,0,0,0 +15/11/2008,Man United,Stoke,5,0,H,2,0,H,P Walton,21,3,15,1,3,0,5,15,1,2,0,0 +15/11/2008,Newcastle,Wigan,2,2,D,0,1,A,A Marriner,17,12,9,7,4,10,9,12,1,3,0,1 +15/11/2008,West Brom,Chelsea,0,3,A,0,3,A,S Bennett,13,15,3,9,3,10,7,10,0,3,0,0 +15/11/2008,West Ham,Portsmouth,0,0,D,0,0,D,M Atkinson,11,11,5,7,8,7,6,11,0,1,0,0 +16/11/2008,Everton,Middlesbrough,1,1,D,0,1,A,H Webb,15,11,7,3,11,1,11,13,0,2,0,0 +16/11/2008,Hull,Man City,2,2,D,1,2,A,P Dowd,11,12,6,8,3,6,15,16,2,3,0,0 +22/11/2008,Aston Villa,Man United,0,0,D,0,0,D,C Foy,10,12,7,7,3,6,12,15,1,0,0,0 +22/11/2008,Chelsea,Newcastle,0,0,D,0,0,D,P Dowd,25,2,14,0,8,1,12,13,0,2,0,0 +22/11/2008,Liverpool,Fulham,0,0,D,0,0,D,M Halsey,18,6,7,6,9,3,7,6,0,0,0,0 +22/11/2008,Man City,Arsenal,3,0,H,1,0,H,A Wiley,12,11,8,5,3,3,16,9,0,1,0,0 +22/11/2008,Middlesbrough,Bolton,1,3,A,0,2,A,M Atkinson,18,11,5,5,8,0,10,15,2,5,0,0 +22/11/2008,Portsmouth,Hull,2,2,D,1,0,H,S Attwell,14,13,7,6,7,7,13,13,2,1,0,0 +22/11/2008,Stoke,West Brom,1,0,H,0,0,D,L Mason,9,8,7,2,4,1,9,7,1,0,0,0 +23/11/2008,Sunderland,West Ham,0,1,A,0,1,A,M Dean,17,10,6,5,4,2,13,16,2,1,0,0 +23/11/2008,Tottenham,Blackburn,1,0,H,1,0,H,H Webb,16,9,7,6,6,6,15,15,2,3,0,1 +24/11/2008,Wigan,Everton,1,0,H,0,0,D,R Styles,12,7,6,2,3,3,8,7,0,0,0,0 +29/11/2008,Aston Villa,Fulham,0,0,D,0,0,D,M Jones,12,5,4,2,11,4,9,16,0,2,0,0 +29/11/2008,Middlesbrough,Newcastle,0,0,D,0,0,D,A Wiley,14,10,7,5,7,2,9,7,1,1,0,0 +29/11/2008,Stoke,Hull,1,1,D,0,1,A,K Stroud,10,6,5,5,5,3,17,20,2,3,0,0 +29/11/2008,Sunderland,Bolton,1,4,A,1,3,A,C Foy,14,11,9,6,5,6,9,6,4,0,0,0 +29/11/2008,Wigan,West Brom,2,1,H,0,0,D,P Dowd,17,17,9,9,6,8,10,7,0,2,0,0 +30/11/2008,Chelsea,Arsenal,1,2,A,1,0,H,M Dean,12,9,5,6,3,2,18,16,2,0,0,0 +30/11/2008,Man City,Man United,0,1,A,0,1,A,H Webb,10,12,1,8,3,8,17,15,2,5,0,1 +30/11/2008,Portsmouth,Blackburn,3,2,H,0,0,D,M Halsey,9,10,6,2,8,10,5,12,0,1,0,0 +30/11/2008,Tottenham,Everton,0,1,A,0,0,D,S Bennett,14,8,6,6,5,1,9,16,1,3,0,0 +01/12/2008,Liverpool,West Ham,0,0,D,0,0,D,P Walton,21,7,8,3,17,5,5,7,1,1,0,0 +06/12/2008,Arsenal,Wigan,1,0,H,1,0,H,S Bennett,11,10,4,4,8,3,13,13,1,2,0,0 +06/12/2008,Blackburn,Liverpool,1,3,A,0,0,D,A Marriner,7,10,3,6,5,6,11,9,2,2,0,0 +06/12/2008,Bolton,Chelsea,0,2,A,0,2,A,H Webb,21,11,11,7,10,2,14,16,2,1,0,0 +06/12/2008,Fulham,Man City,1,1,D,1,1,D,R Styles,12,15,8,7,9,7,6,5,2,1,0,0 +06/12/2008,Hull,Middlesbrough,2,1,H,0,0,D,S Tanner,17,9,7,7,8,6,10,8,1,1,0,1 +06/12/2008,Man United,Sunderland,1,0,H,0,0,D,M Halsey,27,2,15,2,10,1,8,8,1,0,0,0 +06/12/2008,Newcastle,Stoke,2,2,D,2,0,H,M Riley,9,12,6,4,6,3,10,17,0,1,0,0 +07/12/2008,Everton,Aston Villa,2,3,A,1,1,D,M Atkinson,12,5,7,5,10,6,18,16,2,1,0,0 +07/12/2008,West Brom,Portsmouth,1,1,D,1,0,H,M Dean,14,6,8,3,3,5,11,12,1,2,0,0 +08/12/2008,West Ham,Tottenham,0,2,A,0,0,D,C Foy,11,17,4,12,7,5,13,16,2,0,0,0 +13/12/2008,Aston Villa,Bolton,4,2,H,2,1,H,L Probert,16,9,7,5,10,3,7,16,0,1,0,0 +13/12/2008,Liverpool,Hull,2,2,D,2,2,D,A Wiley,22,9,10,3,8,4,12,8,3,3,0,0 +13/12/2008,Man City,Everton,0,1,A,0,0,D,M Halsey,7,11,4,4,5,7,10,7,0,1,0,0 +13/12/2008,Middlesbrough,Arsenal,1,1,D,1,1,D,P Walton,8,17,3,11,5,10,8,11,0,1,0,0 +13/12/2008,Stoke,Fulham,0,0,D,0,0,D,S Attwell,6,9,2,6,5,6,16,2,1,1,0,0 +13/12/2008,Sunderland,West Brom,4,0,H,3,0,H,L Mason,16,14,10,6,1,11,8,14,1,2,0,0 +13/12/2008,Tottenham,Man United,0,0,D,0,0,D,M Dean,11,15,6,12,4,14,10,9,1,1,0,0 +13/12/2008,Wigan,Blackburn,3,0,H,2,0,H,H Webb,10,11,5,4,6,9,9,14,1,1,0,0 +14/12/2008,Chelsea,West Ham,1,1,D,0,1,A,M Riley,19,4,12,2,9,4,13,20,3,2,0,0 +14/12/2008,Portsmouth,Newcastle,0,3,A,0,0,D,C Foy,18,10,6,5,8,5,15,5,4,1,0,0 +20/12/2008,Blackburn,Stoke,3,0,H,3,0,H,P Walton,10,14,9,4,3,7,13,11,0,2,0,0 +20/12/2008,Bolton,Portsmouth,2,1,H,2,1,H,M Atkinson,19,8,10,5,6,3,14,16,1,1,0,0 +20/12/2008,Fulham,Middlesbrough,3,0,H,1,0,H,K Stroud,19,12,13,5,6,4,12,7,0,2,0,0 +20/12/2008,Hull,Sunderland,1,4,A,1,1,D,M Riley,14,15,8,6,6,6,20,10,3,1,1,0 +20/12/2008,West Ham,Aston Villa,0,1,A,0,0,D,M Halsey,17,10,13,4,7,6,7,10,2,1,0,0 +21/12/2008,Arsenal,Liverpool,1,1,D,1,1,D,H Webb,6,10,3,6,2,1,14,17,3,3,1,0 +21/12/2008,Newcastle,Tottenham,2,1,H,1,1,D,A Marriner,7,11,4,6,4,7,9,7,3,2,0,0 +21/12/2008,West Brom,Man City,2,1,H,0,0,D,C Foy,12,11,7,7,5,7,10,9,2,1,0,0 +22/12/2008,Everton,Chelsea,0,0,D,0,0,D,P Dowd,10,6,8,2,6,5,16,12,0,3,0,1 +26/12/2008,Aston Villa,Arsenal,2,2,D,0,1,A,L Mason,13,8,9,6,7,6,10,10,4,4,0,0 +26/12/2008,Chelsea,West Brom,2,0,H,2,0,H,R Styles,23,6,15,2,7,5,7,7,1,0,0,0 +26/12/2008,Liverpool,Bolton,3,0,H,1,0,H,A Wiley,18,4,8,2,16,3,7,12,1,3,0,0 +26/12/2008,Man City,Hull,5,1,H,4,0,H,A Marriner,23,15,16,11,8,10,15,14,0,4,0,0 +26/12/2008,Middlesbrough,Everton,0,1,A,0,0,D,M Riley,7,11,5,6,5,8,28,14,5,1,0,0 +26/12/2008,Portsmouth,West Ham,1,4,A,1,1,D,S Bennett,21,16,14,9,9,5,14,9,1,3,0,0 +26/12/2008,Stoke,Man United,0,1,A,0,0,D,C Foy,6,17,3,10,6,9,13,7,4,2,1,0 +26/12/2008,Sunderland,Blackburn,0,0,D,0,0,D,M Atkinson,13,7,8,3,5,2,5,11,0,3,0,0 +26/12/2008,Tottenham,Fulham,0,0,D,0,0,D,P Walton,14,10,7,7,4,2,7,11,0,1,0,0 +26/12/2008,Wigan,Newcastle,2,1,H,1,0,H,M Dean,11,5,5,3,7,4,14,11,2,3,0,1 +28/12/2008,Arsenal,Portsmouth,1,0,H,0,0,D,A Wiley,11,4,7,3,11,2,9,9,0,1,0,0 +28/12/2008,Blackburn,Man City,2,2,D,1,0,H,H Webb,11,13,6,3,4,3,14,9,2,1,0,0 +28/12/2008,Bolton,Wigan,0,1,A,0,1,A,P Dowd,14,10,8,7,8,7,14,13,2,3,0,0 +28/12/2008,Everton,Sunderland,3,0,H,2,0,H,R Styles,10,8,6,5,8,2,6,10,1,4,0,0 +28/12/2008,Fulham,Chelsea,2,2,D,1,0,H,A Marriner,5,19,2,14,1,7,9,8,1,2,0,0 +28/12/2008,Newcastle,Liverpool,1,5,A,1,2,A,M Halsey,10,17,7,12,4,9,7,11,2,1,0,0 +28/12/2008,West Brom,Tottenham,2,0,H,0,0,D,S Tanner,17,4,11,3,8,4,13,12,3,2,0,1 +28/12/2008,West Ham,Stoke,2,1,H,0,1,A,M Jones,15,8,7,5,8,4,10,13,3,3,0,1 +29/12/2008,Man United,Middlesbrough,1,0,H,0,0,D,M Atkinson,28,7,13,2,8,4,10,17,2,0,0,0 +30/12/2008,Hull,Aston Villa,0,1,A,0,0,D,S Bennett,8,7,3,3,9,5,11,8,1,2,0,0 +10/01/2009,Arsenal,Bolton,1,0,H,0,0,D,C Foy,14,2,9,2,5,0,13,16,2,2,0,0 +10/01/2009,Aston Villa,West Brom,2,1,H,2,0,H,S Bennett,21,9,13,6,10,7,12,17,1,1,0,0 +10/01/2009,Everton,Hull,2,0,H,2,0,H,M Atkinson,8,5,5,0,5,3,14,26,2,5,0,0 +10/01/2009,Middlesbrough,Sunderland,1,1,D,1,0,H,P Dowd,9,10,3,5,2,7,11,13,0,4,0,0 +10/01/2009,Newcastle,West Ham,2,2,D,1,1,D,A Wiley,21,12,12,11,8,3,10,18,1,0,0,0 +10/01/2009,Stoke,Liverpool,0,0,D,0,0,D,L Mason,5,7,0,3,4,5,10,10,2,2,0,0 +11/01/2009,Man United,Chelsea,3,0,H,1,0,H,H Webb,14,10,11,6,6,3,14,14,3,5,0,0 +11/01/2009,Wigan,Tottenham,1,0,H,0,0,D,A Marriner,13,6,5,3,8,3,13,13,3,3,0,0 +14/01/2009,Man United,Wigan,1,0,H,1,0,H,S Bennett,9,13,6,3,4,6,9,9,0,3,0,0 +17/01/2009,Blackburn,Newcastle,3,0,H,0,0,D,R Styles,9,10,4,3,2,5,13,10,2,3,0,1 +17/01/2009,Bolton,Man United,0,1,A,0,0,D,A Marriner,7,16,2,13,1,8,8,5,1,1,0,0 +17/01/2009,Chelsea,Stoke,2,1,H,0,0,D,P Walton,37,4,22,1,10,4,6,16,0,3,0,0 +17/01/2009,Hull,Arsenal,1,3,A,0,1,A,A Wiley,4,13,3,9,4,4,11,19,1,1,0,0 +17/01/2009,Man City,Wigan,1,0,H,0,0,D,L Mason,10,15,3,6,2,11,12,16,1,4,1,0 +17/01/2009,Sunderland,Aston Villa,1,2,A,1,0,H,M Dean,9,4,6,3,8,6,14,17,4,2,0,1 +17/01/2009,West Brom,Middlesbrough,3,0,H,1,0,H,M Halsey,12,9,8,3,5,3,13,4,0,2,0,1 +18/01/2009,Tottenham,Portsmouth,1,1,D,0,0,D,S Bennett,26,11,15,4,8,4,6,10,0,2,0,0 +18/01/2009,West Ham,Fulham,3,1,H,1,1,D,P Dowd,11,9,7,5,3,3,13,17,1,3,0,0 +19/01/2009,Liverpool,Everton,1,1,D,0,0,D,H Webb,9,7,5,6,4,4,19,15,0,2,0,0 +27/01/2009,Portsmouth,Aston Villa,0,1,A,0,1,A,P Walton,17,9,8,2,17,5,9,10,2,2,1,0 +27/01/2009,Sunderland,Fulham,1,0,H,0,0,D,M Halsey,16,15,11,8,3,1,2,10,0,0,0,0 +27/01/2009,Tottenham,Stoke,3,1,H,3,0,H,M Riley,22,6,14,3,7,4,19,21,2,3,0,0 +27/01/2009,West Brom,Man United,0,5,A,0,2,A,R Styles,10,17,4,10,5,4,11,7,5,2,1,0 +28/01/2009,Blackburn,Bolton,2,2,D,0,2,A,M Dean,15,9,12,5,6,4,12,14,3,4,0,0 +28/01/2009,Chelsea,Middlesbrough,2,0,H,0,0,D,L Probert,23,7,12,5,8,3,6,20,0,2,0,0 +28/01/2009,Everton,Arsenal,1,1,D,0,0,D,A Marriner,7,7,3,4,7,6,9,11,2,2,0,0 +28/01/2009,Man City,Newcastle,2,1,H,1,0,H,M Jones,14,2,7,1,7,0,8,11,1,2,0,0 +28/01/2009,West Ham,Hull,2,0,H,1,0,H,H Webb,25,15,13,12,11,5,11,15,0,1,0,0 +28/01/2009,Wigan,Liverpool,1,1,D,0,1,A,P Dowd,3,10,1,5,2,2,12,17,1,0,0,0 +31/01/2009,Arsenal,West Ham,0,0,D,0,0,D,S Bennett,15,2,8,2,13,3,12,12,2,2,0,0 +31/01/2009,Aston Villa,Wigan,0,0,D,0,0,D,R Styles,16,9,11,4,6,2,13,10,1,1,0,0 +31/01/2009,Bolton,Tottenham,3,2,H,1,0,H,P Dowd,13,13,7,10,2,8,11,14,0,1,0,0 +31/01/2009,Fulham,Portsmouth,3,1,H,1,0,H,A Wiley,6,12,6,6,5,8,9,8,0,1,0,0 +31/01/2009,Hull,West Brom,2,2,D,1,0,H,P Walton,16,13,12,7,8,6,13,12,2,4,0,0 +31/01/2009,Man United,Everton,1,0,H,1,0,H,M Halsey,17,7,10,4,6,2,11,14,0,0,0,0 +31/01/2009,Middlesbrough,Blackburn,0,0,D,0,0,D,C Foy,7,15,4,10,3,9,9,16,0,4,0,0 +31/01/2009,Stoke,Man City,1,0,H,1,0,H,M Atkinson,7,17,4,7,7,6,9,9,1,1,1,0 +01/02/2009,Liverpool,Chelsea,2,0,H,0,0,D,M Riley,21,4,15,1,12,1,20,15,4,3,0,1 +01/02/2009,Newcastle,Sunderland,1,1,D,0,1,A,H Webb,15,16,10,9,4,3,13,19,2,4,0,0 +07/02/2009,Blackburn,Aston Villa,0,2,A,0,1,A,S Bennett,17,15,8,8,4,13,11,9,2,0,0,0 +07/02/2009,Chelsea,Hull,0,0,D,0,0,D,L Mason,13,12,6,4,13,3,11,16,1,2,0,0 +07/02/2009,Everton,Bolton,3,0,H,1,0,H,P Walton,20,7,11,4,8,4,7,17,1,1,0,0 +07/02/2009,Man City,Middlesbrough,1,0,H,0,0,D,A Marriner,20,7,12,4,13,3,6,10,0,1,0,0 +07/02/2009,Portsmouth,Liverpool,2,3,A,0,0,D,H Webb,9,14,7,9,2,4,10,9,1,0,0,0 +07/02/2009,Sunderland,Stoke,2,0,H,0,0,D,R Styles,12,5,6,1,3,3,7,9,2,1,0,1 +07/02/2009,West Brom,Newcastle,2,3,A,1,3,A,C Foy,13,10,7,7,3,4,7,11,2,2,0,0 +07/02/2009,Wigan,Fulham,0,0,D,0,0,D,L Probert,10,8,3,2,4,8,9,12,1,2,0,0 +08/02/2009,Tottenham,Arsenal,0,0,D,0,0,D,M Dean,16,4,6,1,6,4,14,14,1,2,0,1 +08/02/2009,West Ham,Man United,0,1,A,0,0,D,P Dowd,9,11,5,6,5,11,14,8,1,1,0,0 +14/02/2009,Portsmouth,Man City,2,0,H,0,0,D,L Probert,15,10,9,7,8,2,12,8,1,3,0,0 +18/02/2009,Man United,Fulham,3,0,H,2,0,H,A Marriner,17,5,9,0,11,2,7,13,1,1,0,0 +21/02/2009,Arsenal,Sunderland,0,0,D,0,0,D,A Wiley,16,3,9,2,8,3,6,12,2,2,0,0 +21/02/2009,Aston Villa,Chelsea,0,1,A,0,1,A,M Halsey,12,18,5,12,6,11,9,12,1,3,0,0 +21/02/2009,Bolton,West Ham,2,1,H,2,0,H,S Tanner,12,17,8,12,4,13,18,14,0,2,0,0 +21/02/2009,Man United,Blackburn,2,1,H,1,1,D,H Webb,15,12,7,7,4,7,7,19,2,2,0,0 +21/02/2009,Middlesbrough,Wigan,0,0,D,0,0,D,M Dean,6,12,3,6,5,1,6,15,2,4,0,0 +21/02/2009,Stoke,Portsmouth,2,2,D,0,0,D,M Jones,6,6,2,2,2,3,6,10,1,3,0,0 +22/02/2009,Fulham,West Brom,2,0,H,0,0,D,M Atkinson,19,8,9,5,12,4,11,10,0,1,0,0 +22/02/2009,Liverpool,Man City,1,1,D,0,0,D,P Dowd,18,10,5,4,6,4,10,13,1,2,0,0 +22/02/2009,Newcastle,Everton,0,0,D,0,0,D,L Mason,4,15,0,5,0,6,15,15,0,1,1,0 +23/02/2009,Hull,Tottenham,1,2,A,1,1,D,L Probert,12,9,4,6,11,13,11,7,2,1,0,0 +28/02/2009,Arsenal,Fulham,0,0,D,0,0,D,P Walton,16,12,8,3,11,6,6,14,0,0,0,0 +28/02/2009,Chelsea,Wigan,2,1,H,1,0,H,L Probert,17,13,13,7,12,8,14,11,3,2,0,0 +28/02/2009,Everton,West Brom,2,0,H,1,0,H,S Bennett,11,17,6,9,3,6,9,14,1,1,0,0 +28/02/2009,Middlesbrough,Liverpool,2,0,H,1,0,H,R Styles,5,16,3,7,2,9,2,6,2,0,0,0 +01/03/2009,Aston Villa,Stoke,2,2,D,1,0,H,H Webb,11,7,7,3,6,6,7,12,1,1,0,0 +01/03/2009,Bolton,Newcastle,1,0,H,0,0,D,A Wiley,10,19,6,11,1,10,14,9,2,3,0,0 +01/03/2009,Hull,Blackburn,1,2,A,0,2,A,M Atkinson,9,8,5,6,6,4,14,19,2,3,1,1 +01/03/2009,West Ham,Man City,1,0,H,0,0,D,M Dean,13,9,9,2,4,7,14,18,2,2,0,0 +03/03/2009,Liverpool,Sunderland,2,0,H,0,0,D,M Halsey,22,5,12,2,6,2,9,6,1,1,0,0 +03/03/2009,Portsmouth,Chelsea,0,1,A,0,0,D,P Dowd,13,20,9,11,6,5,7,15,0,0,0,0 +03/03/2009,West Brom,Arsenal,1,3,A,1,3,A,S Tanner,8,16,4,8,3,4,19,14,4,0,0,0 +04/03/2009,Blackburn,Everton,0,0,D,0,0,D,A Wiley,8,9,3,5,2,4,11,14,1,0,0,0 +04/03/2009,Fulham,Hull,0,1,A,0,0,D,M Jones,17,10,9,3,13,5,11,14,3,1,0,0 +04/03/2009,Man City,Aston Villa,2,0,H,1,0,H,C Foy,15,7,7,5,6,5,7,11,1,2,0,0 +04/03/2009,Newcastle,Man United,1,2,A,1,1,D,S Bennett,9,11,6,9,3,10,10,10,2,2,0,0 +04/03/2009,Stoke,Bolton,2,0,H,1,0,H,M Dean,11,12,6,9,7,6,16,14,2,1,0,0 +04/03/2009,Tottenham,Middlesbrough,4,0,H,3,0,H,H Webb,12,15,7,6,4,8,7,6,0,1,0,0 +04/03/2009,Wigan,West Ham,0,1,A,0,1,A,S Attwell,20,8,8,4,8,1,9,11,2,5,1,1 +07/03/2009,Sunderland,Tottenham,1,1,D,1,0,H,P Dowd,8,13,3,8,4,1,17,9,3,2,0,0 +11/03/2009,Fulham,Blackburn,1,2,A,1,0,H,R Styles,10,16,8,9,9,9,14,8,1,1,0,0 +14/03/2009,Arsenal,Blackburn,4,0,H,1,0,H,P Dowd,18,7,12,3,7,4,8,14,0,4,0,0 +14/03/2009,Bolton,Fulham,1,3,A,1,1,D,C Foy,14,14,9,8,4,5,10,7,0,0,0,0 +14/03/2009,Everton,Stoke,3,1,H,2,0,H,A Marriner,17,10,13,5,4,9,7,12,0,1,0,0 +14/03/2009,Hull,Newcastle,1,1,D,1,1,D,H Webb,9,9,2,3,3,3,18,13,3,3,0,0 +14/03/2009,Man United,Liverpool,1,4,A,1,2,A,A Wiley,14,9,9,6,10,3,14,15,2,3,1,0 +14/03/2009,Middlesbrough,Portsmouth,1,1,D,0,1,A,M Atkinson,16,8,11,4,12,4,9,11,1,3,1,0 +14/03/2009,Sunderland,Wigan,1,2,A,1,2,A,M Dean,7,9,3,5,6,2,8,11,0,1,0,0 +15/03/2009,Aston Villa,Tottenham,1,2,A,0,1,A,S Bennett,11,10,5,9,9,5,12,9,1,3,0,0 +15/03/2009,Chelsea,Man City,1,0,H,1,0,H,M Riley,18,6,6,3,4,3,7,8,0,2,0,0 +16/03/2009,West Ham,West Brom,0,0,D,0,0,D,M Halsey,9,8,4,4,3,4,10,10,1,1,0,0 +21/03/2009,Blackburn,West Ham,1,1,D,0,1,A,C Foy,15,6,11,2,6,1,14,9,2,1,0,0 +21/03/2009,Fulham,Man United,2,0,H,1,0,H,P Dowd,15,13,6,8,3,4,14,10,2,4,0,2 +21/03/2009,Newcastle,Arsenal,1,3,A,0,0,D,M Halsey,11,15,8,10,8,4,12,12,0,1,0,0 +21/03/2009,Portsmouth,Everton,2,1,H,1,1,D,P Walton,16,5,11,2,7,4,19,8,2,1,0,0 +21/03/2009,Stoke,Middlesbrough,1,0,H,0,0,D,L Mason,10,17,4,8,5,4,18,13,4,1,0,0 +21/03/2009,Tottenham,Chelsea,1,0,H,0,0,D,M Dean,8,15,4,9,8,7,14,8,2,2,0,0 +21/03/2009,West Brom,Bolton,1,1,D,0,0,D,H Webb,11,14,4,11,8,4,10,12,2,1,0,0 +22/03/2009,Liverpool,Aston Villa,5,0,H,3,0,H,M Atkinson,14,6,7,4,4,6,10,13,1,3,0,1 +22/03/2009,Man City,Sunderland,1,0,H,0,0,D,S Tanner,18,6,10,5,7,7,16,12,3,6,0,1 +22/03/2009,Wigan,Hull,1,0,H,0,0,D,A Marriner,16,8,6,2,5,4,14,13,1,3,0,0 +04/04/2009,Arsenal,Man City,2,0,H,1,0,H,A Marriner,17,9,9,4,7,2,11,9,1,3,0,0 +04/04/2009,Blackburn,Tottenham,2,1,H,0,1,A,P Walton,11,9,6,3,4,7,13,9,1,1,0,1 +04/04/2009,Bolton,Middlesbrough,4,1,H,2,1,H,A Wiley,16,19,9,4,7,5,20,14,2,1,0,0 +04/04/2009,Fulham,Liverpool,0,1,A,0,0,D,S Bennett,3,19,1,10,3,6,8,10,2,2,0,0 +04/04/2009,Hull,Portsmouth,0,0,D,0,0,D,C Foy,9,7,1,2,1,4,13,14,0,2,0,1 +04/04/2009,Newcastle,Chelsea,0,2,A,0,0,D,R Styles,6,16,4,10,4,6,7,17,0,2,0,0 +04/04/2009,West Brom,Stoke,0,2,A,0,1,A,M Atkinson,12,9,9,6,6,3,10,10,2,1,0,0 +04/04/2009,West Ham,Sunderland,2,0,H,1,0,H,M Jones,16,14,11,9,11,8,10,7,1,1,0,0 +05/04/2009,Everton,Wigan,4,0,H,1,0,H,P Dowd,14,14,10,8,1,4,17,11,0,3,0,0 +05/04/2009,Man United,Aston Villa,3,2,H,1,1,D,M Riley,14,8,9,8,8,4,11,15,1,2,0,0 +11/04/2009,Chelsea,Bolton,4,3,H,1,0,H,P Walton,20,14,9,6,8,4,11,17,0,0,0,0 +11/04/2009,Liverpool,Blackburn,4,0,H,2,0,H,M Riley,22,7,12,5,7,2,6,13,2,1,0,0 +11/04/2009,Middlesbrough,Hull,3,1,H,2,1,H,P Dowd,15,12,9,6,3,3,15,16,3,3,0,0 +11/04/2009,Portsmouth,West Brom,2,2,D,1,0,H,M Dean,14,12,4,7,4,2,16,10,3,1,0,0 +11/04/2009,Stoke,Newcastle,1,1,D,1,0,H,C Foy,17,8,6,2,8,4,14,11,4,2,0,0 +11/04/2009,Sunderland,Man United,1,2,A,0,1,A,R Styles,12,18,7,8,5,4,6,3,1,2,0,0 +11/04/2009,Tottenham,West Ham,1,0,H,0,0,D,M Atkinson,14,8,9,6,10,1,9,12,1,4,0,0 +11/04/2009,Wigan,Arsenal,1,4,A,1,0,H,A Wiley,11,15,5,8,6,5,15,16,3,3,0,0 +12/04/2009,Aston Villa,Everton,3,3,D,1,2,A,H Webb,15,10,8,6,11,7,9,15,0,2,0,0 +12/04/2009,Man City,Fulham,1,3,A,1,0,H,M Halsey,13,15,5,11,8,3,9,8,0,1,0,0 +18/04/2009,Aston Villa,West Ham,1,1,D,1,0,H,R Styles,17,18,7,9,12,11,9,10,2,3,0,0 +18/04/2009,Middlesbrough,Fulham,0,0,D,0,0,D,P Walton,18,11,14,6,9,1,10,14,3,1,0,0 +18/04/2009,Portsmouth,Bolton,1,0,H,0,0,D,P Dowd,20,5,10,1,6,5,11,14,2,4,0,0 +18/04/2009,Stoke,Blackburn,1,0,H,0,0,D,H Webb,6,3,2,1,4,6,11,4,3,1,0,0 +18/04/2009,Sunderland,Hull,1,0,H,1,0,H,M Dean,8,13,4,3,6,6,11,23,2,3,0,0 +19/04/2009,Man City,West Brom,4,2,H,2,1,H,M Jones,18,26,11,17,3,9,18,14,1,2,0,0 +19/04/2009,Tottenham,Newcastle,1,0,H,1,0,H,M Halsey,22,8,18,3,4,7,13,15,1,2,0,0 +21/04/2009,Liverpool,Arsenal,4,4,D,0,1,A,H Webb,22,8,15,4,12,0,10,12,0,1,0,0 +22/04/2009,Chelsea,Everton,0,0,D,0,0,D,M Halsey,18,14,6,5,4,6,9,10,0,1,0,0 +22/04/2009,Man United,Portsmouth,2,0,H,1,0,H,P Walton,20,5,14,1,6,2,10,8,1,0,0,0 +25/04/2009,Bolton,Aston Villa,1,1,D,0,1,A,L Probert,9,14,6,6,4,5,12,9,0,1,0,0 +25/04/2009,Everton,Man City,1,2,A,0,1,A,A Wiley,12,16,4,10,5,3,12,14,1,3,0,0 +25/04/2009,Fulham,Stoke,1,0,H,1,0,H,L Mason,10,9,7,2,5,7,8,11,1,2,0,0 +25/04/2009,Hull,Liverpool,1,3,A,0,1,A,M Atkinson,7,21,5,8,3,8,15,14,3,1,1,0 +25/04/2009,Man United,Tottenham,5,2,H,0,2,A,H Webb,21,6,14,4,6,2,14,10,3,3,0,0 +25/04/2009,West Brom,Sunderland,3,0,H,1,0,H,M Halsey,15,9,9,5,6,3,9,14,0,1,0,0 +25/04/2009,West Ham,Chelsea,0,1,A,0,0,D,M Dean,8,20,6,8,3,5,9,11,1,0,0,0 +26/04/2009,Arsenal,Middlesbrough,2,0,H,1,0,H,C Foy,15,5,11,4,8,1,8,13,0,0,0,0 +26/04/2009,Blackburn,Wigan,2,0,H,1,0,H,P Walton,14,17,8,8,3,9,11,11,0,0,0,0 +27/04/2009,Newcastle,Portsmouth,0,0,D,0,0,D,M Riley,13,15,8,7,7,10,13,8,2,2,0,0 +02/05/2009,Chelsea,Fulham,3,1,H,2,1,H,A Wiley,13,8,5,3,5,4,6,7,0,1,0,0 +02/05/2009,Man City,Blackburn,3,1,H,3,0,H,M Dean,9,16,7,9,7,6,10,14,2,3,0,0 +02/05/2009,Middlesbrough,Man United,0,2,A,0,1,A,M Halsey,11,13,5,4,3,3,7,7,1,1,0,0 +02/05/2009,Portsmouth,Arsenal,0,3,A,0,2,A,L Mason,12,15,4,9,5,11,9,10,3,1,1,0 +02/05/2009,Stoke,West Ham,0,1,A,0,1,A,P Walton,13,7,7,3,7,0,8,9,3,2,0,0 +02/05/2009,Tottenham,West Brom,1,0,H,1,0,H,R Styles,16,10,9,7,9,7,6,7,1,1,0,0 +02/05/2009,Wigan,Bolton,0,0,D,0,0,D,M Jones,16,10,7,2,9,6,14,10,2,3,0,0 +03/05/2009,Liverpool,Newcastle,3,0,H,2,0,H,P Dowd,25,3,11,3,7,1,6,8,0,2,0,1 +03/05/2009,Sunderland,Everton,0,2,A,0,0,D,M Atkinson,8,10,3,8,9,3,15,12,3,4,0,0 +04/05/2009,Aston Villa,Hull,1,0,H,1,0,H,M Dean,12,9,8,5,13,7,13,10,0,2,0,0 +09/05/2009,Blackburn,Portsmouth,2,0,H,1,0,H,M Riley,8,7,6,4,4,3,20,12,2,1,0,0 +09/05/2009,Bolton,Sunderland,0,0,D,0,0,D,R Styles,8,15,4,4,10,3,12,3,2,0,0,0 +09/05/2009,Everton,Tottenham,0,0,D,0,0,D,L Mason,13,4,5,2,4,5,16,10,2,3,0,0 +09/05/2009,Fulham,Aston Villa,3,1,H,1,1,D,M Halsey,15,7,11,2,7,5,10,9,1,0,0,0 +09/05/2009,Hull,Stoke,1,2,A,0,1,A,H Webb,14,10,5,2,5,3,12,15,0,1,0,0 +09/05/2009,West Brom,Wigan,3,1,H,1,1,D,P Walton,12,11,10,4,5,3,12,8,2,0,0,0 +09/05/2009,West Ham,Liverpool,0,3,A,0,2,A,A Wiley,8,16,3,9,2,4,10,17,3,2,0,0 +10/05/2009,Arsenal,Chelsea,1,4,A,0,2,A,P Dowd,15,16,7,8,6,2,19,12,1,0,0,0 +10/05/2009,Man United,Man City,2,0,H,2,0,H,C Foy,15,11,7,5,8,7,11,12,1,1,0,0 +11/05/2009,Newcastle,Middlesbrough,3,1,H,1,1,D,M Dean,19,11,9,5,11,1,7,15,3,1,0,0 +13/05/2009,Wigan,Man United,1,2,A,1,0,H,R Styles,13,18,5,9,5,4,11,2,0,0,0,0 +16/05/2009,Bolton,Hull,1,1,D,1,0,H,P Walton,16,20,9,12,5,7,11,9,1,2,0,0 +16/05/2009,Everton,West Ham,3,1,H,1,1,D,P Dowd,18,8,9,3,6,3,15,14,1,2,0,1 +16/05/2009,Man United,Arsenal,0,0,D,0,0,D,M Dean,11,14,5,11,2,6,13,14,0,5,0,0 +16/05/2009,Middlesbrough,Aston Villa,1,1,D,1,0,H,M Riley,10,8,6,6,6,8,11,16,0,2,0,0 +16/05/2009,Newcastle,Fulham,0,1,A,0,1,A,H Webb,12,12,6,5,6,2,7,17,0,3,1,0 +16/05/2009,Stoke,Wigan,2,0,H,0,0,D,L Probert,13,8,8,3,6,3,8,11,0,0,0,0 +16/05/2009,Tottenham,Man City,2,1,H,1,0,H,M Halsey,11,9,4,3,4,7,5,7,0,1,0,0 +17/05/2009,Chelsea,Blackburn,2,0,H,1,0,H,R Styles,21,8,12,5,1,4,6,6,1,2,0,0 +17/05/2009,West Brom,Liverpool,0,2,A,0,1,A,M Atkinson,12,16,7,10,11,5,13,4,2,0,0,0 +18/05/2009,Portsmouth,Sunderland,3,1,H,0,0,D,A Wiley,9,20,4,9,5,6,11,6,1,0,0,0 +24/05/2009,Arsenal,Stoke,4,1,H,4,1,H,M Atkinson,24,3,11,1,6,0,9,11,1,0,0,0 +24/05/2009,Aston Villa,Newcastle,1,0,H,1,0,H,C Foy,15,8,5,3,5,3,8,10,0,2,0,1 +24/05/2009,Blackburn,West Brom,0,0,D,0,0,D,M Jones,10,13,5,5,4,9,14,11,0,1,1,0 +24/05/2009,Fulham,Everton,0,2,A,0,1,A,M Riley,10,13,4,10,2,2,15,10,0,0,0,0 +24/05/2009,Hull,Man United,0,1,A,0,1,A,A Wiley,12,12,8,5,8,3,17,11,2,1,0,0 +24/05/2009,Liverpool,Tottenham,3,1,H,1,0,H,P Walton,20,8,9,4,9,2,14,4,0,1,0,0 +24/05/2009,Man City,Bolton,1,0,H,1,0,H,M Clattenburg,20,8,12,4,15,9,8,10,0,0,0,0 +24/05/2009,Sunderland,Chelsea,2,3,A,0,0,D,M Halsey,8,14,7,9,6,7,8,7,1,1,0,0 +24/05/2009,West Ham,Middlesbrough,2,1,H,1,0,H,H Webb,14,17,8,6,4,5,14,10,0,1,0,0 +24/05/2009,Wigan,Portsmouth,1,0,H,1,0,H,P Dowd,19,9,10,3,8,2,8,21,0,4,0,0 +15/08/2009,Aston Villa,Wigan,0,2,A,0,1,A,M Clattenburg,11,14,5,7,4,6,15,14,2,2,0,0 +15/08/2009,Blackburn,Man City,0,2,A,0,1,A,M Dean,17,8,9,5,5,4,12,9,2,1,0,0 +15/08/2009,Bolton,Sunderland,0,1,A,0,1,A,A Marriner,11,20,3,13,4,7,16,10,2,1,0,0 +15/08/2009,Chelsea,Hull,2,1,H,1,1,D,A Wiley,26,7,12,3,12,4,13,15,1,2,0,0 +15/08/2009,Everton,Arsenal,1,6,A,0,3,A,M Halsey,8,15,5,9,4,9,11,13,0,0,0,0 +15/08/2009,Portsmouth,Fulham,0,1,A,0,1,A,M Atkinson,16,9,4,3,6,4,11,18,3,2,0,0 +15/08/2009,Stoke,Burnley,2,0,H,2,0,H,S Bennett,12,9,5,5,3,6,15,10,2,2,0,0 +15/08/2009,Wolves,West Ham,0,2,A,0,1,A,C Foy,19,16,11,13,8,6,9,5,0,0,0,0 +16/08/2009,Man United,Birmingham,1,0,H,1,0,H,L Mason,26,6,17,4,13,2,13,7,1,1,0,0 +16/08/2009,Tottenham,Liverpool,2,1,H,1,0,H,P Dowd,17,6,11,3,6,5,14,16,3,3,0,0 +18/08/2009,Sunderland,Chelsea,1,3,A,1,0,H,S Bennett,4,20,3,9,1,14,14,10,2,2,0,0 +18/08/2009,Wigan,Wolves,0,1,A,0,1,A,M Jones,18,9,5,3,4,4,8,21,0,3,0,0 +19/08/2009,Birmingham,Portsmouth,1,0,H,0,0,D,L Probert,9,9,5,4,4,5,11,20,0,5,0,0 +19/08/2009,Burnley,Man United,1,0,H,1,0,H,A Wiley,8,18,2,9,1,12,8,12,2,1,0,0 +19/08/2009,Hull,Tottenham,1,5,A,1,3,A,C Foy,9,18,7,12,6,5,23,13,3,2,0,0 +19/08/2009,Liverpool,Stoke,4,0,H,2,0,H,P Walton,18,6,13,3,11,5,10,9,0,1,0,0 +22/08/2009,Arsenal,Portsmouth,4,1,H,2,1,H,S Bennett,19,9,16,4,8,4,9,10,1,0,0,0 +22/08/2009,Birmingham,Stoke,0,0,D,0,0,D,C Foy,8,13,6,3,3,3,8,13,0,3,0,0 +22/08/2009,Hull,Bolton,1,0,H,0,0,D,M Jones,12,20,5,10,4,12,19,19,3,2,0,0 +22/08/2009,Man City,Wolves,1,0,H,1,0,H,L Mason,16,10,11,2,5,6,7,11,0,1,0,0 +22/08/2009,Sunderland,Blackburn,2,1,H,1,1,D,A Wiley,8,18,5,5,0,10,12,14,2,1,0,0 +22/08/2009,Wigan,Man United,0,5,A,0,0,D,H Webb,16,16,7,13,3,5,11,8,2,2,0,0 +23/08/2009,Burnley,Everton,1,0,H,1,0,H,P Dowd,8,17,4,11,3,7,11,13,1,0,0,0 +23/08/2009,Fulham,Chelsea,0,2,A,0,1,A,A Marriner,4,12,1,4,4,7,11,10,0,0,0,0 +23/08/2009,West Ham,Tottenham,1,2,A,0,0,D,M Clattenburg,17,17,8,10,4,8,13,11,2,1,0,0 +24/08/2009,Liverpool,Aston Villa,1,3,A,0,2,A,M Atkinson,21,7,12,4,10,4,15,11,3,2,0,0 +29/08/2009,Blackburn,West Ham,0,0,D,0,0,D,P Dowd,19,11,9,5,5,2,13,18,1,3,0,0 +29/08/2009,Bolton,Liverpool,2,3,A,1,1,D,A Wiley,8,26,5,14,5,11,14,7,3,1,1,0 +29/08/2009,Chelsea,Burnley,3,0,H,1,0,H,M Clattenburg,27,3,17,0,10,1,7,5,0,1,0,0 +29/08/2009,Man United,Arsenal,2,1,H,0,1,A,M Dean,10,9,4,3,6,5,21,15,3,6,0,0 +29/08/2009,Stoke,Sunderland,1,0,H,1,0,H,M Jones,13,9,7,6,10,10,9,8,0,2,0,0 +29/08/2009,Tottenham,Birmingham,2,1,H,0,0,D,P Walton,20,11,14,6,7,3,12,10,2,2,0,0 +29/08/2009,Wolves,Hull,1,1,D,0,1,A,S Attwell,24,5,17,4,8,2,11,15,0,2,0,0 +30/08/2009,Aston Villa,Fulham,2,0,H,1,0,H,S Bennett,9,6,4,3,9,4,9,9,1,2,0,0 +30/08/2009,Everton,Wigan,2,1,H,0,0,D,L Probert,26,12,14,7,12,5,11,24,1,5,0,0 +30/08/2009,Portsmouth,Man City,0,1,A,0,1,A,H Webb,11,12,6,8,3,11,12,7,2,1,0,0 +12/09/2009,Blackburn,Wolves,3,1,H,1,0,H,S Bennett,13,9,10,5,10,6,13,16,1,2,0,0 +12/09/2009,Liverpool,Burnley,4,0,H,2,0,H,L Mason,27,6,16,3,7,5,8,10,0,2,0,0 +12/09/2009,Man City,Arsenal,4,2,H,1,0,H,M Clattenburg,10,19,6,9,3,12,13,12,3,2,0,0 +12/09/2009,Portsmouth,Bolton,2,3,A,1,2,A,C Foy,21,14,11,6,7,5,17,17,2,0,0,0 +12/09/2009,Stoke,Chelsea,1,2,A,1,1,D,M Dean,4,18,2,10,4,14,13,11,3,3,0,0 +12/09/2009,Sunderland,Hull,4,1,H,1,1,D,M Atkinson,7,6,4,5,7,8,18,15,2,2,0,0 +12/09/2009,Tottenham,Man United,1,3,A,1,2,A,A Marriner,11,17,7,13,3,9,16,13,3,2,0,1 +12/09/2009,Wigan,West Ham,1,0,H,0,0,D,A Wiley,12,12,6,5,6,7,13,11,2,1,0,0 +13/09/2009,Birmingham,Aston Villa,0,1,A,0,0,D,H Webb,11,12,7,5,7,1,10,20,1,2,0,0 +13/09/2009,Fulham,Everton,2,1,H,0,1,A,P Walton,14,6,8,6,5,3,6,10,1,0,0,0 +19/09/2009,Arsenal,Wigan,4,0,H,1,0,H,M Jones,23,8,11,5,6,4,6,18,2,2,0,0 +19/09/2009,Aston Villa,Portsmouth,2,0,H,2,0,H,S Attwell,7,17,5,12,7,5,17,12,2,0,0,0 +19/09/2009,Bolton,Stoke,1,1,D,0,0,D,M Clattenburg,11,10,5,6,4,6,6,11,2,2,0,0 +19/09/2009,Burnley,Sunderland,3,1,H,1,1,D,C Foy,10,8,3,5,4,4,4,15,0,3,0,0 +19/09/2009,Hull,Birmingham,0,1,A,0,0,D,P Dowd,16,14,11,9,10,8,15,13,0,2,0,0 +19/09/2009,West Ham,Liverpool,2,3,A,2,2,D,A Marriner,8,19,5,15,4,4,13,17,3,3,0,0 +20/09/2009,Chelsea,Tottenham,3,0,H,1,0,H,H Webb,25,9,15,4,11,3,7,9,0,2,0,0 +20/09/2009,Everton,Blackburn,3,0,H,1,0,H,L Mason,18,7,11,4,6,2,15,18,1,2,0,0 +20/09/2009,Man United,Man City,4,3,H,1,1,D,M Atkinson,21,10,11,6,11,1,15,14,2,2,0,0 +20/09/2009,Wolves,Fulham,2,1,H,1,0,H,K Friend,12,3,4,1,3,3,14,11,1,1,0,0 +26/09/2009,Birmingham,Bolton,1,2,A,0,1,A,S Bennett,12,10,6,4,8,4,9,17,2,1,0,0 +26/09/2009,Blackburn,Aston Villa,2,1,H,1,1,D,M Clattenburg,15,10,7,5,6,5,14,11,2,3,1,0 +26/09/2009,Fulham,Arsenal,0,1,A,0,0,D,M Atkinson,18,14,12,4,4,6,14,11,4,0,0,0 +26/09/2009,Liverpool,Hull,6,1,H,2,1,H,P Walton,22,7,14,3,13,2,10,3,1,4,0,0 +26/09/2009,Portsmouth,Everton,0,1,A,0,1,A,A Wiley,16,12,12,8,5,6,14,17,0,3,0,0 +26/09/2009,Stoke,Man United,0,2,A,0,0,D,H Webb,2,13,1,7,2,7,9,8,1,1,0,0 +26/09/2009,Tottenham,Burnley,5,0,H,2,0,H,M Dean,18,10,12,7,2,7,10,14,1,1,0,0 +26/09/2009,Wigan,Chelsea,3,1,H,1,0,H,P Dowd,15,14,9,8,4,7,11,11,1,2,0,1 +27/09/2009,Sunderland,Wolves,5,2,H,1,0,H,L Mason,10,15,8,11,7,8,18,11,1,3,0,0 +28/09/2009,Man City,West Ham,3,1,H,2,1,H,C Foy,20,12,12,8,11,5,18,14,1,1,0,0 +03/10/2009,Bolton,Tottenham,2,2,D,1,1,D,M Jones,12,17,9,9,5,6,15,16,2,1,0,0 +03/10/2009,Burnley,Birmingham,2,1,H,0,0,D,K Friend,15,10,13,6,8,4,17,11,0,3,0,0 +03/10/2009,Hull,Wigan,2,1,H,0,0,D,M Clattenburg,11,13,6,5,4,2,14,8,0,2,0,0 +03/10/2009,Man United,Sunderland,2,2,D,0,1,A,A Wiley,17,4,9,3,14,1,11,15,2,3,0,1 +03/10/2009,Wolves,Portsmouth,0,1,A,0,1,A,H Webb,11,12,5,8,8,6,8,12,1,2,0,0 +04/10/2009,Arsenal,Blackburn,6,2,H,3,2,H,P Walton,24,7,18,7,8,1,9,10,1,1,0,0 +04/10/2009,Chelsea,Liverpool,2,0,H,0,0,D,M Atkinson,12,12,8,3,5,8,14,17,1,1,0,0 +04/10/2009,Everton,Stoke,1,1,D,0,0,D,A Marriner,24,8,17,6,11,4,5,15,1,3,0,0 +04/10/2009,West Ham,Fulham,2,2,D,1,0,H,P Dowd,20,10,10,6,8,4,12,9,3,2,0,1 +05/10/2009,Aston Villa,Man City,1,1,D,1,0,H,M Dean,12,9,6,7,6,6,10,8,1,2,0,0 +17/10/2009,Arsenal,Birmingham,3,1,H,2,1,H,L Probert,20,3,12,2,7,3,8,16,1,2,0,0 +17/10/2009,Aston Villa,Chelsea,2,1,H,1,1,D,K Friend,7,21,5,13,6,9,19,13,2,1,0,0 +17/10/2009,Everton,Wolves,1,1,D,0,0,D,S Attwell,13,12,5,6,6,5,6,15,1,3,0,1 +17/10/2009,Man United,Bolton,2,1,H,2,0,H,M Clattenburg,13,15,7,7,13,1,15,8,0,1,0,0 +17/10/2009,Portsmouth,Tottenham,1,2,A,0,2,A,P Dowd,23,9,13,4,10,9,14,13,3,3,1,1 +17/10/2009,Stoke,West Ham,2,1,H,1,1,D,M Atkinson,11,12,4,11,5,6,16,13,3,3,0,0 +17/10/2009,Sunderland,Liverpool,1,0,H,1,0,H,M Jones,13,15,7,6,1,7,20,17,2,1,0,0 +18/10/2009,Blackburn,Burnley,3,2,H,3,1,H,C Foy,12,9,6,5,3,2,18,14,3,3,0,0 +18/10/2009,Wigan,Man City,1,1,D,1,0,H,A Wiley,13,5,7,4,6,6,15,11,2,1,0,1 +19/10/2009,Fulham,Hull,2,0,H,1,0,H,A Marriner,17,5,11,2,7,4,13,15,1,1,0,0 +24/10/2009,Birmingham,Sunderland,2,1,H,1,0,H,M Atkinson,19,14,13,6,6,8,9,14,3,1,0,0 +24/10/2009,Burnley,Wigan,1,3,A,1,1,D,L Mason,12,13,9,8,7,4,12,11,2,0,0,0 +24/10/2009,Chelsea,Blackburn,5,0,H,1,0,H,A Wiley,26,5,15,2,8,0,8,10,0,1,0,0 +24/10/2009,Hull,Portsmouth,0,0,D,0,0,D,S Attwell,7,8,3,2,6,6,16,23,2,1,0,0 +24/10/2009,Tottenham,Stoke,0,1,A,0,0,D,L Probert,22,8,12,2,7,1,8,18,0,3,0,0 +24/10/2009,Wolves,Aston Villa,1,1,D,0,0,D,P Walton,8,12,4,8,8,3,10,16,1,2,0,0 +25/10/2009,Bolton,Everton,3,2,H,2,1,H,P Dowd,12,17,8,12,4,4,14,15,3,1,0,0 +25/10/2009,Liverpool,Man United,2,0,H,0,0,D,A Marriner,11,7,6,5,5,1,20,11,2,3,1,1 +25/10/2009,Man City,Fulham,2,2,D,0,0,D,K Friend,20,11,7,5,14,2,11,12,0,2,0,0 +25/10/2009,West Ham,Arsenal,2,2,D,0,2,A,C Foy,15,15,9,10,6,8,17,15,3,2,1,0 +31/10/2009,Arsenal,Tottenham,3,0,H,2,0,H,M Clattenburg,18,7,12,5,6,2,10,10,1,1,0,0 +31/10/2009,Bolton,Chelsea,0,4,A,0,1,A,P Walton,15,27,6,17,8,10,9,7,1,1,1,0 +31/10/2009,Burnley,Hull,2,0,H,1,0,H,M Jones,11,8,5,6,8,1,12,13,1,4,0,1 +31/10/2009,Everton,Aston Villa,1,1,D,1,0,H,L Probert,9,13,4,8,3,4,15,16,1,3,1,1 +31/10/2009,Fulham,Liverpool,3,1,H,1,1,D,L Mason,10,6,6,3,2,2,10,13,1,0,0,2 +31/10/2009,Man United,Blackburn,2,0,H,0,0,D,P Dowd,20,2,11,1,7,2,10,21,0,2,0,0 +31/10/2009,Portsmouth,Wigan,4,0,H,2,0,H,A Wiley,21,8,11,3,7,4,11,10,0,1,0,0 +31/10/2009,Stoke,Wolves,2,2,D,2,0,H,C Foy,10,11,5,7,4,3,13,15,0,3,0,0 +31/10/2009,Sunderland,West Ham,2,2,D,1,2,A,A Marriner,17,15,7,9,7,3,12,16,3,3,1,1 +01/11/2009,Birmingham,Man City,0,0,D,0,0,D,M Dean,7,14,4,6,4,8,9,8,3,1,1,0 +04/11/2009,West Ham,Aston Villa,2,1,H,1,0,H,S Bennett,14,13,7,10,6,14,11,12,2,4,0,1 +07/11/2009,Aston Villa,Bolton,5,1,H,2,1,H,M Clattenburg,19,13,13,9,8,1,17,16,1,4,0,0 +07/11/2009,Blackburn,Portsmouth,3,1,H,0,1,A,A Marriner,10,9,6,3,8,5,12,18,3,3,0,0 +07/11/2009,Man City,Burnley,3,3,D,1,2,A,S Attwell,13,6,8,4,8,3,10,11,1,3,0,0 +07/11/2009,Tottenham,Sunderland,2,0,H,1,0,H,K Friend,10,14,8,6,4,4,10,19,1,2,0,0 +07/11/2009,Wolves,Arsenal,1,4,A,0,3,A,St Bennett,8,11,6,6,4,4,9,15,2,3,0,0 +08/11/2009,Chelsea,Man United,1,0,H,0,0,D,Mn Atkinson,12,21,9,10,0,7,13,15,3,3,0,0 +08/11/2009,Hull,Stoke,2,1,H,0,1,A,M Dean,16,7,8,4,4,2,12,14,1,4,0,1 +08/11/2009,West Ham,Everton,1,2,A,0,1,A,A Wiley,16,9,7,6,5,4,12,18,0,5,0,0 +08/11/2009,Wigan,Fulham,1,1,D,1,1,D,P Dowd,19,7,12,3,10,6,15,16,2,1,0,0 +09/11/2009,Liverpool,Birmingham,2,2,D,1,2,A,P Walton,27,5,14,2,11,1,7,9,1,2,0,0 +21/11/2009,Birmingham,Fulham,1,0,H,1,0,H,C Foy,3,12,2,5,0,7,7,9,1,0,0,0 +21/11/2009,Burnley,Aston Villa,1,1,D,1,0,H,H Webb,11,10,6,6,6,8,6,12,0,3,0,0 +21/11/2009,Chelsea,Wolves,4,0,H,3,0,H,L Mason,23,4,14,3,5,6,4,14,0,2,0,0 +21/11/2009,Hull,West Ham,3,3,D,3,2,H,M Clattenburg,12,16,8,10,5,3,9,17,0,4,1,0 +21/11/2009,Liverpool,Man City,2,2,D,0,0,D,P Dowd,10,9,6,6,7,3,9,13,2,0,0,0 +21/11/2009,Man United,Everton,3,0,H,1,0,H,S Bennett,18,11,11,5,13,4,4,6,2,2,0,0 +21/11/2009,Sunderland,Arsenal,1,0,H,0,0,D,A Wiley,5,9,1,3,2,6,10,10,2,1,0,0 +22/11/2009,Bolton,Blackburn,0,2,A,0,1,A,M Dean,15,15,8,11,7,7,13,12,3,1,0,0 +22/11/2009,Stoke,Portsmouth,1,0,H,0,0,D,K Friend,9,14,3,7,4,4,15,13,1,2,0,0 +22/11/2009,Tottenham,Wigan,9,1,H,1,0,H,P Walton,28,11,16,5,10,5,9,11,1,0,0,0 +25/11/2009,Fulham,Blackburn,3,0,H,1,0,H,S Attwell,8,7,4,3,3,3,7,13,0,0,0,0 +25/11/2009,Hull,Everton,3,2,H,3,0,H,M Atkinson,8,5,7,3,3,4,18,23,1,4,0,0 +28/11/2009,Aston Villa,Tottenham,1,1,D,1,0,H,P Dowd,12,23,4,17,7,9,10,8,0,1,0,0 +28/11/2009,Blackburn,Stoke,0,0,D,0,0,D,H Webb,11,7,4,4,9,2,12,11,1,2,0,0 +28/11/2009,Fulham,Bolton,1,1,D,0,1,A,S Bennett,12,4,7,2,8,5,7,21,1,6,0,0 +28/11/2009,Man City,Hull,1,1,D,1,0,H,L Probert,17,8,7,3,8,2,14,12,1,3,0,0 +28/11/2009,Portsmouth,Man United,1,4,A,1,1,D,M Dean,20,12,12,8,4,8,16,10,4,3,0,0 +28/11/2009,West Ham,Burnley,5,3,H,3,0,H,C Foy,9,16,5,10,1,7,10,15,2,1,0,1 +28/11/2009,Wigan,Sunderland,1,0,H,0,0,D,Mn Atkinson,16,9,10,4,5,8,12,14,0,3,0,0 +29/11/2009,Arsenal,Chelsea,0,3,A,0,2,A,A Marriner,5,7,3,5,4,3,17,12,2,2,0,0 +29/11/2009,Everton,Liverpool,0,2,A,0,1,A,A Wiley,12,9,8,7,6,4,15,12,1,0,0,0 +29/11/2009,Wolves,Birmingham,0,1,A,0,1,A,M Clattenburg,8,13,2,7,6,5,14,8,2,1,0,0 +05/12/2009,Arsenal,Stoke,2,0,H,1,0,H,M Clattenburg,12,5,8,4,10,2,3,7,0,0,0,0 +05/12/2009,Aston Villa,Hull,3,0,H,2,0,H,S Attwell,13,5,6,1,9,3,15,15,4,3,0,0 +05/12/2009,Blackburn,Liverpool,0,0,D,0,0,D,M Atkinson,9,15,3,11,1,8,15,9,1,0,0,0 +05/12/2009,Man City,Chelsea,2,1,H,1,1,D,H Webb,13,10,8,6,9,6,12,17,1,6,0,0 +05/12/2009,Portsmouth,Burnley,2,0,H,0,0,D,P Dowd,9,14,4,8,5,5,16,14,4,2,0,0 +05/12/2009,West Ham,Man United,0,4,A,0,1,A,P Walton,11,18,7,11,2,8,8,16,0,1,0,0 +05/12/2009,Wigan,Birmingham,2,3,A,1,0,H,L Probert,18,10,7,9,2,2,14,8,2,0,0,0 +05/12/2009,Wolves,Bolton,2,1,H,1,0,H,C Foy,12,17,11,10,11,10,9,16,2,1,0,0 +06/12/2009,Everton,Tottenham,2,2,D,0,0,D,A Marriner,15,19,6,11,1,6,11,18,4,4,0,0 +06/12/2009,Fulham,Sunderland,1,0,H,1,0,H,M Dean,16,8,5,5,4,8,9,12,1,2,0,0 +12/12/2009,Birmingham,West Ham,1,0,H,0,0,D,L Mason,16,14,9,7,11,7,12,13,3,4,0,1 +12/12/2009,Bolton,Man City,3,3,D,2,2,D,M Clattenburg,12,14,8,6,5,9,11,9,3,3,0,1 +12/12/2009,Burnley,Fulham,1,1,D,0,0,D,M Jones,10,12,8,6,7,4,12,8,0,2,0,0 +12/12/2009,Chelsea,Everton,3,3,D,2,2,D,P Dowd,25,10,13,7,7,1,13,17,0,1,0,0 +12/12/2009,Hull,Blackburn,0,0,D,0,0,D,C Foy,9,14,7,9,3,4,14,19,1,2,0,0 +12/12/2009,Man United,Aston Villa,0,1,A,0,1,A,M Atkinson,16,8,9,5,9,4,4,14,1,1,0,0 +12/12/2009,Stoke,Wigan,2,2,D,1,1,D,M Dean,14,11,5,8,5,3,19,8,5,1,0,0 +12/12/2009,Sunderland,Portsmouth,1,1,D,1,0,H,S Bennett,16,9,9,6,9,6,12,14,4,3,0,1 +12/12/2009,Tottenham,Wolves,0,1,A,0,1,A,S Attwell,18,3,10,3,1,5,8,13,0,5,0,0 +13/12/2009,Liverpool,Arsenal,1,2,A,1,0,H,H Webb,6,7,3,5,3,4,19,16,3,3,0,0 +15/12/2009,Birmingham,Blackburn,2,1,H,1,0,H,M Jones,13,8,6,4,6,7,8,16,2,2,0,0 +15/12/2009,Bolton,West Ham,3,1,H,0,0,D,A Marriner,23,9,12,6,8,4,10,7,4,1,0,0 +15/12/2009,Man United,Wolves,3,0,H,2,0,H,S Bennett,12,4,9,1,8,3,11,11,1,1,0,0 +15/12/2009,Sunderland,Aston Villa,0,2,A,0,1,A,K Friend,15,11,5,4,7,1,16,14,2,2,1,0 +16/12/2009,Burnley,Arsenal,1,1,D,1,1,D,M Dean,8,17,3,9,3,6,11,11,2,0,0,0 +16/12/2009,Chelsea,Portsmouth,2,1,H,1,0,H,M Clattenburg,21,7,8,3,11,2,11,11,0,0,0,0 +16/12/2009,Liverpool,Wigan,2,1,H,1,0,H,P Dowd,12,8,7,5,13,0,12,11,1,1,0,0 +16/12/2009,Tottenham,Man City,3,0,H,1,0,H,A Wiley,19,9,7,5,5,6,10,8,2,3,0,0 +19/12/2009,Arsenal,Hull,3,0,H,1,0,H,S Bennett,18,6,9,2,8,0,9,14,1,5,0,0 +19/12/2009,Aston Villa,Stoke,1,0,H,0,0,D,L Probert,17,13,8,5,3,4,9,12,0,1,0,0 +19/12/2009,Blackburn,Tottenham,0,2,A,0,1,A,P Walton,13,7,9,2,5,0,7,6,0,1,0,0 +19/12/2009,Fulham,Man United,3,0,H,1,0,H,H Webb,10,16,7,4,5,2,10,9,0,1,0,0 +19/12/2009,Man City,Sunderland,4,3,H,3,2,H,A Marriner,11,7,5,5,6,4,13,8,2,2,0,1 +19/12/2009,Portsmouth,Liverpool,2,0,H,1,0,H,L Mason,14,7,6,3,8,10,7,12,3,3,0,1 +20/12/2009,Everton,Birmingham,1,1,D,1,1,D,S Attwell,19,2,7,1,6,4,8,9,0,3,0,0 +20/12/2009,West Ham,Chelsea,1,1,D,1,0,H,M Dean,6,20,5,6,5,5,17,16,4,3,0,0 +20/12/2009,Wolves,Burnley,2,0,H,1,0,H,M Atkinson,12,10,12,6,5,5,17,9,2,1,0,0 +26/12/2009,Birmingham,Chelsea,0,0,D,0,0,D,P Walton,11,27,4,12,7,11,6,17,1,2,0,1 +26/12/2009,Burnley,Bolton,1,1,D,0,1,A,C Foy,15,11,7,5,8,2,11,12,0,0,0,0 +26/12/2009,Fulham,Tottenham,0,0,D,0,0,D,S Bennett,15,6,6,1,4,2,19,12,1,0,0,0 +26/12/2009,Liverpool,Wolves,2,0,H,0,0,D,A Marriner,14,6,10,2,7,1,9,10,0,1,0,1 +26/12/2009,Man City,Stoke,2,0,H,2,0,H,L Mason,12,7,6,3,7,6,13,10,0,2,0,0 +26/12/2009,Sunderland,Everton,1,1,D,1,0,H,M Atkinson,10,12,6,4,4,12,17,16,1,3,0,0 +26/12/2009,West Ham,Portsmouth,2,0,H,1,0,H,L Probert,13,11,5,7,6,4,10,19,0,3,0,0 +26/12/2009,Wigan,Blackburn,1,1,D,0,1,A,M Clattenburg,14,8,8,3,6,2,10,18,1,4,0,0 +27/12/2009,Arsenal,Aston Villa,3,0,H,0,0,D,P Dowd,19,7,12,3,10,4,14,18,1,3,0,0 +27/12/2009,Hull,Man United,1,3,A,0,1,A,A Wiley,12,19,6,11,5,6,13,17,0,2,0,0 +28/12/2009,Blackburn,Sunderland,2,2,D,0,0,D,M Dean,21,13,11,8,4,4,6,20,1,4,0,0 +28/12/2009,Chelsea,Fulham,2,1,H,0,1,A,A Marriner,18,3,11,3,10,6,9,13,1,1,0,0 +28/12/2009,Everton,Burnley,2,0,H,0,0,D,H Webb,24,12,16,9,10,6,7,18,1,5,0,1 +28/12/2009,Stoke,Birmingham,0,1,A,0,0,D,M Atkinson,12,9,6,5,10,7,15,13,2,3,0,0 +28/12/2009,Tottenham,West Ham,2,0,H,1,0,H,C Foy,21,3,9,2,9,3,9,14,2,3,0,0 +28/12/2009,Wolves,Man City,0,3,A,0,1,A,M Jones,11,12,6,5,8,3,16,11,3,2,0,0 +29/12/2009,Aston Villa,Liverpool,0,1,A,0,0,D,L Probert,11,12,7,9,8,10,13,11,1,1,0,0 +29/12/2009,Bolton,Hull,2,2,D,1,0,H,P Dowd,16,12,6,7,8,8,14,18,1,2,0,0 +30/12/2009,Man United,Wigan,5,0,H,3,0,H,L Mason,23,11,16,6,7,3,6,11,0,1,0,0 +30/12/2009,Portsmouth,Arsenal,1,4,A,0,2,A,A Wiley,10,14,6,10,2,10,14,3,2,0,0,0 +05/01/2010,Stoke,Fulham,3,2,H,3,0,H,M Clattenburg,11,14,9,7,8,10,13,7,1,0,0,0 +09/01/2010,Arsenal,Everton,2,2,D,1,1,D,P Walton,6,9,5,5,6,8,10,11,1,1,0,0 +09/01/2010,Birmingham,Man United,1,1,D,1,0,H,M Clattenburg,6,14,5,10,2,12,10,13,2,1,0,1 +11/01/2010,Man City,Blackburn,4,1,H,2,0,H,C Foy,12,8,6,3,4,3,6,10,1,1,0,0 +16/01/2010,Chelsea,Sunderland,7,2,H,4,0,H,C Foy,23,7,13,3,15,1,4,10,0,1,0,0 +16/01/2010,Everton,Man City,2,0,H,2,0,H,A Marriner,13,8,6,4,11,3,14,14,0,0,0,0 +16/01/2010,Man United,Burnley,3,0,H,0,0,D,L Probert,24,11,11,5,7,1,12,7,0,0,0,0 +16/01/2010,Stoke,Liverpool,1,1,D,0,0,D,L Mason,6,4,5,3,10,0,14,10,1,2,0,0 +16/01/2010,Tottenham,Hull,0,0,D,0,0,D,M Atkinson,25,8,18,2,14,1,14,11,4,2,0,0 +16/01/2010,Wolves,Wigan,0,2,A,0,0,D,H Webb,9,20,4,14,4,3,14,16,4,3,1,1 +17/01/2010,Aston Villa,West Ham,0,0,D,0,0,D,M Jones,17,8,10,3,16,4,7,12,1,3,0,0 +17/01/2010,Blackburn,Fulham,2,0,H,1,0,H,K Friend,11,14,7,9,3,6,14,9,1,3,0,0 +17/01/2010,Bolton,Arsenal,0,2,A,0,1,A,P Dowd,12,14,6,9,2,4,13,5,1,2,0,0 +20/01/2010,Arsenal,Bolton,4,2,H,1,2,A,A Wiley,20,7,14,4,9,3,16,17,1,2,0,0 +20/01/2010,Liverpool,Tottenham,2,0,H,1,0,H,H Webb,13,7,5,5,2,3,11,17,2,3,0,0 +23/01/2010,Man United,Hull,4,0,H,1,0,H,S Bennett,25,8,15,4,12,8,6,10,1,1,0,0 +26/01/2010,Bolton,Burnley,1,0,H,1,0,H,M Atkinson,14,10,6,5,4,7,12,11,3,2,0,0 +26/01/2010,Portsmouth,West Ham,1,1,D,0,0,D,A Marriner,14,12,8,4,6,4,16,9,3,1,0,0 +26/01/2010,Tottenham,Fulham,2,0,H,1,0,H,M Dean,13,9,7,6,11,3,11,16,1,1,0,0 +26/01/2010,Wolves,Liverpool,0,0,D,0,0,D,P Walton,6,6,4,3,0,7,10,11,0,1,0,0 +27/01/2010,Aston Villa,Arsenal,0,0,D,0,0,D,L Probert,8,12,2,8,7,7,16,15,2,2,0,0 +27/01/2010,Blackburn,Wigan,2,1,H,1,0,H,L Mason,13,13,7,8,5,6,20,13,3,1,0,0 +27/01/2010,Chelsea,Birmingham,3,0,H,2,0,H,S Bennett,25,5,17,3,8,3,11,8,0,0,0,0 +27/01/2010,Everton,Sunderland,2,0,H,2,0,H,P Dowd,11,5,7,3,3,8,10,19,0,1,0,0 +30/01/2010,Birmingham,Tottenham,1,1,D,0,0,D,S Attwell,12,21,6,12,5,8,12,8,4,2,0,0 +30/01/2010,Burnley,Chelsea,1,2,A,0,1,A,P Dowd,5,16,3,9,2,9,12,12,1,1,0,0 +30/01/2010,Fulham,Aston Villa,0,2,A,0,2,A,L Mason,15,9,8,6,9,3,10,15,0,2,0,0 +30/01/2010,Hull,Wolves,2,2,D,1,0,H,M Dean,8,13,4,4,4,7,15,10,2,0,0,0 +30/01/2010,Liverpool,Bolton,2,0,H,1,0,H,S Bennett,13,4,6,1,11,3,12,10,1,3,0,0 +30/01/2010,West Ham,Blackburn,0,0,D,0,0,D,P Walton,7,8,6,4,10,7,17,13,0,1,0,0 +30/01/2010,Wigan,Everton,0,1,A,0,0,D,L Probert,10,10,4,7,4,7,19,10,0,2,0,0 +31/01/2010,Arsenal,Man United,1,3,A,0,2,A,C Foy,18,11,7,5,8,5,9,12,1,0,0,0 +31/01/2010,Man City,Portsmouth,2,0,H,2,0,H,M Atkinson,13,10,6,4,10,7,9,14,1,3,0,0 +01/02/2010,Sunderland,Stoke,0,0,D,0,0,D,H Webb,6,4,5,3,7,3,7,16,2,3,0,0 +02/02/2010,Hull,Chelsea,1,1,D,1,1,D,M Clattenburg,7,21,3,11,6,8,9,12,1,3,0,0 +03/02/2010,Fulham,Portsmouth,1,0,H,0,0,D,A Taylor,14,12,9,8,5,5,12,12,0,1,0,0 +06/02/2010,Bolton,Fulham,0,0,D,0,0,D,M Clattenburg,16,7,11,4,2,5,8,12,0,0,0,0 +06/02/2010,Burnley,West Ham,2,1,H,1,0,H,H Webb,8,20,4,11,3,5,15,13,2,1,0,0 +06/02/2010,Hull,Man City,2,1,H,1,0,H,P Dowd,8,17,4,11,4,8,14,14,2,4,0,0 +06/02/2010,Liverpool,Everton,1,0,H,0,0,D,M Atkinson,9,6,4,3,8,8,11,23,3,3,1,1 +06/02/2010,Man United,Portsmouth,5,0,H,2,0,H,L Mason,21,7,8,5,12,1,9,11,1,4,0,0 +06/02/2010,Stoke,Blackburn,3,0,H,2,0,H,S Bennett,15,9,11,4,12,7,13,20,2,3,0,1 +06/02/2010,Sunderland,Wigan,1,1,D,0,1,A,S Attwell,11,14,8,6,2,3,12,14,4,5,0,0 +06/02/2010,Tottenham,Aston Villa,0,0,D,0,0,D,C Foy,28,10,15,5,12,4,10,13,0,0,0,0 +07/02/2010,Birmingham,Wolves,2,1,H,0,1,A,L Probert,15,12,10,5,11,4,12,18,5,3,0,0 +07/02/2010,Chelsea,Arsenal,2,0,H,2,0,H,M Dean,9,14,3,6,4,8,11,14,2,2,0,0 +09/02/2010,Fulham,Burnley,3,0,H,2,0,H,C Foy,16,10,12,3,6,2,9,9,1,0,0,0 +09/02/2010,Man City,Bolton,2,0,H,1,0,H,M Jones,8,11,4,5,6,3,8,18,0,2,0,0 +09/02/2010,Portsmouth,Sunderland,1,1,D,0,1,A,K Friend,12,7,6,2,8,4,10,15,3,1,1,2 +09/02/2010,Wigan,Stoke,1,1,D,1,0,H,A Marriner,16,10,11,6,9,5,12,7,2,3,0,0 +10/02/2010,Arsenal,Liverpool,1,0,H,0,0,D,H Webb,11,9,6,7,3,6,19,13,3,2,0,0 +10/02/2010,Aston Villa,Man United,1,1,D,1,1,D,P Walton,10,12,6,6,1,7,9,10,0,1,0,1 +10/02/2010,Blackburn,Hull,1,0,H,1,0,H,L Probert,20,7,10,4,6,6,14,13,1,3,0,1 +10/02/2010,Everton,Chelsea,2,1,H,1,1,D,A Wiley,7,14,5,5,5,6,13,15,1,2,0,0 +10/02/2010,West Ham,Birmingham,2,0,H,1,0,H,M Dean,13,9,10,4,3,6,12,9,1,2,0,0 +10/02/2010,Wolves,Tottenham,1,0,H,1,0,H,M Clattenburg,8,10,4,4,5,7,15,10,2,1,0,0 +16/02/2010,Stoke,Man City,1,1,D,0,0,D,A Wiley,8,9,5,3,3,3,21,14,3,3,1,0 +17/02/2010,Wigan,Bolton,0,0,D,0,0,D,H Webb,10,8,1,0,4,9,6,9,0,1,0,0 +20/02/2010,Arsenal,Sunderland,2,0,H,1,0,H,S Bennett,16,6,10,4,7,1,8,10,1,5,0,0 +20/02/2010,Everton,Man United,3,1,H,1,1,D,H Webb,11,11,7,5,4,12,12,10,5,1,0,0 +20/02/2010,Portsmouth,Stoke,1,2,A,1,0,H,M Dean,15,14,12,7,7,7,10,10,2,1,0,1 +20/02/2010,West Ham,Hull,3,0,H,1,0,H,M Atkinson,20,7,12,3,9,2,13,12,1,1,0,1 +20/02/2010,Wolves,Chelsea,0,2,A,0,1,A,K Friend,11,7,7,2,1,5,12,14,0,1,0,0 +21/02/2010,Aston Villa,Burnley,5,2,H,1,1,D,S Attwell,15,13,8,10,9,5,5,12,0,3,0,0 +21/02/2010,Blackburn,Bolton,3,0,H,1,0,H,C Foy,12,10,7,7,8,3,15,12,0,1,0,0 +21/02/2010,Fulham,Birmingham,2,1,H,0,1,A,P Dowd,11,9,6,6,3,1,9,16,1,1,0,0 +21/02/2010,Man City,Liverpool,0,0,D,0,0,D,P Walton,4,6,1,2,8,5,7,15,1,6,0,0 +21/02/2010,Wigan,Tottenham,0,3,A,0,1,A,A Wiley,8,13,7,12,3,2,17,13,3,4,0,0 +23/02/2010,Man United,West Ham,3,0,H,1,0,H,A Wiley,12,10,9,4,6,7,10,12,0,1,0,0 +27/02/2010,Birmingham,Wigan,1,0,H,1,0,H,A Taylor,10,11,5,2,2,7,16,18,1,3,0,0 +27/02/2010,Bolton,Wolves,1,0,H,1,0,H,A Marriner,16,6,9,4,7,11,10,6,0,2,0,0 +27/02/2010,Burnley,Portsmouth,1,2,A,1,1,D,M Clattenburg,14,13,8,5,4,3,12,13,1,2,0,1 +27/02/2010,Chelsea,Man City,2,4,A,1,1,D,M Dean,23,8,13,6,3,2,21,12,3,1,2,0 +27/02/2010,Stoke,Arsenal,1,3,A,1,1,D,P Walton,4,17,3,11,3,6,12,7,0,1,1,0 +28/02/2010,Liverpool,Blackburn,2,1,H,2,1,H,A Wiley,12,12,6,7,4,5,10,22,1,5,0,0 +28/02/2010,Sunderland,Fulham,0,0,D,0,0,D,M Atkinson,12,3,5,1,2,2,15,16,3,3,0,0 +28/02/2010,Tottenham,Everton,2,1,H,2,0,H,S Bennett,13,14,9,10,6,9,9,18,1,4,0,0 +06/03/2010,Arsenal,Burnley,3,1,H,1,0,H,C Foy,20,8,11,5,8,2,5,12,0,4,0,0 +06/03/2010,West Ham,Bolton,1,2,A,0,2,A,L Probert,18,15,8,7,6,3,13,21,2,5,0,1 +06/03/2010,Wolves,Man United,0,1,A,0,0,D,P Walton,12,15,6,6,2,4,9,8,2,1,0,0 +07/03/2010,Everton,Hull,5,1,H,2,1,H,L Mason,18,7,10,4,4,1,12,21,0,1,0,0 +08/03/2010,Wigan,Liverpool,1,0,H,1,0,H,A Marriner,12,11,4,3,4,7,15,14,1,5,0,0 +09/03/2010,Portsmouth,Birmingham,1,2,A,0,2,A,M Jones,11,10,8,6,5,6,12,13,1,2,0,0 +09/03/2010,Sunderland,Bolton,4,0,H,1,0,H,S Bennett,12,7,7,2,2,7,14,10,2,2,0,1 +10/03/2010,Burnley,Stoke,1,1,D,0,1,A,H Webb,10,8,3,3,4,4,12,10,2,0,0,0 +13/03/2010,Birmingham,Everton,2,2,D,1,2,A,L Probert,7,13,4,11,5,4,10,11,1,1,0,0 +13/03/2010,Bolton,Wigan,4,0,H,1,0,H,M Dean,8,13,6,7,3,6,20,13,1,2,0,0 +13/03/2010,Burnley,Wolves,1,2,A,0,1,A,S Bennett,22,7,11,3,11,6,10,21,1,4,0,0 +13/03/2010,Chelsea,West Ham,4,1,H,1,1,D,M Clattenburg,22,4,15,2,11,1,13,10,0,1,0,0 +13/03/2010,Hull,Arsenal,1,2,A,1,1,D,A Marriner,7,18,2,9,2,8,16,13,2,2,1,0 +13/03/2010,Stoke,Aston Villa,0,0,D,0,0,D,K Friend,13,10,6,5,8,6,6,6,0,4,0,0 +13/03/2010,Tottenham,Blackburn,3,1,H,1,0,H,H Webb,14,10,7,7,5,6,12,7,0,0,0,0 +14/03/2010,Man United,Fulham,3,0,H,0,0,D,M Jones,33,8,19,5,11,1,7,11,1,1,0,0 +14/03/2010,Sunderland,Man City,1,1,D,1,0,H,C Foy,12,13,7,9,3,7,12,10,2,4,0,0 +15/03/2010,Liverpool,Portsmouth,4,1,H,3,0,H,S Attwell,23,7,12,3,8,4,10,15,0,2,0,0 +16/03/2010,Wigan,Aston Villa,1,2,A,1,1,D,S Bennett,10,18,6,10,7,8,10,14,2,1,0,0 +20/03/2010,Arsenal,West Ham,2,0,H,1,0,H,M Atkinson,9,8,5,4,4,2,7,24,1,4,1,0 +20/03/2010,Aston Villa,Wolves,2,2,D,1,2,A,M Clattenburg,11,7,3,4,7,5,10,12,1,2,0,0 +20/03/2010,Everton,Bolton,2,0,H,0,0,D,A Wiley,18,6,11,4,6,3,12,17,1,1,0,1 +20/03/2010,Portsmouth,Hull,3,2,H,1,1,D,P Dowd,18,9,10,5,11,1,7,8,1,0,0,0 +20/03/2010,Stoke,Tottenham,1,2,A,0,0,D,M Dean,8,16,4,9,6,7,7,9,2,2,1,0 +20/03/2010,Sunderland,Birmingham,3,1,H,2,0,H,P Walton,11,21,9,14,7,10,8,8,0,1,0,0 +20/03/2010,Wigan,Burnley,1,0,H,0,0,D,M Jones,21,13,11,8,5,3,10,15,1,3,0,0 +21/03/2010,Blackburn,Chelsea,1,1,D,0,1,A,S Bennett,8,14,4,8,3,11,10,9,0,1,0,0 +21/03/2010,Fulham,Man City,1,2,A,0,2,A,L Probert,10,14,4,9,4,2,8,9,0,0,0,0 +21/03/2010,Man United,Liverpool,2,1,H,1,1,D,H Webb,10,4,5,2,1,2,10,15,2,3,0,0 +23/03/2010,West Ham,Wolves,1,3,A,0,1,A,P Dowd,17,5,10,4,8,5,9,24,0,2,0,0 +24/03/2010,Aston Villa,Sunderland,1,1,D,1,1,D,M Dean,11,10,4,5,6,7,5,7,0,0,0,0 +24/03/2010,Blackburn,Birmingham,2,1,H,1,0,H,M Clattenburg,9,8,7,5,5,6,18,10,2,1,0,0 +24/03/2010,Man City,Everton,0,2,A,0,1,A,P Walton,12,10,5,7,7,4,11,10,3,2,0,0 +24/03/2010,Portsmouth,Chelsea,0,5,A,0,1,A,L Mason,10,20,6,11,1,6,11,11,3,2,0,0 +27/03/2010,Birmingham,Arsenal,1,1,D,0,0,D,H Webb,7,13,3,8,3,6,13,13,3,2,0,0 +27/03/2010,Bolton,Man United,0,4,A,0,1,A,M Atkinson,14,13,10,8,11,6,14,15,0,0,0,0 +27/03/2010,Chelsea,Aston Villa,7,1,H,2,1,H,P Walton,13,7,11,5,8,6,12,5,3,2,0,0 +27/03/2010,Hull,Fulham,2,0,H,1,0,H,C Foy,7,12,4,9,6,8,11,15,2,4,0,0 +27/03/2010,Tottenham,Portsmouth,2,0,H,2,0,H,K Friend,16,8,8,3,11,2,5,9,0,1,0,0 +27/03/2010,West Ham,Stoke,0,1,A,0,0,D,A Marriner,17,12,7,7,6,7,14,17,0,2,0,0 +27/03/2010,Wolves,Everton,0,0,D,0,0,D,M Jones,9,22,2,13,3,7,14,12,1,1,0,0 +28/03/2010,Burnley,Blackburn,0,1,A,0,1,A,M Dean,7,12,3,5,7,2,15,12,3,2,0,0 +28/03/2010,Liverpool,Sunderland,3,0,H,2,0,H,P Dowd,25,7,16,2,9,2,11,9,0,1,0,0 +29/03/2010,Man City,Wigan,3,0,H,0,0,D,S Attwell,10,9,8,3,3,4,12,13,3,2,0,1 +03/04/2010,Arsenal,Wolves,1,0,H,0,0,D,A Marriner,23,3,13,2,10,4,10,13,0,2,0,1 +03/04/2010,Bolton,Aston Villa,0,1,A,0,1,A,M Jones,14,13,10,9,6,8,13,14,2,0,0,0 +03/04/2010,Burnley,Man City,1,6,A,0,5,A,A Wiley,14,20,7,14,6,7,5,8,0,0,0,0 +03/04/2010,Man United,Chelsea,1,2,A,0,1,A,M Dean,9,11,3,5,3,3,14,11,3,1,0,0 +03/04/2010,Portsmouth,Blackburn,0,0,D,0,0,D,S Bennett,6,16,3,8,1,9,11,10,2,1,1,0 +03/04/2010,Stoke,Hull,2,0,H,1,0,H,L Probert,9,5,3,3,3,2,12,12,3,2,0,0 +03/04/2010,Sunderland,Tottenham,3,1,H,2,0,H,L Mason,11,12,10,9,6,6,16,15,2,0,0,0 +04/04/2010,Birmingham,Liverpool,1,1,D,0,0,D,M Atkinson,8,16,3,8,5,4,14,9,2,1,0,0 +04/04/2010,Everton,West Ham,2,2,D,1,0,H,H Webb,10,10,5,5,2,8,7,17,2,3,0,0 +04/04/2010,Fulham,Wigan,2,1,H,0,1,A,M Clattenburg,7,12,5,7,6,3,11,10,1,2,0,0 +10/04/2010,Hull,Burnley,1,4,A,1,1,D,M Atkinson,11,9,4,7,6,1,19,14,5,1,0,0 +10/04/2010,West Ham,Sunderland,1,0,H,0,0,D,M Jones,10,9,6,7,2,7,12,20,1,3,0,0 +11/04/2010,Blackburn,Man United,0,0,D,0,0,D,P Walton,8,13,7,6,1,7,14,12,1,2,0,0 +11/04/2010,Liverpool,Fulham,0,0,D,0,0,D,A Marriner,18,2,9,2,10,1,6,16,1,3,0,0 +11/04/2010,Man City,Birmingham,5,1,H,3,1,H,P Dowd,17,5,8,3,8,4,12,9,0,3,0,0 +11/04/2010,Wolves,Stoke,0,0,D,0,0,D,C Foy,10,9,8,1,7,6,7,4,0,0,0,0 +13/04/2010,Chelsea,Bolton,1,0,H,1,0,H,L Probert,18,7,8,2,5,2,13,22,1,4,0,0 +14/04/2010,Aston Villa,Everton,2,2,D,0,1,A,M Atkinson,10,13,7,10,7,6,11,9,2,2,0,0 +14/04/2010,Tottenham,Arsenal,2,1,H,1,0,H,M Clattenburg,13,19,10,11,4,6,13,10,3,1,0,0 +14/04/2010,Wigan,Portsmouth,0,0,D,0,0,D,M Dean,24,6,14,2,10,3,16,9,2,1,0,0 +17/04/2010,Birmingham,Hull,0,0,D,0,0,D,M Clattenburg,10,11,7,4,8,1,11,12,0,0,0,0 +17/04/2010,Blackburn,Everton,2,3,A,0,1,A,A Marriner,15,11,10,4,4,2,15,10,3,2,0,0 +17/04/2010,Fulham,Wolves,0,0,D,0,0,D,M Dean,11,5,4,1,5,9,9,11,1,0,0,0 +17/04/2010,Man City,Man United,0,1,A,0,0,D,M Atkinson,8,13,5,3,7,5,8,12,2,1,0,0 +17/04/2010,Stoke,Bolton,1,2,A,1,0,H,S Attwell,11,13,5,8,5,1,10,5,1,2,0,0 +17/04/2010,Sunderland,Burnley,2,1,H,2,0,H,H Webb,16,9,11,4,3,3,14,17,3,2,0,0 +17/04/2010,Tottenham,Chelsea,2,1,H,2,0,H,P Dowd,20,9,10,6,13,4,7,14,2,4,0,1 +18/04/2010,Portsmouth,Aston Villa,1,2,A,1,1,D,L Probert,16,12,12,9,7,6,5,10,0,0,0,0 +18/04/2010,Wigan,Arsenal,3,2,H,0,1,A,L Mason,16,11,11,8,8,3,15,13,2,1,0,0 +19/04/2010,Liverpool,West Ham,3,0,H,2,0,H,P Walton,17,6,8,2,9,2,8,12,0,2,0,0 +21/04/2010,Hull,Aston Villa,0,2,A,0,1,A,M Dean,14,11,10,5,11,9,8,7,2,2,0,0 +24/04/2010,Arsenal,Man City,0,0,D,0,0,D,M Dean,9,3,4,2,5,1,13,14,4,2,0,0 +24/04/2010,Bolton,Portsmouth,2,2,D,2,0,H,H Webb,14,13,10,6,12,5,16,13,1,1,0,0 +24/04/2010,Hull,Sunderland,0,1,A,0,1,A,L Probert,14,10,8,5,3,6,12,17,1,3,1,1 +24/04/2010,Man United,Tottenham,3,1,H,0,0,D,A Marriner,15,9,8,3,4,4,13,10,1,0,0,0 +24/04/2010,West Ham,Wigan,3,2,H,2,1,H,A Wiley,9,12,7,10,4,5,9,10,1,2,0,0 +24/04/2010,Wolves,Blackburn,1,1,D,0,1,A,M Clattenburg,14,12,5,8,4,4,9,8,0,1,0,0 +25/04/2010,Aston Villa,Birmingham,1,0,H,0,0,D,M Atkinson,10,16,6,14,7,5,12,16,1,4,0,0 +25/04/2010,Burnley,Liverpool,0,4,A,0,0,D,P Dowd,12,12,6,10,4,5,13,11,2,1,0,0 +25/04/2010,Chelsea,Stoke,7,0,H,3,0,H,S Bennett,29,3,18,2,7,0,14,12,0,3,0,0 +25/04/2010,Everton,Fulham,2,1,H,0,1,A,L Mason,15,8,9,6,9,3,7,12,0,1,0,0 +01/05/2010,Birmingham,Burnley,2,1,H,2,0,H,P Walton,13,12,4,7,6,8,11,10,2,1,0,0 +01/05/2010,Man City,Aston Villa,3,1,H,2,1,H,M Clattenburg,17,10,12,6,13,5,6,9,1,2,0,0 +01/05/2010,Portsmouth,Wolves,3,1,H,2,1,H,M Jones,12,18,8,11,7,9,10,13,0,1,0,0 +01/05/2010,Stoke,Everton,0,0,D,0,0,D,H Webb,12,11,6,9,6,5,10,10,0,0,0,0 +01/05/2010,Tottenham,Bolton,1,0,H,1,0,H,C Foy,25,11,12,7,11,7,11,10,0,3,0,0 +02/05/2010,Fulham,West Ham,3,2,H,1,0,H,A Marriner,5,7,3,5,9,3,10,14,0,0,0,0 +02/05/2010,Liverpool,Chelsea,0,2,A,0,1,A,A Wiley,7,15,2,11,9,5,7,7,1,2,0,0 +02/05/2010,Sunderland,Man United,0,1,A,0,1,A,S Bennett,10,14,2,9,9,4,10,9,3,2,0,0 +03/05/2010,Blackburn,Arsenal,2,1,H,1,1,D,M Atkinson,11,6,7,4,5,8,19,15,2,2,0,0 +03/05/2010,Wigan,Hull,2,2,D,1,1,D,P Dowd,13,6,7,3,8,1,11,14,2,0,0,0 +05/05/2010,Fulham,Stoke,0,1,A,0,0,D,P Walton,12,5,6,2,8,3,7,6,1,3,0,0 +05/05/2010,Man City,Tottenham,0,1,A,0,0,D,S Bennett,10,10,5,6,9,7,12,14,0,3,0,0 +09/05/2010,Arsenal,Fulham,4,0,H,3,0,H,M Jones,16,5,11,2,5,2,11,10,2,2,0,0 +09/05/2010,Aston Villa,Blackburn,0,1,A,0,0,D,S Bennett,16,9,12,3,11,5,14,12,2,1,0,0 +09/05/2010,Bolton,Birmingham,2,1,H,1,0,H,K Friend,14,10,8,5,3,6,14,17,2,5,0,0 +09/05/2010,Burnley,Tottenham,4,2,H,1,2,A,M Dean,16,16,12,11,5,6,9,8,0,0,0,0 +09/05/2010,Chelsea,Wigan,8,0,H,2,0,H,M Atkinson,17,4,10,3,2,3,14,9,2,2,0,1 +09/05/2010,Everton,Portsmouth,1,0,H,0,0,D,P Walton,21,10,13,4,14,1,12,11,0,0,0,0 +09/05/2010,Hull,Liverpool,0,0,D,0,0,D,A Marriner,11,19,6,7,2,6,15,11,1,0,0,0 +09/05/2010,Man United,Stoke,4,0,H,2,0,H,M Clattenburg,18,4,13,4,10,2,10,4,2,0,0,0 +09/05/2010,West Ham,Man City,1,1,D,1,1,D,H Webb,12,17,8,6,4,3,12,7,2,0,0,0 +09/05/2010,Wolves,Sunderland,2,1,H,1,1,D,L Mason,14,11,7,5,4,1,11,19,1,2,0,2 +14/08/2010,Aston Villa,West Ham,3,0,H,2,0,H,M Dean,23,12,11,2,16,7,15,15,1,2,0,0 +14/08/2010,Blackburn,Everton,1,0,H,1,0,H,P Dowd,7,17,2,12,1,3,19,14,2,1,0,0 +14/08/2010,Bolton,Fulham,0,0,D,0,0,D,S Attwell,13,12,9,7,4,8,12,13,1,3,0,0 +14/08/2010,Chelsea,West Brom,6,0,H,2,0,H,M Clattenburg,18,10,13,4,3,1,10,10,1,0,0,0 +14/08/2010,Sunderland,Birmingham,2,2,D,1,0,H,A Taylor,6,13,2,7,3,6,13,10,3,3,1,0 +14/08/2010,Tottenham,Man City,0,0,D,0,0,D,A Marriner,22,11,18,7,10,3,13,16,0,2,0,0 +14/08/2010,Wigan,Blackpool,0,4,A,0,3,A,M Halsey,11,9,6,7,6,4,8,11,1,1,0,0 +14/08/2010,Wolves,Stoke,2,1,H,2,0,H,L Probert,13,10,7,6,5,5,17,13,0,2,0,0 +15/08/2010,Liverpool,Arsenal,1,1,D,0,0,D,M Atkinson,7,14,4,7,9,11,13,15,1,3,1,1 +16/08/2010,Man United,Newcastle,3,0,H,2,0,H,C Foy,18,7,10,3,5,3,9,5,2,2,0,0 +21/08/2010,Arsenal,Blackpool,6,0,H,3,0,H,M Jones,26,3,16,1,8,2,9,3,0,0,0,1 +21/08/2010,Birmingham,Blackburn,2,1,H,0,0,D,M Oliver,10,7,7,2,4,12,13,14,2,3,0,0 +21/08/2010,Everton,Wolves,1,1,D,1,0,H,L Mason,14,9,6,4,3,3,18,15,1,2,0,0 +21/08/2010,Stoke,Tottenham,1,2,A,1,2,A,C Foy,15,7,10,6,6,2,15,4,3,1,0,0 +21/08/2010,West Brom,Sunderland,1,0,H,0,0,D,K Friend,11,10,5,4,4,6,16,12,2,2,0,0 +21/08/2010,West Ham,Bolton,1,3,A,0,0,D,A Marriner,17,12,10,8,8,5,11,18,2,4,0,0 +21/08/2010,Wigan,Chelsea,0,6,A,0,1,A,M Dean,17,10,12,8,0,0,12,7,1,2,0,0 +22/08/2010,Fulham,Man United,2,2,D,0,1,A,P Walton,16,14,8,7,5,11,7,6,2,2,0,0 +22/08/2010,Newcastle,Aston Villa,6,0,H,3,0,H,M Atkinson,20,3,14,1,10,2,15,12,3,1,0,0 +23/08/2010,Man City,Liverpool,3,0,H,1,0,H,P Dowd,8,13,3,7,5,4,13,12,1,1,0,0 +28/08/2010,Blackburn,Arsenal,1,2,A,1,1,D,C Foy,15,14,7,9,4,8,9,7,1,0,0,0 +28/08/2010,Blackpool,Fulham,2,2,D,0,1,A,M Oliver,14,12,5,7,4,5,13,16,1,0,0,0 +28/08/2010,Chelsea,Stoke,2,0,H,1,0,H,M Atkinson,15,9,6,3,6,1,5,10,0,2,0,0 +28/08/2010,Man United,West Ham,3,0,H,1,0,H,M Clattenburg,18,7,11,2,9,3,6,11,1,2,0,0 +28/08/2010,Tottenham,Wigan,0,1,A,0,0,D,P Dowd,12,13,7,5,16,7,10,11,3,2,0,0 +28/08/2010,Wolves,Newcastle,1,1,D,1,0,H,S Attwell,5,11,2,6,5,3,20,17,7,4,0,0 +29/08/2010,Aston Villa,Everton,1,0,H,1,0,H,M Jones,12,15,6,9,4,16,19,10,4,0,0,0 +29/08/2010,Bolton,Birmingham,2,2,D,0,1,A,K Friend,11,9,8,5,6,1,19,17,2,3,1,0 +29/08/2010,Liverpool,West Brom,1,0,H,0,0,D,L Probert,11,14,7,5,7,6,9,8,0,0,0,1 +29/08/2010,Sunderland,Man City,1,0,H,0,0,D,M Dean,12,9,4,7,3,7,5,9,0,3,0,0 +11/09/2010,Arsenal,Bolton,4,1,H,1,1,D,S Attwell,23,7,14,6,9,6,11,11,2,2,0,1 +11/09/2010,Everton,Man United,3,3,D,1,1,D,M Atkinson,23,13,14,8,7,5,11,18,1,1,0,0 +11/09/2010,Fulham,Wolves,2,1,H,0,1,A,P Dowd,13,4,6,2,6,6,9,17,3,6,0,1 +11/09/2010,Man City,Blackburn,1,1,D,0,1,A,M Clattenburg,20,4,14,2,14,2,12,10,2,1,0,0 +11/09/2010,Newcastle,Blackpool,0,2,A,0,1,A,L Mason,16,7,13,4,3,2,15,14,2,3,0,0 +11/09/2010,West Brom,Tottenham,1,1,D,1,1,D,H Webb,15,16,8,9,9,4,12,9,2,1,0,0 +11/09/2010,West Ham,Chelsea,1,3,A,0,2,A,C Foy,13,17,7,9,1,5,13,10,2,2,0,0 +11/09/2010,Wigan,Sunderland,1,1,D,0,0,D,A Marriner,17,4,4,2,5,1,19,11,4,2,0,1 +12/09/2010,Birmingham,Liverpool,0,0,D,0,0,D,M Halsey,15,14,3,8,4,3,8,9,1,0,0,0 +13/09/2010,Stoke,Aston Villa,2,1,H,0,1,A,L Probert,20,10,12,6,8,7,11,14,1,2,0,0 +18/09/2010,Aston Villa,Bolton,1,1,D,1,1,D,M Dean,13,13,6,7,8,4,15,8,1,2,0,0 +18/09/2010,Blackburn,Fulham,1,1,D,1,0,H,A Taylor,9,9,5,4,3,3,15,10,0,1,0,0 +18/09/2010,Everton,Newcastle,0,1,A,0,1,A,A Marriner,6,9,3,4,3,6,15,21,2,3,0,0 +18/09/2010,Stoke,West Ham,1,1,D,0,1,A,L Mason,9,13,3,6,6,3,14,8,3,2,0,0 +18/09/2010,Sunderland,Arsenal,1,1,D,0,1,A,P Dowd,16,16,5,7,11,3,11,13,2,2,0,1 +18/09/2010,Tottenham,Wolves,3,1,H,0,1,A,M Jones,20,5,11,2,5,4,7,11,3,3,0,0 +18/09/2010,West Brom,Birmingham,3,1,H,0,1,A,C Foy,16,6,10,3,5,0,11,8,1,1,0,0 +19/09/2010,Chelsea,Blackpool,4,0,H,4,0,H,M Clattenburg,27,11,15,4,9,6,7,4,1,2,0,0 +19/09/2010,Man United,Liverpool,3,2,H,1,0,H,H Webb,16,7,7,1,6,1,15,10,4,1,0,0 +19/09/2010,Wigan,Man City,0,2,A,0,1,A,L Probert,9,11,3,5,3,1,10,14,1,2,0,0 +25/09/2010,Arsenal,West Brom,2,3,A,0,0,D,M Oliver,24,7,12,5,16,4,15,14,4,3,0,0 +25/09/2010,Birmingham,Wigan,0,0,D,0,0,D,M Clattenburg,10,17,4,10,3,7,12,18,2,3,1,0 +25/09/2010,Blackpool,Blackburn,1,2,A,0,1,A,M Dean,11,13,8,8,8,8,12,11,2,1,0,0 +25/09/2010,Fulham,Everton,0,0,D,0,0,D,H Webb,12,15,2,7,4,5,15,13,2,1,0,0 +25/09/2010,Liverpool,Sunderland,2,2,D,1,1,D,S Attwell,13,10,5,1,3,3,15,7,3,1,0,0 +25/09/2010,Man City,Chelsea,1,0,H,0,0,D,A Marriner,8,15,6,4,3,4,10,12,2,3,0,0 +25/09/2010,West Ham,Tottenham,1,0,H,1,0,H,M Atkinson,22,17,15,11,14,7,10,11,2,0,0,0 +26/09/2010,Bolton,Man United,2,2,D,1,1,D,P Dowd,15,12,9,6,7,12,16,18,1,2,0,0 +26/09/2010,Newcastle,Stoke,1,2,A,1,0,H,M Jones,11,8,7,2,3,4,8,11,0,1,0,0 +26/09/2010,Wolves,Aston Villa,1,2,A,0,1,A,M Halsey,14,10,12,8,5,4,15,19,1,2,0,0 +02/10/2010,Birmingham,Everton,0,2,A,0,0,D,P Dowd,6,11,3,6,6,5,5,11,0,0,0,0 +02/10/2010,Stoke,Blackburn,1,0,H,0,0,D,H Webb,18,7,11,4,8,6,8,13,2,3,0,0 +02/10/2010,Sunderland,Man United,0,0,D,0,0,D,C Foy,14,10,8,4,10,4,10,6,1,2,0,0 +02/10/2010,Tottenham,Aston Villa,2,1,H,1,1,D,M Clattenburg,19,13,10,4,10,4,12,19,1,3,0,0 +02/10/2010,West Brom,Bolton,1,1,D,0,0,D,P Walton,17,10,9,5,7,4,8,10,3,2,0,0 +02/10/2010,West Ham,Fulham,1,1,D,0,1,A,A Marriner,12,17,5,5,2,3,15,8,3,1,0,0 +02/10/2010,Wigan,Wolves,2,0,H,0,0,D,L Mason,24,2,17,0,3,3,9,12,1,2,0,1 +03/10/2010,Chelsea,Arsenal,2,0,H,1,0,H,M Dean,11,13,6,5,10,10,7,12,1,1,0,0 +03/10/2010,Liverpool,Blackpool,1,2,A,0,2,A,M Jones,19,11,10,7,5,2,11,10,2,1,0,0 +03/10/2010,Man City,Newcastle,2,1,H,1,1,D,M Atkinson,9,8,5,4,4,4,11,13,3,2,0,0 +16/10/2010,Arsenal,Birmingham,2,1,H,1,1,D,M Atkinson,18,6,13,4,7,3,16,9,2,2,1,0 +16/10/2010,Aston Villa,Chelsea,0,0,D,0,0,D,L Mason,11,13,5,6,9,5,11,11,4,3,0,0 +16/10/2010,Bolton,Stoke,2,1,H,1,0,H,P Walton,11,8,6,3,5,6,10,8,2,2,1,0 +16/10/2010,Fulham,Tottenham,1,2,A,1,1,D,M Dean,11,14,5,6,4,11,9,10,1,1,0,0 +16/10/2010,Man United,West Brom,2,2,D,2,0,H,M Jones,16,7,9,2,3,2,10,7,1,2,0,0 +16/10/2010,Newcastle,Wigan,2,2,D,0,2,A,M Halsey,9,8,4,4,6,3,13,14,2,1,0,0 +16/10/2010,Wolves,West Ham,1,1,D,1,0,H,M Clattenburg,9,9,5,7,8,3,10,8,0,1,0,0 +17/10/2010,Blackpool,Man City,2,3,A,0,0,D,P Dowd,16,13,5,8,3,9,9,9,0,2,0,0 +17/10/2010,Everton,Liverpool,2,0,H,1,0,H,H Webb,9,15,6,9,8,7,10,9,2,2,0,0 +18/10/2010,Blackburn,Sunderland,0,0,D,0,0,D,L Probert,9,16,3,7,5,3,12,11,2,1,1,0 +23/10/2010,Birmingham,Blackpool,2,0,H,1,0,H,A Taylor,17,14,8,6,9,10,8,16,1,1,0,0 +23/10/2010,Chelsea,Wolves,2,0,H,1,0,H,L Probert,17,12,10,9,8,8,8,8,1,2,0,0 +23/10/2010,Sunderland,Aston Villa,1,0,H,1,0,H,M Halsey,13,11,6,7,4,11,11,13,1,2,0,0 +23/10/2010,Tottenham,Everton,1,1,D,1,1,D,M Jones,13,7,8,3,4,4,15,14,3,1,0,0 +23/10/2010,West Brom,Fulham,2,1,H,2,1,H,K Friend,14,9,7,3,7,5,3,8,0,0,0,0 +23/10/2010,West Ham,Newcastle,1,2,A,1,1,D,C Foy,5,13,1,8,2,6,12,6,0,0,0,0 +23/10/2010,Wigan,Bolton,1,1,D,0,0,D,H Webb,15,9,9,2,5,0,8,14,0,2,0,0 +24/10/2010,Liverpool,Blackburn,2,1,H,0,0,D,P Dowd,17,4,10,2,15,4,17,12,2,3,0,0 +24/10/2010,Man City,Arsenal,0,3,A,0,1,A,M Clattenburg,9,12,6,8,6,7,11,10,2,4,1,0 +24/10/2010,Stoke,Man United,1,2,A,0,1,A,A Marriner,6,12,2,5,5,6,6,13,2,2,0,0 +30/10/2010,Arsenal,West Ham,1,0,H,0,0,D,M Jones,20,6,13,3,7,4,14,18,2,2,0,0 +30/10/2010,Blackburn,Chelsea,1,2,A,1,1,D,P Walton,13,16,6,9,4,5,12,14,2,2,0,0 +30/10/2010,Everton,Stoke,1,0,H,0,0,D,L Probert,18,9,10,1,6,2,16,14,1,1,0,0 +30/10/2010,Fulham,Wigan,2,0,H,2,0,H,A Marriner,15,8,9,3,7,1,7,12,0,0,0,0 +30/10/2010,Man United,Tottenham,2,0,H,1,0,H,M Clattenburg,18,11,10,5,1,4,12,10,0,3,0,0 +30/10/2010,Wolves,Man City,2,1,H,1,1,D,M Dean,10,17,6,9,5,7,5,14,1,2,0,0 +31/10/2010,Aston Villa,Birmingham,0,0,D,0,0,D,H Webb,7,10,3,6,1,4,13,11,2,3,0,0 +31/10/2010,Bolton,Liverpool,0,1,A,0,0,D,M Atkinson,14,10,10,4,1,4,15,13,2,2,0,0 +31/10/2010,Newcastle,Sunderland,5,1,H,3,0,H,P Dowd,16,11,12,5,8,6,8,18,3,6,0,1 +01/11/2010,Blackpool,West Brom,2,1,H,1,0,H,M Oliver,21,12,8,5,8,2,16,13,2,3,0,2 +06/11/2010,Birmingham,West Ham,2,2,D,0,0,D,M Oliver,8,16,5,8,3,4,12,14,0,1,0,0 +06/11/2010,Blackburn,Wigan,2,1,H,0,0,D,K Friend,10,16,3,10,3,6,16,15,0,2,0,0 +06/11/2010,Blackpool,Everton,2,2,D,1,1,D,A Marriner,11,20,8,14,3,7,14,12,2,1,0,0 +06/11/2010,Bolton,Tottenham,4,2,H,1,0,H,C Foy,18,11,8,6,7,6,13,9,1,0,0,0 +06/11/2010,Fulham,Aston Villa,1,1,D,0,1,A,P Walton,14,15,10,8,3,3,12,4,1,0,0,0 +06/11/2010,Man United,Wolves,2,1,H,1,0,H,P Dowd,14,8,10,6,3,2,6,11,1,0,0,0 +06/11/2010,Sunderland,Stoke,2,0,H,1,0,H,M Atkinson,14,11,7,5,6,9,10,13,0,5,0,0 +07/11/2010,Arsenal,Newcastle,0,1,A,0,1,A,M Dean,11,7,8,2,12,2,16,12,3,1,1,0 +07/11/2010,Liverpool,Chelsea,2,0,H,2,0,H,H Webb,9,11,7,7,2,5,15,17,0,2,0,0 +07/11/2010,West Brom,Man City,0,2,A,0,2,A,L Probert,13,9,7,5,6,1,15,16,3,4,1,1 +09/11/2010,Stoke,Birmingham,3,2,H,1,0,H,M Clattenburg,19,14,12,10,10,4,6,8,0,0,0,0 +09/11/2010,Tottenham,Sunderland,1,1,D,0,0,D,H Webb,20,8,12,4,17,3,6,15,2,1,0,0 +10/11/2010,Aston Villa,Blackpool,3,2,H,1,1,D,A Taylor,19,12,12,6,11,8,7,10,2,3,0,0 +10/11/2010,Chelsea,Fulham,1,0,H,1,0,H,M Atkinson,19,5,10,3,4,4,14,15,2,0,1,0 +10/11/2010,Everton,Bolton,1,1,D,0,0,D,P Dowd,23,10,12,5,15,10,8,12,0,3,1,0 +10/11/2010,Man City,Man United,0,0,D,0,0,D,C Foy,6,7,4,4,8,5,10,12,0,2,0,0 +10/11/2010,Newcastle,Blackburn,1,2,A,0,1,A,M Jones,9,7,3,5,4,1,16,15,3,4,0,0 +10/11/2010,West Ham,West Brom,2,2,D,1,1,D,M Dean,8,14,5,6,6,7,13,5,3,0,0,0 +10/11/2010,Wigan,Liverpool,1,1,D,0,1,A,P Walton,10,8,8,3,5,4,7,10,0,2,0,0 +10/11/2010,Wolves,Arsenal,0,2,A,0,1,A,M Halsey,14,12,7,8,5,2,7,14,1,2,0,0 +13/11/2010,Aston Villa,Man United,2,2,D,0,0,D,M Dean,12,16,6,10,9,7,11,9,3,4,0,0 +13/11/2010,Man City,Birmingham,0,0,D,0,0,D,M Jones,21,5,11,3,6,4,15,11,2,0,0,0 +13/11/2010,Newcastle,Fulham,0,0,D,0,0,D,L Probert,13,10,7,8,5,5,11,19,2,2,0,0 +13/11/2010,Stoke,Liverpool,2,0,H,0,0,D,M Halsey,14,10,7,6,4,3,14,10,2,3,0,1 +13/11/2010,Tottenham,Blackburn,4,2,H,2,0,H,A Marriner,20,12,12,8,12,6,11,11,2,2,0,0 +13/11/2010,West Ham,Blackpool,0,0,D,0,0,D,K Friend,17,16,4,5,8,7,7,7,0,1,0,0 +13/11/2010,Wigan,West Brom,1,0,H,0,0,D,A Taylor,13,12,4,6,5,5,13,13,1,1,0,0 +13/11/2010,Wolves,Bolton,2,3,A,0,1,A,P Walton,10,13,6,7,12,3,10,10,1,0,0,0 +14/11/2010,Chelsea,Sunderland,0,3,A,0,1,A,C Foy,14,19,7,9,6,3,9,13,1,2,0,0 +14/11/2010,Everton,Arsenal,1,2,A,0,1,A,H Webb,14,13,7,8,9,2,15,14,2,2,0,0 +20/11/2010,Arsenal,Tottenham,2,3,A,2,0,H,P Dowd,12,10,7,6,2,6,17,16,3,1,0,0 +20/11/2010,Birmingham,Chelsea,1,0,H,1,0,H,M Halsey,1,24,1,9,4,14,9,2,2,0,0,0 +20/11/2010,Blackpool,Wolves,2,1,H,2,0,H,M Clattenburg,13,19,9,10,4,11,7,9,0,2,0,0 +20/11/2010,Bolton,Newcastle,5,1,H,2,0,H,H Webb,18,6,9,1,2,3,13,9,0,2,0,1 +20/11/2010,Liverpool,West Ham,3,0,H,3,0,H,L Probert,19,6,11,1,10,2,8,8,0,1,0,0 +20/11/2010,Man United,Wigan,2,0,H,1,0,H,M Atkinson,17,10,12,4,11,1,10,14,0,2,0,2 +20/11/2010,West Brom,Stoke,0,3,A,0,0,D,C Foy,15,6,8,4,8,3,10,9,0,0,0,0 +21/11/2010,Blackburn,Aston Villa,2,0,H,1,0,H,M Oliver,14,16,5,11,5,8,15,10,0,1,0,0 +21/11/2010,Fulham,Man City,1,4,A,0,3,A,L Mason,9,11,3,8,7,7,13,16,2,1,0,0 +22/11/2010,Sunderland,Everton,2,2,D,1,1,D,P Walton,13,10,8,8,4,4,5,7,0,2,0,0 +27/11/2010,Aston Villa,Arsenal,2,4,A,0,2,A,M Clattenburg,6,17,4,10,3,8,10,7,1,1,0,0 +27/11/2010,Bolton,Blackpool,2,2,D,0,1,A,M Dean,24,16,14,10,4,4,8,14,2,2,0,0 +27/11/2010,Everton,West Brom,1,4,A,1,2,A,L Mason,12,7,4,3,2,2,11,18,1,3,1,1 +27/11/2010,Fulham,Birmingham,1,1,D,0,1,A,H Webb,11,7,7,3,7,4,10,9,1,0,0,0 +27/11/2010,Man United,Blackburn,7,1,H,3,0,H,L Probert,15,3,11,2,5,2,6,6,0,2,0,0 +27/11/2010,Stoke,Man City,1,1,D,0,0,D,P Walton,16,9,9,4,6,2,8,10,2,2,0,0 +27/11/2010,West Ham,Wigan,3,1,H,1,0,H,M Halsey,15,10,9,10,7,2,8,8,0,0,0,0 +27/11/2010,Wolves,Sunderland,3,2,H,0,0,D,M Jones,14,15,8,9,11,5,11,10,2,3,0,0 +28/11/2010,Newcastle,Chelsea,1,1,D,1,1,D,A Marriner,5,17,3,13,6,7,9,11,2,1,0,0 +28/11/2010,Tottenham,Liverpool,2,1,H,0,1,A,M Atkinson,20,12,13,8,5,8,6,16,0,5,0,0 +04/12/2010,Arsenal,Fulham,2,1,H,1,1,D,C Foy,15,11,9,7,3,7,7,5,0,0,0,0 +04/12/2010,Birmingham,Tottenham,1,1,D,0,1,A,K Friend,13,10,5,5,6,11,14,7,1,1,0,0 +04/12/2010,Blackburn,Wolves,3,0,H,2,0,H,H Webb,10,10,7,7,8,11,9,11,0,2,0,0 +04/12/2010,Chelsea,Everton,1,1,D,1,0,H,L Probert,12,16,5,7,7,2,7,11,1,4,0,0 +04/12/2010,Man City,Bolton,1,0,H,1,0,H,A Marriner,19,7,10,2,8,8,12,18,3,2,1,0 +04/12/2010,Wigan,Stoke,2,2,D,2,2,D,M Jones,15,11,8,7,5,4,11,10,1,4,0,0 +05/12/2010,Sunderland,West Ham,1,0,H,1,0,H,M Atkinson,17,8,12,2,9,3,6,8,1,3,0,0 +05/12/2010,West Brom,Newcastle,3,1,H,1,0,H,M Dean,20,11,12,7,7,3,11,9,0,5,0,0 +06/12/2010,Liverpool,Aston Villa,3,0,H,2,0,H,P Dowd,12,5,7,2,4,3,12,13,0,1,0,0 +11/12/2010,Aston Villa,West Brom,2,1,H,1,0,H,M Halsey,6,11,3,4,6,13,8,14,0,2,0,0 +11/12/2010,Everton,Wigan,0,0,D,0,0,D,M Oliver,16,5,8,4,9,4,13,18,2,4,0,0 +11/12/2010,Fulham,Sunderland,0,0,D,0,0,D,N Swarbrick,14,11,13,2,4,3,14,10,0,1,0,0 +11/12/2010,Newcastle,Liverpool,3,1,H,1,0,H,L Mason,8,16,5,7,2,5,19,13,2,1,0,0 +11/12/2010,Stoke,Blackpool,0,1,A,0,0,D,A Taylor,16,10,5,5,10,4,12,11,0,2,0,0 +11/12/2010,West Ham,Man City,1,3,A,0,1,A,P Dowd,13,12,3,7,2,6,10,13,1,3,0,0 +12/12/2010,Bolton,Blackburn,2,1,H,0,0,D,M Clattenburg,12,16,7,11,3,10,12,13,4,3,1,0 +12/12/2010,Tottenham,Chelsea,1,1,D,1,0,H,M Dean,10,13,3,7,1,7,9,13,2,2,0,0 +12/12/2010,Wolves,Birmingham,1,0,H,1,0,H,M Atkinson,14,3,9,0,7,2,9,15,2,4,0,0 +13/12/2010,Man United,Arsenal,1,0,H,1,0,H,H Webb,14,11,10,6,5,4,7,16,0,4,0,0 +18/12/2010,Blackburn,West Ham,1,1,D,0,0,D,M Dean,13,11,5,3,9,4,12,10,2,3,0,0 +18/12/2010,Sunderland,Bolton,1,0,H,1,0,H,C Foy,13,12,5,8,2,5,6,13,1,3,0,0 +20/12/2010,Man City,Everton,1,2,A,0,2,A,P Walton,25,4,15,3,11,0,8,13,3,2,1,1 +26/12/2010,Aston Villa,Tottenham,1,2,A,0,1,A,M Atkinson,26,13,15,8,8,4,13,11,3,3,0,1 +26/12/2010,Blackburn,Stoke,0,2,A,0,0,D,M Jones,12,11,8,9,5,3,12,20,1,4,0,0 +26/12/2010,Bolton,West Brom,2,0,H,1,0,H,H Webb,12,15,8,9,5,7,8,8,1,1,0,0 +26/12/2010,Fulham,West Ham,1,3,A,1,2,A,M Halsey,13,10,6,7,8,4,12,10,1,1,0,0 +26/12/2010,Man United,Sunderland,2,0,H,1,0,H,P Dowd,14,6,10,2,5,3,9,10,0,2,0,0 +26/12/2010,Newcastle,Man City,1,3,A,0,2,A,C Foy,20,9,10,6,6,3,7,15,1,5,0,0 +26/12/2010,Wolves,Wigan,1,2,A,0,2,A,M Dean,8,13,4,8,7,3,7,14,1,3,0,0 +27/12/2010,Arsenal,Chelsea,3,1,H,1,0,H,M Clattenburg,10,10,8,4,4,6,12,16,2,3,0,0 +28/12/2010,Birmingham,Man United,1,1,D,0,0,D,L Mason,8,10,3,4,4,4,10,11,3,3,0,0 +28/12/2010,Man City,Aston Villa,4,0,H,3,0,H,M Oliver,11,9,8,4,8,6,14,12,1,2,0,0 +28/12/2010,Stoke,Fulham,0,2,A,0,2,A,K Friend,14,4,8,3,9,2,8,12,1,2,0,0 +28/12/2010,Sunderland,Blackpool,0,2,A,0,0,D,A Marriner,32,9,16,4,9,2,6,5,2,0,0,0 +28/12/2010,Tottenham,Newcastle,2,0,H,0,0,D,A Taylor,13,12,8,8,9,7,9,16,2,4,1,0 +28/12/2010,West Brom,Blackburn,1,3,A,1,1,D,P Dowd,21,6,11,4,8,5,9,13,1,3,1,1 +28/12/2010,West Ham,Everton,1,1,D,1,1,D,H Webb,11,13,2,5,4,7,12,15,1,0,0,0 +29/12/2010,Chelsea,Bolton,1,0,H,0,0,D,M Jones,12,6,7,2,6,3,12,24,1,1,0,0 +29/12/2010,Liverpool,Wolves,0,1,A,0,0,D,P Walton,9,7,5,3,2,8,8,11,1,1,0,0 +29/12/2010,Wigan,Arsenal,2,2,D,1,2,A,L Probert,7,15,5,9,3,4,16,11,1,0,1,0 +01/01/2011,Birmingham,Arsenal,0,3,A,0,1,A,P Walton,2,18,1,11,4,5,7,9,3,1,0,0 +01/01/2011,Liverpool,Bolton,2,1,H,0,1,A,K Friend,16,10,7,4,7,4,11,16,2,2,0,0 +01/01/2011,Man City,Blackpool,1,0,H,1,0,H,M Clattenburg,15,8,4,6,10,4,11,9,0,0,0,0 +01/01/2011,Stoke,Everton,2,0,H,1,0,H,A Marriner,7,15,3,10,1,5,14,8,4,1,0,0 +01/01/2011,Sunderland,Blackburn,3,0,H,2,0,H,M Dean,13,11,7,3,3,2,13,9,1,2,0,0 +01/01/2011,Tottenham,Fulham,1,0,H,1,0,H,M Jones,13,12,9,4,5,1,11,9,2,1,0,0 +01/01/2011,West Brom,Man United,1,2,A,1,1,D,C Foy,17,5,8,3,4,5,8,13,1,3,0,0 +01/01/2011,West Ham,Wolves,2,0,H,0,0,D,L Probert,11,15,6,10,5,10,7,14,0,1,0,0 +02/01/2011,Chelsea,Aston Villa,3,3,D,1,1,D,L Mason,15,16,10,10,9,6,17,11,2,7,0,0 +02/01/2011,Wigan,Newcastle,0,1,A,0,1,A,H Webb,9,13,4,5,3,3,15,14,2,2,0,0 +04/01/2011,Blackpool,Birmingham,1,2,A,0,1,A,J Moss,19,11,13,7,3,7,9,6,0,1,0,0 +04/01/2011,Fulham,West Brom,3,0,H,1,0,H,M Atkinson,12,12,6,4,7,4,7,9,0,1,0,0 +04/01/2011,Man United,Stoke,2,1,H,1,0,H,M Clattenburg,18,5,7,1,6,1,6,11,0,0,0,0 +05/01/2011,Arsenal,Man City,0,0,D,0,0,D,M Jones,13,5,8,0,7,2,6,12,0,2,1,1 +05/01/2011,Aston Villa,Sunderland,0,1,A,0,0,D,P Walton,8,11,2,5,5,9,9,12,3,3,1,1 +05/01/2011,Blackburn,Liverpool,3,1,H,2,0,H,A Marriner,10,13,4,9,0,7,14,13,1,1,0,0 +05/01/2011,Bolton,Wigan,1,1,D,0,0,D,P Dowd,14,10,7,7,8,4,9,8,0,4,0,0 +05/01/2011,Everton,Tottenham,2,1,H,1,1,D,L Probert,19,13,12,5,4,3,16,12,1,0,0,0 +05/01/2011,Newcastle,West Ham,5,0,H,3,0,H,M Dean,11,13,7,6,4,3,10,10,0,4,0,0 +05/01/2011,Wolves,Chelsea,1,0,H,1,0,H,M Halsey,3,17,2,8,4,9,15,12,0,1,0,0 +12/01/2011,Blackpool,Liverpool,2,1,H,1,1,D,M Oliver,13,10,9,6,3,3,6,13,1,3,0,0 +15/01/2011,Chelsea,Blackburn,2,0,H,0,0,D,M Atkinson,27,3,11,2,14,1,10,6,1,0,0,0 +15/01/2011,Man City,Wolves,4,3,H,1,1,D,L Mason,15,10,12,7,11,8,9,9,0,0,0,0 +15/01/2011,Stoke,Bolton,2,0,H,1,0,H,M Oliver,12,13,5,6,5,5,8,13,0,2,0,0 +15/01/2011,West Brom,Blackpool,3,2,H,1,1,D,S Attwell,18,9,11,2,4,4,10,11,1,2,0,0 +15/01/2011,West Ham,Arsenal,0,3,A,0,2,A,A Marriner,7,19,6,13,4,10,8,7,1,0,0,0 +15/01/2011,Wigan,Fulham,1,1,D,0,0,D,A Taylor,8,14,5,5,3,11,21,7,4,2,0,0 +16/01/2011,Birmingham,Aston Villa,1,1,D,0,0,D,M Clattenburg,14,13,4,3,5,7,14,14,1,2,0,0 +16/01/2011,Liverpool,Everton,2,2,D,1,0,H,P Dowd,19,10,14,5,10,6,16,17,3,0,0,0 +16/01/2011,Sunderland,Newcastle,1,1,D,0,0,D,H Webb,10,9,7,2,4,6,17,5,1,1,0,0 +16/01/2011,Tottenham,Man United,0,0,D,0,0,D,M Dean,16,7,6,4,8,2,9,18,2,4,0,1 +22/01/2011,Arsenal,Wigan,3,0,H,1,0,H,K Friend,23,3,15,1,8,1,6,14,0,1,0,1 +22/01/2011,Aston Villa,Man City,1,0,H,1,0,H,H Webb,10,24,8,14,2,13,11,11,1,1,0,0 +22/01/2011,Blackpool,Sunderland,1,2,A,0,2,A,L Mason,13,11,7,6,10,4,13,9,2,0,0,0 +22/01/2011,Everton,West Ham,2,2,D,0,1,A,P Walton,11,13,6,5,12,2,8,12,1,2,0,1 +22/01/2011,Fulham,Stoke,2,0,H,1,0,H,S Attwell,10,9,7,1,3,2,11,10,1,1,0,1 +22/01/2011,Man United,Birmingham,5,0,H,3,0,H,M Jones,23,6,9,1,10,3,10,5,0,0,0,0 +22/01/2011,Newcastle,Tottenham,1,1,D,0,0,D,M Halsey,13,14,6,9,1,4,13,7,0,0,0,0 +22/01/2011,Wolves,Liverpool,0,3,A,0,1,A,M Atkinson,11,15,4,10,5,4,6,17,0,3,0,0 +23/01/2011,Blackburn,West Brom,2,0,H,1,0,H,M Clattenburg,17,24,12,20,9,9,4,6,0,1,0,0 +24/01/2011,Bolton,Chelsea,0,4,A,0,2,A,C Foy,11,14,8,11,4,3,7,4,0,0,0,0 +25/01/2011,Blackpool,Man United,2,3,A,2,0,H,P Walton,6,17,4,13,6,7,10,10,1,2,0,0 +25/01/2011,Wigan,Aston Villa,1,2,A,0,0,D,J Moss,19,11,8,10,7,4,9,13,2,3,0,0 +26/01/2011,Liverpool,Fulham,1,0,H,0,0,D,L Probert,11,12,7,8,6,2,11,10,0,0,0,0 +01/02/2011,Arsenal,Everton,2,1,H,0,1,A,L Mason,19,4,10,4,5,7,12,14,3,5,0,0 +01/02/2011,Man United,Aston Villa,3,1,H,2,0,H,C Foy,18,9,11,5,5,5,6,6,0,0,0,0 +01/02/2011,Sunderland,Chelsea,2,4,A,2,2,D,M Halsey,4,21,3,14,0,6,10,10,1,2,0,0 +01/02/2011,West Brom,Wigan,2,2,D,1,2,A,P Dowd,21,11,10,7,8,5,13,12,4,5,0,0 +02/02/2011,Birmingham,Man City,2,2,D,1,2,A,K Friend,14,12,7,9,6,4,9,12,0,2,0,0 +02/02/2011,Blackburn,Tottenham,0,1,A,0,1,A,A Marriner,17,6,9,5,4,4,14,11,2,1,0,0 +02/02/2011,Blackpool,West Ham,1,3,A,1,3,A,S Attwell,13,12,8,10,10,7,9,12,0,1,0,0 +02/02/2011,Bolton,Wolves,1,0,H,0,0,D,P Walton,13,11,8,5,8,3,16,8,1,3,0,0 +02/02/2011,Fulham,Newcastle,1,0,H,0,0,D,M Jones,11,4,9,3,4,7,14,19,0,3,0,0 +02/02/2011,Liverpool,Stoke,2,0,H,0,0,D,A Taylor,13,6,9,1,7,0,6,8,1,4,0,0 +05/02/2011,Aston Villa,Fulham,2,2,D,1,0,H,L Mason,10,13,5,9,3,6,15,11,3,2,0,0 +05/02/2011,Everton,Blackpool,5,3,H,1,1,D,K Friend,23,11,16,7,9,5,8,17,0,1,0,0 +05/02/2011,Man City,West Brom,3,0,H,3,0,H,M Atkinson,13,17,9,9,8,8,13,10,1,2,0,0 +05/02/2011,Newcastle,Arsenal,4,4,D,0,4,A,P Dowd,16,13,8,10,12,6,13,15,2,3,0,1 +05/02/2011,Stoke,Sunderland,3,2,H,1,1,D,L Probert,18,11,9,7,12,4,7,15,1,3,0,0 +05/02/2011,Tottenham,Bolton,2,1,H,1,0,H,M Clattenburg,13,8,7,3,6,7,13,11,1,3,0,0 +05/02/2011,Wigan,Blackburn,4,3,H,1,1,D,M Dean,11,9,7,5,4,9,11,12,1,2,0,0 +05/02/2011,Wolves,Man United,2,1,H,2,1,H,M Oliver,6,14,5,7,4,6,6,11,2,2,0,0 +06/02/2011,Chelsea,Liverpool,0,1,A,0,0,D,A Marriner,14,7,4,4,4,3,9,9,1,1,0,0 +06/02/2011,West Ham,Birmingham,0,1,A,0,0,D,C Foy,16,9,9,3,5,5,18,13,3,1,0,0 +12/02/2011,Arsenal,Wolves,2,0,H,1,0,H,C Foy,17,2,8,0,3,6,15,9,0,1,0,0 +12/02/2011,Birmingham,Stoke,1,0,H,0,0,D,M Halsey,10,15,4,5,3,5,15,14,1,0,0,0 +12/02/2011,Blackburn,Newcastle,0,0,D,0,0,D,S Attwell,4,12,0,6,2,11,11,11,1,3,0,0 +12/02/2011,Blackpool,Aston Villa,1,1,D,1,1,D,H Webb,14,7,5,4,4,8,13,14,2,1,0,1 +12/02/2011,Liverpool,Wigan,1,1,D,1,0,H,K Friend,13,8,5,2,8,5,11,17,1,3,0,0 +12/02/2011,Man United,Man City,2,1,H,1,0,H,A Marriner,11,13,6,8,4,7,15,13,2,2,0,0 +12/02/2011,Sunderland,Tottenham,1,2,A,1,1,D,M Jones,16,11,10,5,2,3,14,12,2,4,0,0 +12/02/2011,West Brom,West Ham,3,3,D,3,0,H,L Mason,15,18,11,11,4,9,12,13,2,3,0,0 +13/02/2011,Bolton,Everton,2,0,H,1,0,H,L Probert,11,9,5,4,4,4,15,12,1,0,0,0 +14/02/2011,Fulham,Chelsea,0,0,D,0,0,D,M Dean,12,23,8,9,4,6,10,13,2,1,0,0 +15/02/2011,Birmingham,Newcastle,0,2,A,0,1,A,L Mason,11,10,5,7,4,6,13,14,1,3,0,0 +20/02/2011,West Brom,Wolves,1,1,D,0,1,A,M Dean,19,6,11,2,12,1,6,6,0,0,0,0 +22/02/2011,Blackpool,Tottenham,3,1,H,2,0,H,C Foy,8,25,4,10,1,7,11,8,2,0,0,0 +23/02/2011,Arsenal,Stoke,1,0,H,1,0,H,P Walton,9,7,4,4,5,3,8,9,1,2,0,0 +26/02/2011,Aston Villa,Blackburn,4,1,H,0,0,D,M Atkinson,17,5,10,2,10,2,4,12,0,3,0,1 +26/02/2011,Everton,Sunderland,2,0,H,2,0,H,S Attwell,17,12,9,7,4,1,11,8,0,0,0,0 +26/02/2011,Newcastle,Bolton,1,1,D,1,1,D,C Foy,13,17,6,10,3,6,7,11,0,2,1,0 +26/02/2011,Wigan,Man United,0,4,A,0,1,A,M Clattenburg,10,14,4,9,8,4,13,11,0,1,0,0 +26/02/2011,Wolves,Blackpool,4,0,H,1,0,H,N Swarbrick,15,2,11,1,11,4,16,10,1,1,0,1 +27/02/2011,Man City,Fulham,1,1,D,1,0,H,P Walton,14,11,4,5,10,1,17,13,1,2,0,0 +27/02/2011,West Ham,Liverpool,3,1,H,2,0,H,M Halsey,17,12,11,8,6,8,10,8,0,1,0,0 +28/02/2011,Stoke,West Brom,1,1,D,0,0,D,M Oliver,6,16,3,11,4,0,17,14,2,2,0,0 +01/03/2011,Chelsea,Man United,2,1,H,0,1,A,M Atkinson,17,12,11,7,10,2,11,14,3,2,0,1 +05/03/2011,Arsenal,Sunderland,0,0,D,0,0,D,A Taylor,14,6,7,3,5,6,13,10,3,3,0,0 +05/03/2011,Birmingham,West Brom,1,3,A,0,0,D,M Jones,9,9,4,5,7,3,8,10,3,1,0,0 +05/03/2011,Bolton,Aston Villa,3,2,H,1,1,D,K Friend,15,10,9,6,8,5,18,11,5,2,0,0 +05/03/2011,Fulham,Blackburn,3,2,H,1,1,D,M Clattenburg,15,12,11,9,9,7,12,17,2,3,0,0 +05/03/2011,Man City,Wigan,1,0,H,1,0,H,S Attwell,7,12,3,5,4,6,17,8,3,1,0,0 +05/03/2011,Newcastle,Everton,1,2,A,1,2,A,H Webb,10,13,8,8,5,4,12,14,2,4,0,0 +05/03/2011,West Ham,Stoke,3,0,H,2,0,H,A Marriner,15,7,10,5,7,3,9,10,0,3,0,0 +06/03/2011,Liverpool,Man United,3,1,H,2,0,H,P Dowd,17,12,10,7,6,4,12,12,2,3,0,0 +06/03/2011,Wolves,Tottenham,3,3,D,2,2,D,M Halsey,13,15,4,10,7,4,10,11,0,3,0,0 +07/03/2011,Blackpool,Chelsea,1,3,A,0,1,A,M Dean,14,18,5,11,3,13,5,8,0,0,0,0 +09/03/2011,Everton,Birmingham,1,1,D,1,1,D,P Walton,17,5,11,3,9,1,8,12,1,3,0,0 +19/03/2011,Aston Villa,Wolves,0,1,A,0,1,A,P Dowd,11,8,5,5,8,3,15,19,1,3,0,0 +19/03/2011,Blackburn,Blackpool,2,2,D,0,2,A,H Webb,24,8,7,6,8,3,15,12,1,1,0,0 +19/03/2011,Everton,Fulham,2,1,H,1,0,H,M Oliver,16,12,12,6,7,5,15,19,2,0,0,0 +19/03/2011,Man United,Bolton,1,0,H,0,0,D,A Marriner,14,11,8,6,12,5,8,11,0,0,1,0 +19/03/2011,Stoke,Newcastle,4,0,H,1,0,H,L Mason,8,14,6,9,3,7,16,9,1,1,0,0 +19/03/2011,Tottenham,West Ham,0,0,D,0,0,D,M Dean,26,11,14,5,6,2,6,13,0,4,0,0 +19/03/2011,West Brom,Arsenal,2,2,D,1,0,H,S Attwell,4,18,3,11,2,7,9,7,1,3,0,0 +19/03/2011,Wigan,Birmingham,2,1,H,1,1,D,L Probert,16,7,13,4,9,4,13,13,3,1,0,0 +20/03/2011,Chelsea,Man City,2,0,H,0,0,D,C Foy,16,5,11,2,5,1,16,13,2,5,0,0 +20/03/2011,Sunderland,Liverpool,0,2,A,0,1,A,K Friend,3,10,1,4,3,6,11,7,3,3,1,0 +02/04/2011,Arsenal,Blackburn,0,0,D,0,0,D,P Dowd,19,7,11,2,11,4,10,9,1,2,0,1 +02/04/2011,Birmingham,Bolton,2,1,H,1,0,H,M Oliver,9,19,6,12,8,9,11,16,0,2,0,0 +02/04/2011,Everton,Aston Villa,2,2,D,1,0,H,M Jones,14,7,7,5,5,7,13,16,3,2,0,0 +02/04/2011,Newcastle,Wolves,4,1,H,2,0,H,M Dean,17,13,13,9,5,4,19,10,5,3,0,0 +02/04/2011,Stoke,Chelsea,1,1,D,1,1,D,P Walton,13,20,6,10,5,12,9,10,1,2,0,0 +02/04/2011,West Brom,Liverpool,2,1,H,0,0,D,M Atkinson,17,13,14,9,0,5,10,15,2,3,0,0 +02/04/2011,West Ham,Man United,2,4,A,2,0,H,L Mason,6,18,2,9,0,16,10,9,1,2,0,0 +02/04/2011,Wigan,Tottenham,0,0,D,0,0,D,A Marriner,14,10,5,6,8,2,11,13,1,1,0,0 +03/04/2011,Fulham,Blackpool,3,0,H,2,0,H,S Attwell,13,10,11,6,4,7,14,5,2,1,0,0 +03/04/2011,Man City,Sunderland,5,0,H,2,0,H,H Webb,14,8,12,4,6,5,9,20,2,3,0,0 +09/04/2011,Blackburn,Birmingham,1,1,D,1,1,D,C Foy,15,6,5,5,8,2,10,10,2,3,0,0 +09/04/2011,Bolton,West Ham,3,0,H,2,0,H,L Probert,23,14,12,8,5,10,6,10,1,0,0,0 +09/04/2011,Chelsea,Wigan,1,0,H,0,0,D,H Webb,21,11,13,7,10,5,11,12,1,2,0,0 +09/04/2011,Man United,Fulham,2,0,H,2,0,H,M Jones,16,12,12,5,8,3,12,8,0,0,0,0 +09/04/2011,Sunderland,West Brom,2,3,A,2,1,H,P Walton,7,12,4,8,5,2,10,11,3,1,0,0 +09/04/2011,Tottenham,Stoke,3,2,H,3,2,H,K Friend,9,10,8,5,10,5,11,16,1,3,0,0 +09/04/2011,Wolves,Everton,0,3,A,0,3,A,P Dowd,11,9,7,6,13,6,13,14,0,1,0,0 +10/04/2011,Aston Villa,Newcastle,1,0,H,1,0,H,S Attwell,13,6,7,4,9,2,8,10,2,2,0,0 +10/04/2011,Blackpool,Arsenal,1,3,A,0,2,A,L Mason,12,16,6,7,6,4,13,9,2,2,0,0 +11/04/2011,Liverpool,Man City,3,0,H,3,0,H,M Halsey,15,13,7,4,6,2,6,3,1,0,0,0 +16/04/2011,Birmingham,Sunderland,2,0,H,1,0,H,P Dowd,10,15,4,7,4,9,7,10,0,0,0,0 +16/04/2011,Blackpool,Wigan,1,3,A,0,2,A,P Walton,14,9,9,5,5,3,13,12,2,2,0,0 +16/04/2011,Everton,Blackburn,2,0,H,0,0,D,K Friend,17,4,7,2,10,1,9,17,2,3,0,0 +16/04/2011,West Brom,Chelsea,1,3,A,1,3,A,L Probert,12,19,6,11,3,5,7,10,1,0,0,0 +16/04/2011,West Ham,Aston Villa,1,2,A,1,1,D,M Halsey,12,21,7,11,5,11,8,3,0,0,0,0 +17/04/2011,Arsenal,Liverpool,1,1,D,0,0,D,A Marriner,12,9,6,5,10,3,13,13,2,4,0,0 +19/04/2011,Newcastle,Man United,0,0,D,0,0,D,L Probert,12,18,5,8,6,7,9,10,2,2,0,0 +20/04/2011,Chelsea,Birmingham,3,1,H,2,0,H,M Jones,17,6,7,3,7,4,9,11,0,1,0,0 +20/04/2011,Tottenham,Arsenal,3,3,D,2,3,A,M Atkinson,16,13,9,9,7,6,8,13,0,3,0,0 +23/04/2011,Aston Villa,Stoke,1,1,D,1,1,D,C Foy,7,4,2,3,7,8,9,9,0,3,0,0 +23/04/2011,Blackpool,Newcastle,1,1,D,1,1,D,M Atkinson,18,9,7,6,4,4,17,8,1,1,0,0 +23/04/2011,Chelsea,West Ham,3,0,H,1,0,H,P Dowd,16,12,11,7,5,7,7,13,1,2,0,0 +23/04/2011,Liverpool,Birmingham,5,0,H,2,0,H,H Webb,17,10,13,6,5,2,7,12,1,0,0,0 +23/04/2011,Man United,Everton,1,0,H,0,0,D,P Walton,21,5,12,3,13,1,10,9,1,2,0,0 +23/04/2011,Sunderland,Wigan,4,2,H,0,0,D,L Probert,11,11,7,7,7,3,11,9,1,2,0,0 +23/04/2011,Tottenham,West Brom,2,2,D,1,1,D,S Attwell,19,10,9,4,11,1,8,9,1,1,0,0 +23/04/2011,Wolves,Fulham,1,1,D,1,0,H,M Oliver,12,15,3,5,3,5,15,17,2,2,0,0 +24/04/2011,Bolton,Arsenal,2,1,H,1,0,H,M Jones,11,17,10,7,3,9,13,14,3,3,0,0 +25/04/2011,Blackburn,Man City,0,1,A,0,0,D,A Marriner,12,10,6,5,4,6,15,15,2,3,0,0 +26/04/2011,Stoke,Wolves,3,0,H,2,0,H,L Mason,17,5,8,1,7,11,12,10,3,2,0,0 +27/04/2011,Fulham,Bolton,3,0,H,1,0,H,A Taylor,18,14,7,6,10,7,12,8,1,1,0,0 +30/04/2011,Blackburn,Bolton,1,0,H,1,0,H,M Dean,17,11,8,3,3,10,7,9,2,1,0,0 +30/04/2011,Blackpool,Stoke,0,0,D,0,0,D,M Clattenburg,11,9,6,6,4,8,10,11,0,0,0,0 +30/04/2011,Chelsea,Tottenham,2,1,H,1,1,D,A Marriner,16,12,10,7,6,5,11,11,3,1,0,0 +30/04/2011,Sunderland,Fulham,0,3,A,0,1,A,M Atkinson,11,9,7,6,5,4,7,12,1,0,0,0 +30/04/2011,West Brom,Aston Villa,2,1,H,0,1,A,P Dowd,8,11,4,6,3,7,12,12,3,1,1,0 +30/04/2011,Wigan,Everton,1,1,D,1,0,H,L Mason,13,3,9,3,3,8,12,12,2,2,0,0 +01/05/2011,Arsenal,Man United,1,0,H,0,0,D,C Foy,9,11,2,7,3,6,15,7,2,3,0,0 +01/05/2011,Birmingham,Wolves,1,1,D,1,1,D,K Friend,8,7,1,2,0,2,10,16,3,3,1,0 +01/05/2011,Liverpool,Newcastle,3,0,H,1,0,H,P Walton,9,7,5,3,3,12,15,10,1,2,0,0 +01/05/2011,Man City,West Ham,2,1,H,2,1,H,H Webb,19,7,12,4,8,5,13,8,3,1,0,0 +07/05/2011,Aston Villa,Wigan,1,1,D,1,1,D,M Jones,10,7,7,4,2,5,6,11,2,0,0,0 +07/05/2011,Bolton,Sunderland,1,2,A,0,1,A,K Friend,17,10,9,6,5,5,14,10,1,0,0,0 +07/05/2011,Everton,Man City,2,1,H,0,1,A,P Dowd,10,12,6,7,11,6,13,15,4,2,0,0 +07/05/2011,Newcastle,Birmingham,2,1,H,2,1,H,C Foy,19,5,14,2,8,2,10,12,1,4,0,1 +07/05/2011,Tottenham,Blackpool,1,1,D,0,0,D,L Probert,20,13,11,7,7,6,5,13,3,2,0,0 +07/05/2011,West Ham,Blackburn,1,1,D,0,1,A,P Walton,24,8,15,4,7,3,7,14,3,0,0,0 +08/05/2011,Man United,Chelsea,2,1,H,2,0,H,H Webb,13,19,8,11,7,2,12,14,2,4,0,0 +08/05/2011,Stoke,Arsenal,3,1,H,2,0,H,M Halsey,12,14,7,5,2,5,7,10,1,2,0,0 +08/05/2011,Wolves,West Brom,3,1,H,2,0,H,M Dean,14,14,10,6,8,4,9,7,3,1,0,0 +09/05/2011,Fulham,Liverpool,2,5,A,0,3,A,L Mason,10,16,7,11,6,5,18,11,5,2,0,0 +10/05/2011,Man City,Tottenham,1,0,H,1,0,H,M Dean,7,14,4,7,2,8,7,8,1,1,0,0 +14/05/2011,Blackburn,Man United,1,1,D,1,0,H,P Dowd,6,9,2,4,2,6,11,11,3,0,0,0 +14/05/2011,Blackpool,Bolton,4,3,H,3,2,H,A Marriner,12,17,8,9,5,6,16,12,2,4,0,0 +14/05/2011,Sunderland,Wolves,1,3,A,1,1,D,M Jones,14,10,9,6,5,5,8,19,0,0,0,0 +14/05/2011,West Brom,Everton,1,0,H,1,0,H,A Taylor,14,23,9,10,2,12,15,9,2,2,0,1 +15/05/2011,Arsenal,Aston Villa,1,2,A,0,2,A,M Oliver,24,8,15,5,13,2,9,10,1,4,0,0 +15/05/2011,Birmingham,Fulham,0,2,A,0,1,A,P Walton,7,18,2,9,5,5,7,15,0,5,0,0 +15/05/2011,Chelsea,Newcastle,2,2,D,1,1,D,L Mason,15,13,7,9,7,4,17,13,4,2,0,0 +15/05/2011,Liverpool,Tottenham,0,2,A,0,1,A,H Webb,10,8,2,4,11,5,13,7,2,1,0,0 +15/05/2011,Wigan,West Ham,3,2,H,0,2,A,M Dean,19,13,10,10,10,6,7,10,1,1,0,0 +17/05/2011,Man City,Stoke,3,0,H,1,0,H,L Probert,16,4,9,2,4,3,6,9,0,3,0,0 +22/05/2011,Aston Villa,Liverpool,1,0,H,1,0,H,L Probert,10,9,8,4,4,5,12,12,2,2,0,0 +22/05/2011,Bolton,Man City,0,2,A,0,1,A,C Foy,16,16,8,10,6,5,11,10,1,1,1,0 +22/05/2011,Everton,Chelsea,1,0,H,0,0,D,P Walton,11,19,3,9,6,5,12,14,3,2,1,0 +22/05/2011,Fulham,Arsenal,2,2,D,1,1,D,M Atkinson,10,12,6,6,5,3,12,5,2,1,1,0 +22/05/2011,Man United,Blackpool,4,2,H,1,1,D,M Dean,18,14,11,7,8,8,8,5,2,1,0,0 +22/05/2011,Newcastle,West Brom,3,3,D,2,0,H,M Halsey,15,13,10,7,7,6,5,8,0,0,0,0 +22/05/2011,Stoke,Wigan,0,1,A,0,0,D,A Marriner,11,11,5,9,5,3,10,9,1,4,0,0 +22/05/2011,Tottenham,Birmingham,2,1,H,0,0,D,M Clattenburg,22,7,16,3,7,5,5,15,0,3,0,0 +22/05/2011,West Ham,Sunderland,0,3,A,0,1,A,A Taylor,17,17,12,12,4,6,7,8,0,2,0,0 +22/05/2011,Wolves,Blackburn,2,3,A,0,3,A,H Webb,12,13,5,10,8,3,12,9,3,1,0,0 +13/08/2011,Blackburn,Wolves,1,2,A,1,1,D,K Friend,16,13,8,4,12,6,14,10,4,2,0,0 +13/08/2011,Fulham,Aston Villa,0,0,D,0,0,D,L Mason,13,7,9,1,2,3,10,18,2,4,0,0 +13/08/2011,Liverpool,Sunderland,1,1,D,1,0,H,P Dowd,11,15,4,6,6,3,17,12,4,4,0,0 +13/08/2011,Newcastle,Arsenal,0,0,D,0,0,D,P Walton,6,9,1,4,2,5,9,11,3,5,0,1 +13/08/2011,QPR,Bolton,0,4,A,0,1,A,M Atkinson,13,13,7,7,3,2,9,16,1,2,1,0 +13/08/2011,Wigan,Norwich,1,1,D,1,1,D,S Attwell,18,13,11,3,7,3,11,8,2,2,0,0 +14/08/2011,Stoke,Chelsea,0,0,D,0,0,D,M Halsey,6,20,3,11,4,6,12,12,2,2,0,0 +14/08/2011,West Brom,Man United,1,2,A,1,1,D,M Jones,14,11,8,5,6,5,16,9,3,2,0,0 +15/08/2011,Man City,Swansea,4,0,H,0,0,D,M Dean,26,8,19,5,7,4,9,2,0,0,0,0 +20/08/2011,Arsenal,Liverpool,0,2,A,0,0,D,M Atkinson,10,13,6,8,9,5,11,6,2,2,1,0 +20/08/2011,Aston Villa,Blackburn,3,1,H,2,0,H,S Attwell,9,10,6,6,5,4,9,13,3,0,0,0 +20/08/2011,Chelsea,West Brom,2,1,H,0,1,A,L Mason,22,6,12,4,7,4,12,8,2,4,0,0 +20/08/2011,Everton,QPR,0,1,A,0,1,A,K Friend,14,4,7,3,2,1,12,9,1,1,0,0 +20/08/2011,Sunderland,Newcastle,0,1,A,0,0,D,H Webb,15,12,5,10,7,6,17,7,3,3,1,0 +20/08/2011,Swansea,Wigan,0,0,D,0,0,D,P Dowd,12,10,6,4,7,7,8,18,0,3,0,0 +21/08/2011,Bolton,Man City,2,3,A,1,2,A,M Jones,6,17,4,9,7,8,19,14,2,0,0,0 +21/08/2011,Norwich,Stoke,1,1,D,1,0,H,N Swarbrick,9,15,7,5,3,6,13,12,0,2,1,0 +21/08/2011,Wolves,Fulham,2,0,H,2,0,H,M Dean,17,8,7,4,7,8,7,10,2,2,0,0 +22/08/2011,Man United,Tottenham,3,0,H,0,0,D,L Probert,27,20,21,12,11,3,12,9,1,2,0,0 +27/08/2011,Aston Villa,Wolves,0,0,D,0,0,D,M Atkinson,15,3,5,2,11,8,10,9,1,3,0,0 +27/08/2011,Blackburn,Everton,0,1,A,0,0,D,L Mason,14,9,6,4,5,4,17,13,5,2,0,0 +27/08/2011,Chelsea,Norwich,3,1,H,1,0,H,M Jones,16,9,8,6,8,6,17,14,2,2,0,1 +27/08/2011,Liverpool,Bolton,3,1,H,1,0,H,L Probert,17,7,9,4,12,6,5,7,0,2,0,0 +27/08/2011,Swansea,Sunderland,0,0,D,0,0,D,M Halsey,15,12,6,8,7,4,7,10,1,1,0,0 +27/08/2011,Wigan,QPR,2,0,H,1,0,H,M Oliver,13,19,10,9,8,7,13,8,2,0,0,0 +28/08/2011,Man United,Arsenal,8,2,H,3,1,H,H Webb,25,19,15,13,3,5,9,7,2,3,0,1 +28/08/2011,Newcastle,Fulham,2,1,H,0,0,D,K Friend,13,16,9,9,6,6,19,8,2,1,0,0 +28/08/2011,Tottenham,Man City,1,5,A,0,2,A,P Dowd,15,22,10,12,10,5,11,9,2,4,0,0 +28/08/2011,West Brom,Stoke,0,1,A,0,0,D,M Dean,16,7,7,1,5,1,9,12,0,0,0,0 +10/09/2011,Arsenal,Swansea,1,0,H,1,0,H,S Attwell,18,10,8,5,4,5,10,7,3,2,0,0 +10/09/2011,Bolton,Man United,0,5,A,0,3,A,A Marriner,20,13,13,9,5,7,14,1,2,0,0,0 +10/09/2011,Everton,Aston Villa,2,2,D,1,0,H,M Oliver,17,7,10,3,8,0,13,11,1,1,0,0 +10/09/2011,Man City,Wigan,3,0,H,1,0,H,M Atkinson,23,9,10,5,8,3,6,9,0,0,0,0 +10/09/2011,Stoke,Liverpool,1,0,H,1,0,H,M Clattenburg,3,20,1,11,2,12,12,13,0,1,0,0 +10/09/2011,Sunderland,Chelsea,1,2,A,0,1,A,L Probert,14,16,7,8,4,7,7,3,1,1,0,0 +10/09/2011,Wolves,Tottenham,0,2,A,0,0,D,P Walton,10,17,6,11,4,10,5,12,3,2,0,0 +11/09/2011,Fulham,Blackburn,1,1,D,1,1,D,H Webb,13,10,8,5,11,2,7,12,3,1,0,0 +11/09/2011,Norwich,West Brom,0,1,A,0,1,A,M Halsey,17,14,5,9,1,3,10,11,1,2,0,0 +12/09/2011,QPR,Newcastle,0,0,D,0,0,D,P Dowd,14,8,5,3,6,4,10,15,1,3,0,0 +17/09/2011,Aston Villa,Newcastle,1,1,D,1,0,H,N Swarbrick,10,21,2,13,4,9,13,10,1,1,0,0 +17/09/2011,Blackburn,Arsenal,4,3,H,1,2,A,A Marriner,10,24,5,16,2,13,14,9,2,1,0,0 +17/09/2011,Bolton,Norwich,1,2,A,0,2,A,H Webb,7,11,7,5,6,9,8,8,3,1,1,0 +17/09/2011,Everton,Wigan,3,1,H,1,1,D,P Walton,17,9,10,4,4,9,14,8,2,1,0,0 +17/09/2011,Swansea,West Brom,3,0,H,2,0,H,M Atkinson,7,13,5,8,2,8,4,9,1,2,0,0 +17/09/2011,Wolves,QPR,0,3,A,0,2,A,A Taylor,13,17,6,12,4,8,15,13,2,3,0,0 +18/09/2011,Fulham,Man City,2,2,D,0,1,A,M Clattenburg,14,18,11,11,6,9,10,9,1,1,0,0 +18/09/2011,Man United,Chelsea,3,1,H,3,0,H,P Dowd,12,20,7,9,4,10,11,12,2,3,0,0 +18/09/2011,Sunderland,Stoke,4,0,H,3,0,H,K Friend,8,9,7,5,6,5,15,12,1,2,0,0 +18/09/2011,Tottenham,Liverpool,4,0,H,1,0,H,M Jones,24,5,16,2,4,1,10,9,1,4,0,2 +24/09/2011,Arsenal,Bolton,3,0,H,0,0,D,M Clattenburg,23,3,14,3,9,2,7,9,0,1,0,1 +24/09/2011,Chelsea,Swansea,4,1,H,2,0,H,M Dean,15,5,7,4,1,6,15,11,3,3,1,0 +24/09/2011,Liverpool,Wolves,2,1,H,2,0,H,K Friend,14,11,6,6,7,2,15,6,3,4,0,0 +24/09/2011,Man City,Everton,2,0,H,0,0,D,H Webb,18,7,8,3,7,5,9,13,1,5,0,0 +24/09/2011,Newcastle,Blackburn,3,1,H,2,1,H,M Atkinson,19,4,8,1,4,2,6,16,0,4,0,1 +24/09/2011,Stoke,Man United,1,1,D,0,1,A,P Walton,14,13,8,9,8,8,10,3,1,0,0,0 +24/09/2011,West Brom,Fulham,0,0,D,0,0,D,S Attwell,8,18,5,9,1,1,6,14,0,3,0,0 +24/09/2011,Wigan,Tottenham,1,2,A,0,2,A,J Moss,5,14,4,7,1,9,13,6,3,1,1,0 +25/09/2011,QPR,Aston Villa,1,1,D,0,0,D,M Oliver,17,8,9,4,11,8,14,19,1,6,1,0 +26/09/2011,Norwich,Sunderland,2,1,H,1,0,H,C Foy,11,12,8,6,7,4,3,7,2,1,0,0 +01/10/2011,Aston Villa,Wigan,2,0,H,1,0,H,M Clattenburg,16,14,10,9,5,10,12,6,0,1,0,0 +01/10/2011,Blackburn,Man City,0,4,A,0,0,D,P Dowd,5,19,4,12,4,5,9,14,1,1,0,0 +01/10/2011,Everton,Liverpool,0,2,A,0,0,D,M Atkinson,12,15,4,9,5,9,15,9,1,1,1,0 +01/10/2011,Man United,Norwich,2,0,H,0,0,D,S Attwell,17,13,9,6,10,3,9,6,1,0,0,0 +01/10/2011,Sunderland,West Brom,2,2,D,2,2,D,L Mason,12,7,7,3,7,4,9,13,2,0,0,0 +01/10/2011,Wolves,Newcastle,1,2,A,0,2,A,M Halsey,25,10,12,6,10,5,7,10,1,2,0,0 +02/10/2011,Bolton,Chelsea,1,5,A,0,4,A,P Walton,14,21,10,14,4,5,10,11,1,2,0,0 +02/10/2011,Fulham,QPR,6,0,H,3,0,H,A Marriner,19,17,13,10,4,4,10,7,2,3,0,0 +02/10/2011,Swansea,Stoke,2,0,H,1,0,H,M Jones,9,11,5,6,6,5,14,18,3,5,0,0 +02/10/2011,Tottenham,Arsenal,2,1,H,1,0,H,M Dean,16,13,8,7,9,7,11,8,2,1,0,0 +15/10/2011,Chelsea,Everton,3,1,H,2,0,H,M Jones,7,9,5,5,2,2,13,11,2,2,0,0 +15/10/2011,Liverpool,Man United,1,1,D,0,0,D,A Marriner,14,10,8,3,8,3,8,11,1,3,0,0 +15/10/2011,Man City,Aston Villa,4,1,H,1,0,H,A Taylor,15,8,10,6,5,1,10,6,1,1,0,0 +15/10/2011,Norwich,Swansea,3,1,H,2,1,H,J Moss,15,12,8,4,4,4,6,5,0,1,0,0 +15/10/2011,QPR,Blackburn,1,1,D,1,1,D,M Clattenburg,12,10,6,9,5,7,10,13,3,2,0,0 +15/10/2011,Stoke,Fulham,2,0,H,0,0,D,M Atkinson,14,4,6,1,5,2,14,15,4,4,0,0 +15/10/2011,Wigan,Bolton,1,3,A,1,2,A,M Dean,13,14,9,11,5,4,16,14,4,4,0,0 +16/10/2011,Arsenal,Sunderland,2,1,H,1,1,D,H Webb,20,8,11,4,9,2,9,19,3,3,0,0 +16/10/2011,Newcastle,Tottenham,2,2,D,0,1,A,L Probert,12,11,7,8,5,2,11,15,1,2,0,0 +16/10/2011,West Brom,Wolves,2,0,H,1,0,H,C Foy,17,13,11,10,2,11,14,12,0,1,0,0 +22/10/2011,Aston Villa,West Brom,1,2,A,1,1,D,P Dowd,3,8,0,5,3,8,11,8,2,3,1,0 +22/10/2011,Bolton,Sunderland,0,2,A,0,0,D,M Jones,15,18,8,10,2,11,15,14,0,3,0,0 +22/10/2011,Liverpool,Norwich,1,1,D,1,0,H,P Walton,25,12,15,10,10,3,6,8,0,1,0,0 +22/10/2011,Newcastle,Wigan,1,0,H,0,0,D,N Swarbrick,13,12,10,5,7,3,7,22,3,2,0,0 +22/10/2011,Wolves,Swansea,2,2,D,0,2,A,M Oliver,20,16,13,14,8,13,14,11,2,0,0,0 +23/10/2011,Arsenal,Stoke,3,1,H,1,1,D,L Mason,9,5,5,2,10,1,9,13,0,2,0,0 +23/10/2011,Blackburn,Tottenham,1,2,A,1,1,D,S Attwell,19,9,8,7,8,5,19,7,4,1,0,0 +23/10/2011,Fulham,Everton,1,3,A,0,1,A,L Probert,15,11,8,7,5,1,12,8,3,1,0,0 +23/10/2011,Man United,Man City,1,6,A,0,1,A,M Clattenburg,12,19,8,12,7,4,15,11,4,4,1,0 +23/10/2011,QPR,Chelsea,1,0,H,1,0,H,C Foy,8,14,3,7,0,6,17,17,2,7,0,2 +29/10/2011,Chelsea,Arsenal,3,5,A,2,1,H,A Marriner,12,13,10,8,3,2,7,18,2,3,0,0 +29/10/2011,Everton,Man United,0,1,A,0,1,A,M Halsey,18,7,11,6,4,1,12,8,3,0,0,0 +29/10/2011,Man City,Wolves,3,1,H,0,0,D,S Attwell,18,9,9,2,5,4,17,5,2,1,1,0 +29/10/2011,Norwich,Blackburn,3,3,D,0,1,A,A Taylor,13,12,6,7,6,2,9,10,2,1,0,0 +29/10/2011,Sunderland,Aston Villa,2,2,D,1,1,D,C Foy,13,10,8,5,5,6,7,13,1,3,0,0 +29/10/2011,Swansea,Bolton,3,1,H,0,0,D,M Clattenburg,20,8,12,3,10,2,9,14,3,4,0,1 +29/10/2011,West Brom,Liverpool,0,2,A,0,2,A,L Mason,10,17,6,7,3,10,12,9,1,0,0,0 +29/10/2011,Wigan,Fulham,0,2,A,0,1,A,M Jones,18,7,9,5,9,0,17,14,2,5,0,0 +30/10/2011,Tottenham,QPR,3,1,H,2,0,H,H Webb,22,12,13,8,4,8,12,14,0,0,0,0 +31/10/2011,Stoke,Newcastle,1,3,A,0,2,A,M Dean,10,6,4,2,8,2,8,14,0,1,0,0 +05/11/2011,Arsenal,West Brom,3,0,H,2,0,H,M Oliver,12,9,7,4,4,1,12,12,0,0,0,0 +05/11/2011,Aston Villa,Norwich,3,2,H,1,1,D,K Friend,12,9,6,7,6,6,11,9,1,5,0,0 +05/11/2011,Blackburn,Chelsea,0,1,A,0,0,D,M Dean,13,12,8,8,5,8,12,10,4,2,0,0 +05/11/2011,Liverpool,Swansea,0,0,D,0,0,D,P Dowd,18,9,10,3,11,4,13,5,2,1,0,0 +05/11/2011,Man United,Sunderland,1,0,H,1,0,H,L Mason,15,9,9,3,6,2,6,14,0,2,0,0 +05/11/2011,Newcastle,Everton,2,1,H,2,1,H,A Marriner,10,14,6,6,5,3,12,15,2,3,0,0 +05/11/2011,QPR,Man City,2,3,A,1,1,D,M Atkinson,18,16,9,8,3,1,5,14,1,1,0,0 +06/11/2011,Bolton,Stoke,5,0,H,2,0,H,H Webb,20,9,16,6,12,4,10,9,0,1,0,0 +06/11/2011,Fulham,Tottenham,1,3,A,0,2,A,P Walton,26,8,20,8,11,1,5,9,0,1,0,0 +06/11/2011,Wolves,Wigan,3,1,H,1,1,D,L Probert,13,13,12,7,5,6,11,13,2,1,0,0 +19/11/2011,Everton,Wolves,2,1,H,1,1,D,J Moss,13,4,9,1,9,1,14,13,1,2,0,0 +19/11/2011,Man City,Newcastle,3,1,H,2,0,H,C Foy,12,10,9,7,4,5,10,7,0,0,0,0 +19/11/2011,Norwich,Arsenal,1,2,A,1,1,D,P Dowd,7,20,4,14,1,6,11,9,3,0,0,0 +19/11/2011,Stoke,QPR,2,3,A,1,2,A,M Jones,17,9,7,8,9,5,17,10,4,2,0,0 +19/11/2011,Sunderland,Fulham,0,0,D,0,0,D,A Taylor,13,13,8,9,6,7,8,12,1,1,0,0 +19/11/2011,Swansea,Man United,0,1,A,0,1,A,M Dean,9,13,6,7,6,2,6,9,0,1,0,0 +19/11/2011,West Brom,Bolton,2,1,H,1,1,D,N Swarbrick,16,8,9,6,7,5,12,12,1,0,0,0 +19/11/2011,Wigan,Blackburn,3,3,D,2,1,H,A Marriner,21,6,11,2,7,3,14,13,2,3,0,1 +20/11/2011,Chelsea,Liverpool,1,2,A,0,1,A,L Probert,18,9,5,3,6,5,13,11,3,2,0,0 +21/11/2011,Tottenham,Aston Villa,2,0,H,2,0,H,M Halsey,20,5,9,1,11,3,4,5,0,2,0,0 +26/11/2011,Arsenal,Fulham,1,1,D,0,0,D,M Dean,20,10,12,6,13,4,9,7,1,3,0,0 +26/11/2011,Bolton,Everton,0,2,A,0,0,D,M Oliver,8,17,7,14,2,4,9,10,1,1,1,0 +26/11/2011,Chelsea,Wolves,3,0,H,3,0,H,L Mason,17,7,11,2,9,2,10,10,1,2,0,0 +26/11/2011,Man United,Newcastle,1,1,D,0,0,D,M Jones,23,8,13,6,11,3,12,15,1,4,0,1 +26/11/2011,Norwich,QPR,2,1,H,1,0,H,M Clattenburg,15,13,8,4,6,4,15,7,1,0,0,0 +26/11/2011,Stoke,Blackburn,3,1,H,1,0,H,M Halsey,8,16,5,12,2,6,10,5,0,0,0,0 +26/11/2011,Sunderland,Wigan,1,2,A,1,1,D,K Friend,17,7,11,4,3,2,12,13,3,1,0,0 +26/11/2011,West Brom,Tottenham,1,3,A,1,1,D,L Probert,9,23,5,14,1,10,11,13,2,1,0,0 +27/11/2011,Liverpool,Man City,1,1,D,1,1,D,M Atkinson,17,7,9,3,8,5,11,13,1,4,0,1 +27/11/2011,Swansea,Aston Villa,0,0,D,0,0,D,N Swarbrick,6,9,2,4,4,6,9,17,3,4,0,0 +03/12/2011,Aston Villa,Man United,0,1,A,0,1,A,L Probert,7,11,3,5,5,5,4,11,0,0,0,0 +03/12/2011,Blackburn,Swansea,4,2,H,2,1,H,C Foy,14,10,9,5,3,4,12,13,1,1,0,1 +03/12/2011,Man City,Norwich,5,1,H,1,0,H,H Webb,21,5,16,2,12,4,8,7,0,1,0,0 +03/12/2011,Newcastle,Chelsea,0,3,A,0,1,A,M Dean,16,21,9,13,6,5,7,6,2,3,0,0 +03/12/2011,QPR,West Brom,1,1,D,1,0,H,M Atkinson,14,15,4,10,3,5,7,9,0,3,0,0 +03/12/2011,Tottenham,Bolton,3,0,H,1,0,H,S Attwell,25,13,16,9,7,6,10,5,0,2,0,1 +03/12/2011,Wigan,Arsenal,0,4,A,0,2,A,M Clattenburg,7,16,4,10,4,5,8,12,3,2,0,0 +04/12/2011,Everton,Stoke,0,1,A,0,1,A,L Mason,11,7,4,4,10,6,14,13,0,1,0,0 +04/12/2011,Wolves,Sunderland,2,1,H,0,0,D,P Dowd,15,18,10,12,8,3,10,12,2,3,0,0 +05/12/2011,Fulham,Liverpool,1,0,H,0,0,D,K Friend,19,17,13,9,2,10,12,9,3,1,0,1 +10/12/2011,Arsenal,Everton,1,0,H,0,0,D,H Webb,12,7,7,1,7,4,7,14,2,2,0,0 +10/12/2011,Bolton,Aston Villa,1,2,A,0,2,A,P Dowd,17,17,12,11,6,10,10,7,1,2,0,0 +10/12/2011,Liverpool,QPR,1,0,H,0,0,D,L Mason,21,6,15,2,17,5,12,5,2,0,0,0 +10/12/2011,Man United,Wolves,4,1,H,2,0,H,M Oliver,22,11,18,8,8,2,7,8,1,0,0,0 +10/12/2011,Norwich,Newcastle,4,2,H,1,1,D,M Atkinson,13,11,11,8,7,2,9,8,3,2,0,1 +10/12/2011,Swansea,Fulham,2,0,H,0,0,D,J Moss,16,12,8,7,6,5,8,7,1,1,0,0 +10/12/2011,West Brom,Wigan,1,2,A,1,1,D,M Dean,13,7,8,4,13,2,9,7,1,1,0,0 +11/12/2011,Stoke,Tottenham,2,1,H,2,0,H,C Foy,10,15,7,12,7,12,8,7,2,2,0,1 +11/12/2011,Sunderland,Blackburn,2,1,H,0,1,A,P Walton,22,3,12,2,9,0,11,7,2,2,0,0 +12/12/2011,Chelsea,Man City,2,1,H,1,1,D,M Clattenburg,13,7,7,4,5,1,11,18,4,2,0,1 +17/12/2011,Blackburn,West Brom,1,2,A,0,0,D,M Clattenburg,17,13,14,6,3,7,7,10,1,1,0,0 +17/12/2011,Everton,Norwich,1,1,D,0,1,A,L Probert,28,7,15,1,13,0,9,12,1,1,0,0 +17/12/2011,Fulham,Bolton,2,0,H,2,0,H,C Foy,16,14,9,6,8,4,6,11,1,3,0,0 +17/12/2011,Newcastle,Swansea,0,0,D,0,0,D,L Mason,17,3,6,0,9,1,11,11,1,1,0,0 +17/12/2011,Wigan,Chelsea,1,1,D,0,0,D,M Atkinson,15,10,7,5,7,5,8,6,0,0,0,0 +17/12/2011,Wolves,Stoke,1,2,A,1,0,H,A Taylor,10,8,5,4,7,5,8,11,2,4,0,0 +18/12/2011,Aston Villa,Liverpool,0,2,A,0,2,A,P Walton,16,19,8,12,4,10,5,14,1,2,0,0 +18/12/2011,Man City,Arsenal,1,0,H,0,0,D,P Dowd,15,12,9,8,4,6,13,10,2,3,0,0 +18/12/2011,QPR,Man United,0,2,A,0,1,A,H Webb,11,19,3,12,5,10,5,7,1,2,0,0 +18/12/2011,Tottenham,Sunderland,1,0,H,0,0,D,M Dean,16,8,12,4,6,0,6,8,1,2,0,0 +20/12/2011,Blackburn,Bolton,1,2,A,0,2,A,M Atkinson,17,8,7,5,5,9,10,7,3,0,0,0 +20/12/2011,Wolves,Norwich,2,2,D,1,1,D,C Foy,18,18,9,14,4,6,5,11,2,2,0,0 +21/12/2011,Aston Villa,Arsenal,1,2,A,0,1,A,J Moss,10,8,4,6,7,15,10,8,2,3,1,0 +21/12/2011,Everton,Swansea,1,0,H,0,0,D,K Friend,13,4,6,4,9,2,8,13,1,2,0,0 +21/12/2011,Fulham,Man United,0,5,A,0,3,A,M Halsey,13,14,8,12,3,10,7,8,1,0,0,0 +21/12/2011,Man City,Stoke,3,0,H,2,0,H,M Dean,20,2,16,0,5,2,7,5,2,1,0,0 +21/12/2011,Newcastle,West Brom,2,3,A,1,2,A,A Taylor,21,14,11,8,10,1,9,10,1,0,0,0 +21/12/2011,QPR,Sunderland,2,3,A,0,1,A,A Marriner,17,17,9,13,8,11,10,11,2,2,0,0 +21/12/2011,Wigan,Liverpool,0,0,D,0,0,D,M Oliver,15,18,10,10,7,14,16,12,1,2,0,0 +22/12/2011,Tottenham,Chelsea,1,1,D,1,1,D,H Webb,16,20,10,10,7,4,14,8,2,2,0,0 +26/12/2011,Bolton,Newcastle,0,2,A,0,0,D,P Walton,4,12,3,7,4,4,9,11,1,1,0,0 +26/12/2011,Chelsea,Fulham,1,1,D,0,0,D,K Friend,24,9,15,6,9,3,10,12,1,1,0,0 +26/12/2011,Liverpool,Blackburn,1,1,D,0,1,A,M Jones,28,5,13,2,9,2,5,15,1,0,0,0 +26/12/2011,Man United,Wigan,5,0,H,2,0,H,P Dowd,17,9,9,5,10,2,12,10,2,1,0,1 +26/12/2011,Stoke,Aston Villa,0,0,D,0,0,D,M Clattenburg,7,10,2,4,7,7,13,10,3,0,0,0 +26/12/2011,Sunderland,Everton,1,1,D,1,0,H,H Webb,12,9,8,3,5,6,6,14,0,1,0,0 +26/12/2011,West Brom,Man City,0,0,D,0,0,D,L Mason,7,18,3,9,5,9,8,8,0,1,0,0 +27/12/2011,Arsenal,Wolves,1,1,D,1,1,D,S Attwell,23,6,17,4,13,3,3,12,2,3,0,1 +27/12/2011,Norwich,Tottenham,0,2,A,0,0,D,M Oliver,8,19,4,8,3,10,7,6,1,1,0,0 +27/12/2011,Swansea,QPR,1,1,D,1,0,H,L Probert,7,12,4,7,5,11,9,12,1,3,0,0 +30/12/2011,Liverpool,Newcastle,3,1,H,1,1,D,L Probert,15,5,7,2,7,0,7,11,0,2,0,0 +31/12/2011,Arsenal,QPR,1,0,H,0,0,D,M Atkinson,16,6,7,3,12,4,11,13,2,2,0,0 +31/12/2011,Bolton,Wolves,1,1,D,1,0,H,M Clattenburg,13,8,9,5,3,6,11,8,0,0,0,0 +31/12/2011,Chelsea,Aston Villa,1,3,A,1,1,D,M Halsey,16,12,5,9,9,2,7,13,1,1,0,0 +31/12/2011,Man United,Blackburn,2,3,A,0,1,A,M Dean,26,10,13,5,17,3,8,10,2,2,0,0 +31/12/2011,Norwich,Fulham,1,1,D,0,1,A,H Webb,18,11,9,6,4,3,9,6,0,0,0,0 +31/12/2011,Stoke,Wigan,2,2,D,0,1,A,M Oliver,12,10,3,6,8,5,14,5,2,3,0,1 +31/12/2011,Swansea,Tottenham,1,1,D,0,1,A,P Dowd,14,12,8,7,10,5,8,4,1,2,0,0 +01/01/2012,Sunderland,Man City,1,0,H,0,0,D,K Friend,8,25,5,11,1,10,7,6,1,1,0,0 +01/01/2012,West Brom,Everton,0,1,A,0,0,D,S Attwell,10,13,2,8,2,7,10,8,0,2,0,0 +02/01/2012,Aston Villa,Swansea,0,2,A,0,1,A,A Taylor,10,8,2,4,14,0,11,6,2,4,0,0 +02/01/2012,Blackburn,Stoke,1,2,A,0,2,A,L Mason,14,8,7,3,8,3,8,7,2,1,0,0 +02/01/2012,Fulham,Arsenal,2,1,H,0,1,A,L Probert,21,17,8,10,4,3,6,12,0,1,0,1 +02/01/2012,QPR,Norwich,1,2,A,1,1,D,N Swarbrick,9,13,6,6,2,5,12,12,2,1,1,0 +02/01/2012,Wolves,Chelsea,1,2,A,0,0,D,P Walton,9,15,5,10,0,5,9,10,3,2,0,0 +03/01/2012,Man City,Liverpool,3,0,H,2,0,H,M Jones,10,18,8,10,5,6,13,7,1,2,1,0 +03/01/2012,Tottenham,West Brom,1,0,H,0,0,D,M Halsey,20,8,14,2,14,7,3,14,0,5,0,0 +03/01/2012,Wigan,Sunderland,1,4,A,0,1,A,M Dean,20,10,10,9,9,6,9,6,1,0,0,0 +04/01/2012,Everton,Bolton,1,2,A,0,0,D,P Dowd,10,12,4,7,5,10,7,11,1,3,0,0 +04/01/2012,Newcastle,Man United,3,0,H,1,0,H,H Webb,10,6,6,6,4,4,16,15,1,2,0,0 +11/01/2012,Tottenham,Everton,2,0,H,1,0,H,M Atkinson,14,11,7,4,6,4,5,15,1,3,0,0 +14/01/2012,Aston Villa,Everton,1,1,D,0,0,D,M Clattenburg,17,7,11,4,9,3,12,11,1,1,0,0 +14/01/2012,Blackburn,Fulham,3,1,H,1,0,H,A Taylor,15,17,11,7,0,9,14,11,1,1,1,0 +14/01/2012,Chelsea,Sunderland,1,0,H,1,0,H,P Dowd,21,9,10,4,8,2,9,9,3,1,0,0 +14/01/2012,Liverpool,Stoke,0,0,D,0,0,D,H Webb,13,3,5,1,11,1,6,11,0,3,0,0 +14/01/2012,Man United,Bolton,3,0,H,1,0,H,P Walton,19,11,9,5,11,3,7,7,0,1,0,0 +14/01/2012,Tottenham,Wolves,1,1,D,0,1,A,M Jones,22,5,10,3,8,1,6,12,1,3,0,0 +14/01/2012,West Brom,Norwich,1,2,A,0,1,A,M Dean,16,7,8,4,10,5,6,14,0,1,0,0 +15/01/2012,Newcastle,QPR,1,0,H,1,0,H,C Foy,9,15,4,9,3,2,5,9,0,4,0,0 +15/01/2012,Swansea,Arsenal,3,2,H,1,1,D,M Oliver,9,14,5,9,9,2,9,9,0,0,0,0 +16/01/2012,Wigan,Man City,0,1,A,0,1,A,M Atkinson,7,12,4,8,4,5,16,15,3,3,0,0 +21/01/2012,Bolton,Liverpool,3,1,H,2,1,H,K Friend,15,11,8,8,5,5,10,13,0,2,0,0 +21/01/2012,Everton,Blackburn,1,1,D,1,0,H,M Halsey,15,14,8,8,5,8,15,13,2,2,0,0 +21/01/2012,Fulham,Newcastle,5,2,H,0,1,A,L Mason,10,15,7,9,2,6,8,12,0,4,0,0 +21/01/2012,Norwich,Chelsea,0,0,D,0,0,D,M Clattenburg,5,19,3,9,1,11,5,4,0,0,0,0 +21/01/2012,QPR,Wigan,3,1,H,2,0,H,J Moss,16,9,8,4,7,1,10,15,2,2,0,0 +21/01/2012,Stoke,West Brom,1,2,A,0,1,A,A Taylor,10,15,3,6,5,1,10,10,0,0,0,0 +21/01/2012,Sunderland,Swansea,2,0,H,1,0,H,C Foy,7,11,2,4,2,4,9,6,0,0,0,0 +21/01/2012,Wolves,Aston Villa,2,3,A,2,1,H,M Oliver,13,11,9,5,12,3,11,8,1,0,1,0 +22/01/2012,Arsenal,Man United,1,2,A,0,1,A,M Dean,12,9,6,5,0,5,15,6,5,2,0,0 +22/01/2012,Man City,Tottenham,3,2,H,0,0,D,H Webb,13,12,7,7,9,4,7,10,2,1,0,0 +31/01/2012,Everton,Man City,1,0,H,0,0,D,P Walton,9,17,4,11,3,9,12,9,1,2,0,0 +31/01/2012,Man United,Stoke,2,0,H,1,0,H,M Jones,11,8,7,3,9,2,9,14,1,3,0,0 +31/01/2012,Swansea,Chelsea,1,1,D,1,0,H,A Marriner,7,20,5,10,1,11,10,19,2,3,0,1 +31/01/2012,Tottenham,Wigan,3,1,H,2,0,H,L Probert,20,10,13,6,5,0,7,7,0,1,0,0 +31/01/2012,Wolves,Liverpool,0,3,A,0,0,D,A Taylor,5,23,1,14,5,4,6,8,1,3,0,0 +01/02/2012,Aston Villa,QPR,2,2,D,1,2,A,N Swarbrick,23,3,15,3,9,3,3,12,0,1,0,0 +01/02/2012,Blackburn,Newcastle,0,2,A,0,1,A,P Dowd,23,9,12,5,14,2,9,17,1,2,0,0 +01/02/2012,Bolton,Arsenal,0,0,D,0,0,D,C Foy,14,16,8,7,6,6,8,10,1,1,0,0 +01/02/2012,Fulham,West Brom,1,1,D,0,0,D,M Halsey,13,15,6,11,8,7,6,7,1,0,0,0 +01/02/2012,Sunderland,Norwich,3,0,H,2,0,H,L Mason,10,12,6,4,3,6,6,14,1,1,0,0 +04/02/2012,Arsenal,Blackburn,7,1,H,3,1,H,A Marriner,20,3,11,3,11,1,9,14,2,3,0,1 +04/02/2012,Man City,Fulham,3,0,H,2,0,H,M Dean,16,11,10,8,4,6,8,7,1,3,0,0 +04/02/2012,Norwich,Bolton,2,0,H,0,0,D,L Probert,22,10,9,6,4,7,6,6,0,0,0,0 +04/02/2012,QPR,Wolves,1,2,A,1,0,H,M Clattenburg,13,14,8,6,7,3,7,14,0,3,1,0 +04/02/2012,Stoke,Sunderland,0,1,A,0,0,D,M Atkinson,9,4,6,2,5,5,12,6,0,1,1,0 +04/02/2012,West Brom,Swansea,1,2,A,0,0,D,J Moss,18,15,11,8,8,7,11,6,2,1,0,0 +04/02/2012,Wigan,Everton,1,1,D,0,0,D,A Taylor,2,8,1,4,4,3,12,8,2,0,0,0 +05/02/2012,Chelsea,Man United,3,3,D,1,0,H,H Webb,19,14,10,10,6,6,14,13,2,1,0,0 +05/02/2012,Newcastle,Aston Villa,2,1,H,1,1,D,M Halsey,16,13,8,8,4,4,6,11,1,2,0,0 +06/02/2012,Liverpool,Tottenham,0,0,D,0,0,D,M Oliver,15,9,10,6,7,4,6,11,2,2,0,0 +11/02/2012,Blackburn,QPR,3,2,H,3,0,H,M Dean,7,25,5,13,5,6,11,10,1,1,0,0 +11/02/2012,Bolton,Wigan,1,2,A,0,1,A,H Webb,13,10,8,6,7,7,19,10,3,2,0,0 +11/02/2012,Everton,Chelsea,2,0,H,1,0,H,M Jones,10,8,7,7,2,4,13,12,0,3,0,0 +11/02/2012,Fulham,Stoke,2,1,H,2,0,H,C Foy,11,12,4,4,5,4,12,6,1,2,0,0 +11/02/2012,Man United,Liverpool,2,1,H,0,0,D,P Dowd,11,9,6,6,4,3,10,11,1,1,0,0 +11/02/2012,Sunderland,Arsenal,1,2,A,0,0,D,N Swarbrick,8,10,5,8,7,3,7,7,2,1,0,0 +11/02/2012,Swansea,Norwich,2,3,A,1,0,H,M Atkinson,16,18,9,11,6,8,6,12,0,2,0,0 +11/02/2012,Tottenham,Newcastle,5,0,H,4,0,H,A Marriner,18,7,9,3,4,3,7,9,2,1,0,0 +12/02/2012,Aston Villa,Man City,0,1,A,0,0,D,M Oliver,9,17,5,10,5,8,15,5,3,0,0,0 +12/02/2012,Wolves,West Brom,1,5,A,1,1,D,L Mason,10,26,8,16,12,8,6,11,1,0,0,0 +25/02/2012,Chelsea,Bolton,3,0,H,0,0,D,M Oliver,25,8,16,1,14,7,7,13,0,1,0,0 +25/02/2012,Man City,Blackburn,3,0,H,1,0,H,M Jones,20,2,13,2,10,3,6,10,0,2,0,0 +25/02/2012,Newcastle,Wolves,2,2,D,2,0,H,P Walton,13,7,8,6,3,3,8,8,1,1,0,0 +25/02/2012,QPR,Fulham,0,1,A,0,1,A,P Dowd,17,7,8,3,6,5,17,12,1,4,1,0 +25/02/2012,West Brom,Sunderland,4,0,H,2,0,H,C Foy,18,10,13,4,7,0,6,8,1,1,0,0 +25/02/2012,Wigan,Aston Villa,0,0,D,0,0,D,K Friend,14,10,6,3,14,7,8,8,0,4,0,0 +26/02/2012,Arsenal,Tottenham,5,2,H,2,2,D,M Dean,21,8,13,4,7,5,15,12,3,3,0,1 +26/02/2012,Norwich,Man United,1,2,A,0,1,A,A Marriner,16,11,7,9,4,4,9,10,1,2,0,0 +26/02/2012,Stoke,Swansea,2,0,H,2,0,H,H Webb,9,11,6,6,8,4,13,7,1,0,0,0 +03/03/2012,Blackburn,Aston Villa,1,1,D,0,1,A,H Webb,15,17,9,12,4,8,9,5,1,0,0,0 +03/03/2012,Liverpool,Arsenal,1,2,A,1,1,D,M Halsey,12,10,4,7,12,0,11,9,0,1,0,0 +03/03/2012,Man City,Bolton,2,0,H,1,0,H,M Clattenburg,23,8,10,5,7,4,13,8,0,0,0,0 +03/03/2012,QPR,Everton,1,1,D,1,1,D,K Friend,22,8,10,5,7,7,16,15,1,2,0,0 +03/03/2012,Stoke,Norwich,1,0,H,0,0,D,M Oliver,11,7,7,5,8,2,10,16,0,2,0,0 +03/03/2012,West Brom,Chelsea,1,0,H,0,0,D,P Dowd,19,13,11,6,14,5,9,10,3,1,0,0 +03/03/2012,Wigan,Swansea,0,2,A,0,1,A,A Marriner,14,12,8,8,8,4,15,7,2,2,0,1 +04/03/2012,Fulham,Wolves,5,0,H,2,0,H,M Jones,25,8,19,3,7,7,7,9,1,2,0,0 +04/03/2012,Newcastle,Sunderland,1,1,D,0,1,A,M Dean,24,7,12,6,19,4,14,14,5,3,0,1 +04/03/2012,Tottenham,Man United,1,3,A,0,1,A,M Atkinson,18,6,9,5,7,3,8,10,1,2,0,0 +10/03/2012,Aston Villa,Fulham,1,0,H,0,0,D,J Moss,19,5,8,1,10,4,9,11,2,0,0,0 +10/03/2012,Bolton,QPR,2,1,H,1,0,H,M Atkinson,11,17,7,11,10,6,13,13,2,2,0,0 +10/03/2012,Chelsea,Stoke,1,0,H,0,0,D,A Marriner,21,8,11,3,14,1,12,15,1,2,0,1 +10/03/2012,Everton,Tottenham,1,0,H,1,0,H,M Halsey,9,21,5,9,0,8,13,11,2,2,0,0 +10/03/2012,Sunderland,Liverpool,1,0,H,0,0,D,A Taylor,6,9,1,5,3,2,13,15,1,1,0,0 +10/03/2012,Wolves,Blackburn,0,2,A,0,1,A,C Foy,14,12,7,8,3,4,10,10,1,2,0,0 +11/03/2012,Man United,West Brom,2,0,H,1,0,H,L Probert,20,6,9,3,8,5,5,13,0,1,0,1 +11/03/2012,Norwich,Wigan,1,1,D,1,0,H,K Friend,13,18,6,9,3,5,12,7,4,4,0,0 +11/03/2012,Swansea,Man City,1,0,H,0,0,D,L Mason,8,18,5,13,7,9,9,14,0,2,0,0 +12/03/2012,Arsenal,Newcastle,2,1,H,1,1,D,H Webb,21,5,13,3,6,3,11,12,2,3,0,0 +13/03/2012,Liverpool,Everton,3,0,H,1,0,H,P Dowd,15,5,10,3,4,4,6,7,1,4,0,0 +17/03/2012,Fulham,Swansea,0,3,A,0,1,A,M Halsey,14,10,7,7,6,2,8,7,0,0,0,0 +17/03/2012,Wigan,West Brom,1,1,D,0,0,D,M Oliver,20,8,6,2,13,5,8,10,2,4,0,0 +18/03/2012,Newcastle,Norwich,1,0,H,1,0,H,P Dowd,15,12,10,5,4,5,8,15,0,3,0,0 +18/03/2012,Wolves,Man United,0,5,A,0,3,A,A Taylor,7,22,3,18,4,7,6,11,1,1,1,0 +20/03/2012,Blackburn,Sunderland,2,0,H,0,0,D,M Atkinson,16,2,9,0,7,8,13,10,0,1,0,0 +21/03/2012,Everton,Arsenal,0,1,A,0,1,A,L Mason,7,12,2,8,4,6,11,11,2,1,0,0 +21/03/2012,Man City,Chelsea,2,1,H,0,0,D,M Dean,20,6,10,5,11,4,8,7,1,2,0,0 +21/03/2012,QPR,Liverpool,3,2,H,0,0,D,H Webb,10,18,5,11,7,17,8,6,0,0,0,0 +21/03/2012,Tottenham,Stoke,1,1,D,0,0,D,M Jones,24,8,18,2,13,1,10,10,1,1,0,0 +24/03/2012,Arsenal,Aston Villa,3,0,H,2,0,H,P Dowd,20,3,12,2,15,5,6,13,0,5,0,0 +24/03/2012,Bolton,Blackburn,2,1,H,2,0,H,A Marriner,12,10,6,4,11,4,8,3,0,2,0,0 +24/03/2012,Chelsea,Tottenham,0,0,D,0,0,D,M Atkinson,10,19,5,7,6,5,9,11,1,2,0,0 +24/03/2012,Liverpool,Wigan,1,2,A,0,1,A,L Mason,12,6,9,5,6,0,14,10,1,2,0,0 +24/03/2012,Norwich,Wolves,2,1,H,2,1,H,M Clattenburg,15,10,11,6,5,5,13,7,2,2,1,0 +24/03/2012,Stoke,Man City,1,1,D,0,0,D,H Webb,10,16,3,11,5,7,15,12,1,2,0,0 +24/03/2012,Sunderland,QPR,3,1,H,1,0,H,M Jones,15,10,8,6,5,2,11,9,2,3,0,1 +24/03/2012,Swansea,Everton,0,2,A,0,0,D,N Swarbrick,10,15,3,8,3,3,10,10,1,2,0,0 +25/03/2012,West Brom,Newcastle,1,3,A,0,3,A,C Foy,14,10,6,7,10,4,10,4,0,1,0,0 +26/03/2012,Man United,Fulham,1,0,H,1,0,H,M Oliver,19,5,16,4,7,1,17,6,1,0,0,0 +31/03/2012,Aston Villa,Chelsea,2,4,A,0,1,A,L Mason,9,12,5,9,6,4,13,5,1,0,0,0 +31/03/2012,Everton,West Brom,2,0,H,1,0,H,P Walton,16,9,9,5,5,2,8,12,2,1,0,0 +31/03/2012,Fulham,Norwich,2,1,H,2,0,H,A Taylor,14,16,9,8,6,7,11,9,1,2,0,0 +31/03/2012,Man City,Sunderland,3,3,D,1,2,A,P Dowd,19,8,10,4,17,5,11,13,1,1,0,0 +31/03/2012,QPR,Arsenal,2,1,H,1,1,D,M Dean,10,13,8,7,4,6,13,9,5,2,0,0 +31/03/2012,Wigan,Stoke,2,0,H,0,0,D,M Clattenburg,13,6,11,2,8,1,14,13,2,4,0,0 +31/03/2012,Wolves,Bolton,2,3,A,0,0,D,J Moss,14,19,8,11,13,4,11,11,1,1,0,0 +01/04/2012,Newcastle,Liverpool,2,0,H,1,0,H,M Atkinson,11,9,7,6,3,6,10,11,3,3,0,1 +01/04/2012,Tottenham,Swansea,3,1,H,1,0,H,A Marriner,15,11,10,7,7,7,11,12,0,0,0,0 +02/04/2012,Blackburn,Man United,0,2,A,0,0,D,H Webb,11,14,6,6,2,10,8,5,1,1,0,0 +06/04/2012,Swansea,Newcastle,0,2,A,0,1,A,H Webb,19,5,13,4,7,0,10,7,1,1,0,0 +07/04/2012,Bolton,Fulham,0,3,A,0,2,A,M Dean,13,11,7,8,5,3,11,9,1,0,0,0 +07/04/2012,Chelsea,Wigan,2,1,H,0,0,D,M Jones,19,13,10,7,5,3,14,11,2,2,0,0 +07/04/2012,Liverpool,Aston Villa,1,1,D,0,1,A,M Oliver,21,5,5,3,11,3,4,9,1,1,0,0 +07/04/2012,Norwich,Everton,2,2,D,1,1,D,A Marriner,14,9,11,5,2,4,7,17,0,3,0,0 +07/04/2012,Stoke,Wolves,2,1,H,1,1,D,M Halsey,14,9,6,5,3,8,7,11,1,1,0,0 +07/04/2012,Sunderland,Tottenham,0,0,D,0,0,D,C Foy,12,15,9,8,11,4,12,10,2,2,0,0 +07/04/2012,West Brom,Blackburn,3,0,H,1,0,H,L Probert,9,16,6,10,8,4,5,7,1,1,0,1 +08/04/2012,Arsenal,Man City,1,0,H,0,0,D,M Atkinson,12,7,7,2,7,6,13,14,2,4,0,1 +08/04/2012,Man United,QPR,2,0,H,1,0,H,L Mason,24,9,14,7,11,5,13,10,1,0,0,1 +09/04/2012,Aston Villa,Stoke,1,1,D,1,0,H,M Dean,7,7,3,3,5,4,9,9,3,0,0,0 +09/04/2012,Everton,Sunderland,4,0,H,0,0,D,K Friend,22,3,12,0,5,2,7,12,0,2,0,0 +09/04/2012,Fulham,Chelsea,1,1,D,0,1,A,M Clattenburg,11,12,9,4,7,11,5,16,1,3,0,0 +09/04/2012,Newcastle,Bolton,2,0,H,0,0,D,M Jones,8,9,4,6,6,5,7,18,1,1,0,0 +09/04/2012,Tottenham,Norwich,1,2,A,1,1,D,M Oliver,13,12,9,6,10,3,12,12,1,2,0,0 +10/04/2012,Blackburn,Liverpool,2,3,A,1,2,A,A Taylor,10,16,4,12,4,4,16,12,4,5,0,1 +11/04/2012,Man City,West Brom,4,0,H,1,0,H,K Friend,17,4,12,1,6,2,7,9,0,0,0,0 +11/04/2012,QPR,Swansea,3,0,H,1,0,H,L Probert,16,9,8,6,6,9,9,10,2,0,0,0 +11/04/2012,Wigan,Man United,1,0,H,0,0,D,P Dowd,11,8,8,3,7,3,8,14,1,2,0,0 +11/04/2012,Wolves,Arsenal,0,3,A,0,2,A,N Swarbrick,9,10,6,7,6,4,9,4,3,0,1,0 +14/04/2012,Norwich,Man City,1,6,A,0,2,A,C Foy,16,22,8,17,3,5,10,5,1,2,0,0 +14/04/2012,Sunderland,Wolves,0,0,D,0,0,D,M Jones,15,12,6,8,8,4,4,11,0,1,0,0 +14/04/2012,Swansea,Blackburn,3,0,H,2,0,H,M Clattenburg,16,7,8,2,7,3,8,12,0,2,0,0 +14/04/2012,West Brom,QPR,1,0,H,1,0,H,J Moss,15,16,10,6,6,8,13,11,3,1,0,0 +15/04/2012,Man United,Aston Villa,4,0,H,2,0,H,M Halsey,24,10,16,4,11,4,9,7,3,3,0,0 +16/04/2012,Arsenal,Wigan,1,2,A,1,2,A,A Marriner,18,12,8,7,9,3,15,13,2,3,0,0 +21/04/2012,Arsenal,Chelsea,0,0,D,0,0,D,M Dean,13,13,8,7,7,7,11,11,3,4,0,0 +21/04/2012,Aston Villa,Sunderland,0,0,D,0,0,D,A Taylor,12,10,7,5,8,5,11,8,0,2,0,1 +21/04/2012,Blackburn,Norwich,2,0,H,1,0,H,K Friend,9,12,6,5,9,7,8,13,1,1,0,0 +21/04/2012,Bolton,Swansea,1,1,D,1,1,D,P Dowd,7,20,5,12,8,10,19,7,2,1,0,0 +21/04/2012,Fulham,Wigan,2,1,H,0,0,D,L Mason,15,9,9,5,4,0,11,16,1,2,0,0 +21/04/2012,Newcastle,Stoke,3,0,H,2,0,H,A Marriner,16,7,11,2,4,5,8,10,0,2,0,0 +21/04/2012,QPR,Tottenham,1,0,H,1,0,H,M Clattenburg,12,17,5,8,4,13,13,10,4,0,1,0 +22/04/2012,Liverpool,West Brom,0,1,A,0,0,D,N Swarbrick,28,9,12,4,15,3,10,8,2,0,0,0 +22/04/2012,Man United,Everton,4,4,D,1,1,D,M Jones,20,17,11,10,5,2,8,11,1,2,0,0 +22/04/2012,Wolves,Man City,0,2,A,0,1,A,L Probert,7,17,4,9,7,4,6,10,0,1,0,0 +24/04/2012,Aston Villa,Bolton,1,2,A,0,0,D,C Foy,17,12,6,11,9,6,9,16,1,2,0,0 +28/04/2012,Everton,Fulham,4,0,H,3,0,H,P Dowd,11,6,7,5,3,5,13,8,1,1,0,0 +28/04/2012,Norwich,Liverpool,0,3,A,0,2,A,M Halsey,8,15,3,9,5,7,9,4,4,0,0,0 +28/04/2012,Stoke,Arsenal,1,1,D,1,1,D,C Foy,6,17,4,10,2,6,8,10,1,2,0,0 +28/04/2012,Sunderland,Bolton,2,2,D,1,1,D,L Probert,12,16,6,9,7,5,8,16,1,0,0,0 +28/04/2012,Swansea,Wolves,4,4,D,4,2,H,J Moss,10,15,8,9,2,6,8,8,2,1,0,0 +28/04/2012,West Brom,Aston Villa,0,0,D,0,0,D,M Clattenburg,13,8,7,5,10,3,14,16,0,4,0,0 +28/04/2012,Wigan,Newcastle,4,0,H,4,0,H,M Dean,10,11,8,3,4,1,14,12,2,1,0,0 +29/04/2012,Chelsea,QPR,6,1,H,4,0,H,H Webb,25,11,18,7,8,5,9,8,0,1,0,0 +29/04/2012,Tottenham,Blackburn,2,0,H,1,0,H,M Jones,19,0,8,0,6,1,5,14,0,0,0,0 +30/04/2012,Man City,Man United,1,0,H,1,0,H,A Marriner,12,4,5,2,8,8,11,14,3,2,0,0 +01/05/2012,Liverpool,Fulham,0,1,A,0,1,A,L Probert,18,7,5,4,5,4,11,7,0,0,0,0 +01/05/2012,Stoke,Everton,1,1,D,0,1,A,A Taylor,10,11,5,4,4,6,6,14,2,1,0,0 +02/05/2012,Bolton,Tottenham,1,4,A,0,1,A,M Dean,14,13,8,10,4,7,6,9,2,1,0,0 +02/05/2012,Chelsea,Newcastle,0,2,A,0,1,A,M Halsey,17,12,6,6,4,4,9,5,0,3,0,0 +05/05/2012,Arsenal,Norwich,3,3,D,1,2,A,A Taylor,20,12,13,6,6,1,8,15,2,3,0,0 +06/05/2012,Aston Villa,Tottenham,1,1,D,1,0,H,L Probert,4,21,2,10,4,19,10,8,1,2,0,1 +06/05/2012,Bolton,West Brom,2,2,D,1,0,H,K Friend,11,17,6,13,4,4,14,8,1,4,0,0 +06/05/2012,Fulham,Sunderland,2,1,H,2,1,H,M Atkinson,20,12,15,7,8,2,9,13,0,0,0,0 +06/05/2012,Man United,Swansea,2,0,H,2,0,H,C Foy,26,13,18,9,16,3,12,6,1,0,0,0 +06/05/2012,Newcastle,Man City,0,2,A,0,0,D,H Webb,14,19,9,12,2,6,12,7,4,3,0,0 +06/05/2012,QPR,Stoke,1,0,H,0,0,D,A Marriner,21,8,12,1,7,3,11,14,0,0,0,0 +06/05/2012,Wolves,Everton,0,0,D,0,0,D,L Mason,7,18,1,10,3,7,7,10,0,0,0,0 +07/05/2012,Blackburn,Wigan,0,1,A,0,0,D,M Clattenburg,12,19,7,9,5,6,14,12,2,1,0,0 +08/05/2012,Liverpool,Chelsea,4,1,H,3,0,H,K Friend,23,9,13,5,7,4,15,13,2,4,0,0 +13/05/2012,Chelsea,Blackburn,2,1,H,2,0,H,L Mason,18,8,6,5,8,2,13,4,1,0,0,0 +13/05/2012,Everton,Newcastle,3,1,H,2,0,H,A Marriner,17,11,11,5,4,3,15,16,2,1,0,0 +13/05/2012,Man City,QPR,3,2,H,1,0,H,M Dean,35,3,24,3,19,0,4,6,1,1,0,1 +13/05/2012,Norwich,Aston Villa,2,0,H,2,0,H,M Atkinson,21,9,12,5,9,6,6,10,1,2,0,0 +13/05/2012,Stoke,Bolton,2,2,D,1,2,A,C Foy,12,20,8,8,5,9,10,5,1,1,0,0 +13/05/2012,Sunderland,Man United,0,1,A,0,1,A,H Webb,6,17,5,9,1,9,14,11,3,3,0,0 +13/05/2012,Swansea,Liverpool,1,0,H,0,0,D,M Halsey,13,14,8,9,6,5,5,5,1,1,0,0 +13/05/2012,Tottenham,Fulham,2,0,H,1,0,H,P Dowd,15,10,9,7,9,3,8,12,0,2,0,0 +13/05/2012,West Brom,Arsenal,2,3,A,2,2,D,M Jones,12,12,8,8,9,5,12,10,0,1,0,0 +13/05/2012,Wigan,Wolves,3,2,H,2,1,H,M Oliver,14,10,10,7,9,2,15,9,1,2,0,0 +18/08/2012,Arsenal,Sunderland,0,0,D,0,0,D,C Foy,14,3,4,2,7,0,12,8,0,0,0,0 +18/08/2012,Fulham,Norwich,5,0,H,2,0,H,M Oliver,11,4,9,2,6,3,12,11,0,0,0,0 +18/08/2012,Newcastle,Tottenham,2,1,H,0,0,D,M Atkinson,6,12,4,6,3,5,12,8,2,2,0,0 +18/08/2012,QPR,Swansea,0,5,A,0,1,A,L Probert,20,12,11,8,5,3,11,14,2,2,0,0 +18/08/2012,Reading,Stoke,1,1,D,0,1,A,K Friend,9,6,3,3,4,3,9,14,2,4,0,1 +18/08/2012,West Brom,Liverpool,3,0,H,1,0,H,P Dowd,15,14,10,7,7,3,10,11,1,4,0,1 +18/08/2012,West Ham,Aston Villa,1,0,H,1,0,H,M Dean,8,10,4,6,6,4,17,8,0,1,0,0 +19/08/2012,Man City,Southampton,3,2,H,1,0,H,H Webb,20,9,15,6,7,3,4,6,1,2,0,0 +19/08/2012,Wigan,Chelsea,0,2,A,0,2,A,M Jones,12,5,4,3,7,1,16,11,2,2,0,0 +20/08/2012,Everton,Man United,1,0,H,0,0,D,A Marriner,16,12,7,7,6,8,12,11,1,2,0,0 +22/08/2012,Chelsea,Reading,4,2,H,1,2,A,L Mason,23,7,11,5,1,5,11,14,0,2,0,0 +25/08/2012,Aston Villa,Everton,1,3,A,0,3,A,M Oliver,7,19,3,11,1,9,10,7,1,0,1,0 +25/08/2012,Chelsea,Newcastle,2,0,H,2,0,H,P Dowd,11,11,6,5,2,3,8,10,1,0,0,0 +25/08/2012,Man United,Fulham,3,2,H,3,1,H,K Friend,20,14,11,11,8,8,12,7,0,1,0,0 +25/08/2012,Norwich,QPR,1,1,D,1,1,D,M Clattenburg,13,6,4,4,6,2,15,14,0,2,0,0 +25/08/2012,Southampton,Wigan,0,2,A,0,0,D,A Taylor,14,12,9,8,10,3,11,9,0,0,0,0 +25/08/2012,Swansea,West Ham,3,0,H,2,0,H,M Atkinson,10,7,7,6,5,4,13,17,2,4,0,0 +25/08/2012,Tottenham,West Brom,1,1,D,0,0,D,M Dean,18,10,10,5,7,5,8,7,1,2,0,0 +26/08/2012,Liverpool,Man City,2,2,D,1,0,H,A Marriner,15,11,8,5,4,4,10,7,1,0,0,0 +26/08/2012,Stoke,Arsenal,0,0,D,0,0,D,L Mason,7,16,4,6,0,11,9,9,2,0,0,0 +01/09/2012,Man City,QPR,3,1,H,1,0,H,C Foy,19,9,12,5,8,3,12,3,2,1,0,0 +01/09/2012,Swansea,Sunderland,2,2,D,1,2,A,R East,14,4,10,3,8,0,11,7,1,1,1,0 +01/09/2012,Tottenham,Norwich,1,1,D,0,0,D,M Halsey,15,10,9,4,4,2,7,12,1,0,1,0 +01/09/2012,West Brom,Everton,2,0,H,0,0,D,J Moss,14,12,8,5,6,3,11,15,1,4,0,0 +01/09/2012,West Ham,Fulham,3,0,H,3,0,H,A Taylor,17,14,12,13,6,6,12,5,0,0,0,0 +01/09/2012,Wigan,Stoke,2,2,D,1,1,D,M Atkinson,9,16,5,8,2,1,13,11,1,2,0,0 +02/09/2012,Liverpool,Arsenal,0,2,A,0,1,A,H Webb,17,11,8,7,10,2,12,7,2,2,0,0 +02/09/2012,Newcastle,Aston Villa,1,1,D,0,1,A,L Probert,16,13,6,9,10,6,6,20,1,4,0,0 +02/09/2012,Southampton,Man United,2,3,A,1,1,D,M Dean,15,18,8,9,4,7,9,4,1,0,0,0 +15/09/2012,Arsenal,Southampton,6,1,H,4,1,H,K Friend,20,9,12,4,8,3,4,7,0,0,0,0 +15/09/2012,Aston Villa,Swansea,2,0,H,1,0,H,L Mason,17,10,13,5,11,5,11,4,3,1,0,0 +15/09/2012,Fulham,West Brom,3,0,H,2,0,H,R East,23,10,14,8,5,4,6,10,0,0,0,1 +15/09/2012,Man United,Wigan,4,0,H,0,0,D,M Oliver,17,8,10,4,5,5,10,14,2,2,0,0 +15/09/2012,Norwich,West Ham,0,0,D,0,0,D,C Foy,20,9,14,4,8,5,10,12,0,1,0,0 +15/09/2012,QPR,Chelsea,0,0,D,0,0,D,A Marriner,10,13,6,9,2,4,16,17,0,2,0,0 +15/09/2012,Stoke,Man City,1,1,D,1,1,D,M Clattenburg,4,15,3,11,6,5,12,12,0,1,0,0 +15/09/2012,Sunderland,Liverpool,1,1,D,1,0,H,M Atkinson,6,20,4,9,2,7,18,10,1,2,0,0 +16/09/2012,Reading,Tottenham,1,3,A,0,1,A,H Webb,4,22,2,11,7,4,4,9,0,1,0,0 +17/09/2012,Everton,Newcastle,2,2,D,1,0,H,M Jones,17,11,10,7,3,3,16,12,3,3,0,0 +22/09/2012,Chelsea,Stoke,1,0,H,0,0,D,M Oliver,17,13,9,6,13,3,12,15,2,1,0,0 +22/09/2012,Southampton,Aston Villa,4,1,H,0,1,A,J Moss,12,8,4,2,1,4,5,13,1,3,0,0 +22/09/2012,Swansea,Everton,0,3,A,0,2,A,A Taylor,18,30,11,19,8,3,23,7,4,1,1,0 +22/09/2012,West Brom,Reading,1,0,H,0,0,D,C Foy,9,8,4,1,9,1,14,6,1,0,0,0 +22/09/2012,West Ham,Sunderland,1,1,D,0,1,A,L Mason,20,4,11,2,10,1,12,10,2,1,0,0 +22/09/2012,Wigan,Fulham,1,2,A,0,1,A,L Probert,18,12,9,9,8,7,7,13,0,3,0,0 +23/09/2012,Liverpool,Man United,1,2,A,0,0,D,M Halsey,13,8,8,4,7,3,14,7,1,2,1,0 +23/09/2012,Man City,Arsenal,1,1,D,1,0,H,M Dean,14,10,10,4,9,4,11,7,1,0,0,0 +23/09/2012,Newcastle,Norwich,1,0,H,1,0,H,N Swarbrick,16,8,8,3,4,5,11,19,1,1,0,0 +23/09/2012,Tottenham,QPR,2,1,H,0,1,A,P Dowd,18,9,14,6,10,9,7,12,0,2,0,0 +29/09/2012,Arsenal,Chelsea,1,2,A,1,1,D,M Atkinson,14,10,5,5,6,5,14,13,2,3,0,0 +29/09/2012,Everton,Southampton,3,1,H,3,1,H,L Probert,17,8,9,2,10,3,2,3,0,0,0,0 +29/09/2012,Fulham,Man City,1,2,A,1,1,D,M Halsey,7,21,3,12,2,4,7,12,0,1,0,0 +29/09/2012,Man United,Tottenham,2,3,A,0,2,A,C Foy,15,10,8,6,9,5,5,7,0,0,0,0 +29/09/2012,Norwich,Liverpool,2,5,A,0,2,A,M Jones,10,10,7,6,5,5,5,2,1,0,0,0 +29/09/2012,Reading,Newcastle,2,2,D,0,0,D,A Marriner,14,8,7,3,6,3,6,8,0,3,0,0 +29/09/2012,Stoke,Swansea,2,0,H,2,0,H,J Moss,10,8,5,0,6,8,10,8,1,1,0,0 +29/09/2012,Sunderland,Wigan,1,0,H,0,0,D,H Webb,11,8,6,3,8,1,20,16,2,5,0,1 +30/09/2012,Aston Villa,West Brom,1,1,D,0,0,D,A Taylor,19,14,8,7,11,4,9,11,1,1,0,0 +01/10/2012,QPR,West Ham,1,2,A,0,2,A,M Clattenburg,13,17,9,10,4,13,9,21,1,8,1,0 +06/10/2012,Chelsea,Norwich,4,1,H,3,1,H,A Taylor,20,7,13,4,10,2,7,1,0,0,0,0 +06/10/2012,Man City,Sunderland,3,0,H,1,0,H,L Probert,26,8,16,6,15,4,10,10,0,3,0,0 +06/10/2012,Swansea,Reading,2,2,D,0,2,A,M Dean,25,8,16,6,11,3,8,11,1,3,0,0 +06/10/2012,West Brom,QPR,3,2,H,2,1,H,M Jones,13,13,8,6,2,5,16,12,2,2,0,0 +06/10/2012,West Ham,Arsenal,1,3,A,1,1,D,P Dowd,7,20,3,14,4,16,12,7,3,2,0,0 +06/10/2012,Wigan,Everton,2,2,D,2,1,H,K Friend,10,16,6,11,7,6,14,14,2,5,0,0 +07/10/2012,Liverpool,Stoke,0,0,D,0,0,D,L Mason,16,5,5,3,5,5,9,16,0,6,0,0 +07/10/2012,Newcastle,Man United,0,3,A,0,2,A,H Webb,13,12,3,9,8,6,15,12,4,4,0,0 +07/10/2012,Southampton,Fulham,2,2,D,1,0,H,M Clattenburg,13,14,6,5,8,8,13,5,4,0,0,0 +07/10/2012,Tottenham,Aston Villa,2,0,H,0,0,D,N Swarbrick,23,8,10,5,8,0,7,10,1,3,0,0 +20/10/2012,Fulham,Aston Villa,1,0,H,0,0,D,C Foy,17,9,8,4,6,4,7,11,1,2,0,0 +20/10/2012,Liverpool,Reading,1,0,H,1,0,H,R East,23,8,13,5,6,5,13,6,1,1,0,0 +20/10/2012,Man United,Stoke,4,2,H,2,1,H,A Taylor,13,8,8,6,3,3,9,10,1,1,0,0 +20/10/2012,Norwich,Arsenal,1,0,H,1,0,H,L Probert,7,13,3,7,2,10,9,9,4,0,0,0 +20/10/2012,Swansea,Wigan,2,1,H,0,0,D,M Jones,18,10,11,7,9,2,13,11,2,3,0,0 +20/10/2012,Tottenham,Chelsea,2,4,A,0,1,A,M Dean,23,11,16,7,7,4,7,8,3,2,0,0 +20/10/2012,West Brom,Man City,1,2,A,0,0,D,M Clattenburg,16,21,8,13,6,9,11,15,4,3,0,1 +20/10/2012,West Ham,Southampton,4,1,H,0,0,D,N Swarbrick,9,11,8,4,6,3,10,12,0,2,0,0 +21/10/2012,QPR,Everton,1,1,D,1,1,D,J Moss,14,10,6,4,5,4,12,18,2,1,0,1 +21/10/2012,Sunderland,Newcastle,1,1,D,0,1,A,M Atkinson,16,10,6,7,8,4,14,15,0,4,0,1 +27/10/2012,Arsenal,QPR,1,0,H,0,0,D,A Taylor,21,4,11,3,5,2,14,9,1,2,0,1 +27/10/2012,Aston Villa,Norwich,1,1,D,1,0,H,P Dowd,7,14,5,6,3,8,12,7,2,0,1,0 +27/10/2012,Man City,Swansea,1,0,H,0,0,D,M Atkinson,10,9,5,5,3,0,8,10,1,2,0,0 +27/10/2012,Reading,Fulham,3,3,D,1,0,H,M Jones,15,16,9,8,6,8,6,17,1,1,0,0 +27/10/2012,Stoke,Sunderland,0,0,D,0,0,D,M Halsey,11,6,6,3,7,3,12,6,2,2,0,0 +27/10/2012,Wigan,West Ham,2,1,H,1,0,H,J Moss,17,15,13,11,6,6,10,16,3,4,0,0 +28/10/2012,Chelsea,Man United,2,3,A,1,2,A,M Clattenburg,14,15,11,7,8,6,13,11,2,2,2,0 +28/10/2012,Everton,Liverpool,2,2,D,2,2,D,A Marriner,17,12,8,5,9,5,16,18,4,3,0,0 +28/10/2012,Newcastle,West Brom,2,1,H,1,0,H,C Foy,10,8,6,4,6,4,13,7,3,2,0,0 +28/10/2012,Southampton,Tottenham,1,2,A,0,2,A,L Mason,11,11,6,4,2,3,14,8,0,3,0,0 +03/11/2012,Fulham,Everton,2,2,D,1,0,H,N Swarbrick,8,26,5,15,5,9,16,9,3,1,0,0 +03/11/2012,Man United,Arsenal,2,1,H,1,0,H,M Dean,11,7,7,3,6,1,13,13,5,3,0,1 +03/11/2012,Norwich,Stoke,1,0,H,1,0,H,A Marriner,12,8,8,3,9,1,11,13,1,5,0,0 +03/11/2012,Sunderland,Aston Villa,0,1,A,0,0,D,M Jones,11,9,1,6,6,7,10,17,2,3,0,0 +03/11/2012,Swansea,Chelsea,1,1,D,0,0,D,K Friend,9,11,7,7,8,6,12,9,4,1,0,0 +03/11/2012,Tottenham,Wigan,0,1,A,0,0,D,M Atkinson,13,7,10,4,9,3,10,11,1,1,0,0 +03/11/2012,West Ham,Man City,0,0,D,0,0,D,H Webb,6,18,1,8,3,7,11,12,3,0,0,0 +04/11/2012,Liverpool,Newcastle,1,1,D,0,1,A,A Taylor,20,10,12,3,14,3,10,13,0,1,0,1 +04/11/2012,QPR,Reading,1,1,D,0,1,A,M Oliver,23,18,12,8,8,8,11,15,0,2,0,0 +05/11/2012,West Brom,Southampton,2,0,H,1,0,H,M Halsey,16,9,6,5,8,6,6,12,1,0,0,0 +10/11/2012,Arsenal,Fulham,3,3,D,2,2,D,P Dowd,15,9,9,5,8,6,12,10,1,2,0,0 +10/11/2012,Aston Villa,Man United,2,3,A,1,0,H,K Friend,10,16,7,7,5,6,12,7,1,1,0,0 +10/11/2012,Everton,Sunderland,2,1,H,0,1,A,L Mason,16,6,10,4,6,2,14,14,1,3,0,0 +10/11/2012,Reading,Norwich,0,0,D,0,0,D,C Foy,7,10,3,3,10,3,9,16,0,1,0,0 +10/11/2012,Southampton,Swansea,1,1,D,0,0,D,A Marriner,11,5,5,3,10,6,11,10,2,2,0,0 +10/11/2012,Stoke,QPR,1,0,H,0,0,D,M Atkinson,10,17,5,8,5,5,11,16,2,3,0,0 +10/11/2012,Wigan,West Brom,1,2,A,1,2,A,N Swarbrick,14,7,6,4,5,8,9,13,1,4,0,0 +11/11/2012,Chelsea,Liverpool,1,1,D,1,0,H,H Webb,12,9,3,5,4,7,12,13,1,3,0,0 +11/11/2012,Man City,Tottenham,2,1,H,0,1,A,M Oliver,16,6,8,2,8,2,16,7,1,2,0,0 +11/11/2012,Newcastle,West Ham,0,1,A,0,1,A,M Dean,14,10,9,5,11,4,8,14,1,2,0,0 +17/11/2012,Arsenal,Tottenham,5,2,H,3,1,H,H Webb,13,6,10,4,6,6,13,12,1,2,0,1 +17/11/2012,Liverpool,Wigan,3,0,H,0,0,D,K Friend,22,9,11,3,10,5,9,11,0,3,0,0 +17/11/2012,Man City,Aston Villa,5,0,H,1,0,H,J Moss,21,10,8,8,9,4,9,4,0,1,0,0 +17/11/2012,Newcastle,Swansea,1,2,A,0,0,D,P Dowd,27,15,10,9,5,4,7,7,1,3,0,0 +17/11/2012,Norwich,Man United,1,0,H,0,0,D,A Taylor,10,12,5,9,0,12,10,7,0,1,0,0 +17/11/2012,QPR,Southampton,1,3,A,0,2,A,M Dean,13,21,7,15,4,13,12,9,1,0,0,0 +17/11/2012,Reading,Everton,2,1,H,0,1,A,M Atkinson,12,21,4,11,7,13,11,11,3,2,0,0 +17/11/2012,West Brom,Chelsea,2,1,H,1,1,D,M Oliver,8,19,6,12,0,12,7,13,0,0,0,0 +18/11/2012,Fulham,Sunderland,1,3,A,0,0,D,L Probert,18,12,13,9,4,4,9,13,1,3,1,0 +19/11/2012,West Ham,Stoke,1,1,D,0,1,A,C Foy,16,12,11,6,12,4,8,9,1,0,0,0 +24/11/2012,Aston Villa,Arsenal,0,0,D,0,0,D,L Mason,10,13,8,6,7,6,6,9,1,0,0,0 +24/11/2012,Everton,Norwich,1,1,D,1,0,H,M Jones,14,8,9,4,4,2,10,18,0,3,0,0 +24/11/2012,Man United,QPR,3,1,H,0,0,D,L Probert,15,10,10,6,9,7,6,10,1,1,0,0 +24/11/2012,Stoke,Fulham,1,0,H,1,0,H,M Oliver,12,8,3,5,7,2,14,11,1,1,0,0 +24/11/2012,Sunderland,West Brom,2,4,A,0,2,A,M Dean,19,11,12,6,7,2,12,15,2,3,0,0 +24/11/2012,Wigan,Reading,3,2,H,0,1,A,H Webb,19,16,11,10,9,5,15,9,2,0,0,0 +25/11/2012,Chelsea,Man City,0,0,D,0,0,D,C Foy,5,11,1,7,6,11,9,16,0,3,0,0 +25/11/2012,Southampton,Newcastle,2,0,H,1,0,H,M Atkinson,17,8,10,3,4,3,12,14,2,2,0,0 +25/11/2012,Swansea,Liverpool,0,0,D,0,0,D,J Moss,17,21,8,14,7,6,6,10,1,0,0,0 +25/11/2012,Tottenham,West Ham,3,1,H,1,0,H,A Marriner,25,10,13,6,4,2,9,10,1,2,0,0 +27/11/2012,Aston Villa,Reading,1,0,H,0,0,D,L Probert,15,11,7,6,8,3,10,5,1,0,0,0 +27/11/2012,Sunderland,QPR,0,0,D,0,0,D,A Marriner,13,13,8,9,5,8,10,15,1,3,0,0 +28/11/2012,Chelsea,Fulham,0,0,D,0,0,D,A Taylor,17,8,13,7,10,2,10,11,3,0,0,0 +28/11/2012,Everton,Arsenal,1,1,D,1,1,D,M Oliver,14,12,10,9,6,6,15,9,2,1,0,0 +28/11/2012,Man United,West Ham,1,0,H,1,0,H,M Jones,23,9,13,3,5,5,7,9,1,1,0,0 +28/11/2012,Southampton,Norwich,1,1,D,1,1,D,M Clattenburg,8,9,2,5,3,7,11,7,1,1,0,0 +28/11/2012,Stoke,Newcastle,2,1,H,0,0,D,H Webb,11,7,5,6,7,2,12,12,2,2,0,0 +28/11/2012,Swansea,West Brom,3,1,H,3,1,H,L Mason,13,6,8,2,5,2,10,16,0,4,0,0 +28/11/2012,Tottenham,Liverpool,2,1,H,2,0,H,P Dowd,14,14,7,9,1,12,4,14,1,2,0,0 +28/11/2012,Wigan,Man City,0,2,A,0,0,D,M Halsey,9,15,4,6,6,6,13,12,1,1,0,0 +01/12/2012,Arsenal,Swansea,0,2,A,0,0,D,M Clattenburg,10,12,8,10,8,5,11,9,1,0,0,0 +01/12/2012,Fulham,Tottenham,0,3,A,0,0,D,C Foy,8,10,2,6,2,1,10,8,0,2,0,0 +01/12/2012,Liverpool,Southampton,1,0,H,1,0,H,M Oliver,24,8,11,4,10,4,10,8,2,0,0,0 +01/12/2012,Man City,Everton,1,1,D,1,1,D,L Probert,9,8,6,5,5,2,16,12,2,1,0,0 +01/12/2012,QPR,Aston Villa,1,1,D,1,1,D,H Webb,17,7,12,3,11,4,5,6,1,2,0,0 +01/12/2012,Reading,Man United,3,4,A,3,4,A,M Halsey,10,10,7,5,6,1,10,12,1,1,0,0 +01/12/2012,West Brom,Stoke,0,1,A,0,0,D,K Friend,14,6,11,4,5,5,8,15,0,3,0,0 +01/12/2012,West Ham,Chelsea,3,1,H,0,1,A,M Atkinson,16,16,13,9,14,5,15,6,1,2,0,0 +02/12/2012,Norwich,Sunderland,2,1,H,2,1,H,P Dowd,7,16,5,6,2,6,11,16,0,2,0,0 +03/12/2012,Newcastle,Wigan,3,0,H,2,0,H,M Jones,16,9,8,4,4,4,12,14,1,3,0,1 +08/12/2012,Arsenal,West Brom,2,0,H,1,0,H,M Jones,16,7,7,2,7,4,11,12,2,3,0,0 +08/12/2012,Aston Villa,Stoke,0,0,D,0,0,D,R East,9,1,5,0,3,2,8,13,0,1,0,1 +08/12/2012,Southampton,Reading,1,0,H,0,0,D,J Moss,18,6,9,2,7,4,11,10,0,0,0,0 +08/12/2012,Sunderland,Chelsea,1,3,A,0,2,A,M Halsey,14,11,10,5,11,1,11,14,1,3,0,0 +08/12/2012,Swansea,Norwich,3,4,A,0,3,A,H Webb,21,10,10,8,6,2,12,9,2,1,0,0 +08/12/2012,Wigan,QPR,2,2,D,1,1,D,P Dowd,14,8,9,4,11,4,7,11,0,1,0,0 +09/12/2012,Everton,Tottenham,2,1,H,0,0,D,K Friend,17,8,11,6,4,4,10,9,2,2,0,0 +09/12/2012,Man City,Man United,2,3,A,0,2,A,M Atkinson,16,8,11,3,8,2,11,9,4,3,0,0 +09/12/2012,West Ham,Liverpool,2,3,A,2,1,H,L Probert,9,16,6,8,2,5,12,10,1,2,0,0 +10/12/2012,Fulham,Newcastle,2,1,H,1,0,H,L Mason,16,20,7,13,5,4,13,11,0,0,0,0 +11/12/2012,Sunderland,Reading,3,0,H,2,0,H,N Swarbrick,16,12,11,5,6,8,8,11,2,1,0,0 +15/12/2012,Liverpool,Aston Villa,1,3,A,0,2,A,N Swarbrick,26,9,17,6,7,2,4,11,1,2,0,0 +15/12/2012,Man United,Sunderland,3,1,H,2,0,H,C Foy,15,13,6,10,9,9,13,10,0,0,0,0 +15/12/2012,Newcastle,Man City,1,3,A,0,2,A,A Marriner,17,16,8,10,12,7,9,16,3,2,0,0 +15/12/2012,Norwich,Wigan,2,1,H,1,0,H,L Probert,16,12,10,6,7,3,7,4,3,1,0,0 +15/12/2012,QPR,Fulham,2,1,H,0,0,D,M Atkinson,19,6,9,3,3,3,10,14,1,2,0,0 +15/12/2012,Stoke,Everton,1,1,D,0,1,A,M Halsey,14,9,8,5,4,8,11,13,1,0,0,0 +16/12/2012,Tottenham,Swansea,1,0,H,0,0,D,M Dean,24,4,16,0,9,3,12,8,2,3,0,0 +16/12/2012,West Brom,West Ham,0,0,D,0,0,D,P Dowd,19,10,10,2,8,2,12,9,3,3,0,0 +17/12/2012,Reading,Arsenal,2,5,A,0,3,A,A Taylor,7,25,5,17,3,7,8,4,1,1,0,0 +22/12/2012,Liverpool,Fulham,4,0,H,2,0,H,M Clattenburg,22,7,12,6,8,2,13,6,1,2,0,0 +22/12/2012,Man City,Reading,1,0,H,0,0,D,M Dean,17,6,10,1,12,3,11,15,2,3,0,0 +22/12/2012,Newcastle,QPR,1,0,H,0,0,D,K Friend,18,5,8,4,10,3,9,16,3,2,0,0 +22/12/2012,Southampton,Sunderland,0,1,A,0,1,A,H Webb,13,10,7,6,8,5,11,15,0,3,0,0 +22/12/2012,Tottenham,Stoke,0,0,D,0,0,D,L Mason,21,4,14,2,11,4,8,11,2,5,0,0 +22/12/2012,West Brom,Norwich,2,1,H,1,1,D,M Atkinson,12,10,8,4,6,5,14,9,1,1,0,0 +22/12/2012,West Ham,Everton,1,2,A,1,0,H,A Taylor,6,15,5,8,3,5,12,8,1,0,1,1 +22/12/2012,Wigan,Arsenal,0,1,A,0,0,D,J Moss,13,10,5,8,4,4,14,8,1,1,0,0 +23/12/2012,Chelsea,Aston Villa,8,0,H,3,0,H,P Dowd,24,7,18,4,8,4,6,11,0,0,0,0 +23/12/2012,Swansea,Man United,1,1,D,1,1,D,M Oliver,14,19,10,16,3,6,11,16,2,4,0,0 +26/12/2012,Aston Villa,Tottenham,0,4,A,0,0,D,M Clattenburg,4,20,1,16,2,16,11,7,1,0,0,0 +26/12/2012,Everton,Wigan,2,1,H,0,0,D,L Mason,17,10,9,6,11,2,10,11,1,3,0,0 +26/12/2012,Fulham,Southampton,1,1,D,1,0,H,M Oliver,10,7,2,3,3,5,14,12,3,0,0,0 +26/12/2012,Man United,Newcastle,4,3,H,1,2,A,M Dean,16,16,10,11,2,2,12,16,3,3,0,0 +26/12/2012,Norwich,Chelsea,0,1,A,0,1,A,J Moss,8,20,4,11,5,7,7,7,1,1,0,0 +26/12/2012,QPR,West Brom,1,2,A,0,1,A,C Foy,18,12,8,6,14,9,9,10,1,0,0,0 +26/12/2012,Reading,Swansea,0,0,D,0,0,D,M Jones,3,10,2,4,6,4,14,17,2,1,0,0 +26/12/2012,Stoke,Liverpool,3,1,H,2,1,H,H Webb,7,12,6,6,6,8,11,5,3,1,0,0 +26/12/2012,Sunderland,Man City,1,0,H,0,0,D,K Friend,12,19,6,12,3,7,14,16,1,3,0,0 +29/12/2012,Arsenal,Newcastle,7,3,H,1,1,D,C Foy,16,10,12,8,5,6,8,10,0,1,0,0 +29/12/2012,Aston Villa,Wigan,0,3,A,0,1,A,K Friend,11,9,4,6,4,3,16,8,3,2,0,0 +29/12/2012,Fulham,Swansea,1,2,A,0,1,A,A Marriner,22,10,13,9,9,8,14,12,2,1,0,0 +29/12/2012,Man United,West Brom,2,0,H,1,0,H,J Moss,17,13,14,9,9,6,9,7,1,0,0,0 +29/12/2012,Norwich,Man City,3,4,A,1,2,A,M Jones,13,13,8,9,4,9,8,10,3,1,0,1 +29/12/2012,Reading,West Ham,1,0,H,1,0,H,M Oliver,3,11,2,3,2,2,17,10,3,4,0,0 +29/12/2012,Stoke,Southampton,3,3,D,1,3,A,M Clattenburg,10,8,8,7,7,4,6,9,1,2,1,0 +29/12/2012,Sunderland,Tottenham,1,2,A,1,0,H,L Mason,9,18,6,14,3,8,9,15,1,3,0,0 +30/12/2012,Everton,Chelsea,1,2,A,1,1,D,H Webb,12,15,6,10,7,7,14,12,2,3,0,0 +30/12/2012,QPR,Liverpool,0,3,A,0,3,A,A Taylor,14,16,7,11,1,9,10,8,2,1,0,0 +01/01/2013,Man City,Stoke,3,0,H,1,0,H,M Oliver,17,4,9,3,7,2,6,9,2,2,0,0 +01/01/2013,Southampton,Arsenal,1,1,D,1,1,D,L Probert,9,6,6,4,3,3,9,6,2,0,0,0 +01/01/2013,Swansea,Aston Villa,2,2,D,1,1,D,M Halsey,23,8,13,6,3,2,7,15,0,4,0,0 +01/01/2013,Tottenham,Reading,3,1,H,1,1,D,H Webb,30,7,20,4,12,3,9,9,0,0,0,0 +01/01/2013,West Brom,Fulham,1,2,A,0,1,A,M Dean,17,10,10,5,10,5,14,6,0,0,0,0 +01/01/2013,West Ham,Norwich,2,1,H,2,0,H,M Clattenburg,14,9,8,3,11,3,14,7,0,0,0,0 +01/01/2013,Wigan,Man United,0,4,A,0,2,A,A Marriner,7,14,3,9,1,6,13,5,1,1,0,0 +02/01/2013,Chelsea,QPR,0,1,A,0,0,D,L Mason,23,8,11,5,14,2,12,8,1,1,0,0 +02/01/2013,Liverpool,Sunderland,3,0,H,2,0,H,P Dowd,27,6,16,5,9,5,8,5,0,1,0,0 +02/01/2013,Newcastle,Everton,1,2,A,1,1,D,M Atkinson,14,7,11,6,4,6,21,7,3,2,0,0 +12/01/2013,Aston Villa,Southampton,0,1,A,0,1,A,M Halsey,16,11,10,4,9,5,9,6,1,1,0,0 +12/01/2013,Everton,Swansea,0,0,D,0,0,D,P Dowd,16,4,8,2,7,1,13,9,1,4,0,0 +12/01/2013,Fulham,Wigan,1,1,D,1,0,H,M Clattenburg,14,15,5,6,6,8,8,15,0,1,0,0 +12/01/2013,Norwich,Newcastle,0,0,D,0,0,D,A Taylor,12,9,3,4,5,4,5,6,0,1,0,0 +12/01/2013,QPR,Tottenham,0,0,D,0,0,D,L Probert,4,16,1,8,2,6,10,7,1,1,0,0 +12/01/2013,Reading,West Brom,3,2,H,0,1,A,K Friend,7,11,7,6,8,4,6,13,1,1,0,0 +12/01/2013,Stoke,Chelsea,0,4,A,0,1,A,A Marriner,11,11,5,9,6,7,13,9,1,0,0,0 +12/01/2013,Sunderland,West Ham,3,0,H,1,0,H,N Swarbrick,18,9,14,4,4,6,10,13,0,3,0,0 +13/01/2013,Arsenal,Man City,0,2,A,0,2,A,M Dean,9,16,4,9,2,7,11,18,2,4,1,1 +13/01/2013,Man United,Liverpool,2,1,H,1,0,H,H Webb,15,12,10,5,3,1,14,16,1,4,0,0 +16/01/2013,Chelsea,Southampton,2,2,D,2,0,H,M Oliver,19,7,10,3,4,3,10,11,1,0,0,0 +19/01/2013,Liverpool,Norwich,5,0,H,2,0,H,M Oliver,17,5,10,3,10,4,10,8,1,1,0,0 +19/01/2013,Man City,Fulham,2,0,H,1,0,H,J Moss,20,3,11,0,1,2,12,6,1,2,0,0 +19/01/2013,Newcastle,Reading,1,2,A,1,0,H,A Marriner,15,6,10,2,6,2,9,10,2,3,0,0 +19/01/2013,Swansea,Stoke,3,1,H,0,0,D,M Dean,12,7,6,2,7,6,11,9,0,0,0,0 +19/01/2013,West Brom,Aston Villa,2,2,D,0,2,A,L Probert,25,11,9,7,8,8,8,10,3,3,0,0 +19/01/2013,West Ham,QPR,1,1,D,0,1,A,H Webb,22,9,13,6,12,2,6,11,1,0,0,0 +19/01/2013,Wigan,Sunderland,2,3,A,1,3,A,A Taylor,20,10,11,5,9,0,10,16,3,2,0,0 +20/01/2013,Chelsea,Arsenal,2,1,H,2,0,H,M Atkinson,13,12,8,8,3,9,10,11,2,1,0,0 +20/01/2013,Tottenham,Man United,1,1,D,0,1,A,C Foy,23,6,18,4,9,4,7,14,1,4,0,0 +21/01/2013,Southampton,Everton,0,0,D,0,0,D,N Swarbrick,14,10,11,7,10,6,14,11,2,1,0,0 +23/01/2013,Arsenal,West Ham,5,1,H,1,1,D,A Marriner,23,7,17,5,13,5,9,10,0,1,0,0 +29/01/2013,Aston Villa,Newcastle,1,2,A,0,2,A,M Dean,10,10,4,7,3,4,16,9,5,2,0,0 +29/01/2013,QPR,Man City,0,0,D,0,0,D,P Dowd,5,15,3,7,2,10,9,9,2,2,0,0 +29/01/2013,Stoke,Wigan,2,2,D,1,0,H,M Jones,9,11,4,6,0,1,12,10,1,1,0,0 +29/01/2013,Sunderland,Swansea,0,0,D,0,0,D,A Marriner,4,18,2,8,3,4,11,11,1,2,0,0 +30/01/2013,Arsenal,Liverpool,2,2,D,0,1,A,K Friend,14,10,6,5,8,6,9,11,3,1,0,0 +30/01/2013,Everton,West Brom,2,1,H,2,0,H,M Oliver,11,7,4,4,5,2,9,14,0,2,0,0 +30/01/2013,Fulham,West Ham,3,1,H,1,0,H,C Foy,13,8,6,4,2,8,8,9,2,0,0,0 +30/01/2013,Man United,Southampton,2,1,H,2,1,H,L Mason,11,21,7,10,4,8,10,10,2,1,0,0 +30/01/2013,Norwich,Tottenham,1,1,D,1,0,H,N Swarbrick,8,12,4,7,3,6,13,14,4,1,0,0 +30/01/2013,Reading,Chelsea,2,2,D,0,1,A,M Halsey,5,14,2,5,0,6,8,20,1,2,0,0 +02/02/2013,Arsenal,Stoke,1,0,H,0,0,D,C Foy,15,6,9,2,11,1,3,13,0,3,0,0 +02/02/2013,Everton,Aston Villa,3,3,D,1,2,A,M Jones,22,8,13,4,15,2,6,23,1,4,0,0 +02/02/2013,Fulham,Man United,0,1,A,0,0,D,K Friend,15,13,8,8,5,8,8,15,1,1,0,0 +02/02/2013,Newcastle,Chelsea,3,2,H,1,0,H,H Webb,19,12,13,6,5,4,11,5,2,3,0,0 +02/02/2013,QPR,Norwich,0,0,D,0,0,D,J Moss,15,14,7,9,10,8,6,20,2,5,0,0 +02/02/2013,Reading,Sunderland,2,1,H,1,1,D,L Mason,9,10,5,6,6,8,12,15,1,3,0,0 +02/02/2013,West Ham,Swansea,1,0,H,0,0,D,L Probert,18,11,9,7,5,3,7,9,1,1,0,0 +02/02/2013,Wigan,Southampton,2,2,D,1,0,H,A Marriner,5,15,3,7,8,5,6,16,0,2,0,0 +03/02/2013,Man City,Liverpool,2,2,D,1,1,D,A Taylor,9,21,5,12,9,10,14,7,2,4,0,0 +03/02/2013,West Brom,Tottenham,0,1,A,0,0,D,M Clattenburg,4,21,3,13,2,17,9,9,1,0,1,0 +09/02/2013,Chelsea,Wigan,4,1,H,1,0,H,M Dean,25,13,17,10,10,6,9,13,1,2,0,0 +09/02/2013,Norwich,Fulham,0,0,D,0,0,D,H Webb,15,7,8,5,3,4,10,16,2,2,0,0 +09/02/2013,Southampton,Man City,3,1,H,2,1,H,M Atkinson,13,7,7,4,6,6,9,13,1,2,0,0 +09/02/2013,Stoke,Reading,2,1,H,0,0,D,M Oliver,13,5,10,4,11,3,7,4,2,0,0,0 +09/02/2013,Sunderland,Arsenal,0,1,A,0,1,A,A Taylor,12,18,7,9,11,2,6,13,3,2,0,1 +09/02/2013,Swansea,QPR,4,1,H,2,0,H,N Swarbrick,13,14,12,8,5,2,10,9,1,3,0,0 +09/02/2013,Tottenham,Newcastle,2,1,H,1,1,D,P Dowd,16,9,9,7,11,5,5,11,0,2,0,0 +10/02/2013,Aston Villa,West Ham,2,1,H,0,0,D,M Clattenburg,7,13,2,7,4,10,15,12,3,2,0,0 +10/02/2013,Man United,Everton,2,0,H,2,0,H,M Halsey,10,9,6,8,7,6,12,9,0,2,0,0 +11/02/2013,Liverpool,West Brom,0,2,A,0,0,D,J Moss,23,4,14,3,13,5,9,9,1,4,0,0 +17/02/2013,Liverpool,Swansea,5,0,H,1,0,H,H Webb,33,4,18,3,10,1,6,9,0,1,0,0 +23/02/2013,Arsenal,Aston Villa,2,1,H,1,0,H,M Atkinson,24,5,14,3,12,3,7,12,1,2,0,0 +23/02/2013,Fulham,Stoke,1,0,H,1,0,H,L Probert,20,7,9,2,9,4,4,9,1,3,0,0 +23/02/2013,Norwich,Everton,2,1,H,0,1,A,L Mason,10,11,9,5,7,5,15,10,1,1,0,0 +23/02/2013,QPR,Man United,0,2,A,0,1,A,A Taylor,12,13,5,8,2,4,10,6,0,0,0,0 +23/02/2013,Reading,Wigan,0,3,A,0,2,A,P Dowd,7,14,1,8,2,9,9,11,1,1,1,0 +23/02/2013,West Brom,Sunderland,2,1,H,1,0,H,R East,17,10,7,6,5,5,13,9,2,1,0,0 +24/02/2013,Man City,Chelsea,2,0,H,0,0,D,A Marriner,15,4,11,1,6,6,19,5,3,1,0,0 +24/02/2013,Newcastle,Southampton,4,2,H,2,1,H,C Foy,11,11,5,7,7,3,12,12,0,4,0,0 +25/02/2013,West Ham,Tottenham,2,3,A,1,1,D,H Webb,8,25,6,16,6,12,12,7,4,1,0,0 +02/03/2013,Chelsea,West Brom,1,0,H,1,0,H,K Friend,25,8,19,4,9,7,12,16,1,2,0,0 +02/03/2013,Everton,Reading,3,1,H,1,0,H,A Taylor,18,12,12,6,9,10,8,8,1,1,0,0 +02/03/2013,Man United,Norwich,4,0,H,1,0,H,N Swarbrick,13,1,10,1,7,3,5,15,0,4,0,0 +02/03/2013,Southampton,QPR,1,2,A,1,1,D,H Webb,17,5,9,3,6,3,7,15,1,3,0,0 +02/03/2013,Stoke,West Ham,0,1,A,0,1,A,J Moss,10,10,2,7,4,2,12,12,1,2,0,0 +02/03/2013,Sunderland,Fulham,2,2,D,1,2,A,M Halsey,12,7,6,4,7,3,9,14,1,3,0,0 +02/03/2013,Swansea,Newcastle,1,0,H,0,0,D,C Pawson,10,14,7,7,7,7,9,17,0,2,0,0 +02/03/2013,Wigan,Liverpool,0,4,A,0,3,A,M Atkinson,15,8,11,7,4,3,11,15,2,2,0,0 +03/03/2013,Tottenham,Arsenal,2,1,H,2,0,H,M Clattenburg,11,10,5,6,4,6,17,13,3,1,0,0 +04/03/2013,Aston Villa,Man City,0,1,A,0,1,A,M Dean,10,20,5,12,6,5,10,14,2,0,0,0 +09/03/2013,Norwich,Southampton,0,0,D,0,0,D,M Clattenburg,3,15,2,7,1,7,9,10,2,3,0,0 +09/03/2013,QPR,Sunderland,3,1,H,1,1,D,M Jones,15,7,8,3,4,5,4,19,0,2,0,0 +09/03/2013,Reading,Aston Villa,1,2,A,1,2,A,J Moss,10,18,4,7,6,1,8,7,2,3,0,0 +09/03/2013,West Brom,Swansea,2,1,H,1,1,D,L Mason,12,9,6,4,3,3,9,10,1,1,0,0 +10/03/2013,Liverpool,Tottenham,3,2,H,1,1,D,M Oliver,10,15,4,9,1,4,9,8,1,1,0,0 +10/03/2013,Newcastle,Stoke,2,1,H,0,0,D,A Marriner,14,7,6,4,14,4,10,12,1,1,0,0 +16/03/2013,Aston Villa,QPR,3,2,H,1,1,D,K Friend,10,18,6,11,9,6,16,10,3,2,0,0 +16/03/2013,Everton,Man City,2,0,H,1,0,H,L Probert,11,17,5,11,13,6,15,10,4,3,1,0 +16/03/2013,Man United,Reading,1,0,H,1,0,H,L Mason,17,5,8,1,5,4,5,16,0,2,0,0 +16/03/2013,Southampton,Liverpool,3,1,H,2,1,H,P Dowd,17,8,12,6,7,5,10,10,0,1,0,0 +16/03/2013,Stoke,West Brom,0,0,D,0,0,D,M Dean,13,9,5,5,7,1,15,11,2,2,0,0 +16/03/2013,Swansea,Arsenal,0,2,A,0,0,D,J Moss,9,15,2,11,3,7,2,7,1,1,0,0 +17/03/2013,Chelsea,West Ham,2,0,H,1,0,H,M Oliver,24,15,14,9,5,5,5,14,0,2,0,0 +17/03/2013,Sunderland,Norwich,1,1,D,1,1,D,C Foy,16,7,8,3,5,9,11,11,1,3,0,1 +17/03/2013,Tottenham,Fulham,0,1,A,0,0,D,M Jones,12,6,6,2,5,5,10,5,1,1,0,0 +17/03/2013,Wigan,Newcastle,2,1,H,1,0,H,M Halsey,13,9,10,4,7,3,7,12,1,1,0,0 +30/03/2013,Arsenal,Reading,4,1,H,1,0,H,C Foy,23,4,14,2,11,3,9,14,0,1,0,0 +30/03/2013,Everton,Stoke,1,0,H,1,0,H,M Jones,15,10,9,5,4,3,13,18,1,3,0,0 +30/03/2013,Man City,Newcastle,4,0,H,2,0,H,N Swarbrick,19,5,11,1,9,2,6,8,0,2,0,0 +30/03/2013,Southampton,Chelsea,2,1,H,2,1,H,J Moss,13,7,8,4,7,7,12,6,2,1,0,0 +30/03/2013,Sunderland,Man United,0,1,A,0,1,A,K Friend,9,10,4,6,3,6,11,9,3,1,0,0 +30/03/2013,Swansea,Tottenham,1,2,A,0,2,A,A Taylor,11,9,3,7,5,2,5,10,2,4,0,0 +30/03/2013,West Ham,West Brom,3,1,H,2,0,H,A Marriner,14,17,8,11,5,3,10,13,1,1,0,1 +30/03/2013,Wigan,Norwich,1,0,H,0,0,D,H Webb,13,9,6,5,10,3,13,9,1,2,0,0 +31/03/2013,Aston Villa,Liverpool,1,2,A,1,0,H,L Mason,10,16,7,12,7,8,9,8,1,1,0,0 +01/04/2013,Fulham,QPR,3,2,H,3,1,H,L Probert,9,19,5,11,3,6,11,7,2,1,1,0 +06/04/2013,Norwich,Swansea,2,2,D,1,1,D,M Oliver,12,16,9,9,2,5,8,16,5,3,0,0 +06/04/2013,Reading,Southampton,0,2,A,0,1,A,M Jones,8,12,3,4,8,4,5,11,0,0,0,0 +06/04/2013,Stoke,Aston Villa,1,3,A,0,1,A,M Clattenburg,6,11,2,8,4,2,16,11,3,1,0,0 +06/04/2013,West Brom,Arsenal,1,2,A,0,1,A,H Webb,16,11,5,6,6,4,7,15,2,4,0,1 +07/04/2013,Chelsea,Sunderland,2,1,H,0,1,A,N Swarbrick,14,7,7,3,8,5,9,15,0,2,0,0 +07/04/2013,Liverpool,West Ham,0,0,D,0,0,D,A Taylor,19,10,12,4,12,2,8,4,0,1,0,0 +07/04/2013,Newcastle,Fulham,1,0,H,0,0,D,K Friend,20,5,10,3,10,3,13,11,2,2,0,0 +07/04/2013,QPR,Wigan,1,1,D,0,0,D,P Dowd,6,16,2,10,2,10,9,12,2,3,1,0 +07/04/2013,Tottenham,Everton,2,2,D,1,1,D,A Marriner,19,9,10,5,7,6,14,8,1,1,0,0 +08/04/2013,Man United,Man City,1,2,A,0,0,D,M Dean,16,9,7,5,9,4,13,18,3,5,0,0 +13/04/2013,Arsenal,Norwich,3,1,H,0,0,D,M Jones,10,5,7,5,9,3,8,10,1,2,0,0 +13/04/2013,Aston Villa,Fulham,1,1,D,0,0,D,P Dowd,15,8,7,3,8,5,5,15,1,2,0,0 +13/04/2013,Everton,QPR,2,0,H,1,0,H,L Mason,14,8,10,5,13,5,6,12,3,3,0,0 +13/04/2013,Reading,Liverpool,0,0,D,0,0,D,M Clattenburg,7,26,5,15,4,9,9,8,2,1,0,0 +13/04/2013,Southampton,West Ham,1,1,D,0,0,D,M Dean,15,6,11,4,7,4,9,18,2,3,0,0 +14/04/2013,Newcastle,Sunderland,0,3,A,0,1,A,H Webb,15,8,9,7,10,9,14,7,3,2,0,0 +14/04/2013,Stoke,Man United,0,2,A,0,1,A,J Moss,13,9,4,3,3,7,9,9,1,1,0,0 +16/04/2013,Arsenal,Everton,0,0,D,0,0,D,N Swarbrick,10,10,6,5,8,1,11,12,2,3,0,0 +17/04/2013,Fulham,Chelsea,0,3,A,0,2,A,M Dean,11,13,7,10,6,5,11,11,2,1,0,0 +17/04/2013,Man City,Wigan,1,0,H,0,0,D,J Moss,10,8,4,6,5,4,12,5,2,1,0,0 +17/04/2013,West Ham,Man United,2,2,D,1,1,D,L Probert,10,15,4,9,2,5,9,7,1,0,0,0 +20/04/2013,Fulham,Arsenal,0,1,A,0,1,A,A Marriner,7,10,5,7,7,3,13,8,0,2,1,1 +20/04/2013,Norwich,Reading,2,1,H,0,0,D,M Dean,19,14,9,6,8,8,8,12,2,3,0,0 +20/04/2013,QPR,Stoke,0,2,A,0,1,A,C Foy,25,11,11,4,5,5,8,14,1,2,0,0 +20/04/2013,Sunderland,Everton,1,0,H,1,0,H,P Dowd,8,8,4,5,3,10,6,15,1,2,0,0 +20/04/2013,Swansea,Southampton,0,0,D,0,0,D,M Halsey,14,9,7,5,7,2,11,13,0,0,0,0 +20/04/2013,West Brom,Newcastle,1,1,D,0,1,A,M Jones,8,10,4,6,15,2,9,10,0,2,0,0 +20/04/2013,West Ham,Wigan,2,0,H,1,0,H,N Swarbrick,17,18,11,10,3,6,12,11,3,1,0,0 +21/04/2013,Liverpool,Chelsea,2,2,D,0,1,A,K Friend,23,9,12,6,8,5,8,11,5,2,0,0 +21/04/2013,Tottenham,Man City,3,1,H,0,1,A,L Mason,11,11,8,7,4,5,11,10,3,3,0,0 +22/04/2013,Man United,Aston Villa,3,0,H,3,0,H,A Taylor,10,8,5,5,5,3,10,6,1,0,0,0 +27/04/2013,Everton,Fulham,1,0,H,1,0,H,J Moss,17,6,10,2,7,2,12,16,1,3,0,0 +27/04/2013,Man City,West Ham,2,1,H,1,0,H,H Webb,16,9,10,6,9,4,9,7,1,3,0,0 +27/04/2013,Newcastle,Liverpool,0,6,A,0,2,A,A Marriner,5,11,1,10,5,7,14,7,3,2,1,0 +27/04/2013,Southampton,West Brom,0,3,A,0,1,A,R Madley,16,11,4,9,7,6,9,11,2,2,2,1 +27/04/2013,Stoke,Norwich,1,0,H,0,0,D,A Taylor,12,5,4,0,5,3,10,8,4,2,0,0 +27/04/2013,Wigan,Tottenham,2,2,D,1,1,D,M Atkinson,6,14,4,7,3,3,12,10,3,1,0,0 +28/04/2013,Arsenal,Man United,1,1,D,1,1,D,P Dowd,19,10,13,5,7,2,12,10,3,5,0,0 +28/04/2013,Chelsea,Swansea,2,0,H,2,0,H,M Clattenburg,21,11,14,8,6,3,9,10,1,3,0,0 +28/04/2013,Reading,QPR,0,0,D,0,0,D,K Friend,13,15,7,5,9,7,12,14,0,2,0,0 +29/04/2013,Aston Villa,Sunderland,6,1,H,2,1,H,L Probert,18,7,15,4,4,4,10,15,0,3,0,1 +04/05/2013,Fulham,Reading,2,4,A,0,1,A,N Swarbrick,18,20,13,13,3,3,14,7,2,1,0,0 +04/05/2013,Norwich,Aston Villa,1,2,A,0,0,D,K Friend,9,12,4,6,6,5,15,13,2,3,0,0 +04/05/2013,QPR,Arsenal,0,1,A,0,1,A,J Moss,14,13,6,11,3,7,8,6,2,1,0,0 +04/05/2013,Swansea,Man City,0,0,D,0,0,D,M Jones,7,12,1,8,4,4,9,10,0,0,0,0 +04/05/2013,Tottenham,Southampton,1,0,H,0,0,D,M Clattenburg,7,10,4,6,5,5,8,9,1,1,0,0 +04/05/2013,West Brom,Wigan,2,3,A,1,1,D,L Probert,17,13,11,8,7,5,9,7,4,3,0,0 +04/05/2013,West Ham,Newcastle,0,0,D,0,0,D,P Dowd,9,11,3,6,7,3,12,12,1,1,0,0 +05/05/2013,Liverpool,Everton,0,0,D,0,0,D,M Oliver,16,11,8,4,3,5,10,10,1,2,0,0 +05/05/2013,Man United,Chelsea,0,1,A,0,0,D,H Webb,10,15,4,6,2,7,14,9,2,1,1,0 +06/05/2013,Sunderland,Stoke,1,1,D,0,1,A,L Mason,14,11,7,7,5,6,6,12,2,2,1,0 +07/05/2013,Man City,West Brom,1,0,H,1,0,H,P Dowd,21,20,9,11,6,8,10,9,2,1,0,0 +07/05/2013,Wigan,Swansea,2,3,A,1,0,H,K Friend,14,10,11,6,4,4,14,13,2,2,0,0 +08/05/2013,Chelsea,Tottenham,2,2,D,2,1,H,M Dean,15,13,8,6,6,5,6,16,1,2,0,0 +11/05/2013,Aston Villa,Chelsea,1,2,A,1,0,H,L Mason,12,11,7,8,6,7,13,8,2,3,1,1 +12/05/2013,Everton,West Ham,2,0,H,1,0,H,P Dowd,23,7,13,2,7,1,8,11,1,2,0,0 +12/05/2013,Fulham,Liverpool,1,3,A,1,1,D,M Halsey,10,20,8,14,7,8,2,10,0,1,0,0 +12/05/2013,Man United,Swansea,2,1,H,1,0,H,J Moss,16,5,9,2,7,5,5,6,0,0,0,0 +12/05/2013,Norwich,West Brom,4,0,H,1,0,H,H Webb,11,8,8,5,8,2,10,7,2,1,0,0 +12/05/2013,QPR,Newcastle,1,2,A,1,2,A,L Probert,8,10,3,7,2,2,8,8,1,3,0,1 +12/05/2013,Stoke,Tottenham,1,2,A,1,1,D,K Friend,6,23,3,15,2,11,11,5,4,1,1,0 +12/05/2013,Sunderland,Southampton,1,1,D,0,0,D,M Dean,5,15,5,10,3,10,9,10,0,2,0,0 +14/05/2013,Arsenal,Wigan,4,1,H,1,1,D,M Dean,19,10,12,5,7,4,11,11,0,2,0,0 +14/05/2013,Reading,Man City,0,2,A,0,1,A,M Atkinson,11,27,7,20,4,10,6,7,0,1,0,0 +19/05/2013,Chelsea,Everton,2,1,H,1,1,D,A Taylor,20,16,10,9,7,4,7,8,2,2,0,0 +19/05/2013,Liverpool,QPR,1,0,H,1,0,H,M Atkinson,27,10,20,3,14,0,7,3,1,3,0,0 +19/05/2013,Man City,Norwich,2,3,A,1,1,D,M Halsey,12,12,5,7,7,7,10,8,1,0,0,0 +19/05/2013,Newcastle,Arsenal,0,1,A,0,0,D,H Webb,7,9,2,3,4,4,17,10,1,2,0,0 +19/05/2013,Southampton,Stoke,1,1,D,0,0,D,L Probert,11,7,7,3,3,4,9,7,0,2,0,0 +19/05/2013,Swansea,Fulham,0,3,A,0,1,A,L Mason,19,8,11,6,8,0,12,9,2,1,0,0 +19/05/2013,Tottenham,Sunderland,1,0,H,0,0,D,A Marriner,23,6,19,4,14,1,6,12,1,3,0,1 +19/05/2013,West Brom,Man United,5,5,D,1,3,A,M Oliver,15,12,8,8,3,5,10,6,0,1,0,0 +19/05/2013,West Ham,Reading,4,2,H,2,0,H,M Dean,21,17,12,7,6,4,14,8,2,1,0,0 +19/05/2013,Wigan,Aston Villa,2,2,D,2,1,H,N Swarbrick,12,5,6,2,4,2,8,12,2,1,0,0 +17/08/2013,Arsenal,Aston Villa,1,3,A,1,1,D,A Taylor,16,9,4,4,4,3,15,18,4,5,1,0 +17/08/2013,Liverpool,Stoke,1,0,H,1,0,H,M Atkinson,26,10,11,4,12,6,11,11,1,1,0,0 +17/08/2013,Norwich,Everton,2,2,D,0,0,D,M Oliver,8,19,2,6,6,8,13,10,2,0,0,0 +17/08/2013,Sunderland,Fulham,0,1,A,0,0,D,N Swarbrick,20,5,3,1,6,1,14,14,0,3,0,0 +17/08/2013,Swansea,Man United,1,4,A,0,2,A,P Dowd,17,15,6,7,7,4,13,10,1,3,0,0 +17/08/2013,West Brom,Southampton,0,1,A,0,0,D,K Friend,11,7,1,2,4,8,14,24,4,0,0,0 +17/08/2013,West Ham,Cardiff,2,0,H,1,0,H,H Webb,18,12,4,1,4,3,10,7,0,1,0,0 +18/08/2013,Chelsea,Hull,2,0,H,2,0,H,J Moss,22,7,5,2,5,1,7,16,0,1,0,0 +18/08/2013,Crystal Palace,Tottenham,0,1,A,0,0,D,M Clattenburg,5,17,3,2,3,7,6,9,1,0,0,0 +19/08/2013,Man City,Newcastle,4,0,H,2,0,H,A Marriner,20,5,11,1,8,1,9,7,2,3,0,1 +21/08/2013,Chelsea,Aston Villa,2,1,H,1,1,D,K Friend,15,7,3,3,1,2,12,13,1,4,0,0 +24/08/2013,Aston Villa,Liverpool,0,1,A,0,1,A,M Clattenburg,17,5,3,1,8,2,9,8,3,3,0,0 +24/08/2013,Everton,West Brom,0,0,D,0,0,D,R East,22,7,8,2,11,1,14,15,1,1,0,0 +24/08/2013,Fulham,Arsenal,1,3,A,0,2,A,H Webb,16,18,7,7,1,8,10,8,2,2,0,0 +24/08/2013,Hull,Norwich,1,0,H,1,0,H,M Jones,6,13,1,4,1,5,14,19,1,1,1,0 +24/08/2013,Newcastle,West Ham,0,0,D,0,0,D,P Dowd,16,6,0,1,5,4,12,14,0,1,0,0 +24/08/2013,Southampton,Sunderland,1,1,D,0,1,A,L Mason,17,8,6,3,5,2,12,13,2,2,0,0 +24/08/2013,Stoke,Crystal Palace,2,1,H,0,1,A,A Marriner,14,14,5,5,7,0,13,6,3,0,0,0 +25/08/2013,Cardiff,Man City,3,2,H,0,0,D,L Probert,9,16,6,5,3,8,2,9,0,0,0,0 +25/08/2013,Tottenham,Swansea,1,0,H,0,0,D,N Swarbrick,19,7,5,5,8,3,10,15,1,4,0,0 +26/08/2013,Man United,Chelsea,0,0,D,0,0,D,M Atkinson,13,8,3,4,4,1,9,9,0,2,0,0 +31/08/2013,Cardiff,Everton,0,0,D,0,0,D,A Taylor,4,13,1,3,4,5,11,9,1,1,0,0 +31/08/2013,Crystal Palace,Sunderland,3,1,H,1,0,H,L Probert,17,10,6,2,6,6,6,6,1,0,0,1 +31/08/2013,Man City,Hull,2,0,H,0,0,D,P Dowd,12,7,6,2,7,4,15,14,2,3,0,0 +31/08/2013,Newcastle,Fulham,1,0,H,0,0,D,C Foy,24,3,9,2,13,2,13,9,2,2,0,0 +31/08/2013,Norwich,Southampton,1,0,H,0,0,D,H Webb,15,11,4,2,5,5,11,15,0,0,0,0 +31/08/2013,West Ham,Stoke,0,1,A,0,0,D,J Moss,9,15,0,5,3,4,10,12,2,1,0,0 +01/09/2013,Arsenal,Tottenham,1,0,H,1,0,H,M Oliver,12,14,5,4,3,6,15,14,2,2,0,0 +01/09/2013,Liverpool,Man United,1,0,H,1,0,H,A Marriner,10,10,5,4,2,7,15,15,2,4,0,0 +01/09/2013,West Brom,Swansea,0,2,A,0,1,A,M Dean,10,12,1,4,2,3,10,14,4,3,0,0 +14/09/2013,Aston Villa,Newcastle,1,2,A,0,1,A,M Dean,10,17,1,8,4,2,9,11,1,4,0,0 +14/09/2013,Everton,Chelsea,1,0,H,1,0,H,H Webb,11,22,5,5,5,7,10,12,0,4,0,0 +14/09/2013,Fulham,West Brom,1,1,D,1,0,H,L Probert,12,12,2,4,4,5,9,12,0,1,0,0 +14/09/2013,Hull,Cardiff,1,1,D,1,0,H,R Madley,18,10,5,2,6,2,8,8,1,2,0,0 +14/09/2013,Man United,Crystal Palace,2,0,H,1,0,H,J Moss,19,4,7,0,10,1,8,11,1,2,0,1 +14/09/2013,Stoke,Man City,0,0,D,0,0,D,M Clattenburg,11,10,3,3,2,4,13,11,1,1,0,0 +14/09/2013,Sunderland,Arsenal,1,3,A,0,1,A,M Atkinson,17,14,2,6,6,7,12,10,1,2,0,0 +14/09/2013,Tottenham,Norwich,2,0,H,1,0,H,L Mason,23,4,7,1,8,1,2,12,1,3,0,0 +15/09/2013,Southampton,West Ham,0,0,D,0,0,D,A Marriner,15,6,5,1,10,4,12,10,1,3,0,0 +16/09/2013,Swansea,Liverpool,2,2,D,1,2,A,M Oliver,17,12,8,5,3,7,7,10,2,3,0,0 +21/09/2013,Chelsea,Fulham,2,0,H,0,0,D,A Marriner,26,4,10,1,13,3,11,9,0,0,0,0 +21/09/2013,Liverpool,Southampton,0,1,A,0,0,D,N Swarbrick,10,12,5,7,6,6,6,9,0,1,0,0 +21/09/2013,Newcastle,Hull,2,3,A,2,1,H,M Atkinson,15,12,2,5,3,3,11,8,1,1,0,0 +21/09/2013,Norwich,Aston Villa,0,1,A,0,1,A,C Foy,12,16,1,4,7,4,15,10,1,0,0,0 +21/09/2013,West Brom,Sunderland,3,0,H,1,0,H,P Dowd,13,7,4,0,5,6,9,10,0,2,0,0 +21/09/2013,West Ham,Everton,2,3,A,1,0,H,L Mason,9,10,1,4,5,5,16,12,2,1,1,0 +22/09/2013,Arsenal,Stoke,3,1,H,2,1,H,M Dean,16,9,8,3,6,7,8,15,0,2,0,0 +22/09/2013,Cardiff,Tottenham,0,1,A,0,0,D,M Clattenburg,6,29,0,12,2,12,5,7,1,4,0,0 +22/09/2013,Crystal Palace,Swansea,0,2,A,0,1,A,K Friend,9,15,1,7,6,7,13,9,2,2,0,0 +22/09/2013,Man City,Man United,4,1,H,2,0,H,H Webb,18,14,5,3,8,4,8,12,1,2,0,0 +28/09/2013,Aston Villa,Man City,3,2,H,0,1,A,M Jones,8,20,4,7,2,13,13,9,1,1,0,0 +28/09/2013,Fulham,Cardiff,1,2,A,1,1,D,C Pawson,9,22,5,4,11,10,14,13,1,1,0,0 +28/09/2013,Hull,West Ham,1,0,H,1,0,H,K Friend,9,18,2,5,6,6,11,12,1,2,0,0 +28/09/2013,Man United,West Brom,1,2,A,0,0,D,M Oliver,14,12,6,4,5,8,10,14,3,2,0,0 +28/09/2013,Southampton,Crystal Palace,2,0,H,0,0,D,M Atkinson,12,8,3,0,8,2,11,12,1,2,0,0 +28/09/2013,Swansea,Arsenal,1,2,A,0,0,D,M Clattenburg,10,10,3,4,4,3,5,11,3,2,0,0 +28/09/2013,Tottenham,Chelsea,1,1,D,1,0,H,M Dean,12,11,4,5,4,5,14,14,5,1,0,1 +29/09/2013,Stoke,Norwich,0,1,A,0,1,A,A Taylor,8,8,1,3,7,3,14,12,2,1,0,0 +29/09/2013,Sunderland,Liverpool,1,3,A,0,2,A,H Webb,23,15,5,6,10,8,8,10,2,1,0,0 +30/09/2013,Everton,Newcastle,3,2,H,3,0,H,P Dowd,18,15,7,6,1,1,5,15,3,1,0,0 +05/10/2013,Cardiff,Newcastle,1,2,A,0,2,A,K Friend,8,20,2,10,3,3,11,8,1,0,0,0 +05/10/2013,Fulham,Stoke,1,0,H,0,0,D,R East,9,16,2,3,7,5,11,13,3,2,0,0 +05/10/2013,Hull,Aston Villa,0,0,D,0,0,D,M Clattenburg,6,18,1,4,7,7,7,11,2,1,0,0 +05/10/2013,Liverpool,Crystal Palace,3,1,H,3,0,H,A Taylor,13,11,7,2,11,3,11,6,2,1,0,0 +05/10/2013,Man City,Everton,3,1,H,2,1,H,J Moss,12,6,6,4,8,1,15,10,5,4,0,0 +05/10/2013,Sunderland,Man United,1,2,A,1,0,H,C Foy,6,13,2,6,4,4,15,13,2,4,0,0 +06/10/2013,Norwich,Chelsea,1,3,A,0,1,A,N Swarbrick,14,22,3,8,4,6,12,9,1,1,0,0 +06/10/2013,Southampton,Swansea,2,0,H,1,0,H,M Dean,12,17,4,7,7,4,13,8,3,1,0,0 +06/10/2013,Tottenham,West Ham,0,3,A,0,0,D,L Probert,14,16,4,5,6,7,5,9,0,0,0,0 +06/10/2013,West Brom,Arsenal,1,1,D,1,0,H,L Mason,12,14,4,5,6,2,6,11,1,2,0,0 +19/10/2013,Arsenal,Norwich,4,1,H,1,0,H,L Probert,20,12,11,6,10,1,8,7,0,0,0,0 +19/10/2013,Chelsea,Cardiff,4,1,H,1,1,D,A Taylor,20,13,7,3,6,9,12,5,1,2,0,0 +19/10/2013,Everton,Hull,2,1,H,1,1,D,N Swarbrick,14,9,6,1,5,7,7,17,3,1,0,0 +19/10/2013,Man United,Southampton,1,1,D,1,0,H,M Jones,12,18,5,7,4,4,10,19,2,3,0,0 +19/10/2013,Newcastle,Liverpool,2,2,D,1,1,D,A Marriner,14,23,4,7,3,13,8,10,2,3,1,0 +19/10/2013,Stoke,West Brom,0,0,D,0,0,D,H Webb,9,12,3,5,6,6,12,10,1,0,0,0 +19/10/2013,Swansea,Sunderland,4,0,H,0,0,D,C Pawson,14,6,6,1,7,2,12,13,1,2,0,0 +19/10/2013,West Ham,Man City,1,3,A,0,1,A,M Oliver,11,16,2,9,5,5,7,12,1,2,0,0 +20/10/2013,Aston Villa,Tottenham,0,2,A,0,1,A,P Dowd,15,13,2,6,4,7,13,12,3,4,0,0 +21/10/2013,Crystal Palace,Fulham,1,4,A,1,2,A,L Mason,14,9,4,6,5,7,10,13,0,1,0,0 +26/10/2013,Aston Villa,Everton,0,2,A,0,0,D,A Taylor,14,15,3,4,8,6,14,8,0,2,0,0 +26/10/2013,Crystal Palace,Arsenal,0,2,A,0,0,D,C Foy,13,10,4,4,4,3,12,8,0,0,0,1 +26/10/2013,Liverpool,West Brom,4,1,H,2,0,H,J Moss,18,15,7,3,3,9,14,11,0,2,0,0 +26/10/2013,Man United,Stoke,3,2,H,1,2,A,L Mason,19,10,5,5,12,2,8,18,2,6,0,0 +26/10/2013,Norwich,Cardiff,0,0,D,0,0,D,M Jones,31,6,10,4,13,2,8,10,1,1,0,0 +26/10/2013,Southampton,Fulham,2,0,H,2,0,H,A Marriner,20,1,6,0,10,3,12,8,0,2,0,0 +27/10/2013,Chelsea,Man City,2,1,H,1,0,H,H Webb,12,15,4,6,3,6,11,15,2,3,0,0 +27/10/2013,Sunderland,Newcastle,2,1,H,1,0,H,L Probert,8,16,4,4,5,3,13,14,1,1,0,0 +27/10/2013,Swansea,West Ham,0,0,D,0,0,D,P Dowd,10,15,5,4,9,6,12,14,1,3,0,0 +27/10/2013,Tottenham,Hull,1,0,H,0,0,D,M Oliver,15,10,6,4,6,4,13,10,3,3,0,0 +02/11/2013,Arsenal,Liverpool,2,0,H,1,0,H,M Atkinson,12,12,7,4,3,5,11,7,2,1,0,0 +02/11/2013,Fulham,Man United,1,3,A,0,3,A,L Probert,15,10,6,4,4,4,6,12,2,1,0,0 +02/11/2013,Hull,Sunderland,1,0,H,1,0,H,A Marriner,25,9,2,1,4,2,17,11,2,0,0,2 +02/11/2013,Man City,Norwich,7,0,H,4,0,H,P Dowd,27,7,9,1,10,3,6,9,1,1,0,0 +02/11/2013,Newcastle,Chelsea,2,0,H,0,0,D,L Mason,15,13,8,2,3,8,13,11,2,2,0,0 +02/11/2013,Stoke,Southampton,1,1,D,1,1,D,C Foy,9,10,3,2,5,5,13,13,3,1,0,0 +02/11/2013,West Brom,Crystal Palace,2,0,H,1,0,H,N Swarbrick,9,12,4,4,10,3,11,9,0,1,0,0 +02/11/2013,West Ham,Aston Villa,0,0,D,0,0,D,H Webb,17,8,4,2,5,1,8,15,0,2,0,0 +03/11/2013,Cardiff,Swansea,1,0,H,0,0,D,M Dean,9,10,3,3,6,3,11,7,1,1,0,1 +03/11/2013,Everton,Tottenham,0,0,D,0,0,D,K Friend,8,16,1,6,7,4,11,9,4,1,0,0 +09/11/2013,Aston Villa,Cardiff,2,0,H,0,0,D,M Atkinson,17,8,6,2,5,3,10,3,1,1,0,0 +09/11/2013,Chelsea,West Brom,2,2,D,1,0,H,L Probert,17,11,7,3,9,4,11,15,3,7,0,0 +09/11/2013,Crystal Palace,Everton,0,0,D,0,0,D,C Pawson,9,15,2,2,4,7,16,8,1,0,0,0 +09/11/2013,Liverpool,Fulham,4,0,H,3,0,H,M Jones,32,4,10,1,9,3,13,9,0,1,0,0 +09/11/2013,Norwich,West Ham,3,1,H,0,1,A,J Moss,15,12,6,4,4,2,12,11,1,1,0,0 +09/11/2013,Southampton,Hull,4,1,H,3,0,H,H Webb,15,10,7,2,6,5,10,15,1,2,0,0 +10/11/2013,Man United,Arsenal,1,0,H,1,0,H,M Oliver,5,10,2,2,5,6,12,11,2,3,0,0 +10/11/2013,Sunderland,Man City,1,0,H,1,0,H,M Dean,5,24,2,4,0,14,6,13,0,1,0,0 +10/11/2013,Swansea,Stoke,3,3,D,0,2,A,R Madley,17,6,6,3,14,3,12,15,5,3,0,0 +10/11/2013,Tottenham,Newcastle,0,1,A,0,1,A,C Foy,31,8,14,4,11,4,5,15,1,2,0,0 +23/11/2013,Arsenal,Southampton,2,0,H,1,0,H,M Clattenburg,9,10,4,4,5,6,10,14,0,3,0,0 +23/11/2013,Everton,Liverpool,3,3,D,1,2,A,P Dowd,18,11,12,5,6,7,16,15,4,3,0,0 +23/11/2013,Fulham,Swansea,1,2,A,0,0,D,A Marriner,9,21,3,5,8,8,11,9,3,1,0,0 +23/11/2013,Hull,Crystal Palace,0,1,A,0,0,D,A Taylor,15,9,2,2,11,4,8,11,2,1,0,1 +23/11/2013,Newcastle,Norwich,2,1,H,2,0,H,M Dean,21,10,5,2,6,5,8,7,0,1,0,0 +23/11/2013,Stoke,Sunderland,2,0,H,1,0,H,K Friend,17,11,5,3,3,7,11,5,2,1,0,1 +23/11/2013,West Ham,Chelsea,0,3,A,0,2,A,C Foy,8,20,1,8,8,4,12,10,0,0,0,0 +24/11/2013,Cardiff,Man United,2,2,D,1,2,A,N Swarbrick,14,16,4,5,2,8,15,14,4,2,0,0 +24/11/2013,Man City,Tottenham,6,0,H,3,0,H,M Atkinson,15,13,10,5,4,7,7,11,1,3,0,0 +25/11/2013,West Brom,Aston Villa,2,2,D,2,0,H,M Oliver,11,15,4,7,4,2,9,14,1,2,0,0 +30/11/2013,Aston Villa,Sunderland,0,0,D,0,0,D,N Swarbrick,13,7,4,1,7,6,14,12,2,1,0,0 +30/11/2013,Cardiff,Arsenal,0,3,A,0,1,A,L Mason,10,15,4,6,3,3,6,8,0,3,0,0 +30/11/2013,Everton,Stoke,4,0,H,1,0,H,M Jones,22,4,12,3,9,3,9,16,0,3,0,0 +30/11/2013,Newcastle,West Brom,2,1,H,1,0,H,P Dowd,14,14,6,1,3,3,15,15,1,2,0,0 +30/11/2013,Norwich,Crystal Palace,1,0,H,1,0,H,C Foy,10,13,3,3,5,4,12,14,1,2,0,0 +30/11/2013,West Ham,Fulham,3,0,H,0,0,D,M Atkinson,23,10,12,0,6,3,11,8,3,2,0,0 +01/12/2013,Chelsea,Southampton,3,1,H,0,1,A,M Oliver,16,9,7,1,8,4,15,14,1,4,0,0 +01/12/2013,Hull,Liverpool,3,1,H,1,1,D,H Webb,12,9,4,4,2,6,10,16,2,0,0,0 +01/12/2013,Man City,Swansea,3,0,H,1,0,H,M Clattenburg,22,9,7,4,5,4,7,8,1,0,0,0 +01/12/2013,Tottenham,Man United,2,2,D,1,1,D,M Dean,10,8,4,3,7,7,9,15,1,3,0,0 +03/12/2013,Crystal Palace,West Ham,1,0,H,1,0,H,L Mason,10,11,5,2,3,4,10,9,1,2,0,0 +04/12/2013,Arsenal,Hull,2,0,H,1,0,H,A Marriner,20,7,7,2,11,1,9,6,0,0,0,0 +04/12/2013,Fulham,Tottenham,1,2,A,0,0,D,M Clattenburg,16,18,6,5,9,7,7,16,2,1,0,0 +04/12/2013,Liverpool,Norwich,5,1,H,3,0,H,A Taylor,28,14,12,5,4,0,10,6,3,1,0,0 +04/12/2013,Man United,Everton,0,1,A,0,0,D,M Atkinson,18,15,6,4,7,4,12,12,2,0,0,0 +04/12/2013,Southampton,Aston Villa,2,3,A,0,1,A,J Moss,21,6,9,3,8,2,9,13,3,2,0,0 +04/12/2013,Stoke,Cardiff,0,0,D,0,0,D,M Oliver,17,14,2,3,2,5,11,12,2,2,0,0 +04/12/2013,Sunderland,Chelsea,3,4,A,1,2,A,P Dowd,12,19,3,5,6,5,15,14,4,3,0,0 +04/12/2013,Swansea,Newcastle,3,0,H,1,0,H,H Webb,13,14,5,3,5,1,4,8,0,2,0,0 +04/12/2013,West Brom,Man City,2,3,A,0,2,A,C Foy,12,15,1,5,3,3,8,10,1,1,0,0 +07/12/2013,Crystal Palace,Cardiff,2,0,H,1,0,H,M Jones,19,10,5,2,8,3,9,12,0,0,0,0 +07/12/2013,Liverpool,West Ham,4,1,H,1,0,H,M Oliver,32,8,9,1,8,4,9,12,0,3,0,1 +07/12/2013,Man United,Newcastle,0,1,A,0,0,D,A Marriner,8,9,4,3,4,4,11,18,0,2,0,0 +07/12/2013,Southampton,Man City,1,1,D,1,1,D,A Taylor,15,13,4,3,5,4,6,9,0,2,0,0 +07/12/2013,Stoke,Chelsea,3,2,H,1,1,D,J Moss,9,15,3,6,5,2,14,8,4,1,0,0 +07/12/2013,Sunderland,Tottenham,1,2,A,1,1,D,L Mason,11,22,4,5,4,10,14,9,2,0,0,0 +07/12/2013,West Brom,Norwich,0,2,A,0,1,A,M Clattenburg,26,8,12,3,10,3,10,6,1,3,0,0 +08/12/2013,Arsenal,Everton,1,1,D,0,0,D,H Webb,11,12,5,4,3,2,13,11,0,4,0,0 +08/12/2013,Fulham,Aston Villa,2,0,H,2,0,H,M Dean,22,10,10,2,4,7,12,13,0,2,0,0 +09/12/2013,Swansea,Hull,1,1,D,0,1,A,M Atkinson,11,9,2,2,12,1,8,7,3,3,0,0 +14/12/2013,Cardiff,West Brom,1,0,H,0,0,D,H Webb,11,12,3,2,8,6,16,12,2,1,0,0 +14/12/2013,Chelsea,Crystal Palace,2,1,H,2,1,H,M Clattenburg,16,10,7,5,3,1,13,7,2,1,0,0 +14/12/2013,Everton,Fulham,4,1,H,1,0,H,A Taylor,22,16,7,3,12,2,10,7,1,2,0,0 +14/12/2013,Hull,Stoke,0,0,D,0,0,D,M Dean,13,11,4,2,7,5,11,13,1,0,0,0 +14/12/2013,Man City,Arsenal,6,3,H,2,1,H,M Atkinson,22,12,7,6,8,8,12,6,2,1,0,0 +14/12/2013,Newcastle,Southampton,1,1,D,1,0,H,M Jones,16,8,5,3,11,3,10,16,1,2,0,0 +14/12/2013,West Ham,Sunderland,0,0,D,0,0,D,A Marriner,11,19,2,6,2,9,11,5,1,0,0,0 +15/12/2013,Aston Villa,Man United,0,3,A,0,2,A,C Foy,17,14,6,5,3,4,15,8,4,2,0,0 +15/12/2013,Norwich,Swansea,1,1,D,1,1,D,M Oliver,16,10,3,2,8,5,12,11,0,3,0,0 +15/12/2013,Tottenham,Liverpool,0,5,A,0,2,A,J Moss,9,21,0,10,2,9,17,15,3,0,1,0 +21/12/2013,Crystal Palace,Newcastle,0,3,A,0,2,A,N Swarbrick,9,13,4,6,3,7,11,14,1,2,0,0 +21/12/2013,Fulham,Man City,2,4,A,0,2,A,K Friend,12,17,5,10,4,4,7,15,1,1,0,0 +21/12/2013,Liverpool,Cardiff,3,1,H,3,0,H,L Probert,27,10,7,3,9,5,11,13,2,0,0,0 +21/12/2013,Man United,West Ham,3,1,H,2,0,H,M Jones,21,4,8,1,8,2,7,16,1,4,0,0 +21/12/2013,Stoke,Aston Villa,2,1,H,0,0,D,C Pawson,12,14,2,4,6,7,13,17,1,5,0,0 +21/12/2013,Sunderland,Norwich,0,0,D,0,0,D,M Atkinson,18,14,4,5,6,7,13,10,2,1,1,0 +21/12/2013,West Brom,Hull,1,1,D,0,1,A,J Moss,14,6,3,3,9,2,8,15,2,1,0,0 +22/12/2013,Southampton,Tottenham,2,3,A,1,1,D,C Foy,12,13,5,3,2,5,10,18,1,1,0,0 +22/12/2013,Swansea,Everton,1,2,A,0,0,D,L Mason,13,14,3,6,4,8,11,10,1,0,0,0 +23/12/2013,Arsenal,Chelsea,0,0,D,0,0,D,M Dean,7,13,2,4,8,6,7,11,2,1,0,0 +26/12/2013,Aston Villa,Crystal Palace,0,1,A,0,0,D,H Webb,10,12,4,7,7,5,11,8,2,2,0,0 +26/12/2013,Cardiff,Southampton,0,3,A,0,3,A,A Marriner,9,11,0,6,6,3,11,13,1,2,0,0 +26/12/2013,Chelsea,Swansea,1,0,H,1,0,H,M Jones,19,8,7,1,13,7,14,9,1,0,0,0 +26/12/2013,Everton,Sunderland,0,1,A,0,1,A,L Probert,26,13,9,6,11,2,11,7,1,0,1,0 +26/12/2013,Hull,Man United,2,3,A,2,2,D,M Oliver,17,13,5,4,6,5,12,14,2,2,0,1 +26/12/2013,Man City,Liverpool,2,1,H,2,1,H,L Mason,20,12,6,5,7,6,10,9,1,3,0,0 +26/12/2013,Newcastle,Stoke,5,1,H,1,1,D,M Atkinson,27,11,8,3,8,2,10,8,1,1,0,2 +26/12/2013,Norwich,Fulham,1,2,A,1,1,D,J Moss,12,15,3,7,5,4,14,10,3,0,0,0 +26/12/2013,Tottenham,West Brom,1,1,D,1,1,D,A Taylor,20,7,3,4,12,2,8,12,3,2,0,0 +26/12/2013,West Ham,Arsenal,1,3,A,0,0,D,P Dowd,12,29,5,8,0,10,11,6,1,1,0,0 +28/12/2013,Aston Villa,Swansea,1,1,D,1,1,D,R East,7,17,3,3,2,8,13,9,3,2,0,0 +28/12/2013,Cardiff,Sunderland,2,2,D,1,0,H,C Foy,16,21,7,9,6,6,14,10,3,2,0,0 +28/12/2013,Hull,Fulham,6,0,H,0,0,D,R Madley,21,5,13,2,6,1,11,9,0,0,0,0 +28/12/2013,Man City,Crystal Palace,1,0,H,0,0,D,A Marriner,23,9,4,4,12,5,12,7,1,1,0,0 +28/12/2013,Norwich,Man United,0,1,A,0,0,D,P Dowd,15,7,4,3,8,3,11,10,4,3,0,0 +28/12/2013,West Ham,West Brom,3,3,D,1,2,A,M Dean,16,22,6,7,6,5,10,13,3,2,0,0 +29/12/2013,Chelsea,Liverpool,2,1,H,2,1,H,H Webb,11,8,5,4,4,2,17,17,4,1,0,0 +29/12/2013,Everton,Southampton,2,1,H,1,0,H,M Clattenburg,9,12,4,2,2,3,9,14,2,4,0,0 +29/12/2013,Newcastle,Arsenal,0,1,A,0,0,D,L Probert,11,11,3,5,4,1,10,8,0,3,0,0 +29/12/2013,Tottenham,Stoke,3,0,H,1,0,H,K Friend,20,2,7,1,6,1,6,14,2,2,0,0 +01/01/2014,Arsenal,Cardiff,2,0,H,0,0,D,J Moss,28,8,6,2,12,2,6,11,1,2,0,0 +01/01/2014,Crystal Palace,Norwich,1,1,D,1,1,D,M Dean,13,8,4,5,9,4,12,10,2,1,0,1 +01/01/2014,Fulham,West Ham,2,1,H,1,1,D,M Clattenburg,30,11,11,3,12,4,6,5,2,1,0,1 +01/01/2014,Liverpool,Hull,2,0,H,1,0,H,C Pawson,17,10,6,0,3,4,15,15,1,2,0,0 +01/01/2014,Man United,Tottenham,1,2,A,0,1,A,H Webb,16,9,6,4,13,2,7,7,2,1,0,0 +01/01/2014,Southampton,Chelsea,0,3,A,0,0,D,M Atkinson,12,11,2,5,6,8,13,10,4,1,0,0 +01/01/2014,Stoke,Everton,1,1,D,0,0,D,A Marriner,10,16,4,4,2,5,16,11,4,1,0,0 +01/01/2014,Sunderland,Aston Villa,0,1,A,0,1,A,M Jones,17,13,1,5,10,2,8,11,4,3,0,0 +01/01/2014,Swansea,Man City,2,3,A,1,1,D,P Dowd,15,14,4,4,8,6,5,10,2,5,0,0 +01/01/2014,West Brom,Newcastle,1,0,H,0,0,D,L Mason,13,9,4,3,3,4,7,15,0,0,0,1 +11/01/2014,Cardiff,West Ham,0,2,A,0,1,A,L Mason,19,10,7,3,8,7,7,16,1,3,0,1 +11/01/2014,Everton,Norwich,2,0,H,1,0,H,K Friend,23,12,5,3,10,5,7,14,1,2,0,0 +11/01/2014,Fulham,Sunderland,1,4,A,0,2,A,M Dean,17,10,5,6,10,6,15,13,3,3,0,0 +11/01/2014,Hull,Chelsea,0,2,A,0,0,D,M Clattenburg,10,14,2,5,1,7,12,7,2,1,0,0 +11/01/2014,Man United,Swansea,2,0,H,0,0,D,C Foy,16,13,5,2,3,5,9,10,0,2,0,0 +11/01/2014,Southampton,West Brom,1,0,H,0,0,D,H Webb,18,4,5,2,4,4,11,17,1,2,0,0 +11/01/2014,Tottenham,Crystal Palace,2,0,H,0,0,D,M Oliver,10,14,4,2,5,11,12,12,1,2,0,0 +12/01/2014,Newcastle,Man City,0,2,A,0,1,A,M Jones,13,14,5,6,5,6,18,15,4,2,0,0 +12/01/2014,Stoke,Liverpool,3,5,A,2,2,D,A Taylor,13,13,4,9,11,2,10,8,1,1,0,0 +13/01/2014,Aston Villa,Arsenal,1,2,A,0,2,A,N Swarbrick,11,8,5,4,3,5,12,9,2,2,0,0 +18/01/2014,Arsenal,Fulham,2,0,H,0,0,D,L Probert,22,8,8,2,4,4,5,7,0,0,0,0 +18/01/2014,Crystal Palace,Stoke,1,0,H,0,0,D,M Clattenburg,12,17,6,2,4,3,9,13,0,2,0,0 +18/01/2014,Liverpool,Aston Villa,2,2,D,1,2,A,J Moss,14,12,6,3,2,3,14,18,1,4,0,0 +18/01/2014,Man City,Cardiff,4,2,H,2,1,H,N Swarbrick,24,7,9,6,10,7,6,5,2,0,0,0 +18/01/2014,Norwich,Hull,1,0,H,0,0,D,H Webb,25,10,4,0,10,8,8,16,1,2,0,1 +18/01/2014,Sunderland,Southampton,2,2,D,1,2,A,C Foy,10,17,5,8,4,7,8,8,2,0,0,0 +18/01/2014,West Ham,Newcastle,1,3,A,1,2,A,A Marriner,11,19,2,9,4,3,9,6,0,0,0,0 +19/01/2014,Chelsea,Man United,3,1,H,2,0,H,P Dowd,13,10,6,4,5,4,15,21,1,3,0,1 +19/01/2014,Swansea,Tottenham,1,3,A,0,1,A,M Atkinson,13,11,3,5,8,2,6,9,2,1,0,0 +20/01/2014,West Brom,Everton,1,1,D,0,1,A,M Oliver,14,13,6,4,4,2,16,12,3,2,0,0 +28/01/2014,Crystal Palace,Hull,1,0,H,1,0,H,R East,13,13,3,4,8,3,14,8,4,0,0,1 +28/01/2014,Liverpool,Everton,4,0,H,3,0,H,M Atkinson,20,18,9,4,5,8,11,13,0,3,0,0 +28/01/2014,Man United,Cardiff,2,0,H,1,0,H,C Pawson,17,7,5,1,5,5,9,9,0,1,0,0 +28/01/2014,Norwich,Newcastle,0,0,D,0,0,D,C Foy,9,22,2,3,7,7,13,7,1,1,1,1 +28/01/2014,Southampton,Arsenal,2,2,D,1,0,H,L Mason,13,7,5,4,7,5,11,4,1,1,0,1 +28/01/2014,Swansea,Fulham,2,0,H,0,0,D,M Oliver,14,15,7,4,5,8,14,9,1,1,0,0 +29/01/2014,Aston Villa,West Brom,4,3,H,3,3,D,M Clattenburg,10,12,6,4,3,3,15,10,1,3,0,0 +29/01/2014,Chelsea,West Ham,0,0,D,0,0,D,N Swarbrick,39,1,9,1,13,3,12,11,1,3,0,0 +29/01/2014,Sunderland,Stoke,1,0,H,1,0,H,R Madley,16,14,6,5,2,3,16,15,2,2,0,1 +29/01/2014,Tottenham,Man City,1,5,A,0,1,A,A Marriner,4,24,2,10,3,5,11,10,1,4,1,0 +01/02/2014,Cardiff,Norwich,2,1,H,0,1,A,M Clattenburg,14,27,5,6,7,14,8,9,1,4,0,0 +01/02/2014,Everton,Aston Villa,2,1,H,0,1,A,R Madley,13,4,6,1,13,1,4,18,1,0,0,0 +01/02/2014,Fulham,Southampton,0,3,A,0,0,D,M Jones,8,17,4,5,4,5,10,9,0,3,0,0 +01/02/2014,Hull,Tottenham,1,1,D,1,0,H,A Taylor,9,19,4,3,4,10,13,9,1,1,0,0 +01/02/2014,Newcastle,Sunderland,0,3,A,0,2,A,P Dowd,28,16,10,5,7,3,18,13,2,2,0,0 +01/02/2014,Stoke,Man United,2,1,H,1,0,H,N Swarbrick,13,19,6,4,7,10,18,7,2,2,0,0 +01/02/2014,West Ham,Swansea,2,0,H,2,0,H,H Webb,11,20,4,0,8,11,7,7,1,0,1,0 +02/02/2014,Arsenal,Crystal Palace,2,0,H,0,0,D,J Moss,11,10,6,2,6,5,9,14,1,2,0,0 +02/02/2014,West Brom,Liverpool,1,1,D,0,1,A,K Friend,12,12,4,3,3,4,13,15,4,2,0,0 +03/02/2014,Man City,Chelsea,0,1,A,0,1,A,M Dean,25,18,3,6,12,6,11,13,3,3,0,0 +08/02/2014,Aston Villa,West Ham,0,2,A,0,0,D,M Dean,16,10,1,2,8,10,9,8,0,2,0,0 +08/02/2014,Chelsea,Newcastle,3,0,H,2,0,H,H Webb,18,12,8,6,6,5,7,10,0,3,0,0 +08/02/2014,Crystal Palace,West Brom,3,1,H,2,0,H,C Foy,10,16,6,6,4,7,13,13,0,3,0,0 +08/02/2014,Liverpool,Arsenal,5,1,H,4,0,H,M Oliver,22,11,12,6,6,6,14,14,1,2,0,0 +08/02/2014,Norwich,Man City,0,0,D,0,0,D,J Moss,7,15,2,2,4,14,7,6,2,1,0,0 +08/02/2014,Southampton,Stoke,2,2,D,2,2,D,C Pawson,17,5,5,4,7,4,7,14,0,4,0,0 +08/02/2014,Sunderland,Hull,0,2,A,0,1,A,M Jones,7,25,3,9,2,6,12,16,1,3,1,0 +08/02/2014,Swansea,Cardiff,3,0,H,0,0,D,A Marriner,17,7,6,1,5,1,7,8,1,1,0,0 +09/02/2014,Man United,Fulham,2,2,D,0,1,A,K Friend,31,6,9,3,10,1,3,6,0,0,0,0 +09/02/2014,Tottenham,Everton,1,0,H,0,0,D,M Clattenburg,8,11,2,3,9,7,9,13,1,0,0,0 +11/02/2014,Cardiff,Aston Villa,0,0,D,0,0,D,C Foy,12,18,3,6,7,9,5,9,0,0,0,0 +11/02/2014,Hull,Southampton,0,1,A,0,0,D,M Atkinson,12,16,4,7,2,2,6,8,0,3,0,0 +11/02/2014,West Brom,Chelsea,1,1,D,0,1,A,A Taylor,12,15,1,4,5,8,9,13,3,4,0,0 +11/02/2014,West Ham,Norwich,2,0,H,0,0,D,M Oliver,12,13,7,6,5,5,12,16,1,3,0,0 +12/02/2014,Arsenal,Man United,0,0,D,0,0,D,M Clattenburg,17,6,5,2,5,5,10,14,1,2,0,0 +12/02/2014,Fulham,Liverpool,2,3,A,1,1,D,P Dowd,10,21,3,7,7,5,7,9,4,3,0,0 +12/02/2014,Newcastle,Tottenham,0,4,A,0,1,A,N Swarbrick,13,15,5,8,8,6,6,11,0,0,0,0 +12/02/2014,Stoke,Swansea,1,1,D,1,0,H,J Moss,7,13,4,3,2,5,11,12,2,2,0,0 +22/02/2014,Arsenal,Sunderland,4,1,H,3,0,H,A Marriner,12,7,9,3,7,3,10,11,0,1,0,0 +22/02/2014,Cardiff,Hull,0,4,A,0,2,A,H Webb,23,12,5,6,9,2,4,11,1,1,0,0 +22/02/2014,Chelsea,Everton,1,0,H,0,0,D,L Probert,25,8,8,2,8,8,15,12,1,2,0,0 +22/02/2014,Crystal Palace,Man United,0,2,A,0,0,D,M Oliver,15,14,6,5,6,4,12,10,0,2,0,0 +22/02/2014,Man City,Stoke,1,0,H,0,0,D,C Foy,19,7,7,2,8,3,16,13,3,1,0,0 +22/02/2014,West Brom,Fulham,1,1,D,0,1,A,M Dean,16,7,5,3,8,2,13,15,3,3,0,0 +22/02/2014,West Ham,Southampton,3,1,H,2,1,H,M Clattenburg,10,25,5,5,5,11,10,8,0,1,0,0 +23/02/2014,Liverpool,Swansea,4,3,H,3,2,H,M Jones,21,14,10,5,3,5,9,11,1,0,0,0 +23/02/2014,Newcastle,Aston Villa,1,0,H,0,0,D,M Atkinson,23,10,5,2,10,1,12,10,2,2,0,0 +23/02/2014,Norwich,Tottenham,1,0,H,0,0,D,C Pawson,13,9,3,2,5,3,8,15,1,3,0,0 +01/03/2014,Everton,West Ham,1,0,H,0,0,D,J Moss,22,6,3,1,11,5,9,8,1,1,0,0 +01/03/2014,Fulham,Chelsea,1,3,A,0,0,D,M Clattenburg,7,17,3,8,5,4,14,13,2,1,0,0 +01/03/2014,Hull,Newcastle,1,4,A,0,2,A,K Friend,13,17,3,7,6,3,13,10,3,2,0,0 +01/03/2014,Southampton,Liverpool,0,3,A,0,1,A,L Probert,13,12,2,5,3,6,10,6,1,2,0,0 +01/03/2014,Stoke,Arsenal,1,0,H,0,0,D,M Jones,8,10,4,2,3,4,13,11,3,1,0,0 +02/03/2014,Aston Villa,Norwich,4,1,H,4,1,H,A Taylor,10,7,4,4,6,2,12,10,0,3,0,0 +02/03/2014,Swansea,Crystal Palace,1,1,D,1,0,H,M Dean,7,7,5,4,1,4,8,11,1,4,1,0 +02/03/2014,Tottenham,Cardiff,1,0,H,1,0,H,P Dowd,15,8,6,2,4,7,11,12,1,3,0,0 +08/03/2014,Cardiff,Fulham,3,1,H,1,0,H,M Atkinson,19,14,9,6,7,6,9,12,1,0,0,0 +08/03/2014,Chelsea,Tottenham,4,0,H,0,0,D,M Oliver,12,6,4,2,5,4,11,18,2,3,0,1 +08/03/2014,Crystal Palace,Southampton,0,1,A,0,1,A,H Webb,6,11,1,3,8,3,14,18,1,5,0,0 +08/03/2014,Norwich,Stoke,1,1,D,0,0,D,A Marriner,13,6,5,3,3,1,9,12,0,2,0,1 +08/03/2014,West Brom,Man United,0,3,A,0,1,A,J Moss,14,14,3,5,3,5,11,10,1,2,0,0 +15/03/2014,Aston Villa,Chelsea,1,0,H,0,0,D,C Foy,13,15,4,2,4,5,14,8,4,0,0,2 +15/03/2014,Everton,Cardiff,2,1,H,0,0,D,R East,19,10,6,3,10,5,10,10,1,3,0,0 +15/03/2014,Fulham,Newcastle,1,0,H,0,0,D,H Webb,12,12,3,3,9,7,11,6,1,0,0,0 +15/03/2014,Hull,Man City,0,2,A,0,1,A,L Mason,15,8,5,3,3,4,9,14,2,2,0,1 +15/03/2014,Southampton,Norwich,4,2,H,1,0,H,K Friend,16,6,6,3,6,1,11,17,1,2,0,0 +15/03/2014,Stoke,West Ham,3,1,H,1,1,D,C Pawson,17,8,9,2,6,4,13,13,1,1,0,0 +15/03/2014,Sunderland,Crystal Palace,0,0,D,0,0,D,N Swarbrick,19,12,2,5,6,2,16,14,4,4,0,0 +15/03/2014,Swansea,West Brom,1,2,A,1,0,H,M Atkinson,15,13,4,4,4,1,7,13,1,3,0,0 +16/03/2014,Man United,Liverpool,0,3,A,0,1,A,M Clattenburg,13,17,1,7,3,4,13,12,1,4,1,0 +16/03/2014,Tottenham,Arsenal,0,1,A,0,1,A,M Dean,17,7,2,3,5,2,18,11,5,3,0,0 +22/03/2014,Cardiff,Liverpool,3,6,A,2,2,D,N Swarbrick,9,19,3,9,4,6,14,11,2,3,0,0 +22/03/2014,Chelsea,Arsenal,6,0,H,4,0,H,A Marriner,21,12,11,5,7,1,14,8,0,1,0,1 +22/03/2014,Everton,Swansea,3,2,H,1,1,D,M Oliver,9,21,6,7,5,12,9,6,0,0,0,0 +22/03/2014,Hull,West Brom,2,0,H,2,0,H,C Foy,12,17,5,4,4,5,18,13,2,0,0,0 +22/03/2014,Man City,Fulham,5,0,H,1,0,H,J Moss,22,6,12,0,13,2,8,15,0,3,0,1 +22/03/2014,Newcastle,Crystal Palace,1,0,H,0,0,D,L Probert,21,8,8,1,9,5,4,8,0,1,0,0 +22/03/2014,Norwich,Sunderland,2,0,H,2,0,H,P Dowd,14,10,8,2,8,5,16,20,2,4,0,1 +22/03/2014,West Ham,Man United,0,2,A,0,2,A,L Mason,10,16,1,7,12,7,8,8,2,1,0,0 +23/03/2014,Aston Villa,Stoke,1,4,A,1,3,A,M Clattenburg,12,10,3,5,5,3,16,10,5,1,0,0 +23/03/2014,Tottenham,Southampton,3,2,H,1,2,A,A Taylor,15,12,6,4,7,3,9,9,1,2,0,0 +25/03/2014,Arsenal,Swansea,2,2,D,0,1,A,L Probert,13,8,4,2,7,0,11,12,0,1,0,0 +25/03/2014,Man United,Man City,0,3,A,0,1,A,M Oliver,10,13,4,4,5,8,12,8,2,2,0,0 +25/03/2014,Newcastle,Everton,0,3,A,0,1,A,L Mason,16,8,3,6,9,8,8,10,0,1,0,0 +26/03/2014,Liverpool,Sunderland,2,1,H,1,0,H,K Friend,21,12,7,4,3,4,5,15,0,2,0,0 +26/03/2014,West Ham,Hull,2,1,H,1,0,H,M Dean,9,10,3,3,4,5,12,9,0,1,0,1 +29/03/2014,Arsenal,Man City,1,1,D,0,1,A,M Dean,10,15,3,4,6,6,8,11,1,4,0,0 +29/03/2014,Crystal Palace,Chelsea,1,0,H,0,0,D,L Mason,14,21,4,2,2,13,10,4,4,1,0,0 +29/03/2014,Man United,Aston Villa,4,1,H,2,1,H,M Atkinson,9,10,6,1,0,5,10,16,2,4,0,0 +29/03/2014,Southampton,Newcastle,4,0,H,1,0,H,A Marriner,23,5,11,2,7,5,5,6,0,1,0,0 +29/03/2014,Stoke,Hull,1,0,H,0,0,D,N Swarbrick,15,5,3,3,8,1,9,9,2,0,0,0 +29/03/2014,Swansea,Norwich,3,0,H,2,0,H,J Moss,16,8,7,4,7,4,15,9,1,2,0,0 +29/03/2014,West Brom,Cardiff,3,3,D,2,1,H,M Oliver,16,11,6,5,4,1,12,13,1,1,0,0 +30/03/2014,Fulham,Everton,1,3,A,0,0,D,A Taylor,23,14,8,7,5,6,7,4,1,2,0,0 +30/03/2014,Liverpool,Tottenham,4,0,H,2,0,H,P Dowd,11,15,5,4,5,7,9,9,0,2,0,0 +31/03/2014,Sunderland,West Ham,1,2,A,0,1,A,H Webb,26,11,7,6,8,3,11,14,2,4,0,0 +05/04/2014,Aston Villa,Fulham,1,2,A,0,0,D,M Oliver,12,16,6,8,2,7,10,13,3,2,0,0 +05/04/2014,Cardiff,Crystal Palace,0,3,A,0,1,A,P Dowd,13,13,4,8,10,3,5,13,2,4,0,0 +05/04/2014,Chelsea,Stoke,3,0,H,1,0,H,L Probert,18,4,8,2,7,3,8,18,0,0,0,0 +05/04/2014,Hull,Swansea,1,0,H,1,0,H,H Webb,8,12,4,2,7,2,6,18,0,2,0,0 +05/04/2014,Man City,Southampton,4,1,H,3,1,H,C Foy,15,5,8,1,5,9,9,15,3,0,0,0 +05/04/2014,Newcastle,Man United,0,4,A,0,1,A,K Friend,14,8,4,4,4,5,16,6,1,0,0,0 +05/04/2014,Norwich,West Brom,0,1,A,0,1,A,M Clattenburg,12,8,4,5,7,3,10,11,1,0,0,0 +06/04/2014,Everton,Arsenal,3,0,H,2,0,H,M Atkinson,14,15,8,5,3,6,6,5,1,2,0,0 +06/04/2014,West Ham,Liverpool,1,2,A,1,1,D,A Taylor,11,17,1,8,4,4,10,9,4,0,0,0 +07/04/2014,Tottenham,Sunderland,5,1,H,1,1,D,L Mason,29,8,11,4,8,2,12,8,0,2,0,0 +12/04/2014,Crystal Palace,Aston Villa,1,0,H,0,0,D,H Webb,14,7,4,4,2,4,7,12,0,2,0,0 +12/04/2014,Fulham,Norwich,1,0,H,1,0,H,M Dean,14,13,3,3,8,8,10,12,1,3,0,0 +12/04/2014,Southampton,Cardiff,0,1,A,0,0,D,J Moss,19,6,7,4,5,5,9,7,0,0,0,0 +12/04/2014,Stoke,Newcastle,1,0,H,1,0,H,M Jones,10,13,3,1,4,5,12,11,1,1,0,0 +12/04/2014,Sunderland,Everton,0,1,A,0,0,D,L Probert,24,20,6,3,6,6,17,15,3,1,0,0 +12/04/2014,West Brom,Tottenham,3,3,D,3,1,H,N Swarbrick,6,20,5,8,2,2,11,8,4,2,0,0 +13/04/2014,Liverpool,Man City,3,2,H,2,0,H,M Clattenburg,10,13,4,5,7,7,7,9,1,3,1,0 +13/04/2014,Swansea,Chelsea,0,1,A,0,0,D,P Dowd,7,26,4,3,5,12,8,13,0,2,1,0 +15/04/2014,Arsenal,West Ham,3,1,H,1,1,D,K Friend,14,12,8,2,4,3,14,12,2,2,0,0 +16/04/2014,Everton,Crystal Palace,2,3,A,0,1,A,A Marriner,17,7,5,4,13,3,6,10,1,2,0,0 +16/04/2014,Man City,Sunderland,2,2,D,1,0,H,M Atkinson,16,11,4,3,3,6,14,10,1,2,0,0 +19/04/2014,Aston Villa,Southampton,0,0,D,0,0,D,L Mason,6,3,1,2,5,3,11,11,0,2,0,0 +19/04/2014,Cardiff,Stoke,1,1,D,0,1,A,H Webb,13,14,4,5,12,7,6,12,2,3,0,0 +19/04/2014,Chelsea,Sunderland,1,2,A,1,1,D,M Dean,31,7,15,3,10,4,11,14,1,2,0,0 +19/04/2014,Newcastle,Swansea,1,2,A,1,1,D,C Foy,14,10,4,2,2,8,6,9,2,0,0,0 +19/04/2014,Tottenham,Fulham,3,1,H,1,1,D,L Probert,14,11,5,4,4,5,8,11,0,1,0,0 +19/04/2014,West Ham,Crystal Palace,0,1,A,0,0,D,M Atkinson,13,11,4,3,3,11,7,16,1,1,0,0 +20/04/2014,Everton,Man United,2,0,H,2,0,H,M Clattenburg,17,9,5,2,6,5,9,8,2,2,0,0 +20/04/2014,Hull,Arsenal,0,3,A,0,2,A,J Moss,13,12,4,6,7,0,10,7,1,1,0,0 +20/04/2014,Norwich,Liverpool,2,3,A,0,2,A,A Marriner,13,14,5,6,8,3,16,10,3,2,0,0 +21/04/2014,Man City,West Brom,3,1,H,3,1,H,P Dowd,19,10,6,3,8,2,9,7,1,1,0,0 +26/04/2014,Fulham,Hull,2,2,D,0,0,D,L Mason,10,8,3,4,7,2,8,7,1,3,0,0 +26/04/2014,Man United,Norwich,4,0,H,1,0,H,L Probert,25,9,11,2,5,3,13,9,1,1,0,0 +26/04/2014,Southampton,Everton,2,0,H,2,0,H,M Oliver,14,9,4,2,4,7,13,12,0,2,0,0 +26/04/2014,Stoke,Tottenham,0,1,A,0,1,A,A Marriner,17,16,3,4,4,3,11,6,1,2,1,0 +26/04/2014,Swansea,Aston Villa,4,1,H,2,1,H,M Clattenburg,11,11,8,3,9,6,9,12,1,3,0,0 +26/04/2014,West Brom,West Ham,1,0,H,1,0,H,M Dean,19,9,6,3,7,6,14,8,0,1,0,0 +27/04/2014,Crystal Palace,Man City,0,2,A,0,2,A,H Webb,3,16,2,6,4,8,9,10,3,1,0,0 +27/04/2014,Liverpool,Chelsea,0,2,A,0,1,A,M Atkinson,26,11,8,4,14,3,9,7,0,4,0,0 +27/04/2014,Sunderland,Cardiff,4,0,H,2,0,H,P Dowd,21,6,7,1,11,5,10,11,5,3,0,1 +28/04/2014,Arsenal,Newcastle,3,0,H,2,0,H,N Swarbrick,20,8,8,3,14,0,9,8,3,2,0,0 +03/05/2014,Aston Villa,Hull,3,1,H,3,1,H,M Dean,15,7,9,2,8,5,14,9,0,0,0,0 +03/05/2014,Everton,Man City,2,3,A,1,2,A,L Probert,9,18,4,6,4,3,3,9,0,3,0,0 +03/05/2014,Man United,Sunderland,0,1,A,0,1,A,H Webb,17,8,2,1,8,0,11,12,2,1,0,0 +03/05/2014,Newcastle,Cardiff,3,0,H,1,0,H,M Atkinson,22,16,6,3,10,6,9,6,1,0,0,0 +03/05/2014,Stoke,Fulham,4,1,H,1,0,H,C Foy,23,10,8,2,3,5,9,13,0,2,0,0 +03/05/2014,Swansea,Southampton,0,1,A,0,0,D,A Taylor,12,18,3,4,3,5,11,5,1,1,0,0 +03/05/2014,West Ham,Tottenham,2,0,H,2,0,H,P Dowd,20,12,8,3,10,6,10,16,0,4,0,1 +04/05/2014,Arsenal,West Brom,1,0,H,1,0,H,M Jones,15,11,4,1,9,10,9,6,2,2,0,0 +04/05/2014,Chelsea,Norwich,0,0,D,0,0,D,N Swarbrick,23,6,4,3,5,2,9,17,1,3,0,0 +05/05/2014,Crystal Palace,Liverpool,3,3,D,0,1,A,M Clattenburg,10,26,6,9,7,7,7,12,2,3,0,0 +06/05/2014,Man United,Hull,3,1,H,1,0,H,C Pawson,18,2,7,2,8,0,13,9,1,1,0,0 +07/05/2014,Man City,Aston Villa,4,0,H,0,0,D,M Oliver,18,5,9,0,7,2,8,9,1,2,0,0 +07/05/2014,Sunderland,West Brom,2,0,H,2,0,H,L Mason,10,13,4,3,5,0,14,12,0,0,0,0 +11/05/2014,Cardiff,Chelsea,1,2,A,1,0,H,M Oliver,10,28,4,7,3,8,7,9,1,2,0,0 +11/05/2014,Fulham,Crystal Palace,2,2,D,0,1,A,K Friend,15,15,5,6,6,4,11,15,3,1,0,0 +11/05/2014,Hull,Everton,0,2,A,0,1,A,H Webb,12,11,3,4,6,4,8,8,0,1,0,0 +11/05/2014,Liverpool,Newcastle,2,1,H,0,1,A,P Dowd,13,8,5,2,6,2,8,16,1,3,0,2 +11/05/2014,Man City,West Ham,2,0,H,1,0,H,M Atkinson,28,3,7,0,12,1,7,9,2,2,0,0 +11/05/2014,Norwich,Arsenal,0,2,A,0,0,D,L Mason,11,14,5,8,4,4,6,5,1,0,0,0 +11/05/2014,Southampton,Man United,1,1,D,1,0,H,M Dean,15,8,6,2,6,2,16,5,3,2,0,0 +11/05/2014,Sunderland,Swansea,1,3,A,0,2,A,C Foy,20,8,4,4,6,3,14,15,2,3,0,0 +11/05/2014,Tottenham,Aston Villa,3,0,H,3,0,H,J Moss,12,4,6,1,5,1,11,11,1,0,0,0 +11/05/2014,West Brom,Stoke,1,2,A,0,1,A,L Probert,17,15,4,4,11,6,4,7,0,0,0,0 +16/08/2014,Arsenal,Crystal Palace,2,1,H,1,1,D,J Moss,14,4,6,2,9,3,13,19,2,2,0,1 +16/08/2014,Leicester,Everton,2,2,D,1,2,A,M Jones,11,13,3,3,3,6,16,10,1,1,0,0 +16/08/2014,Man United,Swansea,1,2,A,0,1,A,M Dean,14,5,5,4,4,0,14,20,2,4,0,0 +16/08/2014,QPR,Hull,0,1,A,0,0,D,C Pawson,19,11,6,4,8,9,10,10,1,2,0,0 +16/08/2014,Stoke,Aston Villa,0,1,A,0,0,D,A Taylor,12,7,2,2,2,8,14,9,0,3,0,0 +16/08/2014,West Brom,Sunderland,2,2,D,1,1,D,N Swarbrick,10,7,5,2,6,3,18,9,3,1,0,0 +16/08/2014,West Ham,Tottenham,0,1,A,0,0,D,C Foy,18,10,4,4,8,5,12,10,1,0,1,1 +17/08/2014,Liverpool,Southampton,2,1,H,1,0,H,M Clattenburg,12,12,5,6,2,6,8,11,1,2,0,0 +17/08/2014,Newcastle,Man City,0,2,A,0,1,A,M Atkinson,12,13,0,5,3,3,8,11,1,5,0,0 +18/08/2014,Burnley,Chelsea,1,3,A,1,3,A,M Oliver,9,11,2,3,4,3,6,7,1,1,0,0 +23/08/2014,Aston Villa,Newcastle,0,0,D,0,0,D,M Dean,9,11,0,4,7,7,11,8,4,0,0,1 +23/08/2014,Chelsea,Leicester,2,0,H,0,0,D,L Mason,27,6,10,4,8,7,11,11,0,1,0,0 +23/08/2014,Crystal Palace,West Ham,1,3,A,0,2,A,M Clattenburg,9,17,4,8,6,9,14,11,1,3,0,0 +23/08/2014,Everton,Arsenal,2,2,D,2,0,H,K Friend,8,13,2,3,3,3,10,18,1,4,0,0 +23/08/2014,Southampton,West Brom,0,0,D,0,0,D,R East,8,8,2,2,1,5,14,15,1,2,0,0 +23/08/2014,Swansea,Burnley,1,0,H,1,0,H,C Pawson,10,12,5,1,2,3,14,13,2,1,0,0 +24/08/2014,Hull,Stoke,1,1,D,1,0,H,J Moss,9,19,2,3,4,5,10,10,1,3,1,0 +24/08/2014,Sunderland,Man United,1,1,D,1,1,D,M Atkinson,11,10,3,3,4,4,10,15,0,2,0,0 +24/08/2014,Tottenham,QPR,4,0,H,3,0,H,A Taylor,18,9,5,1,7,6,12,6,0,1,0,0 +25/08/2014,Man City,Liverpool,3,1,H,1,0,H,M Oliver,9,11,4,3,6,7,13,7,1,1,0,0 +30/08/2014,Burnley,Man United,0,0,D,0,0,D,C Foy,9,7,3,2,3,6,10,14,2,2,0,0 +30/08/2014,Everton,Chelsea,3,6,A,1,2,A,J Moss,17,12,7,8,8,2,7,16,1,3,0,0 +30/08/2014,Man City,Stoke,0,1,A,0,0,D,L Mason,16,7,2,2,11,4,14,5,2,1,0,0 +30/08/2014,Newcastle,Crystal Palace,3,3,D,1,1,D,M Jones,18,12,4,7,11,4,12,19,1,3,0,0 +30/08/2014,QPR,Sunderland,1,0,H,1,0,H,R Madley,18,16,5,6,4,6,8,9,1,2,0,0 +30/08/2014,Swansea,West Brom,3,0,H,2,0,H,P Tierney,15,16,8,2,0,7,10,8,1,2,0,0 +30/08/2014,West Ham,Southampton,1,3,A,1,1,D,M Dean,4,18,2,8,2,9,11,13,2,1,0,0 +31/08/2014,Aston Villa,Hull,2,1,H,2,0,H,M Clattenburg,11,9,4,3,3,7,9,10,1,4,0,0 +31/08/2014,Leicester,Arsenal,1,1,D,1,1,D,A Taylor,9,24,3,6,5,10,14,6,3,1,0,0 +31/08/2014,Tottenham,Liverpool,0,3,A,0,1,A,P Dowd,7,17,1,8,7,3,14,11,0,4,0,0 +13/09/2014,Arsenal,Man City,2,2,D,0,1,A,M Clattenburg,15,15,6,8,0,3,11,15,3,4,0,0 +13/09/2014,Chelsea,Swansea,4,2,H,1,1,D,K Friend,29,11,9,2,7,4,12,11,0,3,0,0 +13/09/2014,Crystal Palace,Burnley,0,0,D,0,0,D,M Dean,14,11,3,3,5,5,12,10,2,3,0,0 +13/09/2014,Liverpool,Aston Villa,0,1,A,0,1,A,L Mason,18,5,1,1,7,6,9,10,2,1,0,0 +13/09/2014,Southampton,Newcastle,4,0,H,2,0,H,C Foy,10,9,7,4,5,5,15,7,0,2,0,0 +13/09/2014,Stoke,Leicester,0,1,A,0,0,D,M Oliver,24,8,4,1,13,3,8,15,3,0,0,0 +13/09/2014,Sunderland,Tottenham,2,2,D,1,1,D,C Pawson,6,15,1,6,5,5,10,13,5,1,0,0 +13/09/2014,West Brom,Everton,0,2,A,0,1,A,A Taylor,11,11,1,4,9,1,12,8,2,3,0,0 +14/09/2014,Man United,QPR,4,0,H,3,0,H,P Dowd,19,9,9,2,3,1,11,8,1,0,0,0 +15/09/2014,Hull,West Ham,2,2,D,1,0,H,M Atkinson,11,16,6,4,3,10,12,7,3,1,0,0 +20/09/2014,Aston Villa,Arsenal,0,3,A,0,3,A,M Jones,6,9,2,3,5,5,11,9,1,3,0,0 +20/09/2014,Burnley,Sunderland,0,0,D,0,0,D,A Taylor,17,14,6,6,5,1,14,14,1,2,0,0 +20/09/2014,Newcastle,Hull,2,2,D,0,0,D,N Swarbrick,25,10,7,3,4,2,11,11,4,2,0,0 +20/09/2014,QPR,Stoke,2,2,D,1,1,D,M Atkinson,21,8,5,4,10,1,6,15,2,4,0,0 +20/09/2014,Swansea,Southampton,0,1,A,0,0,D,J Moss,5,11,2,4,2,4,11,15,0,4,1,0 +20/09/2014,West Ham,Liverpool,3,1,H,2,1,H,C Pawson,13,11,7,5,7,5,16,11,4,1,0,0 +21/09/2014,Everton,Crystal Palace,2,3,A,1,1,D,M Oliver,17,10,6,3,7,1,8,13,2,2,0,0 +21/09/2014,Leicester,Man United,5,3,H,1,2,A,M Clattenburg,15,16,5,5,2,4,11,9,1,1,0,1 +21/09/2014,Man City,Chelsea,1,1,D,0,0,D,M Dean,16,6,4,2,14,2,16,12,3,4,1,0 +21/09/2014,Tottenham,West Brom,0,1,A,0,0,D,K Friend,7,10,1,4,9,7,12,7,2,2,0,0 +27/09/2014,Arsenal,Tottenham,1,1,D,0,0,D,M Oliver,16,6,6,4,15,5,7,16,3,6,0,0 +27/09/2014,Chelsea,Aston Villa,3,0,H,1,0,H,P Dowd,17,7,8,1,9,2,11,9,2,2,0,0 +27/09/2014,Crystal Palace,Leicester,2,0,H,0,0,D,K Stroud,16,7,6,1,6,6,14,9,0,2,0,0 +27/09/2014,Hull,Man City,2,4,A,2,2,D,A Taylor,10,20,5,6,3,8,10,6,0,2,0,0 +27/09/2014,Liverpool,Everton,1,1,D,0,0,D,M Atkinson,24,11,8,5,8,2,8,9,2,1,0,0 +27/09/2014,Man United,West Ham,2,1,H,2,1,H,L Mason,8,13,3,4,7,10,10,12,1,3,1,0 +27/09/2014,Southampton,QPR,2,1,H,0,0,D,M Clattenburg,19,10,9,2,6,3,12,11,2,1,0,0 +27/09/2014,Sunderland,Swansea,0,0,D,0,0,D,C Foy,15,7,3,1,10,1,13,15,2,1,0,1 +28/09/2014,West Brom,Burnley,4,0,H,2,0,H,J Moss,20,10,6,2,10,2,7,9,1,2,0,0 +29/09/2014,Stoke,Newcastle,1,0,H,1,0,H,C Pawson,12,16,3,1,2,10,20,15,1,2,0,0 +04/10/2014,Aston Villa,Man City,0,2,A,0,0,D,C Foy,6,27,1,7,0,7,4,6,0,1,0,0 +04/10/2014,Hull,Crystal Palace,2,0,H,0,0,D,M Dean,15,11,2,2,6,7,6,14,1,2,0,0 +04/10/2014,Leicester,Burnley,2,2,D,2,1,H,P Dowd,15,10,6,4,9,3,16,12,2,2,0,0 +04/10/2014,Liverpool,West Brom,2,1,H,1,0,H,M Oliver,19,8,7,4,5,5,12,15,2,2,0,0 +04/10/2014,Sunderland,Stoke,3,1,H,2,1,H,N Swarbrick,8,11,3,5,7,4,10,17,3,4,0,0 +04/10/2014,Swansea,Newcastle,2,2,D,1,1,D,L Mason,11,9,5,4,3,1,11,15,1,3,0,0 +05/10/2014,Chelsea,Arsenal,2,0,H,1,0,H,M Atkinson,5,10,3,0,2,2,14,10,4,3,0,0 +05/10/2014,Man United,Everton,2,1,H,1,0,H,K Friend,15,10,4,6,11,6,18,11,4,3,0,0 +05/10/2014,Tottenham,Southampton,1,0,H,1,0,H,M Jones,9,7,4,3,10,5,15,9,2,1,0,0 +05/10/2014,West Ham,QPR,2,0,H,1,0,H,A Taylor,11,10,3,3,7,5,10,11,2,2,0,0 +18/10/2014,Arsenal,Hull,2,2,D,1,1,D,R East,25,4,9,4,10,0,9,13,2,2,0,0 +18/10/2014,Burnley,West Ham,1,3,A,0,0,D,K Friend,17,22,3,6,5,7,3,10,0,1,0,0 +18/10/2014,Crystal Palace,Chelsea,1,2,A,0,1,A,C Pawson,7,14,4,5,3,2,13,7,1,1,1,1 +18/10/2014,Everton,Aston Villa,3,0,H,1,0,H,A Taylor,15,10,6,2,3,4,11,16,2,2,0,0 +18/10/2014,Man City,Tottenham,4,1,H,2,1,H,J Moss,20,19,12,7,3,4,11,9,1,2,0,1 +18/10/2014,Newcastle,Leicester,1,0,H,0,0,D,M Atkinson,19,7,6,2,10,7,13,15,0,3,0,0 +18/10/2014,Southampton,Sunderland,8,0,H,3,0,H,A Marriner,20,12,11,1,5,6,11,8,1,3,0,0 +19/10/2014,QPR,Liverpool,2,3,A,0,0,D,P Dowd,15,15,6,5,7,2,10,15,2,3,0,0 +19/10/2014,Stoke,Swansea,2,1,H,1,1,D,M Oliver,17,13,3,5,3,5,17,7,4,1,0,0 +20/10/2014,West Brom,Man United,2,2,D,1,0,H,M Dean,8,22,2,7,0,11,6,8,1,2,0,0 +25/10/2014,Liverpool,Hull,0,0,D,0,0,D,N Swarbrick,17,6,4,3,14,1,12,17,3,2,0,0 +25/10/2014,Southampton,Stoke,1,0,H,1,0,H,J Moss,20,8,2,1,6,3,10,12,0,2,0,0 +25/10/2014,Sunderland,Arsenal,0,2,A,0,1,A,K Friend,11,15,3,5,3,3,11,9,3,3,0,0 +25/10/2014,Swansea,Leicester,2,0,H,1,0,H,M Jones,7,9,3,4,2,2,7,10,0,0,0,0 +25/10/2014,West Brom,Crystal Palace,2,2,D,0,2,A,M Clattenburg,17,8,4,6,6,3,14,8,3,1,0,0 +25/10/2014,West Ham,Man City,2,1,H,1,0,H,M Atkinson,12,21,4,5,4,8,12,14,2,1,0,0 +26/10/2014,Burnley,Everton,1,3,A,1,2,A,A Marriner,14,15,3,6,5,4,12,7,4,2,0,0 +26/10/2014,Man United,Chelsea,1,1,D,0,0,D,P Dowd,19,9,7,4,4,7,13,14,3,5,0,1 +26/10/2014,Tottenham,Newcastle,1,2,A,1,0,H,A Taylor,17,8,2,2,10,2,9,12,2,4,0,0 +27/10/2014,QPR,Aston Villa,2,0,H,1,0,H,L Mason,11,15,4,6,2,4,11,14,0,2,0,0 +01/11/2014,Arsenal,Burnley,3,0,H,0,0,D,C Pawson,32,6,13,2,18,1,5,9,0,1,0,0 +01/11/2014,Chelsea,QPR,2,1,H,1,0,H,M Jones,18,7,8,1,13,2,8,12,0,0,0,0 +01/11/2014,Everton,Swansea,0,0,D,0,0,D,K Friend,17,8,3,0,11,0,10,15,2,3,0,1 +01/11/2014,Hull,Southampton,0,1,A,0,1,A,M Atkinson,8,10,1,3,7,4,6,9,1,0,0,0 +01/11/2014,Leicester,West Brom,0,1,A,0,0,D,S Attwell,14,8,5,3,8,3,5,12,0,1,0,0 +01/11/2014,Newcastle,Liverpool,1,0,H,0,0,D,A Marriner,14,6,3,3,7,2,13,8,4,3,0,0 +01/11/2014,Stoke,West Ham,2,2,D,1,0,H,C Foy,21,10,6,2,9,2,14,13,1,1,0,0 +02/11/2014,Aston Villa,Tottenham,1,2,A,1,0,H,N Swarbrick,12,18,1,7,3,9,12,11,3,3,1,0 +02/11/2014,Man City,Man United,1,0,H,0,0,D,M Oliver,16,9,6,2,7,4,15,9,3,1,0,1 +03/11/2014,Crystal Palace,Sunderland,1,3,A,0,1,A,P Dowd,9,8,1,7,11,2,12,19,0,3,1,0 +08/11/2014,Burnley,Hull,1,0,H,0,0,D,M Clattenburg,14,12,3,1,7,1,17,17,6,4,0,0 +08/11/2014,Liverpool,Chelsea,1,2,A,1,1,D,A Taylor,12,15,4,5,5,7,9,12,2,5,0,0 +08/11/2014,Man United,Crystal Palace,1,0,H,0,0,D,C Foy,23,6,5,1,11,4,8,12,1,3,0,0 +08/11/2014,QPR,Man City,2,2,D,1,1,D,M Dean,17,23,4,6,7,7,9,10,4,2,0,0 +08/11/2014,Southampton,Leicester,2,0,H,0,0,D,M Oliver,18,5,6,0,13,4,10,12,2,2,0,0 +08/11/2014,West Ham,Aston Villa,0,0,D,0,0,D,J Moss,21,12,5,4,13,2,9,10,0,3,0,0 +09/11/2014,Sunderland,Everton,1,1,D,0,0,D,L Mason,10,16,3,7,5,4,11,13,1,2,0,0 +09/11/2014,Swansea,Arsenal,2,1,H,0,0,D,P Dowd,11,12,5,4,6,10,10,16,5,5,0,0 +09/11/2014,Tottenham,Stoke,1,2,A,0,2,A,M Jones,14,11,4,4,5,5,20,13,3,2,1,0 +09/11/2014,West Brom,Newcastle,0,2,A,0,1,A,C Pawson,10,8,2,3,7,4,12,6,0,0,0,0 +22/11/2014,Arsenal,Man United,1,2,A,0,0,D,M Dean,23,12,9,2,11,5,12,8,2,1,0,0 +22/11/2014,Chelsea,West Brom,2,0,H,2,0,H,L Mason,21,5,8,1,15,0,7,9,1,0,0,1 +22/11/2014,Everton,West Ham,2,1,H,1,0,H,M Clattenburg,14,14,4,3,1,5,10,13,2,3,0,0 +22/11/2014,Leicester,Sunderland,0,0,D,0,0,D,R Madley,13,10,4,5,6,9,18,12,2,3,0,0 +22/11/2014,Man City,Swansea,2,1,H,1,1,D,N Swarbrick,23,8,10,3,7,3,13,9,2,1,0,0 +22/11/2014,Newcastle,QPR,1,0,H,0,0,D,C Foy,14,13,6,4,16,7,12,15,2,3,0,0 +22/11/2014,Stoke,Burnley,1,2,A,1,2,A,M Atkinson,25,5,4,4,14,0,10,9,2,2,0,0 +23/11/2014,Crystal Palace,Liverpool,3,1,H,1,1,D,J Moss,15,12,5,1,4,3,21,10,1,2,0,0 +23/11/2014,Hull,Tottenham,1,2,A,1,0,H,C Pawson,10,23,2,6,3,9,15,9,3,1,1,0 +24/11/2014,Aston Villa,Southampton,1,1,D,1,0,H,P Dowd,7,15,1,4,2,8,15,17,1,2,0,0 +29/11/2014,Burnley,Aston Villa,1,1,D,0,1,A,G Scott,11,18,4,7,1,5,8,8,3,4,0,0 +29/11/2014,Liverpool,Stoke,1,0,H,0,0,D,C Pawson,16,13,3,4,5,5,11,9,0,3,0,0 +29/11/2014,Man United,Hull,3,0,H,2,0,H,A Taylor,12,4,6,2,7,0,12,12,2,2,0,0 +29/11/2014,QPR,Leicester,3,2,H,2,1,H,R East,32,19,6,5,11,5,8,17,3,2,0,0 +29/11/2014,Sunderland,Chelsea,0,0,D,0,0,D,K Friend,12,24,3,6,2,8,13,10,3,2,0,0 +29/11/2014,Swansea,Crystal Palace,1,1,D,1,1,D,M Atkinson,18,11,4,5,4,6,6,12,0,2,0,0 +29/11/2014,West Brom,Arsenal,0,1,A,0,0,D,C Foy,9,17,1,5,4,4,9,10,2,1,0,0 +29/11/2014,West Ham,Newcastle,1,0,H,0,0,D,M Dean,12,7,3,1,6,2,9,16,3,2,0,1 +30/11/2014,Southampton,Man City,0,3,A,0,0,D,M Jones,13,15,4,6,8,6,10,13,1,2,0,1 +30/11/2014,Tottenham,Everton,2,1,H,2,1,H,M Oliver,13,10,6,5,3,4,15,5,5,1,0,0 +02/12/2014,Burnley,Newcastle,1,1,D,1,0,H,P Dowd,12,11,2,4,2,5,12,12,2,2,0,0 +02/12/2014,Crystal Palace,Aston Villa,0,1,A,0,1,A,M Oliver,17,5,5,3,8,2,16,15,2,2,0,0 +02/12/2014,Leicester,Liverpool,1,3,A,1,1,D,L Mason,20,11,4,3,7,4,11,10,1,2,1,0 +02/12/2014,Man United,Stoke,2,1,H,1,1,D,J Moss,10,5,3,4,2,2,8,13,2,4,0,0 +02/12/2014,Swansea,QPR,2,0,H,0,0,D,K Friend,18,5,7,1,9,2,8,12,2,2,0,0 +02/12/2014,West Brom,West Ham,1,2,A,1,2,A,M Jones,23,11,5,6,7,3,11,17,2,1,0,0 +03/12/2014,Arsenal,Southampton,1,0,H,0,0,D,A Marriner,19,6,8,1,9,4,7,14,0,2,0,0 +03/12/2014,Chelsea,Tottenham,3,0,H,2,0,H,M Dean,12,10,8,2,1,6,9,9,1,1,0,0 +03/12/2014,Everton,Hull,1,1,D,1,0,H,R Madley,13,8,5,1,6,4,8,12,1,1,0,0 +03/12/2014,Sunderland,Man City,1,4,A,1,2,A,C Pawson,8,15,2,4,2,0,9,8,1,1,0,0 +06/12/2014,Hull,West Brom,0,0,D,0,0,D,M Oliver,12,6,2,3,3,2,10,16,1,1,0,0 +06/12/2014,Liverpool,Sunderland,0,0,D,0,0,D,N Swarbrick,15,7,2,1,5,7,12,15,1,3,0,0 +06/12/2014,Man City,Everton,1,0,H,1,0,H,A Marriner,19,9,3,2,11,7,8,11,3,3,0,0 +06/12/2014,Newcastle,Chelsea,2,1,H,0,0,D,M Atkinson,9,26,3,8,1,10,8,6,2,3,1,0 +06/12/2014,QPR,Burnley,2,0,H,0,0,D,J Moss,11,15,6,3,3,11,12,12,1,1,1,0 +06/12/2014,Stoke,Arsenal,3,2,H,3,0,H,A Taylor,13,15,4,6,3,9,17,14,3,2,0,1 +06/12/2014,Tottenham,Crystal Palace,0,0,D,0,0,D,L Mason,14,16,3,4,2,12,11,10,1,0,0,0 +07/12/2014,Aston Villa,Leicester,2,1,H,1,1,D,C Pawson,16,10,7,4,5,5,9,14,4,4,0,1 +07/12/2014,West Ham,Swansea,3,1,H,1,1,D,C Foy,15,11,7,2,7,4,11,18,0,1,0,1 +08/12/2014,Southampton,Man United,1,2,A,1,1,D,K Friend,15,3,4,2,5,1,12,9,2,1,0,0 +13/12/2014,Arsenal,Newcastle,4,1,H,1,0,H,L Mason,17,10,4,4,9,6,14,15,2,2,0,0 +13/12/2014,Burnley,Southampton,1,0,H,0,0,D,M Clattenburg,7,15,2,4,2,5,15,11,1,3,0,0 +13/12/2014,Chelsea,Hull,2,0,H,1,0,H,C Foy,12,8,3,0,5,4,13,13,3,3,0,1 +13/12/2014,Crystal Palace,Stoke,1,1,D,1,1,D,K Friend,10,7,3,3,9,8,12,11,2,0,0,0 +13/12/2014,Leicester,Man City,0,1,A,0,1,A,J Moss,7,9,1,4,8,2,15,13,0,2,0,0 +13/12/2014,Sunderland,West Ham,1,1,D,1,1,D,P Dowd,13,19,4,8,6,8,11,18,2,2,0,0 +13/12/2014,West Brom,Aston Villa,1,0,H,0,0,D,M Dean,13,7,5,2,4,2,17,12,4,2,0,1 +14/12/2014,Man United,Liverpool,3,0,H,2,0,H,M Atkinson,11,19,6,9,2,7,13,14,4,3,0,0 +14/12/2014,Swansea,Tottenham,1,2,A,0,1,A,R Madley,15,10,3,4,7,9,7,17,3,4,0,0 +15/12/2014,Everton,QPR,3,1,H,2,0,H,N Swarbrick,13,17,5,4,1,8,11,12,1,1,0,0 +20/12/2014,Aston Villa,Man United,1,1,D,1,0,H,L Mason,10,16,4,9,3,9,9,10,1,1,1,0 +20/12/2014,Hull,Swansea,0,1,A,0,1,A,M Clattenburg,11,13,3,3,4,5,14,16,2,2,0,0 +20/12/2014,Man City,Crystal Palace,3,0,H,0,0,D,P Dowd,15,6,3,1,8,6,17,5,2,1,0,0 +20/12/2014,QPR,West Brom,3,2,H,1,2,A,C Pawson,17,16,7,6,5,11,16,8,1,0,0,0 +20/12/2014,Southampton,Everton,3,0,H,1,0,H,J Moss,11,13,3,4,4,6,11,10,1,1,0,0 +20/12/2014,Tottenham,Burnley,2,1,H,2,1,H,M Jones,23,9,6,5,9,6,9,8,1,0,0,0 +20/12/2014,West Ham,Leicester,2,0,H,1,0,H,M Atkinson,10,10,2,4,9,4,8,17,0,3,0,0 +21/12/2014,Liverpool,Arsenal,2,2,D,1,1,D,M Oliver,27,7,10,3,10,6,6,14,0,3,1,0 +21/12/2014,Newcastle,Sunderland,0,1,A,0,0,D,A Taylor,15,16,7,3,7,1,15,15,4,4,0,0 +22/12/2014,Stoke,Chelsea,0,2,A,0,1,A,N Swarbrick,13,15,2,6,6,5,11,4,3,0,0,0 +26/12/2014,Arsenal,QPR,2,1,H,1,0,H,M Atkinson,15,16,5,2,6,2,6,15,1,4,1,0 +26/12/2014,Burnley,Liverpool,0,1,A,0,0,D,A Taylor,16,10,0,3,8,2,7,5,0,1,0,0 +26/12/2014,Chelsea,West Ham,2,0,H,1,0,H,M Oliver,28,6,9,0,8,6,8,14,0,3,0,0 +26/12/2014,Crystal Palace,Southampton,1,3,A,0,1,A,M Dean,11,13,4,6,8,4,15,10,1,0,0,0 +26/12/2014,Everton,Stoke,0,1,A,0,1,A,L Mason,16,12,5,2,9,3,9,15,2,3,0,0 +26/12/2014,Leicester,Tottenham,1,2,A,0,1,A,N Swarbrick,22,8,6,3,11,1,10,14,1,2,0,0 +26/12/2014,Man United,Newcastle,3,1,H,2,0,H,M Jones,9,8,4,4,4,5,16,9,1,2,0,0 +26/12/2014,Sunderland,Hull,1,3,A,1,1,D,A Marriner,12,13,3,5,10,4,12,6,4,2,0,0 +26/12/2014,Swansea,Aston Villa,1,0,H,1,0,H,R East,7,11,2,3,4,0,17,12,1,4,0,0 +26/12/2014,West Brom,Man City,1,3,A,0,3,A,M Clattenburg,18,9,6,5,10,6,5,9,0,1,0,0 +28/12/2014,Aston Villa,Sunderland,0,0,D,0,0,D,M Atkinson,14,15,3,3,4,4,4,11,1,4,1,0 +28/12/2014,Hull,Leicester,0,1,A,0,1,A,P Dowd,18,4,5,2,7,1,12,12,0,1,1,1 +28/12/2014,Man City,Burnley,2,2,D,2,0,H,K Friend,17,12,6,3,10,3,18,9,4,1,0,0 +28/12/2014,Newcastle,Everton,3,2,H,1,1,D,C Pawson,13,9,6,2,8,3,13,8,2,2,0,0 +28/12/2014,QPR,Crystal Palace,0,0,D,0,0,D,M Jones,11,10,3,3,9,4,11,17,2,1,0,0 +28/12/2014,Southampton,Chelsea,1,1,D,1,1,D,A Taylor,9,7,1,1,4,6,23,14,2,2,1,0 +28/12/2014,Stoke,West Brom,2,0,H,0,0,D,R East,13,15,3,4,5,3,11,14,0,2,0,0 +28/12/2014,Tottenham,Man United,0,0,D,0,0,D,J Moss,9,9,4,5,3,6,11,19,2,4,0,0 +28/12/2014,West Ham,Arsenal,1,2,A,0,2,A,N Swarbrick,14,21,4,7,7,8,12,13,3,3,0,0 +29/12/2014,Liverpool,Swansea,4,1,H,1,0,H,A Marriner,21,11,11,4,6,3,8,5,1,0,0,0 +01/01/2015,Aston Villa,Crystal Palace,0,0,D,0,0,D,R Madley,14,6,3,0,5,2,11,12,1,1,0,0 +01/01/2015,Hull,Everton,2,0,H,2,0,H,K Friend,14,9,5,3,4,8,16,15,2,3,0,1 +01/01/2015,Liverpool,Leicester,2,2,D,2,0,H,M Jones,18,16,6,3,4,5,13,10,2,2,0,0 +01/01/2015,Man City,Sunderland,3,2,H,0,0,D,R East,32,4,12,4,10,4,14,7,2,3,0,0 +01/01/2015,Newcastle,Burnley,3,3,D,2,1,H,M Dean,10,15,4,4,5,5,12,13,2,4,0,0 +01/01/2015,QPR,Swansea,1,1,D,1,0,H,A Taylor,24,25,6,6,4,9,22,5,4,0,0,1 +01/01/2015,Southampton,Arsenal,2,0,H,1,0,H,C Pawson,13,11,6,6,2,7,14,7,2,1,0,0 +01/01/2015,Stoke,Man United,1,1,D,1,1,D,M Oliver,11,6,2,2,11,7,8,12,0,0,0,0 +01/01/2015,Tottenham,Chelsea,5,3,H,3,1,H,P Dowd,11,19,8,7,5,3,14,10,3,1,0,0 +01/01/2015,West Ham,West Brom,1,1,D,1,1,D,J Moss,8,14,3,3,0,1,6,13,1,0,0,0 +10/01/2015,Burnley,QPR,2,1,H,2,1,H,A Marriner,18,17,7,7,10,6,14,9,2,2,0,0 +10/01/2015,Chelsea,Newcastle,2,0,H,1,0,H,R East,9,11,5,5,7,4,13,13,2,3,0,0 +10/01/2015,Crystal Palace,Tottenham,2,1,H,0,0,D,A Taylor,21,9,7,5,4,6,9,14,5,3,0,0 +10/01/2015,Everton,Man City,1,1,D,0,0,D,M Atkinson,11,18,4,2,4,9,9,10,1,2,0,0 +10/01/2015,Leicester,Aston Villa,1,0,H,1,0,H,M Oliver,11,12,3,1,6,6,18,6,1,1,1,1 +10/01/2015,Sunderland,Liverpool,0,1,A,0,1,A,C Pawson,5,21,1,4,6,6,12,11,1,4,1,0 +10/01/2015,Swansea,West Ham,1,1,D,0,1,A,M Dean,19,11,3,5,4,5,9,7,0,1,0,0 +10/01/2015,West Brom,Hull,1,0,H,0,0,D,N Swarbrick,9,12,3,2,5,2,5,13,1,0,0,0 +11/01/2015,Arsenal,Stoke,3,0,H,2,0,H,J Moss,14,7,6,2,9,1,11,11,0,3,0,0 +11/01/2015,Man United,Southampton,0,1,A,0,0,D,P Dowd,10,9,0,1,3,5,9,10,2,3,0,0 +17/01/2015,Aston Villa,Liverpool,0,2,A,0,1,A,M Clattenburg,15,12,4,5,9,5,13,11,1,1,0,0 +17/01/2015,Burnley,Crystal Palace,2,3,A,2,1,H,P Dowd,7,11,4,4,5,7,10,20,1,3,0,0 +17/01/2015,Leicester,Stoke,0,1,A,0,0,D,A Marriner,7,13,3,3,6,5,9,6,1,0,0,0 +17/01/2015,Newcastle,Southampton,1,2,A,1,1,D,R Madley,16,5,3,3,9,0,9,8,0,2,0,0 +17/01/2015,QPR,Man United,0,2,A,0,0,D,N Swarbrick,18,12,4,7,3,3,12,10,3,2,0,0 +17/01/2015,Swansea,Chelsea,0,5,A,0,4,A,J Moss,11,21,0,11,1,2,9,13,0,0,0,0 +17/01/2015,Tottenham,Sunderland,2,1,H,1,1,D,C Foy,25,8,8,4,6,6,14,8,1,2,0,0 +18/01/2015,Man City,Arsenal,0,2,A,0,1,A,M Dean,12,9,4,3,16,3,12,5,3,3,0,0 +18/01/2015,West Ham,Hull,3,0,H,0,0,D,M Atkinson,17,9,6,3,4,7,5,14,1,3,0,0 +19/01/2015,Everton,West Brom,0,0,D,0,0,D,M Oliver,17,5,4,0,8,1,11,12,3,2,0,0 +31/01/2015,Chelsea,Man City,1,1,D,1,1,D,M Clattenburg,3,10,2,5,1,4,12,14,0,2,0,0 +31/01/2015,Crystal Palace,Everton,0,1,A,0,1,A,R East,13,14,3,2,6,1,12,14,0,3,0,0 +31/01/2015,Hull,Newcastle,0,3,A,0,1,A,P Dowd,13,12,8,5,3,3,15,13,4,1,0,0 +31/01/2015,Liverpool,West Ham,2,0,H,0,0,D,A Marriner,21,7,7,2,5,7,12,12,1,1,0,0 +31/01/2015,Man United,Leicester,3,1,H,3,0,H,M Atkinson,12,4,5,1,4,3,8,12,0,1,0,0 +31/01/2015,Stoke,QPR,3,1,H,2,1,H,M Dean,15,14,3,4,3,5,11,12,2,2,0,0 +31/01/2015,Sunderland,Burnley,2,0,H,2,0,H,L Mason,8,13,3,4,6,3,12,12,3,1,0,0 +31/01/2015,West Brom,Tottenham,0,3,A,0,2,A,K Friend,13,14,4,4,6,3,8,13,2,1,0,0 +01/02/2015,Arsenal,Aston Villa,5,0,H,1,0,H,A Taylor,13,9,8,2,7,7,6,10,0,2,0,0 +01/02/2015,Southampton,Swansea,0,1,A,0,0,D,M Oliver,15,6,6,1,9,0,11,9,1,1,1,0 +07/02/2015,Aston Villa,Chelsea,1,2,A,0,1,A,N Swarbrick,8,12,1,5,7,7,8,7,3,2,0,0 +07/02/2015,Everton,Liverpool,0,0,D,0,0,D,A Taylor,6,17,1,6,3,6,11,7,4,1,0,0 +07/02/2015,Leicester,Crystal Palace,0,1,A,0,0,D,L Mason,19,14,5,2,8,6,15,6,4,1,0,0 +07/02/2015,Man City,Hull,1,1,D,0,1,A,J Moss,18,7,5,3,13,1,9,8,2,4,0,0 +07/02/2015,QPR,Southampton,0,1,A,0,0,D,R East,12,16,3,5,3,4,13,16,2,2,0,0 +07/02/2015,Swansea,Sunderland,1,1,D,0,1,A,P Dowd,16,4,7,1,7,1,11,13,3,4,0,0 +07/02/2015,Tottenham,Arsenal,2,1,H,0,1,A,M Atkinson,23,7,8,3,10,3,8,12,3,5,0,0 +08/02/2015,Burnley,West Brom,2,2,D,2,1,H,M Dean,12,12,5,5,5,6,10,15,0,4,0,0 +08/02/2015,Newcastle,Stoke,1,1,D,0,0,D,K Friend,9,7,3,3,5,8,14,17,2,4,0,0 +08/02/2015,West Ham,Man United,1,1,D,0,0,D,M Clattenburg,13,18,6,6,9,9,7,13,2,2,0,1 +10/02/2015,Arsenal,Leicester,2,1,H,2,0,H,M Jones,17,11,7,4,6,6,13,14,2,2,0,0 +10/02/2015,Hull,Aston Villa,2,0,H,1,0,H,M Oliver,10,11,4,4,3,7,16,11,1,2,0,0 +10/02/2015,Liverpool,Tottenham,3,2,H,1,1,D,P Dowd,16,14,7,5,5,7,13,17,3,6,0,0 +10/02/2015,Sunderland,QPR,0,2,A,0,2,A,M Atkinson,16,19,5,4,8,4,8,12,2,3,0,0 +11/02/2015,Chelsea,Everton,1,0,H,0,0,D,J Moss,21,7,7,4,10,2,8,13,3,3,0,1 +11/02/2015,Crystal Palace,Newcastle,1,1,D,0,1,A,A Marriner,7,5,3,1,5,2,10,11,1,2,0,0 +11/02/2015,Man United,Burnley,3,1,H,2,1,H,K Friend,11,13,7,3,5,8,15,8,4,3,0,0 +11/02/2015,Southampton,West Ham,0,0,D,0,0,D,C Pawson,21,4,7,1,8,2,12,11,2,1,0,1 +11/02/2015,Stoke,Man City,1,4,A,1,1,D,L Mason,17,14,5,8,3,2,6,11,1,2,0,0 +11/02/2015,West Brom,Swansea,2,0,H,0,0,D,R Madley,11,10,4,3,5,4,11,12,2,1,0,0 +21/02/2015,Aston Villa,Stoke,1,2,A,1,1,D,R East,3,11,2,6,11,3,11,14,1,3,1,0 +21/02/2015,Chelsea,Burnley,1,1,D,1,0,H,M Atkinson,14,10,5,5,7,7,9,9,1,2,1,0 +21/02/2015,Crystal Palace,Arsenal,1,2,A,0,2,A,M Clattenburg,22,12,3,4,10,1,15,14,1,3,0,0 +21/02/2015,Hull,QPR,2,1,H,1,1,D,A Taylor,16,8,6,3,5,7,18,16,0,4,0,1 +21/02/2015,Man City,Newcastle,5,0,H,3,0,H,C Foy,16,8,9,1,7,3,9,8,3,2,0,0 +21/02/2015,Sunderland,West Brom,0,0,D,0,0,D,M Jones,13,5,2,1,11,2,11,12,1,2,0,0 +21/02/2015,Swansea,Man United,2,1,H,1,1,D,N Swarbrick,11,18,6,3,4,10,6,15,2,4,0,0 +22/02/2015,Everton,Leicester,2,2,D,0,0,D,P Dowd,18,15,3,4,4,7,7,6,0,1,0,0 +22/02/2015,Southampton,Liverpool,0,2,A,0,1,A,K Friend,13,6,5,4,6,1,11,14,2,3,0,0 +22/02/2015,Tottenham,West Ham,2,2,D,0,1,A,J Moss,27,12,8,5,7,9,6,12,0,2,0,0 +28/02/2015,Burnley,Swansea,0,1,A,0,0,D,J Moss,13,8,6,2,6,4,14,8,0,0,0,0 +28/02/2015,Man United,Sunderland,2,0,H,0,0,D,R East,30,5,10,3,13,1,6,11,1,1,0,1 +28/02/2015,Newcastle,Aston Villa,1,0,H,1,0,H,L Mason,14,13,5,3,9,5,6,16,1,5,0,0 +28/02/2015,Stoke,Hull,1,0,H,0,0,D,N Swarbrick,17,1,2,0,5,2,9,10,2,2,0,0 +28/02/2015,West Brom,Southampton,1,0,H,1,0,H,P Dowd,7,12,3,2,2,3,10,17,2,1,0,0 +28/02/2015,West Ham,Crystal Palace,1,3,A,0,1,A,M Dean,22,12,5,5,11,7,12,19,2,2,0,1 +01/03/2015,Arsenal,Everton,2,0,H,1,0,H,A Marriner,17,8,4,2,8,9,12,5,2,0,0,0 +01/03/2015,Liverpool,Man City,2,1,H,1,1,D,M Clattenburg,11,8,5,1,3,0,8,16,1,3,0,0 +03/03/2015,Aston Villa,West Brom,2,1,H,1,0,H,J Moss,16,3,8,1,0,3,15,13,2,4,0,0 +03/03/2015,Hull,Sunderland,1,1,D,1,0,H,M Dean,15,9,5,3,6,4,9,15,2,6,0,0 +03/03/2015,Southampton,Crystal Palace,1,0,H,0,0,D,M Atkinson,23,9,3,3,10,2,11,10,0,2,0,0 +04/03/2015,Liverpool,Burnley,2,0,H,1,0,H,L Mason,20,9,8,1,6,5,8,14,0,1,0,0 +04/03/2015,Man City,Leicester,2,0,H,1,0,H,R Madley,22,6,9,2,10,3,11,3,0,1,0,0 +04/03/2015,Newcastle,Man United,0,1,A,0,0,D,A Taylor,9,11,2,5,4,1,6,14,1,2,0,0 +04/03/2015,QPR,Arsenal,1,2,A,0,0,D,K Friend,10,20,3,9,1,6,10,8,2,1,0,0 +04/03/2015,Stoke,Everton,2,0,H,1,0,H,M Clattenburg,11,10,3,5,5,6,11,14,2,2,0,0 +04/03/2015,Tottenham,Swansea,3,2,H,1,1,D,M Oliver,20,10,7,5,6,4,14,7,1,0,0,0 +04/03/2015,West Ham,Chelsea,0,1,A,0,1,A,A Marriner,18,12,5,3,5,4,10,10,3,4,0,0 +07/03/2015,QPR,Tottenham,1,2,A,0,1,A,C Pawson,17,13,6,6,3,5,15,12,1,2,0,0 +14/03/2015,Arsenal,West Ham,3,0,H,1,0,H,A Taylor,19,7,9,1,7,5,10,9,1,1,0,0 +14/03/2015,Burnley,Man City,1,0,H,0,0,D,A Marriner,10,21,4,5,2,6,6,9,2,1,0,0 +14/03/2015,Crystal Palace,QPR,3,1,H,3,0,H,L Mason,15,11,4,4,6,3,17,16,1,3,0,0 +14/03/2015,Leicester,Hull,0,0,D,0,0,D,J Moss,15,12,3,5,7,5,10,15,0,4,0,1 +14/03/2015,Sunderland,Aston Villa,0,4,A,0,4,A,N Swarbrick,12,14,2,8,2,5,10,9,1,1,0,0 +14/03/2015,West Brom,Stoke,1,0,H,1,0,H,M Oliver,14,9,5,2,7,5,7,16,1,2,0,0 +15/03/2015,Chelsea,Southampton,1,1,D,1,1,D,M Dean,22,12,7,5,9,2,10,11,3,3,0,0 +15/03/2015,Everton,Newcastle,3,0,H,1,0,H,M Atkinson,15,12,9,4,3,4,11,9,1,2,0,1 +15/03/2015,Man United,Tottenham,3,0,H,3,0,H,M Clattenburg,11,5,3,1,4,2,12,10,1,1,0,0 +16/03/2015,Swansea,Liverpool,0,1,A,0,0,D,R East,10,16,3,5,4,5,13,16,0,3,0,0 +21/03/2015,Aston Villa,Swansea,0,1,A,0,0,D,R Madley,12,9,3,6,4,9,13,9,0,0,0,0 +21/03/2015,Man City,West Brom,3,0,H,2,0,H,N Swarbrick,43,3,16,0,9,2,6,5,0,0,0,1 +21/03/2015,Newcastle,Arsenal,1,2,A,0,2,A,M Jones,16,7,4,3,9,4,11,7,0,0,0,0 +21/03/2015,Southampton,Burnley,2,0,H,1,0,H,R East,20,15,3,3,10,5,10,7,0,0,0,0 +21/03/2015,Stoke,Crystal Palace,1,2,A,1,2,A,A Marriner,17,12,4,3,8,4,15,14,3,4,0,0 +21/03/2015,Tottenham,Leicester,4,3,H,2,1,H,M Dean,10,14,4,5,4,4,8,9,2,1,0,0 +21/03/2015,West Ham,Sunderland,1,0,H,0,0,D,L Mason,15,7,4,3,6,4,14,11,0,1,0,0 +22/03/2015,Hull,Chelsea,2,3,A,2,2,D,M Oliver,19,8,8,3,6,6,14,11,0,2,0,0 +22/03/2015,Liverpool,Man United,1,2,A,0,1,A,M Atkinson,7,6,1,4,2,3,14,17,2,2,1,0 +22/03/2015,QPR,Everton,1,2,A,0,1,A,J Moss,14,8,1,3,1,3,11,12,2,1,0,0 +04/04/2015,Arsenal,Liverpool,4,1,H,3,0,H,A Taylor,16,13,10,2,6,5,7,9,1,0,0,1 +04/04/2015,Chelsea,Stoke,2,1,H,1,1,D,J Moss,19,7,10,2,9,0,5,22,1,6,0,0 +04/04/2015,Everton,Southampton,1,0,H,1,0,H,L Mason,11,11,1,2,4,10,12,17,0,1,0,0 +04/04/2015,Leicester,West Ham,2,1,H,1,1,D,M Clattenburg,20,14,7,7,7,6,12,10,0,3,0,0 +04/04/2015,Man United,Aston Villa,3,1,H,1,0,H,R East,19,4,7,2,10,2,15,12,0,1,0,0 +04/04/2015,Swansea,Hull,3,1,H,2,0,H,A Marriner,9,10,4,1,4,7,11,14,1,5,0,1 +04/04/2015,West Brom,QPR,1,4,A,0,3,A,L Probert,21,12,6,7,2,4,6,15,2,3,1,0 +05/04/2015,Burnley,Tottenham,0,0,D,0,0,D,M Atkinson,15,12,4,2,5,3,9,5,2,0,0,0 +05/04/2015,Sunderland,Newcastle,1,0,H,1,0,H,M Dean,16,10,4,1,8,4,14,18,5,2,0,0 +06/04/2015,Crystal Palace,Man City,2,1,H,1,0,H,M Oliver,5,22,3,4,4,13,11,11,2,1,0,0 +07/04/2015,Aston Villa,QPR,3,3,D,2,1,H,C Pawson,21,11,7,5,7,3,8,12,0,4,0,0 +11/04/2015,Burnley,Arsenal,0,1,A,0,1,A,M Dean,8,17,4,5,1,6,15,7,2,0,0,0 +11/04/2015,Southampton,Hull,2,0,H,0,0,D,K Friend,9,15,4,4,2,5,7,11,2,0,0,0 +11/04/2015,Sunderland,Crystal Palace,1,4,A,0,0,D,A Taylor,12,14,3,5,8,4,15,17,2,2,0,0 +11/04/2015,Swansea,Everton,1,1,D,0,1,A,M Oliver,11,12,3,8,3,6,18,12,3,3,0,0 +11/04/2015,Tottenham,Aston Villa,0,1,A,0,1,A,L Probert,14,10,3,3,9,6,11,12,3,2,0,1 +11/04/2015,West Brom,Leicester,2,3,A,2,1,H,M Atkinson,8,12,5,4,4,6,15,15,2,2,0,0 +11/04/2015,West Ham,Stoke,1,1,D,1,0,H,R East,11,18,3,2,4,10,17,13,3,1,0,0 +12/04/2015,Man United,Man City,4,2,H,2,1,H,M Clattenburg,11,10,7,3,1,5,9,16,0,3,0,0 +12/04/2015,QPR,Chelsea,0,1,A,0,0,D,A Marriner,15,9,4,1,5,6,12,6,2,1,0,0 +13/04/2015,Liverpool,Newcastle,2,0,H,1,0,H,L Mason,14,10,6,3,6,6,8,17,2,0,0,1 +18/04/2015,Chelsea,Man United,1,0,H,1,0,H,M Dean,7,15,2,2,3,7,13,11,7,1,0,0 +18/04/2015,Crystal Palace,West Brom,0,2,A,0,1,A,J Moss,19,8,4,4,7,6,17,17,3,3,0,0 +18/04/2015,Everton,Burnley,1,0,H,1,0,H,M Jones,21,10,8,1,5,6,12,15,1,2,0,1 +18/04/2015,Leicester,Swansea,2,0,H,1,0,H,L Probert,18,10,6,6,6,3,6,5,0,2,0,0 +18/04/2015,Stoke,Southampton,2,1,H,0,1,A,M Clattenburg,15,12,2,6,2,5,17,17,0,4,0,0 +19/04/2015,Man City,West Ham,2,0,H,2,0,H,A Taylor,18,9,3,3,6,3,10,6,2,2,0,0 +19/04/2015,Newcastle,Tottenham,1,3,A,0,1,A,K Friend,11,14,5,8,4,3,11,13,2,4,0,0 +25/04/2015,Burnley,Leicester,0,1,A,0,0,D,A Taylor,12,8,4,4,6,4,16,16,3,0,0,0 +25/04/2015,Crystal Palace,Hull,0,2,A,0,0,D,M Clattenburg,7,21,3,7,0,9,15,10,0,0,0,0 +25/04/2015,Man City,Aston Villa,3,2,H,1,0,H,M Dean,11,13,4,4,7,6,13,11,1,2,0,0 +25/04/2015,Newcastle,Swansea,2,3,A,1,1,D,N Swarbrick,12,11,4,6,9,6,14,9,1,2,0,0 +25/04/2015,QPR,West Ham,0,0,D,0,0,D,M Jones,18,8,7,2,6,6,14,12,1,0,0,0 +25/04/2015,Southampton,Tottenham,2,2,D,1,1,D,J Moss,13,7,4,2,8,3,9,10,1,3,0,0 +25/04/2015,Stoke,Sunderland,1,1,D,1,1,D,M Atkinson,23,15,8,5,9,8,14,15,2,3,0,0 +25/04/2015,West Brom,Liverpool,0,0,D,0,0,D,R East,10,22,4,5,2,4,12,11,1,0,0,0 +26/04/2015,Arsenal,Chelsea,0,0,D,0,0,D,M Oliver,12,7,1,3,6,2,17,11,4,3,0,0 +26/04/2015,Everton,Man United,3,0,H,2,0,H,A Marriner,9,17,7,4,7,7,6,10,0,2,0,0 +28/04/2015,Hull,Liverpool,1,0,H,1,0,H,L Probert,7,12,4,9,7,7,10,10,2,0,0,0 +29/04/2015,Leicester,Chelsea,1,3,A,1,0,H,M Clattenburg,8,11,2,4,2,6,10,1,1,0,0,0 +02/05/2015,Aston Villa,Everton,3,2,H,2,0,H,M Clattenburg,11,9,4,5,5,3,13,12,2,1,0,0 +02/05/2015,Leicester,Newcastle,3,0,H,2,0,H,M Dean,19,12,7,3,10,7,10,13,1,2,0,2 +02/05/2015,Liverpool,QPR,2,1,H,1,0,H,M Atkinson,25,10,8,2,9,6,9,18,2,3,0,1 +02/05/2015,Man United,West Brom,0,1,A,0,0,D,A Taylor,26,6,9,3,9,3,10,11,1,0,0,0 +02/05/2015,Sunderland,Southampton,2,1,H,1,1,D,M Jones,12,11,6,2,1,1,12,13,2,2,0,1 +02/05/2015,Swansea,Stoke,2,0,H,0,0,D,C Pawson,16,9,4,3,6,2,9,18,1,2,0,1 +02/05/2015,West Ham,Burnley,1,0,H,1,0,H,J Moss,29,8,7,2,13,3,11,7,3,2,0,1 +03/05/2015,Chelsea,Crystal Palace,1,0,H,1,0,H,K Friend,17,8,7,1,3,6,12,13,2,2,0,0 +03/05/2015,Tottenham,Man City,0,1,A,0,1,A,A Marriner,21,15,5,4,8,7,6,11,3,4,0,0 +04/05/2015,Hull,Arsenal,1,3,A,0,3,A,L Mason,8,22,1,9,0,5,8,11,2,0,0,0 +09/05/2015,Aston Villa,West Ham,1,0,H,1,0,H,L Mason,15,5,5,1,2,6,6,13,1,1,0,0 +09/05/2015,Crystal Palace,Man United,1,2,A,0,1,A,M Oliver,13,13,4,4,7,6,13,12,0,1,0,0 +09/05/2015,Everton,Sunderland,0,2,A,0,0,D,L Probert,22,10,4,3,15,1,10,9,2,3,0,0 +09/05/2015,Hull,Burnley,0,1,A,0,0,D,M Atkinson,21,8,3,2,9,3,16,12,1,1,0,0 +09/05/2015,Leicester,Southampton,2,0,H,2,0,H,R East,10,10,3,3,4,8,12,10,1,0,0,0 +09/05/2015,Newcastle,West Brom,1,1,D,1,1,D,C Foy,15,8,5,3,7,2,11,17,2,3,0,0 +09/05/2015,Stoke,Tottenham,3,0,H,2,0,H,M Clattenburg,17,9,9,3,6,8,14,12,0,0,0,1 +10/05/2015,Chelsea,Liverpool,1,1,D,1,1,D,A Marriner,8,15,3,4,6,4,13,11,4,3,0,0 +10/05/2015,Man City,QPR,6,0,H,2,0,H,M Dean,23,9,11,3,7,4,12,7,0,1,0,0 +11/05/2015,Arsenal,Swansea,0,1,A,0,0,D,K Friend,23,8,9,3,6,1,7,10,0,1,0,0 +16/05/2015,Burnley,Stoke,0,0,D,0,0,D,M Oliver,11,11,4,3,7,6,14,9,1,2,0,0 +16/05/2015,Liverpool,Crystal Palace,1,3,A,1,1,D,J Moss,18,15,3,6,4,5,10,17,2,3,0,0 +16/05/2015,QPR,Newcastle,2,1,H,0,1,A,L Probert,8,22,4,8,3,12,12,8,2,0,0,0 +16/05/2015,Southampton,Aston Villa,6,1,H,5,1,H,R Madley,18,9,11,4,6,3,19,7,1,1,0,0 +16/05/2015,Sunderland,Leicester,0,0,D,0,0,D,M Atkinson,13,11,3,0,8,9,14,15,4,1,0,0 +16/05/2015,Tottenham,Hull,2,0,H,0,0,D,A Taylor,10,14,3,2,6,8,10,6,3,1,0,0 +16/05/2015,West Ham,Everton,1,2,A,0,0,D,K Friend,13,21,3,5,7,8,12,14,1,4,0,0 +17/05/2015,Man United,Arsenal,1,1,D,1,0,H,M Dean,12,5,4,3,5,5,16,8,1,0,0,0 +17/05/2015,Swansea,Man City,2,4,A,1,2,A,M Clattenburg,13,22,9,11,11,7,8,8,0,3,0,0 +18/05/2015,West Brom,Chelsea,3,0,H,1,0,H,M Jones,9,12,5,3,3,4,12,11,3,2,0,1 +20/05/2015,Arsenal,Sunderland,0,0,D,0,0,D,A Taylor,28,7,8,3,5,2,6,8,1,0,0,0 +24/05/2015,Arsenal,West Brom,4,1,H,4,0,H,R Madley,24,14,13,5,7,3,6,7,1,0,0,0 +24/05/2015,Aston Villa,Burnley,0,1,A,0,1,A,M Jones,16,6,5,3,7,2,9,6,1,2,0,0 +24/05/2015,Chelsea,Sunderland,3,1,H,1,1,D,L Mason,24,17,8,9,11,3,12,8,2,1,0,0 +24/05/2015,Crystal Palace,Swansea,1,0,H,0,0,D,C Pawson,13,5,7,2,7,3,22,13,3,0,0,0 +24/05/2015,Everton,Tottenham,0,1,A,0,1,A,J Moss,9,16,1,3,3,5,12,8,1,2,0,0 +24/05/2015,Hull,Man United,0,0,D,0,0,D,L Probert,16,7,6,1,8,1,12,15,2,2,0,1 +24/05/2015,Leicester,QPR,5,1,H,2,0,H,M Oliver,22,18,7,2,5,6,7,6,0,0,0,0 +24/05/2015,Man City,Southampton,2,0,H,1,0,H,C Foy,15,13,6,4,8,4,13,8,1,1,0,0 +24/05/2015,Newcastle,West Ham,2,0,H,0,0,D,M Atkinson,17,4,4,1,2,3,9,9,2,1,0,0 +24/05/2015,Stoke,Liverpool,6,1,H,5,0,H,A Taylor,15,13,9,4,3,9,13,4,4,2,0,0 +,,,,,,,,,,,,,,,,,,,,, +08/08/2015,Bournemouth,Aston Villa,0,1,A,0,0,D,M Clattenburg,11,7,2,3,6,3,13,13,3,4,0,0 +08/08/2015,Chelsea,Swansea,2,2,D,2,1,H,M Oliver,11,18,3,10,4,8,15,16,1,3,1,0 +08/08/2015,Everton,Watford,2,2,D,0,1,A,M Jones,10,11,5,5,8,2,7,13,1,2,0,0 +08/08/2015,Leicester,Sunderland,4,2,H,3,0,H,L Mason,19,10,8,5,6,3,13,17,2,4,0,0 +08/08/2015,Man United,Tottenham,1,0,H,1,0,H,J Moss,9,9,1,4,1,2,12,12,2,3,0,0 +08/08/2015,Norwich,Crystal Palace,1,3,A,0,1,A,S Hooper,17,11,6,7,1,4,14,20,1,0,0,0 +09/08/2015,Arsenal,West Ham,0,2,A,0,1,A,M Atkinson,22,8,6,4,5,4,12,9,1,3,0,0 +09/08/2015,Newcastle,Southampton,2,2,D,1,1,D,C Pawson,9,15,4,5,6,6,9,12,2,4,0,0 +09/08/2015,Stoke,Liverpool,0,1,A,0,0,D,A Taylor,7,8,1,3,3,5,9,16,2,4,0,0 +10/08/2015,West Brom,Man City,0,3,A,0,2,A,M Dean,9,19,2,7,6,6,12,9,4,1,0,0 +14/08/2015,Aston Villa,Man United,0,1,A,0,1,A,M Dean,5,9,1,2,3,5,14,10,2,2,0,0 +15/08/2015,Southampton,Everton,0,3,A,0,2,A,M Oliver,17,10,4,4,9,9,11,10,4,2,0,0 +15/08/2015,Sunderland,Norwich,1,3,A,0,2,A,K Friend,6,19,2,6,6,6,7,7,1,2,0,0 +15/08/2015,Swansea,Newcastle,2,0,H,1,0,H,M Jones,19,4,6,2,4,4,11,8,2,1,0,1 +15/08/2015,Tottenham,Stoke,2,2,D,2,0,H,R Madley,13,16,7,7,4,3,15,11,2,2,0,0 +15/08/2015,Watford,West Brom,0,0,D,0,0,D,P Tierney,16,6,5,0,2,4,13,10,1,2,0,0 +15/08/2015,West Ham,Leicester,1,2,A,0,2,A,A Taylor,10,11,3,6,8,4,11,12,1,3,1,0 +16/08/2015,Crystal Palace,Arsenal,1,2,A,1,1,D,L Mason,11,20,4,7,6,6,14,12,1,1,0,0 +16/08/2015,Man City,Chelsea,3,0,H,1,0,H,M Atkinson,18,10,8,3,5,1,19,13,4,2,0,0 +17/08/2015,Liverpool,Bournemouth,1,0,H,1,0,H,C Pawson,18,13,2,2,6,8,11,18,1,4,0,0 +22/08/2015,Crystal Palace,Aston Villa,2,1,H,0,0,D,K Stroud,16,11,6,2,6,4,12,15,3,3,0,0 +22/08/2015,Leicester,Tottenham,1,1,D,0,0,D,M Atkinson,13,19,2,6,2,7,7,14,1,4,0,0 +22/08/2015,Man United,Newcastle,0,0,D,0,0,D,C Pawson,20,7,8,0,11,5,15,11,2,2,0,0 +22/08/2015,Norwich,Stoke,1,1,D,1,1,D,M Dean,21,6,7,1,9,0,16,5,4,1,0,0 +22/08/2015,Sunderland,Swansea,1,1,D,0,1,A,N Swarbrick,10,20,2,9,3,4,16,9,4,2,0,0 +22/08/2015,West Ham,Bournemouth,3,4,A,0,2,A,J Moss,10,15,4,7,5,4,8,11,0,0,1,0 +23/08/2015,Everton,Man City,0,2,A,0,0,D,A Taylor,10,16,1,9,8,7,5,7,0,3,0,0 +23/08/2015,Watford,Southampton,0,0,D,0,0,D,A Marriner,13,14,0,5,5,2,10,11,0,2,0,0 +23/08/2015,West Brom,Chelsea,2,3,A,1,3,A,M Clattenburg,15,15,6,5,8,7,9,12,2,1,0,1 +24/08/2015,Arsenal,Liverpool,0,0,D,0,0,D,M Oliver,19,15,5,8,7,8,2,13,1,4,0,0 +29/08/2015,Aston Villa,Sunderland,2,2,D,2,1,H,R Madley,21,7,6,3,5,4,10,14,2,1,0,0 +29/08/2015,Bournemouth,Leicester,1,1,D,1,0,H,N Swarbrick,5,4,2,2,4,4,9,19,2,4,0,0 +29/08/2015,Chelsea,Crystal Palace,1,2,A,0,0,D,C Pawson,26,13,9,6,7,7,9,15,1,1,0,0 +29/08/2015,Liverpool,West Ham,0,3,A,0,2,A,K Friend,13,12,1,5,5,7,17,11,3,1,1,1 +29/08/2015,Man City,Watford,2,0,H,0,0,D,M Clattenburg,18,7,5,0,9,3,9,12,1,2,0,0 +29/08/2015,Newcastle,Arsenal,0,1,A,0,0,D,A Marriner,1,22,0,9,0,9,15,8,6,1,1,0 +29/08/2015,Stoke,West Brom,0,1,A,0,1,A,M Oliver,13,15,5,5,6,5,4,9,1,4,2,0 +29/08/2015,Tottenham,Everton,0,0,D,0,0,D,M Jones,20,8,6,3,6,2,20,9,4,1,0,0 +30/08/2015,Southampton,Norwich,3,0,H,1,0,H,J Moss,23,6,8,1,6,4,4,12,0,0,0,1 +30/08/2015,Swansea,Man United,2,1,H,0,0,D,M Atkinson,9,11,4,4,0,1,6,15,2,2,0,0 +12/09/2015,Arsenal,Stoke,2,0,H,1,0,H,J Moss,29,9,12,4,13,1,6,13,1,1,0,0 +12/09/2015,Crystal Palace,Man City,0,1,A,0,0,D,M Jones,10,21,2,7,4,4,13,13,2,3,0,0 +12/09/2015,Everton,Chelsea,3,1,H,2,1,H,A Marriner,14,15,9,2,7,14,12,12,2,2,0,0 +12/09/2015,Man United,Liverpool,3,1,H,0,0,D,M Oliver,9,8,3,4,4,4,6,11,1,2,0,0 +12/09/2015,Norwich,Bournemouth,3,1,H,1,0,H,M Atkinson,12,7,4,2,4,4,10,11,2,3,0,0 +12/09/2015,Watford,Swansea,1,0,H,0,0,D,R Madley,17,8,6,3,1,3,11,14,1,1,1,0 +12/09/2015,West Brom,Southampton,0,0,D,0,0,D,S Attwell,8,14,1,4,4,5,5,5,1,0,0,0 +13/09/2015,Leicester,Aston Villa,3,2,H,0,1,A,M Dean,21,11,6,4,4,5,8,13,2,1,0,0 +13/09/2015,Sunderland,Tottenham,0,1,A,0,0,D,C Pawson,12,15,2,4,6,8,11,12,2,1,0,0 +14/09/2015,West Ham,Newcastle,2,0,H,1,0,H,A Taylor,17,14,3,4,6,7,8,11,0,2,0,0 +19/09/2015,Aston Villa,West Brom,0,1,A,0,1,A,M Atkinson,13,11,3,6,2,7,10,11,2,3,0,0 +19/09/2015,Bournemouth,Sunderland,2,0,H,2,0,H,K Friend,15,9,5,2,7,3,14,15,2,4,0,1 +19/09/2015,Chelsea,Arsenal,2,0,H,0,0,D,M Dean,22,9,7,2,5,5,12,11,3,1,0,2 +19/09/2015,Man City,West Ham,1,2,A,1,2,A,R Madley,27,6,8,3,16,3,10,7,1,2,0,0 +19/09/2015,Newcastle,Watford,1,2,A,0,2,A,R East,15,13,5,4,5,5,8,12,1,2,0,0 +19/09/2015,Stoke,Leicester,2,2,D,2,0,H,A Marriner,11,12,4,5,3,4,7,12,1,4,0,0 +19/09/2015,Swansea,Everton,0,0,D,0,0,D,S Attwell,12,17,3,2,2,4,12,11,2,2,0,1 +20/09/2015,Liverpool,Norwich,1,1,D,0,0,D,A Taylor,23,7,7,2,6,2,4,13,1,2,0,0 +20/09/2015,Southampton,Man United,2,3,A,1,1,D,M Clattenburg,15,10,8,3,5,5,6,13,2,0,0,0 +20/09/2015,Tottenham,Crystal Palace,1,0,H,0,0,D,M Oliver,22,12,6,4,6,5,9,11,1,2,0,0 +26/09/2015,Leicester,Arsenal,2,5,A,1,2,A,C Pawson,16,26,7,12,6,5,9,12,1,1,0,0 +26/09/2015,Liverpool,Aston Villa,3,2,H,1,0,H,J Moss,21,10,12,4,11,1,8,13,0,0,0,0 +26/09/2015,Man United,Sunderland,3,0,H,1,0,H,M Jones,12,9,7,4,5,2,12,17,0,3,0,0 +26/09/2015,Newcastle,Chelsea,2,2,D,1,0,H,M Atkinson,11,16,5,4,4,6,12,15,1,2,0,0 +26/09/2015,Southampton,Swansea,3,1,H,1,0,H,R East,18,11,7,4,4,3,18,14,3,1,0,0 +26/09/2015,Stoke,Bournemouth,2,1,H,1,0,H,L Mason,8,11,5,6,3,7,13,11,1,0,0,0 +26/09/2015,Tottenham,Man City,4,1,H,1,1,D,M Clattenburg,15,22,8,8,5,9,17,6,5,1,0,0 +26/09/2015,West Ham,Norwich,2,2,D,1,1,D,M Dean,19,16,7,7,7,5,7,17,0,0,0,0 +27/09/2015,Watford,Crystal Palace,0,1,A,0,0,D,A Taylor,15,16,2,3,6,4,17,22,4,4,0,0 +28/09/2015,West Brom,Everton,2,3,A,1,0,H,R Madley,9,12,4,4,6,4,9,8,0,3,0,0 +03/10/2015,Aston Villa,Stoke,0,1,A,0,0,D,M Jones,13,12,2,5,9,5,12,12,1,0,0,0 +03/10/2015,Bournemouth,Watford,1,1,D,1,1,D,M Oliver,12,11,6,2,5,3,8,13,1,1,0,0 +03/10/2015,Chelsea,Southampton,1,3,A,1,1,D,R Madley,10,13,3,5,4,6,14,14,2,5,0,0 +03/10/2015,Crystal Palace,West Brom,2,0,H,0,0,D,J Moss,21,7,8,2,8,3,14,13,3,3,0,0 +03/10/2015,Man City,Newcastle,6,1,H,1,1,D,K Friend,23,6,11,4,6,3,13,8,3,1,0,0 +03/10/2015,Norwich,Leicester,1,2,A,0,1,A,M Clattenburg,17,16,2,5,16,8,4,4,0,1,0,0 +03/10/2015,Sunderland,West Ham,2,2,D,2,1,H,N Swarbrick,12,20,3,6,1,8,14,11,2,4,1,0 +04/10/2015,Arsenal,Man United,3,0,H,3,0,H,A Taylor,12,9,5,5,3,6,8,17,1,3,0,0 +04/10/2015,Everton,Liverpool,1,1,D,1,1,D,M Atkinson,14,13,4,4,5,11,10,19,3,3,0,0 +04/10/2015,Swansea,Tottenham,2,2,D,2,1,H,M Dean,11,19,2,11,5,6,8,19,1,4,0,0 +17/10/2015,Chelsea,Aston Villa,2,0,H,1,0,H,R East,9,8,3,1,4,4,22,17,1,3,0,0 +17/10/2015,Crystal Palace,West Ham,1,3,A,1,1,D,M Clattenburg,6,24,1,4,1,9,9,11,1,3,1,0 +17/10/2015,Everton,Man United,0,3,A,0,2,A,J Moss,12,10,3,7,5,3,12,14,2,2,0,0 +17/10/2015,Man City,Bournemouth,5,1,H,4,1,H,M Dean,15,5,11,1,1,7,11,5,2,1,0,0 +17/10/2015,Southampton,Leicester,2,2,D,2,0,H,P Tierney,14,26,6,7,6,7,16,10,2,0,0,0 +17/10/2015,Tottenham,Liverpool,0,0,D,0,0,D,C Pawson,13,12,4,3,8,6,15,11,1,2,0,0 +17/10/2015,Watford,Arsenal,0,3,A,0,0,D,M Jones,8,17,1,6,3,9,11,12,2,1,0,0 +17/10/2015,West Brom,Sunderland,1,0,H,0,0,D,M Atkinson,8,9,1,2,1,5,12,13,2,2,0,0 +18/10/2015,Newcastle,Norwich,6,2,H,3,2,H,A Taylor,11,18,6,6,3,7,10,10,1,1,0,0 +19/10/2015,Swansea,Stoke,0,1,A,0,1,A,R Madley,14,9,2,2,1,2,6,14,2,2,0,0 +24/10/2015,Arsenal,Everton,2,1,H,2,1,H,L Mason,20,10,5,5,7,5,11,14,2,0,0,1 +24/10/2015,Aston Villa,Swansea,1,2,A,0,0,D,N Swarbrick,9,11,3,4,4,6,10,8,2,3,0,0 +24/10/2015,Leicester,Crystal Palace,1,0,H,0,0,D,M Dean,12,9,3,4,3,2,7,11,3,1,0,0 +24/10/2015,Norwich,West Brom,0,1,A,0,0,D,K Friend,16,7,3,4,13,3,11,6,1,2,0,0 +24/10/2015,Stoke,Watford,0,2,A,0,1,A,M Atkinson,12,13,1,3,11,5,10,18,3,1,0,0 +24/10/2015,West Ham,Chelsea,2,1,H,1,0,H,J Moss,12,8,5,4,7,4,10,12,1,5,0,1 +25/10/2015,Bournemouth,Tottenham,1,5,A,1,3,A,R East,11,14,3,9,4,5,8,15,0,1,0,0 +25/10/2015,Liverpool,Southampton,1,1,D,0,0,D,A Marriner,15,8,2,3,10,7,8,11,1,0,0,1 +25/10/2015,Man United,Man City,0,0,D,0,0,D,M Clattenburg,6,6,1,1,6,4,17,13,2,2,0,0 +25/10/2015,Sunderland,Newcastle,3,0,H,1,0,H,R Madley,9,21,3,8,1,10,12,10,0,2,0,1 +31/10/2015,Chelsea,Liverpool,1,3,A,1,1,D,M Clattenburg,8,16,2,7,1,7,9,21,1,4,0,0 +31/10/2015,Crystal Palace,Man United,0,0,D,0,0,D,M Jones,10,5,5,1,7,3,12,15,3,3,0,0 +31/10/2015,Man City,Norwich,2,1,H,0,0,D,R Madley,21,5,5,3,12,4,4,13,2,2,0,1 +31/10/2015,Newcastle,Stoke,0,0,D,0,0,D,R East,15,9,6,2,11,4,6,16,1,3,0,0 +31/10/2015,Swansea,Arsenal,0,3,A,0,0,D,K Friend,8,15,3,5,4,4,12,7,2,0,0,0 +31/10/2015,Watford,West Ham,2,0,H,1,0,H,K Stroud,20,8,5,3,7,2,16,12,3,0,0,1 +31/10/2015,West Brom,Leicester,2,3,A,1,0,H,A Taylor,14,13,6,5,6,4,3,11,0,2,0,0 +01/11/2015,Everton,Sunderland,6,2,H,2,1,H,A Marriner,15,17,8,9,6,1,6,5,3,2,0,0 +01/11/2015,Southampton,Bournemouth,2,0,H,2,0,H,C Pawson,11,16,2,2,4,6,5,5,0,2,1,0 +02/11/2015,Tottenham,Aston Villa,3,1,H,2,0,H,M Dean,17,13,6,2,4,3,6,15,0,3,0,0 +07/11/2015,Bournemouth,Newcastle,0,1,A,0,1,A,L Mason,20,2,5,1,16,0,9,11,2,2,0,0 +07/11/2015,Leicester,Watford,2,1,H,0,0,D,R East,12,8,6,4,4,2,10,10,0,1,0,0 +07/11/2015,Man United,West Brom,2,0,H,0,0,D,M Dean,13,4,3,0,5,1,11,10,1,3,0,1 +07/11/2015,Norwich,Swansea,1,0,H,0,0,D,M Clattenburg,9,8,5,0,5,2,12,7,1,0,0,0 +07/11/2015,Stoke,Chelsea,1,0,H,0,0,D,A Taylor,8,19,2,4,1,9,13,10,3,1,0,0 +07/11/2015,Sunderland,Southampton,0,1,A,0,0,D,M Jones,9,11,3,4,10,11,3,7,1,3,0,0 +07/11/2015,West Ham,Everton,1,1,D,1,1,D,P Tierney,16,13,5,4,3,5,11,12,1,2,0,0 +08/11/2015,Arsenal,Tottenham,1,1,D,0,1,A,M Atkinson,10,14,4,4,7,6,12,17,0,1,0,0 +08/11/2015,Aston Villa,Man City,0,0,D,0,0,D,C Pawson,3,13,0,3,3,8,7,7,1,1,0,0 +08/11/2015,Liverpool,Crystal Palace,1,2,A,1,1,D,N Swarbrick,22,9,4,4,14,6,7,6,1,2,0,0 +21/11/2015,Chelsea,Norwich,1,0,H,0,0,D,C Pawson,21,4,7,2,6,5,4,12,1,4,0,0 +21/11/2015,Everton,Aston Villa,4,0,H,3,0,H,M Oliver,16,10,9,3,2,3,6,9,0,0,0,0 +21/11/2015,Man City,Liverpool,1,4,A,1,3,A,J Moss,11,14,3,9,11,5,5,13,0,3,0,0 +21/11/2015,Newcastle,Leicester,0,3,A,0,1,A,M Jones,9,18,1,7,4,9,9,12,4,1,0,0 +21/11/2015,Southampton,Stoke,0,1,A,0,1,A,L Mason,16,16,1,4,7,3,8,8,2,0,0,0 +21/11/2015,Swansea,Bournemouth,2,2,D,2,2,D,A Marriner,11,16,4,4,4,5,7,9,3,2,0,0 +21/11/2015,Watford,Man United,1,2,A,0,1,A,R Madley,10,13,5,6,4,10,6,12,1,2,0,0 +21/11/2015,West Brom,Arsenal,2,1,H,2,1,H,M Clattenburg,4,11,1,3,3,5,14,10,3,2,0,0 +22/11/2015,Tottenham,West Ham,4,1,H,2,0,H,A Taylor,22,10,12,4,7,6,13,9,2,2,0,0 +23/11/2015,Crystal Palace,Sunderland,0,1,A,0,0,D,M Atkinson,19,12,7,4,11,2,18,11,1,3,0,0 +28/11/2015,Aston Villa,Watford,2,3,A,1,1,D,L Mason,12,14,6,6,10,10,6,13,2,3,0,0 +28/11/2015,Bournemouth,Everton,3,3,D,0,2,A,K Friend,19,11,10,6,10,5,6,6,2,0,0,0 +28/11/2015,Crystal Palace,Newcastle,5,1,H,3,1,H,S Attwell,19,7,8,2,7,5,7,13,1,1,0,0 +28/11/2015,Leicester,Man United,1,1,D,1,1,D,C Pawson,7,10,3,2,1,8,5,7,0,1,0,0 +28/11/2015,Man City,Southampton,3,1,H,2,0,H,R East,16,12,9,8,6,4,14,11,2,1,0,0 +28/11/2015,Sunderland,Stoke,2,0,H,0,0,D,M Dean,18,7,5,3,9,5,4,10,0,2,0,1 +29/11/2015,Liverpool,Swansea,1,0,H,0,0,D,A Taylor,10,8,2,0,8,7,12,6,3,1,0,0 +29/11/2015,Norwich,Arsenal,1,1,D,1,1,D,J Moss,7,12,2,3,3,6,8,6,1,1,0,0 +29/11/2015,Tottenham,Chelsea,0,0,D,0,0,D,M Oliver,9,5,4,1,4,4,14,9,4,2,0,0 +29/11/2015,West Ham,West Brom,1,1,D,1,0,H,M Atkinson,17,7,6,3,5,2,15,16,0,2,0,0 +05/12/2015,Arsenal,Sunderland,3,1,H,1,1,D,R Madley,16,11,7,4,3,2,7,11,1,1,0,0 +05/12/2015,Chelsea,Bournemouth,0,1,A,0,0,D,M Jones,19,15,6,5,11,4,10,13,2,1,0,0 +05/12/2015,Man United,West Ham,0,0,D,0,0,D,M Clattenburg,21,9,1,2,6,2,9,13,1,0,0,0 +05/12/2015,Southampton,Aston Villa,1,1,D,0,1,A,A Taylor,23,9,5,2,10,4,4,9,1,2,0,0 +05/12/2015,Stoke,Man City,2,0,H,2,0,H,M Atkinson,7,11,4,4,1,6,9,10,0,1,0,0 +05/12/2015,Swansea,Leicester,0,3,A,0,2,A,M Oliver,10,16,2,5,3,8,3,19,2,3,0,0 +05/12/2015,Watford,Norwich,2,0,H,1,0,H,M Dean,14,6,7,1,4,1,11,10,3,3,0,0 +05/12/2015,West Brom,Tottenham,1,1,D,1,1,D,J Moss,11,9,4,3,3,6,13,10,3,2,0,0 +06/12/2015,Newcastle,Liverpool,2,0,H,0,0,D,A Marriner,6,10,1,1,2,7,6,10,3,1,0,0 +07/12/2015,Everton,Crystal Palace,1,1,D,0,0,D,C Pawson,19,14,4,5,13,6,4,11,1,2,0,0 +12/12/2015,Bournemouth,Man United,2,1,H,1,1,D,A Taylor,11,11,5,5,5,7,12,12,2,1,0,0 +12/12/2015,Crystal Palace,Southampton,1,0,H,1,0,H,M Dean,12,15,5,3,8,5,11,13,0,0,0,0 +12/12/2015,Man City,Swansea,2,1,H,1,0,H,R Madley,13,13,5,7,5,5,13,15,3,1,0,0 +12/12/2015,Norwich,Everton,1,1,D,0,1,A,M Atkinson,9,15,2,3,5,4,9,6,1,0,0,0 +12/12/2015,Sunderland,Watford,0,1,A,0,1,A,G Scott,14,13,2,6,8,3,6,9,1,0,0,0 +12/12/2015,West Ham,Stoke,0,0,D,0,0,D,A Marriner,22,12,9,3,11,7,7,9,1,2,0,0 +13/12/2015,Aston Villa,Arsenal,0,2,A,0,2,A,K Friend,18,8,2,4,5,5,8,7,0,0,0,0 +13/12/2015,Liverpool,West Brom,2,2,D,1,1,D,C Pawson,28,4,8,2,4,4,7,10,0,0,0,0 +13/12/2015,Tottenham,Newcastle,1,2,A,1,0,H,R East,20,8,8,4,6,2,13,14,2,2,0,0 +14/12/2015,Leicester,Chelsea,2,1,H,1,0,H,M Clattenburg,9,11,5,5,1,8,16,8,2,0,0,0 +19/12/2015,Chelsea,Sunderland,3,1,H,2,0,H,R East,17,11,7,3,2,2,12,12,1,3,0,0 +19/12/2015,Everton,Leicester,2,3,A,1,1,D,J Moss,13,10,5,5,10,2,6,14,2,1,0,0 +19/12/2015,Man United,Norwich,1,2,A,0,1,A,M Oliver,11,6,2,4,11,0,4,10,0,2,0,0 +19/12/2015,Newcastle,Aston Villa,1,1,D,1,0,H,M Atkinson,8,10,4,7,3,7,9,14,1,3,0,0 +19/12/2015,Southampton,Tottenham,0,2,A,0,2,A,K Friend,11,13,4,6,4,7,18,9,1,1,0,0 +19/12/2015,Stoke,Crystal Palace,1,2,A,0,1,A,R Madley,26,12,5,3,7,3,6,12,2,0,0,0 +19/12/2015,West Brom,Bournemouth,1,2,A,0,0,D,M Dean,6,16,2,7,4,8,12,9,2,1,2,0 +20/12/2015,Swansea,West Ham,0,0,D,0,0,D,L Mason,21,11,2,2,8,9,6,9,0,1,0,0 +20/12/2015,Watford,Liverpool,3,0,H,2,0,H,M Clattenburg,6,12,5,4,7,9,6,9,1,0,0,0 +21/12/2015,Arsenal,Man City,2,1,H,2,0,H,A Marriner,8,20,5,6,4,8,5,16,0,2,0,0 +26/12/2015,Aston Villa,West Ham,1,1,D,0,1,A,M Dean,15,10,8,4,9,6,14,13,3,3,0,0 +26/12/2015,Bournemouth,Crystal Palace,0,0,D,0,0,D,M Oliver,10,8,4,0,8,9,10,13,1,0,0,0 +26/12/2015,Chelsea,Watford,2,2,D,1,1,D,A Marriner,17,12,2,3,9,4,7,15,1,4,0,0 +26/12/2015,Liverpool,Leicester,1,0,H,0,0,D,M Atkinson,25,7,4,3,7,6,11,8,2,1,0,0 +26/12/2015,Man City,Sunderland,4,1,H,3,0,H,A Taylor,19,11,9,3,5,1,10,11,1,1,0,0 +26/12/2015,Newcastle,Everton,0,1,A,0,0,D,L Mason,10,20,1,7,1,7,16,12,2,2,0,0 +26/12/2015,Southampton,Arsenal,4,0,H,1,0,H,J Moss,15,7,5,4,3,4,10,9,1,0,0,0 +26/12/2015,Stoke,Man United,2,0,H,2,0,H,K Friend,13,11,3,6,1,12,13,16,1,2,0,0 +26/12/2015,Swansea,West Brom,1,0,H,1,0,H,R East,7,9,2,3,0,7,10,13,1,3,0,0 +26/12/2015,Tottenham,Norwich,3,0,H,2,0,H,M Jones,15,11,10,3,9,4,4,13,1,2,0,0 +28/12/2015,Arsenal,Bournemouth,2,0,H,1,0,H,R East,13,10,6,3,9,3,7,8,2,0,0,0 +28/12/2015,Crystal Palace,Swansea,0,0,D,0,0,D,N Swarbrick,10,5,2,3,7,4,11,16,3,2,0,0 +28/12/2015,Everton,Stoke,3,4,A,1,2,A,M Clattenburg,15,8,3,5,6,2,10,12,2,1,0,0 +28/12/2015,Man United,Chelsea,0,0,D,0,0,D,M Atkinson,12,8,2,3,5,3,16,6,4,2,0,0 +28/12/2015,Norwich,Aston Villa,2,0,H,1,0,H,G Scott,9,6,4,3,3,7,10,21,2,5,0,0 +28/12/2015,Watford,Tottenham,1,2,A,1,1,D,A Taylor,9,10,1,3,5,3,11,13,4,2,1,0 +28/12/2015,West Brom,Newcastle,1,0,H,0,0,D,M Jones,22,7,5,2,18,6,7,13,0,0,0,0 +28/12/2015,West Ham,Southampton,2,1,H,0,1,A,M Oliver,13,15,5,4,4,7,6,12,1,3,0,0 +29/12/2015,Leicester,Man City,0,0,D,0,0,D,C Pawson,11,21,4,5,1,13,11,8,1,3,0,0 +30/12/2015,Sunderland,Liverpool,0,1,A,0,0,D,K Friend,8,17,3,6,6,4,8,16,1,3,0,0 +02/01/2016,Arsenal,Newcastle,1,0,H,0,0,D,A Taylor,16,15,3,6,7,4,10,9,2,2,0,0 +02/01/2016,Leicester,Bournemouth,0,0,D,0,0,D,A Marriner,16,9,2,0,7,1,9,5,1,1,0,1 +02/01/2016,Man United,Swansea,2,1,H,0,0,D,J Moss,19,9,6,4,4,2,14,10,2,1,0,0 +02/01/2016,Norwich,Southampton,1,0,H,0,0,D,M Clattenburg,10,10,1,4,4,9,9,7,1,0,0,1 +02/01/2016,Sunderland,Aston Villa,3,1,H,1,0,H,C Pawson,13,8,6,3,3,3,8,13,3,4,0,0 +02/01/2016,Watford,Man City,1,2,A,0,0,D,M Atkinson,11,16,3,5,4,5,6,10,1,0,0,0 +02/01/2016,West Brom,Stoke,2,1,H,0,0,D,L Mason,16,10,5,3,4,6,7,15,1,1,0,1 +02/01/2016,West Ham,Liverpool,2,0,H,1,0,H,R Madley,18,23,10,2,3,9,11,8,1,1,0,0 +03/01/2016,Crystal Palace,Chelsea,0,3,A,0,1,A,K Friend,8,10,4,4,5,7,12,15,3,0,0,0 +03/01/2016,Everton,Tottenham,1,1,D,1,1,D,M Oliver,12,19,3,4,4,4,8,11,1,2,0,0 +12/01/2016,Aston Villa,Crystal Palace,1,0,H,0,0,D,M Clattenburg,13,9,3,1,9,0,15,9,2,1,0,0 +12/01/2016,Bournemouth,West Ham,1,3,A,1,0,H,M Atkinson,11,10,4,5,4,7,8,5,1,1,0,0 +12/01/2016,Newcastle,Man United,3,3,D,1,2,A,M Dean,14,12,6,4,5,3,10,12,1,3,0,0 +13/01/2016,Chelsea,West Brom,2,2,D,1,1,D,A Taylor,14,13,2,6,7,7,13,8,3,3,0,0 +13/01/2016,Liverpool,Arsenal,3,3,D,2,2,D,M Jones,22,14,6,5,9,3,9,7,2,0,0,0 +13/01/2016,Man City,Everton,0,0,D,0,0,D,R East,22,7,5,2,12,5,5,6,0,1,0,0 +13/01/2016,Southampton,Watford,2,0,H,1,0,H,C Pawson,15,4,7,1,8,1,9,11,1,2,0,0 +13/01/2016,Stoke,Norwich,3,1,H,0,0,D,N Swarbrick,14,5,4,2,7,3,5,8,0,1,0,1 +13/01/2016,Swansea,Sunderland,2,4,A,2,1,H,G Scott,10,7,5,5,8,6,9,12,2,0,1,0 +13/01/2016,Tottenham,Leicester,0,1,A,0,0,D,L Mason,21,10,5,2,16,8,13,10,1,0,0,0 +16/01/2016,Aston Villa,Leicester,1,1,D,0,1,A,R East,14,12,5,5,3,4,13,15,3,2,0,0 +16/01/2016,Bournemouth,Norwich,3,0,H,1,0,H,R Madley,15,10,8,3,5,3,8,13,1,1,0,0 +16/01/2016,Chelsea,Everton,3,3,D,0,0,D,M Jones,14,9,7,4,6,4,12,5,1,1,0,0 +16/01/2016,Man City,Crystal Palace,4,0,H,2,0,H,J Moss,17,9,4,3,5,6,10,11,0,2,0,0 +16/01/2016,Newcastle,West Ham,2,1,H,2,0,H,N Swarbrick,23,10,10,3,3,3,9,4,1,0,0,0 +16/01/2016,Southampton,West Brom,3,0,H,2,0,H,M Atkinson,9,9,5,0,1,3,10,11,0,0,0,0 +16/01/2016,Tottenham,Sunderland,4,1,H,1,1,D,M Dean,26,5,11,2,11,3,8,13,0,2,0,0 +17/01/2016,Liverpool,Man United,0,1,A,0,0,D,M Clattenburg,19,7,4,1,5,4,6,18,0,2,0,0 +17/01/2016,Stoke,Arsenal,0,0,D,0,0,D,C Pawson,14,8,6,3,6,6,16,10,0,0,0,0 +18/01/2016,Swansea,Watford,1,0,H,1,0,H,M Oliver,10,13,2,2,6,3,8,11,2,2,0,0 +23/01/2016,Crystal Palace,Tottenham,1,3,A,1,0,H,M Atkinson,11,24,3,11,3,10,11,12,2,2,0,0 +23/01/2016,Leicester,Stoke,3,0,H,1,0,H,M Dean,15,3,5,2,8,4,10,13,0,1,0,0 +23/01/2016,Man United,Southampton,0,1,A,0,0,D,M Jones,8,7,1,2,4,4,11,8,0,0,0,0 +23/01/2016,Norwich,Liverpool,4,5,A,2,1,H,L Mason,6,13,5,7,3,3,10,14,0,1,0,0 +23/01/2016,Sunderland,Bournemouth,1,1,D,1,1,D,R East,10,15,5,5,3,4,5,9,1,1,0,0 +23/01/2016,Watford,Newcastle,2,1,H,0,0,D,A Marriner,13,11,5,2,2,7,10,12,2,1,0,0 +23/01/2016,West Brom,Aston Villa,0,0,D,0,0,D,R Madley,4,15,0,4,2,6,7,13,0,1,0,0 +23/01/2016,West Ham,Man City,2,2,D,1,1,D,C Pawson,10,16,3,3,5,2,11,15,2,3,0,0 +24/01/2016,Arsenal,Chelsea,0,1,A,0,1,A,M Clattenburg,9,12,1,6,6,7,14,13,1,3,1,0 +24/01/2016,Everton,Swansea,1,2,A,1,2,A,A Taylor,20,7,2,5,7,1,5,12,0,3,0,0 +02/02/2016,Arsenal,Southampton,0,0,D,0,0,D,L Mason,21,14,11,3,13,3,11,19,2,1,0,0 +02/02/2016,Crystal Palace,Bournemouth,1,2,A,1,1,D,M Jones,14,8,3,4,5,3,11,15,3,4,0,0 +02/02/2016,Leicester,Liverpool,2,0,H,0,0,D,A Marriner,13,14,6,2,3,12,6,7,0,1,0,0 +02/02/2016,Man United,Stoke,3,0,H,2,0,H,R East,15,10,5,0,2,5,13,13,2,1,0,0 +02/02/2016,Norwich,Tottenham,0,3,A,0,2,A,K Friend,10,17,2,6,6,8,8,9,2,2,0,0 +02/02/2016,Sunderland,Man City,0,1,A,0,1,A,S Attwell,11,6,2,2,8,4,6,15,1,4,0,0 +02/02/2016,West Brom,Swansea,1,1,D,0,0,D,N Swarbrick,17,18,7,4,7,7,12,13,0,0,0,0 +02/02/2016,West Ham,Aston Villa,2,0,H,0,0,D,J Moss,25,6,6,2,5,5,9,11,3,0,0,1 +03/02/2016,Everton,Newcastle,3,0,H,1,0,H,C Pawson,18,7,7,3,3,3,8,15,1,2,0,1 +03/02/2016,Watford,Chelsea,0,0,D,0,0,D,M Dean,12,12,3,4,7,7,10,13,2,1,0,0 +06/02/2016,Aston Villa,Norwich,2,0,H,1,0,H,M Atkinson,10,12,3,4,2,11,10,11,0,2,0,0 +06/02/2016,Liverpool,Sunderland,2,2,D,0,0,D,R Madley,16,7,5,2,7,2,4,12,1,1,0,0 +06/02/2016,Man City,Leicester,1,3,A,0,1,A,A Taylor,22,14,4,7,11,4,9,15,2,2,0,0 +06/02/2016,Newcastle,West Brom,1,0,H,1,0,H,L Mason,19,7,7,0,8,4,12,12,0,3,0,0 +06/02/2016,Southampton,West Ham,1,0,H,1,0,H,M Clattenburg,8,18,2,2,4,5,17,10,1,2,1,0 +06/02/2016,Stoke,Everton,0,3,A,0,3,A,A Marriner,11,14,3,6,6,4,11,8,1,0,0,0 +06/02/2016,Swansea,Crystal Palace,1,1,D,1,0,H,M Dean,19,12,3,2,9,5,11,14,1,2,0,0 +06/02/2016,Tottenham,Watford,1,0,H,0,0,D,R East,26,3,9,0,15,1,12,9,0,1,0,0 +07/02/2016,Bournemouth,Arsenal,0,2,A,0,2,A,K Friend,17,10,4,5,7,3,6,8,0,1,0,0 +07/02/2016,Chelsea,Man United,1,1,D,0,0,D,M Oliver,16,17,5,7,10,11,6,9,0,3,0,0 +13/02/2016,Bournemouth,Stoke,1,3,A,0,1,A,G Scott,11,11,2,6,9,2,7,18,4,2,0,0 +13/02/2016,Chelsea,Newcastle,5,1,H,3,0,H,R Madley,15,10,5,4,8,3,16,14,0,1,1,0 +13/02/2016,Crystal Palace,Watford,1,2,A,1,1,D,R East,14,10,7,3,8,5,11,16,0,2,0,0 +13/02/2016,Everton,West Brom,0,1,A,0,1,A,M Oliver,34,5,6,1,14,1,9,12,1,3,0,0 +13/02/2016,Norwich,West Ham,2,2,D,0,0,D,M Jones,13,11,3,6,3,3,16,11,2,2,0,0 +13/02/2016,Sunderland,Man United,2,1,H,1,1,D,A Marriner,21,12,5,6,6,8,7,15,2,3,0,0 +13/02/2016,Swansea,Southampton,0,1,A,0,0,D,J Moss,13,13,2,5,5,4,10,12,0,3,0,0 +14/02/2016,Arsenal,Leicester,2,1,H,0,1,A,M Atkinson,24,7,6,3,11,1,9,10,4,3,0,1 +14/02/2016,Aston Villa,Liverpool,0,6,A,0,2,A,N Swarbrick,6,11,1,9,0,6,10,9,3,1,0,0 +14/02/2016,Man City,Tottenham,1,2,A,0,0,D,M Clattenburg,19,6,4,3,7,1,12,17,1,3,0,0 +27/02/2016,Leicester,Norwich,1,0,H,0,0,D,N Swarbrick,13,9,3,1,3,4,9,11,0,2,0,0 +27/02/2016,Southampton,Chelsea,1,2,A,1,0,H,M Atkinson,7,16,2,4,7,8,10,11,3,1,0,0 +27/02/2016,Stoke,Aston Villa,2,1,H,0,0,D,K Friend,11,5,3,1,11,0,7,15,0,3,0,0 +27/02/2016,Watford,Bournemouth,0,0,D,0,0,D,A Taylor,14,11,1,3,4,5,14,17,3,1,0,0 +27/02/2016,West Brom,Crystal Palace,3,2,H,3,0,H,J Moss,11,15,5,2,3,6,14,16,2,2,0,0 +27/02/2016,West Ham,Sunderland,1,0,H,1,0,H,M Dean,9,13,3,4,5,6,11,9,1,0,0,0 +28/02/2016,Tottenham,Swansea,2,1,H,0,1,A,M Jones,34,10,14,4,7,7,11,14,2,3,0,0 +28/02/2016,Man United,Arsenal,3,2,H,2,1,H,C Pawson,7,13,5,5,2,3,21,15,4,3,0,0 +01/03/2016,Aston Villa,Everton,1,3,A,0,2,A,R East,14,10,5,7,9,8,8,7,3,1,0,0 +01/03/2016,Bournemouth,Southampton,2,0,H,1,0,H,M Dean,9,6,4,1,10,2,11,18,0,0,0,0 +01/03/2016,Leicester,West Brom,2,2,D,2,1,H,M Clattenburg,22,11,5,3,11,3,10,8,0,2,0,0 +01/03/2016,Norwich,Chelsea,1,2,A,0,2,A,L Mason,13,10,2,2,4,4,17,5,3,2,0,0 +01/03/2016,Sunderland,Crystal Palace,2,2,D,1,0,H,A Taylor,17,13,6,5,4,5,11,14,1,1,0,0 +02/03/2016,Arsenal,Swansea,1,2,A,1,1,D,R Madley,18,11,4,2,7,3,8,11,0,2,0,0 +02/03/2016,Liverpool,Man City,3,0,H,2,0,H,M Atkinson,11,4,7,2,3,2,14,14,0,1,0,0 +02/03/2016,Man United,Watford,1,0,H,0,0,D,M Jones,14,13,3,3,9,7,7,17,1,2,0,0 +02/03/2016,Stoke,Newcastle,1,0,H,0,0,D,N Swarbrick,9,5,2,3,5,2,11,6,1,0,0,0 +02/03/2016,West Ham,Tottenham,1,0,H,1,0,H,A Marriner,14,9,3,2,6,3,10,11,0,3,0,0 +05/03/2016,Chelsea,Stoke,1,1,D,1,0,H,M Clattenburg,20,16,7,5,8,4,9,10,1,2,0,0 +05/03/2016,Everton,West Ham,2,3,A,1,0,H,A Taylor,15,17,8,5,9,6,12,13,2,3,1,0 +05/03/2016,Man City,Aston Villa,4,0,H,0,0,D,L Mason,22,2,9,1,9,1,10,10,0,1,0,0 +05/03/2016,Newcastle,Bournemouth,1,3,A,0,1,A,P Tierney,14,16,3,5,5,4,13,8,1,3,0,0 +05/03/2016,Southampton,Sunderland,1,1,D,0,0,D,N Swarbrick,16,11,5,3,12,2,9,9,2,1,1,0 +05/03/2016,Swansea,Norwich,1,0,H,0,0,D,C Pawson,10,8,3,3,5,0,14,15,2,5,0,0 +05/03/2016,Tottenham,Arsenal,2,2,D,0,1,A,M Oliver,26,10,11,4,9,2,9,10,2,2,0,1 +05/03/2016,Watford,Leicester,0,1,A,0,0,D,J Moss,11,14,4,7,3,8,10,11,2,2,0,0 +06/03/2016,Crystal Palace,Liverpool,1,2,A,0,0,D,A Marriner,14,19,4,5,6,6,12,16,1,2,0,1 +06/03/2016,West Brom,Man United,1,0,H,0,0,D,M Dean,9,7,2,1,4,5,8,7,1,0,0,1 +12/03/2016,Bournemouth,Swansea,3,2,H,1,1,D,R East,12,12,4,4,5,3,12,11,1,0,0,0 +12/03/2016,Norwich,Man City,0,0,D,0,0,D,J Moss,5,15,0,3,3,8,15,10,3,1,0,0 +12/03/2016,Stoke,Southampton,1,2,A,0,2,A,L Mason,13,11,2,4,1,4,13,12,1,2,0,1 +13/03/2016,Aston Villa,Tottenham,0,2,A,0,1,A,A Taylor,11,19,2,8,5,8,6,6,0,0,0,0 +14/03/2016,Leicester,Newcastle,1,0,H,1,0,H,C Pawson,10,13,1,2,8,4,4,9,0,0,0,0 +19/03/2016,Chelsea,West Ham,2,2,D,1,1,D,R Madley,22,13,5,6,10,9,13,14,3,5,0,0 +19/03/2016,Crystal Palace,Leicester,0,1,A,0,1,A,M Jones,12,8,4,3,10,7,9,13,1,1,0,0 +19/03/2016,Everton,Arsenal,0,2,A,0,2,A,M Clattenburg,8,11,2,3,7,5,7,10,0,0,0,0 +19/03/2016,Swansea,Aston Villa,1,0,H,0,0,D,M Dean,10,12,1,2,4,6,9,10,3,2,0,0 +19/03/2016,Watford,Stoke,1,2,A,0,1,A,C Pawson,9,9,4,6,9,2,10,14,3,1,0,0 +19/03/2016,West Brom,Norwich,0,1,A,0,0,D,A Taylor,10,7,0,1,4,3,8,13,1,2,0,0 +20/03/2016,Man City,Man United,0,1,A,0,1,A,M Oliver,26,5,2,3,11,5,9,12,1,1,0,0 +20/03/2016,Newcastle,Sunderland,1,1,D,0,1,A,M Atkinson,17,14,8,4,2,5,14,7,4,1,0,0 +20/03/2016,Southampton,Liverpool,3,2,H,0,2,A,R East,16,18,5,9,3,5,10,13,1,3,0,0 +20/03/2016,Tottenham,Bournemouth,3,0,H,2,0,H,N Swarbrick,17,2,8,0,4,5,11,6,0,1,0,0 +02/04/2016,Arsenal,Watford,4,0,H,2,0,H,A Taylor,19,6,12,2,8,3,7,10,1,1,0,0 +02/04/2016,Aston Villa,Chelsea,0,4,A,0,2,A,N Swarbrick,15,11,4,8,7,2,12,8,4,1,1,0 +02/04/2016,Bournemouth,Man City,0,4,A,0,3,A,R Madley,11,15,2,7,4,4,9,8,0,1,0,0 +02/04/2016,Liverpool,Tottenham,1,1,D,0,0,D,J Moss,15,18,7,5,12,9,11,11,2,0,0,0 +02/04/2016,Norwich,Newcastle,3,2,H,1,0,H,M Dean,13,13,6,5,6,5,6,11,0,0,0,0 +02/04/2016,Stoke,Swansea,2,2,D,1,0,H,M Atkinson,12,12,5,3,5,5,6,16,0,1,0,0 +02/04/2016,Sunderland,West Brom,0,0,D,0,0,D,R East,22,3,7,0,9,3,11,13,1,1,0,0 +02/04/2016,West Ham,Crystal Palace,2,2,D,2,1,H,M Clattenburg,18,12,6,5,4,5,12,19,2,2,1,0 +03/04/2016,Leicester,Southampton,1,0,H,1,0,H,M Oliver,11,10,4,2,7,8,12,13,2,2,0,0 +03/04/2016,Man United,Everton,1,0,H,0,0,D,A Marriner,10,7,2,1,5,5,15,6,2,0,0,0 +09/04/2016,Aston Villa,Bournemouth,1,2,A,0,1,A,M Atkinson,6,12,1,3,3,8,13,11,2,2,0,0 +09/04/2016,Crystal Palace,Norwich,1,0,H,0,0,D,M Oliver,13,13,6,4,10,4,13,10,2,1,0,0 +09/04/2016,Man City,West Brom,2,1,H,1,1,D,M Jones,13,13,4,2,8,8,10,7,0,1,0,0 +09/04/2016,Southampton,Newcastle,3,1,H,2,0,H,R Madley,20,5,6,3,11,2,8,7,0,0,0,0 +09/04/2016,Swansea,Chelsea,1,0,H,1,0,H,A Marriner,12,8,6,2,1,9,15,13,5,4,0,0 +09/04/2016,Watford,Everton,1,1,D,1,1,D,K Friend,13,12,5,7,7,6,13,10,2,1,0,0 +09/04/2016,West Ham,Arsenal,3,3,D,2,2,D,C Pawson,19,11,7,6,7,4,11,8,2,0,0,0 +10/04/2016,Liverpool,Stoke,4,1,H,2,1,H,M Clattenburg,14,10,6,3,6,3,14,14,0,2,0,0 +10/04/2016,Sunderland,Leicester,0,2,A,0,0,D,A Taylor,15,17,1,7,1,7,9,9,1,1,0,0 +10/04/2016,Tottenham,Man United,3,0,H,0,0,D,M Dean,16,5,8,1,2,7,10,10,3,3,0,0 +13/04/2016,Crystal Palace,Everton,0,0,D,0,0,D,M Jones,18,9,6,5,10,7,7,9,0,1,0,1 +16/04/2016,Chelsea,Man City,0,3,A,0,1,A,M Dean,11,11,4,8,11,3,12,15,2,3,1,0 +16/04/2016,Everton,Southampton,1,1,D,0,0,D,C Pawson,7,18,2,4,3,10,8,14,1,1,0,0 +16/04/2016,Man United,Aston Villa,1,0,H,1,0,H,K Friend,13,7,4,1,14,3,9,10,0,1,0,0 +16/04/2016,Newcastle,Swansea,3,0,H,1,0,H,L Mason,12,11,7,1,3,7,13,9,2,1,0,0 +16/04/2016,Norwich,Sunderland,0,3,A,0,1,A,A Marriner,19,8,6,3,14,0,7,11,2,2,0,0 +16/04/2016,West Brom,Watford,0,1,A,0,1,A,M Oliver,16,11,4,4,9,3,11,17,0,3,0,0 +17/04/2016,Arsenal,Crystal Palace,1,1,D,1,0,H,R East,21,7,6,2,10,5,7,13,0,2,0,0 +17/04/2016,Bournemouth,Liverpool,1,2,A,0,2,A,M Jones,18,19,5,6,5,4,9,9,0,0,0,0 +17/04/2016,Leicester,West Ham,2,2,D,1,0,H,J Moss,6,19,2,4,0,5,14,14,1,3,1,0 +18/04/2016,Stoke,Tottenham,0,4,A,0,1,A,N Swarbrick,12,23,2,6,5,3,12,12,2,0,0,0 +19/04/2016,Newcastle,Man City,1,1,D,1,1,D,K Friend,5,9,3,5,5,3,13,8,3,2,0,0 +20/04/2016,Liverpool,Everton,4,0,H,2,0,H,R Madley,37,3,13,0,9,1,8,11,1,1,0,1 +20/04/2016,Man United,Crystal Palace,2,0,H,1,0,H,L Mason,16,5,10,0,10,2,13,7,1,2,0,0 +20/04/2016,West Ham,Watford,3,1,H,2,0,H,M Dean,18,7,7,4,4,2,11,17,0,2,0,1 +21/04/2016,Arsenal,West Brom,2,0,H,2,0,H,J Moss,16,8,7,1,2,6,14,14,0,1,0,0 +23/04/2016,Aston Villa,Southampton,2,4,A,1,2,A,N Swarbrick,16,17,7,7,6,11,8,9,2,0,0,0 +23/04/2016,Bournemouth,Chelsea,1,4,A,1,2,A,R East,13,16,4,4,10,7,11,4,1,0,0,0 +23/04/2016,Liverpool,Newcastle,2,2,D,2,0,H,A Marriner,13,6,4,3,5,4,8,17,1,1,0,0 +23/04/2016,Man City,Stoke,4,0,H,2,0,H,R Madley,11,14,6,2,9,4,8,8,0,0,0,0 +24/04/2016,Leicester,Swansea,4,0,H,2,0,H,M Clattenburg,18,9,9,1,4,6,15,10,1,2,0,0 +24/04/2016,Sunderland,Arsenal,0,0,D,0,0,D,M Dean,12,20,3,7,7,7,10,8,2,4,0,0 +25/04/2016,Tottenham,West Brom,1,1,D,1,0,H,M Jones,19,16,2,3,9,7,8,13,0,1,0,0 +30/04/2016,Arsenal,Norwich,1,0,H,0,0,D,M Jones,14,12,3,3,7,3,8,7,1,0,0,0 +30/04/2016,Everton,Bournemouth,2,1,H,1,1,D,N Swarbrick,12,16,5,2,3,5,4,8,1,0,0,0 +30/04/2016,Newcastle,Crystal Palace,1,0,H,0,0,D,M Dean,10,9,4,3,4,8,11,12,2,0,0,0 +30/04/2016,Stoke,Sunderland,1,1,D,0,0,D,C Pawson,11,14,3,5,2,8,13,13,2,2,0,0 +30/04/2016,Watford,Aston Villa,3,2,H,1,1,D,A Taylor,18,7,6,2,8,4,14,10,2,3,0,1 +30/04/2016,West Brom,West Ham,0,3,A,0,2,A,L Mason,15,8,5,4,8,6,14,7,0,0,0,0 +01/05/2016,Man United,Leicester,1,1,D,1,1,D,M Oliver,21,14,6,3,6,5,16,10,3,0,0,1 +01/05/2016,Southampton,Man City,4,2,H,2,1,H,A Marriner,14,6,7,5,8,2,12,9,2,1,0,0 +01/05/2016,Swansea,Liverpool,3,1,H,2,0,H,R East,18,11,9,3,4,7,12,13,2,2,0,1 +02/05/2016,Chelsea,Tottenham,2,2,D,0,2,A,M Clattenburg,13,16,5,5,7,8,9,20,3,9,0,0 +07/05/2016,Aston Villa,Newcastle,0,0,D,0,0,D,R Madley,2,9,0,3,0,5,8,13,1,1,0,0 +07/05/2016,Bournemouth,West Brom,1,1,D,0,1,A,M Clattenburg,12,12,4,4,5,3,5,8,2,1,0,0 +07/05/2016,Crystal Palace,Stoke,2,1,H,0,1,A,K Friend,17,12,4,3,4,3,8,18,2,3,0,0 +07/05/2016,Leicester,Everton,3,1,H,2,0,H,A Marriner,32,9,8,5,12,4,4,12,0,3,0,0 +07/05/2016,Norwich,Man United,0,1,A,0,0,D,C Pawson,8,15,2,2,1,6,11,10,1,0,0,0 +07/05/2016,Sunderland,Chelsea,3,2,H,1,2,A,M Jones,15,9,4,8,3,3,11,9,4,1,0,1 +07/05/2016,West Ham,Swansea,1,4,A,0,2,A,M Oliver,25,13,7,6,13,3,11,8,3,0,0,0 +08/05/2016,Liverpool,Watford,2,0,H,1,0,H,L Mason,19,14,5,3,8,8,10,15,1,2,0,0 +08/05/2016,Man City,Arsenal,2,2,D,1,1,D,A Taylor,14,5,3,2,7,2,16,10,2,1,0,0 +08/05/2016,Tottenham,Southampton,1,2,A,1,1,D,J Moss,20,10,6,2,10,2,8,16,0,3,0,0 +10/05/2016,West Ham,Man United,3,2,H,1,0,H,M Dean,20,3,6,2,6,2,6,18,1,3,0,0 +11/05/2016,Liverpool,Chelsea,1,1,D,0,1,A,M Oliver,28,13,9,7,7,3,11,12,3,1,0,0 +11/05/2016,Norwich,Watford,4,2,H,3,1,H,R East,18,12,6,7,5,1,8,12,2,3,0,0 +11/05/2016,Sunderland,Everton,3,0,H,2,0,H,A Taylor,14,17,8,6,5,5,15,7,1,0,0,0 +15/05/2016,Arsenal,Aston Villa,4,0,H,1,0,H,M Clattenburg,16,5,7,2,5,4,12,6,0,1,0,0 +15/05/2016,Chelsea,Leicester,1,1,D,0,0,D,C Pawson,17,18,4,5,7,6,4,12,0,0,0,0 +15/05/2016,Everton,Norwich,3,0,H,2,0,H,L Mason,15,10,8,2,4,11,7,11,0,0,0,0 +15/05/2016,Newcastle,Tottenham,5,1,H,2,0,H,A Taylor,19,17,10,5,3,8,7,6,1,2,1,0 +15/05/2016,Southampton,Crystal Palace,4,1,H,1,0,H,M Oliver,18,16,5,4,2,5,7,14,1,2,0,0 +15/05/2016,Stoke,West Ham,2,1,H,0,1,A,M Jones,7,20,4,7,4,10,8,12,3,1,0,0 +15/05/2016,Swansea,Man City,1,1,D,1,1,D,M Dean,4,18,1,5,4,4,4,19,1,3,0,0 +15/05/2016,Watford,Sunderland,2,2,D,0,1,A,K Friend,21,6,6,4,7,4,11,10,1,0,0,0 +15/05/2016,West Brom,Liverpool,1,1,D,1,1,D,R Madley,13,7,1,3,7,5,11,14,3,1,0,0 +17/05/2016,Man United,Bournemouth,3,1,H,1,0,H,J Moss,12,7,5,0,7,1,9,8,1,0,0,0 +13/08/2016,Burnley,Swansea,0,1,A,0,0,D,J Moss,10,17,3,9,7,4,10,14,3,2,0,0 +13/08/2016,Crystal Palace,West Brom,0,1,A,0,0,D,C Pawson,14,13,4,3,3,6,12,15,2,2,0,0 +13/08/2016,Everton,Tottenham,1,1,D,1,0,H,M Atkinson,12,13,6,4,5,6,10,14,0,0,0,0 +13/08/2016,Hull,Leicester,2,1,H,1,0,H,M Dean,14,18,5,5,5,3,8,17,2,2,0,0 +13/08/2016,Man City,Sunderland,2,1,H,1,0,H,R Madley,16,7,4,3,9,6,11,14,1,2,0,0 +13/08/2016,Middlesbrough,Stoke,1,1,D,1,0,H,K Friend,12,12,2,1,9,6,18,14,3,5,0,0 +13/08/2016,Southampton,Watford,1,1,D,0,1,A,R East,24,5,6,1,6,2,8,12,1,2,0,1 +14/08/2016,Arsenal,Liverpool,3,4,A,1,1,D,M Oliver,9,16,5,7,5,4,13,17,3,3,0,0 +14/08/2016,Bournemouth,Man United,1,3,A,0,1,A,A Marriner,9,11,3,7,4,2,7,10,0,1,0,0 +15/08/2016,Chelsea,West Ham,2,1,H,0,0,D,A Taylor,16,7,6,3,7,1,16,16,5,2,0,0 +19/08/2016,Man United,Southampton,2,0,H,1,0,H,A Taylor,12,13,5,1,4,0,11,8,0,0,0,0 +20/08/2016,Burnley,Liverpool,2,0,H,2,0,H,L Mason,3,26,2,5,1,12,14,5,0,1,0,0 +20/08/2016,Leicester,Arsenal,0,0,D,0,0,D,M Clattenburg,8,13,1,4,2,7,11,7,1,2,0,0 +20/08/2016,Stoke,Man City,1,4,A,0,2,A,M Dean,8,12,3,6,7,7,12,13,3,4,0,0 +20/08/2016,Swansea,Hull,0,2,A,0,0,D,S Attwell,13,12,3,4,8,3,7,10,0,0,0,0 +20/08/2016,Tottenham,Crystal Palace,1,0,H,0,0,D,M Oliver,20,10,5,2,10,4,19,9,3,3,0,0 +20/08/2016,Watford,Chelsea,1,2,A,0,0,D,J Moss,6,13,2,4,0,5,20,10,4,3,0,0 +20/08/2016,West Brom,Everton,1,2,A,1,1,D,N Swarbrick,11,13,4,7,7,4,16,10,3,1,0,0 +21/08/2016,Sunderland,Middlesbrough,1,2,A,0,2,A,M Atkinson,18,8,5,3,8,1,11,14,1,1,0,0 +21/08/2016,West Ham,Bournemouth,1,0,H,0,0,D,C Pawson,15,8,2,5,1,6,15,15,3,1,0,1 +27/08/2016,Chelsea,Burnley,3,0,H,2,0,H,M Clattenburg,22,6,10,0,9,5,6,9,2,2,0,0 +27/08/2016,Crystal Palace,Bournemouth,1,1,D,0,1,A,M Dean,24,10,5,3,10,4,15,10,2,1,0,0 +27/08/2016,Everton,Stoke,1,0,H,0,0,D,M Oliver,21,8,9,1,9,2,14,11,2,1,0,0 +27/08/2016,Hull,Man United,0,1,A,0,0,D,J Moss,8,29,2,9,1,6,9,6,2,3,0,0 +27/08/2016,Leicester,Swansea,2,1,H,1,0,H,R East,13,9,6,3,4,6,14,10,2,2,0,0 +27/08/2016,Southampton,Sunderland,1,1,D,0,0,D,L Mason,16,5,7,2,5,5,15,8,2,2,0,0 +27/08/2016,Tottenham,Liverpool,1,1,D,0,1,A,R Madley,11,13,4,3,5,5,11,17,3,5,0,0 +27/08/2016,Watford,Arsenal,1,3,A,0,3,A,K Friend,14,10,6,7,3,2,18,15,6,1,0,0 +28/08/2016,Man City,West Ham,3,1,H,2,0,H,A Marriner,22,9,5,2,5,4,5,10,1,3,0,0 +28/08/2016,West Brom,Middlesbrough,0,0,D,0,0,D,A Taylor,13,7,2,2,5,4,12,11,1,3,0,0 +10/09/2016,Arsenal,Southampton,2,1,H,1,1,D,R Madley,17,11,2,5,6,1,10,8,2,5,0,0 +10/09/2016,Bournemouth,West Brom,1,0,H,0,0,D,K Friend,12,12,5,2,10,5,9,11,1,3,0,0 +10/09/2016,Burnley,Hull,1,1,D,0,0,D,P Tierney,13,11,2,3,5,9,13,3,2,0,0,0 +10/09/2016,Liverpool,Leicester,4,1,H,2,1,H,C Pawson,17,12,11,3,1,7,4,8,1,2,0,0 +10/09/2016,Man United,Man City,1,2,A,1,2,A,M Clattenburg,14,18,3,6,4,4,15,10,4,2,0,0 +10/09/2016,Middlesbrough,Crystal Palace,1,2,A,1,1,D,N Swarbrick,12,12,4,4,6,6,5,9,1,2,0,0 +10/09/2016,Stoke,Tottenham,0,4,A,0,1,A,A Taylor,15,20,2,8,10,8,13,18,3,2,0,0 +10/09/2016,West Ham,Watford,2,4,A,2,2,D,M Atkinson,19,13,4,8,4,4,10,15,2,2,0,0 +11/09/2016,Swansea,Chelsea,2,2,D,0,1,A,A Marriner,6,28,2,7,1,11,17,8,3,4,0,0 +12/09/2016,Sunderland,Everton,0,3,A,0,0,D,M Jones,11,20,2,9,5,4,11,8,1,1,0,0 +16/09/2016,Chelsea,Liverpool,1,2,A,0,2,A,M Atkinson,12,13,4,5,6,4,6,13,1,1,0,0 +17/09/2016,Everton,Middlesbrough,3,1,H,3,1,H,L Mason,11,7,6,0,6,3,5,15,2,1,0,0 +17/09/2016,Hull,Arsenal,1,4,A,0,1,A,R East,6,24,2,9,2,4,9,12,0,2,1,0 +17/09/2016,Leicester,Burnley,3,0,H,1,0,H,A Taylor,14,7,6,2,7,1,10,9,0,1,0,0 +17/09/2016,Man City,Bournemouth,4,0,H,2,0,H,J Moss,20,6,12,2,5,5,8,11,1,1,1,0 +17/09/2016,West Brom,West Ham,4,2,H,3,0,H,M Clattenburg,8,23,6,5,2,6,14,6,2,1,0,0 +18/09/2016,Crystal Palace,Stoke,4,1,H,2,0,H,C Pawson,14,11,8,4,10,5,15,9,2,1,0,0 +18/09/2016,Southampton,Swansea,1,0,H,0,0,D,M Jones,19,9,6,3,7,1,14,9,1,1,0,0 +18/09/2016,Tottenham,Sunderland,1,0,H,0,0,D,M Dean,31,6,9,2,14,1,7,16,1,5,0,1 +18/09/2016,Watford,Man United,3,1,H,1,0,H,M Oliver,10,15,5,2,5,7,14,19,3,4,0,0 +24/09/2016,Arsenal,Chelsea,3,0,H,3,0,H,M Oliver,14,9,5,2,9,1,9,11,0,2,0,0 +24/09/2016,Bournemouth,Everton,1,0,H,1,0,H,R Madley,12,13,1,2,5,3,10,14,2,2,0,0 +24/09/2016,Liverpool,Hull,5,1,H,3,0,H,A Marriner,32,2,12,1,13,2,8,8,1,2,0,1 +24/09/2016,Man United,Leicester,4,1,H,4,0,H,M Dean,16,10,7,3,8,6,9,12,0,2,0,0 +24/09/2016,Middlesbrough,Tottenham,1,2,A,0,2,A,G Scott,6,19,2,5,2,10,14,9,2,1,0,0 +24/09/2016,Stoke,West Brom,1,1,D,0,0,D,M Atkinson,12,9,2,3,6,6,9,13,3,4,0,0 +24/09/2016,Sunderland,Crystal Palace,2,3,A,1,0,H,A Taylor,11,20,4,4,3,5,15,12,0,2,0,0 +24/09/2016,Swansea,Man City,1,3,A,1,1,D,N Swarbrick,13,18,5,4,2,7,15,9,2,4,0,0 +25/09/2016,West Ham,Southampton,0,3,A,0,1,A,J Moss,13,18,1,9,6,2,9,14,5,2,0,0 +26/09/2016,Burnley,Watford,2,0,H,1,0,H,M Jones,13,12,4,3,7,6,11,15,1,3,0,0 +30/09/2016,Everton,Crystal Palace,1,1,D,1,0,H,J Moss,13,10,3,2,10,9,16,11,5,4,0,0 +01/10/2016,Hull,Chelsea,0,2,A,0,0,D,A Taylor,8,22,3,9,5,7,13,15,2,2,0,0 +01/10/2016,Sunderland,West Brom,1,1,D,0,1,A,S Attwell,7,17,2,7,6,5,7,13,1,3,0,0 +01/10/2016,Swansea,Liverpool,1,2,A,1,0,H,M Oliver,8,18,3,6,3,10,11,9,2,2,0,0 +01/10/2016,Watford,Bournemouth,2,2,D,0,1,A,M Dean,17,11,7,2,4,5,17,12,3,4,0,0 +01/10/2016,West Ham,Middlesbrough,1,1,D,0,0,D,N Swarbrick,19,9,2,3,4,5,13,12,2,3,0,0 +02/10/2016,Burnley,Arsenal,0,1,A,0,0,D,C Pawson,10,18,1,3,2,6,2,3,0,0,0,0 +02/10/2016,Leicester,Southampton,0,0,D,0,0,D,M Atkinson,9,11,1,5,3,6,9,9,1,0,0,0 +02/10/2016,Man United,Stoke,1,1,D,0,0,D,R Madley,24,7,9,6,13,2,14,9,3,0,0,0 +02/10/2016,Tottenham,Man City,2,0,H,2,0,H,A Marriner,13,12,7,6,4,3,20,10,2,2,0,0 +15/10/2016,Arsenal,Swansea,3,2,H,2,1,H,J Moss,16,13,6,6,5,6,7,10,0,1,1,0 +15/10/2016,Bournemouth,Hull,6,1,H,3,1,H,L Mason,22,11,10,4,5,4,8,14,0,3,0,0 +15/10/2016,Chelsea,Leicester,3,0,H,2,0,H,A Marriner,16,5,6,0,8,5,6,12,1,1,0,0 +15/10/2016,Crystal Palace,West Ham,0,1,A,0,1,A,M Atkinson,12,12,3,4,7,6,12,13,1,3,0,1 +15/10/2016,Man City,Everton,1,1,D,0,0,D,M Oliver,19,3,8,2,13,1,7,9,0,2,0,0 +15/10/2016,Stoke,Sunderland,2,0,H,2,0,H,M Jones,18,11,6,1,6,4,10,13,1,1,0,0 +15/10/2016,West Brom,Tottenham,1,1,D,0,0,D,K Friend,8,20,4,8,5,8,14,10,2,2,0,0 +16/10/2016,Middlesbrough,Watford,0,1,A,0,0,D,R East,10,4,3,2,4,6,15,10,4,1,0,0 +16/10/2016,Southampton,Burnley,3,1,H,0,0,D,M Dean,34,6,14,1,8,4,11,10,1,2,0,0 +17/10/2016,Liverpool,Man United,0,0,D,0,0,D,A Taylor,9,7,3,1,3,1,14,20,0,4,0,0 +22/10/2016,Arsenal,Middlesbrough,0,0,D,0,0,D,M Dean,9,11,5,4,8,3,6,13,1,0,0,0 +22/10/2016,Bournemouth,Tottenham,0,0,D,0,0,D,C Pawson,8,16,1,4,7,4,10,15,2,4,0,0 +22/10/2016,Burnley,Everton,2,1,H,1,0,H,M Jones,9,20,3,8,2,8,16,15,2,3,0,0 +22/10/2016,Hull,Stoke,0,2,A,0,1,A,K Friend,9,12,2,6,5,7,16,8,2,3,0,0 +22/10/2016,Leicester,Crystal Palace,3,1,H,1,0,H,M Oliver,17,23,4,7,8,8,10,8,0,0,0,0 +22/10/2016,Liverpool,West Brom,2,1,H,2,0,H,N Swarbrick,21,6,7,1,3,2,9,13,1,3,0,0 +22/10/2016,Swansea,Watford,0,0,D,0,0,D,P Tierney,11,10,5,2,7,2,15,16,2,1,0,0 +22/10/2016,West Ham,Sunderland,1,0,H,0,0,D,R Madley,20,10,3,2,6,2,10,13,2,5,0,0 +23/10/2016,Chelsea,Man United,4,0,H,2,0,H,M Atkinson,14,16,6,5,5,7,7,17,3,2,0,0 +23/10/2016,Man City,Southampton,1,1,D,0,1,A,M Clattenburg,14,6,3,2,10,3,11,10,3,3,0,0 +29/10/2016,Crystal Palace,Liverpool,2,4,A,2,3,A,A Marriner,7,17,6,10,3,3,15,5,1,2,0,0 +29/10/2016,Man United,Burnley,0,0,D,0,0,D,M Clattenburg,37,7,11,1,19,1,12,9,1,3,1,0 +29/10/2016,Middlesbrough,Bournemouth,2,0,H,1,0,H,S Attwell,10,15,3,3,4,7,3,5,1,1,0,0 +29/10/2016,Sunderland,Arsenal,1,4,A,0,1,A,M Atkinson,3,21,1,7,0,8,9,13,4,3,0,0 +29/10/2016,Tottenham,Leicester,1,1,D,1,0,H,R Madley,22,6,5,1,8,1,11,20,3,4,0,0 +29/10/2016,Watford,Hull,1,0,H,0,0,D,J Moss,22,8,0,2,7,2,13,8,1,2,0,0 +29/10/2016,West Brom,Man City,0,4,A,0,2,A,L Mason,9,21,1,8,1,7,17,10,4,3,0,0 +30/10/2016,Everton,West Ham,2,0,H,0,0,D,A Taylor,17,13,6,3,8,8,15,12,2,3,0,0 +30/10/2016,Southampton,Chelsea,0,2,A,0,1,A,M Jones,9,13,1,7,7,3,13,16,0,0,0,0 +31/10/2016,Stoke,Swansea,3,1,H,1,1,D,M Oliver,17,14,4,6,6,4,7,14,2,0,0,0 +05/11/2016,Bournemouth,Sunderland,1,2,A,1,1,D,M Dean,22,9,7,4,12,2,14,17,3,2,0,1 +05/11/2016,Burnley,Crystal Palace,3,2,H,2,0,H,A Taylor,14,16,7,5,4,7,11,12,1,2,0,0 +05/11/2016,Chelsea,Everton,5,0,H,3,0,H,R Madley,21,1,9,0,4,2,5,16,0,3,0,0 +05/11/2016,Man City,Middlesbrough,1,1,D,1,0,H,K Friend,25,5,7,3,8,2,7,15,1,3,0,0 +05/11/2016,West Ham,Stoke,1,1,D,0,0,D,A Marriner,9,12,2,4,6,4,11,10,4,1,0,0 +06/11/2016,Arsenal,Tottenham,1,1,D,1,0,H,M Clattenburg,15,10,2,3,7,2,11,17,1,2,0,0 +06/11/2016,Hull,Southampton,2,1,H,0,1,A,G Scott,6,19,2,7,1,10,13,15,0,5,0,0 +06/11/2016,Leicester,West Brom,1,2,A,0,0,D,C Pawson,5,11,2,5,5,6,8,8,0,2,0,0 +06/11/2016,Liverpool,Watford,6,1,H,3,0,H,M Oliver,28,11,17,8,6,3,14,10,1,2,0,0 +06/11/2016,Swansea,Man United,1,3,A,0,3,A,N Swarbrick,6,9,1,3,2,0,9,16,0,2,0,0 +19/11/2016,Crystal Palace,Man City,1,2,A,0,1,A,R Madley,9,10,2,4,2,7,12,15,1,2,0,0 +19/11/2016,Everton,Swansea,1,1,D,0,1,A,M Atkinson,14,5,6,3,10,3,13,12,4,2,0,0 +19/11/2016,Man United,Arsenal,1,1,D,0,0,D,A Marriner,12,5,5,1,10,4,14,11,3,3,0,0 +19/11/2016,Southampton,Liverpool,0,0,D,0,0,D,M Clattenburg,3,15,0,2,1,8,8,11,1,1,0,0 +19/11/2016,Stoke,Bournemouth,0,1,A,0,1,A,R East,14,9,6,2,3,4,17,10,3,2,0,0 +19/11/2016,Sunderland,Hull,3,0,H,1,0,H,L Mason,11,17,4,7,2,8,9,8,1,0,1,0 +19/11/2016,Tottenham,West Ham,3,2,H,0,1,A,M Dean,14,11,6,3,4,5,13,16,3,2,0,1 +19/11/2016,Watford,Leicester,2,1,H,2,1,H,N Swarbrick,10,10,3,2,4,6,12,18,2,3,0,0 +20/11/2016,Middlesbrough,Chelsea,0,1,A,0,1,A,J Moss,12,13,1,3,2,3,13,10,2,3,0,0 +21/11/2016,West Brom,Burnley,4,0,H,3,0,H,M Jones,16,5,6,2,4,3,12,12,0,0,0,0 +26/11/2016,Burnley,Man City,1,2,A,1,1,D,A Marriner,10,21,5,5,5,8,5,15,2,3,0,0 +26/11/2016,Chelsea,Tottenham,2,1,H,1,1,D,M Oliver,9,12,5,6,3,8,13,16,2,1,0,0 +26/11/2016,Hull,West Brom,1,1,D,0,1,A,P Tierney,10,11,4,2,2,6,7,10,3,2,0,0 +26/11/2016,Leicester,Middlesbrough,2,2,D,1,1,D,L Mason,11,6,3,2,2,3,15,12,4,4,0,0 +26/11/2016,Liverpool,Sunderland,2,0,H,0,0,D,A Taylor,27,6,7,1,9,4,5,9,1,3,0,0 +26/11/2016,Swansea,Crystal Palace,5,4,H,1,1,D,K Friend,18,12,9,4,10,6,12,24,4,5,0,0 +27/11/2016,Arsenal,Bournemouth,3,1,H,1,1,D,M Jones,16,9,3,3,3,10,12,13,3,5,0,0 +27/11/2016,Man United,West Ham,1,1,D,1,1,D,J Moss,17,6,8,2,8,4,16,16,3,1,0,0 +27/11/2016,Southampton,Everton,1,0,H,1,0,H,C Pawson,17,13,7,1,5,2,11,15,0,2,0,0 +27/11/2016,Watford,Stoke,0,1,A,0,1,A,R Madley,11,14,2,1,3,3,24,14,3,0,1,0 +03/12/2016,Crystal Palace,Southampton,3,0,H,2,0,H,N Swarbrick,9,17,4,3,4,9,11,13,3,1,0,0 +03/12/2016,Man City,Chelsea,1,3,A,1,0,H,A Taylor,14,10,5,4,9,2,14,9,2,3,2,0 +03/12/2016,Stoke,Burnley,2,0,H,2,0,H,M Clattenburg,10,14,4,4,4,9,7,13,3,2,0,0 +03/12/2016,Sunderland,Leicester,2,1,H,0,0,D,A Marriner,22,17,6,5,3,4,8,12,3,1,0,0 +03/12/2016,Tottenham,Swansea,5,0,H,2,0,H,J Moss,28,1,15,0,5,0,11,17,0,4,0,0 +03/12/2016,West Brom,Watford,3,1,H,2,0,H,G Scott,11,17,3,5,3,4,12,8,2,1,0,1 +03/12/2016,West Ham,Arsenal,1,5,A,0,1,A,C Pawson,10,19,3,10,3,2,10,11,3,3,0,0 +04/12/2016,Bournemouth,Liverpool,4,3,H,0,2,A,R Madley,12,10,8,3,4,9,9,17,2,2,0,0 +04/12/2016,Everton,Man United,1,1,D,0,1,A,M Oliver,13,10,6,2,6,3,13,11,2,3,0,0 +05/12/2016,Middlesbrough,Hull,1,0,H,0,0,D,M Dean,16,6,4,2,13,7,6,11,1,1,0,0 +10/12/2016,Arsenal,Stoke,3,1,H,1,1,D,L Mason,21,12,6,5,7,7,9,6,0,0,0,0 +10/12/2016,Burnley,Bournemouth,3,2,H,2,1,H,M Atkinson,16,17,7,4,4,11,14,18,2,2,0,0 +10/12/2016,Hull,Crystal Palace,3,3,D,1,0,H,M Jones,15,14,6,6,4,7,16,14,2,4,0,0 +10/12/2016,Leicester,Man City,4,2,H,3,0,H,M Oliver,10,19,6,4,3,11,10,7,2,1,0,0 +10/12/2016,Swansea,Sunderland,3,0,H,0,0,D,C Pawson,14,11,5,2,7,4,10,8,1,2,0,0 +10/12/2016,Watford,Everton,3,2,H,1,1,D,A Taylor,11,11,6,4,2,4,11,16,2,2,0,0 +11/12/2016,Chelsea,West Brom,1,0,H,0,0,D,M Dean,12,6,2,1,3,1,6,10,2,4,0,0 +11/12/2016,Liverpool,West Ham,2,2,D,1,2,A,M Clattenburg,18,7,3,3,11,4,7,8,2,0,0,0 +11/12/2016,Man United,Tottenham,1,0,H,1,0,H,R Madley,15,12,5,4,3,8,19,17,3,3,0,0 +11/12/2016,Southampton,Middlesbrough,1,0,H,0,0,D,S Attwell,14,6,3,2,4,2,14,12,4,3,0,0 +13/12/2016,Bournemouth,Leicester,1,0,H,1,0,H,P Tierney,9,12,3,4,4,11,5,15,0,3,0,0 +13/12/2016,Everton,Arsenal,2,1,H,1,1,D,M Clattenburg,14,9,4,3,8,3,10,10,1,1,1,0 +14/12/2016,Crystal Palace,Man United,1,2,A,0,1,A,C Pawson,6,16,3,6,1,8,16,10,2,2,0,0 +14/12/2016,Man City,Watford,2,0,H,1,0,H,K Friend,13,5,7,2,5,2,16,20,1,2,0,0 +14/12/2016,Middlesbrough,Liverpool,0,3,A,0,1,A,J Moss,8,15,3,4,5,2,11,14,1,0,0,0 +14/12/2016,Stoke,Southampton,0,0,D,0,0,D,A Taylor,5,15,3,4,3,11,9,10,1,2,1,0 +14/12/2016,Sunderland,Chelsea,0,1,A,0,1,A,N Swarbrick,8,19,3,6,5,5,15,10,3,2,0,0 +14/12/2016,Tottenham,Hull,3,0,H,1,0,H,A Marriner,27,10,9,5,5,5,6,14,0,2,0,0 +14/12/2016,West Brom,Swansea,3,1,H,0,0,D,M Oliver,13,8,4,4,0,11,8,8,0,0,0,0 +14/12/2016,West Ham,Burnley,1,0,H,1,0,H,R Madley,20,8,7,2,6,1,12,15,0,2,0,0 +17/12/2016,Crystal Palace,Chelsea,0,1,A,0,1,A,J Moss,6,13,2,6,5,3,12,15,2,3,0,0 +17/12/2016,Middlesbrough,Swansea,3,0,H,2,0,H,N Swarbrick,10,14,4,2,3,8,10,11,1,0,0,0 +17/12/2016,Stoke,Leicester,2,2,D,2,0,H,C Pawson,16,14,7,8,9,9,9,12,2,6,0,1 +17/12/2016,Sunderland,Watford,1,0,H,0,0,D,R Madley,12,9,4,4,4,11,10,9,1,2,0,0 +17/12/2016,West Brom,Man United,0,2,A,0,1,A,A Taylor,10,10,1,5,2,3,9,13,3,3,0,0 +17/12/2016,West Ham,Hull,1,0,H,0,0,D,L Mason,18,16,6,5,10,6,13,9,2,4,0,0 +18/12/2016,Bournemouth,Southampton,1,3,A,1,1,D,M Clattenburg,10,14,5,8,7,4,6,12,0,2,0,0 +18/12/2016,Man City,Arsenal,2,1,H,0,1,A,M Atkinson,14,6,5,1,8,1,9,13,4,2,0,0 +18/12/2016,Tottenham,Burnley,2,1,H,1,1,D,K Friend,30,5,9,2,11,2,10,11,1,2,0,0 +19/12/2016,Everton,Liverpool,0,1,A,0,0,D,M Dean,6,11,1,4,1,6,13,9,3,1,0,0 +26/12/2016,Arsenal,West Brom,1,0,H,0,0,D,N Swarbrick,26,3,11,1,6,5,11,7,2,1,0,0 +26/12/2016,Burnley,Middlesbrough,1,0,H,0,0,D,C Pawson,6,8,4,2,7,3,17,18,6,5,0,0 +26/12/2016,Chelsea,Bournemouth,3,0,H,1,0,H,M Jones,14,7,4,3,5,5,13,14,1,1,0,0 +26/12/2016,Hull,Man City,0,3,A,0,0,D,R Madley,10,16,3,6,4,7,11,14,1,0,0,0 +26/12/2016,Leicester,Everton,0,2,A,0,0,D,S Attwell,7,8,2,3,3,4,14,7,3,0,0,0 +26/12/2016,Man United,Sunderland,3,1,H,1,0,H,M Atkinson,26,7,9,4,5,5,6,9,1,1,0,0 +26/12/2016,Swansea,West Ham,1,4,A,0,1,A,A Marriner,14,14,7,7,8,4,10,12,0,0,0,0 +26/12/2016,Watford,Crystal Palace,1,1,D,0,1,A,M Clattenburg,9,10,3,4,7,3,18,17,2,4,0,0 +27/12/2016,Liverpool,Stoke,4,1,H,2,1,H,M Oliver,20,6,6,2,8,2,9,6,0,1,0,0 +28/12/2016,Southampton,Tottenham,1,4,A,1,1,D,M Dean,9,17,2,5,2,10,7,16,1,4,1,0 +30/12/2016,Hull,Everton,2,2,D,1,1,D,J Moss,13,20,4,7,5,5,10,13,2,2,0,0 +31/12/2016,Burnley,Sunderland,4,1,H,1,0,H,G Scott,16,11,6,3,6,2,10,8,1,1,0,0 +31/12/2016,Chelsea,Stoke,4,2,H,1,0,H,R Madley,18,5,7,2,8,3,12,10,3,2,0,0 +31/12/2016,Leicester,West Ham,1,0,H,1,0,H,A Taylor,12,25,4,5,7,7,11,8,5,3,0,0 +31/12/2016,Liverpool,Man City,1,0,H,1,0,H,C Pawson,5,9,1,2,4,6,12,12,2,1,0,0 +31/12/2016,Man United,Middlesbrough,2,1,H,0,0,D,L Mason,32,9,12,2,12,2,9,14,1,1,0,0 +31/12/2016,Southampton,West Brom,1,2,A,1,1,D,M Jones,10,7,1,3,8,3,13,17,1,3,1,0 +31/12/2016,Swansea,Bournemouth,0,3,A,0,2,A,K Friend,14,11,5,6,4,3,14,9,2,0,0,0 +01/01/2017,Arsenal,Crystal Palace,2,0,H,1,0,H,A Marriner,22,7,7,4,8,4,9,7,1,1,0,0 +01/01/2017,Watford,Tottenham,1,4,A,0,3,A,M Oliver,6,19,2,6,0,3,11,6,3,0,0,0 +02/01/2017,Everton,Southampton,3,0,H,0,0,D,K Friend,17,12,6,3,10,8,5,14,0,1,0,0 +02/01/2017,Man City,Burnley,2,1,H,0,0,D,L Mason,14,11,3,3,8,8,11,11,4,3,1,0 +02/01/2017,Middlesbrough,Leicester,0,0,D,0,0,D,R Madley,9,10,1,4,2,2,10,9,3,0,0,0 +02/01/2017,Sunderland,Liverpool,2,2,D,1,1,D,A Taylor,10,21,5,15,2,5,10,10,2,3,0,0 +02/01/2017,West Brom,Hull,3,1,H,0,1,A,M Clattenburg,8,17,5,6,7,9,5,4,0,0,0,0 +02/01/2017,West Ham,Man United,0,2,A,0,0,D,M Dean,8,13,2,6,2,1,9,13,1,2,1,0 +03/01/2017,Bournemouth,Arsenal,3,3,D,2,0,H,M Oliver,12,14,6,4,4,7,10,12,3,3,1,0 +03/01/2017,Crystal Palace,Swansea,1,2,A,0,1,A,P Tierney,7,11,3,4,4,5,13,14,2,1,0,0 +03/01/2017,Stoke,Watford,2,0,H,1,0,H,N Swarbrick,17,9,5,2,7,6,10,15,1,2,0,0 +04/01/2017,Tottenham,Chelsea,2,0,H,1,0,H,M Atkinson,9,11,2,2,1,6,9,8,3,2,0,0 +14/01/2017,Burnley,Southampton,1,0,H,0,0,D,P Tierney,9,20,3,5,2,7,14,14,1,2,0,0 +14/01/2017,Hull,Bournemouth,3,1,H,1,1,D,M Atkinson,14,8,3,6,4,5,13,11,0,1,0,0 +14/01/2017,Leicester,Chelsea,0,3,A,0,1,A,A Marriner,7,8,2,3,3,5,8,9,1,0,0,0 +14/01/2017,Sunderland,Stoke,1,3,A,1,3,A,M Dean,14,14,3,6,5,2,16,11,3,1,0,0 +14/01/2017,Swansea,Arsenal,0,4,A,0,1,A,M Jones,12,14,3,6,3,2,12,8,2,0,0,0 +14/01/2017,Tottenham,West Brom,4,0,H,2,0,H,A Taylor,21,3,11,0,8,0,11,12,0,2,0,0 +14/01/2017,Watford,Middlesbrough,0,0,D,0,0,D,J Moss,12,5,5,1,3,1,15,15,3,1,0,0 +14/01/2017,West Ham,Crystal Palace,3,0,H,0,0,D,N Swarbrick,14,10,3,1,2,2,10,14,4,2,0,0 +15/01/2017,Everton,Man City,4,0,H,1,0,H,M Clattenburg,6,13,4,5,3,6,17,7,2,2,0,0 +15/01/2017,Man United,Liverpool,1,1,D,0,1,A,M Oliver,9,13,3,4,5,7,17,13,1,3,0,0 +21/01/2017,Bournemouth,Watford,2,2,D,0,1,A,L Mason,15,10,5,3,9,4,6,16,1,1,0,0 +21/01/2017,Crystal Palace,Everton,0,1,A,0,0,D,A Taylor,8,16,2,8,8,7,10,15,0,1,0,0 +21/01/2017,Liverpool,Swansea,2,3,A,0,0,D,K Friend,16,6,5,3,6,3,7,7,1,1,0,0 +21/01/2017,Man City,Tottenham,2,2,D,0,0,D,A Marriner,17,6,7,2,5,4,10,7,2,4,0,0 +21/01/2017,Middlesbrough,West Ham,1,3,A,1,2,A,M Atkinson,11,13,2,5,7,5,7,14,2,1,0,0 +21/01/2017,Stoke,Man United,1,1,D,1,0,H,M Clattenburg,6,25,1,8,1,7,12,13,4,0,0,0 +21/01/2017,West Brom,Sunderland,2,0,H,2,0,H,C Pawson,14,9,5,3,1,4,15,12,1,1,0,0 +22/01/2017,Arsenal,Burnley,2,1,H,0,0,D,J Moss,24,13,8,7,10,4,12,11,1,3,1,0 +22/01/2017,Chelsea,Hull,2,0,H,1,0,H,N Swarbrick,9,9,5,4,9,7,12,10,1,3,0,0 +22/01/2017,Southampton,Leicester,3,0,H,2,0,H,M Oliver,20,11,7,2,7,2,8,10,0,1,0,0 +31/01/2017,Arsenal,Watford,1,2,A,0,2,A,A Marriner,20,10,5,6,9,5,12,15,3,3,0,0 +31/01/2017,Bournemouth,Crystal Palace,0,2,A,0,0,D,J Moss,10,11,2,4,2,3,14,23,0,4,0,0 +31/01/2017,Burnley,Leicester,1,0,H,0,0,D,M Dean,24,13,3,5,10,2,4,8,1,0,0,0 +31/01/2017,Liverpool,Chelsea,1,1,D,0,1,A,M Clattenburg,7,8,3,2,3,3,13,8,2,1,0,0 +31/01/2017,Middlesbrough,West Brom,1,1,D,1,1,D,S Attwell,11,16,3,5,4,2,12,13,2,1,0,0 +31/01/2017,Sunderland,Tottenham,0,0,D,0,0,D,L Mason,3,14,1,3,2,12,9,13,2,1,0,0 +31/01/2017,Swansea,Southampton,2,1,H,1,0,H,R East,11,10,5,2,5,4,10,19,2,0,0,0 +01/02/2017,Man United,Hull,0,0,D,0,0,D,M Jones,15,6,6,2,7,1,20,16,1,4,0,0 +01/02/2017,Stoke,Everton,1,1,D,1,1,D,C Pawson,14,14,7,5,5,5,21,12,1,0,0,0 +01/02/2017,West Ham,Man City,0,4,A,0,3,A,K Friend,6,11,1,4,2,4,11,14,3,3,0,0 +04/02/2017,Chelsea,Arsenal,3,1,H,1,0,H,M Atkinson,13,9,6,5,10,13,14,8,1,1,0,0 +04/02/2017,Crystal Palace,Sunderland,0,4,A,0,4,A,A Marriner,21,10,9,7,6,1,8,8,3,3,0,0 +04/02/2017,Everton,Bournemouth,6,3,H,3,0,H,M Jones,15,18,10,8,4,8,14,4,3,1,0,0 +04/02/2017,Hull,Liverpool,2,0,H,1,0,H,L Mason,7,22,4,5,1,15,9,12,2,1,0,0 +04/02/2017,Southampton,West Ham,1,3,A,1,2,A,G Scott,21,6,7,4,3,2,12,15,0,2,0,0 +04/02/2017,Tottenham,Middlesbrough,1,0,H,0,0,D,M Clattenburg,17,8,5,0,11,2,8,11,0,1,0,0 +04/02/2017,Watford,Burnley,2,1,H,2,0,H,M Oliver,16,11,7,7,3,6,12,10,5,0,0,1 +04/02/2017,West Brom,Stoke,1,0,H,1,0,H,K Friend,16,12,4,2,5,6,6,13,2,2,0,0 +05/02/2017,Leicester,Man United,0,3,A,0,2,A,A Taylor,11,15,1,9,10,3,12,13,2,4,0,0 +05/02/2017,Man City,Swansea,2,1,H,1,0,H,M Dean,17,4,5,2,10,3,9,13,2,3,0,0 +11/02/2017,Arsenal,Hull,2,0,H,1,0,H,M Clattenburg,17,9,7,4,3,8,12,9,3,1,0,1 +11/02/2017,Liverpool,Tottenham,2,0,H,2,0,H,A Taylor,17,7,9,2,10,3,14,14,3,4,0,0 +11/02/2017,Man United,Watford,2,0,H,1,0,H,R Madley,22,9,11,3,5,1,10,12,1,0,0,0 +11/02/2017,Middlesbrough,Everton,0,0,D,0,0,D,M Dean,9,7,3,3,5,2,14,9,2,0,0,0 +11/02/2017,Stoke,Crystal Palace,1,0,H,0,0,D,M Atkinson,7,6,4,1,5,5,10,14,1,3,0,0 +11/02/2017,Sunderland,Southampton,0,4,A,0,2,A,P Tierney,6,15,1,8,6,2,12,14,1,0,0,0 +11/02/2017,West Ham,West Brom,2,2,D,0,1,A,M Oliver,16,8,4,3,7,5,7,14,3,2,0,0 +12/02/2017,Burnley,Chelsea,1,1,D,1,1,D,K Friend,6,13,4,2,1,1,12,11,3,2,0,0 +12/02/2017,Swansea,Leicester,2,0,H,2,0,H,J Moss,10,9,4,1,7,3,7,14,2,2,0,0 +13/02/2017,Bournemouth,Man City,0,2,A,0,1,A,N Swarbrick,5,17,1,5,3,7,8,11,2,3,0,0 +25/02/2017,Chelsea,Swansea,3,1,H,1,1,D,N Swarbrick,16,3,5,2,8,1,11,14,1,3,0,0 +25/02/2017,Crystal Palace,Middlesbrough,1,0,H,1,0,H,R Madley,15,11,3,4,4,2,13,15,3,1,0,0 +25/02/2017,Everton,Sunderland,2,0,H,1,0,H,S Attwell,20,10,8,1,10,6,10,16,0,2,0,0 +25/02/2017,Hull,Burnley,1,1,D,0,0,D,M Atkinson,15,10,1,3,6,3,8,13,1,1,0,1 +25/02/2017,Watford,West Ham,1,1,D,1,0,H,C Pawson,9,13,1,2,4,6,18,14,4,1,0,1 +25/02/2017,West Brom,Bournemouth,2,1,H,2,1,H,M Clattenburg,13,12,4,4,8,6,12,5,2,0,0,0 +26/02/2017,Tottenham,Stoke,4,0,H,4,0,H,J Moss,21,5,9,3,8,5,11,14,2,3,0,0 +27/02/2017,Leicester,Liverpool,3,1,H,2,0,H,M Oliver,13,17,7,7,5,12,8,5,0,0,0,0 +04/03/2017,Leicester,Hull,3,1,H,1,1,D,M Dean,17,10,5,4,10,4,12,13,1,1,0,0 +04/03/2017,Liverpool,Arsenal,3,1,H,2,0,H,R Madley,18,7,7,3,9,3,8,15,1,2,0,0 +04/03/2017,Man United,Bournemouth,1,1,D,1,1,D,K Friend,20,3,7,1,15,2,9,11,3,2,0,1 +04/03/2017,Stoke,Middlesbrough,2,0,H,2,0,H,C Pawson,13,8,8,2,6,1,16,20,1,2,0,0 +04/03/2017,Swansea,Burnley,3,2,H,1,1,D,A Taylor,23,6,10,4,9,2,7,13,2,2,0,0 +04/03/2017,Watford,Southampton,3,4,A,1,2,A,J Moss,16,20,4,11,6,5,16,9,3,2,0,0 +04/03/2017,West Brom,Crystal Palace,0,2,A,0,0,D,M Jones,11,10,1,4,5,5,8,12,0,1,0,0 +05/03/2017,Sunderland,Man City,0,2,A,0,1,A,M Atkinson,11,14,3,7,6,4,11,4,1,0,0,0 +05/03/2017,Tottenham,Everton,3,2,H,1,0,H,M Oliver,20,8,7,4,7,2,9,14,1,2,0,0 +06/03/2017,West Ham,Chelsea,1,2,A,0,1,A,A Marriner,11,9,2,4,6,4,8,8,0,1,0,0 +08/03/2017,Man City,Stoke,0,0,D,0,0,D,N Swarbrick,12,5,1,2,8,1,8,11,1,3,0,0 +11/03/2017,Bournemouth,West Ham,3,2,H,1,1,D,R Madley,21,15,9,7,5,6,20,15,4,2,0,0 +11/03/2017,Everton,West Brom,3,0,H,2,0,H,G Scott,16,5,5,2,4,6,8,12,1,2,0,0 +11/03/2017,Hull,Swansea,2,1,H,0,0,D,A Marriner,15,11,7,3,2,7,14,5,3,1,0,0 +12/03/2017,Liverpool,Burnley,2,1,H,1,1,D,C Pawson,10,11,3,1,11,1,12,16,2,2,0,0 +18/03/2017,Bournemouth,Swansea,2,0,H,1,0,H,M Dean,12,10,6,1,2,4,10,9,0,2,0,0 +18/03/2017,Crystal Palace,Watford,1,0,H,0,0,D,M Atkinson,7,6,0,2,3,2,8,15,0,2,0,0 +18/03/2017,Everton,Hull,4,0,H,1,0,H,P Tierney,14,6,8,0,5,8,9,7,2,0,0,1 +18/03/2017,Stoke,Chelsea,1,2,A,1,1,D,A Taylor,5,20,1,7,5,11,16,11,4,2,1,0 +18/03/2017,Sunderland,Burnley,0,0,D,0,0,D,R Madley,14,17,5,3,3,5,14,7,2,1,0,0 +18/03/2017,West Brom,Arsenal,3,1,H,1,1,D,N Swarbrick,12,11,8,2,4,5,5,6,1,0,0,0 +18/03/2017,West Ham,Leicester,2,3,A,1,3,A,R East,20,11,7,5,6,5,10,14,0,1,0,0 +19/03/2017,Man City,Liverpool,1,1,D,0,0,D,M Oliver,13,13,3,4,9,8,14,7,3,3,0,0 +19/03/2017,Middlesbrough,Man United,1,3,A,0,1,A,J Moss,10,14,3,6,8,3,9,8,1,1,0,0 +19/03/2017,Tottenham,Southampton,2,1,H,2,0,H,A Marriner,12,8,6,3,5,1,12,13,3,3,0,0 +01/04/2017,Burnley,Tottenham,0,2,A,0,0,D,S Attwell,13,19,2,7,3,7,9,6,2,0,0,0 +01/04/2017,Chelsea,Crystal Palace,1,2,A,1,2,A,C Pawson,24,8,11,3,9,3,12,14,3,2,0,0 +01/04/2017,Hull,West Ham,2,1,H,0,1,A,M Jones,7,12,3,4,5,5,16,9,2,2,0,0 +01/04/2017,Leicester,Stoke,2,0,H,1,0,H,L Mason,23,10,10,2,10,4,7,11,1,4,0,0 +01/04/2017,Liverpool,Everton,3,1,H,2,1,H,A Taylor,10,9,6,4,2,3,17,10,1,3,0,0 +01/04/2017,Man United,West Brom,0,0,D,0,0,D,M Dean,18,3,3,1,6,1,15,13,3,3,0,0 +01/04/2017,Southampton,Bournemouth,0,0,D,0,0,D,J Moss,14,12,4,3,6,3,14,8,1,0,0,0 +01/04/2017,Watford,Sunderland,1,0,H,0,0,D,L Probert,23,11,10,3,13,9,12,8,1,3,0,0 +02/04/2017,Arsenal,Man City,2,2,D,1,2,A,A Marriner,8,14,3,5,3,8,8,15,3,2,0,0 +02/04/2017,Swansea,Middlesbrough,0,0,D,0,0,D,R Madley,15,8,3,1,11,3,9,16,2,3,0,0 +04/04/2017,Burnley,Stoke,1,0,H,0,0,D,K Friend,7,15,3,2,3,5,9,16,3,2,0,0 +04/04/2017,Leicester,Sunderland,2,0,H,0,0,D,G Scott,19,15,8,3,5,6,12,13,0,1,0,0 +04/04/2017,Man United,Everton,1,1,D,0,1,A,N Swarbrick,18,11,3,4,6,5,10,18,1,4,0,1 +04/04/2017,Watford,West Brom,2,0,H,1,0,H,P Tierney,9,18,2,2,5,6,11,14,1,4,1,0 +05/04/2017,Arsenal,West Ham,3,0,H,0,0,D,M Atkinson,21,7,8,2,5,0,11,5,2,2,0,0 +05/04/2017,Chelsea,Man City,2,1,H,2,1,H,M Dean,10,17,4,7,2,9,10,14,1,3,0,0 +05/04/2017,Hull,Middlesbrough,4,2,H,3,2,H,M Oliver,17,12,5,4,8,4,13,12,2,1,0,0 +05/04/2017,Liverpool,Bournemouth,2,2,D,1,1,D,L Mason,20,7,8,2,7,2,11,8,1,1,0,0 +05/04/2017,Southampton,Crystal Palace,3,1,H,1,1,D,R East,25,17,9,3,11,11,16,11,2,1,0,0 +05/04/2017,Swansea,Tottenham,1,3,A,1,0,H,J Moss,4,18,1,8,5,7,7,8,1,1,0,0 +08/04/2017,Bournemouth,Chelsea,1,3,A,1,2,A,A Marriner,12,15,1,5,4,4,7,11,2,3,0,0 +08/04/2017,Man City,Hull,3,1,H,1,0,H,J Moss,23,6,7,1,7,3,6,8,0,2,0,0 +08/04/2017,Middlesbrough,Burnley,0,0,D,0,0,D,M Atkinson,12,7,5,2,4,3,9,12,0,3,0,0 +08/04/2017,Stoke,Liverpool,1,2,A,1,0,H,M Dean,9,14,4,4,5,8,7,11,0,2,0,0 +08/04/2017,Tottenham,Watford,4,0,H,3,0,H,A Taylor,19,8,6,2,7,5,12,12,0,1,0,0 +08/04/2017,West Brom,Southampton,0,1,A,0,1,A,C Kavanagh,17,10,6,3,7,3,12,12,2,2,0,0 +08/04/2017,West Ham,Swansea,1,0,H,1,0,H,K Friend,14,4,6,1,4,3,12,12,5,1,0,0 +09/04/2017,Everton,Leicester,4,2,H,3,2,H,R Madley,12,6,7,4,4,6,8,15,2,2,0,0 +09/04/2017,Sunderland,Man United,0,3,A,0,1,A,C Pawson,9,18,4,9,4,5,14,20,1,5,1,0 +10/04/2017,Crystal Palace,Arsenal,3,0,H,1,0,H,M Oliver,17,11,6,3,5,8,11,10,0,1,0,0 +15/04/2017,Crystal Palace,Leicester,2,2,D,0,1,A,M Dean,15,6,4,2,8,5,10,16,0,2,0,0 +15/04/2017,Everton,Burnley,3,1,H,0,0,D,M Clattenburg,29,13,7,6,6,7,15,14,2,0,0,0 +15/04/2017,Southampton,Man City,0,3,A,0,0,D,N Swarbrick,8,18,1,5,3,8,9,8,3,0,0,0 +15/04/2017,Stoke,Hull,3,1,H,1,0,H,S Attwell,14,14,4,3,3,7,12,5,0,2,0,0 +15/04/2017,Sunderland,West Ham,2,2,D,1,1,D,A Marriner,14,7,4,3,8,1,14,10,1,0,0,1 +15/04/2017,Tottenham,Bournemouth,4,0,H,2,0,H,M Oliver,24,5,14,1,13,1,8,10,0,1,0,0 +15/04/2017,Watford,Swansea,1,0,H,1,0,H,L Mason,15,13,7,4,6,2,8,13,0,0,0,0 +16/04/2017,Man United,Chelsea,2,0,H,1,0,H,R Madley,9,5,3,0,1,3,16,20,3,3,0,0 +16/04/2017,West Brom,Liverpool,0,1,A,0,1,A,J Moss,7,15,2,2,4,4,15,9,3,1,0,0 +17/04/2017,Middlesbrough,Arsenal,1,2,A,0,1,A,A Taylor,13,12,5,4,2,4,11,16,2,2,0,0 +22/04/2017,Bournemouth,Middlesbrough,4,0,H,2,0,H,G Scott,20,5,10,2,4,3,4,14,0,2,0,1 +22/04/2017,Hull,Watford,2,0,H,0,0,D,R Madley,8,13,3,2,4,7,10,11,3,1,1,0 +22/04/2017,Swansea,Stoke,2,0,H,1,0,H,M Oliver,10,9,6,3,11,4,5,14,2,2,0,0 +22/04/2017,West Ham,Everton,0,0,D,0,0,D,R East,15,4,3,0,8,4,9,15,2,3,0,0 +23/04/2017,Burnley,Man United,0,2,A,0,2,A,A Taylor,8,12,0,6,3,3,17,16,2,1,0,0 +23/04/2017,Liverpool,Crystal Palace,1,2,A,1,1,D,A Marriner,14,7,1,3,4,3,8,10,1,2,0,0 +25/04/2017,Chelsea,Southampton,4,2,H,2,1,H,L Mason,17,12,7,4,6,7,13,9,2,2,0,0 +26/04/2017,Arsenal,Leicester,1,0,H,0,0,D,M Jones,12,7,5,3,12,1,9,13,2,4,0,0 +26/04/2017,Crystal Palace,Tottenham,0,1,A,0,0,D,J Moss,6,18,1,4,2,11,13,15,3,3,0,0 +26/04/2017,Middlesbrough,Sunderland,1,0,H,1,0,H,M Dean,6,10,2,4,3,5,15,11,3,3,0,0 +27/04/2017,Man City,Man United,0,0,D,0,0,D,M Atkinson,19,3,6,1,7,4,10,8,1,0,0,1 +29/04/2017,Crystal Palace,Burnley,0,2,A,0,1,A,R Madley,15,9,5,3,14,4,11,12,2,0,0,0 +29/04/2017,Southampton,Hull,0,0,D,0,0,D,M Dean,11,9,2,1,6,6,9,13,0,3,0,0 +29/04/2017,Stoke,West Ham,0,0,D,0,0,D,L Probert,19,12,4,4,5,3,12,3,1,0,0,0 +29/04/2017,Sunderland,Bournemouth,0,1,A,0,0,D,S Attwell,17,15,5,7,6,5,13,9,3,2,0,0 +29/04/2017,West Brom,Leicester,0,1,A,0,1,A,M Clattenburg,16,6,4,1,5,6,14,9,2,2,0,0 +30/04/2017,Everton,Chelsea,0,3,A,0,0,D,J Moss,12,11,4,5,2,4,13,11,3,4,0,0 +30/04/2017,Man United,Swansea,1,1,D,1,0,H,N Swarbrick,12,12,6,4,5,2,11,13,2,2,0,0 +30/04/2017,Middlesbrough,Man City,2,2,D,1,0,H,K Friend,14,22,6,4,4,11,9,9,4,4,0,0 +30/04/2017,Tottenham,Arsenal,2,0,H,0,0,D,M Oliver,20,12,11,4,14,5,7,15,1,3,0,0 +01/05/2017,Watford,Liverpool,0,1,A,0,1,A,C Pawson,9,12,2,8,3,5,11,9,3,1,0,0 +05/05/2017,West Ham,Tottenham,1,0,H,0,0,D,A Taylor,13,11,5,5,3,7,8,9,4,2,0,0 +06/05/2017,Bournemouth,Stoke,2,2,D,0,1,A,P Tierney,12,14,2,3,0,7,10,15,2,0,0,0 +06/05/2017,Burnley,West Brom,2,2,D,0,0,D,M Jones,14,11,4,3,5,8,9,14,1,3,0,0 +06/05/2017,Hull,Sunderland,0,2,A,0,0,D,N Swarbrick,16,12,6,5,7,4,10,8,3,1,0,0 +06/05/2017,Leicester,Watford,3,0,H,1,0,H,R East,11,14,5,7,10,1,8,10,0,0,0,0 +06/05/2017,Man City,Crystal Palace,5,0,H,1,0,H,M Oliver,26,5,12,2,9,3,12,8,1,3,0,0 +06/05/2017,Swansea,Everton,1,0,H,1,0,H,M Atkinson,9,8,4,2,9,5,5,13,0,0,0,0 +07/05/2017,Arsenal,Man United,2,0,H,0,0,D,A Marriner,9,10,4,4,5,9,14,10,1,0,0,0 +07/05/2017,Liverpool,Southampton,0,0,D,0,0,D,R Madley,17,4,8,0,3,6,9,4,1,3,0,0 +08/05/2017,Chelsea,Middlesbrough,3,0,H,2,0,H,C Pawson,21,2,7,1,8,1,8,21,0,2,0,0 +10/05/2017,Southampton,Arsenal,0,2,A,0,0,D,J Moss,14,11,3,4,8,5,5,5,1,2,0,0 +12/05/2017,Everton,Watford,1,0,H,0,0,D,K Friend,15,14,6,2,9,8,7,11,0,3,0,0 +12/05/2017,West Brom,Chelsea,0,1,A,0,0,D,M Oliver,7,24,2,5,5,8,13,8,3,0,0,0 +13/05/2017,Bournemouth,Burnley,2,1,H,1,0,H,L Probert,18,14,6,3,7,1,6,11,0,1,0,0 +13/05/2017,Man City,Leicester,2,1,H,2,1,H,R Madley,17,9,5,4,9,3,12,16,3,3,0,0 +13/05/2017,Middlesbrough,Southampton,1,2,A,0,1,A,A Taylor,14,14,3,4,6,3,19,11,3,0,0,0 +13/05/2017,Stoke,Arsenal,1,4,A,0,1,A,M Dean,10,10,4,6,7,6,13,7,1,2,0,0 +13/05/2017,Sunderland,Swansea,0,2,A,0,2,A,A Marriner,13,3,4,3,8,4,13,6,4,0,0,0 +14/05/2017,Crystal Palace,Hull,4,0,H,2,0,H,M Atkinson,12,9,4,0,3,7,13,14,3,5,0,0 +14/05/2017,Tottenham,Man United,2,1,H,1,0,H,J Moss,14,8,7,2,8,3,14,15,1,2,0,0 +14/05/2017,West Ham,Liverpool,0,4,A,0,1,A,N Swarbrick,10,26,3,11,4,6,5,10,2,0,0,0 +15/05/2017,Chelsea,Watford,4,3,H,2,1,H,L Mason,24,9,9,3,8,3,7,12,2,4,0,1 +16/05/2017,Arsenal,Sunderland,2,0,H,0,0,D,R East,36,6,13,2,17,3,15,10,4,2,0,0 +16/05/2017,Man City,West Brom,3,1,H,2,0,H,C Pawson,21,5,10,2,7,1,8,11,1,2,0,0 +17/05/2017,Southampton,Man United,0,0,D,0,0,D,M Dean,17,11,6,1,7,3,7,10,2,1,0,0 +18/05/2017,Leicester,Tottenham,1,6,A,0,2,A,M Oliver,12,26,5,12,4,4,11,8,3,1,0,0 +21/05/2017,Arsenal,Everton,3,1,H,2,0,H,M Oliver,17,22,9,7,4,6,10,15,2,4,1,0 +21/05/2017,Burnley,West Ham,1,2,A,1,1,D,R Madley,9,14,1,4,2,5,11,14,2,2,0,0 +21/05/2017,Chelsea,Sunderland,5,1,H,1,1,D,N Swarbrick,28,7,8,3,11,1,8,15,1,1,0,0 +21/05/2017,Hull,Tottenham,1,7,A,0,3,A,A Marriner,10,19,4,14,2,3,14,5,0,0,0,0 +21/05/2017,Leicester,Bournemouth,1,1,D,0,1,A,l Mason,21,10,5,4,9,1,11,10,4,1,0,0 +21/05/2017,Liverpool,Middlesbrough,3,0,H,1,0,H,M Atkinson,25,9,10,3,3,3,13,8,0,1,0,0 +21/05/2017,Man United,Crystal Palace,2,0,H,2,0,H,A Taylor,9,6,2,1,3,6,16,10,2,0,0,0 +21/05/2017,Southampton,Stoke,0,1,A,0,0,D,L Probert,14,14,6,3,4,10,12,10,2,4,0,0 +21/05/2017,Swansea,West Brom,2,1,H,0,1,A,M Dean,12,16,2,5,7,4,8,10,1,1,0,0 +21/05/2017,Watford,Man City,0,5,A,0,4,A,J Moss,6,15,3,9,4,5,12,10,1,0,0,0 +11/08/2017,Arsenal,Leicester,4,3,H,2,2,D,M Dean,27,6,10,3,9,4,9,12,0,1,0,0 +12/08/2017,Brighton,Man City,0,2,A,0,0,D,M Oliver,6,14,2,4,3,10,6,9,0,2,0,0 +12/08/2017,Chelsea,Burnley,2,3,A,0,3,A,C Pawson,19,10,6,5,8,5,16,11,3,3,2,0 +12/08/2017,Crystal Palace,Huddersfield,0,3,A,0,2,A,J Moss,14,8,4,6,12,9,7,19,1,3,0,0 +12/08/2017,Everton,Stoke,1,0,H,1,0,H,N Swarbrick,9,9,4,1,6,7,13,10,1,1,0,0 +12/08/2017,Southampton,Swansea,0,0,D,0,0,D,M Jones,29,4,2,0,13,0,10,13,2,1,0,0 +12/08/2017,Watford,Liverpool,3,3,D,2,1,H,A Taylor,9,14,4,5,3,3,14,8,0,3,0,0 +12/08/2017,West Brom,Bournemouth,1,0,H,1,0,H,R Madley,16,9,6,2,8,2,15,3,3,1,0,0 +13/08/2017,Man United,West Ham,4,0,H,1,0,H,M Atkinson,22,9,6,1,11,1,19,7,2,2,0,0 +13/08/2017,Newcastle,Tottenham,0,2,A,0,0,D,A Marriner,6,18,3,6,5,7,6,10,1,2,1,0 +19/08/2017,Bournemouth,Watford,0,2,A,0,0,D,R East,6,19,2,7,8,5,6,14,1,3,0,0 +19/08/2017,Burnley,West Brom,0,1,A,0,0,D,M Atkinson,20,8,0,1,5,5,11,11,1,0,0,1 +19/08/2017,Leicester,Brighton,2,0,H,1,0,H,L Probert,14,5,4,2,6,2,8,10,1,0,0,0 +19/08/2017,Liverpool,Crystal Palace,1,0,H,0,0,D,K Friend,23,4,13,1,4,2,12,13,1,3,0,0 +19/08/2017,Southampton,West Ham,3,2,H,2,1,H,L Mason,14,16,5,8,7,2,18,10,1,1,0,1 +19/08/2017,Stoke,Arsenal,1,0,H,0,0,D,A Marriner,11,18,4,6,2,9,6,11,0,0,0,0 +19/08/2017,Swansea,Man United,0,4,A,0,1,A,J Moss,6,17,1,8,3,5,12,11,1,1,0,0 +20/08/2017,Huddersfield,Newcastle,1,0,H,0,0,D,C Pawson,7,13,3,5,7,3,13,10,3,4,0,0 +20/08/2017,Tottenham,Chelsea,1,2,A,0,1,A,A Taylor,18,9,6,2,14,3,14,21,3,3,0,0 +21/08/2017,Man City,Everton,1,1,D,0,1,A,R Madley,19,7,6,2,7,1,7,9,1,2,1,1 +26/08/2017,Bournemouth,Man City,1,2,A,1,1,D,M Dean,9,19,3,8,2,5,13,14,5,4,0,1 +26/08/2017,Crystal Palace,Swansea,0,2,A,0,1,A,A Marriner,16,7,3,3,1,1,9,6,4,1,0,0 +26/08/2017,Huddersfield,Southampton,0,0,D,0,0,D,S Attwell,16,6,6,3,5,4,10,10,0,1,0,0 +26/08/2017,Man United,Leicester,2,0,H,0,0,D,M Oliver,22,11,7,4,9,3,8,7,1,2,0,0 +26/08/2017,Newcastle,West Ham,3,0,H,1,0,H,N Swarbrick,16,8,8,3,7,5,17,11,1,3,0,0 +26/08/2017,Watford,Brighton,0,0,D,0,0,D,G Scott,8,16,0,2,3,11,7,18,0,1,1,0 +27/08/2017,Chelsea,Everton,2,0,H,2,0,H,J Moss,18,7,7,0,6,3,12,7,2,2,0,0 +27/08/2017,Liverpool,Arsenal,4,0,H,2,0,H,C Pawson,18,8,10,0,4,3,6,9,2,4,0,0 +27/08/2017,Tottenham,Burnley,1,1,D,0,0,D,L Mason,28,13,5,3,10,7,9,9,0,0,0,0 +27/08/2017,West Brom,Stoke,1,1,D,0,0,D,A Taylor,10,16,2,2,10,4,10,9,2,2,0,0 +09/09/2017,Arsenal,Bournemouth,3,0,H,2,0,H,A Taylor,17,7,9,2,10,3,14,10,0,1,0,0 +09/09/2017,Brighton,West Brom,3,1,H,1,0,H,C Kavanagh,12,12,6,3,2,6,9,4,2,0,0,0 +09/09/2017,Everton,Tottenham,0,3,A,0,2,A,G Scott,12,15,1,6,7,3,14,14,3,1,0,0 +09/09/2017,Leicester,Chelsea,1,2,A,0,1,A,L Mason,8,16,2,6,2,9,11,8,1,0,0,0 +09/09/2017,Man City,Liverpool,5,0,H,2,0,H,J Moss,13,10,7,3,8,3,10,9,2,2,0,1 +09/09/2017,Southampton,Watford,0,2,A,0,1,A,L Probert,9,12,1,3,7,4,8,11,0,0,0,0 +09/09/2017,Stoke,Man United,2,2,D,1,1,D,N Swarbrick,10,18,5,8,5,11,10,10,0,0,0,0 +10/09/2017,Burnley,Crystal Palace,1,0,H,1,0,H,M Oliver,4,23,2,5,3,13,7,14,1,1,0,0 +10/09/2017,Swansea,Newcastle,0,1,A,0,0,D,M Jones,10,16,4,8,7,5,11,15,2,3,0,0 +11/09/2017,West Ham,Huddersfield,2,0,H,0,0,D,K Friend,19,10,3,2,10,3,11,14,1,2,0,0 +15/09/2017,Bournemouth,Brighton,2,1,H,0,0,D,C Pawson,15,9,3,2,6,4,5,12,0,0,0,0 +16/09/2017,Crystal Palace,Southampton,0,1,A,0,1,A,R Madley,14,13,3,4,5,5,14,13,5,1,0,0 +16/09/2017,Huddersfield,Leicester,1,1,D,0,0,D,J Moss,15,10,3,1,8,4,6,6,1,1,0,0 +16/09/2017,Liverpool,Burnley,1,1,D,1,1,D,R East,35,5,9,4,12,2,7,9,1,2,0,0 +16/09/2017,Newcastle,Stoke,2,1,H,1,0,H,S Attwell,14,13,3,4,7,5,13,11,3,1,0,0 +16/09/2017,Tottenham,Swansea,0,0,D,0,0,D,M Dean,26,4,8,0,11,1,11,9,1,3,0,0 +16/09/2017,Watford,Man City,0,6,A,0,3,A,A Taylor,7,28,1,10,3,11,8,6,2,0,0,0 +16/09/2017,West Brom,West Ham,0,0,D,0,0,D,P Tierney,6,9,1,1,2,3,15,7,2,2,0,0 +17/09/2017,Chelsea,Arsenal,0,0,D,0,0,D,M Oliver,13,11,4,2,5,1,11,15,1,3,1,0 +17/09/2017,Man United,Everton,4,0,H,1,0,H,A Marriner,16,7,7,3,4,1,15,14,1,1,0,0 +23/09/2017,Burnley,Huddersfield,0,0,D,0,0,D,C Kavanagh,5,9,3,2,3,4,9,13,3,2,0,0 +23/09/2017,Everton,Bournemouth,2,1,H,0,0,D,M Atkinson,12,8,4,4,4,6,12,11,3,1,0,0 +23/09/2017,Leicester,Liverpool,2,3,A,1,2,A,A Taylor,12,23,7,6,3,6,13,14,3,3,0,0 +23/09/2017,Man City,Crystal Palace,5,0,H,1,0,H,N Swarbrick,25,5,9,0,8,2,6,7,3,1,0,0 +23/09/2017,Southampton,Man United,0,1,A,0,1,A,C Pawson,14,9,4,4,8,4,12,9,1,4,0,0 +23/09/2017,Stoke,Chelsea,0,4,A,0,2,A,M Dean,13,7,2,4,5,0,5,14,2,2,0,0 +23/09/2017,Swansea,Watford,1,2,A,0,1,A,L Mason,10,8,3,4,5,4,9,16,1,3,0,0 +23/09/2017,West Ham,Tottenham,2,3,A,0,2,A,M Oliver,10,15,4,5,4,3,10,20,4,2,0,1 +24/09/2017,Brighton,Newcastle,1,0,H,0,0,D,A Marriner,7,17,3,5,2,8,6,8,2,1,0,0 +25/09/2017,Arsenal,West Brom,2,0,H,1,0,H,R Madley,16,7,6,3,7,4,8,17,1,4,0,0 +30/09/2017,Bournemouth,Leicester,0,0,D,0,0,D,G Scott,19,8,4,1,7,5,6,10,1,0,0,0 +30/09/2017,Chelsea,Man City,0,1,A,0,0,D,M Atkinson,4,17,2,6,4,8,8,13,0,2,0,0 +30/09/2017,Huddersfield,Tottenham,0,4,A,0,3,A,N Swarbrick,6,14,1,7,4,7,7,4,1,1,0,0 +30/09/2017,Man United,Crystal Palace,4,0,H,2,0,H,M Dean,18,6,7,1,6,5,6,9,0,0,0,0 +30/09/2017,Stoke,Southampton,2,1,H,1,0,H,M Jones,17,21,6,3,5,6,11,10,1,2,0,0 +30/09/2017,West Brom,Watford,2,2,D,2,1,H,M Oliver,9,15,4,3,7,6,12,11,3,0,0,0 +30/09/2017,West Ham,Swansea,1,0,H,0,0,D,R East,9,6,4,1,2,1,15,13,3,2,0,0 +01/10/2017,Arsenal,Brighton,2,0,H,1,0,H,K Friend,25,9,12,1,6,5,7,8,0,2,0,0 +01/10/2017,Everton,Burnley,0,1,A,0,1,A,J Moss,23,5,4,2,9,3,14,7,2,2,0,0 +01/10/2017,Newcastle,Liverpool,1,1,D,1,1,D,C Pawson,8,17,5,2,1,5,11,6,1,1,0,0 +14/10/2017,Burnley,West Ham,1,1,D,0,1,A,S Attwell,20,8,5,6,7,4,11,14,2,2,0,1 +14/10/2017,Crystal Palace,Chelsea,2,1,H,2,1,H,A Marriner,14,15,5,5,5,8,7,6,2,1,0,0 +14/10/2017,Liverpool,Man United,0,0,D,0,0,D,M Atkinson,19,6,5,1,7,3,7,13,0,2,0,0 +14/10/2017,Man City,Stoke,7,2,H,3,1,H,C Pawson,20,5,11,1,5,0,5,8,0,1,0,0 +14/10/2017,Swansea,Huddersfield,2,0,H,1,0,H,P Tierney,7,7,3,2,5,3,12,16,1,5,0,0 +14/10/2017,Tottenham,Bournemouth,1,0,H,0,0,D,R Madley,18,5,6,1,9,5,9,7,1,3,0,0 +14/10/2017,Watford,Arsenal,2,1,H,0,1,A,N Swarbrick,11,9,2,6,2,5,8,10,1,0,0,0 +15/10/2017,Brighton,Everton,1,1,D,0,0,D,M Oliver,12,13,3,6,7,1,12,10,4,1,0,0 +15/10/2017,Southampton,Newcastle,2,2,D,0,1,A,K Friend,12,19,2,5,3,8,11,12,1,2,0,0 +16/10/2017,Leicester,West Brom,1,1,D,0,0,D,M Dean,11,5,3,1,4,1,13,6,2,2,0,0 +20/10/2017,West Ham,Brighton,0,3,A,0,2,A,M Atkinson,16,7,2,5,10,3,5,13,1,0,0,0 +21/10/2017,Chelsea,Watford,4,2,H,1,1,D,J Moss,14,16,8,5,6,6,13,16,2,3,0,0 +21/10/2017,Huddersfield,Man United,2,1,H,2,0,H,L Mason,5,9,3,3,4,7,10,12,4,2,0,0 +21/10/2017,Man City,Burnley,3,0,H,1,0,H,R East,15,6,10,0,4,5,10,6,2,3,0,0 +21/10/2017,Newcastle,Crystal Palace,1,0,H,0,0,D,S Attwell,7,10,3,0,3,5,12,14,4,2,0,0 +21/10/2017,Southampton,West Brom,1,0,H,0,0,D,G Scott,20,7,6,2,8,3,10,8,0,1,0,0 +21/10/2017,Stoke,Bournemouth,1,2,A,0,2,A,L Probert,10,7,2,3,8,3,17,10,0,1,0,0 +21/10/2017,Swansea,Leicester,1,2,A,0,1,A,M Oliver,19,11,4,6,7,6,6,9,0,1,0,0 +22/10/2017,Everton,Arsenal,2,5,A,1,1,D,C Pawson,8,30,3,14,0,7,14,14,1,1,1,0 +22/10/2017,Tottenham,Liverpool,4,1,H,3,1,H,A Marriner,14,12,6,7,3,5,2,8,0,1,0,0 +28/10/2017,Arsenal,Swansea,2,1,H,0,1,A,L Mason,17,4,5,2,5,2,9,9,0,0,0,0 +28/10/2017,Bournemouth,Chelsea,0,1,A,0,0,D,C Pawson,7,18,1,5,5,5,5,5,2,0,0,0 +28/10/2017,Crystal Palace,West Ham,2,2,D,0,2,A,R Madley,19,6,9,2,11,2,18,15,1,2,0,0 +28/10/2017,Liverpool,Huddersfield,3,0,H,0,0,D,K Friend,16,1,8,0,9,2,10,8,0,1,0,0 +28/10/2017,Man United,Tottenham,1,0,H,0,0,D,J Moss,11,13,3,4,6,3,12,14,1,0,0,0 +28/10/2017,Watford,Stoke,0,1,A,0,1,A,M Oliver,13,6,0,2,8,1,10,9,4,4,0,0 +28/10/2017,West Brom,Man City,2,3,A,1,2,A,M Jones,6,15,4,5,1,2,10,7,2,3,0,0 +29/10/2017,Brighton,Southampton,1,1,D,0,1,A,N Swarbrick,7,6,2,1,2,7,9,10,3,1,0,0 +29/10/2017,Leicester,Everton,2,0,H,2,0,H,A Marriner,9,16,3,2,3,10,6,10,0,2,0,0 +30/10/2017,Burnley,Newcastle,1,0,H,0,0,D,M Dean,12,12,5,5,5,3,9,10,3,0,0,0 +04/11/2017,Huddersfield,West Brom,1,0,H,1,0,H,R East,7,9,3,3,3,5,8,15,1,4,1,0 +04/11/2017,Newcastle,Bournemouth,0,1,A,0,0,D,P Tierney,16,16,4,6,11,9,11,12,2,2,0,0 +04/11/2017,Southampton,Burnley,0,1,A,0,0,D,L Probert,13,5,3,1,9,1,4,4,0,0,0,0 +04/11/2017,Stoke,Leicester,2,2,D,1,1,D,R Madley,11,14,7,4,4,10,12,8,0,0,0,0 +04/11/2017,Swansea,Brighton,0,1,A,0,1,A,M Dean,12,5,2,2,8,7,13,12,2,1,0,0 +04/11/2017,West Ham,Liverpool,1,4,A,0,2,A,N Swarbrick,6,15,1,7,2,3,9,13,3,0,0,0 +05/11/2017,Chelsea,Man United,1,0,H,0,0,D,A Taylor,18,10,8,2,4,7,16,20,1,3,0,0 +05/11/2017,Everton,Watford,3,2,H,0,0,D,G Scott,10,11,5,3,2,5,14,12,0,2,0,0 +05/11/2017,Man City,Arsenal,3,1,H,1,0,H,M Oliver,9,6,5,3,5,5,15,15,1,6,0,0 +05/11/2017,Tottenham,Crystal Palace,1,0,H,0,0,D,K Friend,12,11,2,3,10,9,6,7,0,2,0,0 +18/11/2017,Arsenal,Tottenham,2,0,H,2,0,H,M Dean,14,14,5,4,7,4,11,16,4,1,0,0 +18/11/2017,Bournemouth,Huddersfield,4,0,H,2,0,H,L Probert,11,20,5,3,8,11,13,10,3,2,1,0 +18/11/2017,Burnley,Swansea,2,0,H,2,0,H,M Atkinson,16,13,7,1,8,6,12,12,1,1,0,0 +18/11/2017,Crystal Palace,Everton,2,2,D,2,2,D,A Taylor,16,8,7,5,9,4,9,26,2,3,0,0 +18/11/2017,Leicester,Man City,0,2,A,0,1,A,G Scott,2,12,0,4,1,7,8,10,1,1,0,0 +18/11/2017,Liverpool,Southampton,3,0,H,2,0,H,M Jones,21,5,8,0,10,2,15,9,1,1,0,0 +18/11/2017,Man United,Newcastle,4,1,H,2,1,H,C Pawson,16,12,7,5,11,4,10,9,1,1,0,0 +18/11/2017,West Brom,Chelsea,0,4,A,0,3,A,J Moss,7,11,2,7,3,4,13,12,2,3,0,0 +19/11/2017,Watford,West Ham,2,0,H,1,0,H,A Marriner,17,10,7,6,3,1,13,10,1,4,0,0 +20/11/2017,Brighton,Stoke,2,2,D,1,2,A,L Mason,9,13,3,6,6,7,13,5,1,0,0,0 +24/11/2017,West Ham,Leicester,1,1,D,1,1,D,M Atkinson,8,7,4,2,5,5,9,12,1,1,0,0 +25/11/2017,Crystal Palace,Stoke,2,1,H,0,0,D,M Dean,10,9,5,3,5,2,9,5,1,1,0,0 +25/11/2017,Liverpool,Chelsea,1,1,D,0,0,D,M Oliver,16,11,4,3,7,8,10,7,0,0,0,0 +25/11/2017,Man United,Brighton,1,0,H,0,0,D,N Swarbrick,15,8,4,2,5,0,9,9,0,2,0,0 +25/11/2017,Newcastle,Watford,0,3,A,0,2,A,C Kavanagh,12,12,2,4,6,3,8,13,1,2,0,0 +25/11/2017,Swansea,Bournemouth,0,0,D,0,0,D,S Attwell,10,9,1,5,4,5,18,6,4,2,0,0 +25/11/2017,Tottenham,West Brom,1,1,D,0,1,A,M Jones,24,5,5,2,8,0,10,13,1,4,0,0 +26/11/2017,Burnley,Arsenal,0,1,A,0,0,D,L Mason,8,17,2,2,5,5,8,14,2,0,0,0 +26/11/2017,Huddersfield,Man City,1,2,A,1,0,H,C Pawson,4,14,0,5,3,7,10,10,2,3,1,0 +26/11/2017,Southampton,Everton,4,1,H,1,1,D,K Friend,17,5,5,2,7,2,6,13,0,0,0,0 +28/11/2017,Brighton,Crystal Palace,0,0,D,0,0,D,A Marriner,10,10,4,4,10,5,13,8,2,0,0,0 +28/11/2017,Leicester,Tottenham,2,1,H,2,0,H,A Taylor,7,18,4,6,4,9,7,10,1,2,0,0 +28/11/2017,Watford,Man United,2,4,A,0,3,A,J Moss,12,15,3,7,5,5,12,10,2,1,0,0 +28/11/2017,West Brom,Newcastle,2,2,D,1,0,H,L Probert,8,14,4,4,1,6,13,12,0,0,0,0 +29/11/2017,Arsenal,Huddersfield,5,0,H,1,0,H,G Scott,21,7,7,2,7,2,10,12,0,1,0,0 +29/11/2017,Bournemouth,Burnley,1,2,A,0,1,A,R East,10,10,2,4,4,6,9,11,0,1,0,0 +29/11/2017,Chelsea,Swansea,1,0,H,0,0,D,N Swarbrick,21,2,10,0,15,1,10,7,1,0,0,0 +29/11/2017,Everton,West Ham,4,0,H,2,0,H,M Oliver,8,7,5,3,2,6,16,6,2,1,0,0 +29/11/2017,Man City,Southampton,2,1,H,0,0,D,P Tierney,26,7,12,2,6,5,12,11,1,1,0,0 +29/11/2017,Stoke,Liverpool,0,3,A,0,1,A,M Atkinson,10,14,1,7,5,9,8,10,1,5,0,0 +02/12/2017,Arsenal,Man United,1,3,A,0,2,A,A Marriner,33,8,15,4,12,1,11,10,3,2,0,1 +02/12/2017,Brighton,Liverpool,1,5,A,0,2,A,G Scott,6,12,2,6,2,2,8,7,1,0,0,0 +02/12/2017,Chelsea,Newcastle,3,1,H,2,1,H,K Friend,23,8,9,2,11,4,8,13,0,1,0,0 +02/12/2017,Everton,Huddersfield,2,0,H,0,0,D,C Kavanagh,6,5,4,3,2,5,12,9,2,1,0,0 +02/12/2017,Leicester,Burnley,1,0,H,1,0,H,P Tierney,16,14,3,4,10,7,3,11,0,2,0,0 +02/12/2017,Stoke,Swansea,2,1,H,2,1,H,C Pawson,10,10,3,3,2,6,12,10,2,0,0,0 +02/12/2017,Watford,Tottenham,1,1,D,1,1,D,M Atkinson,6,8,2,2,7,2,16,8,4,1,0,1 +02/12/2017,West Brom,Crystal Palace,0,0,D,0,0,D,M Oliver,20,10,6,2,3,7,11,10,3,1,0,0 +03/12/2017,Bournemouth,Southampton,1,1,D,1,0,H,J Moss,16,15,5,4,5,4,6,12,2,1,0,0 +03/12/2017,Man City,West Ham,2,1,H,0,1,A,M Dean,24,7,8,4,10,4,5,7,2,2,0,0 +09/12/2017,Burnley,Watford,1,0,H,1,0,H,L Probert,8,11,5,2,3,5,10,12,1,0,0,1 +09/12/2017,Crystal Palace,Bournemouth,2,2,D,2,2,D,K Friend,24,11,8,5,6,3,14,14,5,2,0,0 +09/12/2017,Huddersfield,Brighton,2,0,H,2,0,H,S Attwell,19,7,6,2,8,1,6,10,0,0,0,0 +09/12/2017,Newcastle,Leicester,2,3,A,1,1,D,N Swarbrick,10,13,4,5,6,5,10,9,2,1,0,0 +09/12/2017,Swansea,West Brom,1,0,H,0,0,D,M Dean,18,7,3,1,6,3,8,18,2,5,0,0 +09/12/2017,Tottenham,Stoke,5,1,H,1,0,H,R East,20,4,11,3,12,6,12,9,1,0,0,0 +09/12/2017,West Ham,Chelsea,1,0,H,1,0,H,A Taylor,5,19,2,2,4,3,10,10,6,1,0,0 +10/12/2017,Liverpool,Everton,1,1,D,1,0,H,C Pawson,23,3,3,2,12,1,8,11,1,3,0,0 +10/12/2017,Man United,Man City,1,2,A,1,1,D,M Oliver,8,14,5,7,2,8,16,10,4,2,0,0 +10/12/2017,Southampton,Arsenal,1,1,D,1,0,H,R Madley,6,11,3,6,4,5,12,10,2,2,0,0 +12/12/2017,Burnley,Stoke,1,0,H,0,0,D,M Jones,10,14,2,7,6,4,6,14,0,0,0,0 +12/12/2017,Crystal Palace,Watford,2,1,H,0,1,A,L Mason,14,7,4,2,4,6,14,18,2,1,0,1 +12/12/2017,Huddersfield,Chelsea,1,3,A,0,2,A,A Marriner,7,12,2,5,1,3,11,4,0,0,0,0 +13/12/2017,Liverpool,West Brom,0,0,D,0,0,D,P Tierney,14,6,5,1,5,7,16,6,1,0,0,0 +13/12/2017,Man United,Bournemouth,1,0,H,1,0,H,G Scott,9,14,2,7,4,9,14,12,1,2,0,0 +13/12/2017,Newcastle,Everton,0,1,A,0,1,A,M Atkinson,16,7,4,4,4,1,10,11,1,2,1,0 +13/12/2017,Southampton,Leicester,1,4,A,0,3,A,C Kavanagh,12,16,4,11,9,9,13,9,1,0,0,0 +13/12/2017,Swansea,Man City,0,4,A,0,2,A,A Taylor,7,23,3,10,2,5,12,6,0,0,0,0 +13/12/2017,Tottenham,Brighton,2,0,H,1,0,H,R Madley,25,7,8,4,10,2,5,10,1,1,0,0 +13/12/2017,West Ham,Arsenal,0,0,D,0,0,D,J Moss,6,22,0,3,1,7,9,9,1,0,0,0 +16/12/2017,Arsenal,Newcastle,1,0,H,1,0,H,S Attwell,23,10,5,2,7,5,13,9,2,1,0,0 +16/12/2017,Brighton,Burnley,0,0,D,0,0,D,C Kavanagh,12,11,2,6,8,2,13,13,4,2,0,0 +16/12/2017,Chelsea,Southampton,1,0,H,1,0,H,R East,24,6,8,2,8,4,8,16,1,3,0,0 +16/12/2017,Leicester,Crystal Palace,0,3,A,0,2,A,M Atkinson,11,13,2,6,6,4,12,13,2,2,1,0 +16/12/2017,Man City,Tottenham,4,1,H,1,0,H,C Pawson,20,7,11,2,5,1,13,20,2,4,0,0 +16/12/2017,Stoke,West Ham,0,3,A,0,1,A,G Scott,17,14,0,7,7,9,12,13,2,1,0,0 +16/12/2017,Watford,Huddersfield,1,4,A,0,2,A,M Oliver,13,15,3,9,8,7,12,17,2,1,1,1 +17/12/2017,Bournemouth,Liverpool,0,4,A,0,3,A,A Marriner,6,21,2,7,2,3,5,10,1,1,0,0 +17/12/2017,West Brom,Man United,1,2,A,0,2,A,A Taylor,12,8,5,3,6,3,14,7,1,3,0,0 +18/12/2017,Everton,Swansea,3,1,H,1,1,D,J Moss,12,8,7,3,6,3,12,12,2,3,0,0 +22/12/2017,Arsenal,Liverpool,3,3,D,0,1,A,M Atkinson,11,14,4,9,7,8,9,11,1,0,0,0 +23/12/2017,Brighton,Watford,1,0,H,0,0,D,P Tierney,14,13,3,1,6,4,8,11,0,0,0,0 +23/12/2017,Burnley,Tottenham,0,3,A,0,1,A,M Oliver,5,18,2,8,4,4,10,10,1,2,0,0 +23/12/2017,Everton,Chelsea,0,0,D,0,0,D,R Madley,5,25,0,8,2,7,8,4,3,0,0,0 +23/12/2017,Leicester,Man United,2,2,D,1,1,D,J Moss,11,19,3,6,4,6,19,8,2,2,1,0 +23/12/2017,Man City,Bournemouth,4,0,H,1,0,H,M Jones,14,5,5,1,4,6,8,7,0,1,0,0 +23/12/2017,Southampton,Huddersfield,1,1,D,1,0,H,L Probert,12,10,6,3,9,1,16,9,2,1,0,0 +23/12/2017,Stoke,West Brom,3,1,H,2,0,H,N Swarbrick,9,17,5,7,1,11,9,11,1,0,0,0 +23/12/2017,Swansea,Crystal Palace,1,1,D,0,0,D,C Pawson,7,13,2,5,6,3,13,11,2,3,0,0 +23/12/2017,West Ham,Newcastle,2,3,A,1,1,D,L Mason,19,11,6,4,6,3,9,12,1,1,0,0 +26/12/2017,Bournemouth,West Ham,3,3,D,1,1,D,S Attwell,26,10,10,5,7,4,12,14,3,4,0,0 +26/12/2017,Chelsea,Brighton,2,0,H,0,0,D,M Dean,25,8,8,1,13,1,8,8,0,1,0,0 +26/12/2017,Huddersfield,Stoke,1,1,D,1,0,H,A Taylor,18,15,8,7,8,5,9,12,2,4,0,0 +26/12/2017,Liverpool,Swansea,5,0,H,1,0,H,K Friend,22,7,9,4,4,2,5,8,0,1,0,0 +26/12/2017,Man United,Burnley,2,2,D,0,2,A,M Atkinson,23,3,6,2,12,2,10,14,3,7,0,0 +26/12/2017,Tottenham,Southampton,5,2,H,2,0,H,G Scott,16,16,7,6,3,8,8,15,0,2,0,0 +26/12/2017,Watford,Leicester,2,1,H,1,1,D,C Kavanagh,11,11,1,5,8,8,7,13,3,3,0,0 +26/12/2017,West Brom,Everton,0,0,D,0,0,D,R East,17,7,3,3,10,2,14,13,2,0,0,0 +27/12/2017,Newcastle,Man City,0,1,A,0,1,A,A Marriner,6,22,2,6,3,8,11,6,1,0,0,0 +28/12/2017,Crystal Palace,Arsenal,2,3,A,0,1,A,M Oliver,16,16,2,9,5,4,12,7,0,1,0,0 +30/12/2017,Bournemouth,Everton,2,1,H,1,0,H,L Probert,19,6,7,1,4,4,8,13,0,0,0,0 +30/12/2017,Chelsea,Stoke,5,0,H,3,0,H,K Friend,21,1,12,1,7,0,5,10,1,1,0,0 +30/12/2017,Huddersfield,Burnley,0,0,D,0,0,D,P Tierney,3,11,1,4,5,4,14,14,2,1,0,0 +30/12/2017,Liverpool,Leicester,2,1,H,0,1,A,N Swarbrick,17,7,6,1,7,1,7,3,3,2,0,0 +30/12/2017,Man United,Southampton,0,0,D,0,0,D,C Pawson,15,8,3,3,10,7,12,13,2,4,0,0 +30/12/2017,Newcastle,Brighton,0,0,D,0,0,D,A Taylor,11,10,2,3,3,6,11,10,2,2,0,0 +30/12/2017,Watford,Swansea,1,2,A,1,0,H,M Atkinson,9,9,4,4,1,8,14,13,4,2,0,0 +31/12/2017,Crystal Palace,Man City,0,0,D,0,0,D,J Moss,10,15,1,4,7,8,14,11,5,4,0,0 +31/12/2017,West Brom,Arsenal,1,1,D,0,0,D,M Dean,14,14,3,4,5,6,14,9,3,3,0,0 +01/01/2018,Brighton,Bournemouth,2,2,D,1,1,D,M Oliver,15,26,6,8,5,12,13,9,2,1,0,0 +01/01/2018,Burnley,Liverpool,1,2,A,0,0,D,R East,13,19,4,5,3,9,4,12,0,0,0,0 +01/01/2018,Everton,Man United,0,2,A,0,0,D,A Marriner,12,21,0,6,6,6,11,10,2,0,0,0 +01/01/2018,Leicester,Huddersfield,3,0,H,0,0,D,G Scott,11,6,5,1,4,1,7,9,1,1,0,0 +01/01/2018,Stoke,Newcastle,0,1,A,0,0,D,C Kavanagh,11,14,4,6,10,5,10,11,3,1,0,0 +02/01/2018,Man City,Watford,3,1,H,2,0,H,L Mason,17,7,7,4,9,1,8,12,1,1,0,0 +02/01/2018,Southampton,Crystal Palace,1,2,A,1,0,H,S Attwell,10,11,3,4,4,7,8,11,0,1,0,0 +02/01/2018,Swansea,Tottenham,0,2,A,0,1,A,R Madley,8,12,1,5,6,5,12,9,2,2,0,0 +02/01/2018,West Ham,West Brom,2,1,H,0,1,A,M Jones,13,10,5,3,3,4,12,12,3,2,0,0 +03/01/2018,Arsenal,Chelsea,2,2,D,0,0,D,A Taylor,14,19,6,6,10,8,11,11,3,2,0,0 +04/01/2018,Tottenham,West Ham,1,1,D,0,0,D,M Dean,31,3,8,1,7,1,10,6,0,2,0,0 +13/01/2018,Chelsea,Leicester,0,0,D,0,0,D,M Jones,17,14,7,1,6,3,14,13,2,2,0,1 +13/01/2018,Crystal Palace,Burnley,1,0,H,1,0,H,M Oliver,17,8,2,2,3,2,16,10,0,3,0,0 +13/01/2018,Huddersfield,West Ham,1,4,A,1,1,D,J Moss,8,8,2,6,7,3,7,9,1,1,0,0 +13/01/2018,Newcastle,Swansea,1,1,D,0,0,D,G Scott,12,8,4,5,4,5,11,10,0,3,0,0 +13/01/2018,Tottenham,Everton,4,0,H,1,0,H,C Pawson,20,7,10,0,3,3,11,15,0,2,0,0 +13/01/2018,Watford,Southampton,2,2,D,0,2,A,R East,16,10,4,4,5,4,11,13,0,5,0,0 +13/01/2018,West Brom,Brighton,2,0,H,1,0,H,M Atkinson,15,13,4,1,9,6,8,15,0,0,0,0 +14/01/2018,Bournemouth,Arsenal,2,1,H,0,0,D,K Friend,13,12,5,3,4,5,8,10,2,2,0,0 +14/01/2018,Liverpool,Man City,4,3,H,1,1,D,A Marriner,16,11,7,4,5,6,10,7,2,3,0,0 +15/01/2018,Man United,Stoke,3,0,H,2,0,H,A Taylor,20,11,9,5,6,1,14,10,2,1,0,0 +20/01/2018,Arsenal,Crystal Palace,4,1,H,4,0,H,C Kavanagh,16,9,10,5,5,10,6,8,0,0,0,0 +20/01/2018,Brighton,Chelsea,0,4,A,0,2,A,J Moss,10,14,3,10,5,3,12,5,3,0,0,0 +20/01/2018,Burnley,Man United,0,1,A,0,0,D,M Dean,13,12,2,2,5,4,7,12,3,3,0,0 +20/01/2018,Everton,West Brom,1,1,D,0,1,A,S Attwell,9,15,4,5,0,3,15,10,2,1,0,0 +20/01/2018,Leicester,Watford,2,0,H,1,0,H,L Probert,9,11,5,3,4,5,11,10,0,0,0,0 +20/01/2018,Man City,Newcastle,3,1,H,1,0,H,P Tierney,21,6,9,4,18,0,5,10,0,1,0,0 +20/01/2018,Stoke,Huddersfield,2,0,H,0,0,D,M Oliver,11,6,6,3,3,2,18,13,1,1,0,0 +20/01/2018,West Ham,Bournemouth,1,1,D,0,0,D,M Atkinson,12,8,4,3,6,3,8,7,3,1,0,0 +21/01/2018,Southampton,Tottenham,1,1,D,1,1,D,K Friend,10,17,4,2,2,9,8,15,2,3,0,0 +22/01/2018,Swansea,Liverpool,1,0,H,1,0,H,N Swarbrick,3,21,2,4,3,9,5,9,0,2,0,0 +30/01/2018,Huddersfield,Liverpool,0,3,A,0,2,A,K Friend,5,14,1,7,1,4,8,8,0,0,0,0 +30/01/2018,Swansea,Arsenal,3,1,H,1,1,D,L Mason,12,9,4,4,4,3,4,8,0,3,0,0 +30/01/2018,West Ham,Crystal Palace,1,1,D,1,1,D,N Swarbrick,8,9,3,3,5,3,11,15,1,3,0,0 +31/01/2018,Chelsea,Bournemouth,0,3,A,0,0,D,L Probert,21,11,5,5,8,5,5,12,1,0,0,0 +31/01/2018,Everton,Leicester,2,1,H,2,0,H,C Kavanagh,9,8,4,1,5,5,9,10,0,2,0,0 +31/01/2018,Man City,West Brom,3,0,H,1,0,H,R Madley,19,3,10,1,9,1,10,10,1,3,0,0 +31/01/2018,Newcastle,Burnley,1,1,D,0,0,D,S Hooper,14,11,4,2,7,4,15,15,1,2,0,0 +31/01/2018,Southampton,Brighton,1,1,D,0,1,A,M Dean,18,9,3,2,7,2,8,10,3,1,0,0 +31/01/2018,Stoke,Watford,0,0,D,0,0,D,J Moss,11,12,4,2,2,6,12,12,4,3,0,0 +31/01/2018,Tottenham,Man United,2,0,H,2,0,H,A Marriner,22,6,6,3,6,3,10,13,2,2,0,0 +03/02/2018,Arsenal,Everton,5,1,H,4,0,H,N Swarbrick,15,11,7,2,4,6,8,8,2,0,0,0 +03/02/2018,Bournemouth,Stoke,2,1,H,0,1,A,P Tierney,15,13,8,4,8,1,14,19,2,3,0,0 +03/02/2018,Brighton,West Ham,3,1,H,1,1,D,R East,21,4,5,1,4,1,11,7,3,1,0,0 +03/02/2018,Burnley,Man City,1,1,D,0,1,A,M Atkinson,8,20,3,7,3,13,9,6,4,1,0,0 +03/02/2018,Leicester,Swansea,1,1,D,1,0,H,A Taylor,13,3,5,1,3,3,9,8,1,0,0,0 +03/02/2018,Man United,Huddersfield,2,0,H,0,0,D,S Attwell,16,4,7,0,5,2,5,19,2,4,0,0 +03/02/2018,West Brom,Southampton,2,3,A,1,2,A,M Oliver,9,13,3,5,5,7,12,10,2,1,0,0 +04/02/2018,Crystal Palace,Newcastle,1,1,D,0,1,A,A Marriner,21,11,6,8,5,6,11,8,1,1,0,0 +04/02/2018,Liverpool,Tottenham,2,2,D,1,0,H,J Moss,9,13,3,6,3,7,15,9,3,1,0,0 +05/02/2018,Watford,Chelsea,4,1,H,1,0,H,M Dean,21,7,8,3,6,2,11,8,2,2,0,1 +10/02/2018,Everton,Crystal Palace,3,1,H,0,0,D,J Moss,11,16,5,4,8,7,14,15,1,1,0,0 +10/02/2018,Man City,Leicester,5,1,H,1,1,D,M Jones,19,2,11,2,8,2,10,12,2,3,0,0 +10/02/2018,Stoke,Brighton,1,1,D,0,1,A,R Madley,15,14,3,6,7,4,6,10,0,0,0,0 +10/02/2018,Swansea,Burnley,1,0,H,0,0,D,A Marriner,12,13,4,1,5,2,10,9,2,1,0,0 +10/02/2018,Tottenham,Arsenal,1,0,H,0,0,D,A Taylor,18,6,6,1,10,2,13,9,2,1,0,0 +10/02/2018,West Ham,Watford,2,0,H,1,0,H,G Scott,8,11,4,4,8,7,11,7,1,1,0,0 +11/02/2018,Huddersfield,Bournemouth,4,1,H,2,1,H,M Oliver,15,7,4,2,2,6,12,12,0,3,0,0 +11/02/2018,Newcastle,Man United,1,0,H,0,0,D,C Pawson,10,13,3,6,0,10,12,15,0,2,0,0 +11/02/2018,Southampton,Liverpool,0,2,A,0,2,A,M Atkinson,6,16,4,4,3,1,5,8,1,2,0,0 +12/02/2018,Chelsea,West Brom,3,0,H,1,0,H,L Mason,20,8,8,1,8,7,11,11,0,2,0,0 +24/02/2018,Bournemouth,Newcastle,2,2,D,0,2,A,R East,18,15,5,4,8,1,12,10,1,4,0,0 +24/02/2018,Brighton,Swansea,4,1,H,1,0,H,M Dean,16,11,5,1,3,5,17,6,1,0,0,0 +24/02/2018,Burnley,Southampton,1,1,D,0,0,D,R Madley,9,13,3,6,7,5,8,14,1,0,0,0 +24/02/2018,Leicester,Stoke,1,1,D,0,1,A,M Oliver,19,6,9,2,13,1,10,16,1,3,0,0 +24/02/2018,Liverpool,West Ham,4,1,H,1,0,H,S Attwell,21,7,12,4,8,2,7,7,0,2,0,0 +24/02/2018,Watford,Everton,1,0,H,0,0,D,A Taylor,13,7,5,2,6,5,10,9,3,1,0,0 +24/02/2018,West Brom,Huddersfield,1,2,A,0,0,D,J Moss,10,16,3,7,6,3,11,9,1,1,0,0 +25/02/2018,Crystal Palace,Tottenham,0,1,A,0,0,D,K Friend,5,14,3,4,2,13,12,5,0,1,0,0 +25/02/2018,Man United,Chelsea,2,1,H,1,1,D,M Atkinson,10,14,5,7,3,4,13,12,2,2,0,0 +01/03/2018,Arsenal,Man City,0,3,A,0,3,A,A Marriner,10,9,5,5,6,1,11,11,1,1,0,0 +03/03/2018,Burnley,Everton,2,1,H,0,1,A,C Kavanagh,21,10,8,4,10,4,9,12,1,1,0,1 +03/03/2018,Leicester,Bournemouth,1,1,D,0,1,A,L Probert,23,8,4,3,13,1,11,7,2,1,0,0 +03/03/2018,Liverpool,Newcastle,2,0,H,1,0,H,G Scott,14,7,3,2,8,3,7,9,0,0,0,0 +03/03/2018,Southampton,Stoke,0,0,D,0,0,D,A Taylor,18,12,6,3,15,4,8,13,0,1,0,0 +03/03/2018,Swansea,West Ham,4,1,H,2,0,H,M Atkinson,9,7,8,2,4,5,9,14,0,3,0,0 +03/03/2018,Tottenham,Huddersfield,2,0,H,1,0,H,K Friend,15,3,7,3,8,1,6,8,1,1,0,0 +03/03/2018,Watford,West Brom,1,0,H,0,0,D,P Tierney,15,9,4,2,4,6,11,12,2,0,0,0 +04/03/2018,Brighton,Arsenal,2,1,H,2,1,H,S Attwell,13,15,6,7,6,11,13,12,3,3,0,0 +04/03/2018,Man City,Chelsea,1,0,H,0,0,D,M Oliver,13,3,3,0,5,2,8,9,2,1,0,0 +05/03/2018,Crystal Palace,Man United,2,3,A,1,0,H,N Swarbrick,10,17,4,8,3,7,10,8,1,3,0,0 +10/03/2018,Chelsea,Crystal Palace,2,1,H,2,0,H,A Taylor,27,9,11,2,12,3,8,9,1,2,0,0 +10/03/2018,Everton,Brighton,2,0,H,0,0,D,R East,13,10,3,4,10,3,10,14,0,1,0,1 +10/03/2018,Huddersfield,Swansea,0,0,D,0,0,D,M Oliver,30,0,4,0,12,0,9,9,2,3,0,1 +10/03/2018,Man United,Liverpool,2,1,H,2,0,H,C Pawson,5,14,2,2,1,13,10,16,2,1,0,0 +10/03/2018,Newcastle,Southampton,3,0,H,2,0,H,A Marriner,7,6,5,2,2,6,10,10,0,2,0,0 +10/03/2018,West Brom,Leicester,1,4,A,1,1,D,R Madley,6,14,2,6,7,6,6,7,1,3,0,0 +10/03/2018,West Ham,Burnley,0,3,A,0,0,D,L Mason,13,7,4,4,5,1,13,7,2,2,0,0 +11/03/2018,Arsenal,Watford,3,0,H,1,0,H,M Atkinson,11,11,7,4,4,9,12,9,2,1,0,0 +11/03/2018,Bournemouth,Tottenham,1,4,A,1,1,D,M Dean,14,12,3,8,4,5,10,7,1,1,0,0 +12/03/2018,Stoke,Man City,0,2,A,0,1,A,J Moss,3,17,0,6,3,10,6,8,1,0,0,0 +17/03/2018,Bournemouth,West Brom,2,1,H,0,0,D,G Scott,17,11,5,6,3,2,6,9,1,2,0,0 +17/03/2018,Huddersfield,Crystal Palace,0,2,A,0,1,A,M Dean,6,17,2,6,4,7,11,12,3,2,0,0 +17/03/2018,Liverpool,Watford,5,0,H,2,0,H,A Taylor,13,4,10,1,6,1,5,4,1,0,0,0 +17/03/2018,Stoke,Everton,1,2,A,0,0,D,M Atkinson,9,17,4,9,1,9,13,12,1,1,1,0 +31/03/2018,Brighton,Leicester,0,2,A,0,0,D,C Kavanagh,15,6,3,4,6,1,11,13,2,4,0,1 +31/03/2018,Crystal Palace,Liverpool,1,2,A,1,0,H,N Swarbrick,6,16,3,6,4,6,6,8,2,2,0,0 +31/03/2018,Everton,Man City,1,3,A,0,3,A,P Tierney,6,18,2,4,2,11,10,8,0,0,0,0 +31/03/2018,Man United,Swansea,2,0,H,2,0,H,R Madley,11,3,5,2,3,3,5,13,0,0,0,0 +31/03/2018,Newcastle,Huddersfield,1,0,H,0,0,D,M Atkinson,18,4,3,0,3,3,9,10,1,4,0,0 +31/03/2018,Watford,Bournemouth,2,2,D,1,1,D,A Marriner,16,13,5,6,7,4,16,6,4,0,0,0 +31/03/2018,West Brom,Burnley,1,2,A,0,1,A,L Probert,8,11,3,4,1,6,12,9,1,3,0,0 +31/03/2018,West Ham,Southampton,3,0,H,3,0,H,J Moss,13,9,5,0,5,9,9,16,0,3,0,0 +01/04/2018,Arsenal,Stoke,3,0,H,0,0,D,C Pawson,24,8,11,2,6,5,9,13,1,2,0,0 +01/04/2018,Chelsea,Tottenham,1,3,A,1,1,D,A Marriner,14,12,4,7,5,5,4,11,0,1,0,0 +07/04/2018,Bournemouth,Crystal Palace,2,2,D,0,0,D,J Moss,20,13,5,6,11,3,8,9,2,2,0,0 +07/04/2018,Brighton,Huddersfield,1,1,D,1,1,D,A Taylor,13,14,4,5,5,3,13,7,1,0,1,0 +07/04/2018,Everton,Liverpool,0,0,D,0,0,D,M Oliver,6,10,1,3,3,2,12,7,0,0,0,0 +07/04/2018,Leicester,Newcastle,1,2,A,0,1,A,S Attwell,8,8,1,4,7,5,7,14,1,3,0,0 +07/04/2018,Man City,Man United,2,3,A,2,0,H,M Atkinson,20,5,6,4,8,4,17,9,6,3,0,0 +07/04/2018,Stoke,Tottenham,1,2,A,0,0,D,G Scott,10,15,4,4,6,3,19,8,4,3,0,0 +07/04/2018,Watford,Burnley,1,2,A,0,0,D,P Tierney,14,7,5,4,11,1,18,12,2,2,0,0 +07/04/2018,West Brom,Swansea,1,1,D,0,0,D,R East,15,7,3,1,5,1,15,5,3,2,0,0 +08/04/2018,Arsenal,Southampton,3,2,H,2,1,H,A Marriner,13,15,7,8,8,6,11,7,2,1,1,1 +08/04/2018,Chelsea,West Ham,1,1,D,1,0,H,K Friend,23,5,6,2,10,6,7,11,0,1,0,0 +14/04/2018,Burnley,Leicester,2,1,H,2,0,H,M Atkinson,8,15,3,6,4,8,9,10,2,1,0,0 +14/04/2018,Crystal Palace,Brighton,3,2,H,3,2,H,A Marriner,15,12,8,3,5,6,12,16,3,2,0,0 +14/04/2018,Huddersfield,Watford,1,0,H,0,0,D,C Pawson,9,4,1,2,9,6,9,8,2,2,0,0 +14/04/2018,Liverpool,Bournemouth,3,0,H,1,0,H,C Kavanagh,20,6,7,1,7,5,9,9,1,1,0,0 +14/04/2018,Southampton,Chelsea,2,3,A,1,0,H,M Dean,10,17,7,5,4,7,13,14,5,3,0,0 +14/04/2018,Swansea,Everton,1,1,D,0,1,A,L Mason,17,12,7,3,6,3,10,11,1,1,0,0 +14/04/2018,Tottenham,Man City,1,3,A,1,2,A,J Moss,8,17,3,6,5,7,11,12,3,4,0,0 +15/04/2018,Man United,West Brom,0,1,A,0,0,D,P Tierney,14,10,4,4,4,4,8,13,1,1,0,0 +15/04/2018,Newcastle,Arsenal,2,1,H,1,1,D,A Taylor,8,15,4,3,2,5,11,9,1,0,0,0 +16/04/2018,West Ham,Stoke,1,1,D,0,0,D,M Oliver,18,11,6,7,10,1,15,21,2,3,0,0 +17/04/2018,Brighton,Tottenham,1,1,D,0,0,D,K Friend,8,16,5,6,3,4,6,5,1,0,0,0 +18/04/2018,Bournemouth,Man United,0,2,A,0,1,A,G Scott,13,13,2,4,8,5,9,8,3,1,0,0 +19/04/2018,Burnley,Chelsea,1,2,A,0,1,A,R Madley,5,16,2,5,3,2,9,11,1,0,0,0 +19/04/2018,Leicester,Southampton,0,0,D,0,0,D,R East,11,6,3,2,11,3,10,10,0,0,0,0 +21/04/2018,Watford,Crystal Palace,0,0,D,0,0,D,C Kavanagh,14,6,4,1,4,3,17,13,3,5,0,0 +21/04/2018,West Brom,Liverpool,2,2,D,0,1,A,S Attwell,13,9,6,3,7,4,12,5,0,1,0,0 +22/04/2018,Arsenal,West Ham,4,1,H,0,0,D,L Mason,20,11,8,4,8,6,11,9,3,2,0,0 +22/04/2018,Man City,Swansea,5,0,H,2,0,H,C Pawson,19,4,12,1,5,1,6,8,0,1,0,0 +22/04/2018,Stoke,Burnley,1,1,D,1,0,H,M Dean,8,16,3,7,3,8,15,5,2,1,0,0 +23/04/2018,Everton,Newcastle,1,0,H,0,0,D,R Madley,9,9,1,2,4,6,10,11,2,1,0,0 +28/04/2018,Burnley,Brighton,0,0,D,0,0,D,R East,10,9,4,1,3,4,5,17,0,2,0,0 +28/04/2018,Crystal Palace,Leicester,5,0,H,2,0,H,M Dean,15,6,9,1,8,2,11,9,1,1,0,1 +28/04/2018,Huddersfield,Everton,0,2,A,0,1,A,L Probert,9,7,2,5,4,3,9,8,1,0,0,0 +28/04/2018,Liverpool,Stoke,0,0,D,0,0,D,A Marriner,20,5,2,1,9,2,7,14,1,2,0,0 +28/04/2018,Newcastle,West Brom,0,1,A,0,1,A,D Coote,17,9,2,2,5,1,8,13,1,3,0,0 +28/04/2018,Southampton,Bournemouth,2,1,H,1,1,D,A Taylor,14,14,6,7,5,8,12,11,5,1,0,0 +28/04/2018,Swansea,Chelsea,0,1,A,0,1,A,J Moss,10,11,3,3,1,2,13,9,1,1,0,0 +29/04/2018,Man United,Arsenal,2,1,H,1,0,H,K Friend,17,8,3,3,9,0,12,7,0,1,0,0 +29/04/2018,West Ham,Man City,1,4,A,1,2,A,N Swarbrick,4,19,1,7,0,7,5,11,0,1,0,0 +30/04/2018,Tottenham,Watford,2,0,H,1,0,H,M Oliver,13,13,3,5,4,0,11,10,1,0,0,0 +04/05/2018,Brighton,Man United,1,0,H,0,0,D,C Pawson,11,16,4,3,5,6,5,3,2,0,0,0 +05/05/2018,Bournemouth,Swansea,1,0,H,1,0,H,K Friend,13,12,5,3,11,6,10,10,1,4,0,0 +05/05/2018,Everton,Southampton,1,1,D,0,0,D,J Moss,9,11,3,5,3,4,14,12,2,4,0,1 +05/05/2018,Leicester,West Ham,0,2,A,0,1,A,C Kavanagh,13,13,2,3,8,3,8,9,1,2,0,0 +05/05/2018,Stoke,Crystal Palace,1,2,A,1,0,H,M Atkinson,7,13,2,2,1,7,18,12,5,2,0,0 +05/05/2018,Watford,Newcastle,2,1,H,2,0,H,R East,11,9,8,1,2,5,9,11,2,1,0,0 +05/05/2018,West Brom,Tottenham,1,0,H,0,0,D,M Jones,9,18,1,5,5,9,13,12,4,1,0,0 +06/05/2018,Arsenal,Burnley,5,0,H,2,0,H,A Marriner,16,6,8,2,4,5,6,7,0,1,0,0 +06/05/2018,Chelsea,Liverpool,1,0,H,1,0,H,A Taylor,12,10,4,5,3,1,12,9,3,2,0,0 +06/05/2018,Man City,Huddersfield,0,0,D,0,0,D,M Dean,15,5,2,3,10,1,9,4,0,3,0,0 +08/05/2018,Swansea,Southampton,0,1,A,0,0,D,M Oliver,11,13,3,8,7,6,7,15,2,1,0,0 +09/05/2018,Chelsea,Huddersfield,1,1,D,0,0,D,L Mason,22,3,5,2,9,0,7,10,0,1,0,0 +09/05/2018,Leicester,Arsenal,3,1,H,1,0,H,G Scott,19,14,10,7,6,6,7,12,2,2,0,1 +09/05/2018,Man City,Brighton,3,1,H,2,1,H,P Tierney,19,6,7,3,4,3,5,7,0,1,0,0 +09/05/2018,Tottenham,Newcastle,1,0,H,0,0,D,N Swarbrick,11,14,7,3,6,4,10,8,2,3,0,0 +10/05/2018,West Ham,Man United,0,0,D,0,0,D,J Moss,9,16,2,6,1,6,12,12,1,1,0,0 +13/05/2018,Burnley,Bournemouth,1,2,A,1,0,H,P Tierney,12,16,4,5,7,8,14,9,0,0,0,0 +13/05/2018,Crystal Palace,West Brom,2,0,H,0,0,D,J Moss,11,7,5,1,4,2,10,11,2,3,0,0 +13/05/2018,Huddersfield,Arsenal,0,1,A,0,1,A,M Oliver,18,9,3,4,7,4,11,7,1,0,0,0 +13/05/2018,Liverpool,Brighton,4,0,H,2,0,H,K Friend,22,2,11,1,7,3,3,6,0,0,0,0 +13/05/2018,Man United,Watford,1,0,H,1,0,H,L Mason,7,7,1,3,6,5,6,11,4,0,0,0 +13/05/2018,Newcastle,Chelsea,3,0,H,1,0,H,M Atkinson,16,6,6,2,4,2,11,10,0,1,0,0 +13/05/2018,Southampton,Man City,0,1,A,0,0,D,A Marriner,8,13,3,2,1,12,8,10,3,1,0,0 +13/05/2018,Swansea,Stoke,1,2,A,1,2,A,A Taylor,26,8,11,5,6,0,12,9,1,2,0,0 +13/05/2018,Tottenham,Leicester,5,4,H,1,2,A,C Pawson,14,16,6,9,4,4,9,13,1,2,0,0 +13/05/2018,West Ham,Everton,3,1,H,1,0,H,G Scott,15,14,4,7,6,6,10,13,0,1,0,0 +10/08/2018,Man United,Leicester,2,1,H,1,0,H,A Marriner,8,13,6,4,2,5,11,8,2,1,0,0 +11/08/2018,Bournemouth,Cardiff,2,0,H,1,0,H,K Friend,12,10,4,1,7,4,11,9,1,1,0,0 +11/08/2018,Fulham,Crystal Palace,0,2,A,0,1,A,M Dean,15,10,6,9,5,5,9,11,1,2,0,0 +11/08/2018,Huddersfield,Chelsea,0,3,A,0,2,A,C Kavanagh,6,13,1,4,2,5,9,8,2,1,0,0 +11/08/2018,Newcastle,Tottenham,1,2,A,1,2,A,M Atkinson,15,15,2,5,3,5,11,12,2,2,0,0 +11/08/2018,Watford,Brighton,2,0,H,1,0,H,J Moss,19,6,5,0,8,2,10,16,2,2,0,0 +11/08/2018,Wolves,Everton,2,2,D,1,1,D,C Pawson,11,6,4,5,3,6,8,7,0,1,0,1 +12/08/2018,Arsenal,Man City,0,2,A,0,1,A,M Oliver,9,17,3,8,2,9,11,14,2,2,0,0 +12/08/2018,Liverpool,West Ham,4,0,H,2,0,H,A Taylor,18,5,8,2,5,4,14,9,1,2,0,0 +12/08/2018,Southampton,Burnley,0,0,D,0,0,D,G Scott,18,16,3,6,8,5,10,9,0,1,0,0 +18/08/2018,Cardiff,Newcastle,0,0,D,0,0,D,C Pawson,12,12,1,6,5,5,14,16,2,2,0,1 +18/08/2018,Chelsea,Arsenal,3,2,H,2,2,D,M Atkinson,24,15,11,6,5,1,12,9,0,2,0,0 +18/08/2018,Everton,Southampton,2,1,H,2,0,H,L Mason,13,15,7,4,2,5,8,20,0,5,0,0 +18/08/2018,Leicester,Wolves,2,0,H,2,0,H,M Dean,6,11,2,3,1,9,10,8,2,1,1,0 +18/08/2018,Tottenham,Fulham,3,1,H,1,0,H,A Taylor,25,10,11,3,5,2,9,5,0,0,0,0 +18/08/2018,West Ham,Bournemouth,1,2,A,1,0,H,S Attwell,11,12,5,5,6,4,14,10,6,2,0,0 +19/08/2018,Brighton,Man United,3,2,H,3,1,H,K Friend,6,9,3,3,3,5,16,13,1,1,0,0 +19/08/2018,Burnley,Watford,1,3,A,1,1,D,P Tierney,8,9,3,6,5,2,8,19,1,2,0,0 +19/08/2018,Man City,Huddersfield,6,1,H,3,1,H,A Marriner,32,5,14,1,10,3,9,4,0,2,0,0 +20/08/2018,Crystal Palace,Liverpool,0,2,A,0,1,A,M Oliver,8,16,2,6,6,7,6,13,1,1,1,0 +25/08/2018,Arsenal,West Ham,3,1,H,1,1,D,G Scott,17,13,10,5,10,2,16,13,1,3,0,0 +25/08/2018,Bournemouth,Everton,2,2,D,0,0,D,L Probert,17,11,5,3,6,2,12,10,0,3,1,1 +25/08/2018,Huddersfield,Cardiff,0,0,D,0,0,D,M Oliver,5,14,1,4,7,7,8,10,0,1,1,0 +25/08/2018,Liverpool,Brighton,1,0,H,1,0,H,C Kavanagh,22,6,8,2,8,5,8,14,1,1,0,0 +25/08/2018,Southampton,Leicester,1,2,A,0,0,D,J Moss,11,8,5,5,10,3,13,11,1,1,1,0 +25/08/2018,Wolves,Man City,1,1,D,0,0,D,M Atkinson,11,18,2,6,5,9,13,8,1,2,0,0 +26/08/2018,Fulham,Burnley,4,2,H,3,2,H,D Coote,25,12,12,2,6,4,11,8,2,1,0,0 +26/08/2018,Newcastle,Chelsea,1,2,A,0,0,D,P Tierney,6,15,2,3,4,5,16,8,3,1,0,0 +26/08/2018,Watford,Crystal Palace,2,1,H,0,0,D,A Taylor,13,9,5,3,6,3,14,11,4,2,0,0 +27/08/2018,Man United,Tottenham,0,3,A,0,0,D,C Pawson,23,9,5,5,5,2,11,16,2,4,0,0 +01/09/2018,Brighton,Fulham,2,2,D,0,1,A,L Probert,15,10,5,5,7,1,12,14,3,3,0,0 +01/09/2018,Chelsea,Bournemouth,2,0,H,0,0,D,L Mason,24,8,6,1,7,6,10,7,2,2,0,0 +01/09/2018,Crystal Palace,Southampton,0,2,A,0,0,D,M Atkinson,20,19,6,6,7,4,11,12,1,1,0,0 +01/09/2018,Everton,Huddersfield,1,1,D,1,1,D,S Attwell,11,9,1,6,4,3,13,14,3,3,0,0 +01/09/2018,Leicester,Liverpool,1,2,A,0,2,A,P Tierney,12,10,5,4,4,4,9,12,3,2,0,0 +01/09/2018,Man City,Newcastle,2,1,H,1,1,D,K Friend,24,3,8,2,4,0,5,13,0,0,0,0 +01/09/2018,West Ham,Wolves,0,1,A,0,0,D,C Kavanagh,13,15,3,6,4,4,10,11,2,1,0,0 +02/09/2018,Burnley,Man United,0,2,A,0,2,A,J Moss,9,21,2,9,2,5,7,13,2,3,0,1 +02/09/2018,Cardiff,Arsenal,2,3,A,1,1,D,A Taylor,14,17,3,11,3,9,12,14,3,4,0,0 +02/09/2018,Watford,Tottenham,2,1,H,0,0,D,A Marriner,7,11,3,2,3,10,9,8,2,1,0,0 +15/09/2018,Bournemouth,Leicester,4,2,H,3,0,H,C Pawson,10,14,5,8,4,6,13,15,3,2,0,1 +15/09/2018,Chelsea,Cardiff,4,1,H,2,1,H,J Moss,18,6,7,2,5,4,8,10,0,0,0,0 +15/09/2018,Huddersfield,Crystal Palace,0,1,A,0,1,A,L Mason,15,7,2,2,5,3,11,17,1,2,0,0 +15/09/2018,Man City,Fulham,3,0,H,2,0,H,S Attwell,28,9,9,3,10,4,7,7,0,0,0,0 +15/09/2018,Newcastle,Arsenal,1,2,A,0,0,D,L Probert,4,12,2,2,10,4,13,11,0,0,0,0 +15/09/2018,Tottenham,Liverpool,1,2,A,0,1,A,M Oliver,11,17,3,10,5,4,17,16,0,0,0,0 +15/09/2018,Watford,Man United,1,2,A,0,2,A,M Dean,14,9,5,6,6,8,9,11,2,1,0,1 +16/09/2018,Everton,West Ham,1,3,A,1,2,A,M Atkinson,16,9,4,4,4,2,15,12,2,5,0,0 +16/09/2018,Wolves,Burnley,1,0,H,0,0,D,A Marriner,30,7,7,2,8,2,10,9,2,4,0,0 +17/09/2018,Southampton,Brighton,2,2,D,1,0,H,A Taylor,14,12,5,4,1,4,10,13,2,3,0,0 +22/09/2018,Brighton,Tottenham,1,2,A,0,1,A,C Kavanagh,8,16,4,7,5,7,15,9,2,1,0,0 +22/09/2018,Burnley,Bournemouth,4,0,H,2,0,H,A Taylor,12,19,5,5,3,8,17,6,2,0,0,0 +22/09/2018,Cardiff,Man City,0,5,A,0,3,A,M Oliver,2,21,2,10,1,9,6,4,1,1,0,0 +22/09/2018,Crystal Palace,Newcastle,0,0,D,0,0,D,A Marriner,16,6,4,3,9,5,8,11,1,1,0,0 +22/09/2018,Fulham,Watford,1,1,D,0,1,A,M Atkinson,15,11,3,6,8,8,11,9,2,1,0,0 +22/09/2018,Leicester,Huddersfield,3,1,H,1,1,D,D Coote,18,9,8,2,3,1,10,16,2,1,0,0 +22/09/2018,Liverpool,Southampton,3,0,H,3,0,H,P Tierney,12,7,4,1,5,4,7,10,0,2,0,0 +22/09/2018,Man United,Wolves,1,1,D,1,0,H,K Friend,15,11,6,8,5,4,5,17,1,1,0,0 +23/09/2018,Arsenal,Everton,2,0,H,0,0,D,J Moss,9,9,5,6,5,9,17,12,2,1,0,0 +23/09/2018,West Ham,Chelsea,0,0,D,0,0,D,M Dean,6,17,1,6,1,8,11,9,2,1,0,0 +29/09/2018,Arsenal,Watford,2,0,H,0,0,D,A Taylor,9,13,2,4,6,6,11,17,2,2,0,0 +29/09/2018,Chelsea,Liverpool,1,1,D,1,0,H,A Marriner,10,13,4,6,4,4,7,9,0,2,0,0 +29/09/2018,Everton,Fulham,3,0,H,0,0,D,R East,19,6,6,0,12,1,13,13,0,3,0,0 +29/09/2018,Huddersfield,Tottenham,0,2,A,0,2,A,C Pawson,9,10,5,6,3,0,17,16,2,2,0,0 +29/09/2018,Man City,Brighton,2,0,H,1,0,H,L Mason,28,4,8,1,10,3,4,10,0,3,0,0 +29/09/2018,Newcastle,Leicester,0,2,A,0,1,A,S Hooper,6,12,1,5,5,9,11,5,0,0,0,0 +29/09/2018,West Ham,Man United,3,1,H,2,0,H,M Oliver,8,9,3,4,4,9,12,12,0,1,0,0 +29/09/2018,Wolves,Southampton,2,0,H,0,0,D,S Attwell,14,17,6,6,8,6,11,7,3,1,0,0 +30/09/2018,Cardiff,Burnley,1,2,A,0,0,D,M Atkinson,19,3,5,2,10,2,11,15,1,3,0,0 +01/10/2018,Bournemouth,Crystal Palace,2,1,H,1,0,H,M Dean,11,10,5,2,3,3,9,12,3,4,0,0 +05/10/2018,Brighton,West Ham,1,0,H,1,0,H,K Friend,9,17,4,4,2,9,12,8,3,2,0,0 +06/10/2018,Burnley,Huddersfield,1,1,D,1,0,H,C Kavanagh,6,19,3,2,1,10,8,11,2,2,0,0 +06/10/2018,Crystal Palace,Wolves,0,1,A,0,0,D,M Oliver,11,7,4,2,6,3,11,13,3,4,0,0 +06/10/2018,Leicester,Everton,1,2,A,1,1,D,A Marriner,8,17,2,8,2,10,10,11,2,1,1,0 +06/10/2018,Man United,Newcastle,3,2,H,0,2,A,A Taylor,18,13,10,8,10,6,16,8,2,2,0,0 +06/10/2018,Tottenham,Cardiff,1,0,H,1,0,H,M Dean,19,8,7,6,6,2,7,11,2,1,0,1 +06/10/2018,Watford,Bournemouth,0,4,A,0,3,A,J Moss,14,10,2,7,10,7,11,9,3,1,1,0 +07/10/2018,Fulham,Arsenal,1,5,A,1,1,D,P Tierney,21,9,4,7,4,2,11,12,2,0,0,0 +07/10/2018,Liverpool,Man City,0,0,D,0,0,D,M Atkinson,7,6,2,2,2,6,10,10,1,3,0,0 +07/10/2018,Southampton,Chelsea,0,3,A,0,1,A,C Pawson,15,21,6,6,4,12,13,11,6,0,0,0 +20/10/2018,Bournemouth,Southampton,0,0,D,0,0,D,L Probert,10,8,2,4,6,4,10,14,1,2,0,0 +20/10/2018,Cardiff,Fulham,4,2,H,2,2,D,K Friend,22,9,5,4,4,4,15,16,3,3,0,0 +20/10/2018,Chelsea,Man United,2,2,D,1,0,H,M Dean,21,7,6,4,5,3,9,17,2,5,0,0 +20/10/2018,Huddersfield,Liverpool,0,1,A,0,1,A,M Oliver,13,11,1,2,2,4,9,6,0,2,0,0 +20/10/2018,Man City,Burnley,5,0,H,1,0,H,J Moss,24,5,10,0,10,1,11,5,2,2,0,0 +20/10/2018,Newcastle,Brighton,0,1,A,0,1,A,A Marriner,27,8,6,2,10,2,13,17,0,2,0,0 +20/10/2018,West Ham,Tottenham,0,1,A,0,1,A,M Atkinson,13,10,4,2,10,6,8,10,3,0,0,0 +20/10/2018,Wolves,Watford,0,2,A,0,2,A,L Mason,10,9,1,3,8,2,23,13,3,1,0,0 +21/10/2018,Everton,Crystal Palace,2,0,H,0,0,D,A Taylor,20,7,4,3,10,5,13,17,2,1,0,0 +22/10/2018,Arsenal,Leicester,3,1,H,1,1,D,C Kavanagh,19,8,6,2,6,4,10,10,2,2,0,0 +27/10/2018,Brighton,Wolves,1,0,H,0,0,D,A Taylor,2,25,1,7,1,10,11,8,3,0,0,0 +27/10/2018,Fulham,Bournemouth,0,3,A,0,1,A,A Marriner,11,12,1,5,5,5,16,3,2,1,1,0 +27/10/2018,Leicester,West Ham,1,1,D,0,1,A,M Oliver,21,11,7,3,8,0,17,5,1,1,0,1 +27/10/2018,Liverpool,Cardiff,4,1,H,1,0,H,S Attwell,19,2,7,1,8,0,6,4,0,0,0,0 +27/10/2018,Southampton,Newcastle,0,0,D,0,0,D,C Kavanagh,22,6,4,0,7,2,9,12,0,1,0,0 +27/10/2018,Watford,Huddersfield,3,0,H,2,0,H,M Dean,12,13,6,7,3,3,9,13,1,2,0,0 +28/10/2018,Burnley,Chelsea,0,4,A,0,1,A,C Pawson,7,24,1,8,4,4,14,10,4,2,0,0 +28/10/2018,Crystal Palace,Arsenal,2,2,D,1,0,H,M Atkinson,16,7,3,2,6,4,10,16,1,2,0,0 +28/10/2018,Man United,Everton,2,1,H,1,0,H,J Moss,14,14,10,6,8,4,15,12,2,1,0,0 +29/10/2018,Tottenham,Man City,0,1,A,0,1,A,K Friend,4,13,1,6,3,6,13,13,2,2,0,0 +03/11/2018,Arsenal,Liverpool,1,1,D,0,0,D,A Marriner,12,13,4,4,5,8,7,7,1,1,0,0 +03/11/2018,Bournemouth,Man United,1,2,A,1,1,D,P Tierney,18,18,7,8,9,5,12,12,4,3,0,0 +03/11/2018,Cardiff,Leicester,0,1,A,0,0,D,L Probert,11,13,2,5,6,12,13,12,2,2,0,0 +03/11/2018,Everton,Brighton,3,1,H,1,1,D,D Coote,14,5,3,3,6,4,9,17,0,1,0,0 +03/11/2018,Newcastle,Watford,1,0,H,0,0,D,C Pawson,10,16,2,1,8,9,12,11,1,5,0,0 +03/11/2018,West Ham,Burnley,4,2,H,1,1,D,R East,22,6,10,3,10,4,7,9,1,4,0,0 +03/11/2018,Wolves,Tottenham,2,3,A,0,2,A,M Dean,16,10,7,8,5,1,11,8,1,2,0,0 +04/11/2018,Chelsea,Crystal Palace,3,1,H,1,0,H,M Oliver,15,7,6,2,4,2,6,13,0,1,0,0 +04/11/2018,Man City,Southampton,6,1,H,4,1,H,L Mason,18,12,8,6,4,4,14,9,1,1,0,0 +05/11/2018,Huddersfield,Fulham,1,0,H,1,0,H,A Taylor,10,7,2,1,5,4,11,8,1,2,0,0 +10/11/2018,Cardiff,Brighton,2,1,H,1,1,D,M Atkinson,20,7,6,3,4,1,7,21,2,1,0,1 +10/11/2018,Crystal Palace,Tottenham,0,1,A,0,0,D,J Moss,11,11,4,2,10,6,12,9,1,1,0,0 +10/11/2018,Huddersfield,West Ham,1,1,D,1,0,H,C Kavanagh,15,12,7,5,5,6,9,8,1,1,0,0 +10/11/2018,Leicester,Burnley,0,0,D,0,0,D,M Dean,22,6,5,1,12,1,13,10,2,0,0,0 +10/11/2018,Newcastle,Bournemouth,2,1,H,2,1,H,L Probert,18,14,6,4,7,10,9,11,2,1,0,0 +10/11/2018,Southampton,Watford,1,1,D,1,0,H,S Hooper,14,12,4,6,7,6,13,13,3,2,0,0 +11/11/2018,Arsenal,Wolves,1,1,D,0,1,A,S Attwell,10,12,3,5,11,2,9,16,2,2,0,0 +11/11/2018,Chelsea,Everton,0,0,D,0,0,D,K Friend,15,6,4,1,5,5,7,11,4,3,0,0 +11/11/2018,Liverpool,Fulham,2,0,H,1,0,H,P Tierney,20,8,7,3,6,3,11,9,1,1,0,0 +11/11/2018,Man City,Man United,3,1,H,1,0,H,A Taylor,17,6,5,1,5,1,12,12,1,1,0,0 +24/11/2018,Brighton,Leicester,1,1,D,1,0,H,C Kavanagh,14,8,3,3,6,1,10,7,3,1,0,1 +24/11/2018,Everton,Cardiff,1,0,H,0,0,D,P Tierney,16,7,8,1,7,3,12,13,0,3,0,0 +24/11/2018,Fulham,Southampton,3,2,H,2,1,H,M Oliver,10,19,5,8,2,5,10,6,2,3,0,0 +24/11/2018,Man United,Crystal Palace,0,0,D,0,0,D,L Mason,12,13,5,2,10,3,13,12,1,2,0,0 +24/11/2018,Tottenham,Chelsea,3,1,H,2,0,H,M Atkinson,18,13,9,2,4,4,19,12,0,3,0,0 +24/11/2018,Watford,Liverpool,0,3,A,0,0,D,J Moss,5,10,1,7,5,5,12,13,0,0,0,1 +24/11/2018,West Ham,Man City,0,4,A,0,3,A,A Marriner,9,9,1,6,8,1,6,3,0,0,0,0 +25/11/2018,Bournemouth,Arsenal,1,2,A,1,1,D,C Pawson,11,20,5,4,5,8,6,9,2,1,0,0 +25/11/2018,Wolves,Huddersfield,0,2,A,0,1,A,K Friend,12,14,3,6,3,5,9,8,1,2,0,0 +26/11/2018,Burnley,Newcastle,1,2,A,1,2,A,A Taylor,14,17,4,3,5,5,6,11,0,1,0,0 +30/11/2018,Cardiff,Wolves,2,1,H,0,1,A,A Marriner,17,15,3,4,7,6,3,12,1,2,0,0 +01/12/2018,Crystal Palace,Burnley,2,0,H,1,0,H,L Probert,29,4,9,0,10,2,9,10,1,1,0,0 +01/12/2018,Huddersfield,Brighton,1,2,A,1,1,D,M Oliver,7,14,2,6,2,6,10,12,0,2,1,0 +01/12/2018,Leicester,Watford,2,0,H,2,0,H,G Scott,7,8,3,0,4,8,7,6,2,1,0,1 +01/12/2018,Man City,Bournemouth,3,1,H,1,1,D,S Attwell,16,4,6,1,8,4,9,3,0,0,0,0 +01/12/2018,Newcastle,West Ham,0,3,A,0,1,A,P Tierney,16,7,4,4,7,3,10,10,3,3,0,0 +01/12/2018,Southampton,Man United,2,2,D,2,2,D,K Friend,16,11,6,5,3,5,12,13,4,4,0,0 +02/12/2018,Arsenal,Tottenham,4,2,H,1,2,A,M Dean,22,11,7,6,8,5,15,17,3,3,0,1 +02/12/2018,Chelsea,Fulham,2,0,H,1,0,H,C Pawson,16,9,9,4,4,6,8,17,2,1,0,0 +02/12/2018,Liverpool,Everton,1,0,H,0,0,D,C Kavanagh,16,9,3,3,8,1,12,7,3,2,0,0 +04/12/2018,Bournemouth,Huddersfield,2,1,H,2,1,H,R East,6,23,2,6,4,8,14,11,4,2,0,0 +04/12/2018,Brighton,Crystal Palace,3,1,H,3,0,H,K Friend,9,18,3,5,4,5,14,12,2,3,1,0 +04/12/2018,Watford,Man City,1,2,A,0,1,A,P Tierney,11,15,7,7,3,11,4,8,0,1,0,0 +04/12/2018,West Ham,Cardiff,3,1,H,0,0,D,G Scott,17,9,11,5,10,4,10,10,1,2,0,0 +05/12/2018,Burnley,Liverpool,1,3,A,0,0,D,S Attwell,10,18,6,12,5,9,10,3,1,0,0,0 +05/12/2018,Everton,Newcastle,1,1,D,1,1,D,L Mason,19,8,3,5,14,2,7,18,0,5,0,0 +05/12/2018,Fulham,Leicester,1,1,D,1,0,H,D Coote,25,13,7,5,10,8,12,7,0,0,0,0 +05/12/2018,Man United,Arsenal,2,2,D,1,1,D,A Marriner,10,9,7,4,4,4,13,10,3,3,0,0 +05/12/2018,Tottenham,Southampton,3,1,H,1,0,H,A Taylor,13,18,8,5,8,6,7,5,0,0,0,0 +05/12/2018,Wolves,Chelsea,2,1,H,0,1,A,J Moss,6,17,2,3,1,5,18,10,4,4,0,0 +08/12/2018,Arsenal,Huddersfield,1,0,H,0,0,D,P Tierney,14,6,2,0,7,1,13,20,5,4,0,0 +08/12/2018,Bournemouth,Liverpool,0,4,A,0,1,A,L Mason,8,10,2,4,6,6,9,11,2,1,0,0 +08/12/2018,Burnley,Brighton,1,0,H,1,0,H,M Atkinson,14,14,4,1,2,7,11,11,2,0,0,0 +08/12/2018,Cardiff,Southampton,1,0,H,0,0,D,J Moss,13,12,4,1,7,7,9,10,2,2,0,0 +08/12/2018,Chelsea,Man City,2,0,H,1,0,H,M Oliver,8,14,5,4,1,13,12,11,2,0,0,0 +08/12/2018,Leicester,Tottenham,0,2,A,0,1,A,C Pawson,11,7,3,2,6,5,12,7,3,1,0,0 +08/12/2018,Man United,Fulham,4,1,H,3,0,H,L Probert,20,10,11,4,10,3,11,15,1,1,0,1 +08/12/2018,West Ham,Crystal Palace,3,2,H,0,1,A,A Taylor,13,8,6,4,5,3,10,8,1,2,0,0 +09/12/2018,Newcastle,Wolves,1,2,A,1,1,D,M Dean,12,13,4,6,4,6,10,17,2,5,1,0 +10/12/2018,Everton,Watford,2,2,D,1,0,H,K Friend,12,15,5,3,6,6,13,13,1,1,0,0 +15/12/2018,Crystal Palace,Leicester,1,0,H,1,0,H,M Oliver,8,12,1,2,4,4,10,8,2,1,0,0 +15/12/2018,Fulham,West Ham,0,2,A,0,2,A,M Dean,16,6,4,3,6,4,14,10,2,1,0,0 +15/12/2018,Huddersfield,Newcastle,0,1,A,0,0,D,A Taylor,15,8,5,5,10,1,5,13,1,1,0,0 +15/12/2018,Man City,Everton,3,1,H,1,0,H,C Pawson,13,9,5,2,6,2,7,9,1,2,0,0 +15/12/2018,Tottenham,Burnley,1,0,H,0,0,D,G Scott,15,4,3,0,8,3,7,8,0,2,0,0 +15/12/2018,Watford,Cardiff,3,2,H,1,0,H,A Madley,17,10,8,3,5,0,7,5,0,0,0,0 +15/12/2018,Wolves,Bournemouth,2,0,H,1,0,H,S Hooper,9,13,3,3,5,3,15,7,1,2,0,0 +16/12/2018,Brighton,Chelsea,1,2,A,0,2,A,S Attwell,6,10,2,3,4,1,14,6,2,2,0,0 +16/12/2018,Liverpool,Man United,3,1,H,1,1,D,M Atkinson,36,6,11,2,13,2,6,14,0,2,0,0 +16/12/2018,Southampton,Arsenal,3,2,H,2,1,H,C Kavanagh,12,13,7,4,4,5,12,10,3,1,0,0 +21/12/2018,Wolves,Liverpool,0,2,A,0,1,A,C Pawson,11,15,5,6,1,10,7,3,0,0,0,0 +22/12/2018,Arsenal,Burnley,3,1,H,1,0,H,K Friend,10,7,6,2,1,3,10,14,2,5,0,0 +22/12/2018,Bournemouth,Brighton,2,0,H,1,0,H,M Dean,14,10,3,5,6,5,8,18,2,0,0,1 +22/12/2018,Cardiff,Man United,1,5,A,1,3,A,M Oliver,9,17,3,9,4,7,13,13,2,1,0,0 +22/12/2018,Chelsea,Leicester,0,1,A,0,0,D,L Probert,17,8,5,3,9,5,10,9,0,2,0,0 +22/12/2018,Huddersfield,Southampton,1,3,A,0,2,A,S Attwell,16,13,5,6,8,2,12,9,2,3,0,0 +22/12/2018,Man City,Crystal Palace,2,3,A,1,2,A,A Marriner,19,5,5,3,13,0,7,6,0,4,0,0 +22/12/2018,Newcastle,Fulham,0,0,D,0,0,D,M Atkinson,9,4,0,2,6,0,9,12,1,2,0,0 +22/12/2018,West Ham,Watford,0,2,A,0,1,A,L Mason,18,11,7,5,10,4,9,11,3,2,0,0 +23/12/2018,Everton,Tottenham,2,6,A,1,3,A,P Tierney,10,17,3,8,2,1,13,9,0,2,0,0 +26/12/2018,Brighton,Arsenal,1,1,D,1,1,D,A Taylor,12,7,3,4,4,9,10,4,2,1,0,0 +26/12/2018,Burnley,Everton,1,5,A,1,3,A,M Oliver,11,13,4,6,5,7,11,19,2,4,0,0 +26/12/2018,Crystal Palace,Cardiff,0,0,D,0,0,D,L Probert,31,9,5,4,12,1,9,11,0,3,0,0 +26/12/2018,Fulham,Wolves,1,1,D,0,0,D,A Marriner,11,14,9,5,2,0,9,6,2,1,0,0 +26/12/2018,Leicester,Man City,2,1,H,1,1,D,M Dean,10,11,5,4,3,7,5,8,2,2,0,1 +26/12/2018,Liverpool,Newcastle,4,0,H,1,0,H,G Scott,16,6,8,2,10,2,7,9,0,0,0,0 +26/12/2018,Man United,Huddersfield,3,1,H,1,0,H,J Moss,16,10,10,2,5,3,9,13,1,1,0,0 +26/12/2018,Tottenham,Bournemouth,5,0,H,3,0,H,C Kavanagh,10,14,7,4,3,4,4,8,2,1,0,0 +26/12/2018,Watford,Chelsea,1,2,A,1,1,D,M Atkinson,10,10,2,4,3,4,15,5,1,0,0,0 +27/12/2018,Southampton,West Ham,1,2,A,0,0,D,C Pawson,11,16,5,5,4,5,10,3,2,0,0,0 +29/12/2018,Brighton,Everton,1,0,H,0,0,D,A Madley,11,13,3,4,6,6,10,11,0,2,0,0 +29/12/2018,Fulham,Huddersfield,1,0,H,0,0,D,K Friend,14,9,5,5,4,3,12,10,3,1,0,0 +29/12/2018,Leicester,Cardiff,0,1,A,0,0,D,S Hooper,16,12,7,3,10,4,14,16,0,2,0,0 +29/12/2018,Liverpool,Arsenal,5,1,H,4,1,H,M Oliver,15,8,10,2,5,3,8,13,1,2,0,0 +29/12/2018,Tottenham,Wolves,1,3,A,1,0,H,S Attwell,10,11,3,4,6,7,7,7,3,2,0,0 +29/12/2018,Watford,Newcastle,1,1,D,0,1,A,R East,12,8,5,2,3,6,17,16,1,2,0,0 +30/12/2018,Burnley,West Ham,2,0,H,2,0,H,D Coote,17,11,5,4,5,5,15,11,1,4,0,0 +30/12/2018,Crystal Palace,Chelsea,0,1,A,0,0,D,C Pawson,4,12,0,4,3,4,11,8,0,1,0,0 +30/12/2018,Man United,Bournemouth,4,1,H,3,1,H,L Mason,11,7,8,3,4,5,10,7,2,0,1,0 +30/12/2018,Southampton,Man City,1,3,A,1,3,A,P Tierney,5,14,4,6,3,8,11,10,2,3,1,0 +01/01/2019,Arsenal,Fulham,4,1,H,1,0,H,G Scott,16,9,9,4,8,3,7,12,0,1,0,0 +01/01/2019,Cardiff,Tottenham,0,3,A,0,3,A,K Friend,6,13,3,4,5,3,6,6,1,0,0,0 +01/01/2019,Everton,Leicester,0,1,A,0,0,D,M Atkinson,17,8,2,4,6,5,5,8,3,1,0,0 +02/01/2019,Bournemouth,Watford,3,3,D,3,3,D,D Coote,25,11,12,3,4,2,9,15,1,4,0,0 +02/01/2019,Chelsea,Southampton,0,0,D,0,0,D,J Moss,17,6,6,2,7,2,8,11,1,2,0,0 +02/01/2019,Huddersfield,Burnley,1,2,A,1,1,D,M Dean,9,16,2,8,5,2,11,9,0,4,1,1 +02/01/2019,Newcastle,Man United,0,2,A,0,0,D,A Marriner,14,16,3,7,1,2,10,9,1,2,0,0 +02/01/2019,West Ham,Brighton,2,2,D,0,0,D,C Kavanagh,13,13,6,6,1,5,9,11,0,1,0,0 +02/01/2019,Wolves,Crystal Palace,0,2,A,0,0,D,R East,9,17,1,4,3,10,9,7,4,1,0,0 +03/01/2019,Man City,Liverpool,2,1,H,1,0,H,A Taylor,9,7,4,5,2,1,12,7,4,2,0,0 +12/01/2019,Brighton,Liverpool,0,1,A,0,0,D,K Friend,7,10,0,3,2,7,15,5,0,0,0,0 +12/01/2019,Burnley,Fulham,2,1,H,2,1,H,M Atkinson,11,12,0,4,2,6,5,9,1,2,0,0 +12/01/2019,Cardiff,Huddersfield,0,0,D,0,0,D,L Mason,3,14,0,2,3,10,12,14,3,1,0,0 +12/01/2019,Chelsea,Newcastle,2,1,H,1,1,D,C Kavanagh,10,9,6,2,8,5,6,13,1,1,0,0 +12/01/2019,Crystal Palace,Watford,1,2,A,1,0,H,P Tierney,15,16,6,2,7,7,11,11,1,4,0,0 +12/01/2019,Leicester,Southampton,1,2,A,0,2,A,M Oliver,23,8,6,3,10,3,7,9,2,1,0,1 +12/01/2019,West Ham,Arsenal,1,0,H,0,0,D,J Moss,11,11,3,2,7,3,7,10,0,2,0,0 +13/01/2019,Everton,Bournemouth,2,0,H,0,0,D,A Taylor,15,16,3,7,5,9,17,8,5,0,0,0 +13/01/2019,Tottenham,Man United,0,1,A,0,1,A,M Dean,21,13,11,8,7,4,8,8,1,2,0,0 +14/01/2019,Man City,Wolves,3,0,H,2,0,H,C Pawson,24,3,9,0,12,1,6,3,1,0,0,1 +19/01/2019,Arsenal,Chelsea,2,0,H,2,0,H,A Taylor,13,13,5,1,5,6,13,15,0,2,0,0 +19/01/2019,Bournemouth,West Ham,2,0,H,0,0,D,S Hooper,10,9,4,1,2,3,6,10,0,1,0,0 +19/01/2019,Liverpool,Crystal Palace,4,3,H,0,1,A,J Moss,19,9,9,3,8,3,6,8,0,1,1,0 +19/01/2019,Man United,Brighton,2,1,H,2,0,H,P Tierney,20,7,5,3,6,5,11,11,1,0,0,0 +19/01/2019,Newcastle,Cardiff,3,0,H,1,0,H,S Attwell,16,9,6,1,7,10,11,6,0,1,0,0 +19/01/2019,Southampton,Everton,2,1,H,0,0,D,G Scott,11,7,4,2,7,6,12,11,2,1,0,0 +19/01/2019,Watford,Burnley,0,0,D,0,0,D,M Oliver,9,11,4,3,1,7,14,9,1,2,0,0 +19/01/2019,Wolves,Leicester,4,3,H,2,0,H,C Kavanagh,12,16,7,6,5,9,11,10,3,3,0,0 +20/01/2019,Fulham,Tottenham,1,2,A,1,0,H,C Pawson,12,14,4,5,7,10,10,8,2,3,0,0 +20/01/2019,Huddersfield,Man City,0,3,A,0,1,A,A Marriner,5,12,2,4,1,4,10,9,2,2,0,0 +29/01/2019,Arsenal,Cardiff,2,1,H,0,0,D,M Dean,15,19,4,2,4,7,14,12,3,3,0,0 +29/01/2019,Fulham,Brighton,4,2,H,0,2,A,L Probert,24,15,7,6,10,1,10,5,2,3,0,0 +29/01/2019,Huddersfield,Everton,0,1,A,0,1,A,S Attwell,10,10,2,5,3,4,13,10,1,4,0,1 +29/01/2019,Man United,Burnley,2,2,D,0,0,D,J Moss,28,6,9,4,11,3,10,9,1,3,0,0 +29/01/2019,Newcastle,Man City,2,1,H,0,1,A,P Tierney,6,12,2,4,1,8,9,7,2,3,0,0 +29/01/2019,Wolves,West Ham,3,0,H,0,0,D,D Coote,20,4,9,0,5,1,8,10,4,1,0,0 +30/01/2019,Bournemouth,Chelsea,4,0,H,0,0,D,R East,12,11,7,7,3,7,8,6,1,0,0,0 +30/01/2019,Liverpool,Leicester,1,1,D,1,1,D,M Atkinson,10,5,3,2,7,1,13,6,1,3,0,0 +30/01/2019,Southampton,Crystal Palace,1,1,D,0,1,A,A Marriner,13,15,4,3,3,8,11,8,2,2,0,1 +30/01/2019,Tottenham,Watford,2,1,H,0,1,A,G Scott,17,9,3,1,9,5,6,9,0,4,0,0 +02/02/2019,Brighton,Watford,0,0,D,0,0,D,S Hooper,21,5,4,0,7,0,15,10,1,0,0,0 +02/02/2019,Burnley,Southampton,1,1,D,0,0,D,A Taylor,15,10,6,4,4,2,10,9,3,1,0,0 +02/02/2019,Cardiff,Bournemouth,2,0,H,1,0,H,J Moss,12,9,5,2,6,7,17,14,1,2,0,0 +02/02/2019,Chelsea,Huddersfield,5,0,H,2,0,H,P Tierney,23,5,7,2,11,2,8,5,0,0,0,0 +02/02/2019,Crystal Palace,Fulham,2,0,H,1,0,H,M Oliver,17,8,6,0,11,1,9,12,2,3,0,0 +02/02/2019,Everton,Wolves,1,3,A,1,2,A,L Mason,13,8,4,4,3,1,12,14,3,1,0,0 +02/02/2019,Tottenham,Newcastle,1,0,H,0,0,D,A Marriner,21,8,4,2,6,3,6,6,0,1,0,0 +03/02/2019,Leicester,Man United,0,1,A,0,1,A,M Dean,17,10,6,6,7,2,14,9,4,4,0,0 +03/02/2019,Man City,Arsenal,3,1,H,2,1,H,M Atkinson,19,4,12,2,4,2,11,8,1,1,0,0 +04/02/2019,West Ham,Liverpool,1,1,D,1,1,D,K Friend,13,11,2,6,2,5,9,11,1,1,0,0 +06/02/2019,Everton,Man City,0,2,A,0,1,A,C Pawson,4,15,1,4,5,6,12,6,1,1,0,0 +09/02/2019,Brighton,Burnley,1,3,A,0,1,A,S Attwell,16,9,6,5,9,3,8,7,1,1,0,0 +09/02/2019,Crystal Palace,West Ham,1,1,D,0,1,A,C Pawson,25,6,5,4,10,4,12,9,3,0,0,0 +09/02/2019,Fulham,Man United,0,3,A,0,2,A,P Tierney,15,15,3,7,5,4,14,9,3,2,0,0 +09/02/2019,Huddersfield,Arsenal,1,2,A,0,2,A,J Moss,15,9,6,4,5,0,17,12,3,2,0,0 +09/02/2019,Liverpool,Bournemouth,3,0,H,2,0,H,A Taylor,20,12,9,2,8,5,14,6,2,2,0,0 +09/02/2019,Southampton,Cardiff,1,2,A,0,0,D,M Atkinson,14,6,7,3,8,2,15,12,3,3,0,0 +09/02/2019,Watford,Everton,1,0,H,0,0,D,L Probert,7,9,2,4,8,2,21,10,2,0,0,1 +10/02/2019,Man City,Chelsea,6,0,H,4,0,H,M Dean,15,12,9,4,2,2,9,13,1,2,0,0 +10/02/2019,Tottenham,Leicester,3,1,H,1,0,H,M Oliver,12,20,5,9,2,9,11,4,3,1,0,0 +11/02/2019,Wolves,Newcastle,1,1,D,0,0,D,G Scott,22,9,6,3,13,1,7,9,1,3,0,0 +22/02/2019,Cardiff,Watford,1,5,A,0,1,A,S Hooper,15,12,6,7,3,3,10,11,1,2,0,0 +22/02/2019,West Ham,Fulham,3,1,H,2,1,H,L Mason,21,8,7,5,12,0,11,11,1,1,0,0 +23/02/2019,Bournemouth,Wolves,1,1,D,1,0,H,R East,10,10,3,2,9,8,10,15,4,5,0,0 +23/02/2019,Burnley,Tottenham,2,1,H,0,0,D,M Dean,10,17,4,6,7,6,9,12,1,3,0,0 +23/02/2019,Leicester,Crystal Palace,1,4,A,0,1,A,A Taylor,27,7,5,5,8,3,7,14,2,1,0,0 +23/02/2019,Newcastle,Huddersfield,2,0,H,0,0,D,K Friend,29,3,12,1,12,0,11,7,2,0,0,1 +24/02/2019,Arsenal,Southampton,2,0,H,2,0,H,G Scott,12,10,4,4,6,4,7,14,0,0,0,0 +24/02/2019,Man United,Liverpool,0,0,D,0,0,D,M Oliver,6,7,3,1,3,7,15,17,1,3,0,0 +26/02/2019,Cardiff,Everton,0,3,A,0,1,A,K Friend,6,12,0,4,3,4,12,12,3,1,0,0 +26/02/2019,Huddersfield,Wolves,1,0,H,0,0,D,D Coote,15,7,3,0,3,5,10,10,2,2,0,0 +26/02/2019,Leicester,Brighton,2,1,H,1,0,H,L Probert,19,15,3,3,4,8,6,4,1,1,0,0 +26/02/2019,Newcastle,Burnley,2,0,H,2,0,H,C Pawson,12,12,3,2,4,7,8,8,1,3,0,0 +27/02/2019,Arsenal,Bournemouth,5,1,H,2,1,H,C Kavanagh,16,13,6,5,9,4,11,9,2,2,0,0 +27/02/2019,Chelsea,Tottenham,2,0,H,0,0,D,A Marriner,11,9,1,0,2,2,7,14,1,1,0,0 +27/02/2019,Crystal Palace,Man United,1,3,A,0,1,A,M Atkinson,17,13,2,4,5,6,8,9,2,3,0,0 +27/02/2019,Liverpool,Watford,5,0,H,2,0,H,G Scott,19,5,10,3,6,4,5,7,0,2,0,0 +27/02/2019,Man City,West Ham,1,0,H,0,0,D,S Attwell,20,2,7,1,12,2,2,6,0,2,0,0 +27/02/2019,Southampton,Fulham,2,0,H,2,0,H,A Taylor,14,14,4,4,7,4,15,13,0,1,0,0 +02/03/2019,Bournemouth,Man City,0,1,A,0,0,D,K Friend,0,23,0,7,0,14,7,7,1,2,0,0 +02/03/2019,Brighton,Huddersfield,1,0,H,0,0,D,M Dean,13,6,4,4,6,3,11,6,2,2,0,0 +02/03/2019,Burnley,Crystal Palace,1,3,A,0,1,A,L Probert,18,10,4,4,8,5,9,9,1,2,0,0 +02/03/2019,Man United,Southampton,3,2,H,0,1,A,S Attwell,16,6,6,3,9,7,7,10,2,1,0,0 +02/03/2019,Tottenham,Arsenal,1,1,D,0,1,A,A Taylor,10,9,3,4,3,4,15,14,3,2,0,1 +02/03/2019,West Ham,Newcastle,2,0,H,2,0,H,C Kavanagh,10,17,4,2,1,6,8,14,3,4,0,0 +02/03/2019,Wolves,Cardiff,2,0,H,2,0,H,A Marriner,13,12,6,4,7,8,11,6,1,2,0,0 +03/03/2019,Everton,Liverpool,0,0,D,0,0,D,M Atkinson,7,10,3,3,3,7,12,10,1,2,0,0 +03/03/2019,Fulham,Chelsea,1,2,A,1,2,A,G Scott,12,20,5,7,5,4,11,10,2,1,0,0 +03/03/2019,Watford,Leicester,2,1,H,1,0,H,J Moss,6,14,5,2,1,5,15,12,5,1,0,0 +09/03/2019,Cardiff,West Ham,2,0,H,1,0,H,G Scott,16,9,7,2,7,5,7,12,2,1,0,0 +09/03/2019,Crystal Palace,Brighton,1,2,A,0,1,A,C Pawson,15,4,3,3,8,0,8,18,1,5,0,0 +09/03/2019,Huddersfield,Bournemouth,0,2,A,0,1,A,A Taylor,8,8,1,5,6,5,14,9,3,1,0,0 +09/03/2019,Leicester,Fulham,3,1,H,1,0,H,D Coote,18,6,8,3,6,5,9,13,0,2,0,0 +09/03/2019,Man City,Watford,3,1,H,0,0,D,P Tierney,19,2,9,1,9,1,11,7,1,1,0,0 +09/03/2019,Newcastle,Everton,3,2,H,0,2,A,L Mason,19,7,7,3,8,2,13,10,3,1,0,0 +09/03/2019,Southampton,Tottenham,2,1,H,0,1,A,K Friend,12,16,4,5,1,10,16,9,4,2,0,0 +10/03/2019,Arsenal,Man United,2,0,H,1,0,H,J Moss,14,14,3,4,5,2,12,18,2,2,0,0 +10/03/2019,Chelsea,Wolves,1,1,D,0,0,D,M Oliver,22,2,6,1,13,0,8,14,1,4,0,0 +10/03/2019,Liverpool,Burnley,4,2,H,2,1,H,A Marriner,23,3,5,2,7,3,4,7,2,0,0,0 +16/03/2019,Bournemouth,Newcastle,2,2,D,0,1,A,M Dean,10,12,3,4,6,6,9,12,3,2,0,0 +16/03/2019,Burnley,Leicester,1,2,A,1,1,D,M Oliver,13,9,2,4,9,3,9,10,1,1,0,1 +16/03/2019,West Ham,Huddersfield,4,3,H,1,2,A,J Moss,16,15,5,5,5,8,7,15,0,2,0,0 +17/03/2019,Everton,Chelsea,2,0,H,0,0,D,A Taylor,15,16,8,5,3,4,17,9,1,2,0,0 +17/03/2019,Fulham,Liverpool,1,2,A,0,1,A,C Pawson,7,16,2,6,1,10,11,7,2,1,0,0 +30/03/2019,Brighton,Southampton,0,1,A,0,0,D,M Oliver,14,8,1,2,6,5,8,10,1,2,0,0 +30/03/2019,Burnley,Wolves,2,0,H,1,0,H,C Kavanagh,5,8,1,1,1,5,12,10,1,1,0,0 +30/03/2019,Crystal Palace,Huddersfield,2,0,H,0,0,D,L Probert,19,15,5,5,5,5,10,7,0,0,0,0 +30/03/2019,Fulham,Man City,0,2,A,0,2,A,K Friend,5,24,0,7,0,11,4,12,2,0,0,0 +30/03/2019,Leicester,Bournemouth,2,0,H,1,0,H,L Mason,18,8,4,2,6,1,6,12,0,2,0,0 +30/03/2019,Man United,Watford,2,1,H,1,0,H,S Attwell,8,20,5,8,3,5,14,9,1,2,0,0 +30/03/2019,West Ham,Everton,0,2,A,0,2,A,P Tierney,3,17,1,9,4,9,7,14,1,1,0,0 +31/03/2019,Cardiff,Chelsea,1,2,A,0,0,D,C Pawson,8,21,3,3,2,7,8,14,2,3,0,0 +31/03/2019,Liverpool,Tottenham,2,1,H,1,0,H,M Atkinson,14,11,3,2,10,3,5,8,0,1,0,0 +01/04/2019,Arsenal,Newcastle,2,0,H,1,0,H,A Taylor,7,3,4,1,6,2,11,10,2,0,0,0 +02/04/2019,Watford,Fulham,4,1,H,1,1,D,R East,15,17,7,7,6,4,12,5,3,2,0,0 +02/04/2019,Wolves,Man United,2,1,H,1,1,D,M Dean,9,18,2,4,3,5,5,11,1,2,0,1 +03/04/2019,Chelsea,Brighton,3,0,H,1,0,H,G Scott,17,3,4,1,7,1,5,9,0,0,0,0 +03/04/2019,Man City,Cardiff,2,0,H,2,0,H,J Moss,27,4,11,1,7,4,3,17,0,3,0,0 +03/04/2019,Tottenham,Crystal Palace,2,0,H,0,0,D,A Marriner,26,5,10,1,8,2,8,5,1,0,0,0 +05/04/2019,Southampton,Liverpool,1,3,A,1,1,D,P Tierney,11,17,1,5,7,5,8,7,2,4,0,0 +06/04/2019,Bournemouth,Burnley,1,3,A,1,2,A,M Atkinson,10,11,2,3,3,6,12,16,2,3,0,0 +06/04/2019,Huddersfield,Leicester,1,4,A,0,1,A,D Coote,10,18,4,9,8,5,13,11,3,0,0,0 +06/04/2019,Newcastle,Crystal Palace,0,1,A,0,0,D,S Attwell,18,3,5,1,9,4,12,13,1,2,0,0 +07/04/2019,Everton,Arsenal,1,0,H,1,0,H,K Friend,23,7,6,2,9,6,8,9,1,4,0,0 +08/04/2019,Chelsea,West Ham,2,0,H,1,0,H,C Kavanagh,16,9,7,2,7,4,8,7,2,1,0,0 +12/04/2019,Leicester,Newcastle,0,1,A,0,1,A,C Kavanagh,12,11,5,5,2,3,6,10,2,3,0,0 +13/04/2019,Brighton,Bournemouth,0,5,A,0,1,A,K Friend,8,14,1,7,4,1,10,6,2,4,1,0 +13/04/2019,Burnley,Cardiff,2,0,H,1,0,H,M Dean,14,14,7,2,8,4,7,10,2,3,0,0 +13/04/2019,Fulham,Everton,2,0,H,0,0,D,L Probert,12,8,5,1,7,3,8,10,2,1,0,0 +13/04/2019,Man United,West Ham,2,1,H,1,0,H,G Scott,14,18,4,4,3,11,14,5,1,1,0,0 +13/04/2019,Southampton,Wolves,3,1,H,2,1,H,J Moss,13,17,6,2,4,9,13,8,1,3,0,0 +13/04/2019,Tottenham,Huddersfield,4,0,H,2,0,H,L Mason,22,7,5,1,4,2,10,12,2,2,0,0 +14/04/2019,Crystal Palace,Man City,1,3,A,0,1,A,M Atkinson,7,20,3,6,2,9,5,11,0,0,0,0 +14/04/2019,Liverpool,Chelsea,2,0,H,0,0,D,M Oliver,15,6,7,3,9,2,5,9,0,1,0,0 +15/04/2019,Watford,Arsenal,0,1,A,0,1,A,C Pawson,11,19,3,6,6,4,12,8,2,0,1,0 +16/04/2019,Brighton,Cardiff,0,2,A,0,1,A,A Marriner,14,10,2,3,5,4,11,13,0,1,0,0 +20/04/2019,Bournemouth,Fulham,0,1,A,0,0,D,D Coote,15,17,5,5,3,7,11,18,1,3,0,0 +20/04/2019,Huddersfield,Watford,1,2,A,0,1,A,R East,13,11,3,6,2,2,15,9,2,2,0,0 +20/04/2019,Man City,Tottenham,1,0,H,1,0,H,M Oliver,15,10,4,4,4,4,11,11,1,2,0,0 +20/04/2019,Newcastle,Southampton,3,1,H,2,0,H,A Taylor,15,15,6,3,4,11,5,11,0,2,0,0 +20/04/2019,West Ham,Leicester,2,2,D,1,0,H,L Probert,11,11,3,5,6,8,8,9,2,0,0,0 +20/04/2019,Wolves,Brighton,0,0,D,0,0,D,C Pawson,22,5,5,0,14,1,0,8,0,1,0,0 +21/04/2019,Arsenal,Crystal Palace,2,3,A,0,1,A,J Moss,12,16,5,7,8,3,15,12,4,1,0,0 +21/04/2019,Cardiff,Liverpool,0,2,A,0,0,D,M Atkinson,7,17,2,6,5,10,6,5,1,1,0,0 +21/04/2019,Everton,Man United,4,0,H,2,0,H,P Tierney,15,7,8,1,10,2,11,7,1,1,0,0 +22/04/2019,Chelsea,Burnley,2,2,D,2,2,D,K Friend,22,6,9,3,10,1,9,4,2,1,0,0 +23/04/2019,Tottenham,Brighton,1,0,H,0,0,D,C Kavanagh,29,6,5,1,6,3,7,13,1,2,0,0 +23/04/2019,Watford,Southampton,1,1,D,0,1,A,M Dean,15,8,4,4,3,4,10,14,5,2,0,0 +24/04/2019,Man United,Man City,0,2,A,0,0,D,A Marriner,12,8,1,5,1,1,10,10,2,2,0,0 +24/04/2019,Wolves,Arsenal,3,1,H,3,0,H,S Attwell,11,11,3,1,5,5,12,9,2,3,0,0 +26/04/2019,Liverpool,Huddersfield,5,0,H,3,0,H,K Friend,21,5,7,1,4,4,5,14,0,0,0,0 +27/04/2019,Brighton,Newcastle,1,1,D,0,1,A,M Dean,9,9,2,1,7,4,7,10,2,3,0,0 +27/04/2019,Crystal Palace,Everton,0,0,D,0,0,D,L Mason,8,22,0,3,5,10,9,15,2,0,0,0 +27/04/2019,Fulham,Cardiff,1,0,H,0,0,D,C Kavanagh,8,13,2,8,10,3,10,8,0,0,0,0 +27/04/2019,Southampton,Bournemouth,3,3,D,1,2,A,G Scott,22,9,7,5,9,3,8,9,2,1,0,0 +27/04/2019,Tottenham,West Ham,0,1,A,0,0,D,A Taylor,14,16,4,7,2,7,3,8,0,2,0,0 +27/04/2019,Watford,Wolves,1,2,A,0,1,A,S Hooper,10,11,1,4,4,5,10,11,3,2,0,0 +28/04/2019,Burnley,Man City,0,1,A,0,0,D,P Tierney,2,25,0,7,0,6,5,7,1,1,0,0 +28/04/2019,Leicester,Arsenal,3,0,H,0,0,D,M Oliver,24,6,12,1,8,6,13,13,3,1,0,1 +28/04/2019,Man United,Chelsea,1,1,D,1,1,D,M Atkinson,7,16,5,3,6,6,9,14,3,2,0,0 +03/05/2019,Everton,Burnley,2,0,H,2,0,H,C Kavanagh,20,5,6,1,8,1,8,9,0,2,0,0 +04/05/2019,Bournemouth,Tottenham,1,0,H,0,0,D,C Pawson,20,11,6,5,10,6,11,12,1,5,0,2 +04/05/2019,Cardiff,Crystal Palace,2,3,A,1,2,A,M Oliver,18,21,8,7,10,5,14,11,0,0,0,0 +04/05/2019,Newcastle,Liverpool,2,3,A,1,2,A,A Marriner,14,11,7,4,2,3,10,4,1,1,0,0 +04/05/2019,West Ham,Southampton,3,0,H,1,0,H,S Attwell,17,11,6,1,2,7,2,11,1,1,0,0 +04/05/2019,Wolves,Fulham,1,0,H,0,0,D,J Moss,19,6,6,2,7,1,10,15,1,3,0,0 +05/05/2019,Arsenal,Brighton,1,1,D,1,0,H,A Taylor,20,11,8,5,16,3,9,14,5,2,0,0 +05/05/2019,Chelsea,Watford,3,0,H,0,0,D,P Tierney,19,15,9,3,6,6,6,12,0,1,0,0 +05/05/2019,Huddersfield,Man United,1,1,D,0,1,A,L Mason,7,23,3,7,3,7,10,10,1,1,0,0 +06/05/2019,Man City,Leicester,1,0,H,0,0,D,M Dean,19,7,5,2,11,0,12,5,3,2,0,0 +12/05/2019,Brighton,Man City,1,4,A,1,2,A,M Oliver,6,20,2,9,2,6,12,8,0,0,0,0 +12/05/2019,Burnley,Arsenal,1,3,A,0,0,D,M Dean,14,17,5,6,4,5,11,3,5,1,0,0 +12/05/2019,Crystal Palace,Bournemouth,5,3,H,3,1,H,R East,17,16,8,8,4,4,11,8,3,0,0,0 +12/05/2019,Fulham,Newcastle,0,4,A,0,2,A,K Friend,16,13,2,6,5,5,6,8,1,0,0,0 +12/05/2019,Leicester,Chelsea,0,0,D,0,0,D,A Taylor,9,14,3,4,4,5,9,8,0,1,0,0 +12/05/2019,Liverpool,Wolves,2,0,H,1,0,H,M Atkinson,13,7,5,2,4,1,3,11,0,2,0,0 +12/05/2019,Man United,Cardiff,0,2,A,0,1,A,J Moss,26,13,10,4,11,2,9,6,3,3,0,0 +12/05/2019,Southampton,Huddersfield,1,1,D,1,0,H,L Probert,10,10,3,3,4,3,8,6,0,1,0,0 +12/05/2019,Tottenham,Everton,2,2,D,1,0,H,A Marriner,11,17,3,9,7,4,10,13,0,2,0,0 +12/05/2019,Watford,West Ham,1,4,A,0,2,A,C Kavanagh,17,16,8,9,7,2,10,10,1,0,1,0 +09/08/2019,Liverpool,Norwich,4,1,H,4,0,H,M Oliver,15,12,7,5,11,2,9,9,0,2,0,0 +10/08/2019,West Ham,Man City,0,5,A,0,1,A,M Dean,5,14,3,9,1,1,6,13,2,2,0,0 +10/08/2019,Bournemouth,Sheffield United,1,1,D,0,0,D,K Friend,13,8,3,3,3,4,10,19,2,1,0,0 +10/08/2019,Burnley,Southampton,3,0,H,0,0,D,G Scott,10,11,4,3,2,7,6,12,0,0,0,0 +10/08/2019,Crystal Palace,Everton,0,0,D,0,0,D,J Moss,6,10,2,3,6,2,16,14,2,1,0,1 +10/08/2019,Watford,Brighton,0,3,A,0,1,A,C Pawson,11,5,3,3,5,2,15,11,0,1,0,0 +10/08/2019,Tottenham,Aston Villa,3,1,H,0,1,A,C Kavanagh,31,7,7,4,14,0,13,9,1,0,0,0 +11/08/2019,Leicester,Wolves,0,0,D,0,0,D,A Marriner,15,8,1,2,12,3,3,13,0,2,0,0 +11/08/2019,Newcastle,Arsenal,0,1,A,0,0,D,M Atkinson,9,8,2,2,5,3,12,7,1,3,0,0 +11/08/2019,Man United,Chelsea,4,0,H,1,0,H,A Taylor,11,18,5,7,3,5,15,13,3,4,0,0 +17/08/2019,Arsenal,Burnley,2,1,H,1,1,D,M Dean,16,18,9,5,10,7,13,11,2,1,0,0 +17/08/2019,Aston Villa,Bournemouth,1,2,A,0,2,A,M Atkinson,22,12,7,4,10,5,10,13,0,2,0,0 +17/08/2019,Brighton,West Ham,1,1,D,0,0,D,A Taylor,16,8,4,3,8,6,11,10,0,2,0,0 +17/08/2019,Everton,Watford,1,0,H,1,0,H,L Mason,12,8,2,2,4,7,11,11,2,3,0,0 +17/08/2019,Norwich,Newcastle,3,1,H,1,0,H,S Attwell,15,10,8,3,7,5,9,11,1,3,0,0 +17/08/2019,Southampton,Liverpool,1,2,A,0,1,A,A Marriner,14,15,3,6,5,9,10,6,2,1,0,0 +17/08/2019,Man City,Tottenham,2,2,D,2,1,H,M Oliver,30,3,10,2,13,2,14,4,1,0,0,0 +18/08/2019,Sheffield United,Crystal Palace,1,0,H,0,0,D,D Coote,15,6,3,4,8,4,16,11,3,1,0,0 +18/08/2019,Chelsea,Leicester,1,1,D,1,0,H,O Langford,14,12,5,3,4,5,9,14,1,0,0,0 +19/08/2019,Wolves,Man United,1,1,D,0,1,A,J Moss,6,9,2,2,4,6,17,8,2,2,0,0 +23/08/2019,Aston Villa,Everton,2,0,H,1,0,H,M Oliver,7,12,3,1,0,6,10,18,2,3,0,0 +24/08/2019,Norwich,Chelsea,2,3,A,2,2,D,M Atkinson,6,23,5,8,1,8,9,9,1,1,0,0 +24/08/2019,Brighton,Southampton,0,2,A,0,0,D,K Friend,12,12,3,4,8,5,9,10,1,3,1,0 +24/08/2019,Man United,Crystal Palace,1,2,A,0,1,A,P Tierney,22,5,3,3,8,1,8,18,2,4,0,0 +24/08/2019,Sheffield United,Leicester,1,2,A,0,1,A,A Madley,8,10,3,2,7,4,11,6,1,0,0,0 +24/08/2019,Watford,West Ham,1,3,A,1,1,D,C Kavanagh,23,16,3,10,8,7,12,11,1,1,0,0 +24/08/2019,Liverpool,Arsenal,3,1,H,1,0,H,A Taylor,25,9,5,3,6,4,8,5,1,1,0,0 +25/08/2019,Bournemouth,Man City,1,3,A,1,2,A,A Marriner,10,19,7,5,4,5,7,13,1,3,0,0 +25/08/2019,Tottenham,Newcastle,0,1,A,0,1,A,M Dean,17,8,2,3,6,6,10,5,2,2,0,0 +25/08/2019,Wolves,Burnley,1,1,D,0,1,A,C Pawson,17,13,2,4,4,3,9,11,0,2,0,0 +31/08/2019,Southampton,Man United,1,1,D,0,1,A,M Dean,10,21,2,8,2,3,17,7,1,2,1,0 +31/08/2019,Chelsea,Sheffield United,2,2,D,2,0,H,S Attwell,13,8,5,2,3,4,6,11,0,1,0,0 +31/08/2019,Crystal Palace,Aston Villa,1,0,H,0,0,D,K Friend,22,10,5,2,13,2,15,16,2,4,0,1 +31/08/2019,Leicester,Bournemouth,3,1,H,2,1,H,P Bankes,13,8,5,2,4,5,9,11,1,3,0,0 +31/08/2019,Man City,Brighton,4,0,H,2,0,H,J Moss,15,6,6,2,8,1,10,6,1,1,0,0 +31/08/2019,Newcastle,Watford,1,1,D,1,1,D,G Scott,13,13,5,3,6,5,5,11,2,3,0,0 +31/08/2019,West Ham,Norwich,2,0,H,1,0,H,P Tierney,18,8,8,3,8,2,16,10,2,1,0,0 +31/08/2019,Burnley,Liverpool,0,3,A,0,2,A,C Kavanagh,7,15,2,7,6,4,10,16,0,0,0,0 +01/09/2019,Everton,Wolves,3,2,H,2,1,H,A Taylor,15,8,6,5,7,7,12,12,1,4,0,1 +01/09/2019,Arsenal,Tottenham,2,2,D,1,2,A,M Atkinson,26,13,8,9,11,6,13,19,3,5,0,0 +14/09/2019,Liverpool,Newcastle,3,1,H,2,1,H,A Marriner,21,8,9,1,10,1,5,4,0,0,0,0 +14/09/2019,Brighton,Burnley,1,1,D,0,0,D,M Oliver,14,7,5,1,3,6,13,7,0,2,0,0 +14/09/2019,Man United,Leicester,1,0,H,1,0,H,M Atkinson,10,9,5,3,3,9,14,14,1,2,0,0 +14/09/2019,Sheffield United,Southampton,0,1,A,0,0,D,L Mason,17,11,4,7,12,6,10,7,1,1,1,0 +14/09/2019,Tottenham,Crystal Palace,4,0,H,4,0,H,C Pawson,13,11,5,6,4,3,19,11,4,2,0,0 +14/09/2019,Wolves,Chelsea,2,5,A,0,3,A,G Scott,11,15,4,6,7,5,8,11,1,2,0,0 +14/09/2019,Norwich,Man City,3,2,H,2,1,H,K Friend,7,25,3,8,3,16,8,7,3,1,0,0 +15/09/2019,Bournemouth,Everton,3,1,H,1,1,D,P Tierney,13,14,6,5,7,7,5,14,0,4,0,0 +15/09/2019,Watford,Arsenal,2,2,D,0,2,A,A Taylor,31,10,7,4,7,1,14,4,3,3,0,0 +16/09/2019,Aston Villa,West Ham,0,0,D,0,0,D,M Dean,10,13,5,1,2,4,13,12,2,1,0,1 +20/09/2019,Southampton,Bournemouth,1,3,A,0,2,A,C Kavanagh,25,6,6,3,4,5,8,9,1,3,0,0 +21/09/2019,Leicester,Tottenham,2,1,H,0,1,A,P Tierney,16,11,7,4,8,2,16,13,1,2,0,0 +21/09/2019,Burnley,Norwich,2,0,H,2,0,H,D Coote,13,11,6,2,7,3,11,10,0,1,0,0 +21/09/2019,Everton,Sheffield United,0,2,A,0,1,A,S Hooper,16,2,3,1,12,3,9,9,1,3,0,0 +21/09/2019,Man City,Watford,8,0,H,5,0,H,M Dean,28,5,11,4,5,4,5,9,2,2,0,0 +21/09/2019,Newcastle,Brighton,0,0,D,0,0,D,M Atkinson,11,16,4,3,5,3,12,8,2,1,0,0 +22/09/2019,Crystal Palace,Wolves,1,1,D,0,0,D,S Attwell,13,14,4,4,6,7,7,10,2,1,0,1 +22/09/2019,West Ham,Man United,2,0,H,1,0,H,A Taylor,8,9,6,3,3,7,8,9,2,2,0,0 +22/09/2019,Arsenal,Aston Villa,3,2,H,0,1,A,J Moss,21,14,6,9,9,4,13,15,5,1,1,0 +22/09/2019,Chelsea,Liverpool,1,2,A,0,2,A,M Oliver,13,6,2,3,6,4,8,13,3,3,0,0 +28/09/2019,Sheffield United,Liverpool,0,1,A,0,0,D,A Taylor,12,16,2,4,6,5,8,4,1,1,0,0 +28/09/2019,Aston Villa,Burnley,2,2,D,1,0,H,L Mason,16,10,3,3,7,3,10,16,1,4,0,0 +28/09/2019,Bournemouth,West Ham,2,2,D,1,1,D,S Attwell,13,17,5,6,6,6,7,8,3,1,0,0 +28/09/2019,Chelsea,Brighton,2,0,H,0,0,D,C Kavanagh,24,8,10,1,5,2,5,13,2,3,0,0 +28/09/2019,Crystal Palace,Norwich,2,0,H,1,0,H,J Moss,15,10,3,3,5,5,14,10,3,1,0,0 +28/09/2019,Tottenham,Southampton,2,1,H,2,1,H,G Scott,9,14,4,6,6,6,5,16,0,2,1,0 +28/09/2019,Wolves,Watford,2,0,H,1,0,H,P Tierney,6,14,2,5,1,6,5,6,0,1,0,0 +28/09/2019,Everton,Man City,1,3,A,1,1,D,M Oliver,12,20,8,9,5,2,4,8,2,2,0,0 +29/09/2019,Leicester,Newcastle,5,0,H,1,0,H,C Pawson,13,3,5,0,9,0,12,9,1,1,0,1 +30/09/2019,Man United,Arsenal,1,1,D,1,0,H,K Friend,16,10,4,5,8,7,18,13,4,2,0,0 +05/10/2019,Brighton,Tottenham,3,0,H,2,0,H,J Moss,17,8,6,3,4,4,11,7,2,1,0,0 +05/10/2019,Burnley,Everton,1,0,H,0,0,D,G Scott,9,11,3,2,7,9,5,12,2,1,0,1 +05/10/2019,Liverpool,Leicester,2,1,H,1,0,H,C Kavanagh,18,2,8,1,4,6,9,17,1,4,0,0 +05/10/2019,Norwich,Aston Villa,1,5,A,0,2,A,A Madley,21,22,5,12,10,6,14,17,1,3,0,0 +05/10/2019,Watford,Sheffield United,0,0,D,0,0,D,A Marriner,8,9,2,3,7,7,7,6,0,2,0,0 +05/10/2019,West Ham,Crystal Palace,1,2,A,0,0,D,M Oliver,9,7,4,4,2,2,11,7,3,2,0,0 +06/10/2019,Arsenal,Bournemouth,1,0,H,1,0,H,M Atkinson,12,10,2,2,14,5,12,6,1,2,0,0 +06/10/2019,Man City,Wolves,0,2,A,0,0,D,C Pawson,18,7,2,2,9,1,11,14,5,2,0,0 +06/10/2019,Southampton,Chelsea,1,4,A,1,3,A,P Tierney,10,13,3,7,3,4,8,15,0,1,0,0 +06/10/2019,Newcastle,Man United,1,0,H,0,0,D,M Dean,12,12,2,3,4,6,12,10,3,3,0,0 +19/10/2019,Everton,West Ham,2,0,H,1,0,H,P Tierney,19,8,10,4,11,2,15,15,2,2,0,0 +19/10/2019,Aston Villa,Brighton,2,1,H,1,1,D,D Coote,24,20,8,5,7,5,9,17,1,2,0,1 +19/10/2019,Bournemouth,Norwich,0,0,D,0,0,D,L Mason,11,10,2,1,6,7,8,11,1,3,0,0 +19/10/2019,Chelsea,Newcastle,1,0,H,0,0,D,A Marriner,16,5,8,0,11,0,9,12,2,1,0,0 +19/10/2019,Leicester,Burnley,2,1,H,1,1,D,J Moss,19,13,3,4,9,4,4,10,0,3,0,0 +19/10/2019,Tottenham,Watford,1,1,D,0,1,A,C Kavanagh,12,7,2,2,11,2,5,9,4,3,0,0 +19/10/2019,Wolves,Southampton,1,1,D,0,0,D,P Bankes,4,14,1,5,1,3,10,17,3,2,0,0 +19/10/2019,Crystal Palace,Man City,0,2,A,0,2,A,A Taylor,7,21,2,10,1,8,8,10,1,1,0,0 +20/10/2019,Man United,Liverpool,1,1,D,1,0,H,M Atkinson,7,10,2,4,3,1,6,14,0,1,0,0 +21/10/2019,Sheffield United,Arsenal,1,0,H,1,0,H,M Dean,8,9,2,3,7,12,10,12,4,4,0,0 +25/10/2019,Southampton,Leicester,0,9,A,0,5,A,A Marriner,6,25,3,15,2,7,3,12,0,0,1,0 +26/10/2019,Man City,Aston Villa,3,0,H,0,0,D,G Scott,25,11,9,5,13,7,10,5,1,1,1,0 +26/10/2019,Brighton,Everton,3,2,H,1,1,D,A Madley,8,10,4,6,0,5,12,12,2,1,0,0 +26/10/2019,Watford,Bournemouth,0,0,D,0,0,D,M Dean,7,15,3,5,5,10,14,8,5,3,0,0 +26/10/2019,West Ham,Sheffield United,1,1,D,1,0,H,D Coote,12,10,4,4,10,4,7,10,2,2,0,0 +26/10/2019,Burnley,Chelsea,2,4,A,0,2,A,M Oliver,13,16,5,7,5,6,7,8,3,2,0,0 +27/10/2019,Newcastle,Wolves,1,1,D,1,0,H,K Friend,13,13,2,5,3,6,8,15,2,2,1,0 +27/10/2019,Arsenal,Crystal Palace,2,2,D,2,1,H,M Atkinson,15,10,6,4,12,5,18,9,2,0,0,0 +27/10/2019,Liverpool,Tottenham,2,1,H,0,1,A,A Taylor,21,11,13,4,8,3,9,11,3,3,0,0 +27/10/2019,Norwich,Man United,1,3,A,0,2,A,S Attwell,10,21,3,11,1,10,13,10,2,2,0,0 +02/11/2019,Bournemouth,Man United,1,0,H,1,0,H,C Kavanagh,12,15,6,4,4,10,14,12,4,3,0,0 +02/11/2019,Arsenal,Wolves,1,1,D,1,0,H,M Oliver,10,25,4,8,8,9,6,15,0,2,0,0 +02/11/2019,Aston Villa,Liverpool,1,2,A,1,0,H,J Moss,5,25,2,6,2,10,12,9,1,2,0,0 +02/11/2019,Brighton,Norwich,2,0,H,0,0,D,K Friend,21,7,5,0,8,3,11,8,0,1,0,0 +02/11/2019,Man City,Southampton,2,1,H,0,1,A,L Mason,26,3,4,3,17,0,11,10,3,1,0,0 +02/11/2019,Sheffield United,Burnley,3,0,H,3,0,H,S Hooper,13,6,6,0,7,6,8,12,1,2,0,0 +02/11/2019,West Ham,Newcastle,2,3,A,0,2,A,S Attwell,16,12,6,9,7,3,10,7,2,1,0,0 +02/11/2019,Watford,Chelsea,1,2,A,0,1,A,A Taylor,12,16,3,10,1,6,8,5,4,2,0,0 +03/11/2019,Crystal Palace,Leicester,0,2,A,0,0,D,P Tierney,12,14,3,5,5,6,15,16,2,2,0,0 +03/11/2019,Everton,Tottenham,1,1,D,0,0,D,M Atkinson,7,4,3,2,5,2,9,8,1,2,0,1 +08/11/2019,Norwich,Watford,0,2,A,0,1,A,A Marriner,17,12,5,2,10,4,7,13,1,2,0,1 +09/11/2019,Chelsea,Crystal Palace,2,0,H,0,0,D,M Dean,23,3,5,1,7,0,9,17,3,2,0,0 +09/11/2019,Burnley,West Ham,3,0,H,2,0,H,K Friend,14,7,12,4,11,4,17,10,2,1,0,0 +09/11/2019,Newcastle,Bournemouth,2,1,H,1,1,D,M Atkinson,20,16,9,3,5,4,10,5,2,0,0,0 +09/11/2019,Southampton,Everton,1,2,A,0,1,A,P Tierney,4,24,3,5,5,9,12,12,1,1,0,0 +09/11/2019,Tottenham,Sheffield United,1,1,D,0,0,D,G Scott,17,14,5,4,4,4,6,9,2,2,0,0 +09/11/2019,Leicester,Arsenal,2,0,H,0,0,D,C Kavanagh,19,8,7,1,5,4,10,10,1,1,0,0 +10/11/2019,Man United,Brighton,3,1,H,2,0,H,J Moss,21,6,11,2,5,2,10,14,2,5,0,0 +10/11/2019,Wolves,Aston Villa,2,1,H,1,0,H,A Taylor,17,13,5,4,6,3,17,8,4,3,0,0 +10/11/2019,Liverpool,Man City,3,1,H,2,0,H,M Oliver,12,18,5,3,4,13,10,5,0,2,0,0 +23/11/2019,West Ham,Tottenham,2,3,A,0,2,A,M Oliver,11,15,4,6,3,7,12,14,3,2,0,0 +23/11/2019,Arsenal,Southampton,2,2,D,1,1,D,S Attwell,12,21,5,6,6,8,13,19,6,2,0,0 +23/11/2019,Bournemouth,Wolves,1,2,A,0,2,A,S Hooper,9,17,2,7,5,9,10,12,2,1,1,0 +23/11/2019,Brighton,Leicester,0,2,A,0,0,D,M Dean,7,19,0,9,6,7,9,1,2,0,0,0 +23/11/2019,Crystal Palace,Liverpool,1,2,A,0,0,D,K Friend,16,12,6,4,9,8,8,7,0,1,0,0 +23/11/2019,Everton,Norwich,0,2,A,0,0,D,A Taylor,18,13,7,5,7,7,12,11,3,5,0,0 +23/11/2019,Watford,Burnley,0,3,A,0,0,D,P Tierney,15,11,4,5,7,5,14,13,0,3,0,0 +23/11/2019,Man City,Chelsea,2,1,H,2,1,H,M Atkinson,15,11,4,2,8,2,5,9,1,1,0,0 +24/11/2019,Sheffield United,Man United,3,3,D,1,0,H,A Marriner,12,11,8,5,6,3,13,10,1,2,0,0 +25/11/2019,Aston Villa,Newcastle,2,0,H,2,0,H,L Mason,17,11,8,5,11,6,10,9,1,1,0,0 +30/11/2019,Newcastle,Man City,2,2,D,1,1,D,C Kavanagh,6,24,3,9,1,10,4,6,1,2,0,0 +30/11/2019,Burnley,Crystal Palace,0,2,A,0,1,A,P Bankes,15,7,4,2,5,6,13,10,2,0,0,0 +30/11/2019,Chelsea,West Ham,0,1,A,0,0,D,J Moss,19,5,6,4,9,3,0,16,0,3,0,0 +30/11/2019,Liverpool,Brighton,2,1,H,2,0,H,M Atkinson,15,12,7,5,4,5,3,10,0,0,1,0 +30/11/2019,Tottenham,Bournemouth,3,2,H,1,0,H,L Mason,15,20,5,6,4,8,10,12,0,2,0,0 +30/11/2019,Southampton,Watford,2,1,H,0,1,A,M Oliver,11,6,6,3,6,7,6,10,1,0,0,0 +01/12/2019,Norwich,Arsenal,2,2,D,2,1,H,P Tierney,15,16,8,7,7,12,8,10,2,1,0,0 +01/12/2019,Wolves,Sheffield United,1,1,D,0,1,A,D Coote,13,9,3,4,7,2,12,18,1,4,0,0 +01/12/2019,Leicester,Everton,2,1,H,0,1,A,G Scott,16,11,6,3,4,4,7,9,0,1,0,0 +01/12/2019,Man United,Aston Villa,2,2,D,1,1,D,C Pawson,16,9,6,3,9,9,16,8,4,1,0,0 +03/12/2019,Crystal Palace,Bournemouth,1,0,H,0,0,D,A Taylor,7,6,2,3,6,9,6,15,3,3,1,0 +03/12/2019,Burnley,Man City,1,4,A,0,1,A,J Moss,4,17,2,8,3,6,9,6,1,1,0,0 +04/12/2019,Chelsea,Aston Villa,2,1,H,1,1,D,C Kavanagh,25,9,9,3,9,5,18,10,2,1,0,0 +04/12/2019,Leicester,Watford,2,0,H,0,0,D,C Pawson,12,4,6,1,7,2,13,16,3,2,0,0 +04/12/2019,Man United,Tottenham,2,1,H,1,1,D,P Tierney,12,8,7,5,4,3,9,9,0,1,0,0 +04/12/2019,Southampton,Norwich,2,1,H,2,0,H,K Friend,14,10,5,3,6,1,12,7,1,1,0,0 +04/12/2019,Wolves,West Ham,2,0,H,1,0,H,A Marriner,13,6,7,3,5,4,8,9,1,2,0,0 +04/12/2019,Liverpool,Everton,5,2,H,4,2,H,M Dean,11,12,5,4,5,6,13,17,1,2,0,0 +05/12/2019,Sheffield United,Newcastle,0,2,A,0,1,A,S Attwell,13,6,6,4,8,1,10,6,2,0,0,0 +05/12/2019,Arsenal,Brighton,1,2,A,0,1,A,G Scott,12,20,5,9,9,9,10,11,3,1,0,0 +07/12/2019,Everton,Chelsea,3,1,H,1,0,H,C Pawson,13,15,7,4,1,5,14,9,3,0,0,0 +07/12/2019,Bournemouth,Liverpool,0,3,A,0,2,A,C Kavanagh,3,21,0,9,1,3,5,6,0,1,0,0 +07/12/2019,Tottenham,Burnley,5,0,H,3,0,H,K Friend,13,7,7,1,1,1,12,8,1,1,0,0 +07/12/2019,Watford,Crystal Palace,0,0,D,0,0,D,M Atkinson,13,3,3,0,6,2,15,16,4,2,0,0 +07/12/2019,Man City,Man United,1,2,A,0,2,A,A Taylor,22,11,5,7,16,3,10,10,3,2,0,0 +08/12/2019,Aston Villa,Leicester,1,4,A,1,2,A,M Oliver,15,23,2,8,8,5,21,15,6,2,0,0 +08/12/2019,Newcastle,Southampton,2,1,H,0,0,D,D Coote,12,19,6,4,3,12,11,17,1,3,0,0 +08/12/2019,Norwich,Sheffield United,1,2,A,1,0,H,S Hooper,14,9,4,3,9,1,6,12,0,4,0,0 +08/12/2019,Brighton,Wolves,2,2,D,2,2,D,J Moss,13,13,4,5,3,4,10,7,3,0,0,0 +09/12/2019,West Ham,Arsenal,1,3,A,1,0,H,M Dean,11,10,4,3,4,3,12,6,2,0,0,0 +14/12/2019,Liverpool,Watford,2,0,H,1,0,H,A Marriner,16,8,6,2,6,3,8,6,2,1,0,0 +14/12/2019,Burnley,Newcastle,1,0,H,0,0,D,T Robinson,7,9,4,0,4,3,15,12,0,2,0,0 +14/12/2019,Chelsea,Bournemouth,0,1,A,0,0,D,G Scott,18,11,5,2,9,4,9,14,1,2,0,0 +14/12/2019,Leicester,Norwich,1,1,D,1,1,D,A Madley,18,10,5,3,12,4,11,15,0,4,0,0 +14/12/2019,Sheffield United,Aston Villa,2,0,H,0,0,D,P Bankes,7,8,3,0,3,2,10,9,2,2,0,0 +14/12/2019,Southampton,West Ham,0,1,A,0,1,A,M Atkinson,11,11,2,4,5,8,9,14,1,3,0,0 +15/12/2019,Man United,Everton,1,1,D,0,1,A,M Oliver,24,8,8,3,6,5,10,12,1,2,0,0 +15/12/2019,Wolves,Tottenham,1,2,A,0,1,A,S Attwell,18,9,5,5,11,2,14,10,4,4,0,0 +15/12/2019,Arsenal,Man City,0,3,A,0,3,A,P Tierney,6,14,1,7,3,3,9,24,1,4,0,0 +16/12/2019,Crystal Palace,Brighton,1,1,D,0,0,D,C Pawson,11,15,4,10,3,7,7,12,1,1,0,0 +21/12/2019,Everton,Arsenal,0,0,D,0,0,D,K Friend,9,6,0,2,5,4,10,11,2,3,0,0 +21/12/2019,Aston Villa,Southampton,1,3,A,0,2,A,C Pawson,19,17,6,8,7,5,16,15,2,1,0,0 +21/12/2019,Bournemouth,Burnley,0,1,A,0,0,D,M Atkinson,3,2,0,1,4,5,13,21,2,4,0,0 +21/12/2019,Brighton,Sheffield United,0,1,A,0,1,A,R Jones,9,7,2,2,4,3,6,11,1,3,0,0 +21/12/2019,Newcastle,Crystal Palace,1,0,H,0,0,D,S Hooper,8,16,3,4,4,5,12,11,2,0,0,0 +21/12/2019,Norwich,Wolves,1,2,A,1,0,H,P Bankes,13,14,7,4,3,11,7,14,2,4,0,0 +21/12/2019,Man City,Leicester,3,1,H,2,1,H,M Dean,23,5,12,2,6,1,14,9,2,2,0,0 +22/12/2019,Watford,Man United,2,0,H,0,0,D,L Mason,11,17,3,8,3,5,16,11,3,1,0,0 +22/12/2019,Tottenham,Chelsea,0,2,A,0,2,A,A Taylor,5,13,1,3,3,5,9,11,4,3,1,0 +26/12/2019,Tottenham,Brighton,2,1,H,0,1,A,G Scott,11,11,5,5,9,2,10,9,4,2,0,0 +26/12/2019,Aston Villa,Norwich,1,0,H,0,0,D,C Kavanagh,11,16,2,6,5,8,15,12,2,3,0,0 +26/12/2019,Bournemouth,Arsenal,1,1,D,1,0,H,S Attwell,12,17,4,2,8,3,5,13,4,4,0,0 +26/12/2019,Chelsea,Southampton,0,2,A,0,1,A,J Moss,10,5,3,3,8,1,10,9,3,2,0,0 +26/12/2019,Crystal Palace,West Ham,2,1,H,0,0,D,A Marriner,13,10,4,2,7,6,5,10,0,2,0,0 +26/12/2019,Everton,Burnley,1,0,H,0,0,D,A Taylor,21,6,5,0,10,2,15,14,0,0,0,0 +26/12/2019,Sheffield United,Watford,1,1,D,1,1,D,D Coote,16,5,4,2,9,1,11,8,1,3,0,0 +26/12/2019,Man United,Newcastle,4,1,H,3,1,H,K Friend,22,7,10,2,5,0,10,7,2,2,0,0 +26/12/2019,Leicester,Liverpool,0,4,A,0,1,A,M Oliver,3,15,0,6,2,8,5,7,1,1,0,0 +27/12/2019,Wolves,Man City,3,2,H,0,1,A,M Atkinson,20,7,8,3,3,1,7,13,0,1,0,1 +28/12/2019,Brighton,Bournemouth,2,0,H,1,0,H,P Tierney,16,10,6,2,9,2,8,11,0,2,0,0 +28/12/2019,Newcastle,Everton,1,2,A,0,1,A,L Mason,20,22,5,9,5,8,13,13,2,2,0,0 +28/12/2019,Southampton,Crystal Palace,1,1,D,0,0,D,A Madley,14,5,5,2,12,4,13,7,1,2,0,0 +28/12/2019,Watford,Aston Villa,3,0,H,1,0,H,S Hooper,20,11,6,2,6,3,16,9,3,1,1,0 +28/12/2019,Norwich,Tottenham,2,2,D,1,0,H,K Friend,5,15,2,7,4,6,12,10,3,3,0,0 +28/12/2019,West Ham,Leicester,1,2,A,1,1,D,D Coote,14,13,4,8,5,5,16,4,4,1,0,0 +28/12/2019,Burnley,Man United,0,2,A,0,1,A,M Dean,8,14,1,5,5,4,17,10,4,3,0,0 +29/12/2019,Arsenal,Chelsea,1,2,A,1,0,H,C Pawson,7,13,2,4,2,3,13,19,5,4,0,0 +29/12/2019,Liverpool,Wolves,1,0,H,1,0,H,A Taylor,10,10,3,2,5,6,7,3,1,0,0,0 +29/12/2019,Man City,Sheffield United,2,0,H,0,0,D,C Kavanagh,16,8,4,0,8,5,5,6,1,1,0,0 +01/01/2020,Brighton,Chelsea,1,1,D,0,1,A,S Attwell,16,16,5,5,5,3,8,15,2,3,0,0 +01/01/2020,Burnley,Aston Villa,1,2,A,0,2,A,M Oliver,20,11,1,6,8,4,12,10,1,1,0,0 +01/01/2020,Newcastle,Leicester,0,3,A,0,2,A,M Atkinson,5,17,2,10,4,5,8,12,1,1,0,0 +01/01/2020,Southampton,Tottenham,1,0,H,1,0,H,M Dean,12,11,3,5,6,9,21,8,3,4,0,0 +01/01/2020,Watford,Wolves,2,1,H,1,0,H,A Madley,9,16,3,4,4,7,12,6,3,1,1,0 +01/01/2020,Man City,Everton,2,1,H,0,0,D,A Marriner,16,7,7,2,3,6,11,11,0,4,0,0 +01/01/2020,Norwich,Crystal Palace,1,1,D,1,0,H,J Moss,15,12,4,3,2,5,12,9,5,0,0,0 +01/01/2020,West Ham,Bournemouth,4,0,H,3,0,H,G Scott,14,3,7,2,9,2,3,12,1,2,0,0 +01/01/2020,Arsenal,Man United,2,0,H,2,0,H,C Kavanagh,10,10,4,4,1,4,11,15,2,0,0,0 +02/01/2020,Liverpool,Sheffield United,2,0,H,1,0,H,P Tierney,19,3,7,2,8,4,5,8,0,0,0,0 +10/01/2020,Sheffield United,West Ham,1,0,H,0,0,D,M Oliver,13,7,3,2,7,3,11,6,1,0,0,0 +11/01/2020,Crystal Palace,Arsenal,1,1,D,0,1,A,P Tierney,6,7,3,4,1,4,14,22,2,3,0,1 +11/01/2020,Chelsea,Burnley,3,0,H,2,0,H,K Friend,18,7,8,2,10,3,7,4,0,3,0,0 +11/01/2020,Everton,Brighton,1,0,H,1,0,H,D Coote,14,9,6,2,4,3,7,7,2,1,0,0 +11/01/2020,Leicester,Southampton,1,2,A,1,1,D,L Mason,5,16,5,10,2,4,9,17,0,3,0,0 +11/01/2020,Man United,Norwich,4,0,H,1,0,H,A Taylor,13,8,6,3,4,2,18,7,0,0,0,0 +11/01/2020,Wolves,Newcastle,1,1,D,1,1,D,P Bankes,9,5,4,1,5,2,13,12,0,3,0,0 +11/01/2020,Tottenham,Liverpool,0,1,A,0,1,A,M Atkinson,14,13,4,7,4,3,4,8,0,2,0,0 +12/01/2020,Bournemouth,Watford,0,3,A,0,1,A,M Dean,10,18,1,6,5,5,3,13,1,1,0,0 +12/01/2020,Aston Villa,Man City,1,6,A,0,4,A,J Moss,7,22,1,12,2,4,5,12,2,1,0,0 +18/01/2020,Watford,Tottenham,0,0,D,0,0,D,M Oliver,15,17,2,5,3,3,13,13,2,3,0,0 +18/01/2020,Arsenal,Sheffield United,1,1,D,1,0,H,M Dean,11,12,4,4,4,5,9,13,1,2,0,0 +18/01/2020,Brighton,Aston Villa,1,1,D,1,0,H,A Madley,10,8,2,3,3,4,11,13,3,3,0,0 +18/01/2020,Man City,Crystal Palace,2,2,D,0,1,A,S Scott,25,5,6,3,14,2,9,7,1,2,0,0 +18/01/2020,Norwich,Bournemouth,1,0,H,1,0,H,P Tierney,16,6,5,3,4,5,10,10,2,2,1,1 +18/01/2020,Southampton,Wolves,2,3,A,2,0,H,D England,11,8,5,4,1,3,13,12,2,1,0,0 +18/01/2020,West Ham,Everton,1,1,D,1,1,D,A Marriner,12,11,6,3,7,6,11,13,1,0,0,0 +18/01/2020,Newcastle,Chelsea,1,0,H,0,0,D,C Kavanagh,7,19,2,4,1,10,9,14,1,1,0,0 +19/01/2020,Burnley,Leicester,2,1,H,0,1,A,A Taylor,8,18,5,8,4,5,14,8,1,0,0,0 +19/01/2020,Liverpool,Man United,2,0,H,1,0,H,C Pawson,16,9,5,4,11,5,7,10,1,3,0,0 +21/01/2020,Aston Villa,Watford,2,1,H,0,1,A,M Atkinson,16,8,5,2,6,3,11,18,2,2,0,0 +21/01/2020,Bournemouth,Brighton,3,1,H,2,0,H,K Friend,11,21,3,8,5,8,9,12,1,1,0,0 +21/01/2020,Crystal Palace,Southampton,0,2,A,0,1,A,A Marriner,6,15,0,6,0,8,12,13,1,2,0,0 +21/01/2020,Everton,Newcastle,2,2,D,1,0,H,S Hooper,17,8,8,2,6,4,13,10,1,1,0,0 +21/01/2020,Sheffield United,Man City,0,1,A,0,0,D,L Mason,4,18,1,5,5,13,13,13,4,3,0,0 +21/01/2020,Chelsea,Arsenal,2,2,D,1,0,H,S Attwell,19,2,8,2,17,5,11,6,2,1,0,1 +22/01/2020,Leicester,West Ham,4,1,H,2,0,H,D Coote,19,6,8,2,7,8,10,19,1,1,0,0 +22/01/2020,Tottenham,Norwich,2,1,H,1,0,H,C Kavanagh,14,12,3,2,7,3,12,7,0,1,0,0 +22/01/2020,Man United,Burnley,0,2,A,0,1,A,J Moss,24,5,7,2,5,2,11,7,0,2,0,0 +23/01/2020,Wolves,Liverpool,1,2,A,0,1,A,M Oliver,10,13,3,6,2,4,6,11,0,1,0,0 +29/01/2020,West Ham,Liverpool,0,2,A,0,1,A,J Moss,7,13,4,5,3,7,6,6,2,0,0,0 +01/02/2020,Leicester,Chelsea,2,2,D,0,0,D,L Mason,14,7,4,3,4,6,16,14,2,2,0,0 +01/02/2020,Bournemouth,Aston Villa,2,1,H,2,0,H,A Taylor,16,18,6,5,1,8,10,15,1,3,1,0 +01/02/2020,Crystal Palace,Sheffield United,0,1,A,0,0,D,A Madley,13,10,3,3,9,5,9,15,3,2,0,0 +01/02/2020,Liverpool,Southampton,4,0,H,0,0,D,K Friend,16,17,9,4,6,11,9,15,0,2,0,0 +01/02/2020,Newcastle,Norwich,0,0,D,0,0,D,M Atkinson,15,19,5,4,8,12,10,15,2,1,0,0 +01/02/2020,Watford,Everton,2,3,A,2,2,D,C Pawson,11,12,2,5,4,4,17,14,2,2,0,1 +01/02/2020,West Ham,Brighton,3,3,D,2,0,H,M Oliver,12,19,6,6,4,6,8,9,1,1,0,0 +01/02/2020,Man United,Wolves,0,0,D,0,0,D,P Tierney,15,14,5,3,4,6,14,15,3,2,0,0 +02/02/2020,Burnley,Arsenal,0,0,D,0,0,D,C Kavanagh,15,13,2,2,5,7,8,11,1,3,0,0 +02/02/2020,Tottenham,Man City,2,0,H,0,0,D,M Dean,3,18,3,5,2,6,8,14,2,3,0,1 +08/02/2020,Everton,Crystal Palace,3,1,H,1,0,H,D Coote,14,10,8,2,4,6,11,12,0,1,0,0 +08/02/2020,Brighton,Watford,1,1,D,0,1,A,K Friend,10,5,2,2,3,2,9,12,2,2,0,0 +09/02/2020,Sheffield United,Bournemouth,2,1,H,1,1,D,J Moss,16,10,6,2,8,4,10,8,2,2,0,0 +14/02/2020,Wolves,Leicester,0,0,D,0,0,D,M Dean,15,9,3,3,7,0,13,10,2,2,0,1 +15/02/2020,Southampton,Burnley,1,2,A,1,1,D,S Hooper,10,9,2,6,4,3,8,11,2,2,0,0 +15/02/2020,Norwich,Liverpool,0,1,A,0,0,D,S Attwell,5,17,1,6,2,7,5,11,1,2,0,0 +16/02/2020,Aston Villa,Tottenham,2,3,A,1,2,A,M Atkinson,18,23,4,10,12,7,12,10,2,0,0,0 +16/02/2020,Arsenal,Newcastle,4,0,H,0,0,D,L Mason,15,10,7,2,5,6,15,9,2,0,0,0 +17/02/2020,Chelsea,Man United,0,2,A,0,1,A,A Taylor,17,9,1,3,9,8,11,11,4,3,0,0 +19/02/2020,Man City,West Ham,2,0,H,1,0,H,K Friend,20,3,7,0,6,1,5,7,0,1,0,0 +22/02/2020,Chelsea,Tottenham,2,1,H,1,0,H,M Oliver,17,5,7,3,3,5,14,14,1,2,0,0 +22/02/2020,Burnley,Bournemouth,3,0,H,0,0,D,M Dean,16,12,10,4,7,3,9,9,4,3,0,0 +22/02/2020,Crystal Palace,Newcastle,1,0,H,1,0,H,P Bankes,18,8,9,2,7,5,15,11,2,6,0,1 +22/02/2020,Sheffield United,Brighton,1,1,D,1,1,D,G Scott,14,8,3,2,12,2,3,6,0,2,0,0 +22/02/2020,Southampton,Aston Villa,2,0,H,1,0,H,C Pawson,28,4,9,1,9,6,13,17,1,4,0,0 +22/02/2020,Leicester,Man City,0,1,A,0,0,D,P Tierney,10,18,3,7,3,7,14,10,0,0,0,0 +23/02/2020,Man United,Watford,3,0,H,1,0,H,M Atkinson,21,7,9,2,3,5,6,15,0,1,0,0 +23/02/2020,Wolves,Norwich,3,0,H,2,0,H,C Kavanagh,19,6,6,4,4,1,11,6,2,3,0,0 +23/02/2020,Arsenal,Everton,3,2,H,2,2,D,S Attwell,9,17,4,5,6,3,12,12,0,4,0,0 +24/02/2020,Liverpool,West Ham,3,2,H,1,1,D,J Moss,25,7,7,4,16,8,4,10,0,3,0,0 +28/02/2020,Norwich,Leicester,1,0,H,0,0,D,C Pawson,11,19,6,4,6,7,13,16,1,2,0,0 +29/02/2020,Brighton,Crystal Palace,0,1,A,0,0,D,M Atkinson,24,12,8,3,8,7,8,9,1,1,0,0 +29/02/2020,Bournemouth,Chelsea,2,2,D,0,1,A,A Marriner,9,23,5,6,4,14,8,5,2,2,0,0 +29/02/2020,Newcastle,Burnley,0,0,D,0,0,D,A Madley,21,8,4,1,11,7,11,9,4,3,0,0 +29/02/2020,West Ham,Southampton,3,1,H,2,1,H,A Taylor,14,10,7,2,4,9,8,11,1,1,0,0 +29/02/2020,Watford,Liverpool,3,0,H,0,0,D,M Oliver,14,7,5,1,3,5,4,8,0,0,0,0 +01/03/2020,Everton,Man United,1,1,D,1,1,D,C Kavanagh,16,14,5,5,13,8,11,11,3,4,0,0 +01/03/2020,Tottenham,Wolves,2,3,A,2,1,H,S Attwell,13,14,5,4,4,5,12,14,3,2,0,0 +07/03/2020,Liverpool,Bournemouth,2,1,H,2,1,H,P Tierney,14,6,6,4,9,4,10,12,0,1,0,0 +07/03/2020,Arsenal,West Ham,1,0,H,0,0,D,M Atkinson,9,14,2,6,6,7,11,9,1,2,0,0 +07/03/2020,Crystal Palace,Watford,1,0,H,1,0,H,A Taylor,10,10,3,4,8,8,12,19,4,4,0,0 +07/03/2020,Sheffield United,Norwich,1,0,H,1,0,H,S Hooper,10,12,4,5,10,5,12,8,0,0,0,0 +07/03/2020,Southampton,Newcastle,0,1,A,0,0,D,G Scott,6,14,3,7,5,8,14,15,0,3,1,0 +07/03/2020,Wolves,Brighton,0,0,D,0,0,D,A Marriner,11,7,1,1,2,0,4,7,1,3,0,0 +07/03/2020,Burnley,Tottenham,1,1,D,1,0,H,J Moss,21,13,8,2,3,5,16,11,5,4,0,0 +08/03/2020,Chelsea,Everton,4,0,H,2,0,H,K Friend,17,3,11,1,6,1,8,10,1,2,0,0 +08/03/2020,Man United,Man City,2,0,H,1,0,H,M Dean,12,7,6,2,2,11,11,9,2,4,0,0 +09/03/2020,Leicester,Aston Villa,4,0,H,1,0,H,M Oliver,15,4,7,1,9,0,15,12,2,1,0,0 +17/06/2020,Aston Villa,Sheffield United,0,0,D,0,0,D,M Oliver,14,5,6,1,12,4,11,14,1,1,0,0 +17/06/2020,Man City,Arsenal,3,0,H,1,0,H,A Taylor,20,3,12,0,5,2,9,7,1,1,0,1 +19/06/2020,Norwich,Southampton,0,3,A,0,0,D,K Friend,9,22,1,9,9,7,9,15,1,1,0,0 +19/06/2020,Tottenham,Man United,1,1,D,1,0,H,J Moss,10,12,3,6,7,6,17,18,0,1,0,0 +20/06/2020,Watford,Leicester,1,1,D,0,0,D,C Pawson,8,15,4,2,2,7,15,12,0,1,0,0 +20/06/2020,Brighton,Arsenal,2,1,H,0,0,D,M Atkinson,9,13,5,6,10,7,13,8,2,1,0,0 +20/06/2020,West Ham,Wolves,0,2,A,0,0,D,A Taylor,7,10,2,6,5,7,8,7,1,1,0,0 +20/06/2020,Bournemouth,Crystal Palace,0,2,A,0,2,A,S Attwell,12,7,1,2,11,4,13,15,3,2,0,0 +21/06/2020,Newcastle,Sheffield United,3,0,H,0,0,D,D Coote,12,7,8,1,2,4,9,10,2,0,0,1 +21/06/2020,Aston Villa,Chelsea,1,2,A,1,0,H,P Tierney,8,19,4,5,2,10,9,17,2,1,0,0 +21/06/2020,Everton,Liverpool,0,0,D,0,0,D,M Dean,9,10,3,3,1,6,15,12,2,2,0,0 +22/06/2020,Man City,Burnley,5,0,H,3,0,H,A Marriner,19,1,7,0,3,1,8,7,1,1,0,0 +23/06/2020,Leicester,Brighton,0,0,D,0,0,D,L Mason,11,10,2,2,6,7,10,13,2,2,0,0 +23/06/2020,Tottenham,West Ham,2,0,H,0,0,D,C Pawson,15,10,4,2,9,4,12,7,2,2,0,0 +24/06/2020,Man United,Sheffield United,3,0,H,2,0,H,A Taylor,15,4,6,1,10,5,10,6,1,0,0,0 +24/06/2020,Newcastle,Aston Villa,1,1,D,0,0,D,C Kavanagh,13,14,3,2,6,9,15,13,2,2,0,0 +24/06/2020,Norwich,Everton,0,1,A,0,0,D,A Madley,9,12,1,5,6,5,8,13,0,1,0,0 +24/06/2020,Wolves,Bournemouth,1,0,H,0,0,D,M Oliver,10,4,2,0,6,4,11,11,3,3,0,0 +24/06/2020,Liverpool,Crystal Palace,4,0,H,2,0,H,M Atkinson,21,3,7,0,6,0,7,5,0,0,0,0 +25/06/2020,Burnley,Watford,1,0,H,0,0,D,M Dean,9,10,5,4,1,7,13,15,1,0,0,0 +25/06/2020,Southampton,Arsenal,0,2,A,0,1,A,G Scott,12,10,3,5,4,6,10,14,0,2,1,0 +25/06/2020,Chelsea,Man City,2,1,H,1,0,H,S Attwell,15,11,10,2,5,7,13,3,1,1,0,1 +27/06/2020,Aston Villa,Wolves,0,1,A,0,0,D,C Pawson,11,9,1,2,2,2,16,16,1,1,0,0 +28/06/2020,Watford,Southampton,1,3,A,0,1,A,M Oliver,8,11,0,7,5,3,18,9,3,0,0,0 +29/06/2020,Crystal Palace,Burnley,0,1,A,0,0,D,S Hooper,17,8,4,4,9,1,12,13,1,1,0,0 +30/06/2020,Brighton,Man United,0,3,A,0,2,A,A Marriner,8,12,2,6,4,5,10,9,0,1,0,0 +01/07/2020,Arsenal,Norwich,4,0,H,2,0,H,P Bankes,13,8,8,2,6,2,10,10,1,4,0,0 +01/07/2020,Bournemouth,Newcastle,1,4,A,0,2,A,K Friend,12,12,1,7,6,3,11,10,2,0,0,0 +01/07/2020,Everton,Leicester,2,1,H,2,0,H,D Coote,7,15,2,3,3,5,13,10,1,1,0,0 +01/07/2020,West Ham,Chelsea,3,2,H,1,1,D,M Atkinson,10,17,4,6,2,3,10,9,2,1,0,0 +02/07/2020,Sheffield United,Tottenham,3,1,H,1,0,H,C Kavanagh,7,9,5,2,1,6,13,13,2,0,0,0 +02/07/2020,Man City,Liverpool,4,0,H,3,0,H,A Taylor,14,11,6,3,3,3,8,7,2,2,0,0 +04/07/2020,Norwich,Brighton,0,1,A,0,1,A,S Attwell,12,8,1,2,0,2,11,13,2,2,0,0 +04/07/2020,Leicester,Crystal Palace,3,0,H,0,0,D,J Moss,15,7,4,1,3,7,18,8,2,1,0,0 +04/07/2020,Man United,Bournemouth,5,2,H,3,1,H,M Dean,19,7,10,3,8,3,13,12,0,1,0,0 +04/07/2020,Wolves,Arsenal,0,2,A,0,1,A,M Oliver,10,8,1,5,3,5,6,11,2,4,0,0 +04/07/2020,Chelsea,Watford,3,0,H,2,0,H,K Friend,21,7,9,3,5,3,9,12,0,2,0,0 +05/07/2020,Burnley,Sheffield United,1,1,D,1,0,H,P Bankes,9,10,4,3,3,9,8,14,0,0,0,0 +05/07/2020,Newcastle,West Ham,2,2,D,1,1,D,C Pawson,11,17,4,7,6,4,9,11,0,0,0,0 +05/07/2020,Liverpool,Aston Villa,2,0,H,0,0,D,P Tierney,6,9,4,3,7,5,18,8,1,1,0,0 +05/07/2020,Southampton,Man City,1,0,H,1,0,H,A Marriner,8,26,4,6,2,13,6,7,1,2,0,0 +06/07/2020,Tottenham,Everton,1,0,H,1,0,H,G Scott,12,11,2,3,5,6,14,18,3,2,0,0 +07/07/2020,Crystal Palace,Chelsea,2,3,A,1,2,A,D Coote,13,14,4,6,7,9,11,10,1,0,0,0 +07/07/2020,Watford,Norwich,2,1,H,1,1,D,A Taylor,9,12,4,3,4,3,11,15,2,4,0,0 +07/07/2020,Arsenal,Leicester,1,1,D,1,0,H,C Kavanagh,11,13,7,5,10,1,10,14,1,0,1,0 +08/07/2020,Man City,Newcastle,5,0,H,2,0,H,A Madley,23,6,9,1,8,1,12,7,0,0,0,0 +08/07/2020,Sheffield United,Wolves,1,0,H,0,0,D,M Atkinson,7,6,3,1,6,1,5,6,2,0,0,0 +08/07/2020,West Ham,Burnley,0,1,A,0,1,A,M Oliver,21,8,5,4,10,3,7,7,1,1,0,0 +08/07/2020,Brighton,Liverpool,1,3,A,1,2,A,C Pawson,12,19,2,8,6,10,7,15,1,4,0,0 +09/07/2020,Bournemouth,Tottenham,0,0,D,0,0,D,P Tierney,9,9,2,0,8,12,11,14,3,2,0,0 +09/07/2020,Everton,Southampton,1,1,D,1,1,D,L Mason,11,17,4,4,3,5,13,14,3,3,0,0 +09/07/2020,Aston Villa,Man United,0,3,A,0,2,A,J Moss,9,14,1,6,3,5,7,13,2,3,0,0 +11/07/2020,Norwich,West Ham,0,4,A,0,2,A,K Friend,11,19,2,7,1,6,9,8,1,0,0,0 +11/07/2020,Watford,Newcastle,2,1,H,0,1,A,C Pawson,17,8,5,4,2,4,23,15,1,4,0,0 +11/07/2020,Liverpool,Burnley,1,1,D,1,0,H,D Coote,23,6,9,2,12,6,7,4,1,2,0,0 +11/07/2020,Sheffield United,Chelsea,3,0,H,2,0,H,A Marriner,9,15,4,4,4,6,8,6,1,0,0,0 +11/07/2020,Brighton,Man City,0,5,A,0,2,A,G Scott,3,26,0,8,2,7,7,3,1,0,0,0 +12/07/2020,Wolves,Everton,3,0,H,1,0,H,A Taylor,14,6,7,2,5,2,9,9,0,2,0,0 +12/07/2020,Aston Villa,Crystal Palace,2,0,H,1,0,H,M Atkinson,12,9,9,5,5,1,21,21,2,4,0,1 +12/07/2020,Tottenham,Arsenal,2,1,H,1,1,D,M Oliver,15,13,9,4,6,5,16,11,5,3,0,0 +12/07/2020,Bournemouth,Leicester,4,1,H,0,1,A,S Attwell,10,13,4,5,2,6,11,12,2,1,0,1 +13/07/2020,Man United,Southampton,2,2,D,2,1,H,C Kavanagh,8,9,4,5,4,6,14,15,1,3,0,0 +14/07/2020,Chelsea,Norwich,1,0,H,1,0,H,J Moss,22,2,6,0,8,1,10,9,2,2,0,0 +15/07/2020,Burnley,Wolves,1,1,D,0,0,D,M Dean,9,14,1,5,3,8,10,8,0,0,0,0 +15/07/2020,Man City,Bournemouth,2,1,H,2,0,H,L Mason,8,15,4,3,3,5,10,10,1,1,0,0 +15/07/2020,Newcastle,Tottenham,1,3,A,0,1,A,D Coote,22,8,6,5,5,4,6,13,2,3,0,0 +15/07/2020,Arsenal,Liverpool,2,1,H,2,1,H,P Tierney,3,24,2,8,2,13,14,10,3,1,0,0 +16/07/2020,Everton,Aston Villa,1,1,D,0,0,D,A Taylor,9,15,1,1,5,5,15,13,2,1,0,0 +16/07/2020,Leicester,Sheffield United,2,0,H,1,0,H,M Oliver,15,5,7,1,9,5,12,10,1,2,0,0 +16/07/2020,Crystal Palace,Man United,0,2,A,0,1,A,G Scott,13,17,5,5,2,7,10,15,1,2,0,0 +16/07/2020,Southampton,Brighton,1,1,D,0,1,A,A Marriner,21,10,6,2,8,2,6,10,1,0,0,0 +17/07/2020,West Ham,Watford,3,1,H,3,0,H,M Atkinson,10,14,4,3,9,3,11,11,0,2,0,0 +18/07/2020,Norwich,Burnley,0,2,A,0,1,A,K Friend,6,23,2,8,6,8,9,16,0,0,2,0 +19/07/2020,Bournemouth,Southampton,0,2,A,0,1,A,C Pawson,11,16,3,8,12,6,12,11,2,2,0,0 +19/07/2020,Tottenham,Leicester,3,0,H,3,0,H,A Taylor,7,24,3,6,4,13,15,10,2,1,0,0 +20/07/2020,Brighton,Newcastle,0,0,D,0,0,D,S Hooper,11,12,3,1,9,7,13,12,4,2,0,0 +20/07/2020,Sheffield United,Everton,0,1,A,0,0,D,S Attwell,8,5,0,2,7,1,11,19,1,3,0,0 +20/07/2020,Wolves,Crystal Palace,2,0,H,1,0,H,P Bankes,11,7,5,3,5,2,11,15,2,1,0,0 +21/07/2020,Watford,Man City,0,4,A,0,2,A,M Oliver,2,26,0,10,0,8,14,11,2,0,0,0 +21/07/2020,Aston Villa,Arsenal,1,0,H,1,0,H,C Kavanagh,8,7,3,0,8,9,13,19,2,4,0,0 +22/07/2020,Man United,West Ham,1,1,D,0,1,A,P Tierney,11,12,4,3,2,3,10,9,3,1,0,0 +22/07/2020,Liverpool,Chelsea,5,3,H,3,1,H,A Marriner,10,10,7,5,6,0,8,11,1,0,0,0 +26/07/2020,Arsenal,Watford,3,2,H,3,1,H,M Dean,13,19,5,6,4,8,9,12,3,3,0,0 +26/07/2020,Burnley,Brighton,1,2,A,1,1,D,J Moss,13,14,2,4,6,7,11,11,3,0,0,0 +26/07/2020,Chelsea,Wolves,2,0,H,2,0,H,S Attwell,11,5,3,1,3,3,10,16,2,3,0,0 +26/07/2020,Crystal Palace,Tottenham,1,1,D,0,1,A,A Marriner,13,7,2,2,7,2,11,13,3,2,0,0 +26/07/2020,Everton,Bournemouth,1,3,A,1,2,A,C Kavanagh,13,13,5,7,2,5,11,9,1,0,0,0 +26/07/2020,Leicester,Man United,0,2,A,0,0,D,M Atkinson,14,7,3,3,3,3,12,11,1,4,1,0 +26/07/2020,Man City,Norwich,5,0,H,2,0,H,C Pawson,31,5,10,4,9,0,7,4,1,1,0,0 +26/07/2020,Newcastle,Liverpool,1,3,A,1,1,D,A Taylor,3,14,2,6,2,4,11,5,1,0,0,0 +26/07/2020,Southampton,Sheffield United,3,1,H,0,1,A,P Bankes,13,5,4,3,9,1,9,16,0,1,0,0 +26/07/2020,West Ham,Aston Villa,1,1,D,0,0,D,M Oliver,10,13,1,4,0,7,16,13,2,1,0,0 +12/09/2020,Fulham,Arsenal,0,3,A,0,1,A,C Kavanagh,5,13,2,6,2,3,12,12,2,2,0,0 +12/09/2020,Crystal Palace,Southampton,1,0,H,1,0,H,J Moss,5,9,3,5,7,3,14,11,2,1,0,0 +12/09/2020,Liverpool,Leeds,4,3,H,3,2,H,M Oliver,22,6,6,3,9,0,9,6,1,0,0,0 +12/09/2020,West Ham,Newcastle,0,2,A,0,0,D,S Attwell,15,15,3,2,8,7,13,7,2,2,0,0 +13/09/2020,West Brom,Leicester,0,3,A,0,0,D,A Taylor,7,13,1,7,2,5,12,9,1,1,0,0 +13/09/2020,Tottenham,Everton,0,1,A,0,0,D,M Atkinson,9,15,5,4,5,3,15,7,1,0,0,0 +14/09/2020,Brighton,Chelsea,1,3,A,0,1,A,C Pawson,13,10,3,5,4,3,8,13,1,0,0,0 +14/09/2020,Sheffield United,Wolves,0,2,A,0,2,A,M Dean,9,11,2,4,12,5,13,7,2,1,0,0 +19/09/2020,Everton,West Brom,5,2,H,2,1,H,M Dean,17,6,7,4,11,1,9,11,1,0,0,1 +19/09/2020,Leeds,Fulham,4,3,H,2,1,H,A Taylor,10,14,7,6,5,3,13,18,1,2,0,0 +19/09/2020,Man United,Crystal Palace,1,3,A,0,1,A,M Atkinson,13,14,4,5,9,3,13,10,2,1,0,0 +19/09/2020,Arsenal,West Ham,2,1,H,1,1,D,M Oliver,7,14,3,3,7,5,11,13,0,1,0,0 +20/09/2020,Southampton,Tottenham,2,5,A,1,1,D,D Coote,14,9,7,6,3,2,16,18,4,3,0,0 +20/09/2020,Newcastle,Brighton,0,3,A,0,2,A,K Friend,6,13,0,6,7,1,16,15,3,0,0,1 +20/09/2020,Chelsea,Liverpool,0,2,A,0,0,D,P Tierney,5,18,3,6,1,11,10,6,0,0,1,0 +20/09/2020,Leicester,Burnley,4,2,H,1,1,D,L Mason,14,16,6,5,15,4,11,13,1,2,0,0 +21/09/2020,Aston Villa,Sheffield United,1,0,H,0,0,D,G Scott,18,4,2,1,10,4,12,13,3,1,0,1 +21/09/2020,Wolves,Man City,1,3,A,0,2,A,A Marriner,10,14,1,9,4,6,6,8,0,3,0,0 +26/09/2020,Brighton,Man United,2,3,A,1,1,D,C Kavanagh,18,7,5,3,7,1,18,16,4,2,0,0 +26/09/2020,Crystal Palace,Everton,1,2,A,1,2,A,K Friend,8,10,1,5,7,6,16,15,3,1,0,0 +26/09/2020,West Brom,Chelsea,3,3,D,3,0,H,J Moss,9,22,3,10,3,9,8,16,1,3,0,0 +26/09/2020,Burnley,Southampton,0,1,A,0,1,A,A Marriner,10,5,2,1,8,2,8,10,1,0,0,0 +27/09/2020,Sheffield United,Leeds,0,1,A,0,0,D,P Tierney,14,17,4,9,5,7,18,4,2,1,0,0 +27/09/2020,Tottenham,Newcastle,1,1,D,1,0,H,P Bankes,23,6,12,1,10,4,15,9,1,4,0,0 +27/09/2020,Man City,Leicester,2,5,A,1,1,D,M Oliver,16,7,5,7,3,4,13,8,1,3,0,0 +27/09/2020,West Ham,Wolves,4,0,H,1,0,H,M Atkinson,15,11,7,2,1,3,7,11,0,1,0,0 +28/09/2020,Fulham,Aston Villa,0,3,A,0,2,A,S Attwell,16,12,3,5,5,3,18,14,6,3,0,0 +28/09/2020,Liverpool,Arsenal,3,1,H,2,1,H,C Pawson,21,4,8,3,7,3,11,7,2,2,0,0 +03/10/2020,Chelsea,Crystal Palace,4,0,H,0,0,D,M Oliver,17,4,6,0,8,5,17,13,2,0,0,0 +03/10/2020,Everton,Brighton,4,2,H,2,1,H,S Hooper,11,11,6,3,3,10,13,14,0,1,0,0 +03/10/2020,Leeds,Man City,1,1,D,0,1,A,M Dean,12,23,7,2,7,10,11,12,2,1,0,0 +03/10/2020,Newcastle,Burnley,3,1,H,1,0,H,D Coote,10,7,5,3,3,4,13,17,2,3,0,0 +04/10/2020,Leicester,West Ham,0,3,A,0,2,A,A Madley,4,14,0,6,6,4,11,8,2,2,0,0 +04/10/2020,Southampton,West Brom,2,0,H,1,0,H,C Kavanagh,13,5,7,2,5,1,19,10,2,1,0,0 +04/10/2020,Arsenal,Sheffield United,2,1,H,0,0,D,L Mason,6,6,5,2,2,4,3,9,0,1,0,0 +04/10/2020,Wolves,Fulham,1,0,H,0,0,D,D England,14,10,5,2,6,2,15,8,1,1,0,0 +04/10/2020,Man United,Tottenham,1,6,A,1,4,A,A Taylor,5,22,2,8,2,4,14,10,2,1,1,0 +04/10/2020,Aston Villa,Liverpool,7,2,H,4,1,H,M Atkinson,18,14,11,8,2,7,7,10,2,1,0,0 +17/10/2020,Everton,Liverpool,2,2,D,1,1,D,M Oliver,11,22,5,8,2,5,15,9,3,2,1,0 +17/10/2020,Chelsea,Southampton,3,3,D,2,1,H,P Bankes,11,13,5,6,6,2,11,9,1,1,0,0 +17/10/2020,Man City,Arsenal,1,0,H,1,0,H,C Kavanagh,13,11,5,3,6,6,15,10,4,1,0,0 +17/10/2020,Newcastle,Man United,1,4,A,1,1,D,C Pawson,7,28,4,14,0,7,9,12,1,1,0,0 +18/10/2020,Sheffield United,Fulham,1,1,D,0,0,D,A Marriner,10,15,6,6,2,5,5,9,0,2,0,0 +18/10/2020,Crystal Palace,Brighton,1,1,D,1,0,H,S Attwell,1,20,1,3,2,5,13,12,3,2,0,1 +18/10/2020,Tottenham,West Ham,3,3,D,3,0,H,P Tierney,11,13,6,4,3,7,13,12,0,5,0,0 +18/10/2020,Leicester,Aston Villa,0,1,A,0,0,D,J Moss,11,10,5,4,2,7,18,18,4,3,0,0 +19/10/2020,West Brom,Burnley,0,0,D,0,0,D,A Taylor,13,9,6,4,8,2,15,17,2,2,0,0 +19/10/2020,Leeds,Wolves,0,1,A,0,0,D,D Coote,13,7,2,3,8,3,13,12,2,1,0,0 +23/10/2020,Aston Villa,Leeds,0,3,A,0,0,D,P Tierney,12,27,4,9,6,5,14,17,2,2,0,0 +24/10/2020,West Ham,Man City,1,1,D,1,0,H,A Taylor,6,14,2,7,2,10,14,6,2,0,0,0 +24/10/2020,Fulham,Crystal Palace,1,2,A,0,1,A,G Scott,16,14,3,10,4,4,11,11,0,1,1,0 +24/10/2020,Man United,Chelsea,0,0,D,0,0,D,M Atkinson,14,6,4,1,9,6,8,6,3,1,0,0 +24/10/2020,Liverpool,Sheffield United,2,1,H,1,1,D,M Dean,17,13,5,2,7,3,7,15,0,3,0,0 +25/10/2020,Southampton,Everton,2,0,H,2,0,H,K Friend,12,6,6,2,4,5,11,11,1,2,0,1 +25/10/2020,Wolves,Newcastle,1,1,D,0,0,D,L Mason,16,5,3,2,2,3,10,10,0,1,0,0 +25/10/2020,Arsenal,Leicester,0,1,A,0,0,D,C Pawson,12,6,4,2,9,3,13,9,3,5,0,0 +26/10/2020,Brighton,West Brom,1,1,D,1,0,H,J Moss,5,9,3,3,6,2,14,7,2,0,0,0 +26/10/2020,Burnley,Tottenham,0,1,A,0,0,D,M Oliver,13,9,4,3,4,5,9,7,2,0,0,0 +30/10/2020,Wolves,Crystal Palace,2,0,H,2,0,H,M Atkinson,15,11,5,2,7,6,13,4,0,2,0,1 +31/10/2020,Sheffield United,Man City,0,1,A,0,1,A,M Oliver,5,16,1,8,2,5,14,6,0,0,0,0 +31/10/2020,Burnley,Chelsea,0,3,A,0,1,A,D Coote,5,14,0,9,3,4,10,15,1,0,0,0 +31/10/2020,Liverpool,West Ham,2,1,H,1,1,D,K Friend,9,4,5,3,1,2,13,14,0,1,0,0 +01/11/2020,Aston Villa,Southampton,3,4,A,0,3,A,D England,19,9,10,4,11,1,12,17,1,2,0,0 +01/11/2020,Newcastle,Everton,2,1,H,0,0,D,S Attwell,11,15,4,4,5,4,9,10,2,4,0,0 +01/11/2020,Man United,Arsenal,0,1,A,0,0,D,M Dean,8,7,2,2,6,3,12,12,3,3,0,0 +01/11/2020,Tottenham,Brighton,2,1,H,1,0,H,G Scott,9,6,3,2,4,5,14,13,2,1,0,0 +02/11/2020,Fulham,West Brom,2,0,H,2,0,H,S Hooper,13,10,6,1,5,2,13,11,3,2,0,0 +02/11/2020,Leeds,Leicester,1,4,A,0,2,A,A Marriner,11,10,4,8,10,3,9,5,1,1,0,0 +06/11/2020,Brighton,Burnley,0,0,D,0,0,D,M Dean,19,4,3,1,5,3,11,5,1,0,0,0 +06/11/2020,Southampton,Newcastle,2,0,H,1,0,H,P Bankes,15,4,8,2,8,3,12,11,1,2,0,0 +07/11/2020,Everton,Man United,1,3,A,1,2,A,P Tierney,7,13,1,5,5,2,12,19,4,2,0,0 +07/11/2020,Crystal Palace,Leeds,4,1,H,3,1,H,C Kavanagh,10,9,6,3,3,6,9,1,0,2,0,0 +07/11/2020,Chelsea,Sheffield United,4,1,H,2,1,H,J Moss,20,6,9,3,6,7,5,14,0,2,0,0 +07/11/2020,West Ham,Fulham,1,0,H,0,0,D,R Jones,16,13,7,3,9,5,10,9,2,2,0,0 +08/11/2020,West Brom,Tottenham,0,1,A,0,0,D,A Madley,12,19,2,5,9,6,17,12,1,0,0,0 +08/11/2020,Leicester,Wolves,1,0,H,1,0,H,A Taylor,10,7,3,2,6,3,12,8,2,3,0,0 +08/11/2020,Man City,Liverpool,1,1,D,1,1,D,C Pawson,7,10,2,3,1,2,19,11,3,1,0,0 +08/11/2020,Arsenal,Aston Villa,0,3,A,0,1,A,M Atkinson,13,15,2,6,3,8,11,13,0,0,0,0 +21/11/2020,Newcastle,Chelsea,0,2,A,0,1,A,C Pawson,8,14,1,3,7,4,11,12,2,0,0,0 +21/11/2020,Aston Villa,Brighton,1,2,A,0,1,A,M Oliver,15,7,4,3,12,3,5,18,2,1,0,1 +21/11/2020,Tottenham,Man City,2,0,H,1,0,H,M Dean,4,22,2,5,0,10,13,19,2,2,0,0 +21/11/2020,Man United,West Brom,1,0,H,0,0,D,D Coote,17,7,7,2,8,2,12,17,1,1,0,0 +22/11/2020,Fulham,Everton,2,3,A,1,3,A,A Madley,14,9,6,7,2,5,14,10,2,0,0,0 +22/11/2020,Sheffield United,West Ham,0,1,A,0,0,D,M Atkinson,9,15,5,3,2,6,7,6,0,0,0,0 +22/11/2020,Leeds,Arsenal,0,0,D,0,0,D,A Taylor,25,9,4,2,5,3,9,8,3,0,0,1 +22/11/2020,Liverpool,Leicester,3,0,H,2,0,H,C Kavanagh,24,11,13,4,10,3,15,6,0,2,0,0 +23/11/2020,Burnley,Crystal Palace,1,0,H,1,0,H,K Friend,10,15,4,5,4,7,13,9,2,0,0,0 +23/11/2020,Wolves,Southampton,1,1,D,0,0,D,A Marriner,20,7,9,2,7,4,14,13,2,0,0,0 +27/11/2020,Crystal Palace,Newcastle,0,2,A,0,0,D,G Scott,12,14,3,7,7,3,9,10,2,2,0,0 +28/11/2020,Brighton,Liverpool,1,1,D,0,0,D,S Attwell,11,6,3,2,4,3,9,13,2,1,0,0 +28/11/2020,Man City,Burnley,5,0,H,3,0,H,L Mason,19,9,6,1,6,4,12,4,0,0,0,0 +28/11/2020,Everton,Leeds,0,1,A,0,0,D,C Kavanagh,15,23,8,7,4,2,8,14,1,2,0,0 +28/11/2020,West Brom,Sheffield United,1,0,H,1,0,H,M Dean,17,21,6,5,14,9,10,9,2,3,0,0 +29/11/2020,Southampton,Man United,2,3,A,2,0,H,J Moss,10,15,5,6,5,9,12,12,1,0,0,0 +29/11/2020,Chelsea,Tottenham,0,0,D,0,0,D,P Tierney,13,5,3,1,3,3,20,9,4,2,0,0 +29/11/2020,Arsenal,Wolves,1,2,A,1,2,A,M Oliver,13,11,2,5,8,4,13,13,3,4,0,0 +30/11/2020,Leicester,Fulham,1,2,A,0,2,A,S Hooper,16,10,3,5,6,5,9,21,0,2,0,0 +30/11/2020,West Ham,Aston Villa,2,1,H,1,1,D,P Bankes,6,16,2,6,3,7,20,7,1,1,0,0 +05/12/2020,Burnley,Everton,1,1,D,1,1,D,A Taylor,8,13,3,6,8,4,6,9,0,0,0,0 +05/12/2020,Man City,Fulham,2,0,H,2,0,H,J Moss,16,6,6,1,9,3,7,6,0,0,0,0 +05/12/2020,West Ham,Man United,1,3,A,1,0,H,A Marriner,19,15,6,6,5,4,9,10,0,0,0,0 +05/12/2020,Chelsea,Leeds,3,1,H,1,1,D,K Friend,23,8,11,3,8,4,12,9,0,2,0,0 +06/12/2020,West Brom,Crystal Palace,1,5,A,1,1,D,P Tierney,9,18,4,6,4,6,10,16,2,2,1,0 +06/12/2020,Sheffield United,Leicester,1,2,A,1,1,D,S Attwell,4,13,1,4,6,5,13,6,1,3,0,0 +06/12/2020,Tottenham,Arsenal,2,0,H,2,0,H,M Atkinson,6,11,3,2,3,9,16,12,1,2,0,0 +06/12/2020,Liverpool,Wolves,4,0,H,1,0,H,C Pawson,11,9,6,3,4,6,8,14,1,1,0,0 +07/12/2020,Brighton,Southampton,1,2,A,1,1,D,D Coote,12,10,4,3,4,3,12,12,3,3,0,0 +11/12/2020,Leeds,West Ham,1,2,A,1,1,D,M Oliver,13,19,7,8,5,6,12,10,1,2,0,0 +12/12/2020,Wolves,Aston Villa,0,1,A,0,0,D,M Dean,16,13,7,2,5,8,23,15,2,5,0,0 +12/12/2020,Newcastle,West Brom,2,1,H,1,0,H,D England,12,13,7,3,4,3,9,12,2,3,0,0 +12/12/2020,Man United,Man City,0,0,D,0,0,D,C Kavanagh,11,9,2,2,4,3,13,6,1,1,0,0 +12/12/2020,Everton,Chelsea,1,0,H,1,0,H,J Moss,9,10,4,3,6,6,10,11,2,3,0,0 +13/12/2020,Southampton,Sheffield United,3,0,H,1,0,H,A Madley,16,3,6,0,8,4,8,14,0,1,0,0 +13/12/2020,Crystal Palace,Tottenham,1,1,D,0,1,A,K Friend,16,14,5,6,4,7,15,11,1,1,0,0 +13/12/2020,Fulham,Liverpool,1,1,D,1,0,H,A Marriner,10,12,5,6,6,8,9,5,3,1,0,0 +13/12/2020,Arsenal,Burnley,0,1,A,0,0,D,G Scott,18,10,6,2,13,4,5,11,2,1,1,0 +13/12/2020,Leicester,Brighton,3,0,H,3,0,H,M Atkinson,15,12,6,4,9,5,9,11,2,1,0,0 +15/12/2020,Wolves,Chelsea,2,1,H,0,0,D,S Attwell,12,13,4,3,5,6,12,12,2,2,0,0 +15/12/2020,Man City,West Brom,1,1,D,1,1,D,P Bankes,26,5,7,1,13,2,5,8,2,3,0,0 +16/12/2020,Arsenal,Southampton,1,1,D,0,1,A,P Tierney,9,13,4,3,2,7,13,13,1,3,1,0 +16/12/2020,Leeds,Newcastle,5,2,H,1,1,D,S Hooper,25,10,10,4,7,4,10,13,3,1,0,0 +16/12/2020,Leicester,Everton,0,2,A,0,1,A,L Mason,14,11,2,6,4,6,12,9,2,3,0,0 +16/12/2020,Fulham,Brighton,0,0,D,0,0,D,R Jones,9,9,4,4,1,8,15,11,1,0,0,0 +16/12/2020,Liverpool,Tottenham,2,1,H,1,1,D,A Taylor,17,8,11,2,7,4,8,9,0,2,0,0 +16/12/2020,West Ham,Crystal Palace,1,1,D,0,1,A,D Coote,9,8,2,2,5,3,8,12,0,1,0,1 +17/12/2020,Aston Villa,Burnley,0,0,D,0,0,D,C Pawson,27,6,7,4,11,1,17,19,1,1,0,0 +17/12/2020,Sheffield United,Man United,2,3,A,1,2,A,M Oliver,12,14,6,8,6,3,17,8,3,2,0,0 +19/12/2020,Crystal Palace,Liverpool,0,7,A,0,3,A,J Moss,5,14,3,8,5,4,5,11,1,0,0,0 +19/12/2020,Southampton,Man City,0,1,A,0,1,A,M Dean,9,11,3,5,6,6,9,11,2,2,0,0 +19/12/2020,Everton,Arsenal,2,1,H,2,1,H,A Marriner,9,13,2,2,4,3,9,9,1,3,0,0 +19/12/2020,Newcastle,Fulham,1,1,D,0,1,A,G Scott,12,11,5,4,2,9,7,17,1,2,0,1 +20/12/2020,Brighton,Sheffield United,1,1,D,0,0,D,P Bankes,21,5,5,3,14,2,9,11,1,5,0,1 +20/12/2020,Tottenham,Leicester,0,2,A,0,1,A,C Pawson,8,17,3,4,5,3,15,14,2,2,0,0 +20/12/2020,Man United,Leeds,6,2,H,4,1,H,A Taylor,26,17,14,3,11,13,6,6,1,0,0,0 +20/12/2020,West Brom,Aston Villa,0,3,A,0,1,A,M Atkinson,1,19,1,10,1,7,14,8,1,2,1,0 +21/12/2020,Burnley,Wolves,2,1,H,1,0,H,L Mason,10,15,4,3,1,7,11,15,2,1,0,0 +21/12/2020,Chelsea,West Ham,3,0,H,1,0,H,C Kavanagh,11,6,7,0,5,4,11,6,0,0,0,0 +26/12/2020,Leicester,Man United,2,2,D,1,1,D,M Dean,10,9,2,4,5,0,10,17,2,1,0,0 +26/12/2020,Aston Villa,Crystal Palace,3,0,H,1,0,H,A Taylor,21,13,10,6,4,4,18,16,1,4,1,0 +26/12/2020,Fulham,Southampton,0,0,D,0,0,D,D England,10,3,1,2,6,3,15,12,2,0,0,0 +26/12/2020,Arsenal,Chelsea,3,1,H,2,0,H,M Oliver,15,19,7,3,7,9,13,10,2,1,0,0 +26/12/2020,Man City,Newcastle,2,0,H,1,0,H,A Marriner,11,7,6,2,7,3,5,12,2,2,0,0 +26/12/2020,Sheffield United,Everton,0,1,A,0,0,D,D Coote,10,7,2,3,6,3,11,10,2,4,0,0 +27/12/2020,Leeds,Burnley,1,0,H,1,0,H,R Jones,11,14,4,6,4,12,8,14,0,3,0,0 +27/12/2020,West Ham,Brighton,2,2,D,0,1,A,S Hooper,11,13,4,4,5,4,6,8,0,0,0,0 +27/12/2020,Liverpool,West Brom,1,1,D,1,0,H,K Friend,17,5,2,3,8,3,8,5,0,1,0,0 +27/12/2020,Wolves,Tottenham,1,1,D,0,1,A,P Tierney,11,6,6,3,9,2,20,13,4,1,0,0 +28/12/2020,Crystal Palace,Leicester,1,1,D,0,0,D,G Scott,4,17,1,3,2,5,10,7,0,1,0,0 +28/12/2020,Chelsea,Aston Villa,1,1,D,1,0,H,S Attwell,16,10,5,2,11,4,16,3,3,1,0,0 +29/12/2020,Brighton,Arsenal,0,1,A,0,0,D,M Atkinson,13,11,2,3,5,4,11,4,2,0,0,0 +29/12/2020,Burnley,Sheffield United,1,0,H,1,0,H,C Kavanagh,5,7,3,3,3,5,13,17,1,2,0,0 +29/12/2020,Southampton,West Ham,0,0,D,0,0,D,A Madley,8,8,3,3,4,3,16,15,3,1,0,0 +29/12/2020,West Brom,Leeds,0,5,A,0,4,A,L Mason,4,14,1,6,3,10,7,10,0,1,0,0 +29/12/2020,Man United,Wolves,1,0,H,0,0,D,J Moss,11,9,3,5,2,2,11,7,1,1,0,0 +30/12/2020,Newcastle,Liverpool,0,0,D,0,0,D,P Tierney,8,11,2,4,5,12,9,13,2,3,0,0 +01/01/2021,Everton,West Ham,0,1,A,0,0,D,K Friend,10,11,2,5,5,4,9,7,2,0,0,0 +01/01/2021,Man United,Aston Villa,2,1,H,1,0,H,M Oliver,19,5,9,5,5,10,22,10,4,0,0,0 +02/01/2021,Tottenham,Leeds,3,0,H,2,0,H,D Coote,20,18,7,5,3,5,15,13,2,1,1,0 +02/01/2021,Crystal Palace,Sheffield United,2,0,H,2,0,H,S Attwell,9,7,4,2,3,6,8,18,1,3,0,0 +02/01/2021,Brighton,Wolves,3,3,D,1,3,A,A Madley,13,11,4,4,5,8,13,8,2,1,0,0 +02/01/2021,West Brom,Arsenal,0,4,A,0,2,A,M Atkinson,7,21,3,12,3,5,7,4,1,2,0,0 +03/01/2021,Newcastle,Leicester,1,2,A,0,0,D,R Jones,8,9,2,3,3,6,10,11,0,2,0,0 +03/01/2021,Chelsea,Man City,1,3,A,0,3,A,A Taylor,9,18,2,6,5,3,11,10,3,1,0,0 +04/01/2021,Southampton,Liverpool,1,0,H,1,0,H,A Marriner,7,17,3,1,1,10,5,12,1,3,0,0 +12/01/2021,Sheffield United,Newcastle,1,0,H,0,0,D,A Madley,17,7,4,3,5,2,13,12,2,3,0,1 +12/01/2021,Burnley,Man United,0,1,A,0,0,D,K Friend,11,13,0,7,3,4,15,13,1,2,0,0 +12/01/2021,Wolves,Everton,1,2,A,1,1,D,M Atkinson,12,5,3,3,6,5,6,11,2,2,0,0 +13/01/2021,Man City,Brighton,1,0,H,1,0,H,D England,16,5,6,1,8,4,4,12,0,2,0,0 +13/01/2021,Tottenham,Fulham,1,1,D,1,0,H,P Tierney,15,15,6,4,6,5,13,15,1,2,0,0 +14/01/2021,Arsenal,Crystal Palace,0,0,D,0,0,D,A Marriner,11,12,4,2,7,3,10,11,1,1,0,0 +16/01/2021,Wolves,West Brom,2,3,A,2,1,H,M Oliver,23,12,6,5,11,0,10,14,2,1,0,0 +16/01/2021,Leeds,Brighton,0,1,A,0,1,A,K Friend,7,7,2,2,5,2,8,9,1,1,0,0 +16/01/2021,West Ham,Burnley,1,0,H,1,0,H,C Kavanagh,15,10,4,2,7,4,12,9,2,1,0,0 +16/01/2021,Fulham,Chelsea,0,1,A,0,0,D,P Bankes,10,21,3,6,1,12,11,10,2,3,1,0 +16/01/2021,Leicester,Southampton,2,0,H,1,0,H,S Attwell,16,8,5,3,2,5,10,11,2,2,0,0 +17/01/2021,Sheffield United,Tottenham,1,3,A,0,2,A,A Marriner,15,14,5,5,5,7,13,4,4,0,0,0 +17/01/2021,Liverpool,Man United,0,0,D,0,0,D,P Tierney,17,8,3,4,7,3,15,6,2,1,0,0 +17/01/2021,Man City,Crystal Palace,4,0,H,1,0,H,L Mason,13,2,6,0,11,1,9,8,0,0,0,0 +18/01/2021,Arsenal,Newcastle,3,0,H,0,0,D,D Coote,20,4,6,1,7,2,9,8,0,0,0,0 +19/01/2021,West Ham,West Brom,2,1,H,1,0,H,G Scott,14,6,6,2,3,1,10,10,0,1,0,0 +19/01/2021,Leicester,Chelsea,2,0,H,2,0,H,C Pawson,8,9,6,5,6,3,7,16,1,3,0,0 +20/01/2021,Man City,Aston Villa,2,0,H,0,0,D,J Moss,28,11,9,4,16,2,10,9,0,2,0,0 +20/01/2021,Fulham,Man United,1,2,A,1,1,D,M Atkinson,12,15,5,5,2,8,6,10,3,1,0,0 +21/01/2021,Liverpool,Burnley,0,1,A,0,0,D,M Dean,27,6,6,4,12,0,11,8,2,1,0,0 +23/01/2021,Aston Villa,Newcastle,2,0,H,2,0,H,S Hooper,14,7,5,1,5,4,9,18,0,3,0,0 +26/01/2021,Crystal Palace,West Ham,2,3,A,1,2,A,S Attwell,6,17,3,7,3,4,9,8,0,0,0,0 +26/01/2021,Newcastle,Leeds,1,2,A,0,1,A,A Taylor,22,9,5,2,9,5,10,15,1,3,0,0 +26/01/2021,Southampton,Arsenal,1,3,A,1,2,A,K Friend,13,9,5,5,4,5,11,6,1,2,0,0 +26/01/2021,West Brom,Man City,0,5,A,0,4,A,C Kavanagh,4,18,1,7,3,8,8,8,2,0,0,0 +27/01/2021,Burnley,Aston Villa,3,2,H,0,1,A,P Tierney,10,18,5,10,2,4,16,7,1,0,0,0 +27/01/2021,Chelsea,Wolves,0,0,D,0,0,D,A Madley,14,4,5,0,13,1,14,7,1,1,0,0 +27/01/2021,Brighton,Fulham,0,0,D,0,0,D,C Pawson,16,10,5,3,10,0,10,14,2,1,0,0 +27/01/2021,Everton,Leicester,1,1,D,1,0,H,M Dean,8,19,2,6,5,11,12,10,1,1,0,0 +27/01/2021,Man United,Sheffield United,1,2,A,0,1,A,P Bankes,16,5,4,3,7,2,10,12,1,1,0,0 +28/01/2021,Tottenham,Liverpool,1,3,A,0,1,A,M Atkinson,3,14,2,7,1,2,9,11,1,2,0,0 +30/01/2021,Everton,Newcastle,0,2,A,0,0,D,S Attwell,11,13,3,3,7,12,13,6,3,4,0,0 +30/01/2021,Crystal Palace,Wolves,1,0,H,0,0,D,S Hooper,11,6,2,4,1,6,7,10,1,2,0,0 +30/01/2021,Man City,Sheffield United,1,0,H,1,0,H,D Coote,10,4,5,1,10,0,7,10,0,3,0,0 +30/01/2021,West Brom,Fulham,2,2,D,0,1,A,A Taylor,5,12,3,7,2,3,9,10,1,0,0,0 +30/01/2021,Arsenal,Man United,0,0,D,0,0,D,M Oliver,17,14,3,3,4,7,9,16,1,3,0,0 +30/01/2021,Southampton,Aston Villa,0,1,A,0,1,A,L Mason,20,8,5,4,7,1,12,11,1,0,0,0 +31/01/2021,Chelsea,Burnley,2,0,H,1,0,H,G Scott,19,1,8,0,4,2,7,9,0,1,0,0 +31/01/2021,Leicester,Leeds,1,3,A,1,1,D,C Kavanagh,18,8,8,7,6,4,17,16,0,3,0,0 +31/01/2021,West Ham,Liverpool,1,3,A,0,0,D,J Moss,8,14,2,5,6,5,7,8,2,0,0,0 +31/01/2021,Brighton,Tottenham,1,0,H,1,0,H,P Bankes,16,8,5,4,9,2,11,11,2,1,0,0 +02/02/2021,Sheffield United,West Brom,2,1,H,0,1,A,P Tierney,14,8,6,4,9,2,11,9,2,1,0,0 +02/02/2021,Wolves,Arsenal,2,1,H,1,1,D,C Pawson,11,9,7,3,9,6,11,8,3,2,0,2 +02/02/2021,Man United,Southampton,9,0,H,4,0,H,M Dean,24,3,14,1,5,3,11,6,2,3,0,2 +02/02/2021,Newcastle,Crystal Palace,1,2,A,1,2,A,D England,21,6,5,3,9,1,14,11,1,3,0,0 +03/02/2021,Burnley,Man City,0,2,A,0,2,A,M Atkinson,2,17,0,6,1,3,8,5,2,1,0,0 +03/02/2021,Fulham,Leicester,0,2,A,0,2,A,R Jones,8,9,1,5,5,2,17,10,3,1,0,0 +03/02/2021,Leeds,Everton,1,2,A,0,2,A,M Oliver,16,10,7,6,7,1,12,12,2,3,0,0 +03/02/2021,Aston Villa,West Ham,1,3,A,0,0,D,A Madley,9,20,2,10,7,3,11,15,1,1,0,0 +03/02/2021,Liverpool,Brighton,0,1,A,0,0,D,K Friend,11,13,1,4,6,3,12,6,1,0,0,0 +04/02/2021,Tottenham,Chelsea,0,1,A,0,1,A,A Marriner,7,18,2,2,2,7,20,14,2,3,0,0 +06/02/2021,Aston Villa,Arsenal,1,0,H,1,0,H,C Kavanagh,12,14,8,3,3,7,12,12,3,2,0,0 +06/02/2021,Burnley,Brighton,1,1,D,0,1,A,A Taylor,20,10,7,2,8,7,8,10,2,0,0,0 +06/02/2021,Newcastle,Southampton,3,2,H,3,1,H,C Pawson,6,14,4,5,0,7,11,14,2,1,1,0 +06/02/2021,Fulham,West Ham,0,0,D,0,0,D,M Dean,20,8,2,1,7,3,11,10,1,2,0,1 +06/02/2021,Man United,Everton,3,3,D,2,0,H,J Moss,14,6,5,3,5,1,14,10,2,1,0,0 +07/02/2021,Tottenham,West Brom,2,0,H,0,0,D,S Attwell,13,4,6,1,8,0,16,12,1,2,0,0 +07/02/2021,Wolves,Leicester,0,0,D,0,0,D,M Atkinson,13,13,1,3,8,2,11,15,1,3,0,0 +07/02/2021,Liverpool,Man City,1,4,A,0,0,D,M Oliver,8,8,3,5,6,1,13,8,2,1,0,0 +07/02/2021,Sheffield United,Chelsea,1,2,A,0,1,A,K Friend,8,9,3,3,1,8,8,8,1,0,0,0 +08/02/2021,Leeds,Crystal Palace,2,0,H,1,0,H,A Marriner,17,8,7,3,5,3,7,8,0,2,0,0 +13/02/2021,Leicester,Liverpool,3,1,H,0,0,D,A Taylor,11,15,6,4,2,12,9,6,1,2,0,0 +13/02/2021,Crystal Palace,Burnley,0,3,A,0,2,A,M Oliver,13,16,3,4,7,4,15,14,0,0,0,0 +13/02/2021,Man City,Tottenham,3,0,H,1,0,H,P Tierney,15,7,6,3,4,2,12,13,1,3,0,0 +13/02/2021,Brighton,Aston Villa,0,0,D,0,0,D,D England,26,4,9,1,9,2,16,12,2,3,0,0 +14/02/2021,Southampton,Wolves,1,2,A,1,0,H,G Scott,7,10,3,4,4,6,10,9,2,2,0,0 +14/02/2021,West Brom,Man United,1,1,D,1,1,D,C Pawson,7,10,3,7,4,5,13,10,5,0,0,0 +14/02/2021,Arsenal,Leeds,4,2,H,3,0,H,S Attwell,13,9,5,5,4,5,8,17,0,2,0,0 +14/02/2021,Everton,Fulham,0,2,A,0,0,D,A Madley,7,12,2,3,5,4,7,16,1,1,0,0 +15/02/2021,West Ham,Sheffield United,3,0,H,1,0,H,S Hooper,13,8,9,5,7,7,11,12,0,2,0,0 +15/02/2021,Chelsea,Newcastle,2,0,H,2,0,H,P Bankes,18,10,5,4,10,5,9,13,0,0,0,0 +17/02/2021,Burnley,Fulham,1,1,D,0,0,D,J Moss,8,8,3,3,5,5,7,13,1,1,0,0 +17/02/2021,Everton,Man City,1,3,A,1,1,D,A Marriner,3,16,2,7,0,10,8,8,2,1,0,0 +19/02/2021,Wolves,Leeds,1,0,H,0,0,D,D Coote,15,18,5,7,7,9,13,8,1,0,0,0 +20/02/2021,Southampton,Chelsea,1,1,D,1,0,H,A Taylor,4,9,1,3,3,6,10,10,1,1,0,0 +20/02/2021,Burnley,West Brom,0,0,D,0,0,D,M Dean,6,10,1,3,5,5,16,10,3,0,0,1 +20/02/2021,Liverpool,Everton,0,2,A,0,1,A,C Kavanagh,15,9,6,6,7,2,10,10,2,1,0,0 +20/02/2021,Fulham,Sheffield United,1,0,H,0,0,D,M Atkinson,15,4,7,2,6,4,11,14,1,2,0,0 +21/02/2021,West Ham,Tottenham,2,1,H,1,0,H,C Pawson,4,20,4,4,5,7,14,7,4,3,0,0 +21/02/2021,Aston Villa,Leicester,1,2,A,0,2,A,M Oliver,11,16,2,7,7,8,11,12,2,1,0,0 +21/02/2021,Arsenal,Man City,0,1,A,0,1,A,J Moss,7,15,1,3,1,3,11,9,2,2,0,0 +21/02/2021,Man United,Newcastle,3,1,H,1,1,D,P Tierney,15,10,7,6,6,4,9,11,1,2,0,0 +22/02/2021,Brighton,Crystal Palace,1,2,A,0,1,A,K Friend,25,3,5,2,13,0,11,6,2,0,0,0 +23/02/2021,Leeds,Southampton,3,0,H,0,0,D,A Marriner,11,13,7,5,2,4,11,13,1,2,0,0 +27/02/2021,Man City,West Ham,2,1,H,1,1,D,M Oliver,10,9,3,3,4,2,8,11,1,1,0,0 +27/02/2021,West Brom,Brighton,1,0,H,1,0,H,L Mason,6,15,2,6,7,4,11,12,0,0,0,0 +27/02/2021,Leeds,Aston Villa,0,1,A,0,1,A,P Bankes,13,9,3,4,7,5,14,14,3,2,0,0 +27/02/2021,Newcastle,Wolves,1,1,D,0,0,D,M Dean,19,14,7,4,3,5,11,13,2,0,0,0 +28/02/2021,Crystal Palace,Fulham,0,0,D,0,0,D,A Taylor,3,16,0,4,1,3,10,14,1,1,0,0 +28/02/2021,Leicester,Arsenal,1,3,A,1,2,A,P Tierney,8,12,3,4,2,7,15,3,2,1,0,0 +28/02/2021,Tottenham,Burnley,4,0,H,3,0,H,K Friend,16,10,7,3,3,4,6,10,0,0,0,0 +28/02/2021,Chelsea,Man United,0,0,D,0,0,D,S Attwell,18,11,6,4,3,4,11,12,1,2,0,0 +28/02/2021,Sheffield United,Liverpool,0,2,A,0,0,D,J Moss,8,16,2,8,4,3,9,9,0,0,0,0 +01/03/2021,Everton,Southampton,1,0,H,1,0,H,M Atkinson,7,9,1,1,4,10,13,9,0,2,0,0 +02/03/2021,Man City,Wolves,4,1,H,1,0,H,C Kavanagh,22,6,10,1,10,2,9,5,0,1,0,0 +03/03/2021,Burnley,Leicester,1,1,D,1,1,D,A Madley,12,15,7,4,5,8,6,12,0,2,0,0 +03/03/2021,Sheffield United,Aston Villa,1,0,H,1,0,H,R Jones,9,16,2,4,2,7,13,6,1,0,1,0 +03/03/2021,Crystal Palace,Man United,0,0,D,0,0,D,A Marriner,8,11,2,1,4,6,4,13,1,0,0,0 +04/03/2021,Fulham,Tottenham,0,1,A,0,1,A,D Coote,11,9,3,2,4,1,13,9,2,1,0,0 +04/03/2021,West Brom,Everton,0,1,A,0,0,D,D England,11,11,3,3,3,3,9,7,3,1,0,0 +04/03/2021,Liverpool,Chelsea,0,1,A,0,1,A,M Atkinson,7,11,1,5,5,2,9,8,0,0,0,0 +06/03/2021,Burnley,Arsenal,1,1,D,1,1,D,A Marriner,9,15,5,3,2,4,5,12,1,1,0,0 +06/03/2021,Sheffield United,Southampton,0,2,A,0,1,A,P Tierney,8,15,2,5,7,3,17,11,4,3,0,0 +06/03/2021,Aston Villa,Wolves,0,0,D,0,0,D,A Madley,9,10,1,2,5,3,13,13,1,0,0,0 +06/03/2021,Brighton,Leicester,1,2,A,1,0,H,M Oliver,7,8,2,4,6,6,14,8,0,1,0,0 +07/03/2021,West Brom,Newcastle,0,0,D,0,0,D,M Atkinson,13,9,3,5,9,7,11,10,0,0,0,0 +07/03/2021,Liverpool,Fulham,0,1,A,0,1,A,K Friend,16,10,3,3,9,4,10,8,2,3,0,0 +07/03/2021,Man City,Man United,0,2,A,0,1,A,A Taylor,23,8,6,6,6,2,9,12,0,3,0,0 +07/03/2021,Tottenham,Crystal Palace,4,1,H,1,1,D,S Attwell,12,5,5,1,1,5,8,17,2,3,0,0 +08/03/2021,Chelsea,Everton,2,0,H,1,0,H,D Coote,19,7,9,1,7,3,11,12,0,3,0,0 +08/03/2021,West Ham,Leeds,2,0,H,2,0,H,M Dean,16,17,5,2,6,6,9,9,1,1,0,0 +10/03/2021,Man City,Southampton,5,2,H,3,1,H,J Moss,17,11,10,6,5,5,6,4,1,0,0,0 +12/03/2021,Newcastle,Aston Villa,1,1,D,0,0,D,P Tierney,12,15,3,6,2,2,10,10,2,3,0,0 +13/03/2021,Leeds,Chelsea,0,0,D,0,0,D,K Friend,7,15,4,8,8,5,16,6,3,0,0,0 +13/03/2021,Crystal Palace,West Brom,1,0,H,1,0,H,S Hooper,6,12,4,1,3,2,16,15,3,1,0,0 +13/03/2021,Everton,Burnley,1,2,A,1,2,A,J Moss,14,14,4,5,7,4,9,6,0,4,0,0 +13/03/2021,Fulham,Man City,0,3,A,0,0,D,A Marriner,3,8,0,7,4,2,14,13,0,0,0,0 +14/03/2021,Southampton,Brighton,1,2,A,1,1,D,S Attwell,9,8,4,6,4,5,11,10,0,0,0,0 +14/03/2021,Leicester,Sheffield United,5,0,H,1,0,H,P Bankes,19,1,10,0,9,1,10,9,0,2,0,0 +14/03/2021,Arsenal,Tottenham,2,1,H,1,1,D,M Oliver,13,6,3,3,6,3,12,14,1,2,0,1 +14/03/2021,Man United,West Ham,1,0,H,0,0,D,M Atkinson,15,7,4,0,6,3,12,2,4,0,0,0 +15/03/2021,Wolves,Liverpool,0,1,A,0,1,A,C Pawson,10,12,5,4,3,2,17,10,2,1,0,0 +19/03/2021,Fulham,Leeds,1,2,A,1,1,D,D Coote,13,13,6,4,13,7,13,12,2,2,0,0 +20/03/2021,Brighton,Newcastle,3,0,H,1,0,H,A Taylor,11,3,6,1,3,1,12,5,0,1,0,0 +21/03/2021,West Ham,Arsenal,3,3,D,3,1,H,J Moss,15,16,5,7,3,5,18,8,3,1,0,0 +21/03/2021,Aston Villa,Tottenham,0,2,A,0,1,A,M Dean,8,9,1,3,5,8,18,13,3,2,0,0 +03/04/2021,Chelsea,West Brom,2,5,A,1,2,A,D Coote,18,14,8,7,10,3,11,6,1,1,1,0 +03/04/2021,Leeds,Sheffield United,2,1,H,1,1,D,G Scott,23,9,8,1,5,7,12,13,1,2,0,0 +03/04/2021,Leicester,Man City,0,2,A,0,0,D,A Taylor,5,11,2,4,1,1,13,12,2,3,0,0 +03/04/2021,Arsenal,Liverpool,0,3,A,0,0,D,S Attwell,3,16,2,7,1,2,10,10,1,1,0,0 +04/04/2021,Southampton,Burnley,3,2,H,2,2,D,A Marriner,24,7,9,5,6,7,11,10,0,0,0,0 +04/04/2021,Newcastle,Tottenham,2,2,D,1,2,A,C Pawson,22,11,6,5,7,3,13,14,3,2,0,0 +04/04/2021,Aston Villa,Fulham,3,1,H,0,0,D,A Madley,14,15,4,7,4,4,14,20,1,2,0,0 +04/04/2021,Man United,Brighton,2,1,H,0,1,A,M Dean,15,5,7,3,5,6,14,7,2,2,0,0 +05/04/2021,Everton,Crystal Palace,1,1,D,0,0,D,K Friend,15,13,7,6,3,4,10,10,2,0,0,0 +05/04/2021,Wolves,West Ham,2,3,A,1,3,A,M Oliver,20,9,5,6,4,2,9,9,1,4,0,0 +09/04/2021,Fulham,Wolves,0,1,A,0,0,D,J Moss,14,7,3,3,3,4,11,8,0,0,0,0 +10/04/2021,Man City,Leeds,1,2,A,0,1,A,A Marriner,29,2,7,2,9,3,12,15,3,1,0,1 +10/04/2021,Liverpool,Aston Villa,2,1,H,0,1,A,P Tierney,23,9,10,5,7,0,16,11,2,4,0,0 +10/04/2021,Crystal Palace,Chelsea,1,4,A,0,3,A,M Oliver,1,23,1,10,0,8,7,12,1,0,0,0 +11/04/2021,Burnley,Newcastle,1,2,A,1,0,H,A Taylor,24,10,4,5,13,3,11,12,1,0,0,0 +11/04/2021,West Ham,Leicester,3,2,H,2,0,H,M Dean,4,15,4,5,1,9,7,13,3,3,0,0 +11/04/2021,Tottenham,Man United,1,3,A,1,0,H,C Kavanagh,12,12,3,7,7,5,11,15,1,5,0,0 +11/04/2021,Sheffield United,Arsenal,0,3,A,0,1,A,P Bankes,7,14,2,5,2,4,9,6,1,1,0,0 +12/04/2021,West Brom,Southampton,3,0,H,2,0,H,S Hooper,15,14,6,4,5,10,12,11,0,1,0,0 +12/04/2021,Brighton,Everton,0,0,D,0,0,D,D England,23,8,3,1,10,3,12,7,1,2,0,0 +16/04/2021,Everton,Tottenham,2,2,D,1,1,D,M Oliver,18,12,7,3,6,3,8,8,1,1,0,0 +17/04/2021,Newcastle,West Ham,3,2,H,2,0,H,K Friend,16,16,8,6,3,8,9,7,2,0,0,1 +17/04/2021,Wolves,Sheffield United,1,0,H,0,0,D,R Jones,10,8,5,2,3,3,8,15,1,2,0,0 +18/04/2021,Arsenal,Fulham,1,1,D,0,0,D,C Pawson,18,3,5,1,11,1,9,11,0,3,0,0 +18/04/2021,Man United,Burnley,3,1,H,0,0,D,J Moss,17,10,9,3,8,3,9,12,1,2,0,0 +19/04/2021,Leeds,Liverpool,1,1,D,0,1,A,A Taylor,12,17,5,7,10,8,17,15,2,1,0,0 +20/04/2021,Chelsea,Brighton,0,0,D,0,0,D,S Attwell,7,11,4,2,0,3,8,9,2,0,0,1 +21/04/2021,Tottenham,Southampton,2,1,H,0,1,A,D Coote,12,10,3,5,5,5,10,14,2,1,0,0 +21/04/2021,Aston Villa,Man City,1,2,A,1,2,A,P Bankes,8,13,3,3,1,11,12,7,0,1,1,1 +22/04/2021,Leicester,West Brom,3,0,H,3,0,H,A Madley,17,4,7,1,7,3,13,12,0,2,0,0 +23/04/2021,Arsenal,Everton,0,1,A,0,0,D,J Moss,14,8,3,1,7,3,13,10,1,3,0,0 +24/04/2021,Liverpool,Newcastle,1,1,D,1,0,H,A Marriner,22,7,9,4,6,3,9,7,2,1,0,0 +24/04/2021,West Ham,Chelsea,0,1,A,0,1,A,C Kavanagh,9,17,2,6,4,2,9,11,0,4,1,0 +24/04/2021,Sheffield United,Brighton,1,0,H,1,0,H,K Friend,7,17,3,4,2,12,13,7,3,0,0,0 +25/04/2021,Wolves,Burnley,0,4,A,0,3,A,D England,12,14,2,7,8,5,8,9,1,2,0,0 +25/04/2021,Leeds,Man United,0,0,D,0,0,D,C Pawson,6,16,3,4,2,6,21,11,4,1,0,0 +25/04/2021,Aston Villa,West Brom,2,2,D,1,1,D,S Attwell,24,10,11,4,11,4,14,9,0,2,0,0 +26/04/2021,Leicester,Crystal Palace,2,1,H,0,1,A,G Scott,13,4,5,2,7,2,13,12,0,1,0,0 +30/04/2021,Southampton,Leicester,1,1,D,0,0,D,R Jones,5,23,3,9,0,10,9,13,1,0,1,0 +01/05/2021,Crystal Palace,Man City,0,2,A,0,0,D,D Coote,9,20,4,3,2,6,9,10,2,0,0,0 +01/05/2021,Brighton,Leeds,2,0,H,1,0,H,C Kavanagh,17,11,5,2,2,9,17,6,1,1,0,0 +01/05/2021,Chelsea,Fulham,2,0,H,1,0,H,K Friend,9,10,5,3,1,8,15,8,1,1,0,0 +01/05/2021,Everton,Aston Villa,1,2,A,1,1,D,S Hooper,16,13,3,5,3,6,10,13,0,2,0,0 +02/05/2021,Newcastle,Arsenal,0,2,A,0,1,A,M Dean,5,19,1,5,3,7,10,9,2,1,1,0 +02/05/2021,Tottenham,Sheffield United,4,0,H,1,0,H,A Marriner,20,8,11,1,6,6,8,9,1,1,0,0 +03/05/2021,West Brom,Wolves,1,1,D,0,1,A,P Tierney,10,20,4,10,5,6,17,11,2,0,0,0 +03/05/2021,Burnley,West Ham,1,2,A,1,2,A,A Taylor,8,22,3,4,5,4,4,8,2,1,0,0 +07/05/2021,Leicester,Newcastle,2,4,A,0,2,A,D England,25,14,9,6,9,6,12,9,1,1,0,0 +08/05/2021,Leeds,Tottenham,3,1,H,2,1,H,M Oliver,16,11,7,3,8,6,12,10,1,2,0,0 +08/05/2021,Sheffield United,Crystal Palace,0,2,A,0,1,A,S Hooper,7,21,0,8,5,11,11,8,1,1,0,0 +08/05/2021,Man City,Chelsea,1,2,A,1,0,H,A Taylor,16,12,4,5,6,2,12,10,2,0,0,0 +08/05/2021,Liverpool,Southampton,2,0,H,1,0,H,K Friend,14,12,6,6,8,4,10,6,0,1,0,0 +09/05/2021,Wolves,Brighton,2,1,H,0,1,A,J Moss,16,5,6,3,10,5,12,7,2,2,0,2 +09/05/2021,Aston Villa,Man United,1,3,A,1,0,H,C Kavanagh,11,18,5,7,5,6,13,18,1,1,1,0 +09/05/2021,West Ham,Everton,0,1,A,0,1,A,S Attwell,11,9,0,3,5,6,8,10,2,1,0,0 +09/05/2021,Arsenal,West Brom,3,1,H,2,0,H,P Bankes,15,12,7,1,6,6,7,9,1,2,0,0 +10/05/2021,Fulham,Burnley,0,2,A,0,2,A,D Coote,21,14,3,5,8,4,11,10,2,0,0,0 +11/05/2021,Man United,Leicester,1,2,A,1,1,D,C Pawson,4,12,1,3,3,4,9,12,0,0,0,0 +11/05/2021,Southampton,Crystal Palace,3,1,H,1,1,D,A Madley,13,11,6,4,12,8,20,13,2,3,0,0 +12/05/2021,Chelsea,Arsenal,0,1,A,0,1,A,A Marriner,19,5,5,2,9,1,7,6,0,1,0,0 +13/05/2021,Aston Villa,Everton,0,0,D,0,0,D,M Atkinson,13,15,2,5,9,1,9,12,2,0,0,0 +13/05/2021,Man United,Liverpool,2,4,A,1,2,A,A Taylor,18,17,3,8,6,4,10,12,3,0,0,0 +14/05/2021,Newcastle,Man City,3,4,A,2,2,D,K Friend,11,15,5,6,4,5,11,10,2,2,0,0 +15/05/2021,Burnley,Leeds,0,4,A,0,1,A,G Scott,16,16,4,6,6,8,10,10,1,1,0,0 +15/05/2021,Southampton,Fulham,3,1,H,1,0,H,C Pawson,11,10,7,3,6,5,9,15,0,1,0,0 +15/05/2021,Brighton,West Ham,1,1,D,0,0,D,A Marriner,10,15,2,3,3,3,7,5,1,1,0,0 +16/05/2021,Crystal Palace,Aston Villa,3,2,H,1,2,A,D Coote,23,19,8,5,7,7,23,13,3,2,0,0 +16/05/2021,Tottenham,Wolves,2,0,H,1,0,H,M Atkinson,24,15,13,3,10,3,8,11,1,0,0,0 +16/05/2021,West Brom,Liverpool,1,2,A,1,1,D,M Dean,10,26,3,6,9,13,7,14,0,0,0,0 +16/05/2021,Everton,Sheffield United,0,1,A,0,1,A,A Moss,16,10,6,3,11,4,8,12,1,3,0,0 +18/05/2021,Man United,Fulham,1,1,D,1,0,H,L Mason,13,8,6,5,6,1,10,15,2,3,0,0 +18/05/2021,Southampton,Leeds,0,2,A,0,0,D,P Bankes,18,15,3,7,4,5,10,12,0,3,0,0 +18/05/2021,Brighton,Man City,3,2,H,0,1,A,S Attwell,19,8,6,4,5,3,13,11,3,3,0,1 +18/05/2021,Chelsea,Leicester,2,1,H,0,0,D,M Dean,17,7,6,3,6,5,15,16,2,4,0,0 +19/05/2021,Everton,Wolves,1,0,H,0,0,D,A Madley,14,9,4,3,7,12,11,11,0,2,0,0 +19/05/2021,Newcastle,Sheffield United,1,0,H,1,0,H,R Jones,16,11,5,1,5,2,7,7,0,2,0,0 +19/05/2021,Tottenham,Aston Villa,1,2,A,1,2,A,C Pawson,10,20,4,4,4,5,14,13,1,1,0,0 +19/05/2021,Crystal Palace,Arsenal,1,3,A,0,1,A,A Taylor,12,6,6,3,6,7,7,7,2,1,0,0 +19/05/2021,Burnley,Liverpool,0,3,A,0,1,A,C Kavanagh,10,20,4,3,6,7,10,7,0,0,0,0 +19/05/2021,West Brom,West Ham,1,3,A,1,1,D,M Oliver,14,21,4,9,6,8,8,11,2,2,0,0 +23/05/2021,Arsenal,Brighton,2,0,H,0,0,D,J Moss,16,5,5,1,11,3,10,8,0,0,0,0 +23/05/2021,Aston Villa,Chelsea,2,1,H,1,0,H,S Attwell,6,23,3,7,2,9,11,12,3,3,0,1 +23/05/2021,Fulham,Newcastle,0,2,A,0,1,A,C Kavanagh,14,10,0,4,5,4,12,6,1,0,0,0 +23/05/2021,Leeds,West Brom,3,1,H,2,0,H,D Coote,17,14,9,5,8,3,12,12,2,1,0,0 +23/05/2021,Leicester,Tottenham,2,4,A,1,1,D,A Taylor,10,11,6,4,8,7,9,8,0,2,0,0 +23/05/2021,Liverpool,Crystal Palace,2,0,H,1,0,H,C Pawson,19,5,5,4,14,1,10,8,2,2,0,0 +23/05/2021,Man City,Everton,5,0,H,2,0,H,M Oliver,21,8,11,3,7,5,8,10,2,2,0,0 +23/05/2021,Sheffield United,Burnley,1,0,H,1,0,H,K Friend,12,10,3,3,8,9,11,1,3,1,0,0 +23/05/2021,West Ham,Southampton,3,0,H,2,0,H,M Atkinson,14,17,7,5,2,3,5,9,0,3,0,0 +23/05/2021,Wolves,Man United,1,2,A,1,2,A,M Dean,14,9,4,4,6,2,14,3,4,1,0,0 +13/08/2021,Brentford,Arsenal,2,0,H,1,0,H,M Oliver,8,22,3,4,2,5,12,8,0,0,0,0 +14/08/2021,Man United,Leeds,5,1,H,1,0,H,P Tierney,16,10,8,3,5,4,11,9,1,2,0,0 +14/08/2021,Burnley,Brighton,1,2,A,1,0,H,D Coote,14,14,3,8,7,6,10,7,2,1,0,0 +14/08/2021,Chelsea,Crystal Palace,3,0,H,2,0,H,J Moss,13,4,6,1,5,2,15,11,0,0,0,0 +14/08/2021,Everton,Southampton,3,1,H,0,1,A,A Madley,14,6,6,3,6,8,13,15,2,0,0,0 +14/08/2021,Leicester,Wolves,1,0,H,1,0,H,C Pawson,9,17,5,3,5,4,6,10,1,2,0,0 +14/08/2021,Watford,Aston Villa,3,2,H,2,0,H,M Dean,13,11,7,2,2,4,18,13,3,1,0,0 +14/08/2021,Norwich,Liverpool,0,3,A,0,1,A,A Marriner,14,19,3,8,3,11,4,14,1,1,0,0 +15/08/2021,Newcastle,West Ham,2,4,A,2,1,H,M Atkinson,17,8,3,9,7,6,4,3,1,0,0,0 +15/08/2021,Tottenham,Man City,1,0,H,0,0,D,A Taylor,13,18,3,4,3,11,11,8,2,1,0,0 +21/08/2021,Liverpool,Burnley,2,0,H,1,0,H,M Dean,27,9,9,3,8,4,6,12,0,0,0,0 +21/08/2021,Aston Villa,Newcastle,2,0,H,1,0,H,D Coote,10,9,2,1,3,4,8,18,3,4,0,0 +21/08/2021,Crystal Palace,Brentford,0,0,D,0,0,D,M Atkinson,7,14,2,3,3,5,12,9,3,1,0,0 +21/08/2021,Leeds,Everton,2,2,D,1,1,D,D England,17,17,4,8,8,5,6,13,2,4,0,0 +21/08/2021,Man City,Norwich,5,0,H,2,0,H,G Scott,16,1,4,0,6,1,13,7,1,0,0,0 +21/08/2021,Brighton,Watford,2,0,H,2,0,H,A Taylor,13,10,3,1,7,2,11,6,4,0,0,0 +22/08/2021,Southampton,Man United,1,1,D,1,0,H,C Pawson,8,15,3,4,7,7,12,10,2,3,0,0 +22/08/2021,Wolves,Tottenham,0,1,A,0,1,A,S Attwell,25,8,6,6,5,4,9,7,1,4,0,0 +22/08/2021,Arsenal,Chelsea,0,2,A,0,2,A,P Tierney,6,22,3,5,9,8,10,4,3,0,0,0 +23/08/2021,West Ham,Leicester,4,1,H,1,0,H,M Oliver,13,5,7,1,10,0,8,8,0,1,0,1 +28/08/2021,Man City,Arsenal,5,0,H,3,0,H,M Atkinson,25,1,10,0,14,0,5,7,1,2,0,1 +28/08/2021,Aston Villa,Brentford,1,1,D,1,1,D,P Bankes,7,9,5,2,3,2,15,14,3,3,0,0 +28/08/2021,Brighton,Everton,0,2,A,0,1,A,J Moss,14,14,3,5,4,5,7,9,1,2,0,0 +28/08/2021,Newcastle,Southampton,2,2,D,0,0,D,P Tierney,10,22,5,6,4,4,11,6,3,3,0,0 +28/08/2021,Norwich,Leicester,1,2,A,1,1,D,R Jones,14,9,4,3,9,9,8,9,2,1,0,0 +28/08/2021,West Ham,Crystal Palace,2,2,D,1,0,H,S Attwell,14,9,4,2,5,3,10,7,1,0,0,0 +28/08/2021,Liverpool,Chelsea,1,1,D,1,1,D,A Taylor,24,6,7,3,12,3,13,4,0,2,0,1 +29/08/2021,Burnley,Leeds,1,1,D,0,0,D,M Oliver,12,12,3,2,5,5,13,9,4,3,0,0 +29/08/2021,Tottenham,Watford,1,0,H,1,0,H,A Marriner,15,9,8,2,10,3,8,14,3,3,0,0 +29/08/2021,Wolves,Man United,0,1,A,0,0,D,M Dean,15,10,6,3,5,7,7,9,3,4,0,0 +11/09/2021,Crystal Palace,Tottenham,3,0,H,0,0,D,J Moss,18,2,4,1,8,2,13,15,2,1,0,1 +11/09/2021,Arsenal,Norwich,1,0,H,0,0,D,M Oliver,30,10,7,1,8,4,9,11,1,2,0,0 +11/09/2021,Brentford,Brighton,0,1,A,0,0,D,G Scott,7,4,1,2,1,6,9,12,3,2,0,0 +11/09/2021,Leicester,Man City,0,1,A,0,0,D,P Tierney,6,25,1,8,5,8,4,11,1,2,0,0 +11/09/2021,Man United,Newcastle,4,1,H,1,0,H,A Taylor,21,12,7,3,7,3,9,2,1,0,0,0 +11/09/2021,Southampton,West Ham,0,0,D,0,0,D,D Coote,11,13,3,3,5,4,10,12,3,1,0,1 +11/09/2021,Watford,Wolves,0,2,A,0,0,D,P Bankes,6,14,2,5,6,8,15,13,3,3,0,0 +11/09/2021,Chelsea,Aston Villa,3,0,H,1,0,H,S Attwell,12,18,4,6,4,11,11,11,1,4,0,0 +12/09/2021,Leeds,Liverpool,0,3,A,0,1,A,C Pawson,9,30,4,9,2,11,9,8,2,1,1,0 +13/09/2021,Everton,Burnley,3,1,H,0,0,D,M Atkinson,14,11,6,6,4,6,3,6,1,1,0,0 +17/09/2021,Newcastle,Leeds,1,1,D,1,1,D,M Dean,17,22,7,9,4,5,9,11,2,2,0,0 +18/09/2021,Wolves,Brentford,0,2,A,0,2,A,D England,11,9,0,3,4,2,10,7,4,2,0,1 +18/09/2021,Burnley,Arsenal,0,1,A,0,1,A,A Taylor,18,13,3,3,8,3,9,8,2,1,0,0 +18/09/2021,Liverpool,Crystal Palace,3,0,H,1,0,H,A Madley,25,13,10,2,10,5,17,11,3,1,0,0 +18/09/2021,Man City,Southampton,0,0,D,0,0,D,J Moss,18,10,1,2,8,5,5,12,0,2,0,0 +18/09/2021,Norwich,Watford,1,3,A,1,1,D,R Jones,12,12,5,8,4,8,10,14,1,1,0,0 +18/09/2021,Aston Villa,Everton,3,0,H,0,0,D,C Pawson,15,11,3,1,5,4,7,10,1,1,0,0 +19/09/2021,Brighton,Leicester,2,1,H,1,0,H,S Attwell,12,14,5,3,4,9,8,10,2,1,0,0 +19/09/2021,West Ham,Man United,1,2,A,1,1,D,M Atkinson,13,17,4,10,2,11,5,8,0,0,0,0 +19/09/2021,Tottenham,Chelsea,0,3,A,0,0,D,P Tierney,8,20,2,10,5,11,4,15,0,1,0,0 +25/09/2021,Chelsea,Man City,0,1,A,0,0,D,M Oliver,5,15,0,4,4,13,12,10,3,2,0,0 +25/09/2021,Man United,Aston Villa,0,1,A,0,0,D,M Dean,28,7,4,3,5,5,9,13,2,2,0,0 +25/09/2021,Everton,Norwich,2,0,H,1,0,H,D Coote,11,10,4,2,6,4,12,9,2,3,0,0 +25/09/2021,Leeds,West Ham,1,2,A,1,0,H,K Friend,15,20,5,7,5,7,10,6,3,2,0,0 +25/09/2021,Leicester,Burnley,2,2,D,1,2,A,C Kavanagh,22,9,5,3,8,4,10,8,2,4,0,0 +25/09/2021,Watford,Newcastle,1,1,D,0,1,A,J Gillett,15,20,4,6,5,6,19,12,4,3,0,0 +25/09/2021,Brentford,Liverpool,3,3,D,1,1,D,S Attwell,12,16,4,6,2,11,8,7,1,1,0,0 +26/09/2021,Southampton,Wolves,0,1,A,0,0,D,A Madley,18,5,6,3,5,5,12,8,0,0,0,0 +26/09/2021,Arsenal,Tottenham,3,1,H,3,0,H,C Pawson,12,10,7,4,4,4,12,13,2,1,0,0 +27/09/2021,Crystal Palace,Brighton,1,1,D,1,0,H,A Marriner,8,8,3,4,6,5,12,10,2,3,0,0 +02/10/2021,Man United,Everton,1,1,D,1,0,H,M Oliver,13,12,6,2,10,1,10,9,1,1,0,0 +02/10/2021,Burnley,Norwich,0,0,D,0,0,D,K Friend,14,10,4,2,9,5,13,3,5,2,0,0 +02/10/2021,Chelsea,Southampton,3,1,H,1,0,H,M Atkinson,20,6,9,2,7,2,11,13,1,3,0,1 +02/10/2021,Leeds,Watford,1,0,H,1,0,H,S Hooper,20,5,4,1,6,3,10,16,0,3,0,0 +02/10/2021,Wolves,Newcastle,2,1,H,1,1,D,G Scott,7,8,3,2,0,4,15,5,0,2,0,0 +02/10/2021,Brighton,Arsenal,0,0,D,0,0,D,J Moss,21,8,2,2,8,5,8,5,2,1,0,0 +03/10/2021,Crystal Palace,Leicester,2,2,D,0,2,A,A Taylor,18,9,4,5,1,3,15,11,2,3,0,0 +03/10/2021,Tottenham,Aston Villa,2,1,H,1,0,H,C Kavanagh,17,14,8,3,5,8,11,14,2,1,0,0 +03/10/2021,West Ham,Brentford,1,2,A,0,1,A,P Bankes,18,13,5,6,11,6,16,13,2,3,0,0 +03/10/2021,Liverpool,Man City,2,2,D,0,0,D,P Tierney,6,12,4,3,3,4,10,12,3,3,0,0 +16/10/2021,Watford,Liverpool,0,5,A,0,2,A,J Moss,6,19,2,8,1,9,4,10,0,0,0,0 +16/10/2021,Aston Villa,Wolves,2,3,A,0,0,D,M Oliver,14,9,5,4,6,7,8,17,2,3,0,0 +16/10/2021,Leicester,Man United,4,2,H,1,1,D,C Pawson,22,18,11,6,4,3,5,17,0,3,0,0 +16/10/2021,Man City,Burnley,2,0,H,1,0,H,M Atkinson,15,7,6,2,6,2,11,5,1,0,0,0 +16/10/2021,Norwich,Brighton,0,0,D,0,0,D,P Bankes,15,11,3,3,8,4,5,14,3,5,0,0 +16/10/2021,Southampton,Leeds,1,0,H,0,0,D,D Coote,19,3,5,0,8,1,16,14,2,3,0,0 +16/10/2021,Brentford,Chelsea,0,1,A,0,1,A,A Taylor,17,5,7,1,5,5,13,8,1,1,0,0 +17/10/2021,Everton,West Ham,0,1,A,0,0,D,S Attwell,15,16,2,4,7,9,3,3,0,2,0,0 +17/10/2021,Newcastle,Tottenham,2,3,A,1,3,A,A Marriner,7,14,1,4,2,9,13,4,4,1,1,0 +18/10/2021,Arsenal,Crystal Palace,2,2,D,1,0,H,M Dean,17,9,6,6,6,4,7,4,1,1,0,0 +22/10/2021,Arsenal,Aston Villa,3,1,H,2,0,H,C Pawson,22,10,9,4,7,4,17,10,2,5,0,0 +23/10/2021,Chelsea,Norwich,7,0,H,3,0,H,A Madley,23,3,13,1,5,0,14,16,0,2,0,1 +23/10/2021,Crystal Palace,Newcastle,1,1,D,0,0,D,D England,15,6,3,2,3,7,13,9,3,4,0,0 +23/10/2021,Everton,Watford,2,5,A,1,1,D,G Scott,15,20,4,8,3,5,9,13,4,3,0,0 +23/10/2021,Leeds,Wolves,1,1,D,0,1,A,R Jones,18,8,4,4,6,3,15,14,2,3,0,0 +23/10/2021,Southampton,Burnley,2,2,D,1,1,D,C Kavanagh,16,9,4,5,6,2,10,11,1,3,0,0 +23/10/2021,Brighton,Man City,1,4,A,0,3,A,K Friend,10,23,5,14,7,6,10,6,2,3,0,0 +24/10/2021,Brentford,Leicester,1,2,A,0,1,A,S Hooper,15,10,5,6,9,2,13,7,1,1,0,0 +24/10/2021,West Ham,Tottenham,1,0,H,0,0,D,P Tierney,13,7,4,4,2,4,11,4,2,1,0,0 +24/10/2021,Man United,Liverpool,0,5,A,0,4,A,A Taylor,12,19,4,8,6,3,10,8,6,0,1,0 +30/10/2021,Leicester,Arsenal,0,2,A,0,2,A,M Oliver,16,9,8,5,7,6,8,11,2,0,0,0 +30/10/2021,Burnley,Brentford,3,1,H,3,0,H,J Moss,15,12,6,5,4,2,7,10,1,1,0,0 +30/10/2021,Liverpool,Brighton,2,2,D,2,1,H,M Dean,14,9,3,6,5,3,8,5,2,2,0,0 +30/10/2021,Man City,Crystal Palace,0,2,A,0,1,A,A Marriner,14,8,3,3,7,1,11,11,2,3,1,0 +30/10/2021,Newcastle,Chelsea,0,3,A,0,0,D,P Tierney,6,19,1,6,0,2,14,12,3,2,0,0 +30/10/2021,Watford,Southampton,0,1,A,0,1,A,P Bankes,10,12,4,1,5,8,16,6,2,2,0,0 +30/10/2021,Tottenham,Man United,0,3,A,0,1,A,S Attwell,9,10,0,4,10,2,12,14,2,3,0,0 +31/10/2021,Norwich,Leeds,1,2,A,0,0,D,A Taylor,14,13,3,6,5,0,14,23,1,4,0,0 +31/10/2021,Aston Villa,West Ham,1,4,A,1,2,A,C Kavanagh,9,21,3,9,3,6,7,13,1,2,1,0 +01/11/2021,Wolves,Everton,2,1,H,2,0,H,M Atkinson,10,14,4,5,4,3,10,8,1,1,0,0 +05/11/2021,Southampton,Aston Villa,1,0,H,1,0,H,A Madley,9,14,3,3,10,8,12,12,2,2,0,0 +06/11/2021,Man United,Man City,0,2,A,0,2,A,M Oliver,5,16,1,5,1,9,9,12,1,2,0,0 +06/11/2021,Brentford,Norwich,1,2,A,0,2,A,J Gillett,19,9,7,5,6,2,12,13,2,3,0,0 +06/11/2021,Chelsea,Burnley,1,1,D,1,0,H,A Marriner,25,5,4,2,14,2,6,9,1,4,0,0 +06/11/2021,Crystal Palace,Wolves,2,0,H,0,0,D,G Scott,13,4,6,2,4,6,14,13,2,1,0,0 +06/11/2021,Brighton,Newcastle,1,1,D,1,0,H,D Coote,14,6,5,1,2,5,16,10,3,4,1,0 +07/11/2021,Arsenal,Watford,1,0,H,0,0,D,K Friend,14,7,6,1,4,2,6,19,4,2,0,1 +07/11/2021,Everton,Tottenham,0,0,D,0,0,D,C Kavanagh,12,8,2,0,4,2,13,17,2,4,1,0 +07/11/2021,Leeds,Leicester,1,1,D,1,1,D,D England,18,9,5,3,10,5,12,5,1,1,0,0 +07/11/2021,West Ham,Liverpool,3,2,H,1,1,D,C Pawson,7,16,3,5,3,2,11,6,1,1,0,0 +20/11/2021,Leicester,Chelsea,0,3,A,0,2,A,P Tierney,4,16,3,7,3,5,15,8,3,1,0,0 +20/11/2021,Aston Villa,Brighton,2,0,H,0,0,D,A Taylor,9,6,5,2,5,7,7,13,3,3,0,0 +20/11/2021,Burnley,Crystal Palace,3,3,D,2,3,A,S Hooper,15,18,9,8,3,6,14,15,2,3,0,0 +20/11/2021,Newcastle,Brentford,3,3,D,2,2,D,R Jones,23,10,9,4,8,2,13,11,2,2,0,0 +20/11/2021,Norwich,Southampton,2,1,H,1,1,D,M Atkinson,8,17,4,5,5,7,8,9,3,2,0,0 +20/11/2021,Watford,Man United,4,1,H,2,0,H,J Moss,20,9,7,3,9,3,7,5,2,1,0,1 +20/11/2021,Wolves,West Ham,1,0,H,0,0,D,M Dean,15,8,5,3,7,3,6,9,1,1,0,0 +20/11/2021,Liverpool,Arsenal,4,0,H,1,0,H,M Oliver,19,5,9,3,6,1,15,12,2,0,0,0 +21/11/2021,Man City,Everton,3,0,H,1,0,H,S Attwell,17,4,7,1,7,1,5,7,1,1,0,0 +21/11/2021,Tottenham,Leeds,2,1,H,0,1,A,A Marriner,13,18,4,7,4,8,11,12,1,4,0,0 +27/11/2021,Arsenal,Newcastle,2,0,H,0,0,D,S Attwell,24,9,6,5,4,4,6,12,1,3,0,0 +27/11/2021,Crystal Palace,Aston Villa,1,2,A,0,1,A,M Salisbury,8,10,3,3,7,3,12,10,3,3,0,0 +27/11/2021,Liverpool,Southampton,4,0,H,3,0,H,A Marriner,20,7,6,3,5,7,7,14,0,1,0,0 +27/11/2021,Norwich,Wolves,0,0,D,0,0,D,S Hooper,14,5,4,2,4,3,11,10,2,2,0,0 +27/11/2021,Brighton,Leeds,0,0,D,0,0,D,C Pawson,20,11,4,4,6,1,12,14,1,3,0,0 +28/11/2021,Brentford,Everton,1,0,H,1,0,H,D England,6,14,4,5,3,3,8,11,4,3,0,0 +28/11/2021,Leicester,Watford,4,2,H,3,1,H,A Madley,18,16,8,5,8,6,5,19,0,0,0,0 +28/11/2021,Man City,West Ham,2,1,H,1,0,H,M Oliver,19,5,9,3,8,4,7,6,2,0,0,0 +28/11/2021,Chelsea,Man United,1,1,D,0,0,D,A Taylor,24,3,6,2,15,2,13,14,2,5,0,0 +30/11/2021,Newcastle,Norwich,1,1,D,0,0,D,A Madley,9,16,1,6,2,5,8,11,0,0,0,1 +30/11/2021,Leeds,Crystal Palace,1,0,H,0,0,D,K Friend,15,9,3,1,7,4,15,15,5,3,0,0 +01/12/2021,Southampton,Leicester,2,2,D,2,1,H,R Jones,15,15,7,6,2,4,14,11,1,3,0,0 +01/12/2021,Watford,Chelsea,1,2,A,1,1,D,D Coote,13,8,6,4,6,0,18,16,4,3,0,0 +01/12/2021,West Ham,Brighton,1,1,D,1,0,H,C Kavanagh,14,9,6,3,9,5,10,8,1,1,0,0 +01/12/2021,Wolves,Burnley,0,0,D,0,0,D,J Brooks,16,6,2,1,5,5,5,7,2,3,0,0 +01/12/2021,Aston Villa,Man City,1,2,A,0,2,A,M Dean,9,17,3,7,4,11,8,12,0,0,0,0 +01/12/2021,Everton,Liverpool,1,4,A,1,2,A,P Tierney,8,16,2,7,3,11,8,16,4,3,0,0 +02/12/2021,Tottenham,Brentford,2,0,H,1,0,H,J Moss,11,6,6,2,11,5,13,11,0,0,0,0 +02/12/2021,Man United,Arsenal,3,2,H,1,1,D,M Atkinson,14,17,10,8,3,8,9,10,1,0,0,0 +04/12/2021,West Ham,Chelsea,3,2,H,1,2,A,A Marriner,11,19,5,7,1,9,10,10,1,3,0,0 +04/12/2021,Newcastle,Burnley,1,0,H,1,0,H,P Tierney,19,10,4,1,10,3,11,9,1,1,0,0 +04/12/2021,Southampton,Brighton,1,1,D,1,0,H,A Taylor,14,14,4,4,6,2,15,12,4,2,0,0 +04/12/2021,Wolves,Liverpool,0,1,A,0,0,D,C Kavanagh,3,17,1,5,2,8,4,7,2,2,0,0 +04/12/2021,Watford,Man City,1,3,A,0,2,A,S Hooper,11,26,4,13,5,11,10,7,3,0,0,0 +05/12/2021,Leeds,Brentford,2,2,D,1,0,H,D Coote,13,9,6,3,5,1,11,9,5,3,0,0 +05/12/2021,Man United,Crystal Palace,1,0,H,0,0,D,C Pawson,16,8,3,2,9,6,16,9,2,2,0,0 +05/12/2021,Tottenham,Norwich,3,0,H,1,0,H,J Gillett,17,10,7,1,6,4,11,4,1,2,0,0 +05/12/2021,Aston Villa,Leicester,2,1,H,1,1,D,M Oliver,13,12,4,5,3,6,8,25,1,1,0,0 +06/12/2021,Everton,Arsenal,2,1,H,0,1,A,M Dean,11,10,5,3,3,3,8,12,2,2,0,0 +10/12/2021,Brentford,Watford,2,1,H,0,1,A,M Oliver,14,6,5,2,8,2,12,18,4,0,0,0 +11/12/2021,Man City,Wolves,1,0,H,0,0,D,J Moss,24,2,10,1,14,1,12,5,3,2,0,1 +11/12/2021,Arsenal,Southampton,3,0,H,2,0,H,J Gillett,15,11,5,6,9,8,5,9,2,0,0,0 +11/12/2021,Chelsea,Leeds,3,2,H,1,1,D,C Kavanagh,16,12,6,5,5,2,9,15,2,5,0,0 +11/12/2021,Liverpool,Aston Villa,1,0,H,0,0,D,S Attwell,20,4,5,0,11,3,13,6,1,2,0,0 +11/12/2021,Norwich,Man United,0,1,A,0,0,D,D England,11,13,5,5,8,6,6,17,1,1,0,0 +12/12/2021,Burnley,West Ham,0,0,D,0,0,D,G Scott,9,16,1,3,6,4,13,11,2,1,0,0 +12/12/2021,Leicester,Newcastle,4,0,H,1,0,H,P Bankes,8,12,5,3,4,6,9,16,2,3,0,0 +12/12/2021,Crystal Palace,Everton,3,1,H,1,0,H,A Madley,17,12,6,6,8,2,15,14,0,2,0,0 +14/12/2021,Norwich,Aston Villa,0,2,A,0,1,A,D Coote,7,17,3,6,7,10,12,9,3,1,0,0 +14/12/2021,Man City,Leeds,7,0,H,3,0,H,P Tierney,31,6,15,3,6,2,5,9,1,1,0,0 +15/12/2021,Brighton,Wolves,0,1,A,0,1,A,T Harrington,12,8,2,5,7,4,11,14,2,0,0,0 +15/12/2021,Crystal Palace,Southampton,2,2,D,1,2,A,S Hooper,9,15,6,4,4,6,16,20,2,4,0,0 +15/12/2021,Arsenal,West Ham,2,0,H,0,0,D,A Taylor,21,7,8,1,5,2,8,9,2,0,0,1 +16/12/2021,Chelsea,Everton,1,1,D,0,0,D,M Oliver,23,5,10,3,7,1,11,11,2,3,0,0 +16/12/2021,Liverpool,Newcastle,3,1,H,2,1,H,M Dean,23,4,8,2,11,0,9,4,1,3,0,0 +18/12/2021,Leeds,Arsenal,1,4,A,0,3,A,A Marriner,9,21,2,12,1,4,11,7,3,1,0,0 +19/12/2021,Newcastle,Man City,0,4,A,0,2,A,M Atkinson,5,18,1,7,3,6,9,6,1,2,0,0 +19/12/2021,Wolves,Chelsea,0,0,D,0,0,D,D Coote,4,8,1,1,4,9,9,7,0,2,0,0 +19/12/2021,Tottenham,Liverpool,2,2,D,1,1,D,P Tierney,10,18,5,6,6,4,12,11,4,4,0,1 +26/12/2021,Man City,Leicester,6,3,H,4,0,H,C Kavanagh,17,14,9,8,14,4,11,9,1,2,0,0 +26/12/2021,Norwich,Arsenal,0,5,A,0,2,A,G Scott,4,16,2,6,3,5,13,7,1,2,0,0 +26/12/2021,Tottenham,Crystal Palace,3,0,H,2,0,H,J Moss,17,4,6,0,4,2,16,10,1,1,0,1 +26/12/2021,West Ham,Southampton,2,3,A,0,1,A,K Friend,11,11,5,6,10,2,10,9,2,3,0,0 +26/12/2021,Aston Villa,Chelsea,1,3,A,1,1,D,M Atkinson,8,10,1,4,2,5,8,11,3,1,0,0 +26/12/2021,Brighton,Brentford,2,0,H,2,0,H,D England,15,16,5,6,6,9,8,10,1,3,0,0 +27/12/2021,Newcastle,Man United,1,1,D,1,0,H,C Pawson,13,13,8,4,2,4,11,8,3,3,0,0 +28/12/2021,Crystal Palace,Norwich,3,0,H,3,0,H,P Tierney,19,12,6,3,8,8,9,13,1,2,0,0 +28/12/2021,Southampton,Tottenham,1,1,D,1,1,D,A Taylor,9,21,2,11,3,7,15,5,2,2,1,0 +28/12/2021,Watford,West Ham,1,4,A,1,2,A,D England,14,13,5,6,4,5,12,12,0,1,0,0 +28/12/2021,Leicester,Liverpool,1,0,H,0,0,D,M Oliver,6,21,1,4,1,12,8,11,0,1,0,0 +29/12/2021,Chelsea,Brighton,1,1,D,1,0,H,M Dean,11,18,5,6,10,8,14,7,4,1,0,0 +29/12/2021,Brentford,Man City,0,1,A,0,1,A,D Coote,6,12,2,3,3,5,12,10,0,0,0,0 +30/12/2021,Man United,Burnley,3,1,H,3,1,H,J Moss,18,10,6,3,7,5,5,10,0,0,0,0 +01/01/2022,Arsenal,Man City,1,2,A,1,0,H,S Attwell,7,15,2,2,5,5,13,5,3,2,1,0 +01/01/2022,Watford,Tottenham,0,1,A,0,0,D,R Jones,6,21,4,9,3,7,6,9,0,1,0,0 +01/01/2022,Crystal Palace,West Ham,2,3,A,0,3,A,D England,22,10,6,5,9,1,6,16,0,2,0,0 +02/01/2022,Brentford,Aston Villa,2,1,H,1,1,D,C Pawson,10,16,4,5,3,7,17,11,2,2,0,0 +02/01/2022,Everton,Brighton,2,3,A,0,2,A,J Brooks,17,12,6,5,8,5,5,12,1,1,0,0 +02/01/2022,Leeds,Burnley,3,1,H,1,0,H,P Tierney,22,8,7,2,9,3,11,7,2,1,0,0 +02/01/2022,Chelsea,Liverpool,2,2,D,2,2,D,A Taylor,15,10,6,6,6,7,5,13,1,2,0,0 +03/01/2022,Man United,Wolves,0,1,A,0,0,D,M Dean,9,19,2,6,3,8,9,8,3,1,0,0 +11/01/2022,Southampton,Brentford,4,1,H,2,1,H,S Attwell,12,5,6,4,5,2,9,8,0,1,0,0 +12/01/2022,West Ham,Norwich,2,0,H,1,0,H,S Hooper,13,7,5,3,11,2,9,8,0,0,0,0 +14/01/2022,Brighton,Crystal Palace,1,1,D,0,0,D,R Jones,19,3,4,1,9,0,11,7,0,2,0,0 +15/01/2022,Man City,Chelsea,1,0,H,0,0,D,C Pawson,11,4,6,1,9,1,11,6,0,2,0,0 +15/01/2022,Newcastle,Watford,1,1,D,0,0,D,P Tierney,12,18,1,5,4,6,13,13,3,2,0,0 +15/01/2022,Norwich,Everton,2,1,H,2,0,H,A Madley,14,12,3,5,8,8,12,14,3,1,0,0 +15/01/2022,Wolves,Southampton,3,1,H,1,0,H,M Salisbury,9,13,5,10,2,10,13,14,2,3,0,0 +15/01/2022,Aston Villa,Man United,2,2,D,0,1,A,D Coote,13,13,9,6,3,6,8,13,1,2,0,0 +16/01/2022,Liverpool,Brentford,3,0,H,1,0,H,J Moss,27,6,13,1,9,0,6,6,0,1,0,0 +16/01/2022,West Ham,Leeds,2,3,A,1,2,A,M Dean,15,18,5,5,4,3,6,9,3,2,0,0 +18/01/2022,Brighton,Chelsea,1,1,D,0,1,A,K Friend,10,15,2,3,5,7,8,5,1,2,0,0 +19/01/2022,Leicester,Tottenham,2,3,A,1,1,D,J Moss,14,27,4,10,3,8,6,12,1,2,0,0 +19/01/2022,Brentford,Man United,1,3,A,0,0,D,A Marriner,18,13,8,5,6,9,7,9,0,0,0,0 +21/01/2022,Watford,Norwich,0,3,A,0,0,D,M Dean,15,7,4,2,5,1,16,11,0,1,1,0 +22/01/2022,Everton,Aston Villa,0,1,A,0,1,A,C Pawson,15,8,1,3,9,2,14,16,5,3,0,0 +22/01/2022,Brentford,Wolves,1,2,A,0,0,D,P Bankes,11,7,1,2,6,3,6,7,2,3,0,0 +22/01/2022,Leeds,Newcastle,0,1,A,0,0,D,C Kavanagh,13,15,4,3,6,7,9,9,4,2,0,0 +22/01/2022,Man United,West Ham,1,0,H,0,0,D,J Moss,18,6,3,1,3,3,9,8,1,1,0,0 +22/01/2022,Southampton,Man City,1,1,D,1,0,H,S Hooper,7,20,3,5,2,11,13,5,3,0,0,0 +23/01/2022,Arsenal,Burnley,0,0,D,0,0,D,D Coote,20,10,5,1,12,4,9,13,0,2,0,0 +23/01/2022,Crystal Palace,Liverpool,1,3,A,0,2,A,K Friend,8,15,5,7,2,5,12,10,4,1,0,0 +23/01/2022,Leicester,Brighton,1,1,D,0,0,D,M Atkinson,15,14,5,5,4,5,6,8,0,1,0,0 +23/01/2022,Chelsea,Tottenham,2,0,H,0,0,D,P Tierney,15,6,7,3,10,3,17,14,2,1,0,0 +05/02/2022,Burnley,Watford,0,0,D,0,0,D,C Pawson,13,11,2,5,4,7,16,9,1,2,0,0 +08/02/2022,Newcastle,Everton,3,1,H,1,1,D,A Taylor,20,11,9,3,6,2,13,13,2,3,0,0 +08/02/2022,West Ham,Watford,1,0,H,0,0,D,M Atkinson,9,6,3,1,5,6,1,6,1,0,0,0 +08/02/2022,Burnley,Man United,1,1,D,0,1,A,M Dean,9,22,3,5,2,10,11,10,2,1,0,0 +09/02/2022,Man City,Brentford,2,0,H,1,0,H,D England,15,6,7,2,9,3,7,13,0,0,0,0 +09/02/2022,Norwich,Crystal Palace,1,1,D,1,0,H,P Tierney,5,13,1,5,4,6,11,15,1,2,0,0 +09/02/2022,Tottenham,Southampton,2,3,A,1,1,D,D Coote,8,23,3,10,2,8,7,13,2,3,0,0 +09/02/2022,Aston Villa,Leeds,3,3,D,3,2,H,J Gillett,12,16,4,8,6,11,11,9,1,3,1,0 +10/02/2022,Liverpool,Leicester,2,0,H,1,0,H,C Kavanagh,22,5,11,1,7,6,11,7,1,0,0,0 +10/02/2022,Wolves,Arsenal,0,1,A,0,1,A,M Oliver,15,12,4,2,7,4,12,13,2,3,0,1 +12/02/2022,Man United,Southampton,1,1,D,1,0,H,S Attwell,12,13,8,4,1,4,12,13,4,0,0,0 +12/02/2022,Brentford,Crystal Palace,0,0,D,0,0,D,S Hooper,9,6,2,3,3,9,10,10,1,0,0,0 +12/02/2022,Everton,Leeds,3,0,H,2,0,H,G Scott,21,7,10,0,5,3,8,9,1,1,0,0 +12/02/2022,Watford,Brighton,0,2,A,0,1,A,J Moss,9,11,1,6,4,5,16,9,3,2,0,0 +12/02/2022,Norwich,Man City,0,4,A,0,1,A,A Marriner,8,22,3,9,4,12,7,8,0,2,0,0 +13/02/2022,Burnley,Liverpool,0,1,A,0,1,A,M Atkinson,8,12,5,4,5,8,7,6,0,1,0,0 +13/02/2022,Newcastle,Aston Villa,1,0,H,1,0,H,C Pawson,10,11,2,1,3,6,22,15,3,4,0,0 +13/02/2022,Tottenham,Wolves,0,2,A,0,2,A,K Friend,17,11,7,7,7,3,13,13,3,1,0,0 +13/02/2022,Leicester,West Ham,2,2,D,1,1,D,M Oliver,13,8,2,3,5,4,10,9,1,2,0,0 +15/02/2022,Man United,Brighton,2,0,H,0,0,D,P Bankes,19,10,7,3,10,2,8,10,3,1,0,1 +19/02/2022,West Ham,Newcastle,1,1,D,1,1,D,C Kavanagh,11,14,3,3,3,7,3,7,3,3,0,0 +19/02/2022,Arsenal,Brentford,2,1,H,0,0,D,J Moss,24,6,8,2,14,0,10,5,1,1,0,0 +19/02/2022,Aston Villa,Watford,0,1,A,0,0,D,R Jones,20,8,1,4,10,5,12,7,1,2,0,0 +19/02/2022,Brighton,Burnley,0,3,A,0,2,A,K Friend,11,10,1,4,4,4,12,12,1,4,0,0 +19/02/2022,Crystal Palace,Chelsea,0,1,A,0,0,D,D Coote,7,9,0,3,4,4,4,20,1,3,0,0 +19/02/2022,Liverpool,Norwich,3,1,H,0,0,D,M Dean,29,6,8,1,9,4,7,8,0,1,0,0 +19/02/2022,Southampton,Everton,2,0,H,0,0,D,A Madley,19,9,11,0,4,4,6,9,0,2,0,0 +19/02/2022,Man City,Tottenham,2,3,A,1,1,D,A Taylor,21,6,4,5,10,0,5,7,0,3,0,0 +20/02/2022,Leeds,Man United,2,4,A,0,2,A,P Tierney,16,15,6,9,3,3,13,19,6,3,0,0 +20/02/2022,Wolves,Leicester,2,1,H,1,1,D,C Pawson,11,17,3,7,7,10,12,9,4,4,0,0 +23/02/2022,Burnley,Tottenham,1,0,H,0,0,D,G Scott,15,12,4,2,3,7,8,8,0,0,0,0 +23/02/2022,Watford,Crystal Palace,1,4,A,1,2,A,A Marriner,8,7,1,5,6,4,12,9,2,1,0,0 +23/02/2022,Liverpool,Leeds,6,0,H,3,0,H,M Oliver,23,3,15,2,4,0,5,11,0,3,0,0 +24/02/2022,Arsenal,Wolves,2,1,H,0,1,A,M Atkinson,26,6,5,2,5,4,8,9,1,1,0,0 +25/02/2022,Southampton,Norwich,2,0,H,1,0,H,S Hooper,27,8,9,1,13,6,12,9,1,2,0,0 +26/02/2022,Leeds,Tottenham,0,4,A,0,3,A,C Pawson,19,15,3,11,3,6,16,11,4,2,0,0 +26/02/2022,Brentford,Newcastle,0,2,A,0,2,A,M Dean,6,26,1,11,6,6,9,7,3,1,1,0 +26/02/2022,Crystal Palace,Burnley,1,1,D,1,0,H,J Moss,11,5,4,1,5,4,5,17,0,2,0,0 +26/02/2022,Man United,Watford,0,0,D,0,0,D,K Friend,22,10,3,2,4,3,2,7,0,0,0,0 +26/02/2022,Brighton,Aston Villa,0,2,A,0,1,A,J Brooks,12,9,1,4,7,2,17,11,4,5,0,0 +26/02/2022,Everton,Man City,0,1,A,0,0,D,P Tierney,6,13,2,8,3,4,7,14,3,2,0,0 +27/02/2022,West Ham,Wolves,1,0,H,0,0,D,A Taylor,13,14,4,1,3,5,8,2,1,0,0,0 +01/03/2022,Burnley,Leicester,0,2,A,0,0,D,C Kavanagh,9,22,2,6,6,8,10,13,1,1,0,0 +05/03/2022,Leicester,Leeds,1,0,H,0,0,D,D Coote,7,19,4,4,5,10,9,12,1,2,0,0 +05/03/2022,Aston Villa,Southampton,4,0,H,2,0,H,P Bankes,14,11,9,1,4,7,10,8,1,1,0,0 +05/03/2022,Burnley,Chelsea,0,4,A,0,0,D,A Marriner,6,11,1,5,5,2,12,7,2,0,0,0 +05/03/2022,Newcastle,Brighton,2,1,H,2,0,H,M Atkinson,10,15,4,4,4,7,8,10,1,1,0,0 +05/03/2022,Norwich,Brentford,1,3,A,0,1,A,A Taylor,15,10,7,6,5,5,13,18,2,5,0,0 +05/03/2022,Wolves,Crystal Palace,0,2,A,0,2,A,A Madley,12,12,5,8,7,7,13,8,0,3,0,0 +05/03/2022,Liverpool,West Ham,1,0,H,1,0,H,J Moss,22,13,5,5,4,6,7,7,2,1,0,0 +06/03/2022,Watford,Arsenal,2,3,A,1,2,A,C Pawson,13,16,7,4,1,5,5,11,1,2,0,0 +06/03/2022,Man City,Man United,4,1,H,2,1,H,M Oliver,24,5,10,2,9,3,10,14,0,1,0,0 +07/03/2022,Tottenham,Everton,5,0,H,3,0,H,S Attwell,14,6,7,0,5,2,15,10,2,0,0,0 +10/03/2022,Norwich,Chelsea,1,3,A,0,2,A,M Atkinson,9,15,3,7,3,8,8,15,0,2,0,0 +10/03/2022,Southampton,Newcastle,1,2,A,1,1,D,K Friend,14,8,5,4,12,5,6,7,1,1,0,0 +10/03/2022,Wolves,Watford,4,0,H,2,0,H,D England,10,9,5,0,5,2,11,10,2,2,0,0 +10/03/2022,Leeds,Aston Villa,0,3,A,0,1,A,S Hooper,4,15,1,9,3,5,17,11,5,3,0,0 +12/03/2022,Brighton,Liverpool,0,2,A,0,1,A,M Dean,8,18,3,9,7,6,12,15,3,1,0,0 +12/03/2022,Brentford,Burnley,2,0,H,0,0,D,P Tierney,13,7,4,1,2,9,6,10,1,1,0,1 +12/03/2022,Man United,Tottenham,3,2,H,2,1,H,J Moss,10,10,6,3,4,5,8,7,2,1,0,0 +13/03/2022,Chelsea,Newcastle,1,0,H,0,0,D,D Coote,8,7,3,2,7,2,17,16,4,3,0,0 +13/03/2022,Everton,Wolves,0,1,A,0,0,D,M Oliver,8,14,2,3,5,2,13,11,2,1,1,0 +13/03/2022,Leeds,Norwich,2,1,H,1,0,H,S Attwell,13,12,7,4,6,2,18,8,3,1,0,0 +13/03/2022,Southampton,Watford,1,2,A,1,2,A,G Scott,13,9,7,5,11,3,8,12,3,2,0,0 +13/03/2022,West Ham,Aston Villa,2,1,H,0,0,D,J Gillett,11,13,4,7,5,7,9,3,1,0,0,0 +13/03/2022,Arsenal,Leicester,2,0,H,1,0,H,A Taylor,21,6,8,3,2,6,8,10,0,3,0,0 +14/03/2022,Crystal Palace,Man City,0,0,D,0,0,D,M Atkinson,7,18,1,4,2,6,6,11,3,1,0,0 +16/03/2022,Brighton,Tottenham,0,2,A,0,1,A,R Jones,15,17,0,7,6,3,11,9,2,2,0,0 +16/03/2022,Arsenal,Liverpool,0,2,A,0,0,D,A Marriner,9,9,2,3,1,6,9,9,0,1,0,0 +17/03/2022,Everton,Newcastle,1,0,H,0,0,D,C Pawson,9,17,3,6,5,7,13,11,2,1,1,0 +18/03/2022,Wolves,Leeds,2,3,A,2,0,H,K Friend,14,15,8,6,2,5,13,14,1,4,1,0 +19/03/2022,Aston Villa,Arsenal,0,1,A,0,1,A,A Madley,8,10,1,3,6,5,13,14,3,3,0,0 +20/03/2022,Leicester,Brentford,2,1,H,2,0,H,D England,12,15,4,6,2,5,10,10,1,2,0,0 +20/03/2022,Tottenham,West Ham,3,1,H,2,1,H,A Taylor,17,6,4,1,5,3,7,7,0,1,0,0 +02/04/2022,Liverpool,Watford,2,0,H,1,0,H,S Attwell,20,5,3,2,9,3,9,8,1,1,0,0 +02/04/2022,Brighton,Norwich,0,0,D,0,0,D,S Hooper,31,6,4,0,11,2,12,11,1,1,0,0 +02/04/2022,Burnley,Man City,0,2,A,0,2,A,C Pawson,3,18,1,6,1,8,11,6,1,0,0,0 +02/04/2022,Chelsea,Brentford,1,4,A,0,0,D,A Madley,21,17,8,6,12,7,11,7,0,0,0,0 +02/04/2022,Leeds,Southampton,1,1,D,1,0,H,A Taylor,12,14,3,6,4,7,14,7,1,1,0,0 +02/04/2022,Wolves,Aston Villa,2,1,H,2,0,H,D England,11,15,5,5,4,9,15,8,4,3,0,0 +02/04/2022,Man United,Leicester,1,1,D,0,0,D,A Marriner,11,11,5,3,6,5,13,6,2,2,0,0 +03/04/2022,West Ham,Everton,2,1,H,1,0,H,M Oliver,11,13,5,2,2,4,9,12,1,1,0,1 +03/04/2022,Tottenham,Newcastle,5,1,H,1,1,D,M Atkinson,19,8,6,1,2,3,10,7,2,3,0,0 +04/04/2022,Crystal Palace,Arsenal,3,0,H,2,0,H,P Tierney,6,12,5,3,0,2,11,15,0,2,0,0 +06/04/2022,Burnley,Everton,3,2,H,1,2,A,M Dean,13,16,4,4,9,6,11,16,2,2,0,0 +08/04/2022,Newcastle,Wolves,1,0,H,0,0,D,P Bankes,12,5,4,2,1,2,6,13,1,1,0,0 +09/04/2022,Everton,Man United,1,0,H,1,0,H,J Moss,7,12,3,4,5,7,4,10,2,2,0,0 +09/04/2022,Arsenal,Brighton,1,2,A,0,1,A,D Coote,20,8,4,3,11,2,12,19,3,4,0,0 +09/04/2022,Southampton,Chelsea,0,6,A,0,4,A,K Friend,4,24,1,14,3,3,11,10,1,0,0,0 +09/04/2022,Watford,Leeds,0,3,A,0,1,A,A Marriner,8,10,1,5,3,8,8,12,2,0,0,0 +09/04/2022,Aston Villa,Tottenham,0,4,A,0,1,A,G Scott,9,11,8,5,9,3,12,14,2,3,0,0 +10/04/2022,Brentford,West Ham,2,0,H,0,0,D,M Atkinson,15,5,7,1,4,6,2,6,0,1,0,0 +10/04/2022,Leicester,Crystal Palace,2,1,H,2,0,H,R Jones,12,11,3,3,3,4,11,12,1,1,0,0 +10/04/2022,Norwich,Burnley,2,0,H,1,0,H,M Oliver,17,18,6,4,6,7,12,10,1,1,0,0 +10/04/2022,Man City,Liverpool,2,2,D,2,1,H,A Taylor,11,6,5,4,4,1,9,11,1,4,0,0 +16/04/2022,Tottenham,Brighton,0,1,A,0,0,D,C Pawson,5,12,0,5,4,4,14,10,3,3,0,0 +16/04/2022,Man United,Norwich,3,2,H,2,1,H,A Madley,20,15,9,4,6,7,7,6,0,0,0,0 +16/04/2022,Southampton,Arsenal,1,0,H,1,0,H,P Bankes,9,23,3,6,6,8,6,6,2,1,0,0 +16/04/2022,Watford,Brentford,1,2,A,0,1,A,S Hooper,17,16,4,5,6,5,11,9,2,1,0,0 +17/04/2022,Newcastle,Leicester,2,1,H,1,1,D,J Gillett,16,8,7,2,3,5,10,10,2,2,0,0 +17/04/2022,West Ham,Burnley,1,1,D,0,1,A,P Tierney,21,9,8,1,13,4,9,10,2,2,0,0 +19/04/2022,Liverpool,Man United,4,0,H,2,0,H,M Atkinson,14,2,5,1,10,2,3,6,1,2,0,0 +20/04/2022,Chelsea,Arsenal,2,4,A,2,2,D,J Moss,11,14,2,4,6,1,7,6,3,1,0,0 +20/04/2022,Everton,Leicester,1,1,D,0,1,A,D Coote,13,11,3,3,5,4,8,17,3,1,0,0 +20/04/2022,Newcastle,Crystal Palace,1,0,H,1,0,H,T Harrington,13,10,4,2,2,5,12,12,1,1,0,0 +20/04/2022,Man City,Brighton,3,0,H,0,0,D,M Dean,17,2,5,1,7,4,12,7,0,3,0,0 +21/04/2022,Burnley,Southampton,2,0,H,2,0,H,S Attwell,16,11,6,3,10,10,7,7,2,2,0,0 +23/04/2022,Arsenal,Man United,3,1,H,2,1,H,C Pawson,14,14,7,5,1,7,9,12,3,4,0,0 +23/04/2022,Leicester,Aston Villa,0,0,D,0,0,D,A Madley,5,11,2,3,7,6,15,16,3,2,0,0 +23/04/2022,Man City,Watford,5,1,H,3,1,H,K Friend,21,5,8,2,11,4,9,7,1,0,0,0 +23/04/2022,Norwich,Newcastle,0,3,A,0,2,A,C Kavanagh,5,13,2,7,2,3,11,10,0,2,0,0 +23/04/2022,Brentford,Tottenham,0,0,D,0,0,D,M Atkinson,15,9,2,0,12,8,8,7,2,1,0,0 +24/04/2022,Brighton,Southampton,2,2,D,2,1,H,R Jones,8,18,5,5,6,6,12,7,1,0,0,0 +24/04/2022,Burnley,Wolves,1,0,H,0,0,D,A Taylor,14,10,5,4,3,6,8,7,3,1,0,0 +24/04/2022,Chelsea,West Ham,1,0,H,0,0,D,M Oliver,26,6,5,2,3,1,11,9,1,0,0,1 +24/04/2022,Liverpool,Everton,2,0,H,0,0,D,S Attwell,18,9,4,1,13,1,8,8,2,5,0,0 +25/04/2022,Crystal Palace,Leeds,0,0,D,0,0,D,D England,17,9,7,2,6,3,12,13,2,2,0,0 +28/04/2022,Man United,Chelsea,1,1,D,0,0,D,M Dean,6,21,3,6,2,9,9,12,0,1,0,0 +30/04/2022,Newcastle,Liverpool,0,1,A,0,1,A,A Marriner,4,24,2,10,0,6,11,11,1,3,0,0 +30/04/2022,Aston Villa,Norwich,2,0,H,1,0,H,J Brooks,21,9,6,3,2,3,16,8,4,1,0,0 +30/04/2022,Southampton,Crystal Palace,1,2,A,1,0,H,J Gillett,8,14,3,5,6,7,13,11,1,3,0,0 +30/04/2022,Watford,Burnley,1,2,A,1,0,H,C Pawson,13,17,1,6,5,6,13,9,1,2,0,0 +30/04/2022,Wolves,Brighton,0,3,A,0,1,A,S Hooper,11,17,1,9,7,3,6,4,2,1,0,0 +30/04/2022,Leeds,Man City,0,4,A,0,1,A,P Tierney,7,19,2,6,3,6,13,3,1,2,0,0 +01/05/2022,Everton,Chelsea,1,0,H,0,0,D,K Friend,9,17,4,5,3,8,9,12,3,5,0,0 +01/05/2022,Tottenham,Leicester,3,1,H,1,0,H,J Moss,13,6,7,2,7,3,13,8,2,3,0,0 +01/05/2022,West Ham,Arsenal,1,2,A,1,1,D,M Dean,8,13,3,7,3,7,8,9,2,2,0,0 +02/05/2022,Man United,Brentford,3,0,H,1,0,H,C Kavanagh,9,12,5,4,3,8,4,6,1,0,0,0 +07/05/2022,Brentford,Southampton,3,0,H,2,0,H,M Salisbury,18,19,5,4,4,7,12,9,0,1,0,0 +07/05/2022,Burnley,Aston Villa,1,3,A,0,2,A,D Coote,14,12,6,7,4,5,13,11,1,2,0,0 +07/05/2022,Chelsea,Wolves,2,2,D,0,0,D,P Bankes,19,14,6,4,9,3,7,11,1,3,0,0 +07/05/2022,Crystal Palace,Watford,1,0,H,1,0,H,G Scott,15,6,7,1,8,5,11,18,2,1,0,1 +07/05/2022,Brighton,Man United,4,0,H,1,0,H,A Madley,17,15,6,5,7,6,13,9,0,2,0,0 +07/05/2022,Liverpool,Tottenham,1,1,D,0,0,D,M Oliver,22,8,3,3,11,5,12,8,3,2,0,0 +08/05/2022,Arsenal,Leeds,2,1,H,2,0,H,C Kavanagh,19,3,9,2,8,2,13,15,2,2,0,1 +08/05/2022,Leicester,Everton,1,2,A,1,2,A,C Pawson,16,10,9,4,6,1,11,6,3,0,0,0 +08/05/2022,Norwich,West Ham,0,4,A,0,3,A,R Jones,8,13,2,5,9,5,7,7,1,1,0,0 +08/05/2022,Man City,Newcastle,5,0,H,2,0,H,S Attwell,21,7,9,3,8,2,6,11,1,3,0,0 +10/05/2022,Aston Villa,Liverpool,1,2,A,1,1,D,J Moss,9,17,4,6,2,3,10,12,0,0,0,0 +11/05/2022,Leeds,Chelsea,0,3,A,0,1,A,A Taylor,5,17,0,4,1,5,10,14,1,0,1,0 +11/05/2022,Leicester,Norwich,3,0,H,0,0,D,S Hooper,20,9,8,5,10,2,10,5,0,0,0,0 +11/05/2022,Watford,Everton,0,0,D,0,0,D,M Dean,6,16,0,5,1,13,5,12,1,2,0,0 +11/05/2022,Wolves,Man City,1,5,A,1,3,A,M Atkinson,7,16,3,5,6,2,2,10,0,1,0,0 +12/05/2022,Tottenham,Arsenal,3,0,H,2,0,H,P Tierney,16,8,6,4,2,2,8,15,1,3,0,1 +15/05/2022,Tottenham,Burnley,1,0,H,1,0,H,K Friend,21,8,8,1,8,2,12,8,3,2,0,0 +15/05/2022,Aston Villa,Crystal Palace,1,1,D,0,0,D,C Kavanagh,13,16,4,4,1,5,11,15,1,3,0,0 +15/05/2022,Leeds,Brighton,1,1,D,0,1,A,M Dean,19,16,5,6,8,1,16,8,3,2,0,0 +15/05/2022,Watford,Leicester,1,5,A,1,2,A,J Gillett,13,13,5,7,7,3,4,6,0,1,0,0 +15/05/2022,West Ham,Man City,2,2,D,2,0,H,A Taylor,6,31,2,8,4,9,9,5,3,1,0,0 +15/05/2022,Wolves,Norwich,1,1,D,0,1,A,T Harrington,17,11,4,2,5,3,5,10,0,3,0,0 +15/05/2022,Everton,Brentford,2,3,A,2,1,H,M Oliver,10,18,5,7,5,2,8,8,0,3,2,0 +16/05/2022,Newcastle,Arsenal,2,0,H,0,0,D,D England,16,11,4,2,12,8,11,8,0,3,0,0 +17/05/2022,Southampton,Liverpool,1,2,A,1,1,D,M Atkinson,4,24,2,5,1,9,6,6,2,0,0,0 +19/05/2022,Everton,Crystal Palace,3,2,H,0,2,A,A Taylor,12,7,6,5,4,5,9,13,2,3,0,0 +19/05/2022,Aston Villa,Burnley,1,1,D,0,1,A,P Tierney,22,10,9,5,13,6,11,20,0,1,0,1 +19/05/2022,Chelsea,Leicester,1,1,D,1,1,D,S Attwell,20,2,7,1,6,0,13,7,4,1,0,0 +22/05/2022,Arsenal,Everton,5,1,H,2,1,H,A Marriner,26,6,9,2,12,2,8,9,0,1,0,0 +22/05/2022,Brentford,Leeds,1,2,A,0,0,D,P Tierney,14,14,5,6,3,2,11,13,2,1,1,0 +22/05/2022,Brighton,West Ham,3,1,H,0,1,A,K Friend,18,7,7,2,5,6,9,9,1,1,0,0 +22/05/2022,Burnley,Newcastle,1,2,A,0,1,A,C Pawson,12,8,5,6,8,2,8,9,1,0,0,0 +22/05/2022,Chelsea,Watford,2,1,H,1,0,H,M Dean,19,8,8,3,3,3,9,9,0,0,0,0 +22/05/2022,Crystal Palace,Man United,1,0,H,1,0,H,M Atkinson,6,10,3,4,3,6,12,22,2,4,0,0 +22/05/2022,Leicester,Southampton,4,1,H,0,0,D,J Moss,12,7,6,2,3,3,10,5,0,1,0,0 +22/05/2022,Liverpool,Wolves,3,1,H,1,1,D,A Taylor,29,7,8,5,5,3,6,3,1,0,0,0 +22/05/2022,Man City,Aston Villa,3,2,H,0,1,A,M Oliver,24,4,5,2,13,1,5,11,0,1,0,0 +22/05/2022,Norwich,Tottenham,0,5,A,0,2,A,C Kavanagh,9,19,0,13,3,2,13,7,3,1,0,0 +05/08/2022,Crystal Palace,Arsenal,0,2,A,0,1,A,A Taylor,10,10,2,2,3,5,16,11,1,2,0,0 +06/08/2022,Fulham,Liverpool,2,2,D,1,0,H,A Madley,9,11,3,4,4,4,7,9,2,0,0,0 +06/08/2022,Bournemouth,Aston Villa,2,0,H,1,0,H,P Bankes,7,15,3,2,5,5,18,16,3,3,0,0 +06/08/2022,Leeds,Wolves,2,1,H,1,1,D,R Jones,12,15,4,6,6,4,13,9,2,0,0,0 +06/08/2022,Newcastle,Nott'm Forest,2,0,H,0,0,D,S Hooper,23,5,10,0,11,1,9,14,0,3,0,0 +06/08/2022,Tottenham,Southampton,4,1,H,2,1,H,A Marriner,18,10,8,2,10,2,11,6,3,0,0,0 +06/08/2022,Everton,Chelsea,0,1,A,0,1,A,C Pawson,8,15,4,6,4,16,14,11,3,2,0,0 +07/08/2022,Leicester,Brentford,2,2,D,1,0,H,J Gillett,14,8,5,3,5,6,6,5,0,0,0,0 +07/08/2022,Man United,Brighton,1,2,A,0,2,A,P Tierney,17,15,5,4,6,2,7,12,4,1,0,0 +07/08/2022,West Ham,Man City,0,2,A,0,1,A,M Oliver,6,14,1,2,1,4,8,4,0,1,0,0 +13/08/2022,Aston Villa,Everton,2,1,H,1,0,H,M Oliver,12,15,4,4,5,8,9,7,4,1,0,0 +13/08/2022,Arsenal,Leicester,4,2,H,2,0,H,D England,19,6,7,2,6,2,15,9,1,1,0,0 +13/08/2022,Brighton,Newcastle,0,0,D,0,0,D,G Scott,13,4,7,1,7,3,8,6,2,3,0,0 +13/08/2022,Man City,Bournemouth,4,0,H,3,0,H,D Coote,19,3,7,1,11,1,9,9,0,3,0,0 +13/08/2022,Southampton,Leeds,2,2,D,0,0,D,T Harrington,14,13,4,5,2,5,10,12,1,3,0,0 +13/08/2022,Wolves,Fulham,0,0,D,0,0,D,J Brooks,7,9,1,3,5,3,7,14,3,3,0,0 +13/08/2022,Brentford,Man United,4,0,H,4,0,H,S Attwell,13,15,7,4,8,2,6,15,0,4,0,0 +14/08/2022,Nott'm Forest,West Ham,1,0,H,1,0,H,R Jones,13,19,6,5,6,7,10,11,4,1,0,0 +14/08/2022,Chelsea,Tottenham,2,2,D,1,0,H,A Taylor,16,10,3,5,8,5,9,9,3,0,0,0 +15/08/2022,Liverpool,Crystal Palace,1,1,D,0,1,A,P Tierney,24,7,4,3,4,2,7,8,2,4,1,0 +20/08/2022,Tottenham,Wolves,1,0,H,0,0,D,S Hooper,11,20,4,3,8,6,10,8,2,2,0,0 +20/08/2022,Crystal Palace,Aston Villa,3,1,H,1,1,D,A Madley,17,13,9,5,4,2,16,14,2,1,0,0 +20/08/2022,Everton,Nott'm Forest,1,1,D,0,0,D,A Marriner,19,14,8,5,6,2,6,9,3,2,0,0 +20/08/2022,Fulham,Brentford,3,2,H,2,1,H,P Bankes,18,13,9,5,6,3,13,7,3,2,0,0 +20/08/2022,Leicester,Southampton,1,2,A,0,0,D,M Salisbury,6,9,1,3,2,5,11,15,2,3,0,0 +20/08/2022,Bournemouth,Arsenal,0,3,A,0,2,A,C Pawson,6,14,1,6,3,4,12,10,2,1,0,0 +21/08/2022,Leeds,Chelsea,3,0,H,2,0,H,S Attwell,12,14,6,3,4,6,16,15,0,1,0,1 +21/08/2022,West Ham,Brighton,0,2,A,0,1,A,A Taylor,13,11,3,2,5,4,10,7,2,0,0,0 +21/08/2022,Newcastle,Man City,3,3,D,2,1,H,J Gillett,12,21,6,10,5,5,14,7,4,2,0,0 +22/08/2022,Man United,Liverpool,2,1,H,1,0,H,M Oliver,12,17,4,5,6,8,11,7,3,1,0,0 +27/08/2022,Southampton,Man United,0,1,A,0,0,D,A Madley,17,11,5,6,5,3,4,14,0,3,0,0 +27/08/2022,Brentford,Everton,1,1,D,0,1,A,J Brooks,19,14,5,7,8,5,8,8,2,2,0,0 +27/08/2022,Brighton,Leeds,1,0,H,0,0,D,M Salisbury,13,10,4,2,5,3,10,14,2,3,0,0 +27/08/2022,Chelsea,Leicester,2,1,H,0,0,D,P Tierney,7,17,3,5,3,11,10,7,1,2,1,0 +27/08/2022,Liverpool,Bournemouth,9,0,H,5,0,H,S Attwell,19,5,12,2,8,1,6,5,0,1,0,0 +27/08/2022,Man City,Crystal Palace,4,2,H,0,2,A,D England,18,2,5,2,6,1,14,8,1,2,0,0 +27/08/2022,Arsenal,Fulham,2,1,H,0,0,D,J Gillett,22,11,8,3,9,2,6,12,2,3,0,0 +28/08/2022,Aston Villa,West Ham,0,1,A,0,0,D,D Coote,9,7,3,2,3,2,10,10,2,0,0,0 +28/08/2022,Wolves,Newcastle,1,1,D,1,0,H,P Bankes,10,21,4,6,4,13,10,11,2,1,0,0 +28/08/2022,Nott'm Forest,Tottenham,0,2,A,0,1,A,C Pawson,17,18,1,7,6,1,11,13,6,1,0,0 +30/08/2022,Crystal Palace,Brentford,1,1,D,0,0,D,S Hooper,13,9,4,3,4,6,11,8,1,2,0,0 +30/08/2022,Fulham,Brighton,2,1,H,0,0,D,T Bramall,10,7,3,4,3,3,9,11,2,1,0,0 +30/08/2022,Southampton,Chelsea,2,1,H,2,1,H,M Oliver,9,10,7,4,5,3,15,10,2,1,0,0 +30/08/2022,Leeds,Everton,1,1,D,0,1,A,D England,14,7,5,2,10,2,10,9,3,3,0,0 +31/08/2022,Arsenal,Aston Villa,2,1,H,1,0,H,R Jones,22,4,9,3,10,3,10,14,2,3,0,0 +31/08/2022,Bournemouth,Wolves,0,0,D,0,0,D,A Taylor,5,17,2,4,2,5,12,12,2,1,0,0 +31/08/2022,Man City,Nott'm Forest,6,0,H,3,0,H,P Tierney,17,8,9,1,10,1,9,7,0,1,0,0 +31/08/2022,West Ham,Tottenham,1,1,D,0,1,A,P Bankes,14,13,4,4,3,4,10,9,2,5,0,0 +31/08/2022,Liverpool,Newcastle,2,1,H,0,1,A,A Marriner,23,5,6,2,13,0,9,10,0,2,0,0 +01/09/2022,Leicester,Man United,0,1,A,0,1,A,C Pawson,10,9,2,2,1,3,7,15,1,3,0,0 +03/09/2022,Everton,Liverpool,0,0,D,0,0,D,A Taylor,14,23,3,8,7,9,7,11,2,2,0,0 +03/09/2022,Brentford,Leeds,5,2,H,2,1,H,R Jones,14,17,8,6,2,6,8,9,2,2,0,0 +03/09/2022,Chelsea,West Ham,2,1,H,0,0,D,A Madley,8,6,3,3,10,3,6,11,4,2,0,0 +03/09/2022,Newcastle,Crystal Palace,0,0,D,0,0,D,M Salisbury,23,19,6,9,13,5,19,12,2,1,0,0 +03/09/2022,Nott'm Forest,Bournemouth,2,3,A,2,0,H,M Oliver,11,8,3,3,4,6,13,10,2,1,0,0 +03/09/2022,Tottenham,Fulham,2,1,H,1,0,H,S Attwell,23,9,10,3,10,3,9,6,3,4,0,0 +03/09/2022,Wolves,Southampton,1,0,H,1,0,H,J Brooks,7,12,2,1,5,6,10,14,4,2,0,0 +03/09/2022,Aston Villa,Man City,1,1,D,0,0,D,S Hooper,3,13,1,4,1,4,7,6,2,0,0,0 +04/09/2022,Brighton,Leicester,5,2,H,2,2,D,T Harrington,23,6,11,4,10,1,6,8,1,1,0,0 +04/09/2022,Man United,Arsenal,3,1,H,1,0,H,P Tierney,10,16,6,3,2,5,13,9,3,3,0,0 +16/09/2022,Aston Villa,Southampton,1,0,H,1,0,H,T Harrington,11,7,3,1,6,3,13,13,3,1,0,0 +16/09/2022,Nott'm Forest,Fulham,2,3,A,1,0,H,J Gillett,11,15,3,7,2,10,11,10,2,4,0,0 +17/09/2022,Wolves,Man City,0,3,A,0,2,A,A Taylor,6,16,1,7,5,7,7,9,2,1,1,0 +17/09/2022,Newcastle,Bournemouth,1,1,D,0,0,D,C Pawson,20,10,7,3,8,1,10,11,2,2,0,0 +17/09/2022,Tottenham,Leicester,6,2,H,2,2,D,S Hooper,16,19,11,7,6,1,9,9,1,2,0,0 +18/09/2022,Brentford,Arsenal,0,3,A,0,2,A,D Coote,5,13,2,7,3,3,10,10,0,2,0,0 +18/09/2022,Everton,West Ham,1,0,H,0,0,D,M Oliver,7,14,2,4,5,14,8,11,3,1,0,0 +01/10/2022,Arsenal,Tottenham,3,1,H,1,1,D,A Taylor,22,7,9,3,5,2,10,10,2,1,0,1 +01/10/2022,Bournemouth,Brentford,0,0,D,0,0,D,T Bramall,7,13,1,4,7,8,11,10,1,2,0,0 +01/10/2022,Crystal Palace,Chelsea,1,2,A,1,1,D,C Kavanagh,7,13,3,3,5,2,11,13,1,3,0,0 +01/10/2022,Fulham,Newcastle,1,4,A,0,3,A,D England,3,19,1,10,1,8,9,10,2,0,1,0 +01/10/2022,Liverpool,Brighton,3,3,D,1,2,A,A Madley,15,6,7,6,9,2,9,13,1,1,0,0 +01/10/2022,Southampton,Everton,1,2,A,0,0,D,A Marriner,22,12,6,5,7,6,11,10,2,2,0,0 +01/10/2022,West Ham,Wolves,2,0,H,1,0,H,P Tierney,18,15,5,4,1,6,8,6,2,2,0,0 +02/10/2022,Man City,Man United,6,3,H,4,0,H,M Oliver,22,12,10,8,5,1,3,9,0,3,0,0 +02/10/2022,Leeds,Aston Villa,0,0,D,0,0,D,S Attwell,9,19,1,7,2,9,23,12,3,2,1,0 +03/10/2022,Leicester,Nott'm Forest,4,0,H,3,0,H,R Jones,17,10,7,3,6,6,13,12,2,4,0,0 +08/10/2022,Bournemouth,Leicester,2,1,H,0,1,A,M Salisbury,10,9,4,5,7,2,8,16,1,2,0,0 +08/10/2022,Chelsea,Wolves,3,0,H,1,0,H,S Hooper,20,8,7,2,4,2,8,8,2,0,0,0 +08/10/2022,Man City,Southampton,4,0,H,2,0,H,A Madley,21,5,8,0,9,1,4,8,0,1,0,0 +08/10/2022,Newcastle,Brentford,5,1,H,2,0,H,J Brooks,16,6,6,3,9,5,14,9,0,1,0,0 +08/10/2022,Brighton,Tottenham,0,1,A,0,1,A,T Harrington,14,8,4,3,6,2,9,14,1,2,0,0 +09/10/2022,Crystal Palace,Leeds,2,1,H,1,1,D,P Tierney,13,10,5,4,5,3,13,23,3,3,0,0 +09/10/2022,West Ham,Fulham,3,1,H,1,1,D,C Kavanagh,17,8,8,2,4,2,9,7,1,3,0,0 +09/10/2022,Arsenal,Liverpool,3,2,H,2,1,H,M Oliver,11,8,7,3,4,3,11,11,1,2,0,0 +09/10/2022,Everton,Man United,1,2,A,1,2,A,D Coote,11,12,2,4,5,4,7,13,2,1,0,0 +10/10/2022,Nott'm Forest,Aston Villa,1,1,D,1,1,D,A Taylor,6,12,3,2,1,4,13,19,3,3,0,0 +14/10/2022,Brentford,Brighton,2,0,H,1,0,H,M Salisbury,7,21,3,7,2,11,12,15,3,1,0,0 +15/10/2022,Leicester,Crystal Palace,0,0,D,0,0,D,A Madley,14,8,5,1,9,2,18,13,2,2,0,0 +15/10/2022,Fulham,Bournemouth,2,2,D,1,2,A,G Scott,19,10,4,6,5,6,9,11,2,0,0,0 +15/10/2022,Wolves,Nott'm Forest,1,0,H,0,0,D,T Bramall,9,10,2,2,4,5,17,13,2,2,0,0 +15/10/2022,Tottenham,Everton,2,0,H,0,0,D,P Tierney,21,4,7,0,7,1,12,13,1,4,0,0 +16/10/2022,Aston Villa,Chelsea,0,2,A,0,1,A,R Jones,18,8,7,4,7,1,12,11,0,3,0,0 +16/10/2022,Leeds,Arsenal,0,1,A,0,1,A,C Kavanagh,16,9,4,4,6,3,10,11,0,2,0,0 +16/10/2022,Man United,Newcastle,0,0,D,0,0,D,C Pawson,15,9,2,2,4,4,13,11,3,1,0,0 +16/10/2022,Southampton,West Ham,1,1,D,1,0,H,P Bankes,10,25,8,4,4,14,7,3,1,1,0,0 +16/10/2022,Liverpool,Man City,1,0,H,0,0,D,A Taylor,13,16,2,6,5,6,10,7,3,1,0,0 +18/10/2022,Brighton,Nott'm Forest,0,0,D,0,0,D,D England,19,3,7,0,9,1,10,14,3,1,0,0 +18/10/2022,Crystal Palace,Wolves,2,1,H,0,1,A,D Coote,14,10,4,4,4,3,14,18,3,1,0,0 +19/10/2022,Bournemouth,Southampton,0,1,A,0,1,A,J Brooks,15,9,3,3,7,3,8,13,1,3,0,0 +19/10/2022,Brentford,Chelsea,0,0,D,0,0,D,J Gillett,8,14,5,5,6,2,7,7,0,0,0,0 +19/10/2022,Liverpool,West Ham,1,0,H,1,0,H,S Attwell,22,8,7,3,8,3,9,8,0,0,0,0 +19/10/2022,Newcastle,Everton,1,0,H,1,0,H,T Harrington,16,1,4,0,8,2,9,12,2,4,0,0 +19/10/2022,Man United,Tottenham,2,0,H,0,0,D,S Hooper,28,9,10,2,8,3,7,7,1,0,0,0 +20/10/2022,Fulham,Aston Villa,3,0,H,1,0,H,M Oliver,18,12,8,6,9,7,11,11,0,2,0,1 +20/10/2022,Leicester,Leeds,2,0,H,2,0,H,P Bankes,5,14,1,2,2,9,15,11,2,1,0,0 +22/10/2022,Nott'm Forest,Liverpool,1,0,H,0,0,D,P Tierney,10,15,7,7,2,11,14,8,2,1,0,0 +22/10/2022,Everton,Crystal Palace,3,0,H,1,0,H,S Hooper,9,9,6,2,3,4,16,14,3,4,0,0 +22/10/2022,Man City,Brighton,3,1,H,2,0,H,C Pawson,10,8,4,2,7,3,11,12,1,3,0,0 +22/10/2022,Chelsea,Man United,1,1,D,0,0,D,S Attwell,6,13,2,6,6,4,8,10,1,4,0,0 +23/10/2022,Aston Villa,Brentford,4,0,H,3,0,H,D England,19,12,11,3,7,6,14,11,1,0,0,0 +23/10/2022,Leeds,Fulham,2,3,A,1,1,D,A Taylor,14,11,5,5,3,6,12,8,1,1,0,0 +23/10/2022,Southampton,Arsenal,1,1,D,0,1,A,R Jones,10,12,3,3,9,4,11,8,2,1,0,0 +23/10/2022,Wolves,Leicester,0,4,A,0,2,A,M Oliver,21,5,5,4,9,2,10,13,2,1,0,0 +23/10/2022,Tottenham,Newcastle,1,2,A,0,2,A,J Gillett,17,13,5,5,6,7,12,6,4,2,0,0 +24/10/2022,West Ham,Bournemouth,2,0,H,1,0,H,D Coote,20,5,4,3,5,0,9,11,0,2,0,0 +29/10/2022,Leicester,Man City,0,1,A,0,0,D,R Jones,10,15,5,5,3,10,5,10,0,0,0,0 +29/10/2022,Bournemouth,Tottenham,2,3,A,1,0,H,A Taylor,6,23,4,7,2,19,10,5,1,2,0,0 +29/10/2022,Brentford,Wolves,1,1,D,0,0,D,R Madley,12,14,3,4,3,6,10,17,3,3,0,1 +29/10/2022,Brighton,Chelsea,4,1,H,3,0,H,A Madley,19,15,9,7,8,7,16,6,1,2,0,0 +29/10/2022,Crystal Palace,Southampton,1,0,H,1,0,H,M Salisbury,12,14,3,4,8,4,13,12,3,3,0,0 +29/10/2022,Newcastle,Aston Villa,4,0,H,1,0,H,P Tierney,20,3,7,0,3,0,11,11,1,1,0,0 +29/10/2022,Fulham,Everton,0,0,D,0,0,D,J Brooks,24,9,6,4,13,4,10,8,2,1,0,0 +29/10/2022,Liverpool,Leeds,1,2,A,1,1,D,M Oliver,22,14,10,6,14,5,9,3,0,1,0,0 +30/10/2022,Arsenal,Nott'm Forest,5,0,H,1,0,H,S Hooper,24,5,10,2,9,4,9,8,0,1,0,0 +30/10/2022,Man United,West Ham,1,0,H,1,0,H,C Kavanagh,16,13,3,5,5,10,12,10,1,3,0,0 +05/11/2022,Leeds,Bournemouth,4,3,H,1,2,A,T Harrington,18,9,4,6,13,3,16,10,2,2,0,0 +05/11/2022,Man City,Fulham,2,1,H,1,1,D,D England,16,4,5,2,9,0,6,11,2,4,1,0 +05/11/2022,Nott'm Forest,Brentford,2,2,D,1,1,D,A Marriner,15,6,7,2,3,3,10,11,2,2,0,0 +05/11/2022,Wolves,Brighton,2,3,A,2,2,D,G Scott,8,19,5,8,4,9,15,11,2,0,1,0 +05/11/2022,Everton,Leicester,0,2,A,0,1,A,D Coote,12,22,2,8,8,3,9,5,0,0,0,0 +06/11/2022,Chelsea,Arsenal,0,1,A,0,0,D,M Oliver,5,14,1,2,4,6,20,13,5,2,0,0 +06/11/2022,Aston Villa,Man United,3,1,H,2,1,H,A Taylor,6,8,4,3,3,5,6,12,3,3,0,0 +06/11/2022,Southampton,Newcastle,1,4,A,0,1,A,S Attwell,16,7,5,4,8,2,7,10,1,0,0,0 +06/11/2022,West Ham,Crystal Palace,1,2,A,1,1,D,P Tierney,5,15,2,6,2,5,10,7,1,1,0,0 +06/11/2022,Tottenham,Liverpool,1,2,A,0,2,A,A Madley,14,13,5,6,5,3,10,8,0,0,0,0 +12/11/2022,Man City,Brentford,1,2,A,1,1,D,P Bankes,29,10,6,8,10,2,9,7,2,2,0,0 +12/11/2022,Bournemouth,Everton,3,0,H,2,0,H,C Pawson,16,15,9,3,3,5,12,8,2,0,0,0 +12/11/2022,Liverpool,Southampton,3,1,H,3,1,H,S Hooper,15,9,7,5,6,0,8,8,0,2,0,0 +12/11/2022,Nott'm Forest,Crystal Palace,1,0,H,0,0,D,J Brooks,9,8,2,0,1,5,11,13,1,4,0,0 +12/11/2022,Tottenham,Leeds,4,3,H,1,2,A,M Salisbury,14,12,8,5,9,2,8,7,1,1,0,1 +12/11/2022,West Ham,Leicester,0,2,A,0,1,A,J Gillett,18,9,6,6,7,2,9,8,3,1,0,0 +12/11/2022,Newcastle,Chelsea,1,0,H,0,0,D,R Jones,10,5,3,2,5,3,14,11,3,2,0,0 +12/11/2022,Wolves,Arsenal,0,2,A,0,0,D,S Attwell,11,14,2,4,6,4,5,6,3,2,0,0 +13/11/2022,Brighton,Aston Villa,1,2,A,1,1,D,C Kavanagh,7,8,2,2,11,4,14,11,2,7,0,0 +13/11/2022,Fulham,Man United,1,2,A,0,1,A,P Tierney,14,14,7,9,10,3,9,10,1,1,0,0 +26/12/2022,Brentford,Tottenham,2,2,D,1,0,H,D Coote,9,16,5,8,6,5,11,10,3,1,0,0 +26/12/2022,Crystal Palace,Fulham,0,3,A,0,1,A,A Madley,4,23,0,11,1,8,12,15,0,0,2,0 +26/12/2022,Everton,Wolves,1,2,A,1,1,D,C Pawson,12,7,6,4,2,2,9,22,1,6,0,0 +26/12/2022,Leicester,Newcastle,0,3,A,0,3,A,J Gillett,8,12,2,5,5,5,9,6,0,0,0,0 +26/12/2022,Southampton,Brighton,1,3,A,0,2,A,R Jones,14,7,4,4,4,2,9,10,1,2,0,0 +26/12/2022,Aston Villa,Liverpool,1,3,A,0,2,A,P Tierney,12,16,6,9,0,6,7,12,0,0,0,0 +26/12/2022,Arsenal,West Ham,3,1,H,0,1,A,M Oliver,16,8,5,4,7,5,13,17,0,2,0,0 +27/12/2022,Chelsea,Bournemouth,2,0,H,2,0,H,S Hooper,15,9,5,4,6,8,8,10,0,0,0,0 +27/12/2022,Man United,Nott'm Forest,3,0,H,2,0,H,A Taylor,17,8,8,3,9,9,13,4,3,1,0,0 +28/12/2022,Leeds,Man City,1,3,A,0,1,A,S Attwell,9,26,1,9,3,5,11,14,3,3,0,0 +30/12/2022,West Ham,Brentford,0,2,A,0,2,A,D England,20,9,5,6,8,1,9,5,1,0,0,0 +30/12/2022,Liverpool,Leicester,2,1,H,2,1,H,C Pawson,21,7,5,2,4,5,8,15,0,1,0,0 +31/12/2022,Wolves,Man United,0,1,A,0,0,D,R Jones,10,16,3,6,7,5,11,11,1,3,0,0 +31/12/2022,Bournemouth,Crystal Palace,0,2,A,0,2,A,A Marriner,6,15,2,6,2,7,17,11,4,1,0,0 +31/12/2022,Fulham,Southampton,2,1,H,1,0,H,G Scott,8,9,2,3,4,4,8,13,0,1,0,0 +31/12/2022,Man City,Everton,1,1,D,1,0,H,A Madley,16,2,3,1,7,1,13,14,3,4,0,0 +31/12/2022,Newcastle,Leeds,0,0,D,0,0,D,S Hooper,16,8,5,1,9,3,13,19,3,5,0,0 +31/12/2022,Brighton,Arsenal,2,4,A,0,2,A,A Taylor,9,14,5,7,7,4,7,11,3,3,0,0 +01/01/2023,Tottenham,Aston Villa,0,2,A,0,0,D,J Brooks,6,13,2,4,5,3,15,10,4,3,0,0 +01/01/2023,Nott'm Forest,Chelsea,1,1,D,0,1,A,P Bankes,12,7,5,2,1,2,16,14,2,2,0,0 +02/01/2023,Brentford,Liverpool,3,1,H,2,0,H,S Attwell,10,16,7,6,4,9,5,8,1,3,0,0 +03/01/2023,Arsenal,Newcastle,0,0,D,0,0,D,A Madley,17,8,4,1,5,5,10,16,4,4,0,0 +03/01/2023,Everton,Brighton,1,4,A,0,1,A,A Marriner,10,19,4,8,5,2,8,10,3,1,0,0 +03/01/2023,Leicester,Fulham,0,1,A,0,1,A,D Bond,15,11,6,2,6,3,12,11,2,6,0,0 +03/01/2023,Man United,Bournemouth,3,0,H,1,0,H,M Salisbury,18,7,6,4,6,5,13,7,0,3,0,0 +04/01/2023,Southampton,Nott'm Forest,0,1,A,0,1,A,T Bramall,8,11,0,1,6,2,14,17,2,2,0,0 +04/01/2023,Leeds,West Ham,2,2,D,1,1,D,D Coote,14,15,6,2,8,1,11,18,3,1,0,0 +04/01/2023,Aston Villa,Wolves,1,1,D,0,1,A,J Gillett,13,10,4,3,7,8,6,12,1,3,0,0 +04/01/2023,Crystal Palace,Tottenham,0,4,A,0,0,D,M Oliver,19,14,4,6,10,4,11,8,1,2,0,0 +05/01/2023,Chelsea,Man City,0,1,A,0,0,D,P Tierney,8,12,2,3,3,2,9,9,2,0,0,0 +12/01/2023,Fulham,Chelsea,2,1,H,1,0,H,D Coote,8,20,3,10,5,7,12,16,4,3,0,1 +13/01/2023,Aston Villa,Leeds,2,1,H,1,0,H,M Oliver,11,16,5,4,0,11,12,8,2,2,0,0 +14/01/2023,Man United,Man City,2,1,H,0,0,D,S Attwell,8,5,4,1,1,2,15,5,3,0,0,0 +14/01/2023,Brighton,Liverpool,3,0,H,0,0,D,D England,16,6,9,2,7,1,8,15,1,3,0,0 +14/01/2023,Everton,Southampton,1,2,A,1,0,H,J Brooks,12,13,4,5,6,4,7,19,0,3,0,0 +14/01/2023,Nott'm Forest,Leicester,2,0,H,0,0,D,P Tierney,15,6,5,1,3,5,11,12,1,2,0,0 +14/01/2023,Wolves,West Ham,1,0,H,0,0,D,S Hooper,17,16,4,4,4,3,10,7,0,2,0,0 +14/01/2023,Brentford,Bournemouth,2,0,H,1,0,H,J Gillett,12,7,4,2,5,3,5,9,1,1,0,0 +15/01/2023,Chelsea,Crystal Palace,1,0,H,0,0,D,P Bankes,15,10,5,5,11,7,10,17,2,5,0,0 +15/01/2023,Newcastle,Fulham,1,0,H,0,0,D,R Jones,20,5,5,0,10,5,6,14,1,3,0,0 +15/01/2023,Tottenham,Arsenal,0,2,A,0,2,A,C Pawson,17,14,7,5,4,3,16,15,4,2,0,0 +18/01/2023,Crystal Palace,Man United,1,1,D,0,1,A,R Jones,10,15,5,4,3,3,9,10,1,2,0,0 +19/01/2023,Man City,Tottenham,4,2,H,0,2,A,S Hooper,16,9,6,3,8,3,10,14,1,2,0,0 +21/01/2023,Liverpool,Chelsea,0,0,D,0,0,D,M Oliver,15,11,3,2,5,5,16,9,3,1,0,0 +21/01/2023,Bournemouth,Nott'm Forest,1,1,D,1,0,H,A Madley,16,20,4,5,4,6,10,7,1,2,0,0 +21/01/2023,Leicester,Brighton,2,2,D,1,1,D,T Bramall,9,13,2,4,5,6,9,9,0,1,0,0 +21/01/2023,Southampton,Aston Villa,0,1,A,0,0,D,M Salisbury,8,16,5,5,2,2,19,9,1,2,0,0 +21/01/2023,West Ham,Everton,2,0,H,2,0,H,S Attwell,11,5,4,2,4,9,9,6,1,1,0,0 +21/01/2023,Crystal Palace,Newcastle,0,0,D,0,0,D,C Pawson,6,16,1,7,3,15,13,7,4,2,0,0 +22/01/2023,Leeds,Brentford,0,0,D,0,0,D,P Bankes,9,6,5,0,2,3,10,18,2,2,0,0 +22/01/2023,Man City,Wolves,3,0,H,1,0,H,D Coote,13,10,7,1,5,7,10,10,1,2,0,0 +22/01/2023,Arsenal,Man United,3,2,H,1,1,D,A Taylor,25,6,5,4,12,4,8,9,1,2,0,0 +23/01/2023,Fulham,Tottenham,0,1,A,0,1,A,P Tierney,10,10,5,3,3,8,10,10,1,3,0,0 +03/02/2023,Chelsea,Fulham,0,0,D,0,0,D,S Attwell,12,10,3,4,6,2,16,12,1,4,0,0 +04/02/2023,Everton,Arsenal,1,0,H,0,0,D,D Coote,12,15,4,3,7,5,13,8,4,1,0,0 +04/02/2023,Aston Villa,Leicester,2,4,A,2,3,A,D England,19,9,4,5,11,3,10,10,0,2,0,0 +04/02/2023,Brentford,Southampton,3,0,H,2,0,H,D Bond,14,14,4,6,4,4,10,9,1,2,0,0 +04/02/2023,Brighton,Bournemouth,1,0,H,0,0,D,C Pawson,20,12,6,2,7,3,14,7,3,1,0,0 +04/02/2023,Man United,Crystal Palace,2,1,H,1,0,H,A Marriner,14,10,5,3,5,5,9,9,1,2,1,0 +04/02/2023,Wolves,Liverpool,3,0,H,2,0,H,P Tierney,12,22,6,4,2,7,9,7,1,1,0,0 +04/02/2023,Newcastle,West Ham,1,1,D,1,1,D,P Bankes,8,10,2,1,4,2,11,13,2,1,0,0 +05/02/2023,Nott'm Forest,Leeds,1,0,H,1,0,H,R Jones,6,10,2,4,0,2,15,14,4,2,0,0 +05/02/2023,Tottenham,Man City,1,0,H,1,0,H,A Madley,12,15,3,5,4,6,19,14,2,3,1,0 +08/02/2023,Man United,Leeds,2,2,D,0,1,A,S Hooper,24,8,7,2,6,5,7,11,1,3,0,0 +11/02/2023,West Ham,Chelsea,1,1,D,1,1,D,C Pawson,10,12,2,4,5,6,9,11,1,1,0,0 +11/02/2023,Arsenal,Brentford,1,1,D,0,0,D,P Bankes,23,9,7,2,7,4,9,9,0,2,0,0 +11/02/2023,Crystal Palace,Brighton,1,1,D,0,0,D,M Oliver,6,17,1,7,1,6,10,11,4,1,0,0 +11/02/2023,Fulham,Nott'm Forest,2,0,H,1,0,H,A Madley,16,10,3,1,5,4,9,11,0,1,0,0 +11/02/2023,Leicester,Tottenham,4,1,H,3,1,H,M Salisbury,15,11,7,4,1,5,14,9,5,2,0,0 +11/02/2023,Southampton,Wolves,1,2,A,1,0,H,J Gillett,17,11,2,2,5,2,12,14,2,2,0,1 +11/02/2023,Bournemouth,Newcastle,1,1,D,1,1,D,S Attwell,11,15,5,7,4,6,8,10,2,3,0,0 +12/02/2023,Leeds,Man United,0,2,A,0,0,D,P Tierney,16,11,6,5,7,3,10,13,2,1,0,0 +12/02/2023,Man City,Aston Villa,3,1,H,3,0,H,R Jones,17,6,9,2,8,4,13,14,1,2,0,0 +13/02/2023,Liverpool,Everton,2,0,H,1,0,H,S Hooper,15,6,6,1,3,3,8,16,1,3,0,0 +15/02/2023,Arsenal,Man City,1,3,A,1,1,D,A Taylor,10,9,1,6,1,3,11,15,2,3,0,0 +18/02/2023,Aston Villa,Arsenal,2,4,A,2,1,H,S Hooper,7,20,5,5,2,9,13,4,2,1,0,0 +18/02/2023,Brentford,Crystal Palace,1,1,D,0,0,D,P Tierney,12,10,5,4,2,6,10,8,2,2,0,0 +18/02/2023,Brighton,Fulham,0,1,A,0,0,D,D England,21,5,7,2,10,2,12,14,0,5,0,0 +18/02/2023,Chelsea,Southampton,0,1,A,0,1,A,D Coote,17,8,5,5,8,2,16,24,3,6,0,0 +18/02/2023,Everton,Leeds,1,0,H,0,0,D,A Madley,15,8,6,0,6,4,8,6,2,3,0,0 +18/02/2023,Nott'm Forest,Man City,1,1,D,0,1,A,G Scott,4,26,1,6,0,10,8,6,1,0,0,0 +18/02/2023,Wolves,Bournemouth,0,1,A,0,0,D,M Salisbury,15,5,4,1,12,2,12,10,3,3,0,0 +18/02/2023,Newcastle,Liverpool,0,2,A,0,2,A,A Taylor,14,13,4,7,6,7,3,8,1,1,1,0 +19/02/2023,Man United,Leicester,3,0,H,1,0,H,S Attwell,26,19,8,3,6,6,9,9,0,2,0,0 +19/02/2023,Tottenham,West Ham,2,0,H,0,0,D,M Oliver,16,6,6,1,4,7,16,10,2,1,0,0 +24/02/2023,Fulham,Wolves,1,1,D,0,1,A,M Oliver,10,8,5,2,4,3,12,17,2,0,0,0 +25/02/2023,Everton,Aston Villa,0,2,A,0,0,D,A Taylor,15,9,5,5,5,1,12,12,2,1,0,0 +25/02/2023,Leeds,Southampton,1,0,H,0,0,D,P Bankes,14,8,4,2,9,2,15,13,2,4,0,0 +25/02/2023,Leicester,Arsenal,0,1,A,0,0,D,C Pawson,1,10,0,2,0,8,14,9,0,1,0,0 +25/02/2023,West Ham,Nott'm Forest,4,0,H,0,0,D,J Gillett,18,8,5,3,12,3,8,5,0,0,0,0 +25/02/2023,Bournemouth,Man City,1,4,A,0,3,A,P Tierney,13,20,2,5,4,6,10,9,2,2,0,0 +25/02/2023,Crystal Palace,Liverpool,0,0,D,0,0,D,D England,6,12,0,4,5,3,11,14,2,5,0,0 +26/02/2023,Tottenham,Chelsea,2,0,H,0,0,D,S Attwell,8,10,3,2,4,4,10,10,2,3,0,0 +01/03/2023,Arsenal,Everton,4,0,H,2,0,H,M Oliver,15,8,5,5,5,1,5,12,0,2,0,0 +01/03/2023,Liverpool,Wolves,2,0,H,0,0,D,P Tierney,15,4,6,1,5,2,13,14,2,3,0,0 +04/03/2023,Man City,Newcastle,2,0,H,1,0,H,S Hooper,11,5,3,2,8,4,8,13,2,4,0,0 +04/03/2023,Arsenal,Bournemouth,3,2,H,0,1,A,C Kavanagh,31,4,9,4,17,1,6,8,0,2,0,0 +04/03/2023,Aston Villa,Crystal Palace,1,0,H,1,0,H,C Pawson,8,3,1,0,2,4,14,17,2,3,0,1 +04/03/2023,Brighton,West Ham,4,0,H,1,0,H,S Attwell,20,3,9,2,7,3,6,8,0,3,0,0 +04/03/2023,Chelsea,Leeds,1,0,H,0,0,D,M Oliver,13,11,3,2,5,7,10,7,2,0,0,0 +04/03/2023,Wolves,Tottenham,1,0,H,0,0,D,T Robinson,8,21,5,6,4,8,11,18,1,1,0,0 +04/03/2023,Southampton,Leicester,1,0,H,1,0,H,R Jones,11,11,5,0,3,4,12,11,4,2,0,0 +05/03/2023,Nott'm Forest,Everton,2,2,D,1,2,A,J Brooks,10,10,4,3,3,3,11,17,3,5,0,0 +05/03/2023,Liverpool,Man United,7,0,H,1,0,H,A Madley,18,8,8,4,4,3,13,14,2,3,0,0 +06/03/2023,Brentford,Fulham,3,2,H,1,1,D,A Taylor,14,9,6,5,2,3,12,17,0,3,0,0 +11/03/2023,Bournemouth,Liverpool,1,0,H,1,0,H,J Brooks,5,15,2,6,3,8,7,9,2,1,0,0 +11/03/2023,Everton,Brentford,1,0,H,1,0,H,S Hooper,13,12,6,4,5,8,11,12,2,1,0,0 +11/03/2023,Leeds,Brighton,2,2,D,1,1,D,P Tierney,12,14,4,2,4,7,12,9,1,1,0,0 +11/03/2023,Leicester,Chelsea,1,3,A,1,2,A,A Marriner,17,12,7,6,2,5,10,11,1,2,1,0 +11/03/2023,Tottenham,Nott'm Forest,3,1,H,2,0,H,C Pawson,15,9,7,5,2,10,13,13,3,1,0,0 +11/03/2023,Crystal Palace,Man City,0,1,A,0,0,D,R Jones,4,14,0,4,2,6,11,17,2,2,0,0 +12/03/2023,Fulham,Arsenal,0,3,A,0,3,A,D Coote,12,15,2,7,4,8,10,9,0,1,0,0 +12/03/2023,Man United,Southampton,0,0,D,0,0,D,A Taylor,10,17,4,4,7,8,12,9,2,0,0,0 +12/03/2023,West Ham,Aston Villa,1,1,D,1,1,D,C Kavanagh,17,12,6,4,13,2,8,6,1,0,0,0 +12/03/2023,Newcastle,Wolves,2,1,H,1,0,H,A Madley,19,7,8,4,10,3,11,10,1,2,0,0 +15/03/2023,Brighton,Crystal Palace,1,0,H,1,0,H,P Bankes,11,11,5,3,1,4,13,20,1,2,0,0 +15/03/2023,Southampton,Brentford,0,2,A,0,1,A,M Salisbury,7,11,1,5,3,3,14,10,1,2,0,0 +17/03/2023,Nott'm Forest,Newcastle,1,2,A,1,1,D,P Tierney,5,15,3,4,3,7,17,9,5,1,0,0 +18/03/2023,Aston Villa,Bournemouth,3,0,H,1,0,H,R Jones,20,10,9,3,9,6,8,13,1,4,0,0 +18/03/2023,Brentford,Leicester,1,1,D,1,0,H,D Bond,11,14,2,1,8,4,10,11,1,2,1,0 +18/03/2023,Southampton,Tottenham,3,3,D,0,1,A,S Hooper,19,17,7,3,10,3,13,8,1,1,0,0 +18/03/2023,Wolves,Leeds,2,4,A,0,1,A,M Salisbury,23,11,8,4,5,4,15,9,3,5,1,0 +18/03/2023,Chelsea,Everton,2,2,D,0,0,D,D England,20,12,7,3,8,2,8,12,2,2,0,0 +19/03/2023,Arsenal,Crystal Palace,4,1,H,2,0,H,S Attwell,15,8,5,4,5,4,8,10,0,1,0,0 +01/04/2023,Man City,Liverpool,4,1,H,1,1,D,S Hooper,17,4,8,1,7,1,9,12,1,1,0,0 +01/04/2023,Arsenal,Leeds,4,1,H,1,0,H,D England,13,7,6,5,4,3,11,13,0,2,0,0 +01/04/2023,Bournemouth,Fulham,2,1,H,0,1,A,P Bankes,12,10,7,2,4,3,15,13,1,4,0,0 +01/04/2023,Brighton,Brentford,3,3,D,2,2,D,M Oliver,33,7,15,5,14,0,16,10,2,1,0,0 +01/04/2023,Crystal Palace,Leicester,2,1,H,0,0,D,T Robinson,31,3,9,2,10,1,12,16,3,4,0,0 +01/04/2023,Nott'm Forest,Wolves,1,1,D,1,0,H,C Kavanagh,17,8,7,1,4,3,14,13,3,5,0,0 +01/04/2023,Chelsea,Aston Villa,0,2,A,0,1,A,A Madley,27,5,8,2,13,2,14,16,3,3,0,0 +02/04/2023,West Ham,Southampton,1,0,H,1,0,H,P Tierney,12,9,2,3,5,3,10,11,0,2,0,0 +02/04/2023,Newcastle,Man United,2,0,H,0,0,D,S Attwell,22,6,6,1,7,4,8,12,0,0,0,0 +03/04/2023,Everton,Tottenham,1,1,D,0,0,D,D Coote,15,8,6,2,3,3,10,14,0,3,1,1 +04/04/2023,Bournemouth,Brighton,0,2,A,0,1,A,D Bond,14,16,3,6,5,5,11,5,1,1,0,0 +04/04/2023,Leeds,Nott'm Forest,2,1,H,2,1,H,R Jones,21,13,6,1,11,4,11,13,1,4,0,0 +04/04/2023,Leicester,Aston Villa,1,2,A,1,1,D,G Scott,9,15,4,5,4,8,10,14,2,0,1,0 +04/04/2023,Chelsea,Liverpool,0,0,D,0,0,D,A Taylor,12,7,3,4,3,5,6,17,1,4,0,0 +05/04/2023,Man United,Brentford,1,0,H,1,0,H,J Brooks,18,6,3,1,12,3,8,13,3,2,0,0 +05/04/2023,West Ham,Newcastle,1,5,A,1,2,A,C Pawson,7,15,2,8,7,6,11,12,2,1,0,0 +08/04/2023,Man United,Everton,2,0,H,1,0,H,M Oliver,29,15,11,1,10,4,10,9,0,0,0,0 +08/04/2023,Aston Villa,Nott'm Forest,2,0,H,0,0,D,A Taylor,8,6,3,2,1,8,7,17,3,4,0,0 +08/04/2023,Brentford,Newcastle,1,2,A,1,0,H,C Kavanagh,12,10,6,4,6,5,10,15,0,0,0,0 +08/04/2023,Fulham,West Ham,0,1,A,0,1,A,J Gillett,16,9,3,5,11,4,10,12,1,1,0,0 +08/04/2023,Leicester,Bournemouth,0,1,A,0,1,A,D Coote,14,19,4,7,6,6,10,13,0,3,0,0 +08/04/2023,Tottenham,Brighton,2,1,H,1,1,D,S Attwell,9,17,3,4,3,7,15,15,3,1,0,0 +08/04/2023,Wolves,Chelsea,1,0,H,1,0,H,P Bankes,9,13,4,1,8,8,14,10,2,5,0,0 +08/04/2023,Southampton,Man City,1,4,A,0,1,A,R Jones,4,13,1,8,0,10,6,10,0,2,0,0 +09/04/2023,Leeds,Crystal Palace,1,5,A,1,1,D,S Hooper,11,16,7,8,2,4,11,8,3,1,0,0 +09/04/2023,Liverpool,Arsenal,2,2,D,1,2,A,P Tierney,21,9,6,5,5,4,11,11,4,4,0,0 +15/04/2023,Aston Villa,Newcastle,3,0,H,1,0,H,J Brooks,16,8,6,2,7,2,13,15,1,1,0,0 +15/04/2023,Chelsea,Brighton,1,2,A,1,1,D,R Jones,8,26,2,10,2,8,11,11,2,2,0,0 +15/04/2023,Everton,Fulham,1,3,A,1,1,D,A Taylor,15,21,7,7,4,8,10,6,3,2,0,0 +15/04/2023,Southampton,Crystal Palace,0,2,A,0,0,D,M Oliver,11,9,4,2,6,2,6,13,3,1,0,0 +15/04/2023,Tottenham,Bournemouth,2,3,A,1,1,D,A Madley,24,6,8,6,8,1,8,9,1,2,0,0 +15/04/2023,Wolves,Brentford,2,0,H,1,0,H,P Tierney,11,10,9,3,5,2,12,8,1,1,0,0 +15/04/2023,Man City,Leicester,3,1,H,3,0,H,D England,12,10,4,4,6,2,8,8,1,1,0,0 +16/04/2023,West Ham,Arsenal,2,2,D,1,2,A,D Coote,16,11,3,5,7,2,11,8,1,2,0,0 +16/04/2023,Nott'm Forest,Man United,0,2,A,0,1,A,S Hooper,6,22,0,8,4,9,9,9,2,2,0,0 +17/04/2023,Leeds,Liverpool,1,6,A,0,2,A,C Pawson,13,13,3,7,4,2,6,12,0,1,0,0 +21/04/2023,Arsenal,Southampton,3,3,D,1,2,A,S Hooper,25,8,6,6,7,2,10,14,1,5,0,0 +22/04/2023,Fulham,Leeds,2,1,H,0,0,D,P Bankes,12,10,5,2,8,6,10,15,1,4,0,0 +22/04/2023,Brentford,Aston Villa,1,1,D,0,0,D,M Salisbury,6,14,5,2,8,4,12,8,3,2,0,0 +22/04/2023,Crystal Palace,Everton,0,0,D,0,0,D,J Brooks,12,10,2,5,8,1,12,14,1,1,0,1 +22/04/2023,Leicester,Wolves,2,1,H,1,1,D,A Madley,15,16,8,3,1,7,17,16,1,3,0,0 +22/04/2023,Liverpool,Nott'm Forest,3,2,H,0,0,D,M Oliver,18,11,6,5,9,3,9,12,0,1,0,0 +23/04/2023,Bournemouth,West Ham,0,4,A,0,3,A,A Taylor,17,18,5,10,8,5,7,8,1,2,0,0 +23/04/2023,Newcastle,Tottenham,6,1,H,5,0,H,D Coote,25,11,8,3,9,3,7,13,0,3,0,0 +25/04/2023,Wolves,Crystal Palace,2,0,H,1,0,H,R Jones,9,14,3,4,6,11,13,14,5,4,0,0 +25/04/2023,Aston Villa,Fulham,1,0,H,1,0,H,T Bramall,14,1,3,0,8,2,6,18,3,2,0,0 +25/04/2023,Leeds,Leicester,1,1,D,1,0,H,P Tierney,13,15,3,3,3,6,14,15,4,2,0,0 +26/04/2023,Nott'm Forest,Brighton,3,1,H,1,1,D,J Gillett,12,17,5,7,2,7,7,8,2,0,0,0 +26/04/2023,Chelsea,Brentford,0,2,A,0,1,A,A Madley,15,7,4,1,5,2,5,13,1,2,0,0 +26/04/2023,West Ham,Liverpool,1,2,A,1,1,D,C Kavanagh,7,20,2,4,3,6,6,8,0,0,0,0 +26/04/2023,Man City,Arsenal,4,1,H,2,0,H,M Oliver,14,8,9,2,1,3,13,13,3,1,0,0 +27/04/2023,Everton,Newcastle,1,4,A,0,1,A,A Marriner,13,15,5,8,3,14,8,6,2,1,0,0 +27/04/2023,Southampton,Bournemouth,0,1,A,0,0,D,D England,11,16,2,2,6,4,9,11,0,3,0,0 +27/04/2023,Tottenham,Man United,2,2,D,0,2,A,A Taylor,18,17,7,8,6,8,8,7,1,2,0,0 +29/04/2023,Crystal Palace,West Ham,4,3,H,3,2,H,C Pawson,16,8,6,4,8,5,9,17,1,2,0,0 +29/04/2023,Brentford,Nott'm Forest,2,1,H,0,1,A,P Bankes,14,5,8,3,6,0,6,13,1,2,0,0 +29/04/2023,Brighton,Wolves,6,0,H,4,0,H,D Coote,22,10,8,2,2,5,14,11,1,1,0,0 +30/04/2023,Bournemouth,Leeds,4,1,H,2,1,H,C Kavanagh,12,15,7,6,2,6,12,14,1,0,0,0 +30/04/2023,Fulham,Man City,1,2,A,1,2,A,S Hooper,4,12,1,9,2,5,9,10,1,3,0,0 +30/04/2023,Man United,Aston Villa,1,0,H,1,0,H,J Gillett,14,7,6,1,1,4,16,7,2,0,0,0 +30/04/2023,Newcastle,Southampton,3,1,H,0,1,A,A Taylor,22,4,5,3,11,2,7,10,1,4,0,0 +30/04/2023,Liverpool,Tottenham,4,3,H,3,1,H,P Tierney,12,10,4,7,5,2,12,6,3,2,0,0 +01/05/2023,Leicester,Everton,2,2,D,2,1,H,M Oliver,15,23,6,8,2,9,10,12,4,1,0,0 +02/05/2023,Arsenal,Chelsea,3,1,H,3,0,H,R Jones,16,7,10,4,7,2,10,10,0,2,0,0 +03/05/2023,Liverpool,Fulham,1,0,H,1,0,H,S Attwell,15,9,3,3,7,5,10,8,0,0,0,0 +03/05/2023,Man City,West Ham,3,0,H,0,0,D,J Brooks,16,6,7,2,6,3,8,8,0,1,0,0 +04/05/2023,Brighton,Man United,1,0,H,0,0,D,A Marriner,22,16,6,5,8,6,10,14,4,4,0,0 +06/05/2023,Bournemouth,Chelsea,1,3,A,1,1,D,J Brooks,10,11,4,5,7,7,9,11,1,3,0,0 +06/05/2023,Man City,Leeds,2,1,H,2,0,H,A Madley,18,4,6,2,10,1,3,16,0,3,0,0 +06/05/2023,Tottenham,Crystal Palace,1,0,H,1,0,H,D England,8,7,3,2,8,4,15,14,3,5,0,0 +06/05/2023,Wolves,Aston Villa,1,0,H,1,0,H,S Attwell,6,16,2,3,5,6,20,9,3,3,0,0 +06/05/2023,Liverpool,Brentford,1,0,H,1,0,H,A Taylor,15,5,5,1,7,3,19,10,4,2,0,0 +07/05/2023,Newcastle,Arsenal,0,2,A,0,1,A,C Kavanagh,12,10,5,6,9,4,16,12,2,1,0,0 +07/05/2023,West Ham,Man United,1,0,H,1,0,H,P Bankes,15,19,4,4,5,6,4,9,0,2,0,0 +08/05/2023,Fulham,Leicester,5,3,H,3,0,H,R Jones,17,18,7,9,2,7,13,15,0,3,0,0 +08/05/2023,Brighton,Everton,1,5,A,0,3,A,S Hooper,23,10,5,5,15,1,11,13,1,5,0,0 +08/05/2023,Nott'm Forest,Southampton,4,3,H,3,1,H,M Oliver,9,19,4,5,2,11,16,10,1,0,0,0 +13/05/2023,Leeds,Newcastle,2,2,D,1,1,D,S Hooper,9,18,4,5,2,6,16,14,3,2,1,0 +13/05/2023,Aston Villa,Tottenham,2,1,H,1,0,H,P Bankes,8,5,4,2,8,3,9,14,3,3,0,0 +13/05/2023,Chelsea,Nott'm Forest,2,2,D,0,1,A,P Tierney,14,11,6,2,2,5,13,15,3,1,0,0 +13/05/2023,Crystal Palace,Bournemouth,2,0,H,1,0,H,M Salisbury,17,5,5,0,11,2,5,12,0,2,0,0 +13/05/2023,Man United,Wolves,2,0,H,1,0,H,J Brooks,27,5,9,0,11,7,9,9,3,2,0,0 +13/05/2023,Southampton,Fulham,0,2,A,0,0,D,T Bramall,5,9,1,4,1,8,17,12,2,0,0,0 +14/05/2023,Brentford,West Ham,2,0,H,2,0,H,M Oliver,24,4,10,4,7,5,5,5,1,0,0,0 +14/05/2023,Everton,Man City,0,3,A,0,2,A,A Taylor,7,9,3,4,8,6,12,4,1,0,0,0 +14/05/2023,Arsenal,Brighton,0,3,A,0,0,D,A Madley,14,12,2,6,5,2,13,17,1,2,0,0 +15/05/2023,Leicester,Liverpool,0,3,A,0,2,A,C Pawson,4,16,4,5,4,4,11,17,2,1,0,0 +18/05/2023,Newcastle,Brighton,4,1,H,2,0,H,R Jones,22,8,9,2,6,1,17,16,2,4,0,0 +20/05/2023,Tottenham,Brentford,1,3,A,1,0,H,J Gillett,22,11,8,4,8,2,11,9,1,1,0,0 +20/05/2023,Bournemouth,Man United,0,1,A,0,1,A,C Kavanagh,10,20,4,5,3,6,6,7,1,0,0,0 +20/05/2023,Fulham,Crystal Palace,2,2,D,1,1,D,J Smith,11,11,4,5,7,4,13,12,3,0,0,0 +20/05/2023,Liverpool,Aston Villa,1,1,D,0,1,A,J Brooks,10,6,5,3,9,4,16,13,3,5,0,0 +20/05/2023,Wolves,Everton,1,1,D,1,0,H,D Coote,13,19,5,4,1,7,18,10,5,1,0,0 +20/05/2023,Nott'm Forest,Arsenal,1,0,H,1,0,H,A Taylor,6,11,2,3,3,6,11,12,3,2,0,0 +21/05/2023,West Ham,Leeds,3,1,H,1,1,D,P Bankes,19,12,9,3,9,5,6,11,1,3,0,0 +21/05/2023,Brighton,Southampton,3,1,H,2,0,H,P Tierney,26,5,8,1,4,3,8,9,2,5,0,0 +21/05/2023,Man City,Chelsea,1,0,H,1,0,H,M Oliver,15,13,2,6,5,2,12,9,0,2,0,0 +22/05/2023,Newcastle,Leicester,0,0,D,0,0,D,A Marriner,23,1,4,1,12,1,9,5,2,0,0,0 +24/05/2023,Brighton,Man City,1,1,D,1,1,D,S Hooper,20,13,7,4,5,1,16,11,2,1,0,0 +25/05/2023,Man United,Chelsea,4,1,H,2,0,H,S Attwell,18,14,9,5,3,4,12,6,1,1,0,0 +28/05/2023,Arsenal,Wolves,5,0,H,3,0,H,A Marriner,14,6,8,0,8,4,8,11,0,0,0,0 +28/05/2023,Aston Villa,Brighton,2,1,H,2,1,H,D Coote,12,8,5,4,4,3,15,16,4,4,0,0 +28/05/2023,Brentford,Man City,1,0,H,0,0,D,J Brooks,11,17,4,3,3,4,12,8,4,0,0,0 +28/05/2023,Chelsea,Newcastle,1,1,D,1,1,D,J Gillett,22,13,5,4,10,3,9,11,0,0,0,0 +28/05/2023,Crystal Palace,Nott'm Forest,1,1,D,0,1,A,T Bramall,16,7,4,4,5,4,9,13,0,2,0,0 +28/05/2023,Everton,Bournemouth,1,0,H,0,0,D,S Attwell,13,7,6,2,9,3,11,12,1,3,0,0 +28/05/2023,Leeds,Tottenham,1,4,A,0,1,A,A Taylor,19,11,2,7,12,3,7,5,3,0,0,0 +28/05/2023,Leicester,West Ham,2,1,H,1,0,H,S Hooper,13,16,4,3,3,5,8,10,1,1,0,0 +28/05/2023,Man United,Fulham,2,1,H,1,1,D,R Jones,21,10,8,3,5,4,14,10,1,2,0,0 +28/05/2023,Southampton,Liverpool,4,4,D,2,2,D,D England,15,30,10,8,2,9,4,10,0,2,0,0 +11/08/2023,Burnley,Man City,0,3,A,0,2,A,C Pawson,6,17,1,8,6,5,11,8,0,0,1,0 +12/08/2023,Arsenal,Nott'm Forest,2,1,H,2,0,H,M Oliver,15,6,7,2,8,3,12,12,2,2,0,0 +12/08/2023,Bournemouth,West Ham,1,1,D,0,0,D,P Bankes,14,16,5,3,10,4,9,14,1,4,0,0 +12/08/2023,Brighton,Luton,4,1,H,1,0,H,D Coote,27,9,12,3,6,7,11,12,2,2,0,0 +12/08/2023,Everton,Fulham,0,1,A,0,0,D,S Attwell,19,9,9,2,10,4,12,6,0,2,0,0 +12/08/2023,Sheffield United,Crystal Palace,0,1,A,0,0,D,J Brooks,8,24,1,8,5,5,18,11,3,0,0,0 +12/08/2023,Newcastle,Aston Villa,5,1,H,2,1,H,A Madley,17,16,13,6,6,5,12,17,4,4,0,0 +13/08/2023,Brentford,Tottenham,2,2,D,2,2,D,R Jones,11,18,6,6,3,6,12,13,1,4,0,0 +13/08/2023,Chelsea,Liverpool,1,1,D,1,1,D,A Taylor,10,13,4,1,4,4,5,12,3,3,0,0 +14/08/2023,Man United,Wolves,1,0,H,0,0,D,S Hooper,15,23,3,6,8,7,14,10,2,3,0,0 +18/08/2023,Nott'm Forest,Sheffield United,2,1,H,1,0,H,P Bankes,16,7,4,3,6,7,5,9,2,3,0,0 +19/08/2023,Fulham,Brentford,0,3,A,0,1,A,D Bond,10,17,2,8,5,5,13,12,2,2,1,0 +19/08/2023,Liverpool,Bournemouth,3,1,H,2,1,H,T Bramall,26,13,10,5,10,2,11,13,1,3,1,0 +19/08/2023,Wolves,Brighton,1,4,A,0,1,A,A Madley,16,16,5,8,5,3,14,11,3,6,1,0 +19/08/2023,Tottenham,Man United,2,0,H,0,0,D,M Oliver,17,22,6,6,5,6,9,8,1,3,0,0 +19/08/2023,Man City,Newcastle,1,0,H,1,0,H,R Jones,14,7,4,1,3,0,12,11,1,5,0,0 +20/08/2023,Aston Villa,Everton,4,0,H,2,0,H,A Taylor,13,9,7,2,7,6,8,12,3,4,0,0 +20/08/2023,West Ham,Chelsea,3,1,H,1,1,D,J Brooks,12,17,6,4,3,9,12,9,2,3,1,0 +21/08/2023,Crystal Palace,Arsenal,0,1,A,0,0,D,D Coote,14,14,2,3,1,8,14,10,2,1,0,1 +25/08/2023,Chelsea,Luton,3,0,H,1,0,H,R Jones,19,11,8,1,6,4,15,12,2,3,0,0 +26/08/2023,Bournemouth,Tottenham,0,2,A,0,1,A,T Robinson,11,17,3,6,6,2,18,11,1,3,0,0 +26/08/2023,Arsenal,Fulham,2,2,D,0,1,A,P Tierney,19,8,11,3,8,3,6,5,0,3,0,1 +26/08/2023,Brentford,Crystal Palace,1,1,D,1,0,H,P Bankes,12,15,1,5,8,6,9,15,0,0,0,0 +26/08/2023,Everton,Wolves,0,1,A,0,0,D,C Pawson,15,11,7,2,7,0,10,14,3,4,0,0 +26/08/2023,Man United,Nott'm Forest,3,2,H,1,2,A,S Attwell,18,9,9,4,11,3,13,11,4,4,0,1 +26/08/2023,Brighton,West Ham,1,3,A,0,1,A,A Taylor,25,12,10,3,17,4,10,9,1,3,0,0 +27/08/2023,Burnley,Aston Villa,1,3,A,0,2,A,M Salisbury,9,16,2,6,4,6,12,11,2,0,0,0 +27/08/2023,Sheffield United,Man City,1,2,A,0,0,D,J Gillett,6,30,2,9,1,12,11,2,3,0,0,0 +27/08/2023,Newcastle,Liverpool,1,2,A,1,0,H,J Brooks,23,9,8,4,5,9,16,10,1,2,0,1 +01/09/2023,Luton,West Ham,1,2,A,0,1,A,P Tierney,16,9,1,3,9,6,8,13,1,1,0,0 +02/09/2023,Sheffield United,Everton,2,2,D,2,1,H,A Madley,13,16,8,6,4,6,11,13,1,2,0,0 +02/09/2023,Brentford,Bournemouth,2,2,D,1,1,D,R Madley,21,12,7,5,5,2,6,13,1,2,0,0 +02/09/2023,Burnley,Tottenham,2,5,A,1,2,A,D England,16,20,4,10,7,4,15,9,4,3,0,0 +02/09/2023,Chelsea,Nott'm Forest,0,1,A,0,0,D,T Robinson,21,7,2,3,8,0,10,9,3,4,0,0 +02/09/2023,Man City,Fulham,5,1,H,2,1,H,M Oliver,7,6,5,4,4,5,12,12,2,4,0,0 +02/09/2023,Brighton,Newcastle,3,1,H,1,0,H,S Attwell,15,9,6,2,5,3,14,17,4,4,0,0 +03/09/2023,Crystal Palace,Wolves,3,2,H,0,0,D,R Jones,16,12,11,4,4,2,12,12,1,3,0,0 +03/09/2023,Liverpool,Aston Villa,3,0,H,2,0,H,S Hooper,17,9,4,3,7,3,12,7,0,1,0,0 +03/09/2023,Arsenal,Man United,3,1,H,1,1,D,A Taylor,17,10,5,2,12,3,8,7,2,3,0,0 +16/09/2023,Wolves,Liverpool,1,3,A,1,0,H,M Oliver,11,16,2,6,4,4,10,4,1,3,0,0 +16/09/2023,Aston Villa,Crystal Palace,3,1,H,0,0,D,D England,16,6,5,3,4,2,13,9,5,3,0,0 +16/09/2023,Fulham,Luton,1,0,H,0,0,D,M Salisbury,13,7,2,2,6,2,6,14,2,2,0,0 +16/09/2023,Man United,Brighton,1,3,A,0,1,A,J Gillett,14,10,4,8,8,1,8,9,2,2,0,0 +16/09/2023,Tottenham,Sheffield United,2,1,H,0,0,D,P Bankes,28,7,10,5,15,2,13,14,6,5,0,1 +16/09/2023,West Ham,Man City,1,3,A,1,0,H,A Madley,6,29,3,15,4,11,7,11,3,2,0,0 +16/09/2023,Newcastle,Brentford,1,0,H,0,0,D,C Pawson,9,11,2,2,8,3,8,12,3,5,0,0 +17/09/2023,Bournemouth,Chelsea,0,0,D,0,0,D,D Coote,13,14,4,6,1,7,15,20,1,5,0,0 +17/09/2023,Everton,Arsenal,0,1,A,0,0,D,S Hooper,8,13,1,4,1,11,12,10,1,1,0,0 +18/09/2023,Nott'm Forest,Burnley,1,1,D,0,1,A,R Jones,14,10,4,3,5,4,16,10,2,3,0,1 +23/09/2023,Crystal Palace,Fulham,0,0,D,0,0,D,P Tierney,7,10,3,5,3,2,10,15,3,2,0,0 +23/09/2023,Luton,Wolves,1,1,D,0,0,D,J Smith,20,3,4,3,10,1,12,17,0,2,0,1 +23/09/2023,Man City,Nott'm Forest,2,0,H,2,0,H,A Taylor,7,10,4,3,6,6,5,17,3,7,1,0 +23/09/2023,Brentford,Everton,1,3,A,1,1,D,M Oliver,12,18,2,6,1,4,9,12,2,2,0,0 +23/09/2023,Burnley,Man United,0,1,A,0,1,A,T Harrington,12,11,4,4,9,5,6,12,1,2,0,0 +24/09/2023,Arsenal,Tottenham,2,2,D,1,1,D,R Jones,13,13,6,5,11,4,12,19,3,4,0,0 +24/09/2023,Brighton,Bournemouth,3,1,H,1,1,D,J Brooks,13,12,4,4,4,4,12,17,3,3,0,0 +24/09/2023,Chelsea,Aston Villa,0,1,A,0,0,D,J Gillett,10,15,4,7,5,11,9,11,2,3,0,0 +24/09/2023,Liverpool,West Ham,3,1,H,1,1,D,C Kavanagh,22,11,7,4,7,4,12,13,0,2,0,0 +24/09/2023,Sheffield United,Newcastle,0,8,A,0,3,A,S Attwell,9,22,1,15,2,6,12,8,3,0,0,0 +30/09/2023,Aston Villa,Brighton,6,1,H,3,0,H,A Madley,19,11,9,3,1,3,19,21,4,4,0,0 +30/09/2023,Bournemouth,Arsenal,0,4,A,0,2,A,M Salisbury,8,15,1,8,6,6,11,10,0,1,0,0 +30/09/2023,Everton,Luton,1,2,A,1,2,A,A Taylor,23,9,5,2,3,6,8,10,0,1,0,0 +30/09/2023,Man United,Crystal Palace,0,1,A,0,1,A,C Kavanagh,19,8,4,2,10,4,10,8,3,2,0,0 +30/09/2023,Newcastle,Burnley,2,0,H,1,0,H,T Bramall,20,8,8,2,5,3,7,15,1,3,0,0 +30/09/2023,West Ham,Sheffield United,2,0,H,2,0,H,G Scott,20,16,9,2,7,4,6,9,1,1,0,0 +30/09/2023,Wolves,Man City,2,1,H,1,0,H,C Pawson,3,23,1,8,0,6,18,12,5,4,0,0 +30/09/2023,Tottenham,Liverpool,2,1,H,1,1,D,S Hooper,24,12,8,4,12,5,11,17,5,4,0,2 +01/10/2023,Nott'm Forest,Brentford,1,1,D,0,0,D,P Tierney,6,18,1,5,1,11,6,10,1,2,1,0 +02/10/2023,Fulham,Chelsea,0,2,A,0,2,A,T Robinson,10,11,3,4,8,1,15,12,1,4,0,0 +03/10/2023,Luton,Burnley,1,2,A,0,1,A,P Bankes,18,14,3,4,7,6,14,6,4,2,0,0 +07/10/2023,Luton,Tottenham,0,1,A,0,0,D,J Brooks,12,15,2,4,5,6,16,7,2,1,0,1 +07/10/2023,Burnley,Chelsea,1,4,A,1,1,D,S Attwell,10,9,3,5,7,3,11,8,2,4,0,0 +07/10/2023,Everton,Bournemouth,3,0,H,2,0,H,D Coote,25,11,8,4,8,7,14,15,1,2,0,0 +07/10/2023,Fulham,Sheffield United,3,1,H,0,0,D,S Barrott,20,5,6,2,9,4,8,10,1,2,0,0 +07/10/2023,Man United,Brentford,2,1,H,0,1,A,A Madley,21,11,8,3,7,5,10,17,0,5,0,0 +07/10/2023,Crystal Palace,Nott'm Forest,0,0,D,0,0,D,C Pawson,8,16,2,5,3,7,7,12,0,1,0,0 +08/10/2023,Brighton,Liverpool,2,2,D,1,2,A,A Taylor,14,14,3,4,8,1,12,20,1,2,0,0 +08/10/2023,West Ham,Newcastle,2,2,D,1,0,H,P Bankes,5,10,3,3,4,2,11,18,3,3,0,0 +08/10/2023,Wolves,Aston Villa,1,1,D,0,0,D,R Jones,8,18,3,4,2,10,19,9,2,2,1,0 +08/10/2023,Arsenal,Man City,1,0,H,0,0,D,M Oliver,12,4,2,1,5,4,8,7,2,3,0,0 +21/10/2023,Liverpool,Everton,2,0,H,0,0,D,C Pawson,26,6,6,1,12,4,9,9,1,1,0,1 +21/10/2023,Bournemouth,Wolves,1,2,A,1,0,H,P Tierney,7,20,4,7,3,12,15,10,2,6,1,0 +21/10/2023,Brentford,Burnley,3,0,H,1,0,H,J Smith,23,6,10,1,5,5,14,12,5,2,0,1 +21/10/2023,Man City,Brighton,2,1,H,2,0,H,R Jones,10,5,6,3,2,2,9,11,2,2,1,0 +21/10/2023,Newcastle,Crystal Palace,4,0,H,3,0,H,T Robinson,10,17,7,3,6,8,7,12,2,3,0,0 +21/10/2023,Nott'm Forest,Luton,2,2,D,0,0,D,S Barrott,19,13,8,4,4,2,12,11,3,0,0,0 +21/10/2023,Chelsea,Arsenal,2,2,D,1,0,H,C Kavanagh,11,13,5,3,2,7,7,14,3,3,0,0 +21/10/2023,Sheffield United,Man United,1,2,A,1,1,D,M Oliver,12,14,6,5,5,4,12,10,3,1,0,0 +22/10/2023,Aston Villa,West Ham,4,1,H,1,0,H,D Coote,15,14,7,4,8,7,9,15,0,1,0,0 +23/10/2023,Tottenham,Fulham,2,0,H,1,0,H,A Taylor,15,10,5,3,3,5,10,14,2,0,0,0 +27/10/2023,Crystal Palace,Tottenham,1,2,A,0,0,D,A Madley,13,10,3,1,11,2,19,11,2,1,0,0 +28/10/2023,Chelsea,Brentford,0,2,A,0,0,D,S Hooper,17,7,2,5,10,1,12,7,1,4,0,0 +28/10/2023,Arsenal,Sheffield United,5,0,H,1,0,H,T Robinson,13,2,8,0,6,1,9,13,0,3,0,0 +28/10/2023,Bournemouth,Burnley,2,1,H,1,1,D,S Barrott,13,6,6,3,10,5,9,12,2,3,0,0 +28/10/2023,Wolves,Newcastle,2,2,D,1,2,A,A Taylor,11,13,6,5,8,7,13,15,1,4,0,0 +29/10/2023,West Ham,Everton,0,1,A,0,0,D,S Attwell,12,10,2,4,4,3,7,11,4,1,0,0 +29/10/2023,Aston Villa,Luton,3,1,H,1,0,H,J Brooks,17,7,6,1,6,4,11,10,3,2,0,0 +29/10/2023,Brighton,Fulham,1,1,D,1,0,H,M Salisbury,18,10,7,5,7,3,12,8,0,3,0,0 +29/10/2023,Liverpool,Nott'm Forest,3,0,H,2,0,H,C Salisbury,21,9,8,1,8,3,9,13,2,3,0,0 +29/10/2023,Man United,Man City,0,3,A,0,1,A,P Tierney,7,21,3,10,7,12,9,4,4,1,0,0 +11/08/2023,Burnley,Man City,0,3,A,0,2,A,C Pawson,6,17,1,8,11,8,6,5,0,0,1,0 +12/08/2023,Arsenal,Nott'm Forest,2,1,H,2,0,H,M Oliver,15,6,7,2,12,12,8,3,2,2,0,0 +12/08/2023,Bournemouth,West Ham,1,1,D,0,0,D,P Bankes,14,16,5,3,9,14,10,4,1,4,0,0 +12/08/2023,Brighton,Luton,4,1,H,1,0,H,D Coote,27,9,12,3,11,12,6,7,2,2,0,0 +12/08/2023,Everton,Fulham,0,1,A,0,0,D,S Attwell,19,9,9,2,12,6,10,4,0,2,0,0 +12/08/2023,Sheffield United,Crystal Palace,0,1,A,0,0,D,J Brooks,8,24,1,8,18,11,5,5,3,0,0,0 +12/08/2023,Newcastle,Aston Villa,5,1,H,2,1,H,A Madley,17,16,13,6,12,17,6,5,4,4,0,0 +13/08/2023,Brentford,Tottenham,2,2,D,2,2,D,R Jones,11,18,6,6,12,13,3,6,1,4,0,0 +13/08/2023,Chelsea,Liverpool,1,1,D,1,1,D,A Taylor,10,13,4,1,5,12,4,4,3,3,0,0 +14/08/2023,Man United,Wolves,1,0,H,0,0,D,S Hooper,15,23,3,6,14,10,8,7,2,3,0,0 +18/08/2023,Nott'm Forest,Sheffield United,2,1,H,1,0,H,P Bankes,16,7,4,3,5,9,6,7,2,3,0,0 +19/08/2023,Fulham,Brentford,0,3,A,0,1,A,D Bond,10,17,2,8,13,12,5,5,2,2,1,0 +19/08/2023,Liverpool,Bournemouth,3,1,H,2,1,H,T Bramall,26,13,10,5,11,13,10,2,1,3,1,0 +19/08/2023,Wolves,Brighton,1,4,A,0,1,A,A Madley,16,16,5,8,14,11,5,3,3,6,1,0 +19/08/2023,Tottenham,Man United,2,0,H,0,0,D,M Oliver,17,22,6,6,9,8,5,6,1,3,0,0 +19/08/2023,Man City,Newcastle,1,0,H,1,0,H,R Jones,14,7,4,1,12,11,3,0,1,5,0,0 +20/08/2023,Aston Villa,Everton,4,0,H,2,0,H,A Taylor,13,9,7,2,8,12,7,6,3,4,0,0 +20/08/2023,West Ham,Chelsea,3,1,H,1,1,D,J Brooks,12,17,6,4,12,9,3,9,2,3,1,0 +21/08/2023,Crystal Palace,Arsenal,0,1,A,0,0,D,D Coote,14,14,2,3,14,10,1,8,2,1,0,1 +25/08/2023,Chelsea,Luton,3,0,H,1,0,H,R Jones,19,11,8,1,15,12,6,4,2,3,0,0 +26/08/2023,Bournemouth,Tottenham,0,2,A,0,1,A,T Robinson,11,17,3,6,18,11,6,2,1,3,0,0 +26/08/2023,Arsenal,Fulham,2,2,D,0,1,A,P Tierney,19,8,11,3,6,5,8,3,0,3,0,1 +26/08/2023,Brentford,Crystal Palace,1,1,D,1,0,H,P Bankes,12,15,1,5,9,15,8,6,0,0,0,0 +26/08/2023,Everton,Wolves,0,1,A,0,0,D,C Pawson,15,11,7,2,10,14,7,0,3,4,0,0 +26/08/2023,Man United,Nott'm Forest,3,2,H,1,2,A,S Attwell,18,9,9,4,13,11,11,3,4,4,0,1 +26/08/2023,Brighton,West Ham,1,3,A,0,1,A,A Taylor,25,12,10,3,10,9,17,4,1,3,0,0 +27/08/2023,Burnley,Aston Villa,1,3,A,0,2,A,M Salisbury,9,16,2,6,12,11,4,6,2,0,0,0 +27/08/2023,Sheffield United,Man City,1,2,A,0,0,D,J Gillett,6,30,2,9,11,2,1,12,3,0,0,0 +27/08/2023,Newcastle,Liverpool,1,2,A,1,0,H,J Brooks,23,9,8,4,16,10,5,9,1,2,0,1 +01/09/2023,Luton,West Ham,1,2,A,0,1,A,P Tierney,16,9,1,3,8,13,9,6,1,1,0,0 +02/09/2023,Sheffield United,Everton,2,2,D,2,1,H,A Madley,13,16,8,6,11,13,4,6,1,2,0,0 +02/09/2023,Brentford,Bournemouth,2,2,D,1,1,D,R Madley,21,12,7,5,6,13,5,2,1,2,0,0 +02/09/2023,Burnley,Tottenham,2,5,A,1,2,A,D England,16,20,4,10,15,9,7,4,4,3,0,0 +02/09/2023,Chelsea,Nott'm Forest,0,1,A,0,0,D,T Robinson,21,7,2,3,10,9,8,0,3,4,0,0 +02/09/2023,Man City,Fulham,5,1,H,2,1,H,M Oliver,7,6,5,4,12,12,4,5,2,4,0,0 +02/09/2023,Brighton,Newcastle,3,1,H,1,0,H,S Attwell,15,9,6,2,14,17,5,3,4,4,0,0 +03/09/2023,Crystal Palace,Wolves,3,2,H,0,0,D,R Jones,16,12,11,4,12,12,4,2,1,3,0,0 +03/09/2023,Liverpool,Aston Villa,3,0,H,2,0,H,S Hooper,17,9,4,3,12,7,7,3,0,1,0,0 +03/09/2023,Arsenal,Man United,3,1,H,1,1,D,A Taylor,17,10,5,2,8,7,12,3,2,3,0,0 +16/09/2023,Wolves,Liverpool,1,3,A,1,0,H,M Oliver,11,16,2,6,10,4,4,4,1,3,0,0 +16/09/2023,Aston Villa,Crystal Palace,3,1,H,0,0,D,D England,16,6,5,3,13,9,4,2,5,3,0,0 +16/09/2023,Fulham,Luton,1,0,H,0,0,D,M Salisbury,13,7,2,2,6,14,6,2,2,2,0,0 +16/09/2023,Man United,Brighton,1,3,A,0,1,A,J Gillett,14,10,4,8,8,9,8,1,2,2,0,0 +16/09/2023,Tottenham,Sheffield United,2,1,H,0,0,D,P Bankes,28,7,10,5,13,14,15,2,6,5,0,1 +16/09/2023,West Ham,Man City,1,3,A,1,0,H,A Madley,6,29,3,15,7,11,4,11,3,2,0,0 +16/09/2023,Newcastle,Brentford,1,0,H,0,0,D,C Pawson,9,11,2,2,8,12,8,3,3,5,0,0 +17/09/2023,Bournemouth,Chelsea,0,0,D,0,0,D,D Coote,13,14,4,6,15,20,1,7,1,5,0,0 +17/09/2023,Everton,Arsenal,0,1,A,0,0,D,S Hooper,8,13,1,4,12,10,1,11,1,1,0,0 +18/09/2023,Nott'm Forest,Burnley,1,1,D,0,1,A,R Jones,14,10,4,3,16,10,5,4,2,3,0,1 +23/09/2023,Crystal Palace,Fulham,0,0,D,0,0,D,P Tierney,7,10,3,5,10,15,3,2,3,2,0,0 +23/09/2023,Luton,Wolves,1,1,D,0,0,D,J Smith,20,3,4,3,12,17,10,1,0,2,0,1 +23/09/2023,Man City,Nott'm Forest,2,0,H,2,0,H,A Taylor,7,10,4,3,5,17,6,6,3,7,1,0 +23/09/2023,Brentford,Everton,1,3,A,1,1,D,M Oliver,12,18,2,6,9,12,1,4,2,2,0,0 +23/09/2023,Burnley,Man United,0,1,A,0,1,A,T Harrington,12,11,4,4,6,12,9,5,1,2,0,0 +24/09/2023,Arsenal,Tottenham,2,2,D,1,1,D,R Jones,13,13,6,5,12,19,11,4,3,4,0,0 +24/09/2023,Brighton,Bournemouth,3,1,H,1,1,D,J Brooks,13,12,4,4,12,17,4,4,3,3,0,0 +24/09/2023,Chelsea,Aston Villa,0,1,A,0,0,D,J Gillett,10,15,4,7,9,11,5,11,2,3,0,0 +24/09/2023,Liverpool,West Ham,3,1,H,1,1,D,C Kavanagh,22,11,7,4,12,13,7,4,0,2,0,0 +24/09/2023,Sheffield United,Newcastle,0,8,A,0,3,A,S Attwell,9,22,1,15,12,8,2,6,3,0,0,0 +30/09/2023,Aston Villa,Brighton,6,1,H,3,0,H,A Madley,19,11,9,3,19,21,1,3,4,4,0,0 +30/09/2023,Bournemouth,Arsenal,0,4,A,0,2,A,M Salisbury,8,15,1,8,11,10,6,6,0,1,0,0 +30/09/2023,Everton,Luton,1,2,A,1,2,A,A Taylor,23,9,5,2,8,10,3,6,0,1,0,0 +30/09/2023,Man United,Crystal Palace,0,1,A,0,1,A,C Kavanagh,19,8,4,2,10,8,10,4,3,2,0,0 +30/09/2023,Newcastle,Burnley,2,0,H,1,0,H,T Bramall,20,8,8,2,7,15,5,3,1,3,0,0 +30/09/2023,West Ham,Sheffield United,2,0,H,2,0,H,G Scott,20,16,9,2,6,9,7,4,1,1,0,0 +30/09/2023,Wolves,Man City,2,1,H,1,0,H,C Pawson,3,23,1,8,18,12,0,6,5,4,0,0 +30/09/2023,Tottenham,Liverpool,2,1,H,1,1,D,S Hooper,24,12,8,4,11,17,12,5,5,4,0,2 +01/10/2023,Nott'm Forest,Brentford,1,1,D,0,0,D,P Tierney,6,18,1,5,6,10,1,11,1,2,1,0 +02/10/2023,Fulham,Chelsea,0,2,A,0,2,A,T Robinson,10,11,3,4,15,12,8,1,1,4,0,0 +03/10/2023,Luton,Burnley,1,2,A,0,1,A,P Bankes,18,14,3,4,14,6,7,6,4,2,0,0 +07/10/2023,Luton,Tottenham,0,1,A,0,0,D,J Brooks,12,15,2,4,16,7,5,6,2,1,0,1 +07/10/2023,Burnley,Chelsea,1,4,A,1,1,D,S Attwell,10,9,3,5,11,8,7,3,2,4,0,0 +07/10/2023,Everton,Bournemouth,3,0,H,2,0,H,D Coote,25,11,8,4,14,15,8,7,1,2,0,0 +07/10/2023,Fulham,Sheffield United,3,1,H,0,0,D,S Barrott,20,5,6,2,8,10,9,4,1,2,0,0 +07/10/2023,Man United,Brentford,2,1,H,0,1,A,A Madley,21,11,8,3,10,17,7,5,0,5,0,0 +07/10/2023,Crystal Palace,Nott'm Forest,0,0,D,0,0,D,C Pawson,8,16,2,5,7,12,3,7,0,1,0,0 +08/10/2023,Brighton,Liverpool,2,2,D,1,2,A,A Taylor,14,14,3,4,12,20,8,1,1,2,0,0 +08/10/2023,West Ham,Newcastle,2,2,D,1,0,H,P Bankes,5,10,3,3,11,18,4,2,3,3,0,0 +08/10/2023,Wolves,Aston Villa,1,1,D,0,0,D,R Jones,8,18,3,4,19,9,2,10,2,2,1,0 +08/10/2023,Arsenal,Man City,1,0,H,0,0,D,M Oliver,12,4,2,1,8,7,5,4,2,3,0,0 +21/10/2023,Liverpool,Everton,2,0,H,0,0,D,C Pawson,26,6,6,1,9,9,12,4,1,1,0,1 +21/10/2023,Bournemouth,Wolves,1,2,A,1,0,H,P Tierney,7,20,4,7,15,10,3,12,2,6,1,0 +21/10/2023,Brentford,Burnley,3,0,H,1,0,H,J Smith,23,6,10,1,14,12,5,5,5,2,0,1 +21/10/2023,Man City,Brighton,2,1,H,2,0,H,R Jones,10,5,6,3,9,11,2,2,2,2,1,0 +21/10/2023,Newcastle,Crystal Palace,4,0,H,3,0,H,T Robinson,10,17,7,3,7,12,6,8,2,3,0,0 +21/10/2023,Nott'm Forest,Luton,2,2,D,0,0,D,S Barrott,19,13,8,4,12,11,4,2,3,0,0,0 +21/10/2023,Chelsea,Arsenal,2,2,D,1,0,H,C Kavanagh,11,13,5,3,7,14,2,7,3,3,0,0 +21/10/2023,Sheffield United,Man United,1,2,A,1,1,D,M Oliver,12,14,6,5,12,10,5,4,3,1,0,0 +22/10/2023,Aston Villa,West Ham,4,1,H,1,0,H,D Coote,15,14,7,4,9,15,8,7,0,1,0,0 +23/10/2023,Tottenham,Fulham,2,0,H,1,0,H,A Taylor,15,10,5,3,10,14,3,5,2,0,0,0 +27/10/2023,Crystal Palace,Tottenham,1,2,A,0,0,D,A Madley,13,10,3,1,19,11,11,2,2,1,0,0 +28/10/2023,Chelsea,Brentford,0,2,A,0,0,D,S Hooper,17,7,2,5,12,7,10,1,1,4,0,0 +28/10/2023,Arsenal,Sheffield United,5,0,H,1,0,H,T Robinson,13,2,8,0,9,13,6,1,0,3,0,0 +28/10/2023,Bournemouth,Burnley,2,1,H,1,1,D,S Barrott,13,6,6,3,9,12,10,5,2,3,0,0 +28/10/2023,Wolves,Newcastle,2,2,D,1,2,A,A Taylor,11,13,6,5,13,15,8,7,1,4,0,0 +29/10/2023,West Ham,Everton,0,1,A,0,0,D,S Attwell,12,10,2,4,7,11,4,3,4,1,0,0 +29/10/2023,Aston Villa,Luton,3,1,H,1,0,H,J Brooks,17,7,6,1,11,10,6,4,3,2,0,0 +29/10/2023,Brighton,Fulham,1,1,D,1,0,H,M Salisbury,18,10,7,5,12,8,7,3,0,3,0,0 +29/10/2023,Liverpool,Nott'm Forest,3,0,H,2,0,H,C Kavanagh,21,9,8,1,9,13,8,3,2,3,0,0 +29/10/2023,Man United,Man City,0,3,A,0,1,A,P Tierney,7,21,3,10,9,4,7,12,4,1,0,0 +04/11/2023,Fulham,Man United,0,1,A,0,0,D,J Brooks,18,12,3,5,9,15,9,4,5,2,0,0 +04/11/2023,Brentford,West Ham,3,2,H,1,2,A,T Bramall,16,12,4,2,12,14,4,3,1,4,0,0 +04/11/2023,Burnley,Crystal Palace,0,2,A,0,1,A,P Bankes,17,4,5,3,9,12,12,1,0,5,0,0 +04/11/2023,Everton,Brighton,1,1,D,1,0,H,T Robinson,10,7,4,2,15,5,3,3,4,2,0,0 +04/11/2023,Man City,Bournemouth,6,1,H,3,0,H,C Pawson,21,5,8,1,8,9,12,1,0,1,0,0 +04/11/2023,Sheffield United,Wolves,2,1,H,0,0,D,R Jones,11,10,2,3,13,10,4,4,2,3,0,0 +04/11/2023,Newcastle,Arsenal,1,0,H,0,0,D,S Attwell,9,14,2,1,14,9,0,11,5,1,0,0 +05/11/2023,Nott'm Forest,Aston Villa,2,0,H,1,0,H,J Gillett,5,13,3,3,6,9,0,10,1,1,0,0 +05/11/2023,Luton,Liverpool,1,1,D,0,0,D,A Madley,8,24,5,6,7,13,4,7,1,1,0,0 +06/11/2023,Tottenham,Chelsea,1,4,A,1,1,D,M Oliver,8,17,5,8,12,21,1,6,1,5,2,0 +11/11/2023,Wolves,Tottenham,2,1,H,0,1,A,T Robinson,17,6,4,2,19,15,11,3,4,3,0,0 +11/11/2023,Arsenal,Burnley,3,1,H,1,0,H,M Oliver,16,8,6,5,8,9,3,3,0,1,1,0 +11/11/2023,Crystal Palace,Everton,2,3,A,1,1,D,S Barrott,13,8,4,4,9,18,9,2,1,4,0,0 +11/11/2023,Man United,Luton,1,0,H,0,0,D,G Scott,15,10,4,4,9,11,11,3,0,2,0,0 +11/11/2023,Bournemouth,Newcastle,2,0,H,0,0,D,C Kavanagh,19,8,10,5,10,4,6,5,2,1,0,0 +12/11/2023,Aston Villa,Fulham,3,1,H,2,0,H,S Hooper,12,9,6,5,4,17,2,2,2,5,0,0 +12/11/2023,Brighton,Sheffield United,1,1,D,1,0,H,J Brooks,11,9,6,1,13,16,6,3,3,2,1,0 +12/11/2023,Liverpool,Brentford,3,0,H,1,0,H,P Tierney,17,16,10,3,16,12,6,8,1,2,0,0 +12/11/2023,West Ham,Nott'm Forest,3,2,H,1,1,D,M Salisbury,16,10,6,5,9,12,9,4,2,2,0,0 +12/11/2023,Chelsea,Man City,4,4,D,2,2,D,A Taylor,17,15,9,10,12,15,3,3,5,3,0,0 +25/11/2023,Man City,Liverpool,1,1,D,1,0,H,C Kavanagh,16,8,5,3,9,11,9,6,1,3,0,0 +25/11/2023,Burnley,West Ham,1,2,A,0,0,D,S Barrott,11,11,5,3,11,7,4,5,1,3,0,0 +25/11/2023,Luton,Crystal Palace,2,1,H,0,0,D,J Gillett,8,16,3,8,15,12,4,5,6,1,0,0 +25/11/2023,Newcastle,Chelsea,4,1,H,1,1,D,S Hooper,14,7,5,4,13,19,4,2,4,4,0,1 +25/11/2023,Nott'm Forest,Brighton,2,3,A,1,2,A,A Taylor,18,11,5,5,12,7,6,4,2,4,0,1 +25/11/2023,Sheffield United,Bournemouth,1,3,A,0,2,A,A Madley,10,23,2,11,6,8,4,7,4,1,0,0 +25/11/2023,Brentford,Arsenal,0,1,A,0,0,D,T Robinson,9,15,3,4,9,7,1,8,1,1,0,0 +26/11/2023,Tottenham,Aston Villa,1,2,A,1,1,D,R Jones,18,15,8,5,11,13,9,3,0,4,0,0 +26/11/2023,Everton,Man United,0,3,A,0,1,A,J Brooks,24,9,6,4,10,8,6,5,3,0,0,0 +27/11/2023,Fulham,Wolves,3,2,H,1,1,D,M Salisbury,12,10,6,6,13,13,6,2,2,2,0,0 +02/12/2023,Arsenal,Wolves,2,1,H,2,0,H,P Bankes,19,6,6,3,10,10,4,0,1,2,0,0 +02/12/2023,Brentford,Luton,3,1,H,0,0,D,A Taylor,27,7,6,1,10,8,7,5,1,2,0,0 +02/12/2023,Burnley,Sheffield United,5,0,H,2,0,H,C Kavanagh,19,6,7,3,9,12,6,1,2,5,0,1 +02/12/2023,Nott'm Forest,Everton,0,1,A,0,0,D,P Tierney,13,12,2,3,15,14,4,3,1,0,0,0 +02/12/2023,Newcastle,Man United,1,0,H,0,0,D,R Jones,22,8,4,1,6,13,7,3,1,2,0,0 +03/12/2023,Bournemouth,Aston Villa,2,2,D,1,1,D,T Bramall,15,11,7,3,16,12,7,7,5,3,0,0 +03/12/2023,Chelsea,Brighton,3,2,H,2,1,H,C Pawson,8,18,5,9,16,12,5,8,3,5,1,0 +03/12/2023,Liverpool,Fulham,4,3,H,2,2,D,S Attwell,26,9,12,5,6,7,3,4,0,0,0,0 +03/12/2023,West Ham,Crystal Palace,1,1,D,1,0,H,M Oliver,9,9,3,2,17,7,2,3,2,0,0,0 +03/12/2023,Man City,Tottenham,3,3,D,2,1,H,S Hooper,18,8,4,4,14,14,10,8,4,4,0,0 +05/12/2023,Wolves,Burnley,1,0,H,1,0,H,J Gillett,7,12,4,3,11,15,6,8,2,4,0,0 +05/12/2023,Luton,Arsenal,3,4,A,1,2,A,S Barrott,6,23,4,9,14,7,3,8,2,1,0,0 +06/12/2023,Brighton,Brentford,2,1,H,1,1,D,P Bankes,18,8,7,2,10,8,7,3,3,1,0,0 +06/12/2023,Crystal Palace,Bournemouth,0,2,A,0,1,A,C Pawson,16,11,3,4,10,17,3,9,1,3,0,0 +06/12/2023,Fulham,Nott'm Forest,5,0,H,2,0,H,J Smith,14,4,6,1,7,10,6,1,0,1,0,0 +06/12/2023,Sheffield United,Liverpool,0,2,A,0,1,A,S Hooper,6,15,1,8,12,8,2,12,2,3,0,0 +06/12/2023,Aston Villa,Man City,1,0,H,0,0,D,J Brooks,22,2,7,2,13,13,6,0,2,2,0,0 +06/12/2023,Man United,Chelsea,2,1,H,1,1,D,C Kavanagh,28,13,9,3,12,12,12,4,4,0,0,0 +07/12/2023,Everton,Newcastle,3,0,H,0,0,D,T Robinson,21,13,6,3,18,8,5,2,3,0,0,0 +07/12/2023,Tottenham,West Ham,1,2,A,1,0,H,M Salisbury,23,11,7,5,11,12,8,4,3,3,0,0 +09/12/2023,Crystal Palace,Liverpool,1,2,A,0,0,D,A Madley,8,14,4,2,17,17,6,5,5,2,1,0 +09/12/2023,Brighton,Burnley,1,1,D,0,1,A,S Hooper,29,6,11,3,5,8,9,1,0,4,0,0 +09/12/2023,Man United,Bournemouth,0,3,A,0,1,A,P Bankes,20,10,3,4,7,10,10,4,3,1,0,0 +09/12/2023,Sheffield United,Brentford,1,0,H,1,0,H,S Attwell,9,10,4,4,14,13,3,4,5,1,0,0 +09/12/2023,Wolves,Nott'm Forest,1,1,D,1,1,D,S Barrott,10,8,4,2,9,11,4,3,0,1,0,0 +09/12/2023,Aston Villa,Arsenal,1,0,H,1,0,H,J Gillett,10,12,3,5,16,11,3,3,4,2,0,0 +10/12/2023,Everton,Chelsea,2,0,H,0,0,D,M Oliver,9,16,5,4,12,12,4,8,2,2,0,0 +10/12/2023,Fulham,West Ham,5,0,H,3,0,H,P Tierney,14,9,8,5,5,10,3,6,2,1,0,0 +10/12/2023,Luton,Man City,1,2,A,1,0,H,T Robinson,4,18,2,6,16,6,6,3,2,1,0,0 +10/12/2023,Tottenham,Newcastle,4,1,H,2,0,H,C Kavanagh,23,9,12,3,10,12,3,6,1,3,0,0 +15/12/2023,Nott'm Forest,Tottenham,0,2,A,0,1,A,J Gillett,15,12,1,6,16,11,6,4,2,4,0,1 +16/12/2023,Chelsea,Sheffield United,2,0,H,0,0,D,A Madley,15,6,6,1,9,14,7,6,1,3,0,0 +16/12/2023,Man City,Crystal Palace,2,2,D,1,0,H,P Tierney,19,5,9,2,12,9,6,1,1,1,0,0 +16/12/2023,Newcastle,Fulham,3,0,H,0,0,D,S Barrott,27,6,10,4,7,12,5,1,0,2,0,1 +16/12/2023,Burnley,Everton,0,2,A,0,2,A,A Taylor,14,9,2,6,9,8,8,6,0,1,0,0 +17/12/2023,Arsenal,Brighton,2,0,H,0,0,D,T Robinson,26,6,9,1,11,9,10,1,1,3,0,0 +17/12/2023,Brentford,Aston Villa,1,2,A,1,0,H,D Coote,4,15,3,5,19,6,7,12,6,4,1,1 +17/12/2023,West Ham,Wolves,3,0,H,2,0,H,C Kavanagh,13,14,4,3,12,15,6,6,3,1,0,0 +17/12/2023,Liverpool,Man United,0,0,D,0,0,D,M Oliver,34,6,8,1,13,8,12,0,2,4,0,1 +21/12/2023,Crystal Palace,Brighton,1,1,D,1,0,H,J Brooks,11,18,3,6,12,11,3,1,5,2,0,0 +22/12/2023,Aston Villa,Sheffield United,1,1,D,0,0,D,A Taylor,12,5,4,2,11,16,9,1,3,3,0,0 +23/12/2023,West Ham,Man United,2,0,H,0,0,D,S Hooper,12,11,5,3,13,15,4,4,2,3,0,0 +23/12/2023,Fulham,Burnley,0,2,A,0,0,D,R Welch,19,7,5,2,5,17,8,6,2,1,0,0 +23/12/2023,Luton,Newcastle,1,0,H,1,0,H,D England,16,15,5,2,11,5,6,7,2,1,0,0 +23/12/2023,Nott'm Forest,Bournemouth,2,3,A,0,0,D,R Jones,11,18,5,7,10,10,6,6,0,3,1,0 +23/12/2023,Tottenham,Everton,2,1,H,2,0,H,S Attwell,13,18,6,8,6,18,3,8,1,4,0,0 +23/12/2023,Liverpool,Arsenal,1,1,D,1,1,D,C Kavanagh,13,13,3,2,13,14,4,5,2,5,0,0 +24/12/2023,Wolves,Chelsea,2,1,H,0,0,D,D Coote,14,16,6,5,11,9,10,6,3,5,0,0 +26/12/2023,Newcastle,Nott'm Forest,1,3,A,1,1,D,C Kavanagh,19,15,7,6,10,11,10,2,3,3,0,0 +26/12/2023,Bournemouth,Fulham,3,0,H,1,0,H,T Robinson,16,8,4,3,10,10,6,5,0,2,0,0 +26/12/2023,Sheffield United,Luton,2,3,A,0,1,A,S Allison,21,12,6,4,13,13,9,4,5,0,0,0 +26/12/2023,Burnley,Liverpool,0,2,A,0,1,A,P Tierney,9,19,0,10,7,10,4,4,2,1,0,0 +26/12/2023,Man United,Aston Villa,3,2,H,0,2,A,C Pawson,13,10,7,4,10,10,3,6,1,3,0,0 +27/12/2023,Brentford,Wolves,1,4,A,1,3,A,A Madley,14,11,4,5,5,14,8,2,1,1,0,0 +27/12/2023,Chelsea,Crystal Palace,2,1,H,1,1,D,M Salisbury,9,13,4,5,13,16,4,3,3,2,0,0 +27/12/2023,Everton,Man City,1,3,A,1,0,H,J Brooks,7,23,2,9,13,5,8,4,4,2,0,0 +28/12/2023,Brighton,Tottenham,4,2,H,2,0,H,J Gillett,15,19,9,3,18,6,7,6,3,2,0,0 +28/12/2023,Arsenal,West Ham,0,2,A,0,1,A,M Oliver,30,6,8,3,8,8,10,3,2,0,0,0 +30/12/2023,Luton,Chelsea,2,3,A,0,2,A,P Tierney,15,12,6,8,14,21,7,2,2,2,0,0 +30/12/2023,Aston Villa,Burnley,3,2,H,2,1,H,S Attwell,19,9,7,5,9,14,6,2,4,1,0,1 +30/12/2023,Crystal Palace,Brentford,3,1,H,2,1,H,R Jones,13,9,6,4,14,10,7,4,0,1,0,0 +30/12/2023,Man City,Sheffield United,2,0,H,1,0,H,D Coote,18,4,4,2,5,6,12,2,1,1,0,0 +30/12/2023,Wolves,Everton,3,0,H,1,0,H,T Bramall,12,10,6,0,9,14,5,2,2,3,0,0 +30/12/2023,Nott'm Forest,Man United,2,1,H,0,0,D,T Robinson,8,10,2,5,9,11,4,5,2,4,0,0 +31/12/2023,Fulham,Arsenal,2,1,H,1,1,D,J Smith,15,13,4,3,11,10,5,4,4,1,0,0 +31/12/2023,Tottenham,Bournemouth,3,1,H,1,0,H,S Hooper,12,24,6,4,9,18,4,13,3,3,0,0 +01/01/2024,Liverpool,Newcastle,4,2,H,0,0,D,A Taylor,34,5,15,3,16,15,7,3,3,5,0,0 +02/01/2024,West Ham,Brighton,0,0,D,0,0,D,S Barrott,6,22,2,8,4,6,2,0,1,0,0,0 +12/01/2024,Burnley,Luton,1,1,D,1,0,H,T Harrington,13,14,7,5,7,12,2,8,1,0,0,0 +13/01/2024,Chelsea,Fulham,1,0,H,1,0,H,A Taylor,17,14,3,4,13,9,6,4,5,1,0,0 +13/01/2024,Newcastle,Man City,2,3,A,2,1,H,C Kavanagh,12,27,5,11,7,7,3,13,1,2,0,0 +14/01/2024,Everton,Aston Villa,0,0,D,0,0,D,D Coote,10,16,2,5,13,14,4,5,1,4,0,0 +14/01/2024,Man United,Tottenham,2,2,D,2,1,H,J Brooks,9,16,2,6,8,5,8,13,2,1,0,0 +20/01/2024,Arsenal,Crystal Palace,5,0,H,2,0,H,P Tierney,21,12,6,5,8,12,6,7,0,0,0,0 +20/01/2024,Brentford,Nott'm Forest,3,2,H,1,1,D,D England,11,12,5,3,10,14,6,4,1,3,0,0 +21/01/2024,Sheffield United,West Ham,2,2,D,1,1,D,M Salisbury,21,16,6,5,11,10,4,1,3,1,1,1 +21/01/2024,Bournemouth,Liverpool,0,4,A,0,0,D,A Madley,11,14,1,7,9,12,8,5,1,2,0,0 +22/01/2024,Brighton,Wolves,0,0,D,0,0,D,C Pawson,11,8,4,3,10,13,8,2,1,4,0,0 +30/01/2024,Nott'm Forest,Arsenal,1,2,A,0,0,D,S Hooper,9,19,3,3,6,7,2,11,0,1,0,0 +30/01/2024,Fulham,Everton,0,0,D,0,0,D,T Bramall,25,21,6,4,6,13,15,6,1,2,0,0 +30/01/2024,Luton,Brighton,4,0,H,3,0,H,R Jones,18,9,8,2,13,14,7,4,2,2,0,0 +30/01/2024,Crystal Palace,Sheffield United,3,2,H,2,2,D,T Harrington,12,9,4,5,8,18,11,1,1,4,0,0 +30/01/2024,Aston Villa,Newcastle,1,3,A,0,2,A,J Brooks,12,14,6,5,14,12,8,7,4,1,0,0 +31/01/2024,Man City,Burnley,3,1,H,2,0,H,S Barrott,14,8,4,3,3,13,7,3,2,2,0,0 +31/01/2024,Tottenham,Brentford,3,2,H,0,1,A,D Coote,19,9,5,5,13,15,5,3,2,3,0,0 +31/01/2024,Liverpool,Chelsea,4,1,H,2,0,H,P Tierney,28,4,13,3,15,16,8,1,2,4,0,0 +01/02/2024,West Ham,Bournemouth,1,1,D,0,1,A,T Robinson,9,9,3,4,13,11,1,5,1,3,0,0 +01/02/2024,Wolves,Man United,3,4,A,0,2,A,J Gillett,16,21,7,8,10,10,4,5,2,5,0,0 +03/02/2024,Everton,Tottenham,2,2,D,1,2,A,M Oliver,14,9,5,6,8,14,9,5,3,0,0,0 +03/02/2024,Brighton,Crystal Palace,4,1,H,3,0,H,S Hooper,13,7,6,5,8,14,3,2,1,3,0,0 +03/02/2024,Burnley,Fulham,2,2,D,0,2,A,D Bond,12,15,4,7,11,7,2,13,2,2,0,0 +03/02/2024,Newcastle,Luton,4,4,D,2,2,D,T Bramall,19,11,6,8,13,16,8,2,2,2,0,0 +03/02/2024,Sheffield United,Aston Villa,0,5,A,0,4,A,P Tierney,10,14,5,10,10,7,3,4,1,0,0,0 +04/02/2024,Bournemouth,Nott'm Forest,1,1,D,1,1,D,R Welch,9,8,2,6,19,12,11,6,3,2,1,0 +04/02/2024,Chelsea,Wolves,2,4,A,1,2,A,T Robinson,15,14,6,7,15,11,10,1,3,2,0,0 +04/02/2024,Man United,West Ham,3,0,H,1,0,H,A Madley,12,22,5,3,10,5,5,8,0,1,0,0 +04/02/2024,Arsenal,Liverpool,3,1,H,1,1,D,A Taylor,15,10,7,1,11,11,2,4,6,2,0,1 +05/02/2024,Brentford,Man City,1,3,A,1,1,D,J Gillett,9,25,3,15,2,4,8,13,1,0,0,0 +10/02/2024,Man City,Everton,2,0,H,0,0,D,J Brooks,19,5,3,1,6,11,9,0,0,2,0,0 +10/02/2024,Fulham,Bournemouth,3,1,H,2,0,H,D England,7,25,6,4,11,11,1,14,1,2,0,0 +10/02/2024,Liverpool,Burnley,3,1,H,1,1,D,T Robinson,25,9,10,4,11,13,9,3,3,2,0,0 +10/02/2024,Luton,Sheffield United,1,3,A,0,2,A,C Kavanagh,20,7,5,3,7,12,13,1,1,2,0,0 +10/02/2024,Tottenham,Brighton,2,1,H,0,1,A,S Barrott,16,6,6,3,14,13,9,6,2,3,0,0 +10/02/2024,Wolves,Brentford,0,2,A,0,1,A,S Hooper,17,9,5,6,11,9,7,4,2,3,0,0 +10/02/2024,Nott'm Forest,Newcastle,2,3,A,2,2,D,A Taylor,13,7,3,5,9,6,4,4,1,2,0,0 +11/02/2024,West Ham,Arsenal,0,6,A,0,4,A,C Pawson,5,25,1,12,17,11,2,6,4,0,0,0 +11/02/2024,Aston Villa,Man United,1,2,A,0,1,A,R Jones,23,17,10,5,9,7,10,8,3,2,0,0 +12/02/2024,Crystal Palace,Chelsea,1,3,A,1,0,H,M Oliver,13,14,4,5,14,7,1,7,2,2,0,0 +17/02/2024,Brentford,Liverpool,1,4,A,0,1,A,M Oliver,15,15,6,8,4,18,1,6,2,1,0,0 +17/02/2024,Burnley,Arsenal,0,5,A,0,2,A,J Gillett,8,16,0,7,11,8,4,6,2,1,0,0 +17/02/2024,Fulham,Aston Villa,1,2,A,0,1,A,L Smith,15,12,4,3,11,6,4,4,5,2,0,0 +17/02/2024,Newcastle,Bournemouth,2,2,D,0,0,D,M Salisbury,17,12,5,7,9,22,4,2,1,3,0,0 +17/02/2024,Nott'm Forest,West Ham,2,0,H,1,0,H,T Bramall,19,10,8,3,13,15,5,3,4,4,0,1 +17/02/2024,Tottenham,Wolves,1,2,A,0,1,A,A Taylor,15,12,4,7,8,13,10,4,0,1,0,0 +17/02/2024,Man City,Chelsea,1,1,D,0,1,A,A Madley,31,9,5,6,7,12,12,1,1,3,0,0 +18/02/2024,Sheffield United,Brighton,0,5,A,0,2,A,S Attwell,6,24,1,10,3,5,5,10,1,4,1,0 +18/02/2024,Luton,Man United,1,2,A,1,2,A,D Coote,22,21,4,9,7,21,8,6,3,5,0,0 +19/02/2024,Everton,Crystal Palace,1,1,D,0,0,D,P Tierney,19,10,4,4,8,12,3,6,0,2,0,0 +20/02/2024,Man City,Brentford,1,0,H,0,0,D,D England,25,6,11,2,12,6,10,1,2,4,0,0 +21/02/2024,Liverpool,Luton,4,1,H,0,1,A,A Madley,29,12,13,3,15,14,13,4,1,4,0,0 +24/02/2024,Aston Villa,Nott'm Forest,4,2,H,3,1,H,S Barrott,16,10,6,3,7,16,7,2,1,2,0,0 +24/02/2024,Brighton,Everton,1,1,D,0,0,D,T Harrington,23,6,7,3,9,14,11,3,2,3,1,0 +24/02/2024,Crystal Palace,Burnley,3,0,H,0,0,D,L Smith,15,2,6,0,8,13,12,3,3,1,0,1 +24/02/2024,Man United,Fulham,1,2,A,0,0,D,M Oliver,21,17,9,5,6,11,10,9,2,5,0,0 +24/02/2024,Bournemouth,Man City,0,1,A,0,1,A,J Gillett,13,15,4,6,14,6,3,8,2,2,0,0 +24/02/2024,Arsenal,Newcastle,4,1,H,2,0,H,P Tierney,18,3,8,2,15,6,9,0,1,0,0,0 +25/02/2024,Wolves,Sheffield United,1,0,H,1,0,H,D Bond,13,12,2,4,8,4,5,6,2,1,0,0 +26/02/2024,West Ham,Brentford,4,2,H,2,1,H,S Hooper,17,14,6,8,5,9,1,7,1,2,0,0 +02/03/2024,Brentford,Chelsea,2,2,D,0,1,A,J Gillett,14,17,5,6,10,9,0,6,3,1,0,0 +02/03/2024,Everton,West Ham,1,3,A,0,0,D,C Pawson,22,12,11,5,9,6,4,5,1,2,0,0 +02/03/2024,Fulham,Brighton,3,0,H,2,0,H,S Hooper,12,15,5,5,16,10,2,6,1,2,0,0 +02/03/2024,Newcastle,Wolves,3,0,H,2,0,H,T Robinson,14,12,7,3,8,11,3,5,0,1,0,0 +02/03/2024,Nott'm Forest,Liverpool,0,1,A,0,0,D,P Tierney,8,22,2,2,11,12,3,12,2,2,0,0 +02/03/2024,Tottenham,Crystal Palace,3,1,H,0,0,D,J Brooks,14,4,6,1,7,16,11,1,1,2,0,0 +02/03/2024,Luton,Aston Villa,2,3,A,0,2,A,M Oliver,13,12,5,8,8,19,8,6,1,4,0,0 +03/03/2024,Burnley,Bournemouth,0,2,A,0,1,A,D Coote,20,10,6,3,8,13,7,2,2,4,0,0 +03/03/2024,Man City,Man United,3,1,H,0,1,A,A Madley,27,3,8,1,5,10,15,2,0,1,0,0 +04/03/2024,Sheffield United,Arsenal,0,6,A,0,5,A,S Barrott,4,22,0,10,11,4,0,7,1,0,0,0 +09/03/2024,Man United,Everton,2,0,H,2,0,H,S Hooper,15,23,8,6,13,11,5,8,1,2,0,0 +09/03/2024,Bournemouth,Sheffield United,2,2,D,0,1,A,A Taylor,32,13,12,9,12,11,10,3,3,5,0,0 +09/03/2024,Crystal Palace,Luton,1,1,D,1,0,H,S Singh,21,8,4,2,20,11,8,6,2,3,0,0 +09/03/2024,Wolves,Fulham,2,1,H,0,0,D,T Harrington,8,23,3,6,7,11,3,4,2,2,0,0 +09/03/2024,Arsenal,Brentford,2,1,H,1,1,D,R Jones,17,9,6,4,9,10,10,4,2,4,0,0 +10/03/2024,Aston Villa,Tottenham,0,4,A,0,0,D,C Kavanagh,10,9,1,5,11,15,6,4,0,2,1,0 +10/03/2024,Brighton,Nott'm Forest,1,0,H,1,0,H,M Salisbury,10,9,3,4,14,14,6,3,4,4,0,0 +10/03/2024,West Ham,Burnley,2,2,D,0,2,A,D England,22,11,4,4,11,18,4,4,2,4,0,0 +10/03/2024,Liverpool,Man City,1,1,D,0,1,A,M Oliver,19,10,6,6,6,10,7,4,0,3,0,0 +11/03/2024,Chelsea,Newcastle,3,2,H,1,1,D,J Brooks,12,11,8,3,12,7,0,4,3,1,0,0 +13/03/2024,Bournemouth,Luton,4,3,H,0,3,A,S Allison,24,8,10,4,16,11,11,3,1,2,0,0 +16/03/2024,Burnley,Brentford,2,1,H,1,0,H,D Bond,17,9,6,5,14,17,5,4,2,4,0,1 +16/03/2024,Luton,Nott'm Forest,1,1,D,0,1,A,D England,10,16,4,6,10,17,6,7,1,2,0,0 +16/03/2024,Fulham,Tottenham,3,0,H,1,0,H,R Jones,16,14,7,5,9,10,4,5,2,3,0,0 +17/03/2024,West Ham,Aston Villa,1,1,D,1,0,H,J Gillett,13,13,5,5,13,12,7,11,3,3,0,0 +30/03/2024,Newcastle,West Ham,4,3,H,1,2,A,R Jones,24,10,9,4,9,8,3,4,1,1,1,0 +30/03/2024,Bournemouth,Everton,2,1,H,0,0,D,S Barrott,11,7,4,3,14,11,9,8,1,2,0,0 +30/03/2024,Chelsea,Burnley,2,2,D,1,0,H,D England,33,18,13,7,12,7,12,4,2,2,0,1 +30/03/2024,Nott'm Forest,Crystal Palace,1,1,D,0,1,A,C Kavanagh,12,10,5,3,14,12,4,6,1,1,0,0 +30/03/2024,Sheffield United,Fulham,3,3,D,0,0,D,T Robinson,8,24,4,9,10,13,2,15,4,1,0,0 +30/03/2024,Tottenham,Luton,2,1,H,0,1,A,J Gillett,17,7,4,3,16,14,3,6,2,4,0,0 +30/03/2024,Aston Villa,Wolves,2,0,H,1,0,H,P Tierney,11,13,5,3,11,19,5,4,3,4,0,0 +30/03/2024,Brentford,Man United,1,1,D,0,0,D,S Hooper,31,11,5,5,7,14,14,4,2,2,0,0 +31/03/2024,Liverpool,Brighton,2,1,H,1,1,D,D Coote,30,9,8,3,20,6,8,4,5,3,0,0 +31/03/2024,Man City,Arsenal,0,0,D,0,0,D,A Taylor,12,6,1,2,9,20,7,4,0,2,0,0 +02/04/2024,Newcastle,Everton,1,1,D,1,0,H,T Harrington,18,10,6,3,9,14,7,3,0,3,0,0 +02/04/2024,Nott'm Forest,Fulham,3,1,H,3,0,H,M Oliver,16,18,4,3,7,8,3,8,0,0,0,0 +02/04/2024,Bournemouth,Crystal Palace,1,0,H,0,0,D,G Scott,11,3,6,2,13,14,9,2,3,2,0,0 +02/04/2024,Burnley,Wolves,1,1,D,1,1,D,T Bramall,13,8,6,2,10,12,7,4,0,2,0,0 +02/04/2024,West Ham,Tottenham,1,1,D,1,1,D,J Brooks,11,13,4,4,13,9,9,5,1,4,0,0 +03/04/2024,Arsenal,Luton,2,0,H,2,0,H,C Pawson,13,5,4,1,12,8,3,1,1,1,0,0 +03/04/2024,Brentford,Brighton,0,0,D,0,0,D,A Madley,5,24,2,6,9,12,1,5,1,1,0,0 +03/04/2024,Man City,Aston Villa,4,1,H,2,1,H,D England,25,8,11,3,6,13,2,3,1,2,0,0 +04/04/2024,Liverpool,Sheffield United,3,1,H,1,0,H,S Attwell,29,8,9,3,6,9,13,1,0,1,0,0 +04/04/2024,Chelsea,Man United,4,3,H,2,2,D,J Gillett,28,19,10,5,7,15,12,3,3,0,0,0 +06/04/2024,Crystal Palace,Man City,2,4,A,1,1,D,P Tierney,7,18,4,8,13,3,2,9,1,1,0,0 +06/04/2024,Aston Villa,Brentford,3,3,D,1,0,H,M Salisbury,11,8,3,3,15,11,11,3,2,2,0,0 +06/04/2024,Everton,Burnley,1,0,H,1,0,H,M Oliver,12,6,3,1,11,13,3,5,2,1,0,1 +06/04/2024,Fulham,Newcastle,0,1,A,0,0,D,S Allison,14,12,4,5,10,13,14,3,1,2,0,0 +06/04/2024,Luton,Bournemouth,2,1,H,0,0,D,A Madley,19,8,9,1,7,14,7,2,0,0,0,0 +06/04/2024,Wolves,West Ham,1,2,A,1,0,H,T Harrington,14,11,7,4,15,7,4,4,5,2,0,0 +06/04/2024,Brighton,Arsenal,0,3,A,0,1,A,J Brooks,10,20,2,7,14,13,8,7,1,2,0,0 +07/04/2024,Man United,Liverpool,2,2,D,0,1,A,A Taylor,9,28,5,7,9,10,6,11,5,2,0,0 +07/04/2024,Sheffield United,Chelsea,2,2,D,1,1,D,R Jones,11,6,6,3,12,10,7,6,4,2,0,0 +07/04/2024,Tottenham,Nott'm Forest,3,1,H,1,1,D,S Hooper,17,13,7,6,11,12,12,2,2,3,0,0 +13/04/2024,Newcastle,Tottenham,4,0,H,2,0,H,T Robinson,18,11,5,2,14,12,16,3,0,4,0,0 +13/04/2024,Brentford,Sheffield United,2,0,H,0,0,D,S Barrott,9,8,5,2,8,11,3,6,3,1,0,0 +13/04/2024,Burnley,Brighton,1,1,D,0,0,D,S Hooper,11,20,3,8,18,8,4,5,1,2,0,0 +13/04/2024,Man City,Luton,5,1,H,1,0,H,J Brooks,33,4,13,2,7,6,12,1,1,0,0,0 +13/04/2024,Nott'm Forest,Wolves,2,2,D,1,1,D,C Pawson,18,11,9,4,15,10,10,1,2,3,0,0 +13/04/2024,Bournemouth,Man United,2,2,D,2,1,H,T Harrington,20,8,5,2,13,14,7,2,4,0,0,0 +14/04/2024,Liverpool,Crystal Palace,0,1,A,0,1,A,C Kavanagh,21,8,6,5,10,10,11,1,1,1,0,0 +14/04/2024,West Ham,Fulham,0,2,A,0,1,A,S Attwell,15,18,4,10,6,13,8,3,1,2,0,0 +14/04/2024,Arsenal,Aston Villa,0,2,A,0,0,D,D Coote,18,11,4,2,12,7,4,6,3,1,0,0 +15/04/2024,Chelsea,Everton,6,0,H,4,0,H,P Tierney,14,10,10,2,5,21,4,8,1,4,0,0 +20/04/2024,Luton,Brentford,1,5,A,0,2,A,J Gillett,6,21,3,9,13,9,4,9,1,2,0,0 +20/04/2024,Sheffield United,Burnley,1,4,A,0,2,A,A Madley,18,16,11,9,10,9,11,2,1,0,0,0 +20/04/2024,Wolves,Arsenal,0,2,A,0,1,A,P Tierney,5,24,3,9,14,9,1,2,3,2,0,0 +21/04/2024,Everton,Nott'm Forest,2,0,H,1,0,H,A Taylor,7,8,4,3,15,9,6,4,3,2,0,0 +21/04/2024,Aston Villa,Bournemouth,3,1,H,1,1,D,T Robinson,14,14,4,6,8,14,8,4,1,3,0,0 +21/04/2024,Crystal Palace,West Ham,5,2,H,4,1,H,G Scott,18,4,7,1,9,9,4,0,3,2,0,0 +21/04/2024,Fulham,Liverpool,1,3,A,1,1,D,C Pawson,12,14,5,7,5,11,1,4,3,0,0,0 +23/04/2024,Arsenal,Chelsea,5,0,H,1,0,H,S Hooper,27,7,10,1,12,11,4,2,2,2,0,0 +24/04/2024,Wolves,Bournemouth,0,1,A,0,1,A,S Attwell,15,21,4,6,10,17,8,12,4,1,0,1 +24/04/2024,Crystal Palace,Newcastle,2,0,H,0,0,D,T Bramall,20,7,7,2,10,15,7,2,3,3,0,0 +24/04/2024,Everton,Liverpool,2,0,H,1,0,H,A Madley,16,23,6,7,6,13,4,13,0,3,0,0 +24/04/2024,Man United,Sheffield United,4,2,H,1,1,D,M Salisbury,25,10,13,4,7,9,9,4,0,1,0,0 +25/04/2024,Brighton,Man City,0,4,A,0,3,A,J Gillett,7,14,3,6,10,3,4,4,2,0,0,0 +27/04/2024,West Ham,Liverpool,2,2,D,1,0,H,A Taylor,11,28,8,8,8,10,5,8,0,2,0,0 +27/04/2024,Fulham,Crystal Palace,1,1,D,0,0,D,S Attwell,9,14,2,4,11,15,5,9,0,2,0,0 +27/04/2024,Man United,Burnley,1,1,D,0,0,D,J Brooks,27,16,10,7,7,11,6,5,1,3,0,0 +27/04/2024,Newcastle,Sheffield United,5,1,H,1,1,D,T Harrington,20,15,7,5,9,10,4,5,0,0,0,0 +27/04/2024,Wolves,Luton,2,1,H,1,0,H,D Coote,13,9,5,6,14,14,4,4,3,1,0,0 +27/04/2024,Everton,Brentford,1,0,H,0,0,D,D England,18,10,1,4,10,12,6,6,1,2,0,0 +27/04/2024,Aston Villa,Chelsea,2,2,D,2,0,H,C Pawson,9,21,4,5,13,14,2,4,3,5,0,0 +28/04/2024,Bournemouth,Brighton,3,0,H,1,0,H,P Tierney,15,13,6,1,14,10,8,2,2,2,0,0 +28/04/2024,Tottenham,Arsenal,2,3,A,0,3,A,M Oliver,14,9,2,3,12,16,8,6,2,1,0,0 +28/04/2024,Nott'm Forest,Man City,0,2,A,0,1,A,S Hooper,14,11,2,5,8,6,4,5,0,0,0,0 +02/05/2024,Chelsea,Tottenham,2,0,H,1,0,H,R Jones,16,19,5,3,12,16,4,9,0,1,0,0 +03/05/2024,Luton,Everton,1,1,D,1,1,D,T Robinson,18,10,5,3,14,12,8,7,1,3,0,0 +04/05/2024,Arsenal,Bournemouth,3,0,H,1,0,H,D Coote,25,7,9,2,16,12,6,1,2,3,0,0 +04/05/2024,Brentford,Fulham,0,0,D,0,0,D,G Scott,7,15,3,3,3,6,7,6,1,1,0,0 +04/05/2024,Burnley,Newcastle,1,4,A,0,3,A,A Taylor,17,23,5,11,8,8,4,11,2,2,0,0 +04/05/2024,Sheffield United,Nott'm Forest,1,3,A,1,1,D,C Kavanagh,17,15,5,4,9,7,5,3,0,1,1,0 +04/05/2024,Man City,Wolves,5,1,H,3,0,H,C Pawson,20,2,12,1,4,17,6,0,0,4,0,0 +05/05/2024,Brighton,Aston Villa,1,0,H,0,0,D,R Jones,15,2,8,1,13,10,7,3,2,1,0,0 +05/05/2024,Chelsea,West Ham,5,0,H,3,0,H,A Madley,25,13,14,2,12,10,8,6,1,3,0,0 +05/05/2024,Liverpool,Tottenham,4,2,H,2,0,H,P Tierney,25,11,13,6,14,12,8,3,1,4,0,0 +06/05/2024,Crystal Palace,Man United,4,0,H,2,0,H,J Gillett ,18,7,10,2,12,8,6,3,1,1,0,0 +11/05/2024,Fulham,Man City,0,4,A,0,1,A,A Taylor,1,16,1,9,9,6,1,7,1,0,1,0 +11/05/2024,Bournemouth,Brentford,1,2,A,0,0,D,M Donohue,13,11,4,5,16,12,3,8,2,2,0,0 +11/05/2024,Everton,Sheffield United,1,0,H,1,0,H,S Attwell,15,13,6,1,6,11,3,6,1,2,0,0 +11/05/2024,Newcastle,Brighton,1,1,D,1,1,D,D England,18,15,7,4,9,16,8,4,3,4,0,0 +11/05/2024,Tottenham,Burnley,2,1,H,1,1,D,J Gillett,21,7,9,3,10,14,9,3,3,5,0,0 +11/05/2024,West Ham,Luton,3,1,H,0,1,A,M Oliver,24,4,8,1,9,15,7,6,1,3,0,0 +11/05/2024,Wolves,Crystal Palace,1,3,A,0,2,A,T Bramall,13,13,7,3,13,12,8,3,3,2,0,1 +11/05/2024,Nott'm Forest,Chelsea,2,3,A,1,1,D,T Harrington,20,12,4,5,14,8,5,6,2,3,0,0 +12/05/2024,Man United,Arsenal,0,1,A,0,1,A,P Tierney,14,11,2,5,5,6,4,7,0,2,0,0 +13/05/2024,Aston Villa,Liverpool,3,3,D,1,2,A,S Hooper,19,14,5,7,11,15,5,4,1,1,0,0 +14/05/2024,Tottenham,Man City,0,2,A,0,0,D,C Kavanagh,10,8,5,5,14,11,7,4,3,3,0,0 +15/05/2024,Brighton,Chelsea,1,2,A,0,1,A,M Salisbury,12,13,2,6,13,14,4,6,2,2,0,1 +15/05/2024,Man United,Newcastle,3,2,H,1,0,H,R Jones,17,21,8,8,16,7,9,9,3,3,0,0 +19/05/2024,Arsenal,Everton,2,1,H,1,1,D,M Oliver,26,5,5,2,8,11,8,1,4,3,0,0 +19/05/2024,Brentford,Newcastle,2,4,A,0,3,A,S Hooper,10,12,5,7,15,11,3,0,4,4,0,0 +19/05/2024,Brighton,Man United,0,2,A,0,0,D,C Pawson,17,11,3,4,10,9,7,5,1,3,0,0 +19/05/2024,Burnley,Nott'm Forest,1,2,A,0,2,A,G Scott,20,12,3,6,11,5,4,3,1,0,0,0 +19/05/2024,Chelsea,Bournemouth,2,1,H,1,0,H,A Taylor,16,22,6,5,5,9,6,5,2,3,0,0 +19/05/2024,Crystal Palace,Aston Villa,5,0,H,2,0,H,D Bond,15,8,9,2,10,8,2,4,1,4,0,0 +19/05/2024,Liverpool,Wolves,2,0,H,2,0,H,C Kavanagh,36,4,14,3,14,11,10,2,1,1,0,1 +19/05/2024,Luton,Fulham,2,4,A,1,2,A,M Donohue,15,15,6,7,15,20,4,4,5,4,0,0 +19/05/2024,Man City,West Ham,3,1,H,2,1,H,J Brooks,28,3,12,2,3,12,11,2,0,1,0,0 +19/05/2024,Sheffield United,Tottenham,0,3,A,0,1,A,A Madley,6,18,1,9,11,9,2,6,2,0,0,0 diff --git a/csv/historical_matches.csv b/csv/historical_matches.csv new file mode 100644 index 0000000000000000000000000000000000000000..69eaf5e42e968ef4e865785546f940515214a098 --- /dev/null +++ b/csv/historical_matches.csv @@ -0,0 +1,261 @@ +Date,Home,Away,HomeGoals,AwayGoals,HomePass%,AwayPass% +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 diff --git a/csv/historical_matches3.csv b/csv/historical_matches3.csv new file mode 100644 index 0000000000000000000000000000000000000000..69eaf5e42e968ef4e865785546f940515214a098 --- /dev/null +++ b/csv/historical_matches3.csv @@ -0,0 +1,261 @@ +Date,Home,Away,HomeGoals,AwayGoals,HomePass%,AwayPass% +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 +2025-08-15,Liverpool,Bournemouth,4,2,78.9,70.7 +2025-08-16,Aston Villa,Newcastle Utd,0,0,80.1,76.8 +2025-08-16,Sunderland,West Ham,3,0,74.6,80.6 +2025-08-16,Tottenham,Burnley,3,0,80.8,71.7 +2025-08-16,Brighton,Fulham,1,1,76.9,76.3 +2025-08-16,Wolves,Manchester City,0,4,76.8,83.1 +2025-08-17,Nott'ham Forest,Brentford,3,1,76.8,73.1 +2025-08-17,Chelsea,Crystal Palace,0,0,80.2,71.4 +2025-08-17,Manchester Utd,Arsenal,0,1,76.8,79.2 +2025-08-18,Leeds United,Everton,1,0,75.1,76.2 +2025-08-22,West Ham,Chelsea,1,5,80.6,80.2 +2025-08-23,Manchester City,Tottenham,0,2,83.1,80.8 +2025-08-23,Bournemouth,Wolves,1,0,70.7,76.8 +2025-08-23,Burnley,Sunderland,2,0,71.7,74.6 +2025-08-23,Brentford,Aston Villa,1,0,73.1,80.1 +2025-08-23,Arsenal,Leeds United,5,0,79.2,75.1 +2025-08-24,Crystal Palace,Nott'ham Forest,1,1,71.4,76.8 +2025-08-24,Everton,Brighton,2,0,76.2,76.9 +2025-08-24,Fulham,Manchester Utd,1,1,76.3,76.8 +2025-08-25,Newcastle Utd,Liverpool,2,3,76.8,78.9 +2025-08-30,Chelsea,Fulham,2,0,80.2,76.3 +2025-08-30,Tottenham,Bournemouth,0,1,80.8,70.7 +2025-08-30,Sunderland,Brentford,2,1,74.6,73.1 +2025-08-30,Wolves,Everton,2,3,76.8,76.2 +2025-08-30,Manchester Utd,Burnley,3,2,76.8,71.7 +2025-08-30,Leeds United,Newcastle Utd,0,0,75.1,76.8 +2025-08-31,Nott'ham Forest,West Ham,0,3,76.8,80.6 +2025-08-31,Brighton,Manchester City,2,1,76.9,83.1 +2025-08-31,Liverpool,Arsenal,1,0,78.9,79.2 +2025-08-31,Aston Villa,Crystal Palace,0,3,80.1,71.4 +2025-09-13,Arsenal,Nott'ham Forest,3,0,79.2,76.8 +2025-09-13,Everton,Aston Villa,0,0,76.2,80.1 +2025-09-13,Fulham,Leeds United,1,0,76.3,75.1 +2025-09-13,Newcastle Utd,Wolves,1,0,76.8,76.8 +2025-09-13,Crystal Palace,Sunderland,0,0,71.4,74.6 +2025-09-13,Bournemouth,Brighton,2,1,70.7,76.9 +2025-09-13,West Ham,Tottenham,0,3,80.6,80.8 +2025-09-13,Brentford,Chelsea,2,2,73.1,80.2 +2025-09-14,Burnley,Liverpool,0,1,71.7,78.9 +2025-09-14,Manchester City,Manchester Utd,3,0,83.1,76.8 +2025-09-20,Liverpool,Everton,2,1,78.9,76.2 +2025-09-20,West Ham,Crystal Palace,1,2,80.6,71.4 +2025-09-20,Brighton,Tottenham,2,2,76.9,80.8 +2025-09-20,Wolves,Leeds United,1,3,76.8,75.1 +2025-09-20,Burnley,Nott'ham Forest,1,1,71.7,76.8 +2025-09-20,Manchester Utd,Chelsea,2,1,76.8,80.2 +2025-09-20,Fulham,Brentford,3,1,76.3,73.1 +2025-09-21,Sunderland,Aston Villa,1,1,74.6,80.1 +2025-09-21,Bournemouth,Newcastle Utd,0,0,70.7,76.8 +2025-09-21,Arsenal,Manchester City,1,1,79.2,83.1 +2025-09-27,Brentford,Manchester Utd,3,1,73.1,76.8 +2025-09-27,Leeds United,Bournemouth,2,2,75.1,70.7 +2025-09-27,Crystal Palace,Liverpool,2,1,71.4,78.9 +2025-09-27,Chelsea,Brighton,1,3,80.2,76.9 +2025-09-27,Manchester City,Burnley,5,1,83.1,71.7 +2025-09-27,Nott'ham Forest,Sunderland,0,1,76.8,74.6 +2025-09-27,Tottenham,Wolves,1,1,80.8,76.8 +2025-09-28,Aston Villa,Fulham,3,1,80.1,76.3 +2025-09-28,Newcastle Utd,Arsenal,1,2,76.8,79.2 +2025-09-29,Everton,West Ham,1,1,76.2,80.6 +2025-10-03,Bournemouth,Fulham,3,1,70.7,76.3 +2025-10-04,Leeds United,Tottenham,1,2,75.1,80.8 +2025-10-04,Arsenal,West Ham,2,0,79.2,80.6 +2025-10-04,Manchester Utd,Sunderland,2,0,76.8,74.6 +2025-10-04,Chelsea,Liverpool,2,1,80.2,78.9 diff --git a/csv/premier_league_passing_analysis.xlsx b/csv/premier_league_passing_analysis.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ac963a80b470c3a85c28c6ae23d4c2b665f2a5bd Binary files /dev/null and b/csv/premier_league_passing_analysis.xlsx differ diff --git a/csv/premier_league_player_passing.csv b/csv/premier_league_player_passing.csv new file mode 100644 index 0000000000000000000000000000000000000000..b0672f4c78691c1dbcbb0c199f32d6f25a0eda10 --- /dev/null +++ b/csv/premier_league_player_passing.csv @@ -0,0 +1,430 @@ +Player,Squad,Total_Cmp,Total_Att,Total_Cmp%,Total_TotDist,Total_Cmp,Total_Att,Total_Cmp%,Total_Cmp,Total_Att,Total_Cmp%,Total_Cmp,Total_Att,Total_Cmp% +Brenden Aaronson,Leeds United,86,109,78.9,1167,48,55,87.3,32,40,80.0,1,4,25.0 +Joshua Acheampong,Chelsea,135,152,88.8,2261,58,63,92.1,75,79,94.9,2,8,25.0 +Tyler Adams,Bournemouth,286,343,83.4,4935,124,142,87.3,141,156,90.4,19,29,65.5 +Tosin Adarabioyo,Chelsea,314,349,90.0,5739,102,109,93.6,194,206,94.2,16,28,57.1 +Simon Adingra,Sunderland,48,69,69.6,803,22,28,78.6,20,22,90.9,3,7,42.9 +Amine Adli,Bournemouth,54,74,73.0,748,34,38,89.5,15,20,75.0,3,6,50.0 +Emmanuel Agbadou,Wolves,280,349,80.2,6296,72,80,90.0,168,187,89.8,38,77,49.4 +Nayef Aguerd,West Ham,106,119,89.1,1731,52,53,98.1,47,49,95.9,5,13,38.5 +Ola Aina,Nott'ham Forest,125,157,79.6,2161,58,59,98.3,56,70,80.0,9,21,42.9 +Rayan Aรฏt-Nouri,Manchester City,105,121,86.8,1529,64,71,90.1,39,40,97.5,2,5,40.0 +Kristoffer Ajer,Brentford,23,24,95.8,428,7,7,100.0,15,16,93.8,1,1,100.0 +Nathan Akรฉ,Manchester City,52,61,85.2,863,24,24,100.0,22,29,75.9,5,7,71.4 +Carlos Alcaraz,Everton,28,36,77.8,509,11,14,78.6,14,17,82.4,3,3,100.0 +Omar Alderete,Sunderland,285,337,84.6,5883,86,96,89.6,160,172,93.0,38,66,57.6 +Alisson,Liverpool,211,254,83.1,5044,56,56,100.0,106,107,99.1,49,91,53.8 +Ethan Ampadu,Leeds United,253,306,82.7,4813,91,104,87.5,127,144,88.2,30,49,61.2 +Joachim Andersen,Fulham,377,447,84.3,8057,112,120,93.3,195,211,92.4,64,105,61.0 +Elliot Anderson,Nott'ham Forest,453,530,85.5,7707,204,218,93.6,215,231,93.1,29,62,46.8 +Andrรฉ,Wolves,159,174,91.4,2721,73,77,94.8,74,78,94.9,11,16,68.8 +Jaidon Anthony,Burnley,103,159,64.8,1545,60,79,75.9,34,46,73.9,5,18,27.8 +Alphonse Areola,West Ham,68,106,64.2,1703,17,17,100.0,37,37,100.0,14,52,26.9 +Jhon Arias,Wolves,101,123,82.1,1672,52,58,89.7,44,55,80.0,5,10,50.0 +Harrison Armstrong,Everton,0,0,,0,0,0,,0,0,,0,0, +Tolu Arokodare,Wolves,11,22,50.0,127,8,12,66.7,2,7,28.6,0,0, +Yasin Ayari,Brighton,161,198,81.3,2691,75,84,89.3,70,82,85.4,11,22,50.0 +Benoรฎt Badiashile,Chelsea,53,57,93.0,989,22,23,95.7,24,24,100.0,7,10,70.0 +Dilane Bakwa,Nott'ham Forest,60,81,74.1,902,36,40,90.0,18,23,78.3,4,12,33.3 +Carlos Baleba,Brighton,83,107,77.6,1573,37,42,88.1,29,38,76.3,15,21,71.4 +Daniel Ballard,Sunderland,61,71,85.9,1208,19,20,95.0,36,40,90.0,5,10,50.0 +Harvey Barnes,Newcastle Utd,68,92,73.9,1038,43,49,87.8,21,28,75.0,4,9,44.4 +Thierno Barry,Everton,23,33,69.7,272,15,18,83.3,6,11,54.5,0,1,0.0 +Calvin Bassey,Fulham,377,428,88.1,6800,147,163,90.2,197,208,94.7,29,47,61.7 +Altay Bayฤฑndฤฑr,Manchester Utd,122,192,63.5,3438,30,30,100.0,52,53,98.1,40,109,36.7 +Jean-Ricner Bellegarde,Wolves,74,97,76.3,1408,27,31,87.1,37,45,82.2,9,17,52.9 +Rodrigo Bentancur,Tottenham,204,235,86.8,3483,86,98,87.8,106,111,95.5,9,16,56.3 +Sander Berge,Fulham,266,305,87.2,4420,123,137,89.8,128,137,93.4,13,16,81.3 +Lucas Bergvall,Tottenham,101,143,70.6,1736,47,56,83.9,41,52,78.8,10,28,35.7 +Beto,Everton,26,48,54.2,304,18,30,60.0,7,15,46.7,0,0, +Marco Bizot,Aston Villa,46,67,68.7,1272,10,10,100.0,22,22,100.0,14,35,40.0 +Oscar Bobb,Manchester City,103,115,89.6,1438,67,71,94.4,28,31,90.3,4,7,57.1 +Lamare Bogarde,Aston Villa,92,99,92.9,1300,57,59,96.6,30,32,93.8,3,4,75.0 +Jayden Bogle,Leeds United,181,238,76.1,2968,88,102,86.3,72,88,81.8,13,27,48.1 +Sven Botman,Newcastle Utd,118,140,84.3,2250,37,43,86.0,70,75,93.3,10,19,52.6 +Jarrod Bowen,West Ham,119,163,73.0,1758,73,84,86.9,35,42,83.3,6,14,42.9 +Conor Bradley,Liverpool,157,182,86.3,2395,90,96,93.8,57,64,89.1,8,15,53.3 +Brian Brobbey,Sunderland,4,7,57.1,45,3,4,75.0,1,1,100.0,0,1,0.0 +Armando Broja,Burnley,3,3,100.0,42,2,2,100.0,1,1,100.0,0,0, +David Brooks,Bournemouth,145,210,69.0,2202,75,87,86.2,52,74,70.3,10,29,34.5 +Jacob Bruun Larsen,Burnley,43,54,79.6,548,32,36,88.9,10,13,76.9,0,3,0.0 +Emi Buendรญa,Aston Villa,91,114,79.8,1325,56,62,90.3,24,29,82.8,6,11,54.5 +Hugo Bueno,Wolves,160,214,74.8,2872,67,81,82.7,76,89,85.4,16,35,45.7 +Santiago Bueno,Wolves,94,110,85.5,1847,23,28,82.1,62,66,93.9,7,11,63.6 +Facundo Buonanotte,Chelsea,20,22,90.9,251,15,16,93.8,3,4,75.0,1,1,100.0 +Dan Burn,Newcastle Utd,183,262,69.8,3530,64,83,77.1,99,126,78.6,17,41,41.5 +Sam Byram,Leeds United,1,1,100.0,12,1,1,100.0,0,0,,0,0, +Moisรฉs Caicedo,Chelsea,400,439,91.1,6314,215,223,96.4,166,177,93.8,16,25,64.0 +Tom Cairney,Fulham,87,94,92.6,1549,35,37,94.6,42,46,91.3,8,9,88.9 +Riccardo Calafiori,Arsenal,241,301,80.1,3883,119,135,88.1,102,121,84.3,13,28,46.4 +Dominic Calvert-Lewin,Leeds United,44,63,69.8,546,28,39,71.8,12,15,80.0,1,2,50.0 +Fabio Carvalho,Brentford,19,25,76.0,289,11,14,78.6,3,4,75.0,4,6,66.7 +Casemiro,Manchester Utd,173,212,81.6,3173,77,85,90.6,68,77,88.3,25,35,71.4 +Matty Cash,Aston Villa,267,337,79.2,4550,119,124,96.0,118,140,84.3,22,55,40.0 +Timothy Castagne,Fulham,109,137,79.6,1656,59,66,89.4,41,51,80.4,6,13,46.2 +Trevoh Chalobah,Chelsea,377,419,90.0,6942,139,148,93.9,208,218,95.4,28,44,63.6 +Rayan Cherki,Manchester City,53,64,82.8,736,31,34,91.2,17,19,89.5,2,6,33.3 +Federico Chiesa,Liverpool,13,18,72.2,155,9,10,90.0,4,6,66.7,0,1,0.0 +Ryan Christie,Bournemouth,72,96,75.0,1214,38,41,92.7,28,38,73.7,5,9,55.6 +Samuel Chukwueze,Fulham,18,25,72.0,270,10,11,90.9,6,8,75.0,1,3,33.3 +Nathaniel Clyne,Crystal Palace,3,3,100.0,28,3,3,100.0,0,0,,0,0, +Sรฉamus Coleman,Everton,0,0,,0,0,0,,0,0,,0,0, +Nathan Collins,Brentford,194,231,84.0,4152,44,52,84.6,126,134,94.0,24,43,55.8 +Lewis Cook,Bournemouth,1,2,50.0,13,1,1,100.0,0,0,,0,1,0.0 +Diego Coppola,Brighton,3,3,100.0,71,1,1,100.0,1,1,100.0,1,1,100.0 +Marc Cucurella,Chelsea,308,353,87.3,5112,146,159,91.8,136,147,92.5,18,32,56.3 +Jorge Cuenca,Fulham,51,58,87.9,868,20,20,100.0,28,28,100.0,2,7,28.6 +Josh Cullen,Burnley,217,266,81.6,3706,114,124,91.9,73,93,78.5,26,38,68.4 +Matheus Cunha,Manchester Utd,75,91,82.4,987,41,45,91.1,20,27,74.1,5,7,71.4 +Diogo Dalot,Manchester Utd,139,185,75.1,2468,65,74,87.8,57,79,72.2,15,27,55.6 +Mikkel Damsgaard,Brentford,101,149,67.8,1786,45,57,78.9,40,47,85.1,12,36,33.3 +Kevin Danso,Tottenham,24,25,96.0,543,3,3,100.0,15,16,93.8,6,6,100.0 +Karl Darlow,Leeds United,113,178,63.5,3404,15,15,100.0,59,59,100.0,39,103,37.9 +Maxim De Cuyper,Brighton,116,143,81.1,2016,50,57,87.7,59,67,88.1,6,15,40.0 +Liam Delap,Chelsea,9,15,60.0,137,4,7,57.1,5,7,71.4,0,0, +Sepp van den Berg,Brentford,211,238,88.7,3773,74,81,91.4,118,128,92.2,15,21,71.4 +Justin Devenny,Crystal Palace,38,53,71.7,619,20,24,83.3,14,18,77.8,3,7,42.9 +Kiernan Dewsbury-Hall,Everton,181,232,78.0,2972,90,104,86.5,72,82,87.8,14,36,38.9 +Bafodรฉ Diakitรฉ,Bournemouth,279,314,88.9,5548,76,83,91.6,175,186,94.1,26,39,66.7 +Amad Diallo,Manchester Utd,185,205,90.2,2537,126,131,96.2,49,58,84.5,6,9,66.7 +Habib Diarra,Sunderland,93,121,76.9,1328,56,63,88.9,31,38,81.6,4,7,57.1 +Rรบben Dias,Manchester City,284,310,91.6,4794,124,132,93.9,150,159,94.3,8,16,50.0 +Tyler Dibling,Everton,7,7,100.0,115,3,3,100.0,4,4,100.0,0,0, +Lucas Digne,Aston Villa,152,215,70.7,2432,82,94,87.2,58,74,78.4,8,24,33.3 +Issa Diop,Fulham,24,35,68.6,414,9,12,75.0,14,14,100.0,1,6,16.7 +El Hadji Malick Diouf,West Ham,223,306,72.9,3553,129,151,85.4,72,94,76.6,18,44,40.9 +Matt Doherty,Wolves,87,105,82.9,1800,33,36,91.7,42,50,84.0,12,17,70.6 +Jeremy Doku,Manchester City,111,133,83.5,1532,73,81,90.1,33,37,89.2,3,4,75.0 +Nicolรกs Domรญnguez,Nott'ham Forest,37,38,97.4,715,13,13,100.0,18,19,94.7,6,6,100.0 +Gianluigi Donnarumma,Manchester City,53,81,65.4,1433,10,10,100.0,22,22,100.0,20,47,42.6 +Patrick Dorgu,Manchester Utd,147,204,72.1,2121,88,116,75.9,47,65,72.3,6,10,60.0 +Max Dowman,Arsenal,4,4,100.0,66,2,2,100.0,2,2,100.0,0,0, +Martin Dรบbravka,Burnley,110,222,49.5,3586,22,22,100.0,45,46,97.8,43,153,28.1 +Lewis Dunk,Brighton,339,371,91.4,5953,140,143,97.9,178,189,94.2,15,29,51.7 +Odsonne ร‰douard,Crystal Palace,0,1,0.0,0,0,0,,0,0,,0,0, +Marcus Edwards,Burnley,0,0,,0,0,0,,0,0,,0,0, +Hjalmar Ekdal,Burnley,138,163,84.7,2390,64,67,95.5,63,71,88.7,11,19,57.9 +Hugo Ekitike,Liverpool,55,82,67.1,724,34,46,73.9,17,23,73.9,2,2,100.0 +Anthony Elanga,Newcastle Utd,51,87,58.6,685,28,40,70.0,16,27,59.3,3,12,25.0 +Harvey Elliott,Liverpool,4,8,50.0,52,3,6,50.0,1,1,100.0,0,1,0.0 +Harvey Elliott,Aston Villa,53,62,85.5,688,39,41,95.1,11,13,84.6,1,5,20.0 +Wataru Endo,Liverpool,19,22,86.4,283,14,16,87.5,3,4,75.0,2,2,100.0 +Romain Esse,Crystal Palace,5,7,71.4,72,2,2,100.0,2,3,66.7,0,0, +Maxime Estรจve,Burnley,158,182,86.8,2776,68,74,91.9,72,79,91.1,12,21,57.1 +Evanilson,Bournemouth,89,106,84.0,967,57,62,91.9,21,24,87.5,1,2,50.0 +Eberechi Eze,Arsenal,105,125,84.0,1619,57,61,93.4,40,47,85.1,6,9,66.7 +Eberechi Eze,Crystal Palace,16,25,64.0,241,7,9,77.8,7,10,70.0,1,5,20.0 +Bruno Fernandes,Manchester Utd,353,458,77.1,6422,168,185,90.8,124,149,83.2,52,95,54.7 +Mateus Fernandes,West Ham,151,167,90.4,2506,70,80,87.5,55,57,96.5,17,20,85.0 +Enzo Fernรกndez,Chelsea,332,404,82.2,5263,182,200,91.0,119,140,85.0,26,51,51.0 +Zian Flemming,Burnley,2,5,40.0,23,2,3,66.7,0,0,,0,1,0.0 +Phil Foden,Manchester City,127,166,76.5,1855,88,95,92.6,25,32,78.1,11,28,39.3 +Wesley Fofana,Chelsea,96,105,91.4,1478,48,50,96.0,45,49,91.8,2,4,50.0 +Lyle Foster,Burnley,58,94,61.7,809,34,44,77.3,12,21,57.1,5,8,62.5 +Jeremie Frimpong,Liverpool,31,41,75.6,445,16,20,80.0,13,18,72.2,0,0, +Niclas Fรผllkrug,West Ham,53,87,60.9,654,39,58,67.2,8,13,61.5,2,3,66.7 +Cody Gakpo,Liverpool,146,202,72.3,2038,91,103,88.3,44,67,65.7,7,18,38.9 +Idrissa Gana Gueye,Everton,235,273,86.1,3701,116,128,90.6,101,113,89.4,13,20,65.0 +Ben Gannon-Doak,Bournemouth,11,13,84.6,134,9,9,100.0,2,3,66.7,0,0, +Alejandro Garnacho,Chelsea,22,28,78.6,290,14,14,100.0,6,10,60.0,1,3,33.3 +James Garner,Everton,267,328,81.4,4657,124,137,90.5,116,140,82.9,22,39,56.4 +Lutsharel Geertruida,Sunderland,15,24,62.5,239,7,10,70.0,5,6,83.3,2,4,50.0 +Tyrique George,Chelsea,19,22,86.4,326,9,10,90.0,8,9,88.9,2,2,100.0 +Morgan Gibbs-White,Nott'ham Forest,222,276,80.4,3355,130,148,87.8,77,93,82.8,11,22,50.0 +Jamie Gittens,Chelsea,38,43,88.4,577,24,24,100.0,12,13,92.3,2,2,100.0 +Wilfried Gnonto,Leeds United,55,71,77.5,693,40,44,90.9,8,14,57.1,3,4,75.0 +Joรฃo Gomes,Wolves,251,288,87.2,4222,123,134,91.8,93,106,87.7,25,32,78.1 +Rodrigo Gomes,Wolves,40,52,76.9,707,22,27,81.5,15,22,68.2,3,3,100.0 +Toti Gomes,Wolves,250,295,84.7,4590,89,98,90.8,140,152,92.1,17,40,42.5 +Diego Gรณmez,Brighton,53,68,77.9,865,28,31,90.3,18,21,85.7,5,9,55.6 +Joe Gomez,Liverpool,14,19,73.7,231,7,8,87.5,7,8,87.5,0,3,0.0 +Nicolรกs Gonzรกlez,Manchester City,212,232,91.4,3019,120,125,96.0,80,88,90.9,5,7,71.4 +Anthony Gordon,Newcastle Utd,33,50,66.0,495,25,31,80.6,5,9,55.6,3,8,37.5 +Ryan Gravenberch,Liverpool,343,390,87.9,5677,157,173,90.8,166,173,96.0,14,31,45.2 +Archie Gray,Tottenham,40,43,93.0,719,15,15,100.0,21,24,87.5,2,2,100.0 +Jack Grealish,Everton,162,198,81.8,2339,96,107,89.7,53,61,86.9,7,9,77.8 +Brajan Gruda,Brighton,31,40,77.5,429,18,21,85.7,11,14,78.6,0,2,0.0 +Ilia Gruev,Leeds United,91,98,92.9,1377,53,55,96.4,28,32,87.5,7,7,100.0 +Gabriel Gudmundsson,Leeds United,238,309,77.0,3560,139,158,88.0,76,89,85.4,13,36,36.1 +Marc Guรฉhi,Crystal Palace,248,289,85.8,4544,85,94,90.4,136,148,91.9,22,38,57.9 +Evann Guessand,Aston Villa,44,61,72.1,530,34,39,87.2,9,14,64.3,1,1,100.0 +Luis Guilherme,West Ham,10,10,100.0,135,6,6,100.0,3,3,100.0,0,0, +Bruno Guimarรฃes,Newcastle Utd,172,209,82.3,2929,83,95,87.4,69,80,86.3,15,23,65.2 +Marc Guiu,Sunderland,3,5,60.0,30,3,3,100.0,0,1,0.0,0,0, +Marc Guiu,Chelsea,0,1,0.0,0,0,0,,0,0,,0,1,0.0 +Malo Gusto,Chelsea,185,217,85.3,3064,94,104,90.4,75,85,88.2,13,17,76.5 +Joลกko Gvardiol,Manchester City,146,167,87.4,2648,50,53,94.3,86,95,90.5,8,13,61.5 +Viktor Gyรถkeres,Arsenal,41,74,55.4,498,29,45,64.4,7,12,58.3,2,4,50.0 +Erling Haaland,Manchester City,45,67,67.2,441,25,36,69.4,9,15,60.0,0,1,0.0 +Lewis Hall,Newcastle Utd,52,78,66.7,954,23,27,85.2,24,31,77.4,5,18,27.8 +Jack Harrison,Leeds United,39,61,63.9,686,23,28,82.1,11,18,61.1,5,13,38.5 +Quilindschy Hartman,Burnley,127,197,64.5,2122,65,74,87.8,51,79,64.6,9,31,29.0 +Jorrel Hato,Chelsea,97,110,88.2,1439,49,56,87.5,45,46,97.8,2,6,33.3 +Kai Havertz,Arsenal,5,9,55.6,79,4,7,57.1,1,2,50.0,0,0, +Ayden Heaven,Manchester Utd,0,0,,0,0,0,,0,0,,0,0, +Jan Paul van Hecke,Brighton,307,355,86.5,6289,107,116,92.2,157,172,91.3,40,60,66.7 +Hwang Hee-chan,Wolves,28,52,53.8,496,13,24,54.2,13,22,59.1,2,5,40.0 +Dean Henderson,Crystal Palace,147,226,65.0,4218,45,45,100.0,51,52,98.1,51,129,39.5 +Jordan Henderson,Brentford,152,200,76.0,2905,65,72,90.3,61,78,78.2,19,35,54.3 +Rico Henry,Brentford,36,69,52.2,569,18,25,72.0,17,32,53.1,1,7,14.3 +Mads Hermansen,West Ham,104,157,66.2,2719,22,22,100.0,57,57,100.0,25,78,32.1 +Aaron Hickey,Brentford,37,44,84.1,642,19,22,86.4,11,14,78.6,6,7,85.7 +James Hill,Bournemouth,94,139,67.6,1674,45,48,93.8,39,53,73.6,9,29,31.0 +Jack Hinshelwood,Brighton,46,54,85.2,725,24,27,88.9,18,19,94.7,2,4,50.0 +Ki-Jana Hoever,Wolves,29,47,61.7,624,10,17,58.8,14,16,87.5,5,11,45.5 +Callum Hudson-Odoi,Nott'ham Forest,139,178,78.1,1915,82,89,92.1,41,52,78.8,7,19,36.8 +Will Hughes,Crystal Palace,123,154,79.9,2018,66,77,85.7,43,52,82.7,11,20,55.0 +Trai Hume,Sunderland,216,316,68.4,3264,125,147,85.0,74,104,71.2,12,44,27.3 +Bashir Humphreys,Burnley,2,2,100.0,22,2,2,100.0,0,0,,0,0, +Omari Hutchinson,Nott'ham Forest,17,26,65.4,287,8,10,80.0,6,9,66.7,2,5,40.0 +Igor,West Ham,0,0,,0,0,0,,0,0,,0,0, +Tim Iroegbunam,Everton,46,64,71.9,783,21,29,72.4,23,27,85.2,2,3,66.7 +Andy Irving,West Ham,17,23,73.9,345,5,6,83.3,11,13,84.6,1,4,25.0 +Alexander Isak,Liverpool,25,38,65.8,350,14,17,82.4,10,14,71.4,0,2,0.0 +Wilson Isidor,Sunderland,20,28,71.4,242,14,15,93.3,3,6,50.0,1,3,33.3 +Alex Iwobi,Fulham,214,275,77.8,3780,91,111,82.0,96,118,81.4,22,33,66.7 +Daniel James,Leeds United,23,50,46.0,341,12,23,52.2,6,12,50.0,3,7,42.9 +Reece James,Chelsea,300,356,84.3,5271,142,150,94.7,126,142,88.7,30,52,57.7 +Vitaly Janelt,Brentford,14,18,77.8,263,6,7,85.7,6,8,75.0,2,2,100.0 +Mathias Jensen,Brentford,45,61,73.8,945,22,25,88.0,14,20,70.0,9,15,60.0 +Igor Jesus,Nott'ham Forest,18,21,85.7,220,13,15,86.7,5,5,100.0,0,0, +รlex Jimรฉnez,Bournemouth,82,102,80.4,1204,46,51,90.2,28,33,84.8,4,8,50.0 +Raรบl Jimรฉnez,Fulham,19,29,65.5,234,14,16,87.5,3,6,50.0,1,2,50.0 +Joรฃo Pedro,Chelsea,139,169,82.2,1782,98,105,93.3,27,37,73.0,6,9,66.7 +Joelinton,Newcastle Utd,75,99,75.8,983,49,60,81.7,21,26,80.8,2,4,50.0 +Brennan Johnson,Tottenham,48,72,66.7,730,30,34,88.2,15,24,62.5,3,12,25.0 +Sam Johnstone,Wolves,49,90,54.4,1534,10,10,100.0,22,22,100.0,17,58,29.3 +Curtis Jones,Liverpool,147,161,91.3,2543,70,73,95.9,67,69,97.1,9,15,60.0 +Eli Junior Kroupi,Bournemouth,4,9,44.4,67,2,5,40.0,1,2,50.0,1,1,100.0 +Hamed Junior Traorรจ,Bournemouth,2,3,66.7,23,1,1,100.0,1,2,50.0,0,0, +James Justin,Leeds United,3,6,50.0,101,0,1,0.0,1,2,50.0,2,3,66.7 +Filip Jรธrgensen,Chelsea,27,35,77.1,616,10,11,90.9,11,11,100.0,6,13,46.2 +Ferdi Kadioglu,Brighton,95,116,81.9,1515,44,49,89.8,44,52,84.6,4,5,80.0 +Sasa Kalajdzic,Wolves,4,6,66.7,41,4,4,100.0,0,0,,0,0, +Arnaud Kalimuendo,Nott'ham Forest,23,29,79.3,328,11,13,84.6,12,14,85.7,0,0, +Daichi Kamada,Crystal Palace,102,135,75.6,1788,48,60,80.0,37,41,90.2,11,21,52.4 +Boubacar Kamara,Aston Villa,106,120,88.3,1714,46,53,86.8,49,53,92.5,7,9,77.8 +Michael Kayode,Brentford,167,228,73.2,2624,96,103,93.2,51,68,75.0,16,45,35.6 +Michael Keane,Everton,196,236,83.1,4149,50,56,89.3,119,129,92.2,27,49,55.1 +Caoimhรญn Kelleher,Brentford,149,222,67.1,4536,18,18,100.0,77,80,96.3,53,122,43.4 +Milos Kerkez,Liverpool,248,300,82.7,3966,136,142,95.8,94,112,83.9,12,31,38.7 +Kevin,Fulham,33,48,68.8,618,14,16,87.5,10,13,76.9,7,13,53.8 +Abdukodir Khusanov,Manchester City,121,148,81.8,1957,64,66,97.0,49,57,86.0,6,15,40.0 +Max Kilman,West Ham,279,308,90.6,5394,80,84,95.2,171,175,97.7,24,43,55.8 +Joshua King,Fulham,92,116,79.3,1324,54,65,83.1,33,37,89.2,2,3,66.7 +Justin Kluivert,Bournemouth,52,72,72.2,813,23,30,76.7,23,28,82.1,3,9,33.3 +Ibrahima Konatรฉ,Liverpool,375,410,91.5,6630,141,147,95.9,215,233,92.3,15,22,68.2 +Ezri Konsa,Aston Villa,304,319,95.3,5301,129,131,98.5,158,164,96.3,16,22,72.7 +Ladislav Krejฤรญ,Wolves,127,143,88.8,2543,40,43,93.0,74,76,97.4,13,20,65.0 +Mohammed Kudus,Tottenham,158,215,73.5,2726,80,89,89.9,53,71,74.6,23,42,54.8 +Maxence Lacroix,Crystal Palace,217,251,86.5,4286,69,74,93.2,124,133,93.2,23,41,56.1 +Senne Lammens,Manchester Utd,20,46,43.5,818,4,4,100.0,4,4,100.0,12,38,31.6 +Jamaal Lascelles,Newcastle Utd,6,8,75.0,116,1,2,50.0,5,5,100.0,0,1,0.0 +Josh Laurent,Burnley,73,102,71.6,1064,43,49,87.8,25,34,73.5,3,12,25.0 +Romรฉo Lavia,Chelsea,29,32,90.6,620,8,9,88.9,14,15,93.3,6,7,85.7 +Enzo Le Fรฉe,Sunderland,125,158,79.1,1799,77,86,89.5,40,48,83.3,5,11,45.5 +Bernd Leno,Fulham,214,276,77.5,4992,52,52,100.0,124,124,100.0,38,99,38.4 +Jefferson Lerma,Crystal Palace,61,77,79.2,1047,28,29,96.6,27,31,87.1,5,14,35.7 +Rico Lewis,Manchester City,113,127,89.0,1908,52,56,92.9,50,53,94.3,9,13,69.2 +Keane Lewis-Potter,Brentford,78,108,72.2,1340,34,35,97.1,33,45,73.3,9,21,42.9 +Myles Lewis-Skelly,Arsenal,53,57,93.0,909,24,24,100.0,26,27,96.3,3,4,75.0 +Matthijs de Ligt,Manchester Utd,305,349,87.4,5554,111,120,92.5,171,189,90.5,19,31,61.3 +Victor Lindelรถf,Aston Villa,6,6,100.0,92,3,3,100.0,3,3,100.0,0,0, +Valentino Livramento,Newcastle Utd,235,289,81.3,3777,134,150,89.3,82,96,85.4,16,30,53.3 +Sean Longstaff,Leeds United,179,212,84.4,3329,72,78,92.3,78,89,87.6,23,34,67.6 +Fer Lรณpez,Wolves,46,59,78.0,822,18,23,78.3,25,28,89.3,3,6,50.0 +Florentino Luรญs,Burnley,61,70,87.1,1032,31,35,88.6,20,23,87.0,7,8,87.5 +Douglas Luiz,Nott'ham Forest,50,60,83.3,681,33,35,94.3,17,19,89.5,0,5,0.0 +Saลกa Lukiฤ‡,Fulham,191,234,81.6,3159,95,108,88.0,73,83,88.0,20,33,60.6 +Ian Maatsen,Aston Villa,113,137,82.5,1913,57,60,95.0,45,55,81.8,9,16,56.3 +Alexis Mac Allister,Liverpool,194,231,84.0,2906,104,114,91.2,74,86,86.0,9,17,52.9 +Noni Madueke,Arsenal,73,122,59.8,1044,43,57,75.4,21,37,56.8,5,11,45.5 +Gabriel Magalhรฃes,Arsenal,376,418,90.0,6761,148,159,93.1,190,205,92.7,29,41,70.7 +Soungoutou Magassa,West Ham,56,67,83.6,872,32,33,97.0,17,20,85.0,5,9,55.6 +Harry Maguire,Manchester Utd,91,109,83.5,1779,25,28,89.3,58,68,85.3,7,11,63.6 +Kobbie Mainoo,Manchester Utd,62,72,86.1,958,32,35,91.4,22,25,88.0,3,4,75.0 +Donyell Malen,Aston Villa,30,38,78.9,429,19,21,90.5,8,8,100.0,2,2,100.0 +Giorgi Mamardashvili,Liverpool,42,52,80.8,973,16,16,100.0,16,16,100.0,10,20,50.0 +Reinildo Mandava,Sunderland,190,230,82.6,3009,95,103,92.2,85,96,88.5,8,21,38.1 +Omar Marmoush,Manchester City,39,49,79.6,508,27,30,90.0,11,13,84.6,0,0, +Callum Marshall,West Ham,1,1,100.0,11,1,1,100.0,0,0,,0,0, +Gabriel Martinelli,Arsenal,18,29,62.1,346,9,12,75.0,5,6,83.3,3,6,50.0 +Emiliano Martรญnez,Aston Villa,115,145,79.3,2795,33,33,100.0,59,60,98.3,23,51,45.1 +Arthur Masuaku,Sunderland,44,61,72.1,720,21,23,91.3,18,22,81.8,5,14,35.7 +Pape Matar Sarr,Tottenham,134,163,82.2,2083,70,85,82.4,47,53,88.7,10,13,76.9 +Jean-Philippe Mateta,Crystal Palace,47,71,66.2,719,27,31,87.1,12,18,66.7,6,9,66.7 +Konstantinos Mavropanos,West Ham,208,236,88.1,4193,64,65,98.5,111,120,92.5,27,44,61.4 +Eliezer Mayenda,Sunderland,11,16,68.8,145,8,12,66.7,3,3,100.0,0,0, +Noussair Mazraoui,Manchester Utd,74,97,76.3,1269,34,41,82.9,33,39,84.6,6,11,54.5 +Bryan Mbeumo,Manchester Utd,172,239,72.0,2862,89,106,84.0,54,74,73.0,19,30,63.3 +James Mcatee,Nott'ham Forest,55,62,88.7,771,31,32,96.9,18,20,90.0,3,5,60.0 +John McGinn,Aston Villa,181,228,79.4,3450,67,73,91.8,91,110,82.7,22,35,62.9 +Dwight McNeil,Everton,5,6,83.3,66,4,5,80.0,1,1,100.0,0,0, +Hannibal Mejbri,Burnley,46,58,79.3,738,21,26,80.8,14,18,77.8,6,8,75.0 +Mikel Merino,Arsenal,79,101,78.2,1012,54,60,90.0,21,25,84.0,2,10,20.0 +Antoni Milambo,Brentford,8,11,72.7,116,4,6,66.7,4,5,80.0,0,0, +Nikola Milenkoviฤ‡,Nott'ham Forest,303,331,91.5,5690,100,103,97.1,178,185,96.2,22,37,59.5 +Lewis Miley,Newcastle Utd,79,99,79.8,1319,34,39,87.2,41,46,89.1,4,9,44.4 +James Milner,Brighton,49,61,80.3,993,20,22,90.9,21,22,95.5,8,16,50.0 +Veljko Milosavljeviฤ‡,Bournemouth,35,42,83.3,598,14,15,93.3,19,20,95.0,2,6,33.3 +Tyrone Mings,Aston Villa,302,338,89.3,5767,101,109,92.7,174,180,96.7,24,43,55.8 +Yankuba Minteh,Brighton,74,138,53.6,1053,44,59,74.6,18,38,47.4,5,18,27.8 +Tyrick Mitchell,Crystal Palace,183,261,70.1,2934,90,103,87.4,76,96,79.2,12,33,36.4 +Kaoru Mitoma,Brighton,106,137,77.4,1326,72,85,84.7,28,38,73.7,2,3,66.7 +Morato,Nott'ham Forest,176,200,88.0,3221,77,87,88.5,79,86,91.9,19,25,76.0 +Cristhian Mosquera,Arsenal,157,173,90.8,2657,64,67,95.5,79,87,90.8,10,14,71.4 +Yerson Mosquera,Wolves,61,65,93.8,1249,10,10,100.0,48,49,98.0,2,4,50.0 +Mason Mount,Manchester Utd,81,107,75.7,1270,47,55,85.5,20,24,83.3,10,19,52.6 +Nordi Mukiele,Sunderland,187,250,74.8,3515,72,79,91.1,89,114,78.1,23,46,50.0 +Marshall Munetsi,Wolves,55,90,61.1,837,28,48,58.3,19,28,67.9,5,8,62.5 +Rodrigo Muniz,Fulham,11,36,30.6,186,6,11,54.5,4,13,30.8,1,6,16.7 +Daniel Muรฑoz,Crystal Palace,155,204,76.0,2602,82,94,87.2,55,70,78.6,16,26,61.5 +Murillo,Nott'ham Forest,187,224,83.5,3766,60,62,96.8,108,119,90.8,19,42,45.2 +Jacob Murphy,Newcastle Utd,59,97,60.8,986,34,47,72.3,19,30,63.3,6,15,40.0 +Vitaliy Mykolenko,Everton,99,135,73.3,1640,52,57,91.2,43,56,76.8,4,20,20.0 +David Mรธller Wolfe,Wolves,26,50,52.0,422,14,21,66.7,10,20,50.0,2,7,28.6 +Iliman Ndiaye,Everton,118,152,77.6,1622,72,83,86.7,38,46,82.6,2,4,50.0 +Dan Ndoye,Nott'ham Forest,90,132,68.2,1436,51,61,83.6,28,43,65.1,7,13,53.8 +Dan Neil,Sunderland,0,0,,0,0,0,,0,0,,0,0, +Pedro Neto,Chelsea,144,203,70.9,2241,78,93,83.9,52,71,73.2,10,28,35.7 +Rio Ngumoha,Liverpool,8,9,88.9,110,5,5,100.0,3,4,75.0,0,0, +Eddie Nketiah,Crystal Palace,7,9,77.8,150,1,1,100.0,5,7,71.4,1,1,100.0 +Lukas Nmecha,Leeds United,25,36,69.4,274,17,20,85.0,5,8,62.5,0,0, +Matheus Nunes,Manchester City,158,182,86.8,2503,89,97,91.8,58,61,95.1,8,16,50.0 +Ethan Nwaneri,Arsenal,72,81,88.9,1391,25,27,92.6,39,40,97.5,7,12,58.3 +Jake O'Brien,Everton,163,252,64.7,3035,74,91,81.3,71,109,65.1,17,34,50.0 +Matt O'Riley,Brighton,51,61,83.6,762,29,32,90.6,13,15,86.7,5,7,71.4 +Wilson Odobert,Tottenham,92,107,86.0,1402,49,55,89.1,36,43,83.7,5,6,83.3 +Noah Okafor,Leeds United,52,72,72.2,1003,21,26,80.8,22,30,73.3,8,12,66.7 +Amadou Onana,Aston Villa,61,72,84.7,1069,23,25,92.0,30,35,85.7,7,10,70.0 +Frank Onyeka,Brentford,22,30,73.3,303,15,16,93.8,6,9,66.7,1,2,50.0 +William Osula,Newcastle Utd,16,19,84.2,209,11,13,84.6,5,5,100.0,0,0, +Dango Ouattara,Brentford,21,53,39.6,229,16,25,64.0,2,9,22.2,1,9,11.1 +Nico Oโ€™Reilly,Manchester City,133,168,79.2,1675,92,97,94.8,28,38,73.7,5,17,29.4 +Joรฃo Palhinha,Tottenham,209,257,81.3,3749,90,100,90.0,99,112,88.4,17,32,53.1 +Cole Palmer,Chelsea,40,53,75.5,617,20,24,83.3,17,21,81.0,3,5,60.0 +Lucas Paquetรก,West Ham,262,341,76.8,4324,134,151,88.7,98,122,80.3,25,50,50.0 +Bradley Paul Burrowes,Aston Villa,3,8,37.5,65,1,2,50.0,1,1,100.0,1,3,33.3 +Lucas Perri,Leeds United,53,111,47.7,1859,6,6,100.0,20,21,95.2,27,84,32.1 +ฤorฤ‘e Petroviฤ‡,Bournemouth,145,210,69.0,4179,24,24,100.0,70,70,100.0,51,113,45.1 +Jordan Pickford,Everton,168,262,64.1,5634,20,20,100.0,74,75,98.7,74,167,44.3 +Ethan Pinnock,Brentford,51,71,71.8,904,21,26,80.8,24,28,85.7,3,11,27.3 +Yeremi Pino,Crystal Palace,60,86,69.8,947,30,36,83.3,23,34,67.6,5,10,50.0 +Joรซl Piroe,Leeds United,30,37,81.1,448,15,19,78.9,10,10,100.0,1,1,100.0 +Nick Pope,Newcastle Utd,115,179,64.2,3099,18,19,94.7,63,63,100.0,34,96,35.4 +Pedro Porro,Tottenham,256,370,69.2,4931,103,114,90.4,121,145,83.4,31,101,30.7 +Freddie Potts,West Ham,45,60,75.0,623,23,28,82.1,19,25,76.0,0,2,0.0 +Jacob Ramsey,Newcastle Utd,24,28,85.7,358,12,14,85.7,11,12,91.7,1,1,100.0 +David Raya,Arsenal,188,256,73.4,5174,44,44,100.0,92,94,97.9,52,118,44.1 +Tijjani Reijnders,Manchester City,245,286,85.7,3420,147,152,96.7,83,100,83.0,5,18,27.8 +Declan Rice,Arsenal,370,435,85.1,6518,165,181,91.2,167,177,94.4,33,67,49.3 +Chris Richards,Crystal Palace,180,220,81.8,3631,51,59,86.4,114,127,89.8,15,31,48.4 +Richarlison,Tottenham,44,74,59.5,714,28,39,71.8,8,15,53.3,6,8,75.0 +Chris Rigg,Sunderland,18,24,75.0,301,9,13,69.2,7,7,100.0,2,3,66.7 +Patrick Roberts,Sunderland,17,21,81.0,192,11,12,91.7,3,4,75.0,1,1,100.0 +Andrew Robertson,Liverpool,73,94,77.7,1237,42,49,85.7,27,37,73.0,4,8,50.0 +Antonee Robinson,Fulham,50,64,78.1,858,23,24,95.8,20,25,80.0,5,8,62.5 +Joe Rodon,Leeds United,309,341,90.6,5902,88,97,90.7,197,208,94.7,20,29,69.0 +Rodri,Manchester City,186,216,86.1,3206,89,102,87.3,70,81,86.4,19,21,90.5 +Guido Rodrรญguez,West Ham,28,34,82.4,419,16,18,88.9,11,12,91.7,0,2,0.0 +Robin Roefs,Sunderland,181,271,66.8,5133,26,26,100.0,98,99,99.0,56,145,38.6 +Morgan Rogers,Aston Villa,111,162,68.5,1706,60,74,81.1,37,54,68.5,10,20,50.0 +Merlin Rรถhl,Everton,7,10,70.0,98,4,6,66.7,3,4,75.0,0,0, +Cristian Romero,Tottenham,382,426,89.7,7417,116,126,92.1,230,246,93.5,32,47,68.1 +Georginio Rutter,Brighton,41,61,67.2,786,15,21,71.4,20,23,87.0,4,7,57.1 +Josรฉ Sรก,Wolves,89,131,67.9,2683,13,13,100.0,47,48,97.9,28,69,40.6 +Noah Sadiki,Sunderland,221,264,83.7,3384,115,125,92.0,83,95,87.4,13,24,54.2 +Bukayo Saka,Arsenal,99,134,73.9,1694,55,62,88.7,31,41,75.6,11,23,47.8 +Mohamed Salah,Liverpool,148,215,68.8,2151,92,108,85.2,41,64,64.1,11,26,42.3 +William Saliba,Arsenal,357,383,93.2,6152,151,153,98.7,187,199,94.0,15,22,68.2 +Robert Sรกnchez,Chelsea,164,246,66.7,4247,33,34,97.1,82,83,98.8,46,126,36.5 +Jadon Sancho,Aston Villa,15,18,83.3,220,6,6,100.0,8,9,88.9,0,1,0.0 +Ibrahim Sangarรฉ,Nott'ham Forest,206,231,89.2,3477,92,98,93.9,92,101,91.1,15,21,71.4 +Andrey Santos,Chelsea,87,103,84.5,1173,53,60,88.3,27,30,90.0,1,2,50.0 +Ismaila Sarr,Crystal Palace,52,71,73.2,824,28,35,80.0,21,25,84.0,2,5,40.0 +Sรกvio,Manchester City,26,32,81.3,372,18,22,81.8,7,7,100.0,1,1,100.0 +Nicolรฒ Savona,Nott'ham Forest,20,26,76.9,305,13,14,92.9,6,9,66.7,1,2,50.0 +Kevin Schade,Brentford,54,91,59.3,890,27,44,61.4,16,25,64.0,6,11,54.5 +Fabian Schรคr,Newcastle Utd,184,225,81.8,4201,50,55,90.9,99,106,93.4,35,62,56.5 +Alex Scott,Bournemouth,203,250,81.2,3261,102,108,94.4,79,89,88.8,15,38,39.5 +Jenson Seelt,Sunderland,96,110,87.3,1786,38,40,95.0,46,48,95.8,10,16,62.5 +Matz Sels,Nott'ham Forest,119,158,75.3,2887,38,38,100.0,57,58,98.3,23,61,37.7 +Antoine Semenyo,Bournemouth,147,219,67.1,2343,83,103,80.6,51,74,68.9,12,32,37.5 +Marcos Senesi,Bournemouth,349,450,77.6,7803,86,95,90.5,183,200,91.5,76,138,55.1 +Benjamin ล eลกko,Manchester Utd,49,74,66.2,595,28,42,66.7,9,18,50.0,4,4,100.0 +Ryan Sessegnon,Fulham,177,251,70.5,2697,96,111,86.5,72,93,77.4,6,25,24.0 +Luke Shaw,Manchester Utd,335,400,83.8,5553,173,200,86.5,120,136,88.2,29,43,67.4 +Bernardo Silva,Manchester City,155,182,85.2,2496,81,87,93.1,56,64,87.5,13,19,68.4 +Jota Silva,Nott'ham Forest,2,3,66.7,27,1,1,100.0,1,2,50.0,0,0, +Xavi Simons,Tottenham,89,119,74.8,1518,44,54,81.5,33,38,86.8,9,22,40.9 +Adam Smith,Bournemouth,69,87,79.3,1169,35,39,89.7,27,30,90.0,7,16,43.8 +Emile Smith Rowe,Fulham,76,87,87.4,1145,40,47,85.1,27,30,90.0,5,5,100.0 +Dominic Solanke,Tottenham,2,2,100.0,19,2,2,100.0,0,0,,0,0, +Julio Soler,Bournemouth,0,0,,0,0,0,,0,0,,0,0, +Oliver Sonne,Burnley,19,29,65.5,247,14,17,82.4,3,7,42.9,1,3,33.3 +Tomรกลก Souฤek,West Ham,70,91,76.9,945,40,45,88.9,21,29,72.4,3,5,60.0 +Djed Spence,Tottenham,207,261,79.3,3558,94,103,91.3,93,111,83.8,16,34,47.1 +Anton Stach,Leeds United,197,267,73.8,2978,109,130,83.8,59,76,77.6,15,30,50.0 +John Stones,Manchester City,201,220,91.4,3334,89,91,97.8,105,112,93.8,6,12,50.0 +Jรธrgen Strand Larsen,Wolves,41,58,70.7,563,28,39,71.8,8,10,80.0,3,4,75.0 +Pascal Struijk,Leeds United,382,410,93.2,7044,145,147,98.6,209,215,97.2,24,38,63.2 +Crysencio Summerville,West Ham,89,103,86.4,1208,55,60,91.7,23,26,88.5,4,6,66.7 +Dominik Szoboszlai,Liverpool,390,468,83.3,6945,181,197,91.9,149,163,91.4,48,83,57.8 +Chemsdine Talbi,Sunderland,116,149,77.9,1816,67,81,82.7,37,45,82.2,8,16,50.0 +Ao Tanaka,Leeds United,89,104,85.6,1408,48,55,87.3,31,35,88.6,7,7,100.0 +James Tarkowski,Everton,203,258,78.7,4291,57,61,93.4,109,127,85.8,33,58,56.9 +Marcus Tavernier,Bournemouth,164,211,77.7,2656,82,93,88.2,63,74,85.1,11,29,37.9 +Loum Tchaouna,Burnley,42,60,70.0,555,27,30,90.0,14,19,73.7,0,1,0.0 +Jackson Tchatchoua,Wolves,101,144,70.1,1732,50,62,80.6,45,61,73.8,6,16,37.5 +Mathys Tel,Tottenham,22,32,68.8,461,11,13,84.6,7,10,70.0,4,5,80.0 +Kenny Tete,Fulham,207,251,82.5,3212,117,127,92.1,67,82,81.7,17,31,54.8 +Thiago,Brentford,63,85,74.1,862,41,48,85.4,15,26,57.7,4,4,100.0 +Malick Thiaw,Newcastle Utd,50,61,82.0,975,17,21,81.0,29,33,87.9,3,5,60.0 +Youri Tielemans,Aston Villa,195,235,83.0,3535,87,98,88.8,77,88,87.5,26,38,68.4 +Jurriรซn Timber,Arsenal,250,294,85.0,3944,117,127,92.1,126,144,87.5,5,14,35.7 +Jean-Clair Todibo,West Ham,80,88,90.9,1467,30,31,96.8,42,43,97.7,8,11,72.7 +Sandro Tonali,Newcastle Utd,247,309,79.9,4122,132,146,90.4,88,107,82.2,21,47,44.7 +Pau Torres,Aston Villa,138,154,89.6,2421,63,67,94.0,55,60,91.7,17,24,70.8 +James Trafford,Manchester City,88,118,74.6,1948,25,27,92.6,43,43,100.0,19,46,41.3 +Adama Traorรฉ,Fulham,31,45,68.9,460,15,18,83.3,13,18,72.2,1,4,25.0 +Bertrand Traorรฉ,Sunderland,25,33,75.8,346,14,15,93.3,5,8,62.5,2,3,66.7 +Kieran Trippier,Newcastle Utd,216,283,76.3,4026,86,102,84.3,98,115,85.2,28,56,50.0 +Leandro Trossard,Arsenal,89,129,69.0,1734,34,46,73.9,40,50,80.0,12,20,60.0 +Adrien Truffert,Bournemouth,294,366,80.3,4472,171,182,94.0,105,135,77.8,13,33,39.4 +Stefanos Tzimas,Brighton,1,3,33.3,7,1,1,100.0,0,0,,0,1,0.0 +Christantus Uche,Crystal Palace,5,9,55.6,70,3,4,75.0,2,4,50.0,0,1,0.0 +Destiny Udogie,Tottenham,183,216,84.7,2932,88,100,88.0,87,94,92.6,5,10,50.0 +Manuel Ugarte,Manchester Utd,108,128,84.4,1768,60,69,87.0,37,40,92.5,9,13,69.2 +Lesley Ugochukwu,Burnley,64,77,83.1,1029,32,35,91.4,23,26,88.5,7,8,87.5 +Virgil van Dijk,Liverpool,495,554,89.4,9087,181,188,96.3,269,286,94.1,38,70,54.3 +Joรซl Veltman,Brighton,80,108,74.1,1520,30,35,85.7,40,47,85.1,9,20,45.0 +Micky van de Ven,Tottenham,388,421,92.2,7790,110,113,97.3,250,259,96.5,27,46,58.7 +Bart Verbruggen,Brighton,162,230,70.4,4039,42,42,100.0,76,78,97.4,44,109,40.4 +Guglielmo Vicario,Tottenham,251,311,80.7,5805,51,51,100.0,154,154,100.0,46,105,43.8 +Kyle Walker,Burnley,181,266,68.0,3342,89,102,87.3,61,86,70.9,25,60,41.7 +Kyle Walker-Peters,West Ham,184,217,84.8,2680,112,119,94.1,57,69,82.6,7,17,41.2 +Aaron Wan-Bissaka,West Ham,119,144,82.6,1656,76,82,92.7,33,41,80.5,5,7,71.4 +James Ward-Prowse,West Ham,185,218,84.9,3214,81,84,96.4,71,81,87.7,23,37,62.2 +Ollie Watkins,Aston Villa,58,79,73.4,681,41,54,75.9,11,15,73.3,1,1,100.0 +Danny Welbeck,Brighton,43,57,75.4,514,24,30,80.0,14,16,87.5,0,1,0.0 +Adam Wharton,Crystal Palace,122,156,78.2,2271,41,48,85.4,66,72,91.7,12,27,44.4 +Ben White,Arsenal,21,27,77.8,353,10,11,90.9,10,12,83.3,1,3,33.3 +Mats Wieffer,Brighton,102,125,81.6,1699,52,57,91.2,41,49,83.7,6,9,66.7 +Neco Williams,Nott'ham Forest,302,370,81.6,4643,163,178,91.6,125,145,86.2,9,32,28.1 +Estรชvรฃo Willian,Chelsea,84,104,80.8,1372,43,51,84.3,37,41,90.2,3,8,37.5 +Joe Willock,Newcastle Utd,15,20,75.0,261,7,9,77.8,7,7,100.0,1,2,50.0 +Callum Wilson,West Ham,13,22,59.1,164,5,7,71.4,6,10,60.0,0,1,0.0 +Harry Wilson,Fulham,106,143,74.1,1584,59,65,90.8,33,44,75.0,8,22,36.4 +Ben Winterburn,Bournemouth,0,1,0.0,0,0,1,0.0,0,0,,0,0, +Florian Wirtz,Liverpool,194,251,77.3,3086,99,117,84.6,72,86,83.7,16,28,57.1 +Nick Woltemade,Newcastle Utd,29,39,74.4,314,22,27,81.5,6,10,60.0,0,0, +Chris Wood,Nott'ham Forest,48,70,68.6,670,28,35,80.0,17,25,68.0,0,2,0.0 +Joe Worrall,Burnley,1,4,25.0,26,0,1,0.0,1,2,50.0,0,0, +Granit Xhaka,Sunderland,333,415,80.2,6457,130,149,87.2,146,169,86.4,49,78,62.8 +Yehor Yarmoliuk,Brentford,167,202,82.7,2882,83,91,91.2,66,75,88.0,13,25,52.0 +Ryan Yates,Nott'ham Forest,20,22,90.9,320,12,12,100.0,5,5,100.0,3,4,75.0 +Leny Yoro,Manchester Utd,215,250,86.0,3858,92,99,92.9,103,113,91.2,17,31,54.8 +Oleksandr Zinchenko,Nott'ham Forest,165,178,92.7,2726,78,80,97.5,77,82,93.9,10,13,76.9 +Joshua Zirkzee,Manchester Utd,26,32,81.3,301,22,26,84.6,4,6,66.7,0,0, +Martรญn Zubimendi,Arsenal,362,407,88.9,5900,172,187,92.0,165,183,90.2,21,28,75.0 +Martin ร˜degaard,Arsenal,111,140,79.3,1924,53,59,89.8,45,55,81.8,12,19,63.2 diff --git a/debug/debug_match_page.html b/debug/debug_match_page.html new file mode 100644 index 0000000000000000000000000000000000000000..a53e93e544406727be24568ed944c6e5d6c5d442 --- /dev/null +++ b/debug/debug_match_page.html @@ -0,0 +1,2 @@ +Just a moment...

fbref.com

Verify you are human by completing the action below.

fbref.com needs to review the security of your connection before proceeding.
+ \ No newline at end of file diff --git a/debug/debug_page.html b/debug/debug_page.html new file mode 100644 index 0000000000000000000000000000000000000000..a64d121dbde0ecf18c95190ed69757d2a5410e88 --- /dev/null +++ b/debug/debug_page.html @@ -0,0 +1,4807 @@ + + + + + + + + + + + + + Premier League Passing Stats | FBref.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + + + +
+

+ + 2025-2026 Premier League Passing Stats + +

+
+ + + + + + +
+ + + + +

Governing Country: England eng

Level: 1st Tier (See League Structure)

Gender: Male

Most Goals: Erling Haaland (Manchester City) - 8

Most Assists: Jack Grealish (Everton) - 4

Most Clean Sheets: Nick Pope (Newcastle United) - 4

Big 5: View Big 5 European Leagues together

+ + + + + + +
+
+ +
+ + +
+ +
+ + + +
+ + +
+
+
+ + + + + + +
+ + +
+ + +
+ + + + + + + + + + + +
+ + +
+
+
+ + + +
+ + + + + +
+ + + + +
+

Squad Passing 2025-2026 Premier League

+
+
    +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
Squad Passing 2025-2026 Premier League Table
Total Short Medium Long Expected
Squad
โ–ฒ
# Pl 90s Cmp Att Cmp% TotDist PrgDist Cmp Att Cmp% Cmp Att Cmp% Cmp Att Cmp% Ast xAG xA A-xAG KP 1/3 PPA CrsPA PrgP
Arsenal216.02555308882.745611144321119124589.91165130889.122640855.495.46.2+3.657184557247
Aston Villa226.02483301482.443255141741133123891.51078121988.422041053.743.74.6+0.3422225111250
Bournemouth237.02577331977.646023161141128125889.71123131185.726856747.396.15.7+2.9642445716267
Brentford206.01612216074.6304381208566677486.070585182.819942347.055.03.90.035119249140
Brighton206.01943243679.8348261280185395589.385698187.318235950.766.74.5-0.7461643611178
Burnley206.01448201371.9256021009972282287.852266478.616039240.853.73.2+1.3391182812138
Chelsea246.02970348285.350275150831396151392.31331146091.220138951.776.75.5+0.354236445240
Crystal Palace206.01771230876.7330091238472682887.781594186.619641846.937.85.6-4.8491224213176
Everton196.01934253076.4361871331282794987.1854101784.021844349.276.75.4+0.3561594712210
Fulham217.02730338480.748283167521191133789.11226138988.325749052.455.96.1-0.9542116818250
Leeds United216.02018256678.63566212895924105187.981192887.420742249.124.03.0-2.043143256162
Liverpool216.02926350483.549934164301379151191.31258141389.022843252.886.95.3+1.1672666414292
Manchester City226.02756324584.943615139321449155992.91071119689.515931949.8109.77.9+0.3541934710236
Manchester Utd196.02363297279.541393141091140129288.2899106884.225445555.837.26.5-4.2691965623255
Newcastle Utd216.02027267375.83662713523910107284.9878103784.720845845.434.54.1-1.5511684013237
Nott'ham Forest236.02837340383.448210155871332144192.41256141588.819940948.735.34.7-2.3572825819300
Sunderland236.01954251777.6355151231186597289.082996885.622345149.453.93.0+1.1401603112172
Tottenham196.02521309481.546601157421008112789.41241138489.723747150.395.44.6+3.6502195019238
West Ham236.02241276881.038154130461051115690.9919103588.819340547.744.64.3-0.6521674214202
Wolves236.02163276478.3418081422282797684.71078123787.122946649.133.63.4-0.6441705426179
+ + + +
+
+ +
Squad Passing 2025-2026 Premier League Table
Total Short Medium Long Expected
Squad # Pl 90s Cmp Att Cmp% TotDist PrgDist Cmp Att Cmp% Cmp Att Cmp% Cmp Att Cmp% Ast xAG xA A-xAG KP 1/3 PPA CrsPA PrgP
vs Arsenal216.01870239578.1329331178287799388.372485384.919840049.523.42.4-1.445134306152
vs Aston Villa226.01735222677.9317851214878488189.072183586.319339049.537.14.9-4.1571523513173
vs Bournemouth237.02443318176.844962178111073122387.71056126283.726553349.765.44.1+0.6511984813230
vs Brentford206.02600317282.044417149311262139090.81060122286.722842254.065.65.1+0.4592205218242
vs Brighton206.02422297881.342597153341132126889.31010113888.823344053.055.75.2-0.751187407212
vs Burnley206.03317391784.754008162271699184392.21321148988.723041156.068.57.8-2.5863027328316
vs Chelsea246.01770225878.4321171196774986087.178691286.218836651.486.74.8+1.3441414117173
vs Crystal Palace206.02884343983.952548158631159126291.81422155891.325447054.025.05.5-3.0522575013289
vs Everton196.02461304580.841801135961163130389.31031116688.419140647.044.94.3-0.9521824916225
vs Fulham217.02708331981.648519169971169129190.51219137788.525248651.975.15.2+1.948231445261
vs Leeds United216.02769334382.851249161951108125088.61358152788.927446159.464.74.3+1.3482355123253
vs Liverpool216.01321193768.224584956358770683.152565680.016944637.955.43.8-0.4421024114148
vs Manchester City226.02124266179.83948813217930105188.5936107187.422643152.444.24.2-0.2471724411181
vs Manchester Utd196.02026260877.73456312866984111088.679494584.018539147.386.86.3+1.238141417178
vs Newcastle Utd216.02130274977.53761812700949107488.4928107686.219446541.753.83.6+1.243170479217
vs Nott'ham Forest236.01953250378.0343941234587398288.982997585.019540148.676.64.8+0.4521374113185
vs Sunderland236.02677325082.446259145411200130592.01218136889.021044147.644.54.9-0.5482325822279
vs Tottenham196.01770229677.1315331196778688189.276288985.717240142.925.54.5-3.5531313711168
vs West Ham236.02479303781.643392145111114123790.11124126788.720241149.1106.65.4+3.4552064711239
vs Wolves236.02370292681.042261144721048116689.91091123688.320541549.4107.46.3+2.6522135013248
+ + + +
+ +
+ + +
+ +
+

Player Passing 2025-2026 Premier League

+
+
    +
  • Scroll Right For More Stats ยท Switch to Widescreen View
+
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Player Passing 2025-2026 Premier League Table
TotalShortMediumLongExpected
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
1Brenden Aaronsonus USAFWLeeds United24-34720003.7719078.9930223414787.2253083.3030.000.70.8-0.7463010Matches
2Joshua Acheampongeng ENGDFChelsea19-15220061.410010793.51668324444597.8555796.51425.000.00.00.000003Matches
3Tyler Adamsus USAMFBournemouth26-23219996.728634383.44935126712414287.314115690.4192965.500.00.20.01290031Matches
4Tosin Adarabioyoeng ENGDFChelsea28-01019973.931434990.05739199010210993.619420694.2162857.100.00.10.00171015Matches
5Simon Adingraci CIVFWSunderland23-27620022.6456668.274073212777.8182090.03742.900.50.2-0.540312Matches
6Amine Adlima MARFW,MFBournemouth25-14720001.7547473.0748176343889.5152075.03650.000.20.1-0.241105Matches
7Emmanuel Agbadouci CIVDFWolves28-10919975.528034980.262962535728090.016818789.8387749.400.10.3-0.12355225Matches
8Nayef Aguerdma MARDFWest Ham29-18819962.010611989.11731663525398.1474995.951338.500.00.00.0012009Matches
9Ola Ainang NGADFNott'ham Forest28-36119963.012515779.62161962585998.3567080.092142.900.10.3-0.12132111Matches
10Rayan Aรฏt-Nouridz ALGDFManchester City24-12020012.210512186.81529385647190.1394097.52540.000.20.5-0.226007Matches
11Kristoffer Ajerno NORDFBrentford27-17019980.3232495.842816377100.0151693.811100.010.70.4+0.312006Matches
12Nathan Akรฉnl NEDDFManchester City30-22819951.3526185.28632972424100.0222975.95771.400.10.2-0.112326Matches
13Carlos Alcarazar ARGFW,MFEverton22-30820021.3283677.850926111478.6141782.433100.000.00.00.001001Matches
14Omar Alderetepy PARDFSunderland28-28219965.424529184.251192162717989.913814992.6366258.110.00.3+1.01212223Matches
15Alissonbr BRAGKLiverpool33-00219926.021125483.1504432815656100.010610799.1499153.800.00.00.003004Matches
16Ethan Ampaduwls WALMFLeeds United25-02020003.918723181.03465886758885.2849885.7233762.200.00.10.00170010Matches
17Joachim Andersendk DENDFFulham29-12619967.037744784.38057292711212093.319521192.46410561.000.70.7-0.72454028Matches
18Elliot Andersoneng ENGMFNott'ham Forest22-33220026.045353085.57707210620421893.621523193.1296246.811.30.8-0.38609358Matches
19Andrรฉbr BRAMFWolves24-08020013.915917491.42721744737794.8747894.9111668.800.00.10.00170015Matches
20Jaidon Anthonyeng ENGMF,FWBurnley25-30719995.910315964.81545393607975.9344673.951827.810.30.6+0.7786311Matches
21Alphonse Areolafr FRAGKWest Ham32-21919932.0547968.413037891212100.03232100.0103528.600.00.00.003000Matches
22Jhon Ariasco COLFWWolves28-01319973.010112382.11672315525889.7445580.051050.000.30.2-0.367419Matches
23Harrison Armstrongeng ENGFWEverton18-25820070.0000000000000.00.00.000000Matches
24Tolu Arokodareng NGAFWWolves24-31520001.8112250.01271181266.72728.60000.10.0-0.111000Matches
25Yasin Ayarise SWEMFBrighton21-36320035.016119881.32691744758489.3708285.4112250.000.10.4-0.14114119Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
26Benoรฎt Badiashilefr FRADFChelsea24-19220010.18988.91562833100.044100.01250.000.00.00.001001Matches
27Dilane Bakwafr FRAFWNott'ham Forest23-03920021.8608174.1902239364090.0182378.341233.300.30.2-0.343117Matches
28Carlos Balebacm CMRMFBrighton21-27420043.48310777.61573296374288.1293876.3152171.400.00.00.028106Matches
29Daniel Ballardnir NIRDFSunderland26-01219991.9283677.86052861111100.0131776.54850.000.40.2-0.424001Matches
30Harvey Barneseng ENGFW,MFNewcastle Utd27-29919973.3689273.91038362434987.8212875.04944.400.70.8-0.7568316Matches
31Thierno Barryfr FRAFWEverton22-34820021.8233369.727227151883.361154.5010.000.00.00.000002Matches
32Calvin Basseyng NGADFFulham25-27719997.037742888.16800236314716390.219720894.7294761.700.00.10.01322019Matches
33Altay Bayฤฑndฤฑrtr TURGKManchester Utd27-17319986.012219263.5343826463030100.0525398.14010936.700.20.0-0.218100Matches
34Jean-Ricner Bellegardeht HAIMF,FWWolves27-09919982.7749776.31408355273187.1374582.291752.900.30.2-0.3326017Matches
35Rodrigo Bentancuruy URUMFTottenham28-10119973.617820586.83072704718286.69610096.081553.300.20.4-0.23183020Matches
36Sander Bergeno NORMFFulham27-23219986.626630587.24420114412313789.812813793.4131681.300.30.3-0.32204129Matches
37Lucas Bergvallse SWEMFTottenham19-24420064.110114370.61736489475683.9415278.8102835.710.30.2+0.72134117Matches
38Betogw GNBFWEverton27-24619984.2264854.230439183060.071546.70000.20.1-0.213002Matches
39Marco Bizotnl NEDGKAston Villa34-20819912.0466768.712728361010100.02222100.0143540.000.00.00.000001Matches
40Oscar Bobbno NORFW,MFManchester City22-08420033.210311589.61438373677194.4283190.34757.110.40.6+0.6549210Matches
41Lamare Bogardenl NEDMFAston Villa21-27220042.1929992.91300288575996.6303293.83475.000.00.10.0110104Matches
42Jayden Bogleeng ENGDFLeeds United25-06920005.814819675.52377735748686.0566882.4102245.500.10.1-0.1282113Matches
43Sven Botmannl NEDDFNewcastle Utd25-26520002.811814084.32250905374386.0707593.3101952.600.00.00.010005Matches
44Jarrod Boweneng ENGFW,MFWest Ham28-28819966.010714474.31561311657487.8323688.951241.700.90.5-0.9466213Matches
45Conor Bradleynir NIRDFLiverpool22-08720032.113515786.02017488818694.2455286.571353.800.00.10.00143110Matches
46Brian Brobbeynl NEDFWSunderland23-24520020.33560.038122366.711100.0010.000.00.00.001002Matches
47Armando Brojaal ALBFWBurnley24-02420010.133100.042022100.011100.00000.00.00.000000Matches
48David Brookswls WALFW,MFBournemouth28-08819975.014521069.02202724758786.2527470.3102934.511.41.3-0.47156416Matches
49Jacob Bruun Larsendk DENFW,MFBurnley27-01519982.3435479.6548115323688.9101376.9030.010.60.3+0.421114Matches
50Emi Buendรญaar ARGMF,FWAston Villa28-28319963.19111479.81325373566290.3242982.861154.510.40.5+0.66116019Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
51Hugo Buenoes ESPDF,MFWolves23-01620024.016021474.82872701678182.7768985.4163545.700.50.5-0.588101010Matches
52Santiago Buenouy URUDFWolves26-32919982.49411085.51847616232882.1626693.971163.600.00.10.023119Matches
53Facundo Buonanottear ARGMFChelsea20-28520040.5202290.925129151693.83475.011100.000.30.1-0.322000Matches
54Dan Burneng ENGDFNewcastle Utd33-14819926.018326269.835301604648377.19912678.6174141.500.00.10.0192117Matches
55Sam Byrameng ENGDFLeeds United32-01819930.111100.012011100.0000000.00.00.000000Matches
56Moisรฉs Caicedoec ECUMFChelsea23-33620016.035538891.55686165518519196.915216293.8152365.200.10.2-0.11382033Matches
57Tom Cairneysct SCOMFFulham34-25719911.3879492.61549469353794.6424691.38988.900.00.10.01102012Matches
58Riccardo Calafioriit ITADFArsenal23-13820025.018723579.63134765839785.6859985.9132748.120.50.1+1.54114121Matches
59Dominic Calvert-Lewineng ENGFWLeeds United28-20219973.0335066.0431100203066.7101376.91250.000.10.1-0.133205Matches
60Fabio Carvalhopt PORFW,MFBrentford23-03520021.1192576.028999111478.63475.04666.700.00.00.000102Matches
61Casemirobr BRAMFManchester Utd33-22319922.712615481.82453757546090.0475487.0243275.000.20.1-0.22100015Matches
62Matty Cashpl POLDFAston Villa28-05819975.826733779.24550130311912496.011814084.3225540.000.40.5-0.44303124Matches
63Timothy Castagnebe BELDF,FWFulham29-30319953.410913779.61656481596689.4415180.461346.200.20.1-0.232528Matches
64Trevoh Chalobaheng ENGDFChelsea26-09119995.337741990.06942169713914893.920821895.4284463.600.00.60.01354125Matches
65Rayan Cherkifr FRAMF,FWManchester City22-04820030.8536482.8736236313491.2171989.52633.300.50.5-0.517306Matches
66Federico Chiesait ITAFW,MFLiverpool27-34419970.8131872.21554391090.04666.7010.000.00.00.002102Matches
67Ryan Christiesct SCOMFBournemouth30-22419951.7729675.01214280384192.7283873.75955.600.00.10.01171012Matches
68Samuel Chukwuezeng NGAFW,MFFulham26-13519990.4182572.027054101190.96875.01333.310.50.4+0.520101Matches
69Nathaniel Clyneeng ENGDFCrystal Palace34-18219910.133100.028733100.0000000.00.00.000000Matches
70Sรฉamus Colemanie IRLFWEverton36-35819880.0000000000000.00.00.000000Matches
71Nathan Collinsie IRLDFBrentford24-15720016.019423184.041521641445284.612613494.0244355.800.50.4-0.5292014Matches
72Lewis Cookeng ENGMFBournemouth28-24319970.01250.013011100.000010.000.00.00.000000Matches
73Diego Coppolait ITADFBrighton21-28020030.233100.0713511100.011100.011100.000.00.00.001001Matches
74Marc Cucurellaes ESPDFChelsea27-07419985.026329988.04401133412213193.112213193.1142556.010.80.5+0.27193019Matches
75Jorge Cuencaes ESPDFFulham25-32119991.0515887.98683572020100.02828100.02728.600.00.00.001002Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
76Josh Cullenie IRLMFBurnley29-18019965.621726681.63706121911412491.9739378.5263868.410.80.6+0.27323031Matches
77Matheus Cunhabr BRAFW,MFManchester Utd26-13019993.6708582.4937283384192.7192673.15771.400.20.5-0.2465314Matches
78Diogo Dalotpt PORDFManchester Utd26-20019993.011314975.81941603596689.4415870.7122157.110.90.7+0.165448Matches
79Mikkel Damsgaarddk DENMFBrentford25-09320003.810114967.81786747455778.9404785.1123633.300.40.8-0.45125318Matches
80Kevin Dansoat AUTDFTottenham27-01519980.1212295.54608733100.0141593.344100.000.00.00.003002Matches
81Karl Darlowwls WALGKLeeds United34-36119903.08813963.3271219781414100.04242100.0328239.000.00.00.001000Matches
82Maxim De Cuyperbe BELDFBrighton24-28620003.111614381.12016656505787.7596788.161540.000.10.5-0.1273111Matches
83Liam Delapeng ENGFWChelsea22-23820031.091560.0137384757.15771.40000.00.00.001103Matches
84Sepp van den Bergnl NEDDFBrentford23-28820016.021123888.737731234748191.411812892.2152171.400.00.00.0090012Matches
85Justin Devennynir NIRMF,FWCrystal Palace21-35820031.6385371.7619134202483.3141877.83742.900.10.2-0.125105Matches
86Kiernan Dewsbury-Halleng ENGMFEverton27-02819986.018123278.029727779010486.5728287.8143638.911.81.4-0.8132316234Matches
87Bafodรฉ Diakitรฉfr FRADFBournemouth24-27120016.027931488.955481829768391.617518694.1263966.700.10.1-0.12200017Matches
88Amad Dialloci CIVDF,MFManchester Utd23-08520024.014316288.319894469810395.1364481.86966.700.70.7-0.7556116Matches
89Habib Diarrasn SENMFSunderland21-27420043.89312176.91328214566388.9313881.64757.100.00.10.01112013Matches
90Rรบben Diaspt PORDFManchester City28-14319975.128431091.64794172812413293.915015994.381650.000.00.10.00130014Matches
91Tyler Diblingeng ENGFWEverton19-20620060.277100.0115333100.044100.00000.00.00.001000Matches
92Lucas Dignefr FRADFAston Villa32-07619934.415221570.72432869829487.2587478.482433.310.30.5+0.7156210Matches
93Issa Diopfr FRADFFulham28-26819970.9243568.641422691275.01414100.01616.700.00.00.005005Matches
94El Hadji Malick Dioufsn SENDFWest Ham20-28020046.019927672.13167118311413584.4678876.1143737.830.70.7+2.36136318Matches
95Matt Dohertyie IRLDFWolves33-26119922.58710582.91800629333691.7425084.0121770.600.00.00.005005Matches
96Jeremy Dokube BELMF,FWManchester City23-13020024.111113383.51532355738190.1333789.23475.032.11.5+0.912610213Matches
97Nicolรกs Domรญnguezar ARGMFNott'ham Forest27-09819980.5373897.47152311313100.0181994.766100.000.00.00.0012005Matches
98Gianluigi Donnarummait ITAGKManchester City26-22119993.0538165.4143310371010100.02222100.0204742.600.00.00.001000Matches
99Patrick Dorgudk DENDFManchester Utd20-34320044.914219572.820364258611376.1466175.45955.610.80.6+0.255637Matches
100Max Dowmaneng ENGFWArsenal15-27720090.344100.066022100.022100.00000.00.00.000000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
101Martin Dรบbravkask SVKGKBurnley36-26219896.011022249.5358626972222100.0454697.84315328.100.00.00.005000Matches
102Lewis Dunkeng ENGDFBrighton33-31719916.033937191.45953167114014397.917818994.2152951.700.00.00.01160016Matches
103Odsonne ร‰douardfr FRAFWCrystal Palace27-26119980.0010.00000000000.00.00.000000Matches
104Marcus Edwardseng ENGFWBurnley26-30519980.1000000000000.00.00.000000Matches
105Hjalmar Ekdalse SWEDFBurnley26-34819985.913816384.72390825646795.5637188.7111957.900.10.2-0.125006Matches
106Hugo Ekitikefr FRAFWLiverpool23-10620024.1507665.8643155334475.0142070.022100.010.30.2+0.741308Matches
107Anthony Elangase SWEFW,MFNewcastle Utd23-16020022.9518758.6685165284070.0162759.331225.000.30.2-0.333324Matches
108Harvey Elliotteng ENGMF,FWLiverpool22-18320030.04850.05273650.011100.0010.000.00.00.000000Matches
109Harvey Elliotteng ENGMF,FWAston Villa22-18320031.1536285.5688107394195.1111384.61520.000.00.10.006115Matches
110Wataru Endojp JPNMF,DFLiverpool32-23719930.4161888.924277131586.711100.022100.010.10.0+0.913001Matches
111Romain Esseeng ENGFWCrystal Palace20-14420050.35771.4722922100.02366.70000.00.00.001102Matches
112Maxime Estรจvefr FRADFBurnley23-13120025.915818286.827761107687491.9727991.1122157.100.00.00.01150012Matches
113Evanilsonbr BRAFWBournemouth25-36319996.88910684.0967150576291.9212487.51250.000.80.3-0.893307Matches
114Eberechi Ezeeng ENGFW,MFArsenal27-09719982.6779382.81180316424495.5283482.45862.520.70.8+1.3334014Matches
115Eberechi Ezeeng ENGMFCrystal Palace27-09719980.9162564.0241347977.871070.01520.000.00.00.002001Matches
116Bruno Fernandespt PORMFManchester Utd31-02619946.031240477.25792184914516090.611213483.6488953.901.31.0-1.3163911153Matches
117Mateus Fernandespt PORMF,FWWest Ham21-08620043.411613287.91907348546484.4414395.3131681.300.50.3-0.5482011Matches
118Enzo Fernรกndezar ARGMFChelsea24-26020015.929635683.14716114016117890.410912984.5224153.710.80.5+0.27358137Matches
119Zian Flemmingnl NEDFWBurnley27-06419980.32540.023142366.700010.000.10.0-0.110000Matches
120Phil Fodeneng ENGMFManchester City25-12920002.912716676.51855516889592.6253278.1112839.300.10.2-0.15113014Matches
121Wesley Fofanafr FRADFChelsea24-29120001.49610591.41478359485096.0454991.82450.000.00.00.0010004Matches
122Lyle Fosterza RSAFWBurnley25-03120005.5589461.7809123344477.3122157.15862.510.60.1+0.456005Matches
123Jeremie Frimpongnl NEDDFLiverpool24-29820000.9314175.6445115162080.0131872.20000.00.10.002113Matches
124Niclas Fรผllkrugde GERFWWest Ham32-23719933.5477463.557894355070.06966.72366.700.60.2-0.661305Matches
125Cody Gakponl NEDFW,MFLiverpool26-15019995.211816372.41636418748488.1345265.461637.521.61.0+0.41149118Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
126Idrissa Gana Gueyesn SENMFEverton36-00819895.823527386.1370191911612890.610111389.4132065.000.10.3-0.12194130Matches
127Ben Gannon-Doaksct SCOFWBournemouth19-32720050.3111384.6134799100.02366.70010.60.1+0.410000Matches
128Alejandro Garnachoar ARGFWChelsea21-09520040.12540.015322100.0020.0010.000.10.0-0.110000Matches
129James Garnereng ENGDF,MFEverton24-20520016.026732881.44657156312413790.511614082.9223956.410.70.6+0.37297234Matches
130Lutsharel Geertruidanl NEDDF,MFSunderland25-07820000.6131872.2193547887.533100.02450.000.00.00.000000Matches
131Tyrique Georgeeng ENGFWChelsea19-24220061.5192286.43268391090.08988.922100.000.00.10.003214Matches
132Morgan Gibbs-Whiteeng ENGMFNott'ham Forest25-25020005.122227680.4335578013014887.8779382.8112250.010.40.6+0.672911341Matches
133Jamie Gittenseng ENGFWChelsea21-05720041.6323688.9479401919100.0121392.311100.000.10.1-0.130101Matches
134Wilfried Gnontoit ITAFWLeeds United21-33320032.1557177.569380404490.981457.13475.000.10.1-0.111104Matches
135Joรฃo Gomesbr BRAMFWolves24-23420015.725128887.2422295412313491.89310687.7253278.100.10.3-0.13279333Matches
136Rodrigo Gomespt PORDF,MFWolves22-08920031.8405276.970778222781.5152268.233100.000.10.1-0.111112Matches
137Toti Gomespt PORDFWolves26-26119994.325029584.745901472899890.814015292.1174042.500.00.00.02190010Matches
138Diego Gรณmezpy PARMFBrighton22-19120032.6536877.9865205283190.3182185.75955.600.40.1-0.4210217Matches
139Joe Gomezeng ENGDFLiverpool28-13419970.3141973.7231967887.57887.5030.000.00.00.001001Matches
140Nicolรกs Gonzรกlezes ESPMFManchester City23-27420023.221223291.4301985012012596.0808890.95771.400.30.3-0.32250022Matches
141Anthony Gordoneng ENGFWNewcastle Utd24-22220012.2335066.049590253180.65955.63837.500.10.1-0.133005Matches
142Ryan Gravenberchnl NEDMFLiverpool23-14120025.029133287.74798105313615090.713714296.5122744.410.60.4+0.46323027Matches
143Archie Grayeng ENGMFTottenham19-20620060.8404393.07191171515100.0212487.522100.000.00.00.0110107Matches
144Jack Grealisheng ENGFWEverton30-02419955.216219881.823395619610789.7536186.97977.842.41.8+1.617117224Matches
145Brajan Grudade GERMFBrighton21-12620041.7314077.5429142182185.7111478.6020.010.40.3+0.631103Matches
146Ilia Gruevbg BULMFLeeds United25-15120002.2919892.91377270535596.4283287.577100.000.00.00.009006Matches
147Gabriel Gudmundssonse SWEDFLeeds United26-15819995.921627977.43184141212714388.8678083.8123435.300.30.4-0.34135219Matches
148Marc Guรฉhieng ENGDFCrystal Palace25-08320006.024828985.845441624859490.413614891.9223857.910.50.1+0.53210023Matches
149Evann Guessandci CIVFWAston Villa24-09520012.9446172.1530128343987.291464.311100.000.00.10.002407Matches
150Luis Guilhermebr BRAFW,MFWest Ham19-23720060.61010100.01355266100.033100.00000.00.00.000000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
151Bruno Guimarรฃesbr BRAMFNewcastle Utd27-32219975.017220982.32929836839587.4698086.3152365.200.20.5-0.25155028Matches
152Marc Guiues ESPFWSunderland19-27320060.23560.030733100.0010.00000.00.00.000101Matches
153Malo Gustofr FRADF,FWChelsea22-13820032.513215485.72116524738289.0505590.971163.600.00.10.01121013Matches
154Joลกko Gvardiolhr CRODFManchester City23-25420022.814616787.42648952505394.3869590.581361.500.10.2-0.11212117Matches
155Viktor Gyรถkeresse SWEFWArsenal27-12219985.4366357.144994253964.16966.72450.000.30.4-0.351204Matches
156Erling Haalandno NORFWManchester City25-07520005.6456767.2441142253669.491560.0010.011.10.7-0.144105Matches
157Lewis Halleng ENGDFNewcastle Utd21-02620041.5527866.7954377232785.2243177.451827.800.00.00.005109Matches
158Jack Harrisoneng ENGFWLeeds United28-31819961.2264163.4465149151883.371163.641040.000.00.00.004103Matches
159Quilindschy Hartmannl NEDDFBurnley23-32420016.012719764.52122933657487.8517964.693129.010.30.5+0.7696414Matches
160Jorrel Hatonl NEDDFChelsea19-21120061.6819387.11202279414787.2373897.42633.300.00.00.005003Matches
161Kai Havertzde GERFWArsenal26-11519990.35955.679334757.11250.00000.00.00.000002Matches
162Ayden Heaveneng ENGDFManchester Utd19-01220060.0000000000000.00.00.000000Matches
163Jan Paul van Heckenl NEDDFBrighton25-11820006.030735586.56289278210711692.215717291.3406066.700.00.20.00391034Matches
164Hwang Hee-chankr KORFWWolves29-25119962.4285253.8496117132454.2132259.12540.000.00.00.012004Matches
165Dean Hendersoneng ENGGKCrystal Palace28-20619976.014722665.0421829144545100.0515298.15112939.500.00.00.014100Matches
166Jordan Hendersoneng ENGMFBrentford35-10919904.515220076.02905885657290.3617878.2193554.320.50.3+1.53164220Matches
167Rico Henryeng ENGDFBrentford28-08819971.9366952.2569167182572.0173253.11714.300.10.1-0.111102Matches
168Mads Hermansendk DENGKWest Ham25-08520004.010415766.2271918912222100.05757100.0257832.100.00.00.006000Matches
169Aaron Hickeysct SCODFBrentford23-11620021.1374484.1642244192286.4111478.66785.700.00.00.004103Matches
170James Hilleng ENGDFBournemouth23-26720022.99413967.61674740454893.8395373.692931.000.00.20.00153017Matches
171Jack Hinshelwoodeng ENGMFBrighton20-17620051.2465485.2725139242788.9181994.72450.000.00.00.002102Matches
172Ki-Jana Hoevernl NEDMFWolves23-25920021.2294761.7624184101758.8141687.551145.500.20.2-0.222322Matches
173Callum Hudson-Odoieng ENGFWNott'ham Forest24-33120004.113917878.11915334828992.1415278.871936.800.70.4-0.7937216Matches
174Will Hugheseng ENGMFCrystal Palace30-17019954.612315479.92018702667785.7435282.7112055.000.50.6-0.5596125Matches
175Trai Humenir NIRDFSunderland23-20020026.019028167.62881114911012786.6669668.8114226.200.20.2-0.25204121Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
176Bashir Humphreyseng ENGDFBurnley22-20320030.022100.022322100.0000000.00.00.000000Matches
177Omari Hutchinsoneng ENGFW,MFNott'ham Forest21-33920030.3172665.42879581080.06966.72540.000.60.3-0.641432Matches
178Igorbr BRADFWest Ham27-23919980.0000000000000.00.00.000000Matches
179Tim Iroegbunameng ENGMFEverton22-09620032.4466471.9783176212972.4232785.22366.700.00.00.004006Matches
180Andy Irvingsct SCOMFWest Ham25-14420000.3172373.9345565683.3111384.61425.000.00.10.013004Matches
181Alexander Isakse SWEFWLiverpool26-01319991.2111764.7146446875.044100.0010.000.00.00.001102Matches
182Wilson Isidorfr FRAFWSunderland25-03820003.6172568.019977131492.92540.01333.300.00.00.003003Matches
183Alex Iwobing NGAFW,MFFulham29-15419966.021427577.8378011509111182.09611881.4223366.721.41.1+0.692122638Matches
184Daniel Jameswls WALFWLeeds United27-32819972.4235046.0341148122352.261250.03742.900.30.2-0.323215Matches
185Reece Jameseng ENGDFChelsea25-30019993.724528984.84255123512212895.39811287.5243961.511.10.8-0.17223117Matches
186Vitaly Janeltde GERMFBrentford27-14719980.3141877.8263826785.76875.022100.000.00.00.014002Matches
187Mathias Jensendk DENMFBrentford29-27619961.7456173.8945402222588.0142070.091560.000.20.1-0.23102011Matches
188Igor Jesusbr BRAFW,MFNott'ham Forest24-22120010.9182185.722011131586.755100.00000.00.00.010101Matches
189รlex Jimรฉnezes ESPDFBournemouth20-14920051.78210280.41204332465190.2283384.84850.000.10.0-0.115008Matches
190Raรบl Jimรฉnezmx MEXFWFulham34-15219911.6192965.523430141687.53650.01250.000.00.00.001003Matches
191Joรฃo Pedrobr BRAFW,MFChelsea24-00820015.713115684.01667287939993.9243372.76875.031.40.2+1.6786019Matches
192Joelintonbr BRAMFNewcastle Utd29-05119963.4759975.8983330496081.7212680.82450.000.70.3-0.72120014Matches
193Brennan Johnsonwls WALFWTottenham24-13420013.0487167.6730179303488.2152462.531127.300.20.1-0.235216Matches
194Sam Johnstoneeng ENGGKWolves32-19319932.0499054.4153411351010100.02222100.0175829.300.00.00.003000Matches
195Curtis Joneseng ENGMFLiverpool24-24720012.212714190.12208723606395.2586096.781457.100.40.4-0.43178125Matches
196Eli Junior Kroupifr FRAFW,MFBournemouth19-10320060.34944.46702540.01250.011100.000.00.00.000000Matches
197Hamed Junior Traorรจci CIVMFBournemouth25-23020000.22366.7231811100.01250.00010.20.0+0.810000Matches
198James Justineng ENGDFLeeds United27-22319980.222100.05500011100.011100.000.00.00.000000Matches
199Filip Jรธrgensendk DENGKChelsea23-17120020.9273577.1616444101190.91111100.061346.200.00.00.000000Matches
200Ferdi Kadioglutr TURDF,FWBrighton25-36219993.29511681.91515598444989.8445284.64580.000.40.2-0.42104311Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
201Sasa Kalajdzicat AUTFWWolves28-08919970.24666.741044100.0000000.00.00.000000Matches
202Arnaud Kalimuendofr FRAFWNott'ham Forest23-25720020.8232979.332832111384.6121485.70000.00.00.011102Matches
203Daichi Kamadajp JPNMFCrystal Palace29-06019963.910213575.61788580486080.0374190.2112152.400.90.7-0.95135118Matches
204Boubacar Kamarafr FRAMF,DFAston Villa25-31519992.310612088.31714580465386.8495392.57977.810.10.1+0.92194023Matches
205Michael Kayodeit ITADFBrentford21-08620045.816722873.226247569610393.2516875.0164535.600.40.2-0.4491011Matches
206Michael Keaneeng ENGDFEverton32-26619936.019623683.141491357505689.311912992.2274955.100.10.1-0.1160013Matches
207Caoimhรญn Kelleherie IRLGKBrentford26-31519986.014922267.1453633761818100.0778096.35312243.400.00.00.016001Matches
208Milos Kerkezhu HUNDFLiverpool21-33120035.122727582.53623101912713296.28410183.2112839.300.10.1-0.13194122Matches
209Kevinbr BRAFW,MFFulham22-27320031.0334868.8618130141687.5101376.971353.800.00.10.012333Matches
210Abdukodir Khusanovuz UZBDFManchester City21-21720042.512114881.81957757646697.0495786.061540.000.00.00.003005Matches
211Max Kilmaneng ENGDFWest Ham28-13419976.026028192.548871268778096.316116398.8183256.300.20.2-0.22131115Matches
212Joshua Kingeng ENGMF,FWFulham18-27420075.29211679.31324242546583.1333789.22366.700.10.3-0.1363016Matches
213Justin Kluivertnl NEDMFBournemouth26-15219991.8527272.2813180233076.7232882.13933.300.30.4-0.344206Matches
214Ibrahima Konatรฉfr FRADFLiverpool26-13219995.633736991.35956174412813495.519120792.3142070.000.30.1-0.33291024Matches
215Ezri Konsaeng ENGDFAston Villa27-34619974.730431995.35301183012913198.515816496.3162272.700.00.10.01291018Matches
216Ladislav Krejฤรญcz CZEDF,MFWolves26-16719993.012714388.82543869404393.0747697.4132065.000.10.2-0.13112011Matches
217Mohammed Kudusgh GHAFWTottenham25-06320005.913418672.02338546707889.7415870.7224055.031.81.1+1.21289613Matches
218Maxence Lacroixfr FRADFCrystal Palace25-18120006.021725186.542861446697493.212413393.2234156.110.50.2+0.518108Matches
219Jamaal Lascelleseng ENGDFNewcastle Utd31-32719930.26875.0116141250.055100.0010.000.00.00.000000Matches
220Josh Laurenteng ENGDF,MFBurnley30-15119954.17310271.61064307434987.8253473.531225.000.20.1-0.217107Matches
221Romรฉo Laviabe BELMFChelsea21-27120040.1121392.32617944100.055100.03475.000.00.00.001001Matches
222Enzo Le Fรฉefr FRAFW,MFSunderland25-24320003.29812081.71429303626792.5313783.84850.000.30.2-0.338426Matches
223Bernd Lenode GERGKFulham33-21419927.021427677.5499231525252100.0124124100.0389938.400.00.00.001000Matches
224Jefferson Lermaco COLMFCrystal Palace30-34419942.0617779.21047379282996.6273187.151435.700.00.00.0160010Matches
225Rico Lewiseng ENGDFManchester City20-31720041.911312789.01908434525692.9505394.391369.211.00.70.02120018Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
226Keane Lewis-Pottereng ENGDF,FWBrentford24-22420014.47810872.21340477343597.1334573.392142.900.00.10.013115Matches
227Myles Lewis-Skellyeng ENGDFArsenal19-00820060.8475094.0818722020100.0252696.222100.000.00.00.002001Matches
228Matthijs de Ligtnl NEDDFManchester Utd26-05319996.026730587.54872158010010991.714516090.6182864.300.50.5-0.52134313Matches
229Victor Lindelรถfse SWEMFAston Villa31-07919940.266100.0923533100.033100.00000.00.00.000002Matches
230Valentino Livramentoeng ENGDFNewcastle Utd22-32620025.823528981.33777129913415089.3829685.4163053.310.20.4+0.83245224Matches
231Sean Longstaffeng ENGMFLeeds United27-33919974.313616085.02487660576390.5566290.3182669.211.00.30.0810008Matches
232Fer Lรณpezes ESPMF,FWWolves21-13320041.2465978.0822207182378.3252889.33650.010.70.5+0.3376013Matches
233Florentino Luรญspt PORMFBurnley26-04619992.3617087.11032414313588.6202387.07887.500.00.00.0260012Matches
234Douglas Luizbr BRAMFNott'ham Forest27-14819980.8506083.3681108333594.3171989.5050.000.00.00.012001Matches
235Saลกa Lukiฤ‡rs SRBMFFulham29-05219966.119123481.631597099510888.0738388.0203360.611.00.90.011145118Matches
236Ian Maatsennl NEDDFAston Villa23-20820021.611313782.51913524576095.0455581.891656.300.20.2-0.23143114Matches
237Alexis Mac Allisterar ARGMFLiverpool26-28419983.316719585.624936089310390.3616889.781266.710.80.2+0.24182013Matches
238Noni Maduekeeng ENGFWArsenal23-20820023.47312259.81044279435775.4213756.851145.500.80.8-0.880719Matches
239Gabriel Magalhรฃesbr BRADFArsenal27-28919976.032436389.35876168412913992.816117592.0263868.400.00.10.00210026Matches
240Soungoutou Magassafr FRAMFWest Ham21-36120031.1465485.2723163282996.6131586.75771.400.00.00.004002Matches
241Harry Maguireeng ENGDFManchester Utd32-21319932.08910783.21742509242788.9576785.171163.610.40.0+0.613008Matches
242Kobbie Mainooeng ENGMFManchester Utd20-16820051.1586885.3880179313491.2192286.43475.000.10.1-0.136215Matches
243Donyell Malennl NEDFW,MFAston Villa26-26819991.7303878.942953192190.588100.022100.000.10.1-0.112103Matches
244Reinildo Mandavamz MOZDFSunderland31-25619944.419023082.630097919510392.2859688.582138.100.00.10.00222121Matches
245Omar Marmousheg EGYFW,MFManchester City26-23919991.7394979.650888273090.0111384.60011.00.50.022105Matches
246Gabriel Martinellibr BRAFWArsenal24-10820011.9172568.03184591275.044100.03475.000.00.00.011104Matches
247Emiliano Martรญnezar ARGGKAston Villa33-03219924.011514579.3279517323333100.0596098.3235145.100.00.00.004200Matches
248Arthur Masuakucd CODDFSunderland31-33119930.8253473.5418103101283.3131492.92633.300.00.00.001002Matches
249Pape Matar Sarrsn SENMFTottenham23-02020024.012815682.11954397688381.9445088.091275.020.50.3+1.52160012Matches
250Jean-Philippe Matetafr FRAFWCrystal Palace28-09819975.9477166.2719135273187.1121866.76966.700.10.4-0.113215Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
251Konstantinos Mavropanosgr GREDFWest Ham27-29719974.018421087.637171521545598.210010892.6244158.500.00.00.00140017Matches
252Eliezer Mayendaes ESPFWSunderland20-14920052.461154.585274850.022100.00000.30.0-0.310001Matches
253Noussair Mazraouima MARDFManchester Utd27-32419971.5749776.31269521344182.9333984.661154.500.00.10.00122219Matches
254Bryan Mbeumocm CMRMFManchester Utd26-05819995.814620870.22491606718781.6496872.1172763.001.41.3-1.41289414Matches
255James Mcateeeng ENGMF,FWNott'ham Forest22-35120021.4556288.7771203313296.9182090.03560.000.40.2-0.435409Matches
256John McGinnsct SCOMF,FWAston Villa30-35119945.318122879.43450869677391.89111082.7223562.900.70.7-0.761210624Matches
257Dwight McNeileng ENGFWEverton25-31619990.25683.36684580.011100.00000.10.0-0.110000Matches
258Hannibal Mejbritn TUNMFBurnley22-25620032.8465879.3738165212680.8141877.86875.000.40.2-0.422215Matches
259Mikel Merinoes ESPMF,FWArsenal29-10419962.6709077.8929265475388.7192382.62825.000.10.1-0.129309Matches
260Antoni Milambonl NEDMFBrentford20-18420050.581172.7116494666.74580.00000.00.00.002003Matches
261Nikola Milenkoviฤ‡rs SRBDFNott'ham Forest27-35719976.030333191.55690203410010397.117818596.2223759.500.30.2-0.31293018Matches
262Lewis Mileyeng ENGMFNewcastle Utd19-15620061.9799979.81319286343987.2414689.14944.400.10.0-0.126005Matches
263James Milnereng ENGMFBrighton39-27319861.4496180.3993395202290.9212295.581650.000.00.10.017105Matches
264Veljko Milosavljeviฤ‡rs SRBDFBournemouth18-09820071.1354283.3598208141593.3192095.02633.300.10.2-0.114003Matches
265Tyrone Mingseng ENGDFAston Villa32-20519935.330233889.35767206910110992.717418096.7244355.800.10.1-0.13281027Matches
266Yankuba Mintehgm GAMFWBrighton21-07420045.67413853.61053359445974.6183847.451827.821.81.0+0.284325Matches
267Tyrick Mitchelleng ENGDF,MFCrystal Palace26-03319996.018326170.129349759010387.4769679.2123336.400.60.5-0.6887610Matches
268Kaoru Mitomajp JPNFWBrighton28-13719975.610613777.41326371728584.7283873.72366.711.10.3-0.1757213Matches
269Moratobr BRADFNott'ham Forest24-09620013.017620088.032211513778788.5798691.9192576.000.00.00.00100012Matches
270Cristhian Mosqueraes ESPDFArsenal21-09920042.815717390.82657743646795.5798790.8101471.400.00.00.003005Matches
271Yerson Mosqueraco COLDFWolves24-15520011.5616593.812492561010100.0484998.02450.000.00.00.001000Matches
272Mason Mounteng ENGMFManchester Utd26-26719992.3608273.21011365323884.2151883.391752.900.40.5-0.45132014Matches
273Nordi Mukielefr FRADFSunderland27-33719974.014619375.62861840515691.1718979.8224153.700.30.2-0.345009Matches
274Marshall Munetsizw ZIMFW,MFWolves29-10419964.8559061.1837212284858.3192867.95862.510.40.2+0.624228Matches
275Rodrigo Munizbr BRAFWFulham24-15320013.5113630.61865861154.541330.81616.710.30.0+0.742102Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
276Daniel Muรฑozco COLDF,MFCrystal Palace29-13119966.015520476.02602861829487.2557078.6162661.511.61.2-0.6447412Matches
277Murillobr BRADFNott'ham Forest23-09220023.418722483.537661702606296.810811990.8194245.200.00.20.00260026Matches
278Jacob Murphyeng ENGFW,MFNewcastle Utd30-22219953.2599760.8986298344772.3193063.361540.010.20.3+0.818518Matches
279Vitaliy Mykolenkoua UKRDFEverton26-12819992.99913573.31640643525791.2435676.842020.000.00.30.0163017Matches
280David Mรธller Wolfeno NORMFWolves23-16420021.5265052.0422223142166.7102050.02728.610.30.1+0.712112Matches
281Iliman Ndiayesn SENFWEverton25-21220005.511815277.61622210728386.7384682.62450.010.80.6+0.2945210Matches
282Dan Ndoyech SUIFWNott'ham Forest24-34420004.89013268.21436278516183.6284365.171353.810.50.6+0.544428Matches
283Dan Neileng ENGMFSunderland23-29520010.0000000000000.00.00.000000Matches
284Pedro Netopt PORFWChelsea25-20920004.812617870.82004643678182.7486475.092437.500.90.9-0.9948116Matches
285Rio Ngumohaeng ENGFW,MFLiverpool17-03620080.18988.91101155100.03475.00000.10.1-0.110000Matches
286Eddie Nketiaheng ENGMFCrystal Palace26-12719990.47977.81502011100.05771.411100.000.10.0-0.121001Matches
287Lukas Nmechade GERFWLeeds United26-29419981.5243568.625515172085.04757.10000.00.00.000001Matches
288Matheus Nunespt PORDFManchester City27-03819982.715818286.82503476899791.8586195.181650.000.00.10.0091012Matches
289Ethan Nwanerieng ENGMFArsenal18-19720071.5657389.01262291222491.7353697.271163.600.00.00.0160010Matches
290Jake O'Brienie IRLDFEverton24-14220016.016325264.730351150749181.37110965.1173450.000.00.10.01142215Matches
291Matt O'Rileydk DENMFBrighton24-31720002.0516183.6762174293290.6131586.75771.400.20.2-0.245115Matches
292Wilson Odobertfr FRAFW,MFTottenham20-31020041.7718484.51042249414689.1243080.04580.000.10.1-0.1165012Matches
293Noah Okaforch SUIFWLeeds United25-13320002.5415771.9750268182281.8162466.76875.000.30.3-0.326319Matches
294Amadou Onanabe BELMFAston Villa24-04920011.7617284.71069262232592.0303585.771070.000.00.10.004008Matches
295Frank Onyekang NGAMFBrentford27-27619980.8223073.3303133151693.86966.71250.010.10.4+0.915002Matches
296William Osuladk DENFWNewcastle Utd22-06120031.8161984.220916111384.655100.00000.10.0-0.110001Matches
297Dango Ouattarabf BFAFWBrentford23-23520023.2215339.622993162564.02922.21911.100.20.3-0.210001Matches
298Nico Oโ€™Reillyeng ENGDF,MFManchester City20-19720053.413316879.21675590929794.8283873.751729.400.10.2-0.123319Matches
299Joรฃo Palhinhapt PORMFTottenham30-08719954.718521984.53327850798691.9889691.7152657.700.00.10.01171014Matches
300Cole Palmereng ENGMFChelsea23-15120021.6405375.5617158202483.3172181.03560.000.00.10.0132011Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
301Lucas Paquetรกbr BRAMF,FWWest Ham28-03819975.723130376.23738104812113788.38510878.7214250.000.40.6-0.49269243Matches
302Bradley Paul Burroweseng ENGDFAston Villa17-21420080.23837.56541250.011100.01333.300.00.00.000000Matches
303Lucas Perribr BRAGKLeeds United27-29819973.05311147.71859141566100.0202195.2278432.100.00.00.003000Matches
304ฤorฤ‘e Petroviฤ‡rs SRBGKBournemouth25-36119997.014521069.0417928912424100.07070100.05111345.100.00.00.006000Matches
305Jordan Pickfordeng ENGGKEverton31-21119946.016826264.1563442642020100.0747598.77416744.300.00.00.0016100Matches
306Ethan Pinnockjm JAMDFBrentford32-12819931.7517171.8904423212680.8242885.731127.300.00.00.005002Matches
307Yeremi Pinoes ESPMFCrystal Palace22-34920022.5608669.8947286303683.3233467.651050.000.70.4-0.7344015Matches
308Joรซl Piroenl NEDFWLeeds United26-06319991.5242982.833938131681.366100.011100.000.00.00.003001Matches
309Nick Popeeng ENGGKNewcastle Utd33-16819926.011517964.230992005181994.76363100.0349635.400.00.00.000000Matches
310Pedro Porroes ESPDFTottenham26-02119995.122431670.9435518949310390.310112084.2298434.500.70.9-0.710288724Matches
311Freddie Pottseng ENGMFWest Ham22-02220030.9394979.6532210192286.4172181.0010.000.00.00.002001Matches
312Jacob Ramseyeng ENGMFNewcastle Utd24-12920010.7242885.735881121485.7111291.711100.000.30.1-0.331115Matches
313David Rayaes ESPGKArsenal30-01919956.016222572.0465234403636100.0757797.45111245.500.10.0-0.1117103Matches
314Tijjani Reijndersnl NEDMFManchester City27-06719985.924528685.7342063714715296.78310083.051827.821.00.4+1.06234024Matches
315Declan Riceeng ENGMFArsenal26-26319995.231336585.85596146013815290.813814694.5325954.221.11.0+0.99255231Matches
316Chris Richardsus USADFCrystal Palace25-19020006.018022081.836311181515986.411412789.8153148.400.00.10.0180010Matches
317Richarlisonbr BRAFWTottenham28-14719974.9427159.2679191273773.071450.06875.010.80.2+0.244109Matches
318Chris Riggeng ENGMFSunderland18-10820071.1182475.03015291369.277100.02366.700.00.00.010101Matches
319Patrick Robertseng ENGMF,FWSunderland28-24119970.3172181.019250111291.73475.011100.000.00.00.003001Matches
320Andrew Robertsonsct SCODFLiverpool31-20719940.9617779.2962344374386.0222975.92540.000.30.4-0.3510549Matches
321Antonee Robinsonus USADFFulham28-05719970.7506478.1858317232495.8202580.05862.500.00.10.013215Matches
322Joe Rodonwls WALDFLeeds United27-34719976.024026789.945381400717989.914715495.5182766.700.00.00.0080019Matches
323Rodries ESPMFManchester City29-10419963.018621686.132069108910287.3708186.4192190.500.10.2-0.12171019Matches
324Guido Rodrรญguezar ARGMFWest Ham31-17519940.8283482.4419106161888.9111291.7020.000.00.00.002104Matches
325Robin Roefsnl NEDGKSunderland22-26020036.015322568.0445330502121100.0787998.75312442.700.20.0-0.228100Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
326Morgan Rogerseng ENGFW,MFAston Villa23-07020025.911116268.51706501607481.1375468.5102050.010.90.6+0.1694018Matches
327Merlin Rรถhlde GERMFEverton23-09120020.371070.098394666.73475.00000.10.0-0.112001Matches
328Cristian Romeroar ARGDFTottenham27-16019986.033537689.16560237710411491.220021493.5294367.410.20.2+0.82253028Matches
329Georginio Rutterfr FRAFW,MFBrighton23-16720023.6416167.2786279152171.4202387.04757.111.20.4-0.2583011Matches
330Josรฉ Sรกpt PORGKWolves32-26019934.08913167.9268320981313100.0474897.9286940.600.10.0-0.113100Matches
331Noah Sadikicd CODMFSunderland20-29120045.918021484.128046399310093.0708186.4101855.600.10.3-0.13124215Matches
332Bukayo Sakaeng ENGFWArsenal24-02920012.87710275.51347275424691.3243275.091850.000.20.3-0.250304Matches
333Mohamed Salaheg EGYFWLiverpool33-11119926.013118969.31878547829883.7345364.2112544.020.70.7+1.37212220Matches
334William Salibafr FRADFArsenal24-19420013.526728693.44714137211011199.114115094.0141973.700.10.1-0.11140011Matches
335Robert Sรกnchezes ESPGKChelsea27-32019975.012719066.832312249222395.7686998.6349535.800.10.0-0.124000Matches
336Jadon Sanchoeng ENGFWAston Villa25-19320000.1151883.32207566100.08988.9010.000.00.00.000303Matches
337Ibrahim Sangarรฉci CIVMFNott'ham Forest27-30619973.920623189.234771085929893.99210191.1152171.400.00.10.00251022Matches
338Andrey Santosbr BRAMFChelsea21-15420041.98710384.51173361536088.3273090.01250.000.10.0-0.13131012Matches
339Ismaila Sarrsn SENMF,FWCrystal Palace27-22119983.8527173.2824260283580.0212584.02540.000.30.2-0.3264012Matches
340Sรกviobr BRAMF,FWManchester City21-17720040.9263281.337259182281.877100.011100.000.00.10.000101Matches
341Nicolรฒ Savonait ITADFNott'ham Forest22-19920030.6202676.930558131492.96966.71250.000.00.00.011000Matches
342Kevin Schadede GERFWBrentford23-31120015.4549159.3890244274461.4162564.061154.500.60.4-0.633002Matches
343Fabian Schรคrch SUIDFNewcastle Utd33-28819913.918422581.842011793505590.99910693.4356256.500.10.1-0.12181013Matches
344Alex Scotteng ENGMFBournemouth22-04420035.020325081.2326165010210894.4798988.8153839.500.30.2-0.34133213Matches
345Jenson Seeltnl NEDDFSunderland22-13420031.59611087.31786705384095.0464895.8101662.500.00.00.0060014Matches
346Matz Selsbe BELGKNott'ham Forest33-22019926.011915875.3288718593838100.0575898.3236137.700.00.00.006000Matches
347Antoine Semenyogh GHAFW,MFBournemouth25-27020007.014721967.123437048310380.6517468.9123237.530.50.4+2.510712024Matches
348Marcos Senesiar ARGDFBournemouth28-14719976.934945077.678033495869590.518320091.57613855.120.31.1+1.744911340Matches
349Benjamin ล eลกkosi SVNFWManchester Utd22-12620033.3355267.341275202774.171546.722100.000.10.1-0.133103Matches
350Ryan Sessegnoneng ENGDFFulham25-13920005.317725170.526978739611186.5729377.462524.000.80.6-0.84183018Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
351Luke Shaweng ENGDFManchester Utd30-08419955.730035784.04950171216018287.910111587.8284168.300.10.1-0.11292029Matches
352Bernardo Silvapt PORMFManchester City31-05519944.015518285.22496591818793.1566487.5131968.411.71.1-0.77134215Matches
353Jota Silvapt PORFWNott'ham Forest26-06419990.12366.727511100.01250.00000.00.00.000000Matches
354Xavi Simonsnl NEDFW,MFTottenham22-16620032.0719773.21199279364481.8242982.882138.110.50.4+0.5354213Matches
355Adam Smitheng ENGDFBournemouth34-15819912.3698779.31169441353989.7273090.071643.800.30.1-0.317006Matches
356Emile Smith Roweeng ENGMFFulham25-06820001.5768787.41145170404785.1273090.055100.000.00.10.015208Matches
357Dominic Solankeeng ENGFWTottenham28-02019970.422100.019022100.0000000.00.00.000000Matches
358Julio Solerar ARGFWBournemouth20-23020050.0000000000000.00.00.000000Matches
359Oliver Sonnepe PERDF,FWBurnley24-32820001.2192965.524788141782.43742.91333.300.00.10.011214Matches
360Tomรกลก Souฤekcz CZEMFWest Ham30-21919952.6709176.9945258404588.9212972.43560.000.00.00.005004Matches
361Djed Spenceeng ENGDFTottenham25-05620004.520726179.3355810609410391.39311183.8163447.100.10.2-0.12223124Matches
362Anton Stachde GERMFLeeds United26-32319986.017824173.9266276610112084.2496476.6152951.711.00.60.013184131Matches
363John Stoneseng ENGDFManchester City31-12919942.920122091.433341262899197.810511293.861250.000.00.00.00111014Matches
364Jรธrgen Strand Larsenno NORFWWolves25-24020003.2415870.756387283971.881080.03475.000.00.10.006002Matches
365Pascal Struijknl NEDDFLeeds United26-05419996.031934492.75813216213313598.516016696.4223464.700.10.1-0.12231012Matches
366Crysencio Summervillenl NEDFW,MFWest Ham23-33920012.5667686.8889201434595.6131681.34580.010.20.4+0.822217Matches
367Dominik Szoboszlaihu HUNMF,DFLiverpool24-34420006.035141983.86239147816317593.113114590.3457857.701.00.6-1.08415339Matches
368Chemsdine Talbima MARFWSunderland20-14820055.210613578.51647226657883.3293680.681457.110.20.3+0.841324Matches
369Ao Tanakajp JPNMFLeeds United27-02419981.8627483.8917190364187.8192382.644100.000.10.0-0.127106Matches
370James Tarkowskieng ENGDFEverton32-31919926.020325878.742911550576193.410912785.8335856.900.20.2-0.22202121Matches
371Marcus Taverniereng ENGMFBournemouth26-19619995.516421177.72656623829388.2637485.1112937.900.50.5-0.57186026Matches
372Loum Tchaounafr FRAMFBurnley22-02620032.8426070.0555172273090.0141973.7010.000.10.2-0.113627Matches
373Jackson Tchatchouacm CMRDF,MFWolves24-02020013.010114470.11732424506280.6456173.861637.500.10.1-0.134332Matches
374Mathys Telfr FRAFW,DFTottenham20-16020051.2202580.04161511010100.07977.833100.000.00.00.003113Matches
375Kenny Tetenl NEDDFFulham29-36019955.020725182.53212144211712792.1678281.7173154.800.10.4-0.12123124Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
376Thiagobr BRAFWBrentford24-10020015.5638574.1862185414885.4152657.744100.001.10.2-1.144326Matches
377Malick Thiawde GERDFNewcastle Utd24-05720012.0506182.0975278172181.0293387.93560.000.00.00.002106Matches
378Youri Tielemansbe BELMFAston Villa28-15019973.519523583.03535862879888.8778887.5263868.400.20.4-0.24251021Matches
379Jurriรซn Timbernl NEDDFArsenal24-10920014.820324184.233019729310093.010512186.851435.710.40.5+0.672211135Matches
380Jean-Clair Todibofr FRADFWest Ham25-27819991.4808890.91467498303196.8424397.781172.700.00.00.008007Matches
381Sandro Tonaliit ITAMFNewcastle Utd25-14920005.724730979.94122121113214690.48810782.2214744.711.10.7-0.110355242Matches
382Pau Torreses ESPDFAston Villa28-26119971.813815489.62421771636794.0556091.7172470.800.00.10.00100013Matches
383James Traffordeng ENGGKManchester City22-35920023.08811874.619481257252792.64343100.0194641.300.00.00.000000Matches
384Adama Traorรฉes ESPFWFulham29-25219961.6314568.9460109151883.3131872.21425.000.50.5-0.531432Matches
385Bertrand Traorรฉbf BFAMF,FWSunderland30-02819950.47887.581744100.02366.70000.60.0-0.610000Matches
386Kieran Trippiereng ENGDFNewcastle Utd35-01519903.821628376.3402614848610284.39811585.2285650.000.30.3-0.38183132Matches
387Leandro Trossardbe BELFWArsenal30-30419942.4648971.91347340233271.9293485.3111668.810.20.5+0.834526Matches
388Adrien Truffertfr FRADFBournemouth23-31820017.029436680.34472139917118294.010513577.8133339.410.60.4+0.46319736Matches
389Stefanos Tzimasgr GREFW,MFBrighton19-27120060.31333.37011100.000010.000.00.00.010000Matches
390Christantus Ucheng NGAMFCrystal Palace22-13820030.15955.670113475.02450.0010.000.00.00.001000Matches
391Destiny Udogieit ITADFTottenham22-31020022.414016883.32270859677885.9687393.24850.000.10.2-0.12145016Matches
392Manuel Ugarteuy URUMFManchester Utd24-17620012.610612584.81733455606987.0353892.191369.200.00.00.01160018Matches
393Lesley Ugochukwufr FRAMFBurnley21-19220043.0647783.11029276323591.4232688.57887.500.00.00.015006Matches
394Virgil van Dijknl NEDDFLiverpool34-08819916.044149489.38148341515816396.924125694.1356553.800.00.10.01481036Matches
395Joรซl Veltmannl NEDDFBrighton33-26219923.68010874.11520659303585.7404785.192045.000.00.00.01110014Matches
396Micky van de Vennl NEDDFTottenham24-16820015.934737592.56876211210210498.122323196.5213756.800.10.2-0.12170018Matches
397Bart Verbruggennl NEDGKBrighton23-04720026.016223070.4403925934242100.0767897.44410940.400.10.0-0.115100Matches
398Guglielmo Vicarioit ITAGKTottenham28-36219966.022727482.8529132014949100.0134134100.0449048.900.00.00.005000Matches
399Kyle Walkereng ENGDFBurnley35-12919906.018126668.0334212488910287.3618670.9256041.700.00.20.00131014Matches
400Kyle Walker-Peterseng ENGDFWest Ham28-17419974.217720785.5257982110711394.7556584.671741.200.10.1-0.13143015Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
401Aaron Wan-Bissakacd CODDFWest Ham27-31219972.010212184.31434514656895.6283482.45683.300.10.1-0.125446Matches
402James Ward-Prowseeng ENGMFWest Ham30-33719944.618521884.932141005818496.4718187.7233762.200.90.9-0.912195120Matches
403Ollie Watkinseng ENGFWAston Villa29-27819955.9587973.4681103415475.9111573.311100.000.40.2-0.442006Matches
404Danny Welbeckeng ENGFWBrighton34-31219903.1435775.4514101243080.0141687.5010.000.10.3-0.115001Matches
405Adam Whartoneng ENGMFCrystal Palace21-24020044.012215678.22271806414885.4667291.7122744.401.81.1-1.810183019Matches
406Ben Whiteeng ENGDFArsenal27-36119970.8212777.8353131101190.9101283.31333.300.00.00.015004Matches
407Mats Wieffernl NEDDFBrighton25-32219992.410212581.61699602525791.2414983.76966.710.60.4+0.4193014Matches
408Neco Williamswls WALDFNott'ham Forest24-17420016.030237081.64643124916317891.612514586.293228.100.50.4-0.56284435Matches
409Estรชvรฃo Willianbr BRAFWChelsea18-16320072.8718682.61139104384584.4293193.53650.010.91.0+0.123103Matches
410Joe Willockeng ENGMFNewcastle Utd26-04519991.0152075.0261297977.877100.01250.000.00.00.001001Matches
411Callum Wilsoneng ENGFWWest Ham33-21919921.9132259.1164465771.461060.0010.000.00.00.011001Matches
412Harry Wilsonwls WALFW,MFFulham28-19619974.910614374.11584349596590.8334475.082236.400.10.3-0.1410209Matches
413Ben Winterburneng ENGMFBournemouth21-03020040.1010.000010.0000000.00.00.000000Matches
414Florian Wirtzde GERMF,FWLiverpool22-15420034.918223378.129087649210885.2677984.8162857.100.70.8-0.710195028Matches
415Nick Woltemadede GERFWNewcastle Utd23-23220022.4293974.431460222781.561060.00000.00.00.012002Matches
416Chris Woodnz NZLFWNott'ham Forest33-30119915.1487068.667096283580.0172568.0020.000.20.1-0.234106Matches
417Joe Worralleng ENGDFBurnley28-26719970.21425.0260010.01250.00000.00.00.000000Matches
418Granit Xhakach SUIMFSunderland33-00719926.027534479.95316148410812387.812014284.5426564.630.90.6+2.18344132Matches
419Yehor Yarmoliukua UKRMFBrentford21-21720045.916720282.72882685839191.2667588.0132552.010.20.2+0.84153117Matches
420Ryan Yateseng ENGMFNott'ham Forest27-31719970.3202290.9320451212100.055100.03475.000.00.00.001102Matches
421Leny Yorofr FRADFManchester Utd19-32520054.117419887.931461025767996.2819090.0152560.000.00.00.01131116Matches
422Oleksandr Zinchenkoua UKRDFNott'ham Forest28-29319962.016517892.72726562788097.5778293.9101376.900.00.20.02194018Matches
423Joshua Zirkzeenl NEDFW,MFManchester Utd24-13520010.9263281.330173222684.64666.70000.00.00.012003Matches
424Martรญn Zubimendies ESPMFArsenal26-24419995.929333487.74792127413614991.313815489.6162369.600.50.6-0.54223027Matches
425Martin ร˜degaardno NORMFArsenal26-29119981.99311978.21697581414787.2394881.3121770.610.40.6+0.62186021Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
+ + +
+
+ + + + + + + + +
+
+

About FBref.com

+ + + + + +

+ FBref.com launched (June 13, 2018) with domestic league coverage for England, France, Germany, Italy, Spain, and United States. Since then we have been steadily expanding our coverage to include domestic leagues from over 40 countries as well as domestic cup, super cup and youth leagues from top European countries. We have also added coverage for major international cups such as the UEFA Champions League and Copa Libertadores. +

+ + +

+ FBref is the most complete sources for women's football data on the internet. This includes the entire history of the FIFA Women's World Cup as well as recent domestic league seasons from nine countries, including advanced stats like xG for most of those nine. +

+ +

+ In collaboration with Opta, we are including advanced analytical data such as xG, xA, progressive passing, duels and more for over twenty competitions. For more information on the expected goals model and which competitions have advanced data, see our xG explainer. +

+ + + +
+

+ Note that player records are likely not complete for their careers. Players may come from or move to leagues we don't currently cover. This issue will go down over time, as we add new leagues and seasons. We will never in the future have less data than we do today. +

+ +

You can sign up to receive an e-mail when new countries and features launch.

+ +

For more information, see our Launch Blog Post, the overall leagues/competition page with details on leagues and seasons we include, or our About Page. Let us know if you find an issue or have a suggestion.

+ +

FBref is one of seven Sports-Reference.com sites.

+
+
+ + + + + + + +
+ + + + +
+ + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/debug/debug_schedule.html b/debug/debug_schedule.html new file mode 100644 index 0000000000000000000000000000000000000000..15f416da24872bbbbba51114eea93904bcf77387 --- /dev/null +++ b/debug/debug_schedule.html @@ -0,0 +1,2 @@ +Just a moment...

fbref.com

Verify you are human by completing the action below.

fbref.com needs to review the security of your connection before proceeding.
+ \ No newline at end of file diff --git a/debug/debug_team_stats.html b/debug/debug_team_stats.html new file mode 100644 index 0000000000000000000000000000000000000000..a865eb6ca82a349c6c0ca8bcce46b61d6111db66 --- /dev/null +++ b/debug/debug_team_stats.html @@ -0,0 +1,4834 @@ + + + + + + + + + + + + + Premier League Passing Stats | FBref.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + + + +
+

+ + 2025-2026 Premier League Passing Stats + +

+
+ + + + + + +
+ + + + +

Governing Country: England eng

Level: 1st Tier (See League Structure)

Gender: Male

Most Goals: Erling Haaland (Manchester City) - 8

Most Assists: Jack Grealish (Everton), Mohammed Kudus (Tottenham Hotspur) - 4

Most Clean Sheets: David Raya (Arsenal), Nick Pope (Newcastle United) - 4

Big 5: View Big 5 European Leagues together

+ + + + + + +
+
+ +
+ + +
+ +
+ + + +
+ + +
+
+
+ + + + + + +
+ + +
+ + +
+ + + + + + + + + + + +
+ + +
+
+
+ + + +
+ + + + + +
+ + + + +
+

Squad Passing 2025-2026 Premier League

+
+
    +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
Squad Passing 2025-2026 Premier League Table
Total Short Medium Long Expected
Squad # Pl 90s Cmp Att Cmp% TotDist PrgDist Cmp Att Cmp% Cmp Att Cmp% Cmp Att Cmp% Ast xAG xA A-xAG KP 1/3 PPA CrsPA PrgP
Arsenal217.03071369983.053658167311379152690.41396156689.124245353.496.58.2+2.567205707293
Aston Villa226.02483301482.443255141741133123891.51078121988.422041053.743.74.6+0.3422225111250
Bournemouth237.02577331977.646023161141128125889.71123131185.726856747.396.15.7+2.9642445716267
Brentford206.01612216074.6304381208566677486.070585182.819942347.055.03.90.035119249140
Brighton206.01943243679.8348261280185395589.385698187.318235950.766.74.5-0.7461643611178
Burnley206.01448201371.9256021009972282287.852266478.616039240.853.73.2+1.3391182812138
Chelsea257.03419403784.758121175471606174392.11514166590.924748451.097.66.6+1.463266527278
Crystal Palace206.01771230876.7330091238472682887.781594186.619641846.937.85.6-4.8491224213176
Everton196.01934253076.4361871331282794987.1854101784.021844349.276.75.4+0.3561594712210
Fulham217.02730338480.748283167521191133789.11226138988.325749052.455.96.1-0.9542116818250
Leeds United217.02443308079.343913155151059120388.01063120588.224249049.424.83.9-2.8561863210208
Liverpool227.03332400183.357028190321558170791.31455164188.725448652.398.86.2+0.2772987215337
Manchester City226.02756324584.943615139321449155992.91071119689.515931949.8109.77.9+0.3541934710236
Manchester Utd207.02732345079.247731165101312149188.01052124484.628452753.948.17.2-4.1802186525290
Newcastle Utd216.02027267375.83662713523910107284.9878103784.720845845.434.54.1-1.5511684013237
Nott'ham Forest236.02837340383.448210155871332144192.41256141588.819940948.735.34.7-2.3572825819300
Sunderland237.02309298077.541645144881018115088.5990114886.224752047.554.53.3+0.5461843614208
Tottenham197.02834349281.252316177151117125089.41416157889.726153548.8105.84.9+4.2552445319271
West Ham247.02470306880.542274145671162128590.41005113888.321946247.445.04.8-1.0561844614222
Wolves236.02163276478.3418081422282797684.71078123787.122946649.133.63.4-0.6441705426179
+ + + +
+
+ +
Squad Passing 2025-2026 Premier League Table
Total Short Medium Long Expected
Squad # Pl 90s Cmp Att Cmp% TotDist PrgDist Cmp Att Cmp% Cmp Att Cmp% Cmp Att Cmp% Ast xAG xA A-xAG KP 1/3 PPA CrsPA PrgP
vs Arsenal217.02099269577.93705313303988112288.181095684.722445749.023.82.8-1.849151346172
vs Aston Villa226.01735222677.9317851214878488189.072183586.319339049.537.14.9-4.1571523513173
vs Bournemouth237.02443318176.844962178111073122387.71056126283.726553349.765.44.1+0.6511984813230
vs Brentford206.02600317282.044417149311262139090.81060122286.722842254.065.65.1+0.4592205218242
vs Brighton206.02422297881.342597153341132126889.31010113888.823344053.055.75.2-0.751187407212
vs Burnley206.03317391784.754008162271699184392.21321148988.723041156.068.57.8-2.5863027328316
vs Chelsea257.02176275579.03921114569928105687.9983114086.221442051.098.65.7+0.4541734918218
vs Crystal Palace206.02884343983.952548158631159126291.81422155891.325447054.025.05.5-3.0522575013289
vs Everton196.02461304580.841801135961163130389.31031116688.419140647.044.94.3-0.9521824916225
vs Fulham217.02708331981.648519169971169129190.51219137788.525248651.975.15.2+1.948231445261
vs Leeds United217.03082374182.456964181681217137388.61533172189.129852556.875.04.7+2.0532605423286
vs Liverpool227.01770249271.0324301202779793685.170886182.221554139.776.44.8+0.6511324916186
vs Manchester City226.02124266179.83948813217930105188.5936107187.422643152.444.24.2-0.2471724411181
vs Manchester Utd207.02381307177.540693150431137128888.3955112584.920946045.487.46.7+0.644165469214
vs Newcastle Utd216.02130274977.53761812700949107488.4928107686.219446541.753.83.6+1.243170479217
vs Nott'ham Forest236.01953250378.0343941234587398288.982997585.019540148.676.64.8+0.4521374113185
vs Sunderland237.03046372881.752597169421372150491.21371154488.824051346.855.45.6-0.4592546724314
vs Tottenham197.02195281078.13978414587921103389.21014116687.020746944.126.35.3-4.3661744415214
vs West Ham247.02995364882.151439168101374151890.51355152588.921845647.8107.77.5+2.3652276211285
vs Wolves236.02370292681.042261144721048116689.91091123688.320541549.4107.46.3+2.6522135013248
+ + + +
+ +
+ + +
+ +
+

Player Passing 2025-2026 Premier League

+
+
    +
+
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Player Passing 2025-2026 Premier League Table
TotalShortMediumLongExpected
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
1Brenden Aaronsonus USAFWLeeds United24-34820004.48610978.91167261485587.3324080.01425.000.70.8-0.7583013Matches
2Joshua Acheampongeng ENGDFChelsea19-15320062.213515288.82261441586392.1757994.92825.000.00.00.000004Matches
3Tyler Adamsus USAMFBournemouth26-23319996.728634383.44935126712414287.314115690.4192965.500.00.20.01290031Matches
4Tosin Adarabioyoeng ENGDFChelsea28-01119973.931434990.05739199010210993.619420694.2162857.100.00.10.00171015Matches
5Simon Adingraci CIVFWSunderland23-27720023.0486969.680394222878.6202290.93742.900.60.2-0.650422Matches
6Amine Adlima MARFW,MFBournemouth25-14820001.7547473.0748176343889.5152075.03650.000.20.1-0.241105Matches
7Emmanuel Agbadouci CIVDFWolves28-11019975.528034980.262962535728090.016818789.8387749.400.10.3-0.12355225Matches
8Nayef Aguerdma MARDFWest Ham29-18919962.010611989.11731663525398.1474995.951338.500.00.00.0012009Matches
9Ola Ainang NGADFNott'ham Forest28-36219963.012515779.62161962585998.3567080.092142.900.10.3-0.12132111Matches
10Rayan Aรฏt-Nouridz ALGDFManchester City24-12120012.210512186.81529385647190.1394097.52540.000.20.5-0.226007Matches
11Kristoffer Ajerno NORDFBrentford27-17119980.3232495.842816377100.0151693.811100.010.70.4+0.312006Matches
12Nathan Akรฉnl NEDDFManchester City30-22919951.3526185.28632972424100.0222975.95771.400.10.2-0.112326Matches
13Carlos Alcarazar ARGFW,MFEverton22-30920021.3283677.850926111478.6141782.433100.000.00.00.001001Matches
14Omar Alderetepy PARDFSunderland28-28319966.428533784.658832426869689.616017293.0386657.610.00.3+1.01242228Matches
15Alissonbr BRAGKLiverpool33-00319926.021125483.1504432815656100.010610799.1499153.800.00.00.003004Matches
16Ethan Ampaduwls WALMFLeeds United25-02120004.925330682.7481312749110487.512714488.2304961.200.00.10.01262216Matches
17Joachim Andersendk DENDFFulham29-12719967.037744784.38057292711212093.319521192.46410561.000.70.7-0.72454028Matches
18Elliot Andersoneng ENGMFNott'ham Forest22-33320026.045353085.57707210620421893.621523193.1296246.811.30.8-0.38609358Matches
19Andrรฉbr BRAMFWolves24-08120013.915917491.42721744737794.8747894.9111668.800.00.10.00170015Matches
20Jaidon Anthonyeng ENGMF,FWBurnley25-30819995.910315964.81545393607975.9344673.951827.810.30.6+0.7786311Matches
21Alphonse Areolafr FRAGKWest Ham32-22019933.06810664.2170310911717100.03737100.0145226.900.00.00.004000Matches
22Jhon Ariasco COLFWWolves28-01419973.010112382.11672315525889.7445580.051050.000.30.2-0.367419Matches
23Harrison Armstrongeng ENGFWEverton18-25920070.0000000000000.00.00.000000Matches
24Tolu Arokodareng NGAFWWolves24-31620001.8112250.01271181266.72728.60000.10.0-0.111000Matches
25Yasin Ayarise SWEMFBrighton21-36420035.016119881.32691744758489.3708285.4112250.000.10.4-0.14114119Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
26Benoรฎt Badiashilefr FRADFChelsea24-19320010.7535793.0989427222395.72424100.071070.000.00.00.006104Matches
27Dilane Bakwafr FRAFWNott'ham Forest23-04020021.8608174.1902239364090.0182378.341233.300.30.2-0.343117Matches
28Carlos Balebacm CMRMFBrighton21-27520043.48310777.61573296374288.1293876.3152171.400.00.00.028106Matches
29Daniel Ballardnir NIRDFSunderland26-01319992.5617185.91208502192095.0364090.051050.000.40.2-0.425005Matches
30Harvey Barneseng ENGFW,MFNewcastle Utd27-30019973.3689273.91038362434987.8212875.04944.400.70.8-0.7568316Matches
31Thierno Barryfr FRAFWEverton22-34920021.8233369.727227151883.361154.5010.000.00.00.000002Matches
32Calvin Basseyng NGADFFulham25-27819997.037742888.16800236314716390.219720894.7294761.700.00.10.01322019Matches
33Altay Bayฤฑndฤฑrtr TURGKManchester Utd27-17419986.012219263.5343826463030100.0525398.14010936.700.20.0-0.218100Matches
34Jean-Ricner Bellegardeht HAIMF,FWWolves27-10019982.7749776.31408355273187.1374582.291752.900.30.2-0.3326017Matches
35Rodrigo Bentancuruy URUMFTottenham28-10219974.620423586.83483765869887.810611195.591656.300.20.4-0.23193021Matches
36Sander Bergeno NORMFFulham27-23319986.626630587.24420114412313789.812813793.4131681.300.30.3-0.32204129Matches
37Lucas Bergvallse SWEMFTottenham19-24520064.110114370.61736489475683.9415278.8102835.710.30.2+0.72134117Matches
38Betogw GNBFWEverton27-24719984.2264854.230439183060.071546.70000.20.1-0.213002Matches
39Marco Bizotnl NEDGKAston Villa34-20919912.0466768.712728361010100.02222100.0143540.000.00.00.000001Matches
40Oscar Bobbno NORFW,MFManchester City22-08520033.210311589.61438373677194.4283190.34757.110.40.6+0.6549210Matches
41Lamare Bogardenl NEDMFAston Villa21-27320042.1929992.91300288575996.6303293.83475.000.00.10.0110104Matches
42Jayden Bogleeng ENGDFLeeds United25-07020006.818123876.129689798810286.3728881.8132748.100.10.1-0.12124218Matches
43Sven Botmannl NEDDFNewcastle Utd25-26620002.811814084.32250905374386.0707593.3101952.600.00.00.010005Matches
44Jarrod Boweneng ENGFW,MFWest Ham28-28919967.011916373.01758366738486.9354283.361442.900.90.5-0.9577215Matches
45Conor Bradleynir NIRDFLiverpool22-08820032.615718286.32395550909693.8576489.181553.300.00.10.00143112Matches
46Brian Brobbeynl NEDFWSunderland23-24620020.74757.145123475.011100.0010.000.00.00.001002Matches
47Armando Brojaal ALBFWBurnley24-02520010.133100.042022100.011100.00000.00.00.000000Matches
48David Brookswls WALFW,MFBournemouth28-08919975.014521069.02202724758786.2527470.3102934.511.41.3-0.47156416Matches
49Jacob Bruun Larsendk DENFW,MFBurnley27-01619982.3435479.6548115323688.9101376.9030.010.60.3+0.421114Matches
50Emi Buendรญaar ARGMF,FWAston Villa28-28419963.19111479.81325373566290.3242982.861154.510.40.5+0.66116019Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
51Hugo Buenoes ESPDF,MFWolves23-01720024.016021474.82872701678182.7768985.4163545.700.50.5-0.588101010Matches
52Santiago Buenouy URUDFWolves26-33019982.49411085.51847616232882.1626693.971163.600.00.10.023119Matches
53Facundo Buonanottear ARGMFChelsea20-28620040.5202290.925129151693.83475.011100.000.30.1-0.322000Matches
54Dan Burneng ENGDFNewcastle Utd33-14919926.018326269.835301604648377.19912678.6174141.500.00.10.0192117Matches
55Sam Byrameng ENGDFLeeds United32-01919930.111100.012011100.0000000.00.00.000000Matches
56Moisรฉs Caicedoec ECUMFChelsea23-33720017.040043991.16314183621522396.416617793.8162564.000.10.2-0.11392036Matches
57Tom Cairneysct SCOMFFulham34-25819911.3879492.61549469353794.6424691.38988.900.00.10.01102012Matches
58Riccardo Calafioriit ITADFArsenal23-13920025.824130180.1388390411913588.110212184.3132846.420.50.1+1.54134124Matches
59Dominic Calvert-Lewineng ENGFWLeeds United28-20319974.0446369.8546120283971.8121580.01250.000.40.4-0.465306Matches
60Fabio Carvalhopt PORFW,MFBrentford23-03620021.1192576.028999111478.63475.04666.700.00.00.000102Matches
61Casemirobr BRAMFManchester Utd33-22419923.617321281.631731002778590.6687788.3253571.400.20.1-0.22130022Matches
62Matty Cashpl POLDFAston Villa28-05919975.826733779.24550130311912496.011814084.3225540.000.40.5-0.44303124Matches
63Timothy Castagnebe BELDF,FWFulham29-30419953.410913779.61656481596689.4415180.461346.200.20.1-0.232528Matches
64Trevoh Chalobaheng ENGDFChelsea26-09219995.337741990.06942169713914893.920821895.4284463.600.00.60.01354125Matches
65Rayan Cherkifr FRAMF,FWManchester City22-04920030.8536482.8736236313491.2171989.52633.300.50.5-0.517306Matches
66Federico Chiesait ITAFW,MFLiverpool27-34519970.8131872.21554391090.04666.7010.000.00.00.002102Matches
67Ryan Christiesct SCOMFBournemouth30-22519951.7729675.01214280384192.7283873.75955.600.00.10.01171012Matches
68Samuel Chukwuezeng NGAFW,MFFulham26-13619990.4182572.027054101190.96875.01333.310.50.4+0.520101Matches
69Nathaniel Clyneeng ENGDFCrystal Palace34-18319910.133100.028733100.0000000.00.00.000000Matches
70Sรฉamus Colemanie IRLFWEverton36-35919880.0000000000000.00.00.000000Matches
71Nathan Collinsie IRLDFBrentford24-15820016.019423184.041521641445284.612613494.0244355.800.50.4-0.5292014Matches
72Lewis Cookeng ENGMFBournemouth28-24419970.01250.013011100.000010.000.00.00.000000Matches
73Diego Coppolait ITADFBrighton21-28120030.233100.0713511100.011100.011100.000.00.00.001001Matches
74Marc Cucurellaes ESPDFChelsea27-07519986.030835387.35112157314615991.813614792.5183256.321.31.1+0.710274028Matches
75Jorge Cuencaes ESPDFFulham25-32219991.0515887.98683572020100.02828100.02728.600.00.00.001002Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
76Josh Cullenie IRLMFBurnley29-18119965.621726681.63706121911412491.9739378.5263868.410.80.6+0.27323031Matches
77Matheus Cunhabr BRAMF,FWManchester Utd26-13119993.9759182.4987289414591.1202774.15771.400.20.5-0.2465314Matches
78Diogo Dalotpt PORDFManchester Utd26-20119993.713918575.12468772657487.8577972.2152755.610.90.8+0.1676410Matches
79Mikkel Damsgaarddk DENMFBrentford25-09420003.810114967.81786747455778.9404785.1123633.300.40.8-0.45125318Matches
80Kevin Dansoat AUTDFTottenham27-01619980.2242596.054315133100.0151693.866100.000.00.00.003002Matches
81Karl Darlowwls WALGKLeeds United34-36219904.011317863.5340425111515100.05959100.03910337.900.00.00.003001Matches
82Maxim De Cuyperbe BELDFBrighton24-28720003.111614381.12016656505787.7596788.161540.000.10.5-0.1273111Matches
83Liam Delapeng ENGFWChelsea22-23920031.091560.0137384757.15771.40000.00.00.001103Matches
84Sepp van den Bergnl NEDDFBrentford23-28920016.021123888.737731234748191.411812892.2152171.400.00.00.0090012Matches
85Justin Devennynir NIRMF,FWCrystal Palace21-35920031.6385371.7619134202483.3141877.83742.900.10.2-0.125105Matches
86Kiernan Dewsbury-Halleng ENGMFEverton27-02919986.018123278.029727779010486.5728287.8143638.911.81.4-0.8132316234Matches
87Bafodรฉ Diakitรฉfr FRADFBournemouth24-27220016.027931488.955481829768391.617518694.1263966.700.10.1-0.12200017Matches
88Amad Dialloci CIVDF,MFManchester Utd23-08620025.018520590.2253752212613196.2495884.56966.700.80.8-0.8868119Matches
89Habib Diarrasn SENMFSunderland21-27520043.89312176.91328214566388.9313881.64757.100.00.10.01112013Matches
90Rรบben Diaspt PORDFManchester City28-14419975.128431091.64794172812413293.915015994.381650.000.00.10.00130014Matches
91Tyler Diblingeng ENGFWEverton19-20720060.277100.0115333100.044100.00000.00.00.001000Matches
92Lucas Dignefr FRADFAston Villa32-07719934.415221570.72432869829487.2587478.482433.310.30.5+0.7156210Matches
93Issa Diopfr FRADFFulham28-26919970.9243568.641422691275.01414100.01616.700.00.00.005005Matches
94El Hadji Malick Dioufsn SENDFWest Ham20-28120047.022330672.93553139412915185.4729476.6184440.930.70.7+2.37168321Matches
95Matt Dohertyie IRLDFWolves33-26219922.58710582.91800629333691.7425084.0121770.600.00.00.005005Matches
96Jeremy Dokube BELMF,FWManchester City23-13120024.111113383.51532355738190.1333789.23475.032.11.5+0.912610213Matches
97Nicolรกs Domรญnguezar ARGMFNott'ham Forest27-09919980.5373897.47152311313100.0181994.766100.000.00.00.0012005Matches
98Gianluigi Donnarummait ITAGKManchester City26-22219993.0538165.4143310371010100.02222100.0204742.600.00.00.001000Matches
99Patrick Dorgudk DENDFManchester Utd20-34420045.214720472.121214598811675.9476572.361060.010.80.6+0.265637Matches
100Max Dowmaneng ENGFWArsenal15-27820090.344100.066022100.022100.00000.00.00.000000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
101Martin Dรบbravkask SVKGKBurnley36-26319896.011022249.5358626972222100.0454697.84315328.100.00.00.005000Matches
102Lewis Dunkeng ENGDFBrighton33-31819916.033937191.45953167114014397.917818994.2152951.700.00.00.01160016Matches
103Odsonne ร‰douardfr FRAFWCrystal Palace27-26219980.0010.00000000000.00.00.000000Matches
104Marcus Edwardseng ENGFWBurnley26-30619980.1000000000000.00.00.000000Matches
105Hjalmar Ekdalse SWEDFBurnley26-34919985.913816384.72390825646795.5637188.7111957.900.10.2-0.125006Matches
106Hugo Ekitikefr FRAFWLiverpool23-10720024.2558267.1724192344673.9172373.922100.010.30.2+0.742409Matches
107Anthony Elangase SWEFW,MFNewcastle Utd23-16120022.9518758.6685165284070.0162759.331225.000.30.2-0.333324Matches
108Harvey Elliotteng ENGMF,FWLiverpool22-18420030.04850.05273650.011100.0010.000.00.00.000000Matches
109Harvey Elliotteng ENGMF,FWAston Villa22-18420031.1536285.5688107394195.1111384.61520.000.00.10.006115Matches
110Wataru Endojp JPNMF,DFLiverpool32-23819930.4192286.428379141687.53475.022100.010.10.0+0.913001Matches
111Romain Esseeng ENGFWCrystal Palace20-14520050.35771.4722922100.02366.70000.00.00.001102Matches
112Maxime Estรจvefr FRADFBurnley23-13220025.915818286.827761107687491.9727991.1122157.100.00.00.01150012Matches
113Evanilsonbr BRAFWBournemouth25-36419996.88910684.0967150576291.9212487.51250.000.80.3-0.893307Matches
114Eberechi Ezeeng ENGMF,FWArsenal27-09819983.510512584.01619374576193.4404785.16966.720.70.8+1.3355018Matches
115Eberechi Ezeeng ENGMFCrystal Palace27-09819980.9162564.0241347977.871070.01520.000.00.00.002001Matches
116Bruno Fernandespt PORMFManchester Utd31-02719947.035345877.16422212216818590.812414983.2529554.701.71.3-1.7194412161Matches
117Mateus Fernandespt PORMF,FWWest Ham21-08720044.415116790.42506511708087.5555796.5172085.000.50.3-0.54112012Matches
118Enzo Fernรกndezar ARGMFChelsea24-26120016.933240482.25263124418220091.011914085.0265151.010.80.5+0.283910144Matches
119Zian Flemmingnl NEDFWBurnley27-06519980.32540.023142366.700010.000.10.0-0.110000Matches
120Phil Fodeneng ENGMFManchester City25-13020002.912716676.51855516889592.6253278.1112839.300.10.2-0.15113014Matches
121Wesley Fofanafr FRADFChelsea24-29220001.49610591.41478359485096.0454991.82450.000.00.00.0010004Matches
122Lyle Fosterza RSAFWBurnley25-03220005.5589461.7809123344477.3122157.15862.510.60.1+0.456005Matches
123Jeremie Frimpongnl NEDDFLiverpool24-29920000.9314175.6445115162080.0131872.20000.00.10.002113Matches
124Niclas Fรผllkrugde GERFWWest Ham32-23819934.2538760.9654107395867.281361.52366.700.60.2-0.662305Matches
125Cody Gakponl NEDFWLiverpool26-15119996.214620272.320385679110388.3446765.771838.922.21.3-0.214811124Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
126Idrissa Gana Gueyesn SENMFEverton36-00919895.823527386.1370191911612890.610111389.4132065.000.10.3-0.12194130Matches
127Ben Gannon-Doaksct SCOFWBournemouth19-32820050.3111384.6134799100.02366.70010.60.1+0.410000Matches
128Alejandro Garnachoar ARGFWChelsea21-09620041.0222878.6290271414100.061060.01333.300.10.1-0.110001Matches
129James Garnereng ENGDF,MFEverton24-20620016.026732881.44657156312413790.511614082.9223956.410.70.6+0.37297234Matches
130Lutsharel Geertruidanl NEDDF,MFSunderland25-07920000.7152462.52397971070.05683.32450.000.00.00.000001Matches
131Tyrique Georgeeng ENGFWChelsea19-24320061.5192286.43268391090.08988.922100.000.00.10.003214Matches
132Morgan Gibbs-Whiteeng ENGMFNott'ham Forest25-25120005.122227680.4335578013014887.8779382.8112250.010.40.6+0.672911341Matches
133Jamie Gittenseng ENGFWChelsea21-05820041.8384388.4577632424100.0121392.322100.000.10.1-0.130102Matches
134Wilfried Gnontoit ITAFWLeeds United21-33420032.1557177.569380404490.981457.13475.000.10.1-0.111104Matches
135Joรฃo Gomesbr BRAMFWolves24-23520015.725128887.2422295412313491.89310687.7253278.100.10.3-0.13279333Matches
136Rodrigo Gomespt PORDF,MFWolves22-09020031.8405276.970778222781.5152268.233100.000.10.1-0.111112Matches
137Toti Gomespt PORDFWolves26-26219994.325029584.745901472899890.814015292.1174042.500.00.00.02190010Matches
138Diego Gรณmezpy PARMFBrighton22-19220032.6536877.9865205283190.3182185.75955.600.40.1-0.4210217Matches
139Joe Gomezeng ENGDFLiverpool28-13519970.3141973.7231967887.57887.5030.000.00.00.001001Matches
140Nicolรกs Gonzรกlezes ESPMFManchester City23-27520023.221223291.4301985012012596.0808890.95771.400.30.3-0.32250022Matches
141Anthony Gordoneng ENGFWNewcastle Utd24-22320012.2335066.049590253180.65955.63837.500.10.1-0.133005Matches
142Ryan Gravenberchnl NEDMF,DFLiverpool23-14220026.034339087.95677133815717390.816617396.0143145.210.60.4+0.46343033Matches
143Archie Grayeng ENGMFTottenham19-20720060.8404393.07191171515100.0212487.522100.000.00.00.0110107Matches
144Jack Grealisheng ENGFWEverton30-02519955.216219881.823395619610789.7536186.97977.842.41.8+1.617117224Matches
145Brajan Grudade GERMFBrighton21-12720041.7314077.5429142182185.7111478.6020.010.40.3+0.631103Matches
146Ilia Gruevbg BULMFLeeds United25-15220002.2919892.91377270535596.4283287.577100.000.00.00.009006Matches
147Gabriel Gudmundssonse SWEDFLeeds United26-15919996.723830977.03560152613915888.0768985.4133636.100.30.4-0.34165223Matches
148Marc Guรฉhieng ENGDFCrystal Palace25-08420006.024828985.845441624859490.413614891.9223857.910.50.1+0.53210023Matches
149Evann Guessandci CIVFWAston Villa24-09620012.9446172.1530128343987.291464.311100.000.00.10.002407Matches
150Luis Guilhermebr BRAFW,MFWest Ham19-23820060.61010100.01355266100.033100.00000.00.00.000000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
151Bruno Guimarรฃesbr BRAMFNewcastle Utd27-32319975.017220982.32929836839587.4698086.3152365.200.20.5-0.25155028Matches
152Marc Guiues ESPFWSunderland19-27420060.23560.030733100.0010.00000.00.00.000101Matches
153Marc Guiues ESPFWChelsea19-27420060.2010.0000000010.000.00.00.000000Matches
154Malo Gustofr FRADF,MFChelsea22-13920033.518521785.330646529410490.4758588.2131776.510.10.1+0.92152018Matches
155Joลกko Gvardiolhr CRODFManchester City23-25520022.814616787.42648952505394.3869590.581361.500.10.2-0.11212117Matches
156Viktor Gyรถkeresse SWEFWArsenal27-12319986.4417455.4498108294564.471258.32450.000.30.5-0.351306Matches
157Erling Haalandno NORFWManchester City25-07620005.6456767.2441142253669.491560.0010.011.10.7-0.144105Matches
158Lewis Halleng ENGDFNewcastle Utd21-02720041.5527866.7954377232785.2243177.451827.800.00.00.005109Matches
159Jack Harrisoneng ENGFWLeeds United28-31919961.5396163.9686162232882.1111861.151338.500.00.00.004103Matches
160Quilindschy Hartmannl NEDDFBurnley23-32520016.012719764.52122933657487.8517964.693129.010.30.5+0.7696414Matches
161Jorrel Hatonl NEDDFChelsea19-21220061.99711088.21439386495687.5454697.82633.300.00.00.005004Matches
162Kai Havertzde GERFWArsenal26-11619990.35955.679334757.11250.00000.00.00.000002Matches
163Ayden Heaveneng ENGDFManchester Utd19-01320060.0000000000000.00.00.000000Matches
164Jan Paul van Heckenl NEDDFBrighton25-11920006.030735586.56289278210711692.215717291.3406066.700.00.20.00391034Matches
165Hwang Hee-chankr KORFWWolves29-25219962.4285253.8496117132454.2132259.12540.000.00.00.012004Matches
166Dean Hendersoneng ENGGKCrystal Palace28-20719976.014722665.0421829144545100.0515298.15112939.500.00.00.014100Matches
167Jordan Hendersoneng ENGMFBrentford35-11019904.515220076.02905885657290.3617878.2193554.320.50.3+1.53164220Matches
168Rico Henryeng ENGDFBrentford28-08919971.9366952.2569167182572.0173253.11714.300.10.1-0.111102Matches
169Mads Hermansendk DENGKWest Ham25-08620004.010415766.2271918912222100.05757100.0257832.100.00.00.006000Matches
170Aaron Hickeysct SCODFBrentford23-11720021.1374484.1642244192286.4111478.66785.700.00.00.004103Matches
171James Hilleng ENGDFBournemouth23-26820022.99413967.61674740454893.8395373.692931.000.00.20.00153017Matches
172Jack Hinshelwoodeng ENGMFBrighton20-17720051.2465485.2725139242788.9181994.72450.000.00.00.002102Matches
173Ki-Jana Hoevernl NEDMFWolves23-26020021.2294761.7624184101758.8141687.551145.500.20.2-0.222322Matches
174Callum Hudson-Odoieng ENGFWNott'ham Forest24-33220004.113917878.11915334828992.1415278.871936.800.70.4-0.7937216Matches
175Will Hugheseng ENGMFCrystal Palace30-17119954.612315479.92018702667785.7435282.7112055.000.50.6-0.5596125Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
176Trai Humenir NIRDFSunderland23-20120026.921631668.43264128912514785.07410471.2124427.300.20.2-0.25224122Matches
177Bashir Humphreyseng ENGDFBurnley22-20420030.022100.022322100.0000000.00.00.000000Matches
178Omari Hutchinsoneng ENGFW,MFNott'ham Forest21-34020030.3172665.42879581080.06966.72540.000.60.3-0.641432Matches
179Igorbr BRADFWest Ham27-24019980.0000000000000.00.00.000000Matches
180Tim Iroegbunameng ENGMFEverton22-09720032.4466471.9783176212972.4232785.22366.700.00.00.004006Matches
181Andy Irvingsct SCOMFWest Ham25-14520000.3172373.9345565683.3111384.61425.000.00.10.013004Matches
182Alexander Isakse SWEFWLiverpool26-01419992.0253865.835065141782.4101471.4020.010.50.0+0.511105Matches
183Wilson Isidorfr FRAFWSunderland25-03920004.2202871.424282141593.33650.01333.300.00.00.004003Matches
184Alex Iwobing NGAFW,MFFulham29-15519966.021427577.8378011509111182.09611881.4223366.721.41.1+0.692122638Matches
185Daniel Jameswls WALFWLeeds United27-32919972.4235046.0341148122352.261250.03742.900.30.2-0.323215Matches
186Reece Jameseng ENGDFChelsea25-30119994.730035684.35271144514215094.712614288.7305257.711.20.9-0.29254119Matches
187Vitaly Janeltde GERMFBrentford27-14819980.3141877.8263826785.76875.022100.000.00.00.014002Matches
188Mathias Jensendk DENMFBrentford29-27719961.7456173.8945402222588.0142070.091560.000.20.1-0.23102011Matches
189Igor Jesusbr BRAFW,MFNott'ham Forest24-22220010.9182185.722011131586.755100.00000.00.00.010101Matches
190รlex Jimรฉnezes ESPDFBournemouth20-15020051.78210280.41204332465190.2283384.84850.000.10.0-0.115008Matches
191Raรบl Jimรฉnezmx MEXFWFulham34-15319911.6192965.523430141687.53650.01250.000.00.00.001003Matches
192Joรฃo Pedrobr BRAFW,MFChelsea24-00920016.513916982.217822919810593.3273773.06966.731.40.2+1.6796019Matches
193Joelintonbr BRAMFNewcastle Utd29-05219963.4759975.8983330496081.7212680.82450.000.70.3-0.72120014Matches
194Brennan Johnsonwls WALFWTottenham24-13520013.0487266.7730179303488.2152462.531225.000.20.1-0.235216Matches
195Sam Johnstoneeng ENGGKWolves32-19419932.0499054.4153411351010100.02222100.0175829.300.00.00.003000Matches
196Curtis Joneseng ENGMFLiverpool24-24820012.514716191.32543811707395.9676997.191560.000.40.4-0.43218128Matches
197Eli Junior Kroupifr FRAFW,MFBournemouth19-10420060.34944.46702540.01250.011100.000.00.00.000000Matches
198Hamed Junior Traorรจci CIVMFBournemouth25-23120000.22366.7231811100.01250.00010.20.0+0.810000Matches
199James Justineng ENGDF,FWLeeds United27-22419980.23650.010143010.01250.02366.700.00.00.001000Matches
200Filip Jรธrgensendk DENGKChelsea23-17220020.9273577.1616444101190.91111100.061346.200.00.00.000000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
201Ferdi Kadioglutr TURDF,FWBrighton25-36319993.29511681.91515598444989.8445284.64580.000.40.2-0.42104311Matches
202Sasa Kalajdzicat AUTFWWolves28-09019970.24666.741044100.0000000.00.00.000000Matches
203Arnaud Kalimuendofr FRAFWNott'ham Forest23-25820020.8232979.332832111384.6121485.70000.00.00.011102Matches
204Daichi Kamadajp JPNMFCrystal Palace29-06119963.910213575.61788580486080.0374190.2112152.400.90.7-0.95135118Matches
205Boubacar Kamarafr FRAMF,DFAston Villa25-31619992.310612088.31714580465386.8495392.57977.810.10.1+0.92194023Matches
206Michael Kayodeit ITADFBrentford21-08720045.816722873.226247569610393.2516875.0164535.600.40.2-0.4491011Matches
207Michael Keaneeng ENGDFEverton32-26719936.019623683.141491357505689.311912992.2274955.100.10.1-0.1160013Matches
208Caoimhรญn Kelleherie IRLGKBrentford26-31619986.014922267.1453633761818100.0778096.35312243.400.00.00.016001Matches
209Milos Kerkezhu HUNDFLiverpool21-33220035.724830082.73966118013614295.89411283.9123138.700.10.1-0.13225125Matches
210Kevinbr BRAFW,MFFulham22-27420031.0334868.8618130141687.5101376.971353.800.00.10.012333Matches
211Abdukodir Khusanovuz UZBDFManchester City21-21820042.512114881.81957757646697.0495786.061540.000.00.00.003005Matches
212Max Kilmaneng ENGDFWest Ham28-13519977.027930890.653941490808495.217117597.7244355.800.20.2-0.22141116Matches
213Joshua Kingeng ENGMF,FWFulham18-27520075.29211679.31324242546583.1333789.22366.700.10.3-0.1363016Matches
214Justin Kluivertnl NEDMFBournemouth26-15319991.8527272.2813180233076.7232882.13933.300.30.4-0.344206Matches
215Ibrahima Konatรฉfr FRADFLiverpool26-13319996.237541091.56630202914114795.921523392.3152268.200.30.1-0.33321029Matches
216Ezri Konsaeng ENGDFAston Villa27-34719974.730431995.35301183012913198.515816496.3162272.700.00.10.01291018Matches
217Ladislav Krejฤรญcz CZEDF,MFWolves26-16819993.012714388.82543869404393.0747697.4132065.000.10.2-0.13112011Matches
218Mohammed Kudusgh GHAFWTottenham25-06420006.915821573.52726671808989.9537174.6234254.841.91.1+2.113119617Matches
219Maxence Lacroixfr FRADFCrystal Palace25-18220006.021725186.542861446697493.212413393.2234156.110.50.2+0.518108Matches
220Senne Lammensbe BELGKManchester Utd23-09020021.0204643.581872944100.044100.0123831.600.00.00.001000Matches
221Jamaal Lascelleseng ENGDFNewcastle Utd31-32819930.26875.0116141250.055100.0010.000.00.00.000000Matches
222Josh Laurenteng ENGDF,MFBurnley30-15219954.17310271.61064307434987.8253473.531225.000.20.1-0.217107Matches
223Romรฉo Laviabe BELMFChelsea21-27220040.5293290.66201688988.9141593.36785.700.00.00.004005Matches
224Enzo Le Fรฉefr FRAFW,MFSunderland25-24420004.212515879.11799376778689.5404883.351145.500.30.2-0.33104211Matches
225Bernd Lenode GERGKFulham33-21519927.021427677.5499231525252100.0124124100.0389938.400.00.00.001000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
226Jefferson Lermaco COLMFCrystal Palace30-34519942.0617779.21047379282996.6273187.151435.700.00.00.0160010Matches
227Rico Lewiseng ENGDFManchester City20-31820041.911312789.01908434525692.9505394.391369.211.00.70.02120018Matches
228Keane Lewis-Pottereng ENGDF,FWBrentford24-22520014.47810872.21340477343597.1334573.392142.900.00.10.013115Matches
229Myles Lewis-Skellyeng ENGDFArsenal19-00920061.0535793.09091072424100.0262796.33475.000.00.00.002001Matches
230Matthijs de Ligtnl NEDDFManchester Utd26-05419997.030534987.45554166711112092.517118990.5193161.300.50.5-0.52154314Matches
231Victor Lindelรถfse SWEMFAston Villa31-08019940.266100.0923533100.033100.00000.00.00.000002Matches
232Valentino Livramentoeng ENGDFNewcastle Utd22-32720025.823528981.33777129913415089.3829685.4163053.310.20.4+0.83245224Matches
233Sean Longstaffeng ENGMFLeeds United27-34019975.317921284.43329906727892.3788987.6233467.611.30.5-0.313140012Matches
234Fer Lรณpezes ESPMF,FWWolves21-13420041.2465978.0822207182378.3252889.33650.010.70.5+0.3376013Matches
235Florentino Luรญspt PORMFBurnley26-04719992.3617087.11032414313588.6202387.07887.500.00.00.0260012Matches
236Douglas Luizbr BRAMFNott'ham Forest27-14919980.8506083.3681108333594.3171989.5050.000.00.00.012001Matches
237Saลกa Lukiฤ‡rs SRBMFFulham29-05319966.119123481.631597099510888.0738388.0203360.611.00.90.011145118Matches
238Ian Maatsennl NEDDFAston Villa23-20920021.611313782.51913524576095.0455581.891656.300.20.2-0.23143114Matches
239Alexis Mac Allisterar ARGMFLiverpool26-28519984.319423184.0290674310411491.2748686.091752.911.00.20.05212014Matches
240Noni Maduekeeng ENGFWArsenal23-20920023.47312259.81044279435775.4213756.851145.500.80.8-0.880719Matches
241Gabriel Magalhรฃesbr BRADFArsenal27-29019977.037641890.06761197914815993.119020592.7294170.700.00.10.00230029Matches
242Soungoutou Magassafr FRAMFWest Ham21-36220031.8566783.6872178323397.0172085.05955.600.00.00.005002Matches
243Harry Maguireeng ENGDFManchester Utd32-21419932.09110983.51779522252889.3586885.371163.610.40.0+0.613009Matches
244Kobbie Mainooeng ENGMFManchester Utd20-16920051.3627286.1958217323591.4222588.03475.000.10.1-0.137216Matches
245Donyell Malennl NEDFW,MFAston Villa26-26919991.7303878.942953192190.588100.022100.000.10.1-0.112103Matches
246Giorgi Mamardashvilige GEOGKLiverpool25-00620001.0425280.89736611616100.01616100.0102050.000.00.00.001000Matches
247Reinildo Mandavamz MOZDFSunderland31-25719944.419023082.630097919510392.2859688.582138.100.00.10.00222121Matches
248Omar Marmousheg EGYFW,MFManchester City26-24019991.7394979.650888273090.0111384.60011.00.50.022105Matches
249Callum Marshallnir NIRFWWest Ham20-31120040.311100.011811100.0000000.00.00.000001Matches
250Gabriel Martinellibr BRAFWArsenal24-10920012.1182962.13467291275.05683.33650.000.10.1-0.121104Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
251Emiliano Martรญnezar ARGGKAston Villa33-03319924.011514579.3279517323333100.0596098.3235145.100.00.00.004200Matches
252Arthur Masuakucd CODDF,MFSunderland31-33219931.4446172.1720178212391.3182281.851435.700.10.1-0.113114Matches
253Pape Matar Sarrsn SENMFTottenham23-02120024.213416382.22083428708582.4475388.7101376.920.50.3+1.52160012Matches
254Jean-Philippe Matetafr FRAFWCrystal Palace28-09919975.9477166.2719135273187.1121866.76966.700.10.4-0.113215Matches
255Konstantinos Mavropanosgr GREDFWest Ham27-29819975.020823688.141931628646598.511112092.5274461.400.00.00.00151017Matches
256Eliezer Mayendaes ESPFWSunderland20-15020052.7111668.81455381266.733100.00000.50.1-0.520103Matches
257Noussair Mazraouima MARDFManchester Utd27-32519971.5749776.31269521344182.9333984.661154.500.00.10.00122219Matches
258Bryan Mbeumocm CMRMFManchester Utd26-05919996.617223972.028627348910684.0547473.0193063.311.61.5-0.6131011615Matches
259James Mcateeeng ENGMF,FWNott'ham Forest22-35220021.4556288.7771203313296.9182090.03560.000.40.2-0.435409Matches
260John McGinnsct SCOMF,FWAston Villa30-35219945.318122879.43450869677391.89111082.7223562.900.70.7-0.761210624Matches
261Dwight McNeileng ENGFWEverton25-31719990.25683.36684580.011100.00000.10.0-0.110000Matches
262Hannibal Mejbritn TUNMFBurnley22-25720032.8465879.3738165212680.8141877.86875.000.40.2-0.422215Matches
263Mikel Merinoes ESPMFArsenal29-10519962.77910178.21012290546090.0212584.021020.000.10.1-0.1293010Matches
264Antoni Milambonl NEDMFBrentford20-18520050.581172.7116494666.74580.00000.00.00.002003Matches
265Nikola Milenkoviฤ‡rs SRBDFNott'ham Forest27-35819976.030333191.55690203410010397.117818596.2223759.500.30.2-0.31293018Matches
266Lewis Mileyeng ENGMFNewcastle Utd19-15720061.9799979.81319286343987.2414689.14944.400.10.0-0.126005Matches
267James Milnereng ENGMFBrighton39-27419861.4496180.3993395202290.9212295.581650.000.00.10.017105Matches
268Veljko Milosavljeviฤ‡rs SRBDFBournemouth18-09920071.1354283.3598208141593.3192095.02633.300.10.2-0.114003Matches
269Tyrone Mingseng ENGDFAston Villa32-20619935.330233889.35767206910110992.717418096.7244355.800.10.1-0.13281027Matches
270Yankuba Mintehgm GAMFWBrighton21-07520045.67413853.61053359445974.6183847.451827.821.81.0+0.284325Matches
271Tyrick Mitchelleng ENGDF,MFCrystal Palace26-03419996.018326170.129349759010387.4769679.2123336.400.60.5-0.6887610Matches
272Kaoru Mitomajp JPNFWBrighton28-13819975.610613777.41326371728584.7283873.72366.711.10.3-0.1757213Matches
273Moratobr BRADFNott'ham Forest24-09720013.017620088.032211513778788.5798691.9192576.000.00.00.00100012Matches
274Cristhian Mosqueraes ESPDFArsenal21-10020042.815717390.82657743646795.5798790.8101471.400.00.00.003005Matches
275Yerson Mosqueraco COLDFWolves24-15620011.5616593.812492561010100.0484998.02450.000.00.00.001000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
276Mason Mounteng ENGMFManchester Utd26-26819993.08110775.71270447475585.5202483.3101952.600.50.5-0.56153018Matches
277Nordi Mukielefr FRADFSunderland27-33819975.018725074.835151094727991.18911478.1234650.000.30.2-0.3460012Matches
278Marshall Munetsizw ZIMFW,MFWolves29-10519964.8559061.1837212284858.3192867.95862.510.40.2+0.624228Matches
279Rodrigo Munizbr BRAFWFulham24-15420013.5113630.61865861154.541330.81616.710.30.0+0.742102Matches
280Daniel Muรฑozco COLDF,MFCrystal Palace29-13219966.015520476.02602861829487.2557078.6162661.511.61.2-0.6447412Matches
281Murillobr BRADFNott'ham Forest23-09320023.418722483.537661702606296.810811990.8194245.200.00.20.00260026Matches
282Jacob Murphyeng ENGFW,MFNewcastle Utd30-22319953.2599760.8986298344772.3193063.361540.010.20.3+0.818518Matches
283Vitaliy Mykolenkoua UKRDFEverton26-12919992.99913573.31640643525791.2435676.842020.000.00.30.0163017Matches
284David Mรธller Wolfeno NORMFWolves23-16520021.5265052.0422223142166.7102050.02728.610.30.1+0.712112Matches
285Iliman Ndiayesn SENFWEverton25-21320005.511815277.61622210728386.7384682.62450.010.80.6+0.2945210Matches
286Dan Ndoyech SUIFWNott'ham Forest24-34520004.89013268.21436278516183.6284365.171353.810.50.6+0.544428Matches
287Dan Neileng ENGMFSunderland23-29620010.0000000000000.00.00.000000Matches
288Pedro Netopt PORFWChelsea25-21020005.614420370.92241684789383.9527173.2102835.700.91.0-0.9948117Matches
289Rio Ngumohaeng ENGFW,MFLiverpool17-03720080.18988.91101155100.03475.00000.10.1-0.110000Matches
290Eddie Nketiaheng ENGMFCrystal Palace26-12819990.47977.81502011100.05771.411100.000.10.0-0.121001Matches
291Lukas Nmechade GERFWLeeds United26-29519981.7253669.427415172085.05862.50000.00.00.000001Matches
292Matheus Nunespt PORDFManchester City27-03919982.715818286.82503476899791.8586195.181650.000.00.10.0091012Matches
293Ethan Nwanerieng ENGMFArsenal18-19820071.7728188.91391356252792.6394097.571258.300.00.10.0182013Matches
294Jake O'Brienie IRLDFEverton24-14320016.016325264.730351150749181.37110965.1173450.000.00.10.01142215Matches
295Matt O'Rileydk DENMFBrighton24-31820002.0516183.6762174293290.6131586.75771.400.20.2-0.245115Matches
296Wilson Odobertfr FRAFWTottenham20-31120042.79210786.01402364495589.1364383.75683.300.20.3-0.2295014Matches
297Noah Okaforch SUIFWLeeds United25-13420003.4527272.21003306212680.8223073.381266.700.40.3-0.4464111Matches
298Amadou Onanabe BELMFAston Villa24-05020011.7617284.71069262232592.0303585.771070.000.00.10.004008Matches
299Frank Onyekang NGAMFBrentford27-27719980.8223073.3303133151693.86966.71250.010.10.4+0.915002Matches
300William Osuladk DENFWNewcastle Utd22-06220031.8161984.220916111384.655100.00000.10.0-0.110001Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
301Dango Ouattarabf BFAFWBrentford23-23620023.2215339.622993162564.02922.21911.100.20.3-0.210001Matches
302Nico Oโ€™Reillyeng ENGDF,MFManchester City20-19820053.413316879.21675590929794.8283873.751729.400.10.2-0.123319Matches
303Joรฃo Palhinhapt PORMFTottenham30-08819955.720925781.337498949010090.09911288.4173253.100.00.10.01181017Matches
304Cole Palmereng ENGMFChelsea23-15220021.6405375.5617158202483.3172181.03560.000.00.10.0132011Matches
305Lucas Paquetรกbr BRAMF,FWWest Ham28-03919976.726234176.84324122013415188.79812280.3255050.000.81.0-0.811289248Matches
306Bradley Paul Burroweseng ENGDFAston Villa17-21520080.23837.56541250.011100.01333.300.00.00.000000Matches
307Lucas Perribr BRAGKLeeds United27-29919973.05311147.71859141566100.0202195.2278432.100.00.00.003000Matches
308ฤorฤ‘e Petroviฤ‡rs SRBGKBournemouth25-36219997.014521069.0417928912424100.07070100.05111345.100.00.00.006000Matches
309Jordan Pickfordeng ENGGKEverton31-21219946.016826264.1563442642020100.0747598.77416744.300.00.00.0016100Matches
310Ethan Pinnockjm JAMDFBrentford32-12919931.7517171.8904423212680.8242885.731127.300.00.00.005002Matches
311Yeremi Pinoes ESPMFCrystal Palace22-35020022.5608669.8947286303683.3233467.651050.000.70.4-0.7344015Matches
312Joรซl Piroenl NEDFW,MFLeeds United26-06419991.6303781.144838151978.91010100.011100.000.00.00.003001Matches
313Nick Popeeng ENGGKNewcastle Utd33-16919926.011517964.230992005181994.76363100.0349635.400.00.00.000000Matches
314Pedro Porroes ESPDFTottenham26-02219996.125637069.24931215110311490.412114583.43110130.700.70.9-0.710328732Matches
315Freddie Pottseng ENGMFWest Ham22-02320031.2456075.0623240232882.1192576.0020.000.00.00.002002Matches
316Jacob Ramseyeng ENGMFNewcastle Utd24-13020010.7242885.735881121485.7111291.711100.000.30.1-0.331115Matches
317David Rayaes ESPGKArsenal30-02019957.018825673.4517436934444100.0929497.95211844.100.10.0-0.1118103Matches
318Tijjani Reijndersnl NEDMFManchester City27-06819985.924528685.7342063714715296.78310083.051827.821.00.4+1.06234024Matches
319Declan Riceeng ENGMFArsenal26-26419996.037043585.16518166016518191.216717794.4336749.321.21.2+0.811276236Matches
320Chris Richardsus USADFCrystal Palace25-19120006.018022081.836311181515986.411412789.8153148.400.00.10.0180010Matches
321Richarlisonbr BRAFWTottenham28-14819975.0447459.5714200283971.881553.36875.010.80.2+0.244109Matches
322Chris Riggeng ENGMFSunderland18-10920071.1182475.03015291369.277100.02366.700.00.00.010101Matches
323Patrick Robertseng ENGMF,FWSunderland28-24219970.3172181.019250111291.73475.011100.000.00.00.003001Matches
324Andrew Robertsonsct SCODFLiverpool31-20819941.3739477.71237462424985.7273773.04850.000.30.4-0.35115410Matches
325Antonee Robinsonus USADFFulham28-05819970.7506478.1858317232495.8202580.05862.500.00.10.013215Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
326Joe Rodonwls WALDFLeeds United27-34819977.030934190.659021842889790.719720894.7202969.000.00.00.00120026Matches
327Rodries ESPMFManchester City29-10519963.018621686.132069108910287.3708186.4192190.500.10.2-0.12171019Matches
328Guido Rodrรญguezar ARGMFWest Ham31-17619940.8283482.4419106161888.9111291.7020.000.00.00.002104Matches
329Robin Roefsnl NEDGKSunderland22-26120037.018127166.8513335032626100.0989999.05614538.600.20.0-0.229100Matches
330Morgan Rogerseng ENGFW,MFAston Villa23-07120025.911116268.51706501607481.1375468.5102050.010.90.6+0.1694018Matches
331Merlin Rรถhlde GERMFEverton23-09220020.371070.098394666.73475.00000.10.0-0.112001Matches
332Cristian Romeroar ARGDFTottenham27-16119987.038242689.77417276511612692.123024693.5324768.110.20.2+0.82273032Matches
333Georginio Rutterfr FRAFW,MFBrighton23-16820023.6416167.2786279152171.4202387.04757.111.20.4-0.2583011Matches
334Josรฉ Sรกpt PORGKWolves32-26119934.08913167.9268320981313100.0474897.9286940.600.10.0-0.113100Matches
335Noah Sadikicd CODMFSunderland20-29220046.922126483.7338482911512592.0839587.4132454.200.10.3-0.13154219Matches
336Bukayo Sakaeng ENGFWArsenal24-03020013.89913473.91694352556288.7314175.6112347.800.81.5-0.870506Matches
337Mohamed Salaheg EGYFWLiverpool33-11219927.014821568.821516079210885.2416464.1112642.321.21.0+0.811413322Matches
338William Salibafr FRADFArsenal24-19520014.535738393.26152186615115398.718719994.0152268.200.10.1-0.11151016Matches
339Robert Sรกnchezes ESPGKChelsea27-32119976.016424666.742472947333497.1828398.84612636.500.10.0-0.126000Matches
340Jadon Sanchoeng ENGFWAston Villa25-19420000.1151883.32207566100.08988.9010.000.00.00.000303Matches
341Ibrahim Sangarรฉci CIVMFNott'ham Forest27-30719973.920623189.234771085929893.99210191.1152171.400.00.10.00251022Matches
342Andrey Santosbr BRAMFChelsea21-15520041.98710384.51173361536088.3273090.01250.000.10.0-0.13131012Matches
343Ismaila Sarrsn SENMF,FWCrystal Palace27-22219983.8527173.2824260283580.0212584.02540.000.30.2-0.3264012Matches
344Sรกviobr BRAMF,FWManchester City21-17820040.9263281.337259182281.877100.011100.000.00.10.000101Matches
345Nicolรฒ Savonait ITADFNott'ham Forest22-20020030.6202676.930558131492.96966.71250.000.00.00.011000Matches
346Kevin Schadede GERFWBrentford23-31220015.4549159.3890244274461.4162564.061154.500.60.4-0.633002Matches
347Fabian Schรคrch SUIDFNewcastle Utd33-28919913.918422581.842011793505590.99910693.4356256.500.10.1-0.12181013Matches
348Alex Scotteng ENGMFBournemouth22-04520035.020325081.2326165010210894.4798988.8153839.500.30.2-0.34133213Matches
349Jenson Seeltnl NEDDFSunderland22-13520031.59611087.31786705384095.0464895.8101662.500.00.00.0060014Matches
350Matz Selsbe BELGKNott'ham Forest33-22119926.011915875.3288718593838100.0575898.3236137.700.00.00.006000Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
351Antoine Semenyogh GHAFW,MFBournemouth25-27120007.014721967.123437048310380.6517468.9123237.530.50.4+2.510712024Matches
352Marcos Senesiar ARGDFBournemouth28-14819976.934945077.678033495869590.518320091.57613855.120.31.1+1.744911340Matches
353Benjamin ล eลกkosi SVNFWManchester Utd22-12720034.3497466.2595114284266.791850.044100.000.20.1-0.254205Matches
354Ryan Sessegnoneng ENGDFFulham25-14020005.317725170.526978739611186.5729377.462524.000.80.6-0.84183018Matches
355Luke Shaweng ENGDFManchester Utd30-08519956.733540083.85553187317320086.512013688.2294367.400.10.1-0.11302031Matches
356Bernardo Silvapt PORMFManchester City31-05619944.015518285.22496591818793.1566487.5131968.411.71.1-0.77134215Matches
357Jota Silvapt PORFWNott'ham Forest26-06519990.12366.727511100.01250.00000.00.00.000000Matches
358Xavi Simonsnl NEDFW,MFTottenham22-16720032.88911974.81518348445481.5333886.892240.910.60.5+0.4567217Matches
359Adam Smitheng ENGDFBournemouth34-15919912.3698779.31169441353989.7273090.071643.800.30.1-0.317006Matches
360Emile Smith Roweeng ENGMFFulham25-06920001.5768787.41145170404785.1273090.055100.000.00.10.015208Matches
361Dominic Solankeeng ENGFWTottenham28-02119970.422100.019022100.0000000.00.00.000000Matches
362Julio Solerar ARGFWBournemouth20-23120050.0000000000000.00.00.000000Matches
363Oliver Sonnepe PERDF,FWBurnley24-32920001.2192965.524788141782.43742.91333.300.00.10.011214Matches
364Tomรกลก Souฤekcz CZEMFWest Ham30-22019952.6709176.9945258404588.9212972.43560.000.00.00.005004Matches
365Djed Spenceeng ENGDFTottenham25-05720004.520726179.3355810609410391.39311183.8163447.100.10.2-0.12223124Matches
366Anton Stachde GERMFLeeds United26-32419986.719726773.8297881910913083.8597677.6153050.011.00.60.013215234Matches
367John Stoneseng ENGDFManchester City31-13019942.920122091.433341262899197.810511293.861250.000.00.00.00111014Matches
368Jรธrgen Strand Larsenno NORFWWolves25-24120003.2415870.756387283971.881080.03475.000.00.10.006002Matches
369Pascal Struijknl NEDDFLeeds United26-05519997.038241093.27044248614514798.620921597.2243863.200.10.1-0.12291018Matches
370Crysencio Summervillenl NEDMF,FWWest Ham23-34020013.58910386.41208264556091.7232688.54666.710.20.4+0.8242112Matches
371Dominik Szoboszlaihu HUNMF,DFLiverpool24-34520007.039046883.36945162718119791.914916391.4488357.801.00.7-1.08446342Matches
372Chemsdine Talbima MARFWSunderland20-14920055.611614977.91816267678182.7374582.281650.010.30.3+0.752324Matches
373Ao Tanakajp JPNMFLeeds United27-02519982.08910485.61408314485587.3313588.677100.000.20.1-0.23101010Matches
374James Tarkowskieng ENGDFEverton32-32019926.020325878.742911550576193.410912785.8335856.900.20.2-0.22202121Matches
375Marcus Taverniereng ENGMFBournemouth26-19719995.516421177.72656623829388.2637485.1112937.900.50.5-0.57186026Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
376Loum Tchaounafr FRAMFBurnley22-02720032.8426070.0555172273090.0141973.7010.000.10.2-0.113627Matches
377Jackson Tchatchouacm CMRDF,MFWolves24-02120013.010114470.11732424506280.6456173.861637.500.10.1-0.134332Matches
378Mathys Telfr FRAFWTottenham20-16120052.0223268.8461190111384.671070.04580.000.00.00.004113Matches
379Kenny Tetenl NEDDFFulham29-36119955.020725182.53212144211712792.1678281.7173154.800.10.4-0.12123124Matches
380Thiagobr BRAFWBrentford24-10120015.5638574.1862185414885.4152657.744100.001.10.2-1.144326Matches
381Malick Thiawde GERDFNewcastle Utd24-05820012.0506182.0975278172181.0293387.93560.000.00.00.002106Matches
382Youri Tielemansbe BELMFAston Villa28-15119973.519523583.03535862879888.8778887.5263868.400.20.4-0.24251021Matches
383Jurriรซn Timbernl NEDDFArsenal24-11020015.825029485.03944111311712792.112614487.551435.710.40.5+0.682412137Matches
384Jean-Clair Todibofr FRADFWest Ham25-27919991.4808890.91467498303196.8424397.781172.700.00.00.008007Matches
385Sandro Tonaliit ITAMFNewcastle Utd25-15020005.724730979.94122121113214690.48810782.2214744.711.10.7-0.110355242Matches
386Pau Torreses ESPDFAston Villa28-26219971.813815489.62421771636794.0556091.7172470.800.00.10.00100013Matches
387James Traffordeng ENGGKManchester City22-36020023.08811874.619481257252792.64343100.0194641.300.00.00.000000Matches
388Adama Traorรฉes ESPFWFulham29-25319961.6314568.9460109151883.3131872.21425.000.50.5-0.531432Matches
389Bertrand Traorรฉbf BFAFW,MFSunderland30-02919951.0253375.834652141593.35862.52366.700.70.0-0.721101Matches
390Kieran Trippiereng ENGDFNewcastle Utd35-01619903.821628376.3402614848610284.39811585.2285650.000.30.3-0.38183132Matches
391Leandro Trossardbe BELFWArsenal30-30519943.38912969.01734411344673.9405080.0122060.010.30.5+0.755728Matches
392Adrien Truffertfr FRADFBournemouth23-31920017.029436680.34472139917118294.010513577.8133339.410.60.4+0.46319736Matches
393Stefanos Tzimasgr GREFW,MFBrighton19-27220060.31333.37011100.000010.000.00.00.010000Matches
394Christantus Ucheng NGAMFCrystal Palace22-13920030.15955.670113475.02450.0010.000.00.00.001000Matches
395Destiny Udogieit ITADFTottenham22-31120023.418321684.7293211538810088.0879492.651050.000.20.2-0.23215021Matches
396Manuel Ugarteuy URUMFManchester Utd24-17720012.710812884.41768470606987.0374092.591369.200.00.00.01160019Matches
397Lesley Ugochukwufr FRAMFBurnley21-19320043.0647783.11029276323591.4232688.57887.500.00.00.015006Matches
398Virgil van Dijknl NEDDFLiverpool34-08919917.049555489.49087374418118896.326928694.1387054.300.00.10.01521040Matches
399Joรซl Veltmannl NEDDFBrighton33-26319923.68010874.11520659303585.7404785.192045.000.00.00.01110014Matches
400Micky van de Vennl NEDDFTottenham24-16920016.938842192.27790237311011397.325025996.5274658.700.10.2-0.12190020Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
401Bart Verbruggennl NEDGKBrighton23-04820026.016223070.4403925934242100.0767897.44410940.400.10.0-0.115100Matches
402Guglielmo Vicarioit ITAGKTottenham28-36319967.025131180.7580534175151100.0154154100.04610543.800.00.00.005000Matches
403Kyle Walkereng ENGDFBurnley35-13019906.018126668.0334212488910287.3618670.9256041.700.00.20.00131014Matches
404Kyle Walker-Peterseng ENGDFWest Ham28-17519974.418421784.8268085611211994.1576982.671741.200.10.1-0.13143015Matches
405Aaron Wan-Bissakacd CODDFWest Ham27-31319972.911914482.61656639768292.7334180.55771.400.10.1-0.126447Matches
406James Ward-Prowseeng ENGMFWest Ham30-33819944.618521884.932141005818496.4718187.7233762.200.90.9-0.912195120Matches
407Ollie Watkinseng ENGFWAston Villa29-27919955.9587973.4681103415475.9111573.311100.000.40.2-0.442006Matches
408Danny Welbeckeng ENGFWBrighton34-31319903.1435775.4514101243080.0141687.5010.000.10.3-0.115001Matches
409Adam Whartoneng ENGMFCrystal Palace21-24120044.012215678.22271806414885.4667291.7122744.401.81.1-1.810183019Matches
410Ben Whiteeng ENGDFArsenal27-36219970.8212777.8353131101190.9101283.31333.300.00.00.015004Matches
411Mats Wieffernl NEDDFBrighton25-32319992.410212581.61699602525791.2414983.76966.710.60.4+0.4193014Matches
412Neco Williamswls WALDFNott'ham Forest24-17520016.030237081.64643124916317891.612514586.293228.100.50.4-0.56284435Matches
413Estรชvรฃo Willianbr BRAFWChelsea18-16420072.98410480.81372204435184.3374190.23837.511.21.2-0.243323Matches
414Joe Willockeng ENGMFNewcastle Utd26-04619991.0152075.0261297977.877100.01250.000.00.00.001001Matches
415Callum Wilsoneng ENGFWWest Ham33-22019921.9132259.1164465771.461060.0010.000.00.00.011001Matches
416Harry Wilsonwls WALFW,MFFulham28-19719974.910614374.11584349596590.8334475.082236.400.10.3-0.1410209Matches
417Ben Winterburneng ENGMFBournemouth21-03120040.1010.000010.0000000.00.00.000000Matches
418Florian Wirtzde GERMF,FWLiverpool22-15520035.419425177.330868249911784.6728683.7162857.100.80.9-0.811207033Matches
419Nick Woltemadede GERFWNewcastle Utd23-23320022.4293974.431460222781.561060.00000.00.00.012002Matches
420Chris Woodnz NZLFWNott'ham Forest33-30219915.1487068.667096283580.0172568.0020.000.20.1-0.234106Matches
421Joe Worralleng ENGDFBurnley28-26819970.21425.0260010.01250.00000.00.00.000000Matches
422Granit Xhakach SUIMFSunderland33-00819927.033341580.26457183313014987.214616986.4497862.831.00.7+2.09405140Matches
423Yehor Yarmoliukua UKRMFBrentford21-21820045.916720282.72882685839191.2667588.0132552.010.20.2+0.84153117Matches
424Ryan Yateseng ENGMFNott'ham Forest27-31819970.3202290.9320451212100.055100.03475.000.00.00.001102Matches
425Leny Yorofr FRADFManchester Utd19-32620055.021525086.038581331929992.910311391.2173154.800.00.00.01141118Matches
RkPlayerNationPosSquadAgeBorn90sCmpAttCmp%TotDistPrgDistCmpAttCmp%CmpAttCmp%CmpAttCmp%AstxAGxAA-xAGKP1/3PPACrsPAPrgPMatches
426Oleksandr Zinchenkoua UKRDFNott'ham Forest28-29419962.016517892.72726562788097.5778293.9101376.900.00.20.02194018Matches
427Joshua Zirkzeenl NEDFW,MFManchester Utd24-13620010.9263281.330173222684.64666.70000.00.00.012003Matches
428Martรญn Zubimendies ESPMFArsenal26-24519996.636240788.95900161417218792.016518390.2212875.000.60.8-0.65274036Matches
429Martin ร˜degaardno NORMFArsenal26-29219982.311114079.31924646535989.8455581.8121963.210.50.8+0.53199026Matches
+ + +
+
+ + + + + + + + +
+
+

About FBref.com

+ + + + + +

+ FBref.com launched (June 13, 2018) with domestic league coverage for England, France, Germany, Italy, Spain, and United States. Since then we have been steadily expanding our coverage to include domestic leagues from over 40 countries as well as domestic cup, super cup and youth leagues from top European countries. We have also added coverage for major international cups such as the UEFA Champions League and Copa Libertadores. +

+ + +

+ FBref is the most complete sources for women's football data on the internet. This includes the entire history of the FIFA Women's World Cup as well as recent domestic league seasons from nine countries, including advanced stats like xG for most of those nine. +

+ +

+ In collaboration with Opta, we are including advanced analytical data such as xG, xA, progressive passing, duels and more for over twenty competitions. For more information on the expected goals model and which competitions have advanced data, see our xG explainer. +

+ + + +
+

+ Note that player records are likely not complete for their careers. Players may come from or move to leagues we don't currently cover. This issue will go down over time, as we add new leagues and seasons. We will never in the future have less data than we do today. +

+ +

You can sign up to receive an e-mail when new countries and features launch.

+ +

For more information, see our Launch Blog Post, the overall leagues/competition page with details on leagues and seasons we include, or our About Page. Let us know if you find an issue or have a suggestion.

+ +

FBref is one of seven Sports-Reference.com sites.

+
+
+ + + + + + + +
+ + + + +
+ + + + + +
+ + + + +
+ + + + + + +
+
+ +
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/main/fbrefdata_example.py b/main/fbrefdata_example.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc281e6bc99c7d0e232d513e745fbd5325b142e --- /dev/null +++ b/main/fbrefdata_example.py @@ -0,0 +1,131 @@ +import time +import pandas as pd +from io import StringIO +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.chrome.options import Options as ChromeOptions +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + +def pull_premier_league_passing(): + """ + Ambil data passing (otomatis deteksi: tim atau pemain) + dari halaman FBref Premier League terbaru. + """ + # URL utama + url = "https://fbref.com/en/comps/9/passing/Premier-League-Stats" + print(f"๐ŸŒ Opening browser to download passing stats from {url} ...") + + # --- Setup browser Chrome --- + options = ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + # options.add_argument("--headless") # aktifkan jika ingin tanpa tampilan browser + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option('useAutomationExtension', False) + + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get(url) + + # --- Handle cookie banner (jika muncul) --- + try: + wait = WebDriverWait(driver, 10) + accept_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, "//button[contains(text(), 'Accept All Cookies')]") + )) + accept_button.click() + print("๐Ÿช Cookie banner accepted.") + except TimeoutException: + print("No cookie banner found or it took too long.") + + # --- Coba deteksi tabel TIM terlebih dahulu --- + table_html = None + try: + wait = WebDriverWait(driver, 15) + div_team = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing_team"))) + print("โœ… Team passing table found.") + table_html = div_team.get_attribute("outerHTML") + table_type = "team" + except TimeoutException: + print("โš ๏ธ Team passing table not found. Trying player table...") + + # --- Fallback ke tabel pemain --- + try: + div_player = wait.until(EC.presence_of_element_located((By.ID, "all_stats_passing"))) + print("โœ… Player passing table found.") + table_html = div_player.get_attribute("outerHTML") + table_type = "player" + except TimeoutException: + print("โŒ No passing table found at all. Saving debug files...") + driver.save_screenshot('debug_screenshot.png') + with open('debug_page.html', 'w', encoding='utf-8') as f: + f.write(driver.page_source) + driver.quit() + return None + + driver.quit() + print("๐Ÿ“„ Data downloaded. Processing with pandas...") + + # --- Parse HTML table ke DataFrame --- + df = pd.read_html(StringIO(table_html))[0] + print(f"โœ… Table found with shape: {df.shape}") + + # Gabungkan header dua baris (jika ada) + if isinstance(df.columns, pd.MultiIndex): + df.columns = ['_'.join(col).strip() for col in df.columns.values] + + # Pilih kolom relevan + cols_to_use = [c for c in df.columns if any(x in c for x in ['Squad', 'Player', 'Cmp', 'Att', 'Cmp%', 'TotDist'])] + df = df[cols_to_use] + + # Normalisasi nama kolom + rename_map = {} + for c in df.columns: + if 'Squad' in c: rename_map[c] = 'Squad' + elif 'Player' in c: rename_map[c] = 'Player' + elif 'Cmp%' in c: rename_map[c] = 'Total_Cmp%' + elif 'Cmp' in c and 'Cmp%' not in c: rename_map[c] = 'Total_Cmp' + elif 'Att' in c: rename_map[c] = 'Total_Att' + elif 'TotDist' in c: rename_map[c] = 'Total_TotDist' + df.rename(columns=rename_map, inplace=True) + + # Bersihkan baris kosong / header duplikat + if 'Squad' in df.columns: + df = df[df['Squad'].notna()] + df = df[~df['Squad'].str.contains("Squad|Rk", na=False)] + + print(f"โœ… Cleaned dataframe shape: {df.shape}") + return df, table_type + + +def filter_teams(df, teams): + """Filter baris berdasarkan nama tim""" + if "Squad" not in df.columns: + print("โš ๏ธ 'Squad' column not found, skipping team filter.") + return df + return df[df["Squad"].isin(teams)] + + +def main(): + df, table_type = pull_premier_league_passing() + if df is not None: + # Simpan hasil + filename = f"premier_league_{table_type}_passing.csv" + df.to_csv(filename, index=False) + print(f"\n๐Ÿ’พ Saved to {filename}") + + # Filter contoh tim + teams = ["Arsenal", "Wolves", "Brighton"] + df_filtered = filter_teams(df, teams) + print(f"\n๐Ÿ“Š Passing Stats ({table_type.title()} Level) for selected teams") + print("=" * 80) + print(df_filtered.head()) + + +if __name__ == "__main__": + main() diff --git a/main/historical_data.py b/main/historical_data.py new file mode 100644 index 0000000000000000000000000000000000000000..c6f721f460a43ea548c725509f2b6e79628c1eda --- /dev/null +++ b/main/historical_data.py @@ -0,0 +1,111 @@ +# File: create_historical_data.py (Versi Final Sebenarnya) +import pandas as pd +from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from io import StringIO +import time +import sys +import random + +# Fungsi-fungsi lainnya tetap sama +def calculate_team_passing_avg(passing_stats_file): + try: + df_pass = pd.read_csv(passing_stats_file) + df_pass['Total_Cmp%'] = pd.to_numeric(df_pass['Total_Cmp%'], errors='coerce') + team_avg_pass = df_pass.groupby('Squad')['Total_Cmp%'].mean().reset_index().rename(columns={'Total_Cmp%': 'AvgPass%'}) + print("โœ… Berhasil menghitung rata-rata passing % per tim.") + return team_avg_pass + except FileNotFoundError: + print(f"โŒ Error: File '{passing_stats_file}' tidak ditemukan."); return None + +def scrape_season_data(url, driver): + print(f"\n - Mengakses: {url}") + try: + driver.get(url); print(" โณ Menunggu tabel data muncul...") + table_element = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "table.stats_table"))) + print(" โœ… Tabel ditemukan, mengambil data HTML...") + html_source = table_element.get_attribute('outerHTML') + df_season = pd.read_html(StringIO(html_source))[0] + print(f" ๐Ÿ‘ Berhasil mengambil data untuk musim ini.") + return df_season + except Exception as e: + print(f" โŒ Gagal mengambil data dari URL ini."); return None + +# --- MAIN SCRIPT --- +if __name__ == "__main__": + PASSING_STATS_FILE = "premier_league_player_passing.csv" + OUTPUT_FILE = "historical_matches.csv" + + team_pass_avg_df = calculate_team_passing_avg(PASSING_STATS_FILE) + if team_pass_avg_df is None: sys.exit() + + seasons_urls = [ + "https://fbref.com/en/comps/9/schedule/2023-2024/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2022-2023/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2021-2022/Premier-League-Scores-and-Fixtures", + "https://fbref.com/en/comps/9/schedule/2020-2021/Premier-League-Scores-and-Fixtures" + ] + all_matches_df = pd.DataFrame() + + print("\n๐ŸŒ Memulai proses pengambilan data dari 4 musim terakhir...") + # ... Kode scraping sama ... + options = webdriver.ChromeOptions(); options.add_argument("--headless"); options.add_argument("--no-sandbox"); options.add_argument("--disable-dev-shm-usage") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36") + driver = None + try: + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options) + driver.get("https://fbref.com") + try: + wait = WebDriverWait(driver, 5); accept_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[text()="Accept All"]'))); accept_button.click(); print("โœ… Cookie banner diterima.") + except: print("โ„น๏ธ Tidak ada cookie banner atau sudah diterima.") + for url in seasons_urls: + season_df = scrape_season_data(url, driver) + if season_df is not None: all_matches_df = pd.concat([all_matches_df, season_df], ignore_index=True) + jeda = random.randint(5, 10); print(f" โ˜• Istirahat sejenak selama {jeda} detik..."); time.sleep(jeda) + finally: + if driver: driver.quit(); print("\nโœ… Browser ditutup.") + + if all_matches_df.empty: + print("โŒ Tidak ada data yang berhasil diambil. Proses dihentikan."); sys.exit() + + # === PERBAIKAN FINAL PADA LOGIKA PEMBERSIHAN DATA === + print("\nโš™๏ธ Memproses semua data pertandingan...") + + # Kunci perbaikan: Kita hanya peduli pada baris yang memiliki skor valid. + # Ubah kolom 'Score' menjadi string untuk memastikan .str.contains() bekerja + all_matches_df['Score'] = all_matches_df['Score'].astype(str) + + # Filter hanya baris yang merupakan pertandingan (memiliki skor dengan format 'angkaโ€“angka') + df_matches = all_matches_df[all_matches_df['Score'].str.contains(r'\d+โ€“\d+', na=False)].copy() + + # Setelah difilter, baru kita proses kolomnya + scores = df_matches['Score'].str.split('โ€“', expand=True) + df_matches['HomeGoals'] = pd.to_numeric(scores[0]) + df_matches['AwayGoals'] = pd.to_numeric(scores[1]) + + print("๐Ÿ”„ Menggabungkan data pertandingan dengan data passing...") + pass_map = {row['Squad']: row['AvgPass%'] for index, row in team_pass_avg_df.iterrows()} + + def get_pass_perc(team_name): + name_map = {"Manchester Utd": "Manchester United", "Newcastle Utd": "Newcastle United", "Nott'ham Forest": "Nottingham Forest", "West Brom": "West Bromwich Albion", "Sheffield Utd": "Sheffield United", "West Ham": "West Ham United", "Spurs": "Tottenham Hotspur", "Wolves": "Wolverhampton Wanderers"} + mapped_name = name_map.get(team_name, team_name) + if mapped_name in pass_map: return pass_map[mapped_name] + for squad_name, perc in pass_map.items(): + if mapped_name in squad_name or squad_name in mapped_name: return perc + return team_pass_avg_df['AvgPass%'].mean() + + df_matches['HomePass%'] = df_matches['Home'].apply(get_pass_perc) + df_matches['AwayPass%'] = df_matches['Away'].apply(get_pass_perc) + + final_df = df_matches[['Date', 'Home', 'Away', 'HomeGoals', 'AwayGoals', 'HomePass%', 'AwayPass%']] + final_df = final_df.round(1) + + try: + final_df.to_csv(OUTPUT_FILE, index=False) + print(f"\n๐ŸŽ‰ SUKSES! File '{OUTPUT_FILE}' berhasil dibuat dengan {len(final_df)} data pertandingan.") + except Exception as e: + print(f"โŒ Gagal menyimpan file CSV: {e}") \ No newline at end of file diff --git a/main/match_predictor.py b/main/match_predictor.py new file mode 100644 index 0000000000000000000000000000000000000000..7292d3076e8bd146c6799c1901ab0a0416963f76 --- /dev/null +++ b/main/match_predictor.py @@ -0,0 +1,180 @@ +import pandas as pd +import numpy as np +import time +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from bs4 import BeautifulSoup +from webdriver_manager.chrome import ChromeDriverManager + + +# ============================================== +# 1๏ธโƒฃ FUNGSI UNTUK SCRAPE JADWAL PREMIER LEAGUE +# ============================================== +def scrape_premier_league_schedule(): + url = "https://fbref.com/en/comps/9/schedule/Premier-League-Scores-and-Fixtures" + print(f"๐ŸŒ Opening browser to scrape data from: {url}") + + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920,1080") + options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36") + + driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) + + try: + driver.get(url) + time.sleep(5) + html = driver.page_source + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table", id=lambda x: x and "schedule" in x.lower()) + if not table: + table = soup.find("table", class_=lambda x: x and "stats_table" in x.lower()) + + if not table: + print("โŒ Match table not found. Saving debug page...") + with open("debug_schedule.html", "w", encoding="utf-8") as f: + f.write(soup.prettify()) + driver.quit() + return None + + rows = table.find_all("tr") + data = [] + for row in rows: + cols = [c.text.strip() for c in row.find_all(["th", "td"])] + if len(cols) >= 8: + data.append(cols[:8]) + + df = pd.DataFrame(data, columns=["Date", "Time", "Home", "xG_H", "Score", "xG_A", "Away", "Attendance"]) + df = df[df["Home"].notna()] + print(f"โœ… Match table loaded: {len(df)} rows") + return df + + except Exception as e: + print(f"โŒ Gagal mengakses halaman FBref: {e}") + return None + finally: + driver.quit() + + +# ================================================== +# 2๏ธโƒฃ FUNGSI UNTUK MEMUAT DATA PASSING (DARI CSV) +# ================================================== +def load_passing_data(): + try: + df = pd.read_csv("premier_league_player_passing.csv") + print(f"๐Ÿ“Š Loaded passing data: {df.shape}") + return df + except FileNotFoundError: + print("โš ๏ธ Tidak ditemukan file premier_league_player_passing.csv.") + return None + + +# ================================================== +# 3๏ธโƒฃ MODEL PREDIKSI BERBASIS STATISTIK +# ================================================== +def predict_match(home_team, away_team, passing_df=None): + print(f"\n๐Ÿ”ฎ Memprediksi pertandingan: {home_team} vs {away_team}") + + # --- Data dasar asumsi form --- + team_stats = { + "Man City": {"form": 0.85, "goals": 2.3, "concede": 0.7, "home_adv": 1.2}, + "Brentford": {"form": 0.45, "goals": 1.1, "concede": 1.5, "home_adv": 1.0}, + } + + if home_team not in team_stats or away_team not in team_stats: + print("โš ๏ธ Data form tidak ditemukan, menggunakan nilai default.") + team_stats[home_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + team_stats[away_team] = {"form": 0.5, "goals": 1.0, "concede": 1.0, "home_adv": 1.0} + + # --- Faktor performa passing jika tersedia --- + if passing_df is not None: + home_pass = passing_df[passing_df["Squad"].str.contains(home_team, case=False, na=False)] + away_pass = passing_df[passing_df["Squad"].str.contains(away_team, case=False, na=False)] + + def avg_pass(df): + if df.empty: + return 0 + try: + return df["Total_Cmp%"].astype(float).mean() / 100 + except: + return 0.8 + + home_passing = avg_pass(home_pass) + away_passing = avg_pass(away_pass) + else: + home_passing, away_passing = 0.82, 0.76 # default rata-rata passing EPL + + # --- Perhitungan skor probabilitas --- + home_score = ( + team_stats[home_team]["form"] * 0.5 + + team_stats[home_team]["goals"] / (team_stats[away_team]["concede"] + 0.1) * 0.3 + + home_passing * 0.1 + + team_stats[home_team]["home_adv"] * 0.1 + ) + + away_score = ( + team_stats[away_team]["form"] * 0.5 + + team_stats[away_team]["goals"] / (team_stats[home_team]["concede"] + 0.1) * 0.3 + + away_passing * 0.1 + + team_stats[away_team]["home_adv"] * 0.1 + ) + + p_home = home_score / (home_score + away_score) + p_away = away_score / (home_score + away_score) + p_draw = 0.15 + + print("\n๐Ÿ“ˆ Probabilitas Prediksi:") + print(f" ๐Ÿ  {home_team} menang : {p_home:.2%}") + print(f" ๐Ÿš— {away_team} menang : {p_away:.2%}") + print(f" ๐Ÿค Seri : {p_draw:.2%}") + + # --- Prediksi skor --- + exp_home_goals = round(team_stats[home_team]["goals"] * p_home + np.random.uniform(0, 0.5)) + exp_away_goals = round(team_stats[away_team]["goals"] * p_away + np.random.uniform(0, 0.3)) + + print(f"\nโšฝ Prediksi Skor: {home_team} {exp_home_goals} โ€“ {exp_away_goals} {away_team}") + + if p_home > p_away: + result = f"{home_team} berpeluang menang" + elif p_away > p_home: + result = f"{away_team} berpeluang menang" + else: + result = "kemungkinan besar imbang" + + print(f"\n๐Ÿง  Analisis Akhir: {result}\n") + print("โœ… Prediksi selesai.") + + +# ================================================== +# 4๏ธโƒฃ MAIN PROGRAM +# ================================================== +def main(): + schedule_df = scrape_premier_league_schedule() + passing_df = load_passing_data() + + if schedule_df is None: + print("โŒ Gagal mendapatkan data pertandingan.") + return + + # Cek apakah ada Brighton vs Wolves + mask = ( + (schedule_df["Home"].str.contains("Brighton", na=False) & schedule_df["Away"].str.contains("Wolves", na=False)) | + (schedule_df["Home"].str.contains("Wolves", na=False) & schedule_df["Away"].str.contains("Brighton", na=False)) + ) + + match_df = schedule_df[mask] + if match_df.empty: + print("โš ๏ธ Tidak ditemukan pertandingan Brighton vs Wolves dalam jadwal.") + else: + print("\n๐Ÿ“… Jadwal Pertandingan Ditemukan:") + print(match_df.tail(1)[["Date", "Home", "Away"]].to_string(index=False)) + + predict_match("Brentford", "Man City", passing_df) + + +if __name__ == "__main__": + main() diff --git a/main/model_report.py b/main/model_report.py new file mode 100644 index 0000000000000000000000000000000000000000..cabf52f858f1a5f64221a76922fbdd80497ec16d --- /dev/null +++ b/main/model_report.py @@ -0,0 +1,144 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Memproses Data) tidak berubah +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain (untuk prediksi)...") +try: + passing_df = pd.read_csv("premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() +print("\n๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") + pass_map = passing_df.groupby('Squad')['Total_Cmp%'].mean() + hist_df['HomePass%'] = hist_df['Home'].map(pass_map) + hist_df['AwayPass%'] = hist_df['Away'].map(pass_map) + average_pass_perc = passing_df['Total_Cmp%'].mean() + hist_df['HomePass%'].fillna(average_pass_perc, inplace=True) + hist_df['AwayPass%'].fillna(average_pass_perc, inplace=True) + print("โœ… Fitur passing berhasil dibuat untuk data historis.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index, row in hist_df.iterrows(): + past_matches = hist_df.iloc[:index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 4๏ธโƒฃ Training & Evaluasi Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +print("\n๐Ÿง  Melatih model RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, passing_df, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + + # === PERBAIKAN BUG H2H === + # Standarisasi nama tim agar sesuai dengan dataset Kaggle + name_map_kaggle = {"Man Utd": "Man United", "Spurs": "Tottenham"} + home_team_kaggle = name_map_kaggle.get(home_team_input, home_team_input) + away_team_kaggle = name_map_kaggle.get(away_team_input, away_team_input) + + def avg_pass_perc(team_name, passing_stats_df): + # Standarisasi nama tim agar sesuai dengan data passing + name_map_pass = {"Man Utd": "Manchester United", "Man City": "Manchester City", "Spurs": "Tottenham Hotspur"} + team_name_pass = name_map_pass.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(team_name_pass, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team_input, passing_df) + away_pass = avg_pass_perc(away_team_input, passing_df) + + # Gunakan nama yang sudah distandarisasi untuk H2H + h2h_stats = calculate_h2h_stats(home_team_kaggle, away_team_kaggle, historical_df) + h2h_home, h2h_away, h2h_draw = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + + print(f" - Rekor H2H dari dataset: {home_team_kaggle} Menang ({h2h_home}), {away_team_kaggle} Menang ({h2h_away}), Seri ({h2h_draw})") + + input_features = np.array([[home_pass, away_pass, h2h_home, h2h_away, h2h_draw]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) +predict_match("Chelsea", "Tottenham", passing_df, hist_df, model) \ No newline at end of file diff --git a/main/pl-predict_lightgbm.py b/main/pl-predict_lightgbm.py new file mode 100644 index 0000000000000000000000000000000000000000..e6c5aa74074404beee28a7faaa8d2c0f3ba96628 --- /dev/null +++ b/main/pl-predict_lightgbm.py @@ -0,0 +1,148 @@ +import pandas as pd +import numpy as np +import lightgbm as lgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# Bagian 1, 2, 3 (Memuat & Feature Engineering) - Tidak Berubah +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H (DENGAN PERBAIKAN SYNTAX) +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + # === INI BAGIAN YANG DIPERBAIKI === + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + # ================================== + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# Bagian 5, 6, 7 (Training, Prediksi, Contoh) - Tidak Berubah +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model LightGBM Classifier...") +model = lgb.LGBMClassifier( + n_estimators=1000, + learning_rate=0.01, + objective='multiclass', + random_state=42 +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model LightGBM...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) +predict_match("Man Utd", "Liverpool", hist_df, model, scaler) \ No newline at end of file diff --git a/main/pl-predict_smalldataset.py b/main/pl-predict_smalldataset.py new file mode 100644 index 0000000000000000000000000000000000000000..7eb379cc24ef29f992e77299607448736841d2f3 --- /dev/null +++ b/main/pl-predict_smalldataset.py @@ -0,0 +1,156 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ Memuat Data +# ================================ +print("๐Ÿ“Š Memuat data statistik passing pemain...") +try: + passing_df = pd.read_csv("csv/premier_league_player_passing.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'premier_league_player_passing.csv' tidak ditemukan.") + exit() + +print("\n๐Ÿ“Š Memuat data pertandingan historis...") +try: + hist_df = pd.read_csv("csv/historical_matches.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except (FileNotFoundError, pd.errors.EmptyDataError): + print("โŒ Gagal: File 'historical_matches.csv' tidak ditemukan atau kosong.") + print(" Pastikan Anda sudah menjalankan script 'create_historical_data.py' versi multi-season.") + exit() + + +# ================================ +# 2๏ธโƒฃ PERBAIKAN: Feature Engineering H2H Tanpa Kebocoran Data +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur H2H (tanpa kebocoran data)...") + +# Pastikan kolom Tanggal berformat datetime dan urutkan +try: + hist_df['Date'] = pd.to_datetime(hist_df['Date']) + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +except Exception as e: + print(f"โŒ Error memproses kolom tanggal: {e}") + print(" Pastikan format tanggal di CSV Anda benar (misal: YYYY-MM-DD).") + exit() + + +# Fungsi untuk menghitung statistik H2H (tidak berubah) +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[ + ((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | + ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team)) + ] + + home_team_wins, away_team_wins, draws = 0, 0, 0 + + if h2h_matches.empty: + return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_team_wins += 1 + else: away_team_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_team_wins += 1 + else: home_team_wins += 1 + else: + draws += 1 + + return {'h2h_home_wins': home_team_wins, 'h2h_away_wins': away_team_wins, 'h2h_draws': draws} + +# === INI BAGIAN PENTING PERBAIKANNYA === +# Kita akan loop melalui setiap pertandingan dan hanya menghitung H2H dari laga SEBELUMNYA. +h2h_results = [] +for index, row in hist_df.iterrows(): + # Ambil semua pertandingan yang terjadi SEBELUM tanggal pertandingan saat ini + past_matches = hist_df.iloc[:index] + # Hitung H2H hanya berdasarkan data masa lalu tersebut + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) + +# Gabungkan fitur-fitur baru ini ke dataframe utama +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results)], axis=1) +print("โœ… Fitur H2H yang benar berhasil ditambahkan.") + +# ================================ +# 3๏ธโƒฃ Preprocessing & Persiapan Model +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") + +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 + +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = ["HomePass%", "AwayPass%", "h2h_home_wins", "h2h_away_wins", "h2h_draws"] +X = hist_df[features] +y = hist_df["Result"] + +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("โœ… Data siap.") + +# ================================ +# 4๏ธโƒฃ Melatih & Mengevaluasi Model +# ================================ +print("\n๐Ÿง  Melatih model dengan fitur yang benar...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") + +print("\nโš–๏ธ Mengevaluasi akurasi model baru yang realistis...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + +# ================================ +# 5๏ธโƒฃ Fungsi Prediksi (Sudah Benar) +# ================================ +def predict_match(home_team, away_team, passing_df, historical_df, model): + print(f"\n๐Ÿ”ฎ Prediksi Pertandingan: {home_team} vs {away_team}") + + def avg_pass_perc(team_name, passing_stats_df): + name_map = {"Man City": "Manchester City", "Man Utd": "Manchester United", "Spurs": "Tottenham Hotspur"} + standard_name = name_map.get(team_name, team_name) + team_df = passing_stats_df[passing_stats_df["Squad"].str.contains(standard_name, case=False, na=False)] + if team_df.empty: return passing_stats_df["Total_Cmp%"].astype(float).mean() + return team_df["Total_Cmp%"].astype(float).mean() + + home_pass = avg_pass_perc(home_team, passing_df) + away_pass = avg_pass_perc(away_team, passing_df) + print(f" - Rata-rata Pass: {home_team} ({home_pass:.2f}%) vs {away_team} ({away_pass:.2f}%)") + + # Saat memprediksi laga baru, kita gunakan SEMUA data historis, ini sudah benar. + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_home_wins, h2h_away_wins, h2h_draws = h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws'] + print(f" - Rekor H2H: {home_team} Menang ({h2h_home_wins}), {away_team} Menang ({h2h_away_wins}), Seri ({h2h_draws})") + + input_features = np.array([[home_pass, away_pass, h2h_home_wins, h2h_away_wins, h2h_draws]]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + # ... (sisa kode sama) + +# ================================ +# 6๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Brentford", "Man City", passing_df, hist_df, model) +predict_match("Liverpool", "Everton", passing_df, hist_df, model) +predict_match("Arsenal", "Man Utd", passing_df, hist_df, model) +predict_match("Liverpool", "Man City", passing_df, hist_df, model) \ No newline at end of file diff --git a/main/pl-predict_with-xgboost.py b/main/pl-predict_with-xgboost.py new file mode 100644 index 0000000000000000000000000000000000000000..19bb01c0f5856cd6b9c1e153627f37e6c166bfc6 --- /dev/null +++ b/main/pl-predict_with-xgboost.py @@ -0,0 +1,163 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report +from sklearn.preprocessing import StandardScaler +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan."); exit() +print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") + +print("\nโœจ Menyesuaikan data dari Kaggle...") +relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY'] +hist_df = hist_df[relevant_cols] +hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget', 'HC': 'HomeCorners', 'AC': 'AwayCorners', 'HF': 'HomeFouls', 'AF': 'AwayFouls', 'HY': 'HomeYellowCards', 'AY': 'AwayYellowCards'}, inplace=True) +stats_cols = ['HomeGoals', 'AwayGoals', 'HomeShots', 'AwayShots', 'HomeShotsOnTarget', 'AwayShotsOnTarget', 'HomeCorners', 'AwayCorners', 'HomeFouls', 'AwayFouls', 'HomeYellowCards', 'AwayYellowCards'] +for col in stats_cols: + hist_df[col] = pd.to_numeric(hist_df[col], errors='coerce') +hist_df.dropna(subset=stats_cols, inplace=True) +hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') +hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) +print("โœ… Data berhasil dibersihkan dan disesuaikan.") + +# ================================ +# 3๏ธโƒฃ Feature Engineering 'Team Form' +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget'], 'CornersFor': row['HomeCorners'], 'FoulsCommitted': row['HomeFouls'], 'Yellows': row['HomeYellowCards']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget'], 'CornersFor': row['AwayCorners'], 'FoulsCommitted': row['AwayFouls'], 'Yellows': row['AwayYellowCards']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_L5', 'CornersFor': 'AvgCorners_L5', 'FoulsCommitted': 'AvgFouls_L5', 'Yellows': 'AvgYellows_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_L5', 'AvgCorners_L5', 'AvgFouls_L5', 'AvgYellows_L5']] +home_rolling_stats = rolling_stats.copy() +away_rolling_stats = rolling_stats.copy() +home_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5'] +away_rolling_stats.columns = ['Team', 'Date', 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5'] +hist_df = pd.merge(hist_df, home_rolling_stats, left_on=['Date', 'Home'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df = pd.merge(hist_df, away_rolling_stats, left_on=['Date', 'Away'], right_on=['Date', 'Team'], how='left').drop('Team', axis=1) +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' yang lebih lengkap berhasil dibuat.") + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: + home_wins += 1 + else: + away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: + away_wins += 1 + else: + home_wins += 1 + else: + draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi dengan MODEL YANG DI-TUNING +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 0 + else: return 2 +hist_df["Result"] = hist_df.apply(get_result, axis=1) + +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_Home_L5', 'AvgCorners_Home_L5', 'AvgFouls_Home_L5', 'AvgYellows_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_Away_L5', 'AvgCorners_Away_L5', 'AvgFouls_Away_L5', 'AvgYellows_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +scaler = StandardScaler() +X_train_scaled = scaler.fit_transform(X_train) +X_test_scaled = scaler.transform(X_test) +print("โœ… Fitur berhasil di-scale.") + +print("\n๐Ÿง  Melatih model XGBoost yang sudah di-Tuning...") +# === PERUBAHAN: Menambahkan Hyperparameter untuk Tuning === +model = xgb.XGBClassifier( + n_estimators=1000, # Tambah jumlah pohon untuk belajar lebih detail + learning_rate=0.01, # Perkecil learning rate agar belajar lebih hati-hati + max_depth=3, # Batasi kedalaman pohon agar tidak terlalu spesifik + subsample=0.8, # Gunakan 80% data per pohon untuk mencegah overfitting + colsample_bytree=0.8, # Gunakan 80% fitur per pohon + gamma=0.1, # Regularisasi untuk "menghukum" model yang terlalu kompleks + random_state=42, + objective='multi:softprob', + eval_metric='mlogloss', + use_label_encoder=False +) +model.fit(X_train_scaled, y_train) +print("โœ… Model berhasil dilatih.") +# ======================================================= + +print("\nโš–๏ธ Mengevaluasi akurasi model yang sudah di-Tuning...") +y_pred = model.predict(X_test_scaled) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Menang Tamu', 'Menang Tuan Rumah', 'Seri'])) + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model, scaler): + print("\n" + "="*40); print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}"); print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"}; home_team = name_map.get(home_team_input, home_team_input); away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1); away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: print(f" โš ๏ธ Peringatan: Data form tidak ditemukan."); return + form_values = [home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_L5'].values[0], home_form['AvgCorners_L5'].values[0], home_form['AvgFouls_L5'].values[0], home_form['AvgYellows_L5'].values[0], away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_L5'].values[0], away_form['AvgCorners_L5'].values[0], away_form['AvgFouls_L5'].values[0], away_form['AvgYellows_L5'].values[0]] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df); h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})"); print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[7]:.2f})"); print(f" - Form Corners (5 Laga): {home_team} ({form_values[4]:.2f}), {away_team} ({form_values[11]:.2f})") + input_features_raw = np.array([form_values + h2h_values]); input_features_scaled = scaler.transform(input_features_raw) + probs = model.predict_proba(input_features_scaled)[0]; p_away, p_home, p_draw = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:"); print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%"); print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%"); print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + + if p_home > max(p_draw, p_away): + conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): + conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: + conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model, scaler) +predict_match("Liverpool", "Man City", hist_df, model, scaler) +predict_match("Chelsea", "Tottenham", hist_df, model, scaler) \ No newline at end of file diff --git a/main/player_passing.py b/main/player_passing.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8c34e056f37bc3fa286b9bd967e9dab9f53d02 --- /dev/null +++ b/main/player_passing.py @@ -0,0 +1,60 @@ +import pandas as pd +import matplotlib.pyplot as plt + +# --- 1. Load data hasil scraping --- +df = pd.read_csv("premier_league_player_passing.csv") + +print("โœ… Data loaded successfully!") +print(df.head()) + +# --- 2. Bersihkan kolom angka (hapus simbol %, ubah ke float) --- +def clean_numeric(x): + if isinstance(x, str): + x = x.replace('%', '').replace(',', '').strip() + return pd.to_numeric(x, errors='coerce') + +df['Total_Cmp%'] = df['Total_Cmp%'].apply(clean_numeric) +df['Total_Att'] = df['Total_Att'].apply(clean_numeric) +df['Total_Cmp'] = df['Total_Cmp'].apply(clean_numeric) + +# --- 3. 10 pemain dengan akurasi passing tertinggi --- +top_accuracy = df.sort_values('Total_Cmp%', ascending=False).head(10)[['Player', 'Squad', 'Total_Cmp%']] +print("\n๐ŸŽฏ Top 10 Players by Passing Accuracy:") +print(top_accuracy) + +# --- 4. 10 pemain dengan jumlah umpan terbanyak --- +top_volume = df.sort_values('Total_Att', ascending=False).head(10)[['Player', 'Squad', 'Total_Att']] +print("\n๐Ÿ“ˆ Top 10 Players by Total Passes Attempted:") +print(top_volume) + +# --- 5. Rata-rata akurasi passing per klub --- +club_avg = df.groupby('Squad')['Total_Cmp%'].mean().sort_values(ascending=False).reset_index() +print("\n๐ŸŸ๏ธ Average Passing Accuracy per Team:") +print(club_avg.head(10)) + +# --- 6. Simpan hasil analisis --- +output_df = { + 'Top 10 Accuracy': top_accuracy, + 'Top 10 Volume': top_volume, + 'Team Average Accuracy': club_avg +} +with pd.ExcelWriter("premier_league_passing_analysis.xlsx") as writer: + top_accuracy.to_excel(writer, sheet_name='Top_Accuracy', index=False) + top_volume.to_excel(writer, sheet_name='Top_Volume', index=False) + club_avg.to_excel(writer, sheet_name='Team_Average', index=False) + +print("\n๐Ÿ’พ Analysis results saved to premier_league_passing_analysis.xlsx") + +# --- 7. Visualisasi: rata-rata akurasi passing antar tim --- +plt.figure(figsize=(10,6)) +top10_clubs = club_avg.head(10) +plt.barh(top10_clubs['Squad'], top10_clubs['Total_Cmp%'], color='skyblue') +plt.xlabel("Average Passing Accuracy (%)") +plt.ylabel("Club") +plt.title("Top 10 Premier League Teams by Passing Accuracy (2025/2026)") +plt.gca().invert_yaxis() # Biar ranking teratas di atas +plt.tight_layout() +plt.savefig("top10_passing_accuracy.png", dpi=300) +plt.show() + +print("๐Ÿ“Š Visualization saved as top10_passing_accuracy.png") diff --git a/main/predict-pl-match_otomatis.py b/main/predict-pl-match_otomatis.py new file mode 100644 index 0000000000000000000000000000000000000000..1a7bec06bf09d33a04f796175cad7cfff1ac2d3f --- /dev/null +++ b/main/predict-pl-match_otomatis.py @@ -0,0 +1,185 @@ +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report +import warnings + +warnings.filterwarnings("ignore", category=FutureWarning) +warnings.filterwarnings("ignore", category=UserWarning, module='sklearn') + +# ================================ +# 1๏ธโƒฃ & 2๏ธโƒฃ (Memuat & Menyesuaikan Data) +# ================================ +print("๐Ÿ“Š Memuat data pertandingan historis dari Kaggle...") +try: + hist_df = pd.read_csv("epl-training.csv") + print(f"โœ… Berhasil memuat data historis: {hist_df.shape[0]} pertandingan") +except FileNotFoundError: + print("โŒ Gagal: File 'epl-training.csv' tidak ditemukan.") + exit() + +print("\nโœจ Menyesuaikan data dari Kaggle...") +try: + relevant_cols = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] + hist_df = hist_df[relevant_cols] + hist_df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) + hist_df['Date'] = pd.to_datetime(hist_df['Date'], format='%d/%m/%Y') + hist_df = hist_df.sort_values(by='Date').reset_index(drop=True) + print("โœ… Nama kolom dan format tanggal berhasil disesuaikan.") +except KeyError: + print("โŒ Error: Kolom yang dibutuhkan tidak ditemukan di 'epl-training.csv'.") + exit() + +# ================================ +# 3๏ธโƒฃ FEATURE ENGINEERING TINGKAT LANJUT: "TEAM FORM" (VERSI PERBAIKAN) +# ================================ +print("\n๐Ÿš€ Membuat fitur 'Team Form' (Performa Terkini)...") + +team_stats = [] +for index, row in hist_df.iterrows(): + team_stats.append({'Date': row['Date'], 'Team': row['Home'], 'GoalsFor': row['HomeGoals'], 'GoalsAgainst': row['AwayGoals'], 'ShotsFor': row['HomeShots'], 'SOT_For': row['HomeShotsOnTarget']}) + team_stats.append({'Date': row['Date'], 'Team': row['Away'], 'GoalsFor': row['AwayGoals'], 'GoalsAgainst': row['HomeGoals'], 'ShotsFor': row['AwayShots'], 'SOT_For': row['AwayShotsOnTarget']}) +team_stats_df = pd.DataFrame(team_stats).sort_values(by=['Team', 'Date']) + +rolling_stats = team_stats_df.groupby('Team').rolling(window=5, on='Date').mean().reset_index() +rolling_stats.rename(columns={'GoalsFor': 'AvgGoalsFor_L5', 'GoalsAgainst': 'AvgGoalsAgainst_L5', 'ShotsFor': 'AvgShotsFor_L5', 'SOT_For': 'AvgSOT_For_L5'}, inplace=True) +rolling_stats = rolling_stats[['Team', 'Date', 'AvgGoalsFor_L5', 'AvgGoalsAgainst_L5', 'AvgShotsFor_L5', 'AvgSOT_For_L5']] + +# === INI BAGIAN YANG DIPERBAIKI === +# Kita akan merge dua kali dengan cara yang lebih aman untuk menghindari nama kolom yang bentrok +# Merge untuk Tim Tuan Rumah +hist_df = pd.merge( + hist_df, + rolling_stats, + left_on=['Date', 'Home'], + right_on=['Date', 'Team'], + how='left', + suffixes=('', '_Home') # Menambahkan suffix jika ada nama kolom yg sama +).rename(columns={ + 'AvgGoalsFor_L5': 'AvgGoalsFor_Home_L5', + 'AvgGoalsAgainst_L5': 'AvgGoalsAgainst_Home_L5', + 'AvgShotsFor_L5': 'AvgShotsFor_Home_L5', + 'AvgSOT_For_L5': 'AvgSOT_For_Home_L5' +}).drop('Team', axis=1) + +# Merge untuk Tim Tamu +hist_df = pd.merge( + hist_df, + rolling_stats, + left_on=['Date', 'Away'], + right_on=['Date', 'Team'], + how='left', + suffixes=('', '_Away') +).rename(columns={ + 'AvgGoalsFor_L5': 'AvgGoalsFor_Away_L5', + 'AvgGoalsAgainst_L5': 'AvgGoalsAgainst_Away_L5', + 'AvgShotsFor_L5': 'AvgShotsFor_Away_L5', + 'AvgSOT_For_L5': 'AvgSOT_For_Away_L5' +}).drop('Team', axis=1) +# ================================ + +hist_df.dropna(inplace=True) +print("โœ… Fitur 'Team Form' berhasil dibuat.") + + +# ================================ +# 4๏ธโƒฃ Feature Engineering H2H +# ================================ +print("\n๐Ÿ› ๏ธ Membuat fitur Head-to-Head (H2H)...") +def calculate_h2h_stats(home_team, away_team, past_matches_df): + h2h_matches = past_matches_df[((past_matches_df['Home'] == home_team) & (past_matches_df['Away'] == away_team)) | ((past_matches_df['Home'] == away_team) & (past_matches_df['Away'] == home_team))] + home_wins, away_wins, draws = 0, 0, 0 + if h2h_matches.empty: return {'h2h_home_wins': 0, 'h2h_away_wins': 0, 'h2h_draws': 0} + for _, row in h2h_matches.iterrows(): + if row['HomeGoals'] > row['AwayGoals']: + if row['Home'] == home_team: home_wins += 1 + else: away_wins += 1 + elif row['AwayGoals'] > row['HomeGoals']: + if row['Away'] == away_team: away_wins += 1 + else: home_wins += 1 + else: draws += 1 + return {'h2h_home_wins': home_wins, 'h2h_away_wins': away_wins, 'h2h_draws': draws} +h2h_results = [] +# Menggunakan .index untuk memastikan urutan benar setelah merge +for index in hist_df.index: + row = hist_df.loc[index] + past_matches = hist_df.loc[hist_df.index < index] + stats = calculate_h2h_stats(row['Home'], row['Away'], past_matches) + h2h_results.append(stats) +hist_df = pd.concat([hist_df, pd.DataFrame(h2h_results, index=hist_df.index)], axis=1) +print("โœ… Fitur H2H berhasil ditambahkan.") + + +# ================================ +# 5๏ธโƒฃ Training & Evaluasi Model dengan FITUR PRO +# ================================ +print("\nโš™๏ธ Mempersiapkan data untuk training model...") +def get_result(row): + if row["HomeGoals"] > row["AwayGoals"]: return 1 + elif row["HomeGoals"] < row["AwayGoals"]: return 2 + else: return 0 +hist_df["Result"] = hist_df.apply(get_result, axis=1) +features = [ + 'AvgGoalsFor_Home_L5', 'AvgGoalsAgainst_Home_L5', 'AvgShotsFor_Home_L5', 'AvgSOT_For_Home_L5', + 'AvgGoalsFor_Away_L5', 'AvgGoalsAgainst_Away_L5', 'AvgShotsFor_Away_L5', 'AvgSOT_For_Away_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws' +] +X = hist_df[features] +y = hist_df["Result"] +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) +print("\n๐Ÿง  Melatih model PRO RandomForestClassifier...") +model = RandomForestClassifier(n_estimators=200, random_state=42, class_weight='balanced') +model.fit(X_train, y_train) +print("โœ… Model berhasil dilatih.") +print("\nโš–๏ธ Mengevaluasi akurasi model PRO...") +y_pred = model.predict(X_test) +acc = accuracy_score(y_test, y_pred) +print(f"๐ŸŽฏ Akurasi Model Secara Keseluruhan: {acc*100:.2f}%") +print("\nLaporan Detail Kinerja Model:\n") +print(classification_report(y_test, y_pred, target_names=['Seri', 'Menang Tuan Rumah', 'Menang Tamu'])) + + +# ================================ +# 6๏ธโƒฃ Fungsi Prediksi yang Menggunakan "Team Form" +# ================================ +def predict_match(home_team_input, away_team_input, historical_df, model): + print("\n" + "="*40) + print(f"๐Ÿ”ฎ PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Chealsea": "Chelsea"} + home_team = name_map.get(home_team_input, home_team_input) + away_team = name_map.get(away_team_input, away_team_input) + home_form = rolling_stats[rolling_stats['Team'] == home_team].tail(1) + away_form = rolling_stats[rolling_stats['Team'] == away_team].tail(1) + if home_form.empty or away_form.empty: + print(f" โš ๏ธ Peringatan: Data form tidak ditemukan untuk salah satu tim.") + return + form_values = [ + home_form['AvgGoalsFor_L5'].values[0], home_form['AvgGoalsAgainst_L5'].values[0], + home_form['AvgShotsFor_L5'].values[0], home_form['AvgSOT_For_L5'].values[0], + away_form['AvgGoalsFor_L5'].values[0], away_form['AvgGoalsAgainst_L5'].values[0], + away_form['AvgShotsFor_L5'].values[0], away_form['AvgSOT_For_L5'].values[0] + ] + h2h_stats = calculate_h2h_stats(home_team, away_team, historical_df) + h2h_values = [h2h_stats['h2h_home_wins'], h2h_stats['h2h_away_wins'], h2h_stats['h2h_draws']] + print(f" - Rekor H2H: {home_team} Menang ({h2h_values[0]}), {away_team} Menang ({h2h_values[1]}), Seri ({h2h_values[2]})") + print(f" - Form Gol (5 Laga): {home_team} ({form_values[0]:.2f}), {away_team} ({form_values[4]:.2f})") + input_features = np.array([form_values + h2h_values]) + probs = model.predict_proba(input_features)[0] + p_draw, p_home, p_away = probs[0], probs[1], probs[2] + print("\n๐Ÿ“ˆ Probabilitas Hasil:") + print(f" - ๐Ÿ  {home_team_input} Menang: {p_home*100:.2f}%") + print(f" - ๐Ÿš— {away_team_input} Menang: {p_away*100:.2f}%") + print(f" - ๐Ÿค Seri : {p_draw*100:.2f}%") + if p_home > max(p_draw, p_away): conclusion = f"{home_team_input} kemungkinan besar akan menang." + elif p_away > max(p_home, p_draw): conclusion = f"{away_team_input} kemungkinan besar akan menang." + else: conclusion = "Pertandingan ini kemungkinan besar akan berakhir seri." + print(f"\n๐Ÿ’ก Kesimpulan: {conclusion}") + +# ================================ +# 7๏ธโƒฃ Jalankan Contoh Prediksi +# ================================ +predict_match("Arsenal", "Man Utd", hist_df, model) +predict_match("Liverpool", "Man City", hist_df, model) +predict_match("Chelsea", "Tottenham", hist_df, model) \ No newline at end of file diff --git a/main/predict-pl-match_otomatis_v2.py b/main/predict-pl-match_otomatis_v2.py new file mode 100644 index 0000000000000000000000000000000000000000..5fdc2d027f9ac4c770c4052faec4122501df2bcf --- /dev/null +++ b/main/predict-pl-match_otomatis_v2.py @@ -0,0 +1,213 @@ +import pandas as pd +import numpy as np +import os +import warnings +from datetime import timedelta +import joblib + +warnings.filterwarnings("ignore") + +# Model & util +from sklearn.model_selection import TimeSeriesSplit +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, balanced_accuracy_score, log_loss +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + +try: + from xgboost import XGBClassifier + HAS_XGB = True +except ImportError: + HAS_XGB = False + +# ============================================================================= +# BAGIAN 1, 2, 3, 4, 5 (LOAD DATA & FEATURE ENGINEERING) - TIDAK BERUBAH +# ============================================================================= +print("๐Ÿ“Š Memuat data...") +df = pd.read_csv("csv/epl-training.csv", encoding='ISO-8859-1', low_memory=False) +expected = ['Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'HS', 'AS', 'HST', 'AST'] +df = df[expected].copy() +df.rename(columns={'HomeTeam': 'Home', 'AwayTeam': 'Away', 'FTHG': 'HomeGoals', 'FTAG': 'AwayGoals', 'HS': 'HomeShots', 'AS': 'AwayShots', 'HST': 'HomeShotsOnTarget', 'AST': 'AwayShotsOnTarget'}, inplace=True) +df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce') +df = df.sort_values('Date').reset_index(drop=True) +print(f"โœ… Data dimuat: {df.shape[0]} baris, dari {df['Date'].min().date()} sampai {df['Date'].max().date()}") + +print("๐Ÿ”ง Membuat fitur dasar (Result)...") +def result_label(row): + if row['HomeGoals'] > row['AwayGoals']: return 1 + if row['HomeGoals'] < row['AwayGoals']: return 2 + return 0 +df['Result'] = df.apply(result_label, axis=1) + +print("๐Ÿ“ˆ Menghitung rolling stats per tim (window=5)...") +team_rows = [] +for _, r in df.iterrows(): + team_rows.append({'Date': r['Date'], 'Team': r['Home'], 'GoalsFor': r['HomeGoals'], 'GoalsAgainst': r['AwayGoals'], 'ShotsFor': r['HomeShots'], 'SOT_For': r['HomeShotsOnTarget'], 'Win': 1 if r['HomeGoals']>r['AwayGoals'] else 0, 'Draw': 1 if r['HomeGoals']==r['AwayGoals'] else 0}) + team_rows.append({'Date': r['Date'], 'Team': r['Away'], 'GoalsFor': r['AwayGoals'], 'GoalsAgainst': r['HomeGoals'], 'ShotsFor': r['AwayShots'], 'SOT_For': r['AwayShotsOnTarget'], 'Win': 1 if r['AwayGoals']>r['HomeGoals'] else 0, 'Draw': 1 if r['AwayGoals']==r['HomeGoals'] else 0}) +team_df = pd.DataFrame(team_rows).sort_values(['Team', 'Date']).reset_index(drop=True) + +window = 5 +agg = team_df.groupby('Team').rolling(window=window, on='Date', min_periods=1).mean().reset_index().rename(columns={'GoalsFor': f'AvgGoalsFor_L{window}', 'GoalsAgainst': f'AvgGoalsAgainst_L{window}', 'ShotsFor': f'AvgShotsFor_L{window}', 'SOT_For': f'AvgSOTFor_L{window}', 'Win': f'WinRate_L{window}', 'Draw': f'DrawRate_L{window}'}) +rolling_stats = agg[['Team', 'Date', f'AvgGoalsFor_L{window}', f'AvgGoalsAgainst_L{window}', f'AvgShotsFor_L{window}', f'AvgSOTFor_L{window}', f'WinRate_L{window}', f'DrawRate_L{window}']] +df = pd.merge(df, rolling_stats, left_on=['Home','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Home_L{window}', f'WinRate_L{window}': f'WinRate_Home_L{window}', f'DrawRate_L{window}': f'DrawRate_Home_L{window}'}).drop(columns=['Team']) +df = pd.merge(df, rolling_stats, left_on=['Away','Date'], right_on=['Team','Date'], how='left').rename(columns={f'AvgGoalsFor_L{window}': f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_L{window}': f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_L{window}': f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_L{window}': f'AvgSOTFor_Away_L{window}', f'WinRate_L{window}': f'WinRate_Away_L{window}', f'DrawRate_L{window}': f'DrawRate_Away_L{window}'}).drop(columns=['Team']) + +print("๐Ÿ” Membuat fitur Last Result & Streak...") +def get_last_result(team, date, df_matches): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)] + if prev.empty: return 0 + row = prev.iloc[-1] + if (row['Home']==team and row['HomeGoals']>row['AwayGoals']) or (row['Away']==team and row['AwayGoals']>row['HomeGoals']): return 1 + if row['HomeGoals']==row['AwayGoals']: return 0 + return -1 + +def get_recent_streak(team, date, df_matches, lookback=5): + prev = df_matches[((df_matches['Home']==team)|(df_matches['Away']==team)) & (df_matches['Date'] < date)].tail(lookback) + if prev.empty: return 0 + streak = 0 + for i in range(len(prev)-1, -1, -1): + r = prev.iloc[i] + is_win = (r['Home']==team and r['HomeGoals']>r['AwayGoals']) or (r['Away']==team and r['AwayGoals']>r['HomeGoals']) + if is_win: streak += 1 + else: break + return streak + +df['HomeLastResult'] = df.apply(lambda r: get_last_result(r['Home'], r['Date'], df), axis=1) +df['AwayLastResult'] = df.apply(lambda r: get_last_result(r['Away'], r['Date'], df), axis=1) +df['HomeWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Home'], r['Date'], df, 5), axis=1) +df['AwayWinStreak_L5'] = df.apply(lambda r: get_recent_streak(r['Away'], r['Date'], df, 5), axis=1) + +print("โš”๏ธ Menghitung H2H sederhana...") +def calc_h2h_counts(home, away, date, df_matches): + prev = df_matches[((df_matches['Home']==home)&(df_matches['Away']==away))|((df_matches['Home']==away)&(df_matches['Away']==home))] + prev = prev[prev['Date'] < date] + if prev.empty: return 0,0,0 + home_wins = ((prev['Home']==home) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==home) & (prev['AwayGoals']>prev['HomeGoals'])) + away_wins = ((prev['Home']==away) & (prev['HomeGoals']>prev['AwayGoals'])) | ((prev['Away']==away) & (prev['AwayGoals']>prev['HomeGoals'])) + draws = prev['HomeGoals']==prev['AwayGoals'] + return int(home_wins.sum()), int(away_wins.sum()), int(draws.sum()) +h2h_home_wins, h2h_away_wins, h2h_draws = [], [], [] +for _, r in df.iterrows(): + a,b,c = calc_h2h_counts(r['Home'], r['Away'], r['Date'], df) + h2h_home_wins.append(a); h2h_away_wins.append(b); h2h_draws.append(c) +df['h2h_home_wins'] = h2h_home_wins; df['h2h_away_wins'] = h2h_away_wins; df['h2h_draws'] = h2h_draws + +# ============================================================================= +# 6. Final feature set & cleaning - DENGAN PERBAIKAN +# ============================================================================= +print("๐Ÿงน Membersihkan & menyiapkan fitur akhir...") +features = [ + f'AvgGoalsFor_Home_L{window}', f'AvgGoalsAgainst_Home_L{window}', f'AvgShotsFor_Home_L{window}', f'AvgSOTFor_Home_L{window}', + f'WinRate_Home_L{window}', f'DrawRate_Home_L{window}', 'HomeLastResult', 'HomeWinStreak_L5', + f'AvgGoalsFor_Away_L{window}', f'AvgGoalsAgainst_Away_L{window}', f'AvgShotsFor_Away_L{window}', f'AvgSOTFor_Away_L{window}', + f'WinRate_Away_L{window}', f'DrawRate_Away_L{window}', 'AwayLastResult', 'AwayWinStreak_L5', + 'h2h_home_wins', 'h2h_away_wins', 'h2h_draws', +] # === 'GoalDiff' DIHAPUS DARI SINI === + +df_model = df.dropna(subset=features + ['Result']).copy() +X = df_model[features] +y = df_model['Result'] +print(f"Dataset untuk modeling: {X.shape[0]} baris x {X.shape[1]} fitur") + +# ============================================================================= +# 7. Train / validation (TimeSeriesSplit) - Tidak Berubah +# ============================================================================= +print("๐Ÿงช Membuat TimeSeriesSplit untuk cross-validation temporal...") +tscv = TimeSeriesSplit(n_splits=5) +if HAS_XGB: + print("โšก XGBoost tersedia, menggunakan XGBClassifier.") + base_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42) +else: + print("โ„น๏ธ XGBoost tidak tersedia โ€” fallback ke RandomForestClassifier.") + base_model = RandomForestClassifier(random_state=42, class_weight='balanced') + +pipeline = Pipeline([('scaler', StandardScaler()), ('clf', base_model)]) +# Untuk mempercepat, kita lewati GridSearchCV dan langsung pakai parameter bagus +params = {'learning_rate': 0.05, 'max_depth': 4, 'n_estimators': 200, 'subsample': 0.8} +if HAS_XGB: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', XGBClassifier(**params, use_label_encoder=False, eval_metric='mlogloss', random_state=42))]) +else: + final_model = Pipeline([('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42, class_weight='balanced'))]) + +# ============================================================================= +# 8. Final evaluation on last fold hold-out - Tidak Berubah +# ============================================================================= +split_idx = int(len(df_model) * 0.8) +X_train_final, y_train_final = X.iloc[:split_idx], y.iloc[:split_idx] +X_test_final, y_test_final = X.iloc[split_idx:], y.iloc[split_idx:] + +print("๐Ÿงฉ Melatih model final pada 80% data awal dan evaluasi pada 20% terakhir...") +final_model.fit(X_train_final, y_train_final) +y_pred = final_model.predict(X_test_final) +y_proba = final_model.predict_proba(X_test_final) + +acc = accuracy_score(y_test_final, y_pred) +bal_acc = balanced_accuracy_score(y_test_final, y_pred) +ll = log_loss(y_test_final, y_proba) +print("\n๐Ÿ“Š Evaluasi Akhir (Hold-out temporal):") +print(f" - Accuracy : {acc*100:.2f}%") +print(f" - Balanced Acc. : {bal_acc*100:.2f}%") +print(f" - Log Loss : {ll:.4f}") +print("\nClassification Report:\n") +print(classification_report(y_test_final, y_pred, target_names=['Draw','Home','Away'])) +cm = confusion_matrix(y_test_final, y_pred) +print("Confusion Matrix (rows true, cols pred):\n", cm) + +# ============================================================================= +# 9. Predict function interactive - DENGAN PERBAIKAN +# ============================================================================= +print("\n๐Ÿ”ฎ Menyediakan fungsi predict_match (dinamis)...") +team_latest_stats = rolling_stats.sort_values('Date').groupby('Team').tail(1).set_index('Team') +full_match_history = df_model.copy() + +def predict_match(home_team_input, away_team_input, model=final_model, team_roll_df=team_latest_stats, history_df=full_match_history): + name_map = {"Man Utd": "Man United", "Spurs": "Tottenham", "Nottingham Forest": "Nott'm Forest"} + home = name_map.get(home_team_input, home_team_input) + away = name_map.get(away_team_input, away_team_input) + print("="*40) + print(f"PREDIKSI: {home_team_input} vs {away_team_input}") + print("="*40) + if home not in team_roll_df.index or away not in team_roll_df.index: + print("โš ๏ธ Data form/rolling tidak ditemukan untuk salah satu tim."); return None + + h = team_roll_df.loc[home] + a = team_roll_df.loc[away] + + # Hitung fitur dinamis untuk pertandingan "hari ini" + today = pd.to_datetime('today') + h_last_res = get_last_result(home, today, history_df) + a_last_res = get_last_result(away, today, history_df) + h_streak = get_recent_streak(home, today, history_df, 5) + a_streak = get_recent_streak(away, today, history_df, 5) + h2h_hw, h2h_aw, h2h_d = calc_h2h_counts(home, away, today, history_df) + + feat_vals = [ + h[f'AvgGoalsFor_L{window}'], h[f'AvgGoalsAgainst_L{window}'], h[f'AvgShotsFor_L{window}'], h[f'AvgSOTFor_L{window}'], + h[f'WinRate_L{window}'], h[f'DrawRate_L{window}'], h_last_res, h_streak, + a[f'AvgGoalsFor_L{window}'], a[f'AvgGoalsAgainst_L{window}'], a[f'AvgShotsFor_L{window}'], a[f'AvgSOTFor_L{window}'], + a[f'WinRate_L{window}'], a[f'DrawRate_L{window}'], a_last_res, a_streak, + h2h_hw, h2h_aw, h2h_d + ] + + X_input = pd.DataFrame([feat_vals], columns=features) + probs = model.predict_proba(X_input)[0] + + print(f"Info Form (Home): WinRate={h[f'WinRate_L{window}']:.2f}, AvgGoals={h[f'AvgGoalsFor_L{window}']:.2f}, LastResult={h_last_res}, WinStreak={h_streak}") + print(f"Info Form (Away): WinRate={a[f'WinRate_L{window}']:.2f}, AvgGoals={a[f'AvgGoalsFor_L{window}']:.2f}, LastResult={a_last_res}, WinStreak={a_streak}") + print(f"Info H2H: {home} menang {h2h_hw}, {away} menang {h2h_aw}, seri {h2h_d}") + + print(f"\nProbabilities -> Draw: {probs[0]*100:.2f}%, Home: {probs[1]*100:.2f}%, Away: {probs[2]*100:.2f}%") + pred_class = np.argmax(probs) + label_map = {0: "Seri", 1: home_team_input + " Menang", 2: away_team_input + " Menang"} + print("Kesimpulan:", label_map[pred_class]) + return probs + +# contoh prediksi +try: + predict_match("Aston Villa", "Burnley") + predict_match("Wolves", "Brighton") +except Exception as e: + print(f"Info: contoh prediksi gagal - {e}") + +joblib.dump(final_model, "model_epl_final.joblib") +print("\n๐Ÿ’พ Model tersimpan sebagai 'model_epl_final.joblib'") \ No newline at end of file diff --git a/outputs/feature_columns_v3_1.joblib b/outputs/feature_columns_v3_1.joblib new file mode 100644 index 0000000000000000000000000000000000000000..673025a15caf7befceed17ead97006ab0d1c9e12 --- /dev/null +++ b/outputs/feature_columns_v3_1.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc01e452e96aee60811ce24c3c5059b2debe88fbeac85db23336ad7cfbe60cb6 +size 240 diff --git a/outputs/labelencoder_v1.joblib b/outputs/labelencoder_v1.joblib new file mode 100644 index 0000000000000000000000000000000000000000..9759c6a7a71eda31922f85eb821d031b5f81eae3 --- /dev/null +++ b/outputs/labelencoder_v1.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72fb2ad27b86ec8bd562bcd3bf2bf3cf4deee31ddfef8ab0eefaf2d4175741dd +size 487 diff --git a/outputs/labelencoder_v3.joblib b/outputs/labelencoder_v3.joblib new file mode 100644 index 0000000000000000000000000000000000000000..9759c6a7a71eda31922f85eb821d031b5f81eae3 --- /dev/null +++ b/outputs/labelencoder_v3.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72fb2ad27b86ec8bd562bcd3bf2bf3cf4deee31ddfef8ab0eefaf2d4175741dd +size 487 diff --git a/outputs/labelencoder_v3_1.joblib b/outputs/labelencoder_v3_1.joblib new file mode 100644 index 0000000000000000000000000000000000000000..9759c6a7a71eda31922f85eb821d031b5f81eae3 --- /dev/null +++ b/outputs/labelencoder_v3_1.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72fb2ad27b86ec8bd562bcd3bf2bf3cf4deee31ddfef8ab0eefaf2d4175741dd +size 487 diff --git a/outputs/model_epl_best.joblib b/outputs/model_epl_best.joblib new file mode 100644 index 0000000000000000000000000000000000000000..5c9e8be5675dad1276dc83c1e8dc1d74099cd19f --- /dev/null +++ b/outputs/model_epl_best.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19a4321f64510ea6298df726d93c86e95bc27079432ce26b141b9bc6fbe0c2d7 +size 472174 diff --git a/outputs/model_epl_final.joblib b/outputs/model_epl_final.joblib new file mode 100644 index 0000000000000000000000000000000000000000..3c578bbbd22902ec4b33fb0757ec6029ad14a404 --- /dev/null +++ b/outputs/model_epl_final.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2c54cc99c0a0c2a9d1a33f87c3f7b0c99a0db7f90824412c09ce346af13fd7a +size 789719 diff --git a/outputs/model_epl_final2.joblib b/outputs/model_epl_final2.joblib new file mode 100644 index 0000000000000000000000000000000000000000..3c578bbbd22902ec4b33fb0757ec6029ad14a404 --- /dev/null +++ b/outputs/model_epl_final2.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2c54cc99c0a0c2a9d1a33f87c3f7b0c99a0db7f90824412c09ce346af13fd7a +size 789719 diff --git a/outputs/model_epl_final_v3.joblib b/outputs/model_epl_final_v3.joblib new file mode 100644 index 0000000000000000000000000000000000000000..ae7144e939c65277c3e221c3c73a4f287c58a046 --- /dev/null +++ b/outputs/model_epl_final_v3.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ad0d7d1b01e63f00862e5f988da32def8ad0d699a13311819a830e9fb763e66 +size 5543223 diff --git a/outputs/model_epl_finalv4.joblib b/outputs/model_epl_finalv4.joblib new file mode 100644 index 0000000000000000000000000000000000000000..3c578bbbd22902ec4b33fb0757ec6029ad14a404 --- /dev/null +++ b/outputs/model_epl_finalv4.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2c54cc99c0a0c2a9d1a33f87c3f7b0c99a0db7f90824412c09ce346af13fd7a +size 789719 diff --git a/outputs/model_epl_v1.joblib b/outputs/model_epl_v1.joblib new file mode 100644 index 0000000000000000000000000000000000000000..9c8ecef81e305fb037de409d3e14ff719e09a71d --- /dev/null +++ b/outputs/model_epl_v1.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec8a9170532a7569b76da73a1a47d984d26f0dcc7a626e7911e0dc40d8c89de5 +size 1062045 diff --git a/outputs/model_epl_v3.joblib b/outputs/model_epl_v3.joblib new file mode 100644 index 0000000000000000000000000000000000000000..6291d68b30508972a6bf3bd913528c5f54fe0a4c --- /dev/null +++ b/outputs/model_epl_v3.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa5129043dd441ce3e46c2a9564b1225bfd3926c7fc9d4d530053be87b341d8c +size 347820 diff --git a/outputs/model_epl_v3_1.joblib b/outputs/model_epl_v3_1.joblib new file mode 100644 index 0000000000000000000000000000000000000000..fc7d4c5d7483bb472b15ca351a068d84bbad06c5 --- /dev/null +++ b/outputs/model_epl_v3_1.joblib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:036e021472a247ab83573df285e304e02473b65eedec8eee99c13bd209a1ff45 +size 352349 diff --git a/outputs/requirements.txt b/outputs/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f4eab063898374e8fef29badaa60aa466b57c77 --- /dev/null +++ b/outputs/requirements.txt @@ -0,0 +1,8 @@ +selenium +webdriver-manager +pandas +numpy +scikit-learn +joblib +lxml +xgboost \ No newline at end of file diff --git a/visual/Figure_1.png b/visual/Figure_1.png new file mode 100644 index 0000000000000000000000000000000000000000..e4cbc452694ad4e9a0d3b4a8dcf72961940e2e45 Binary files /dev/null and b/visual/Figure_1.png differ diff --git a/visual/Figure_2.png b/visual/Figure_2.png new file mode 100644 index 0000000000000000000000000000000000000000..3b55ce2938ca6744491cafa08cee16dddca902ea Binary files /dev/null and b/visual/Figure_2.png differ diff --git a/visual/debug_screenshot.png b/visual/debug_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..fe1b33031c75bd47447f0e567c93f9578641a7d6 --- /dev/null +++ b/visual/debug_screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f895cb044ed42d9a1a43f6b7f89bec22cdfd8923d4434f14498b960e6f8c2f6 +size 150466 diff --git a/visual/debug_team_stats.png b/visual/debug_team_stats.png new file mode 100644 index 0000000000000000000000000000000000000000..995a59b8a0676ded5d955cc2a7f92a239a1c4370 --- /dev/null +++ b/visual/debug_team_stats.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a88f63abfd85682b628579253b3a47d68aac0914e47f604a48755ccb43f289bb +size 391521 diff --git a/visual/feature_importance.png b/visual/feature_importance.png new file mode 100644 index 0000000000000000000000000000000000000000..3b55ce2938ca6744491cafa08cee16dddca902ea Binary files /dev/null and b/visual/feature_importance.png differ diff --git a/visual/top10_passing_accuracy.png b/visual/top10_passing_accuracy.png new file mode 100644 index 0000000000000000000000000000000000000000..b5ce7543eae85ec3c0979057993171d51e3a6fa0 --- /dev/null +++ b/visual/top10_passing_accuracy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ebe8866071718c7f3645675d674eb86e61cc8bb34d1bc28ad3095ea6d7f97c6 +size 144417