{"id":859,"date":"2019-06-23T21:10:09","date_gmt":"2019-06-23T19:10:09","guid":{"rendered":"http:\/\/35.180.88.53\/?p=859"},"modified":"2021-04-05T10:19:25","modified_gmt":"2021-04-05T08:19:25","slug":"web-scraping-advanced-football-statistics","status":"publish","type":"post","link":"https:\/\/www.sergilehkyi.com\/es\/2019\/06\/web-scraping-advanced-football-statistics\/","title":{"rendered":"Web Scraping Advanced Football Statistics"},"content":{"rendered":"\n<p>\u00daltimamente estaba debatiendo sobre el rol de la suerte en el f\u00fatbol. O tal vez no es pura suerte, sino una habilidad? \u00bfLos equipos ganan sus ligas puramente en habilidad? O la importancia de la suerte es bastante grande? \u00bfQui\u00e9n tiene suerte y qui\u00e9n no? \u00bfMerec\u00eda ese equipo el descenso? Y muchos muchos mas.<\/p>\n\n\n\n<p>Pero como soy un tipo de datos, pens\u00e9, as\u00ed que obtengamos los datos y descubramos eso. Aunque, \u00bfc\u00f3mo se mide la suerte? \u00bfC\u00f3mo se mide la habilidad? No existe una m\u00e9trica \u00fanica como la de los juegos de computadora FIFA o PES. Tenemos que mirar el panorama general, en un dato a largo plazo con m\u00faltiples variables y teniendo en cuenta el contexto de cada juego jugado. Porque en algunos momentos el jugador de un equipo simplemente no tiene suficiente suerte para marcar el gol de la victoria en los \u00faltimos minutos despu\u00e9s de la dominaci\u00f3n total sobre el oponente y termina como ecualizador y ambos equipos obtienen 1 punto, aunque qued\u00f3 claro que el primero equipo mereci\u00f3 la victoria. Un equipo tuvo suerte, otro no. S\u00ed, en esta situaci\u00f3n es una suerte, porque un equipo hizo todo, cre\u00f3 suficientes momentos peligrosos, pero no anot\u00f3. Sucede. Y por eso nos encanta el f\u00fatbol. Porque todo puede pasar aqu\u00ed.<\/p>\n\n\n\n<p>Aunque no puedes medir la suerte, pero entiendes c\u00f3mo jug\u00f3 el equipo bas\u00e1ndose en una m\u00e9trica relativamente nueva en el f\u00fatbol: xG, o goles esperados.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p> xG \u2013 es una medida estad\u00edstica de la calidad de las oportunidades creadas y concedidas <\/p><\/blockquote>\n\n\n\n<p>Puede encontrar los datos con esta m\u00e9trica en <a href=\"https:\/\/understat.com\/\">understat.com<\/a>. Esta es la web que estoy a punto de raspar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Entendiendo los datos<\/h3>\n\n\n\n<p>Entonces, \u00bfqu\u00e9 diablos es xG y por qu\u00e9 es importante. Respuesta que podemos encontrar en la p\u00e1gina de inicio de <a href=\"https:\/\/understat.com\/\">understat.com<\/a>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p> Los goles esperados (xG) es la nueva m\u00e9trica del f\u00fatbol revolucionario, que le permite evaluar el rendimiento del equipo y del jugador.<\/p><p><br> En un juego de baja puntuaci\u00f3n como el f\u00fatbol, el puntuaci\u00f3n final del partido no proporciona una imagen clara del rendimiento.<\/p><p><br> Esta es la raz\u00f3n por la que cada vez m\u00e1s analistas deportivos recurren a modelos avanzados como xG, que es una medida estad\u00edstica de la calidad de las oportunidades creadas y concedidas.<\/p><p><br> Nuestro objetivo era crear el m\u00e9todo m\u00e1s preciso para la evaluaci\u00f3n de la calidad del disparo.<\/p><p><br> Para este caso, entrenamos algoritmos de predicci\u00f3n de redes neuronales con el gran conjunto de datos (&gt; 100,000 disparos, m\u00e1s de 10 par\u00e1metros para cada uno). <\/p><cite>understat.com<\/cite><\/blockquote>\n\n\n\n<p>Los investigadores entrenaron una red neuronal basada en situaciones que llevaron a los goles y ahora nos da una estimaci\u00f3n de cu\u00e1ntas oportunidades reales tuvo el equipo durante el partido. Porque puedes tener 25 tiros durante el juego, pero si todos son de larga distancia o de \u00e1ngulo bajo o demasiado d\u00e9biles, tiros m\u00e1s cortos y de baja calidad, no te llevar\u00e1n a la meta. Mientras que algunos \u201cexpertos\u201d que no vieron el juego dir\u00e1n que el equipo domin\u00f3, crearon muchas oportunidades bla-bla-bla-bla. La calidad de esas posibilidades importa. Y ah\u00ed es donde la m\u00e9trica xG se vuelve muy \u00fatil. Con esta m\u00e9trica, ahora entiendes que Messi crea goles en condiciones en las que es muy dif\u00edcil de marcar, o que el portero hace el \u201csave\u201d donde deber\u00eda haber sido el gol. Todas estas cosas se suman y vemos campeones que tienen jugadores h\u00e1biles y algo de suerte, y vemos perdedores que podr\u00edan tener buenos jugadores, pero no tienen suficiente suerte. Y mi intenci\u00f3n con este proyecto es comprender y presentar estos n\u00fameros para demostrar el rol de la suerte en el f\u00fatbol de hoy.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Vamos a empezar<\/h3>\n\n\n\n<p>Comenzamos importando las librer\u00edas que se utilizar\u00e1n en este proyecto:<\/p>\n\n\n\n<ul><li>numpy &#8211; paquete fundamental para la computaci\u00f3n cient\u00edfica con Python<\/li><li>pandas &#8211; librer\u00eda que proporciona alto rendimiento, estructuras de datos f\u00e1ciles de usar y los datos de herramientas de an\u00e1lisis<\/li><li>requests &#8211; es la \u00fanica librer\u00eda HTTP sin OMG para Python, segura para el consumo humano (me encanta esta l\u00ednea de documentos oficiales :D)<\/li><li>BeautifulSoup &#8211; una  librer\u00eda de Python para extraer datos de archivos HTML y XML.<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-preformatted\">import numpy as np <em># linear algebra<\/em>\nimport pandas as pd <em># data processing, CSV file I\/O (e.g. pd.read_csv)<\/em>\nimport requests\nfrom bs4 import BeautifulSoup<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">La investigaci\u00f3n y la estructura de datos del sitio web<\/h3>\n\n\n\n<p>En la p\u00e1gina de inicio podemos observar que el sitio tiene datos para 6 ligas europeas:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"769\" height=\"117\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/leagues.jpg\" alt=\"\" class=\"wp-image-855\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/leagues.jpg 769w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/leagues-300x46.jpg 300w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/leagues-768x117.jpg 768w\" sizes=\"(max-width: 769px) 100vw, 769px\" \/><figcaption>understat.com header menu<\/figcaption><\/figure>\n\n\n\n<ul><li>La Liga<\/li><li>EPL<\/li><li>BundesLiga<\/li><li>Serie A<\/li><li>Ligue 1<\/li><li>RFPL<\/li><\/ul>\n\n\n\n<p>Y tambi\u00e9n vemos que los datos recopilados comienzan a partir de la temporada 2014\/2015. Otra noci\u00f3n que hacemos es la estructura de la URL. Es \u2018<code>https:\/\/understat.com\/league'<\/code>&nbsp;+ \u2018<code>\/name_of_the_league<\/code>\u2018 + \u2018<code>\/year_start_of_the_season<\/code>\u2018<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"509\" height=\"299\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/seasons.jpg\" alt=\"\" class=\"wp-image-858\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/seasons.jpg 509w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/seasons-300x176.jpg 300w\" sizes=\"(max-width: 509px) 100vw, 509px\" \/><\/figure>\n\n\n\n<p>As\u00ed que creamos variables con estos datos para poder seleccionar cualquier temporada o liga.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><em># create urls for all seasons of all leagues<\/em>\nbase_url = 'https:\/\/understat.com\/league'\nleagues = ['La_liga', 'EPL', 'Bundesliga', 'Serie_A', 'Ligue_1', 'RFPL']\nseasons = ['2014', '2015', '2016', '2017', '2018']<\/pre>\n\n\n\n<p>El siguiente paso es comprender d\u00f3nde se encuentran los datos en la p\u00e1gina web. Para esto abrimos las Herramientas de desarrollo en Chrome, vaya a la pesta\u00f1a &#8220;Red&#8221;, busque el archivo con datos (en este caso, 2018) y verifique la pesta\u00f1a &#8220;Respuesta&#8221;. Esto es lo que obtendremos despu\u00e9s de ejecutar <em>requests.get(URL)<\/em>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"914\" height=\"509\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/requests_response_1.jpg\" alt=\"\" class=\"wp-image-856\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/requests_response_1.jpg 914w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/requests_response_1-300x167.jpg 300w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/requests_response_1-768x428.jpg 768w\" sizes=\"(max-width: 914px) 100vw, 914px\" \/><\/figure>\n\n\n\n<p>Despu\u00e9s de revisar el contenido de la p\u00e1gina web, encontramos que los datos se almacenan bajo la etiqueta &#8220;script&#8221; y est\u00e1n codificados en JSON. As\u00ed que tendremos que encontrar esta etiqueta, obtener JSON de ella y convertirla en la estructura de datos legible de Python.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"683\" height=\"144\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/requests_response_2.jpg\" alt=\"\" class=\"wp-image-857\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/requests_response_2.jpg 683w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/requests_response_2-300x63.jpg 300w\" sizes=\"(max-width: 683px) 100vw, 683px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-preformatted\"><em># Starting with latest data for Spanish league, because I'm a Barcelona fan<\/em>\nurl = base_url+'\/'+leagues[0]+'\/'+seasons[4]\nres = requests.get(url)\nsoup = BeautifulSoup(res.content, \"lxml\")\n\n<em># Based on the structure of the webpage, I found that data is in the JSON variable, under 'script' tags<\/em>\nscripts = soup.find_all('script')<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Trabajando con JSON <\/h3>\n\n\n\n<p>Descubrimos que los datos que nos interesan se almacenan en la variable teamsData, despu\u00e9s de crear una sopa de etiquetas html, se convierte en solo una string, por lo que encontramos ese texto y extraemos JSON de ella.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">import json\n\nstring_with_json_obj = ''\n\n<em># Find data for teams<\/em>\nfor el <strong>in<\/strong> scripts:\n    if 'teamsData' <strong>in<\/strong> str(el):\n      string_with_json_obj = str(el).strip()\n      \n<em># print(string_with_json_obj)<\/em>\n\n<em># strip unnecessary symbols and get only JSON data<\/em>\nind_start = string_with_json_obj.index(\"('\")+2\nind_end = string_with_json_obj.index(\"')\")\njson_data = string_with_json_obj[ind_start:ind_end]\n\njson_data = json_data.encode('utf8').decode('unicode_escape')<\/pre>\n\n\n\n<p>Una vez que hayamos obtenido nuestro JSON y lo hayamos limpiado, podemos convertirlo en el diccionario de Python y comprobar su aspecto (declaraci\u00f3n print comentada).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Entendiendo los datos con Python<\/h3>\n\n\n\n<p>Cuando comenzamos a investigar los datos, entendemos que este es un diccionario de diccionarios de 3 claves: <em><strong>id<\/strong><\/em>, <em><strong>t\u00edtulo<\/strong><\/em> e <em><strong>historia<\/strong><\/em>. La primera capa del diccionario usa tambi\u00e9n las <strong><em>ids<\/em><\/strong> como claves.<\/p>\n\n\n\n<p>Tambi\u00e9n de esto entendemos que <strong><em>history<\/em><\/strong> tiene datos con respecto a cada partido que el equipo jug\u00f3 en su propia liga (los juegos de la Copa de la Liga o de la Liga de Campeones no est\u00e1n incluidos).<\/p>\n\n\n\n<p>Podemos reunir los nombres de los equipos despu\u00e9s de revisar el diccionario de la primera capa.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><em># Get teams and their relevant ids and put them into separate dictionary<\/em>\nteams = {}\nfor id <strong>in<\/strong> data.keys():\n  teams[id] = data[id]['title']<\/pre>\n\n\n\n<p>El <strong><em>history<\/em><\/strong> es el conjunto de diccionarios donde las claves son nombres de m\u00e9tricas (nombres de columnas) y los valores son valores, a pesar de lo tautol\u00f3gico que es :D.<\/p>\n\n\n\n<p>Entendemos que los nombres de las columnas se repiten una y otra vez, por lo que los agregamos a una lista separada. Tambi\u00e9n verificando c\u00f3mo se ven los valores de la muestra.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><em># EDA to get a feeling of how the JSON is structured<\/em>\n<em># Column names are all the same, so we just use first element<\/em>\ncolumns = []\n<em># Check the sample of values per each column<\/em>\nvalues = []\nfor id <strong>in<\/strong> data.keys():\n  columns = list(data[id]['history'][0].keys())\n  values = list(data[id]['history'][0].values())\n  break<\/pre>\n\n\n\n<p>Despu\u00e9s de emitir algunas declaraciones impresas, encontramos que Sevilla tiene el id = 138, por lo que obtener todos los datos para que este equipo pueda reproducir los mismos pasos para todos los equipos de la liga.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sevilla_data = []\nfor row <strong>in<\/strong> data['138']['history']:\n  sevilla_data.append(list(row.values()))\n  \ndf = pd.DataFrame(sevilla_data, columns=columns)<\/pre>\n\n\n\n<p>Por el bien de dejar este art\u00edculo limpio, no agregar\u00e9 el contenido del DataFrame creado, pero al final encontrar\u00e1s enlaces a las notebooks IPython en Github y Kaggle con todos los c\u00f3digos y resultados. Aqu\u00ed solo muestras para el contexto.<\/p>\n\n\n\n<p>Entonces, wualya, felicidades! \u00a1Tenemos los datos de todos los partidos de Sevilla en la temporada 2018-2019 dentro de La Liga! Ahora queremos obtener esa informaci\u00f3n para todos los equipos espa\u00f1oles. \u00a1Vamos a dar vueltas por esas mordeduras beb\u00e9!<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><em># Getting data for all teams<\/em>\ndataframes = {}\nfor id, team <strong>in<\/strong> teams.items():\n  teams_data = []\n  for row <strong>in<\/strong> data[id]['history']:\n    teams_data.append(list(row.values()))\n    \n  df = pd.DataFrame(teams_data, columns=columns)\n  dataframes[team] = df\n  print('Added data for <strong>{}<\/strong>.'.format(team))<\/pre>\n\n\n\n<p>Despu\u00e9s de que este c\u00f3digo termine de ejecutarse, tenemos un diccionario de DataFrames donde key es el nombre del equipo y value es el DataFrame con todos los juegos de ese equipo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Manipulaciones para hacer datos como en la fuente original<\/h3>\n\n\n\n<p>Cuando observamos el contenido de DataFrame podemos observar que m\u00e9tricas como PPDA y OPPDA (ppda y ppda_allowed) se representan como cantidades totales de acciones de ataque \/ defensa, pero en la tabla original se muestran como coeficientes. \u00a1Vamos a arreglar eso!<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">for team, df <strong>in<\/strong> dataframes.items():\n  dataframes[team]['ppda_coef'] = dataframes[team]['ppda'].apply(lambda x: x['att']\/x['def'] if x['def'] != 0 else 0)\n  dataframes[team]['oppda_coef'] = dataframes[team]['ppda_allowed'].apply(lambda x: x['att']\/x['def'] if x['def'] != 0 else 0)<\/pre>\n\n\n\n<p>Ahora tenemos todos nuestros n\u00fameros, pero para cada juego. Lo que necesitamos son los totales para el equipo. Averig\u00fcemos las columnas que tenemos que resumir. Para esto volvemos a la tabla original en understat.com y encontramos que todas las m\u00e9tricas deben resumirse y que solo PPDA y OPPDA son medios al final.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cols_to_sum = ['xG', 'xGA', 'npxG', 'npxGA', 'deep', 'deep_allowed', 'scored', 'missed', 'xpts', 'wins', 'draws', 'loses', 'pts', 'npxGD']\ncols_to_mean = ['ppda_coef', 'oppda_coef']<\/pre>\n\n\n\n<p>Estamos listos para calcular nuestros totales y medios. Para esto hacemos un bucle a trav\u00e9s del diccionario de marcos de datos y llamamos a .sum() y .mean() M\u00e9todos de DataFrame que devuelven Series, por eso agregamos .transpose() a esas llamadas Ponemos estos nuevos DataFrames en una lista y luego los convertimos en un nuevo DataFrame <strong><em>full_stat<\/em><\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">frames = []\nfor team, df <strong>in<\/strong> dataframes.items():\n  sum_data = pd.DataFrame(df[cols_to_sum].sum()).transpose()\n  mean_data = pd.DataFrame(df[cols_to_mean].mean()).transpose()\n  final_df = sum_data.join(mean_data)\n  final_df['team'] = team\n  final_df['matches'] = len(df)\n  frames.append(final_df)\n  \nfull_stat = pd.concat(frames)<\/pre>\n\n\n\n<p>A continuaci\u00f3n, reorganizamos las columnas para una mejor legibilidad, ordenamos las filas seg\u00fan los puntos, restablecemos el \u00edndice y agregamos la columna &#8220;position&#8221;<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">full_stat = full_stat[['team', 'matches', 'wins', 'draws', 'loses', 'scored', 'missed', 'pts', 'xG', 'npxG', 'xGA', 'npxGA', 'npxGD', 'ppda_coef', 'oppda_coef', 'deep', 'deep_allowed', 'xpts']]\nfull_stat.sort_values('pts', ascending=False, inplace=True)\nfull_stat.reset_index(inplace=True, drop=True)\nfull_stat['position'] = range(1,len(full_stat)+1)<\/pre>\n\n\n\n<p>Tambi\u00e9n en la tabla original tenemos valores de diferencias entre m\u00e9tricas esperadas y reales. Vamos a agregar esos tambi\u00e9n.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">full_stat['xG_diff'] = full_stat['xG'] - full_stat['scored']\nfull_stat['xGA_diff'] = full_stat['xGA'] - full_stat['missed']\nfull_stat['xpts_diff'] = full_stat['xpts'] - full_stat['pts']<\/pre>\n\n\n\n<p>Convertir los flotantes a n\u00fameros enteros cuando sea apropiado.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cols_to_int = ['wins', 'draws', 'loses', 'scored', 'missed', 'pts', 'deep', 'deep_allowed']\nfull_stat[cols_to_int] = full_stat[cols_to_int].astype(int)<\/pre>\n\n\n\n<p>Prettifying la vista final de nuestra DataFrame<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">col_order = ['position','team', 'matches', 'wins', 'draws', 'loses', 'scored', 'missed', 'pts', 'xG', 'xG_diff', 'npxG', 'xGA', 'xGA_diff', 'npxGA', 'npxGD', 'ppda_coef', 'oppda_coef', 'deep', 'deep_allowed', 'xpts', 'xpts_diff']\nfull_stat = full_stat[col_order]\nfull_stat.columns = ['#', 'team', 'M', 'W', 'D', 'L', 'G', 'GA', 'PTS', 'xG', 'xG_diff', 'NPxG', 'xGA', 'xGA_diff', 'NPxGA', 'NPxGD', 'PPDA', 'OPPDA', 'DC', 'ODC', 'xPTS', 'xPTS_diff']\npd.options.display.float_format = '<strong>{:,.2f}<\/strong>'.format\nfull_stat.head(10)<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"314\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/collab-1024x314.jpg\" alt=\"\" class=\"wp-image-860\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/collab-1024x314.jpg 1024w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/collab-300x92.jpg 300w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/collab-768x236.jpg 768w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/collab.jpg 1262w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>Printscreen of Collaboratory output<\/figcaption><\/figure>\n\n\n\n<p>Tabla original:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"660\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/full_table-1024x660.jpg\" alt=\"\" class=\"wp-image-854\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/full_table-1024x660.jpg 1024w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/full_table-300x193.jpg 300w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/full_table-768x495.jpg 768w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/full_table.jpg 1283w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>Printscreen <a href=\"https:\/\/understat.com\/\">understat.com<\/a><\/figcaption><\/figure>\n\n\n\n<p>Ahora, cuando obtuvimos nuestros n\u00fameros para una temporada de una liga, podemos replicar el c\u00f3digo y ponerlo en el bucle para obtener todos los datos de todas las temporadas de todas las ligas. No pondr\u00e9 este c\u00f3digo aqu\u00ed, pero dejar\u00e9 un enlace a la soluci\u00f3n de raspado completa en <a href=\"https:\/\/github.com\/slehkyi\/notebooks-for-articles\/blob\/master\/web_scrapping_understatcom_for_xG_dataset.ipynb\">Github<\/a> y <a href=\"https:\/\/www.kaggle.com\/slehkyi\/web-scraping-football-statistics-2014-now?scriptVersionId=16171675\">Kaggle<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Dataset final<\/h3>\n\n\n\n<p>Despu\u00e9s de loop por todas las ligas y todas las temporadas y unos pocos pasos de manipulaci\u00f3n para hacer que los datos sean exportables, tengo un archivo CSV con n\u00fameros raspados. El conjunto de datos est\u00e1 disponible <a href=\"https:\/\/www.kaggle.com\/slehkyi\/extended-football-stats-for-european-leagues-xg\">aqu\u00ed.<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"216\" src=\"http:\/\/35.180.88.53\/wp-content\/uploads\/2019\/06\/banner-1024x216.jpg\" alt=\"\" class=\"wp-image-861\" srcset=\"https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/banner-1024x216.jpg 1024w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/banner-300x63.jpg 300w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/banner-768x162.jpg 768w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/banner-1600x337.jpg 1600w, https:\/\/www.sergilehkyi.com\/wp-content\/uploads\/2019\/06\/banner.jpg 1900w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Conclusi\u00f3n<\/h3>\n\n\n\n<p>Espero que lo hayas encontrado \u00fatil y tengas informaci\u00f3n valiosa. De todos modos, si llegaste a este punto solo quiero agradecerte por leer, por dedicar tu tiempo, energ\u00eda y atenci\u00f3n a mis 5 centavos y te deseo mucho amor y felicidad, \u00a1eres incre\u00edble!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"has-text-align-center\">Foto de&nbsp;<a href=\"https:\/\/unsplash.com\/@guoshiwushuang?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Michael Lee<\/a>&nbsp;en&nbsp;<a href=\"https:\/\/unsplash.com\/search\/photos\/soccer-stadium?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Unsplash<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u00daltimamente estaba debatiendo sobre el rol de la suerte en el f\u00fatbol. O tal vez no es pura suerte, sino&hellip;<\/p>\n","protected":false},"author":1,"featured_media":863,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,11],"tags":[],"translation":{"provider":"WPGlobus","version":"3.0.0","language":"es","enabled_languages":["gb","es","uk"],"languages":{"gb":{"title":true,"content":true,"excerpt":false},"es":{"title":true,"content":true,"excerpt":false},"uk":{"title":true,"content":true,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/859"}],"collection":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/comments?post=859"}],"version-history":[{"count":17,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/859\/revisions"}],"predecessor-version":[{"id":1184,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/859\/revisions\/1184"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/media\/863"}],"wp:attachment":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/media?parent=859"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/categories?post=859"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/tags?post=859"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}