diff --git a/data/clasificacion2026.csv b/data/clasificacion2026.csv new file mode 100644 index 0000000..5d1b012 --- /dev/null +++ b/data/clasificacion2026.csv @@ -0,0 +1,21 @@ +posicion,equipo,PJ,PG,PE,PP,GF,GC,DG,Pts,Racha1,Racha2,Racha3,Racha4,Racha5 +1,Barcelona,33,28,1,4,87,30,57,85,G,G,G,G,G +2,Real Madrid,33,23,5,5,68,31,37,74,G,P,E,G,E +3,Villarreal,33,20,5,8,59,38,21,65,G,P,G,E,G +4,Atletico Madrid,33,18,6,9,56,37,19,60,P,P,P,P,G +5,Real Betis,33,12,14,7,49,41,8,50,P,E,E,G,E +6,Getafe,33,13,5,15,28,34,-6,44,G,G,P,G,P +7,Celta Vigo,33,11,11,11,45,43,2,44,P,G,P,P,P +8,Real Sociedad,33,11,10,12,52,52,0,43,P,G,E,P,E +9,Osasuna,33,11,9,13,39,40,-1,42,G,E,E,P,G +10,Athletic Club,33,12,5,16,36,48,-12,41,G,P,P,G,P +11,Rayo Vallecano,33,9,12,12,33,41,-8,39,P,G,P,G,E +12,Valencia,33,10,9,14,37,48,-11,39,G,P,P,E,G +13,Espanyol,33,10,9,14,37,49,-12,39,P,E,P,P,E +14,Elche,33,9,11,13,44,50,-6,38,G,P,G,G,G +15,Girona,33,9,11,13,36,50,-14,38,P,G,E,P,P +16,Deportivo Alaves,33,9,9,15,38,49,-11,36,G,E,E,P,G +17,Mallorca,33,9,8,16,41,51,-10,35,P,G,G,E,P +18,Sevilla,33,9,7,17,40,55,-15,34,P,P,G,P,P +19,Levante,33,8,9,16,37,50,-13,33,G,P,G,G,E +20,Real Oviedo,33,6,10,17,26,51,-25,28,P,G,G,E,P diff --git a/data/espana b/data/espana new file mode 160000 index 0000000..98851d5 --- /dev/null +++ b/data/espana @@ -0,0 +1 @@ +Subproject commit 98851d5d630896c679facf4fa693e48462566575 diff --git a/data/estadisticas_por_equipo.csv b/data/estadisticas_por_equipo.csv new file mode 100644 index 0000000..4855db9 --- /dev/null +++ b/data/estadisticas_por_equipo.csv @@ -0,0 +1,21 @@ +equipo,goles_favor,goles_contra,disparos_puerta,faltas,amarillas,rojas +Real Oviedo,26,51,109,426,67,8 +Getafe CF,28,34,91,508,92,7 +Rayo Vallecano,33,41,156,434,85,9 +Girona FC,36,50,126,349,65,7 +Athletic Club,36,48,149,450,66,7 +Levante UD,37,50,116,420,75,4 +Valencia CF,37,48,105,412,64,2 +RCD Espanyol de Barcelona,37,49,134,455,74,5 +Deportivo Alavés,38,49,130,501,71,5 +CA Osasuna,39,40,126,445,80,6 +Sevilla FC,40,55,113,481,92,5 +RCD Mallorca,41,51,133,382,69,4 +Elche CF,44,50,130,426,63,6 +Celta,45,43,137,388,64,1 +Real Betis,49,41,155,351,67,1 +Real Sociedad,52,52,160,469,71,4 +Atlético de Madrid,56,37,172,371,67,4 +Villarreal CF,59,38,148,403,71,3 +Real Madrid,68,31,231,330,58,7 +FC Barcelona,87,30,233,309,50,2 diff --git a/data/prediccion20260430.txt b/data/prediccion20260430.txt new file mode 100644 index 0000000..3210e26 --- /dev/null +++ b/data/prediccion20260430.txt @@ -0,0 +1,19 @@ +Predicciones para la jornada (con features de clasificación y estadísticas): + local visitante ... dif_rojas prediccion +0 Villarreal Levante ... 0.00 1 +1 Valencia At. Madrid ... 0.00 2 +2 Alavés Athletic Club ... -2.15 1 +3 Osasuna Barcelona ... 0.00 2 +4 Celta Elche ... -3.85 1 +5 Getafe Rayo Vallecano ... -4.15 1 +6 Betis R. Oviedo ... 0.00 X +7 Espanyol Real Madrid ... -2.15 2 +8 Cultural Leonesa Cádiz ... 0.00 X +9 Castellón Córdoba ... 0.00 X +10 Eibar Málaga ... 0.00 X +11 Racing Santander Huesca ... 0.00 X +12 Sporting Ceuta ... 0.00 X +13 Las Palmas Valladolid ... 0.00 X +14 Sevilla Real Sociedad ... 0.85 2 + +[15 rows x 12 columns] diff --git a/main.py b/main.py new file mode 100644 index 0000000..0fe4639 --- /dev/null +++ b/main.py @@ -0,0 +1,11 @@ +# main.py +""" +Script principal para predecir quinielas de fútbol español. +""" + +def main(): + print("Bienvenido al predictor de quinielas de fútbol español.") + # Aquí se cargarán los datos, el modelo y se harán predicciones + +if __name__ == "__main__": + main() diff --git a/models/modelo_rf.pkl b/models/modelo_rf.pkl new file mode 100644 index 0000000..136590d Binary files /dev/null and b/models/modelo_rf.pkl differ diff --git a/partidos_jornada.txt b/partidos_jornada.txt new file mode 100644 index 0000000..ac1f4e6 --- /dev/null +++ b/partidos_jornada.txt @@ -0,0 +1,15 @@ +Villarreal - Levante +Valencia - At. Madrid +Alavés - Athletic Club +Osasuna - Barcelona +Celta - Elche +Getafe - Rayo Vallecano +Betis - R. Oviedo +Espanyol - Real Madrid +Cultural Leonesa - Cádiz +Castellón - Córdoba +Eibar - Málaga +Racing Santander - Huesca +Sporting - Ceuta +Las Palmas - Valladolid +Sevilla - Real Sociedad \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fad550e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pandas +requests +scikit-learn diff --git a/scripts/descargar_datos.py b/scripts/descargar_datos.py new file mode 100644 index 0000000..ba99ba4 --- /dev/null +++ b/scripts/descargar_datos.py @@ -0,0 +1,21 @@ +# descargar_datos.py +""" +Script para descargar datos históricos de quinielas de fútbol español. +""" +import os +import requests + +def descargar_datos(url, destino): + response = requests.get(url) + if response.status_code == 200: + with open(destino, 'wb') as f: + f.write(response.content) + print(f"Datos descargados en {destino}") + else: + print(f"Error al descargar datos: {response.status_code}") + +if __name__ == "__main__": + # Ejemplo de URL de datos históricos (reemplazar por una fuente válida) + url = "https://www.example.com/quinielas_historico.csv" + destino = os.path.join(os.path.dirname(__file__), '../data/quinielas_historico.csv') + descargar_datos(url, destino) diff --git a/scripts/entrenar_modelo.py b/scripts/entrenar_modelo.py new file mode 100644 index 0000000..6d834b3 --- /dev/null +++ b/scripts/entrenar_modelo.py @@ -0,0 +1,33 @@ +# entrenar_modelo.py +""" +Entrena y evalúa un modelo simple para predecir quinielas (1/X/2). +""" +import pandas as pd +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import classification_report, accuracy_score +import os + +# Cargar datos +DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/espana/partidos_todos.csv') +df = pd.read_csv(DATA_PATH) + +# Features simples: diferencia de goles históricos entre local y visitante +# (Para un modelo más avanzado, se pueden agregar más features) +df['dif_goles'] = df['goles_local'] - df['goles_visitante'] + +# Features y etiquetas +X = df[['dif_goles']] +y = df['resultado'] + +# Separar en train/test +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +# Modelo +clf = RandomForestClassifier(n_estimators=100, random_state=42) +clf.fit(X_train, y_train) + +# Predicción y evaluación +y_pred = clf.predict(X_test) +print("Accuracy:", accuracy_score(y_test, y_pred)) +print("\nClassification report:\n", classification_report(y_test, y_pred)) diff --git a/scripts/predecir_jornada.py b/scripts/predecir_jornada.py new file mode 100644 index 0000000..2f8e5a8 --- /dev/null +++ b/scripts/predecir_jornada.py @@ -0,0 +1,56 @@ +# predecir_jornada.py +""" +Predice los resultados de una jornada futura usando el modelo entrenado. +""" + +import pandas as pd +import os +import joblib +from sklearn.ensemble import RandomForestClassifier + +MODEL_PATH = os.path.join(os.path.dirname(__file__), '../models/modelo_rf.pkl') +DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/espana/partidos_todos.csv') +PARTIDOS_PATH = os.path.join(os.path.dirname(__file__), '../partidos_jornada.txt') + +# Cargar datos históricos +df_hist = pd.read_csv(DATA_PATH) + +# Calcular medias históricas de goles local y visitante +media_goles_local = df_hist.groupby('local')['goles_local'].mean().to_dict() +media_goles_visitante = df_hist.groupby('visitante')['goles_visitante'].mean().to_dict() + +# Cargar modelo entrenado o entrenar si no existe +if os.path.exists(MODEL_PATH): + clf = joblib.load(MODEL_PATH) +else: + df_hist['dif_goles'] = df_hist['goles_local'] - df_hist['goles_visitante'] + X = df_hist[['dif_goles']] + y = df_hist['resultado'] + clf = RandomForestClassifier(n_estimators=100, random_state=42) + clf.fit(X, y) + joblib.dump(clf, MODEL_PATH) + +# Leer partidos desde archivo +partidos = [] +if os.path.exists(PARTIDOS_PATH): + with open(PARTIDOS_PATH, 'r', encoding='utf-8') as f: + for linea in f: + linea = linea.strip() + if not linea or '-' not in linea: + continue + local, visitante = [x.strip() for x in linea.split('-', 1)] + goles_local = media_goles_local.get(local, df_hist['goles_local'].mean()) + goles_visitante = media_goles_visitante.get(visitante, df_hist['goles_visitante'].mean()) + dif_goles = goles_local - goles_visitante + partidos.append({'local': local, 'visitante': visitante, 'dif_goles': dif_goles}) +else: + print(f"No se encontró el archivo {PARTIDOS_PATH}") + +if partidos: + df_pred = pd.DataFrame(partidos) + predicciones = clf.predict(df_pred[['dif_goles']]) + df_pred['prediccion'] = predicciones + print("\nPredicciones para la jornada:") + print(df_pred[['local', 'visitante', 'prediccion']]) +else: + print("No se introdujeron partidos.") diff --git a/scripts/predecir_jornada_mejorado.py b/scripts/predecir_jornada_mejorado.py new file mode 100644 index 0000000..3448484 --- /dev/null +++ b/scripts/predecir_jornada_mejorado.py @@ -0,0 +1,83 @@ +# predecir_jornada_mejorado.py +""" +Predice resultados de la quiniela usando features de la clasificación actual. +""" +import pandas as pd +import os +import joblib +from sklearn.ensemble import RandomForestClassifier + +MODEL_PATH = os.path.join(os.path.dirname(__file__), '../models/modelo_rf.pkl') +DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/espana/partidos_todos.csv') +PARTIDOS_PATH = os.path.join(os.path.dirname(__file__), '../partidos_jornada.txt') +CLASIF_PATH = os.path.join(os.path.dirname(__file__), '../data/clasificacion2026.csv') + +# Cargar datos históricos y clasificación +clasif = pd.read_csv(CLASIF_PATH) +clasif.set_index('equipo', inplace=True) +df_hist = pd.read_csv(DATA_PATH) + +# Cargar modelo entrenado o entrenar si no existe +if os.path.exists(MODEL_PATH): + clf = joblib.load(MODEL_PATH) +else: + df_hist['dif_goles'] = df_hist['goles_local'] - df_hist['goles_visitante'] + X = df_hist[['dif_goles']] + y = df_hist['resultado'] + clf = RandomForestClassifier(n_estimators=100, random_state=42) + clf.fit(X, y) + joblib.dump(clf, MODEL_PATH) + +# Leer partidos desde archivo +def leer_partidos(path): + partidos = [] + with open(path, 'r', encoding='utf-8') as f: + for linea in f: + linea = linea.strip() + if not linea or '-' not in linea: + continue + local, visitante = [x.strip() for x in linea.split('-', 1)] + partidos.append({'local': local, 'visitante': visitante}) + return partidos + +partidos = leer_partidos(PARTIDOS_PATH) + +# Generar features usando la clasificación +features = [] +for p in partidos: + local = p['local'] + visitante = p['visitante'] + # Si el equipo no está en la clasificación, usar valores medios + if local in clasif.index: + pos_local = clasif.loc[local, 'posicion'] + pts_local = clasif.loc[local, 'Pts'] + dg_local = clasif.loc[local, 'DG'] + else: + pos_local = clasif['posicion'].mean() + pts_local = clasif['Pts'].mean() + dg_local = clasif['DG'].mean() + if visitante in clasif.index: + pos_visit = clasif.loc[visitante, 'posicion'] + pts_visit = clasif.loc[visitante, 'Pts'] + dg_visit = clasif.loc[visitante, 'DG'] + else: + pos_visit = clasif['posicion'].mean() + pts_visit = clasif['Pts'].mean() + dg_visit = clasif['DG'].mean() + features.append({ + 'local': local, + 'visitante': visitante, + 'dif_puntos': pts_local - pts_visit, + 'dif_posicion': pos_visit - pos_local, # positivo si local va mejor + 'dif_dg': dg_local - dg_visit + }) + +# Predecir usando el modelo (por ahora solo con dif_goles, pero mostramos features para extender) +df_pred = pd.DataFrame(features) +# Usar solo dif_puntos como feature para el modelo actual (puedes reentrenar el modelo con más features luego) +df_pred['dif_goles'] = df_pred['dif_puntos'] # placeholder para compatibilidad +predicciones = clf.predict(df_pred[['dif_goles']]) +df_pred['prediccion'] = predicciones + +print("Predicciones para la jornada (con features de clasificación):") +print(df_pred[['local', 'visitante', 'dif_puntos', 'dif_posicion', 'dif_dg', 'prediccion']]) diff --git a/scripts/predecir_jornada_stats.py b/scripts/predecir_jornada_stats.py new file mode 100644 index 0000000..d9436c4 --- /dev/null +++ b/scripts/predecir_jornada_stats.py @@ -0,0 +1,94 @@ +# predecir_jornada_stats.py +""" +Predice resultados de la quiniela usando features de la clasificación y estadísticas por equipo. +""" +import pandas as pd +import os +import joblib +from sklearn.ensemble import RandomForestClassifier + +MODEL_PATH = os.path.join(os.path.dirname(__file__), '../models/modelo_rf.pkl') +DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/espana/partidos_todos.csv') +PARTIDOS_PATH = os.path.join(os.path.dirname(__file__), '../partidos_jornada.txt') +CLASIF_PATH = os.path.join(os.path.dirname(__file__), '../data/clasificacion2026.csv') +STATS_PATH = os.path.join(os.path.dirname(__file__), '../data/estadisticas_por_equipo.csv') + +# Cargar datos +clasif = pd.read_csv(CLASIF_PATH) +clasif.set_index('equipo', inplace=True) +stats = pd.read_csv(STATS_PATH) +stats.set_index('equipo', inplace=True) +df_hist = pd.read_csv(DATA_PATH) + +# Cargar modelo entrenado o entrenar si no existe +if os.path.exists(MODEL_PATH): + clf = joblib.load(MODEL_PATH) +else: + df_hist['dif_goles'] = df_hist['goles_local'] - df_hist['goles_visitante'] + X = df_hist[['dif_goles']] + y = df_hist['resultado'] + clf = RandomForestClassifier(n_estimators=100, random_state=42) + clf.fit(X, y) + joblib.dump(clf, MODEL_PATH) + +# Leer partidos desde archivo +def leer_partidos(path): + partidos = [] + with open(path, 'r', encoding='utf-8') as f: + for linea in f: + linea = linea.strip() + if not linea or '-' not in linea: + continue + local, visitante = [x.strip() for x in linea.split('-', 1)] + partidos.append({'local': local, 'visitante': visitante}) + return partidos + +partidos = leer_partidos(PARTIDOS_PATH) + +# Generar features usando la clasificación y estadísticas +features = [] +for p in partidos: + local = p['local'] + visitante = p['visitante'] + # Clasificación + pos_local = clasif.loc[local, 'posicion'] if local in clasif.index else clasif['posicion'].mean() + pos_visit = clasif.loc[visitante, 'posicion'] if visitante in clasif.index else clasif['posicion'].mean() + pts_local = clasif.loc[local, 'Pts'] if local in clasif.index else clasif['Pts'].mean() + pts_visit = clasif.loc[visitante, 'Pts'] if visitante in clasif.index else clasif['Pts'].mean() + dg_local = clasif.loc[local, 'DG'] if local in clasif.index else clasif['DG'].mean() + dg_visit = clasif.loc[visitante, 'DG'] if visitante in clasif.index else clasif['DG'].mean() + # Estadísticas + gf_local = stats.loc[local, 'goles_favor'] if local in stats.index else stats['goles_favor'].mean() + gf_visit = stats.loc[visitante, 'goles_favor'] if visitante in stats.index else stats['goles_favor'].mean() + gc_local = stats.loc[local, 'goles_contra'] if local in stats.index else stats['goles_contra'].mean() + gc_visit = stats.loc[visitante, 'goles_contra'] if visitante in stats.index else stats['goles_contra'].mean() + disparos_local = stats.loc[local, 'disparos_puerta'] if local in stats.index else stats['disparos_puerta'].mean() + disparos_visit = stats.loc[visitante, 'disparos_puerta'] if visitante in stats.index else stats['disparos_puerta'].mean() + faltas_local = stats.loc[local, 'faltas'] if local in stats.index else stats['faltas'].mean() + faltas_visit = stats.loc[visitante, 'faltas'] if visitante in stats.index else stats['faltas'].mean() + amarillas_local = stats.loc[local, 'amarillas'] if local in stats.index else stats['amarillas'].mean() + amarillas_visit = stats.loc[visitante, 'amarillas'] if visitante in stats.index else stats['amarillas'].mean() + rojas_local = stats.loc[local, 'rojas'] if local in stats.index else stats['rojas'].mean() + rojas_visit = stats.loc[visitante, 'rojas'] if visitante in stats.index else stats['rojas'].mean() + features.append({ + 'local': local, + 'visitante': visitante, + 'dif_puntos': pts_local - pts_visit, + 'dif_posicion': pos_visit - pos_local, + 'dif_dg': dg_local - dg_visit, + 'dif_gf': gf_local - gf_visit, + 'dif_gc': gc_local - gc_visit, + 'dif_disparos': disparos_local - disparos_visit, + 'dif_faltas': faltas_local - faltas_visit, + 'dif_amarillas': amarillas_local - amarillas_visit, + 'dif_rojas': rojas_local - rojas_visit + }) + +# Por compatibilidad con el modelo actual, usamos solo dif_puntos (puedes reentrenar el modelo con más features luego) +df_pred = pd.DataFrame(features) +df_pred['dif_goles'] = df_pred['dif_puntos'] +predicciones = clf.predict(df_pred[['dif_goles']]) +df_pred['prediccion'] = predicciones + +print("Predicciones para la jornada (con features de clasificación y estadísticas):") +print(df_pred[['local', 'visitante', 'dif_puntos', 'dif_posicion', 'dif_dg', 'dif_gf', 'dif_gc', 'dif_disparos', 'dif_faltas', 'dif_amarillas', 'dif_rojas', 'prediccion']]) diff --git a/scripts/procesar_txt_a_csv.py b/scripts/procesar_txt_a_csv.py new file mode 100644 index 0000000..57346d2 --- /dev/null +++ b/scripts/procesar_txt_a_csv.py @@ -0,0 +1,65 @@ +# procesar_txt_a_csv.py +""" +Convierte archivos de texto de resultados de fútbol a formato CSV. +""" +import os +import re +import csv +from glob import glob + +# Carpeta base de datos +BASE_DIR = os.path.join(os.path.dirname(__file__), '../data/espana') + +# Expresión regular para partidos +PARTIDO_REGEX = re.compile(r"\s*(\d{1,2}\.\d{2})?\s*([\w .ÁÉÍÓÚÑáéíóúñB]+)\s+v\s+([\w .ÁÉÍÓÚÑáéíóúñB]+)\s+(\d+)-(\d+)") + +# Expresión regular para fecha de jornada +FECHA_REGEX = re.compile(r"\s*(\d{1,2}\.\d{2})?\s*([A-Za-z]{3,9})\s*(\w+)?") + +def procesar_archivo_txt(ruta_txt, temporada, division, salida_csv): + with open(ruta_txt, 'r', encoding='utf-8') as f: + lines = f.readlines() + + jornada = None + fecha = None + partidos = [] + for line in lines: + line = line.strip() + if line.startswith('» Matchday'): + jornada = line.split()[-1] + elif re.match(r"\d{2}\.\d{2}", line) or line.startswith(('Sat', 'Sun', 'Mon', 'Fri', 'Thu', 'Wed', 'Tue')): + fecha = line + elif 'v' in line and '-' in line: + m = PARTIDO_REGEX.search(line) + if m: + equipo_local = m.group(2).strip() + equipo_visitante = m.group(3).strip() + goles_local = int(m.group(4)) + goles_visitante = int(m.group(5)) + if goles_local > goles_visitante: + resultado = '1' + elif goles_local < goles_visitante: + resultado = '2' + else: + resultado = 'X' + partidos.append([ + temporada, division, jornada, fecha, equipo_local, equipo_visitante, goles_local, goles_visitante, resultado + ]) + + with open(salida_csv, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['temporada', 'division', 'jornada', 'fecha', 'local', 'visitante', 'goles_local', 'goles_visitante', 'resultado']) + writer.writerows(partidos) + print(f"Procesado {ruta_txt} -> {salida_csv}") + +if __name__ == "__main__": + # Procesar todos los archivos txt de todas las temporadas y divisiones + for temporada in os.listdir(BASE_DIR): + temp_path = os.path.join(BASE_DIR, temporada) + if os.path.isdir(temp_path): + for archivo in os.listdir(temp_path): + if archivo.endswith('.txt'): + division = archivo.split('-')[1].replace('.txt', '') if '-' in archivo else '1' + ruta_txt = os.path.join(temp_path, archivo) + salida_csv = os.path.join(temp_path, archivo.replace('.txt', '.csv')) + procesar_archivo_txt(ruta_txt, temporada, division, salida_csv) diff --git a/scripts/unir_y_analizar_csv.py b/scripts/unir_y_analizar_csv.py new file mode 100644 index 0000000..02fe6e0 --- /dev/null +++ b/scripts/unir_y_analizar_csv.py @@ -0,0 +1,30 @@ +# unir_y_analizar_csv.py +""" +Une todos los CSV de partidos en uno solo y analiza la cantidad de datos. +""" +import os +import pandas as pd +from glob import glob + +BASE_DIR = os.path.join(os.path.dirname(__file__), '../data/espana') +SALIDA = os.path.join(BASE_DIR, 'partidos_todos.csv') + +# Buscar todos los CSV de partidos +csvs = glob(os.path.join(BASE_DIR, '*', '*.csv')) + +# Unir todos los CSV +dfs = [] +for csv_file in csvs: + df = pd.read_csv(csv_file) + dfs.append(df) + +df_total = pd.concat(dfs, ignore_index=True) +df_total.to_csv(SALIDA, index=False) + +# Análisis básico +total_partidos = len(df_total) +print(f"Total de partidos: {total_partidos}") +print("Primeras filas:") +print(df_total.head()) +print("Distribución de resultados:") +print(df_total['resultado'].value_counts())