{"id":180120,"date":"2024-09-16T23:54:06","date_gmt":"2024-09-16T23:54:06","guid":{"rendered":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/"},"modified":"2025-01-13T15:51:43","modified_gmt":"2025-01-13T15:51:43","slug":"football-and-geometry-passing-networks-6e201ceff6ef","status":"publish","type":"post","link":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/","title":{"rendered":"Football and Geometry &#8211; Passing Networks"},"content":{"rendered":"<h3 class=\"wp-block-heading\">Football Analytics<\/h3>\n<h3 class=\"wp-block-heading\">Understanding networks through the analysis of Bayer Leverkusen&#8217;s passing networks<\/h3>\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"665645\" data-has-transparency=\"false\" style=\"--dominant-color: #665645;\" loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1707\" src=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\" alt=\"Photo by Clint Adair on Unsplash\" class=\"wp-image-180121 not-transparent\" srcset=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg 2560w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-300x200.jpg 300w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-1024x683.jpg 1024w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-768x512.jpg 768w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-1536x1024.jpg 1536w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-2048x1365.jpg 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">Photo by <a href=\"https:\/\/unsplash.com\/@clintadair?utm_source=medium&amp;utm_medium=referral\">Clint Adair<\/a> on <a href=\"https:\/\/unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash<\/a><\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">Long time no see&#8230; But for a good reason.<\/p>\n<p class=\"wp-block-paragraph\">After some months I&#8217;m back on Medium and today we&#8217;re merging two exciting worlds: football and geometry.<\/p>\n<p class=\"wp-block-paragraph\">Concretely, we&#8217;ll touch upon the topic of networks but, as always, through a practical case. We&#8217;ll study football passing networks focusing on last year&#8217;s Bayer Leverkusen matches.<\/p>\n<p class=\"wp-block-paragraph\">The Bundesliga winners had an amazing season playing outstanding football under Xabi Alonso. I&#8217;m curious to investigate how that translates to mathematical terms and understand their playing style and most relevant players through their passing networks.<\/p>\n<p class=\"wp-block-paragraph\">While the importance of networks is already established to study interconnection between nodes, its application in football isn&#8217;t different from that. It&#8217;s basic stuff, in fact, but it&#8217;s worth dedicating a post for anyone who hasn&#8217;t seen one yet.<\/p>\n<p class=\"wp-block-paragraph\">Statsbomb[1] has high-quality data and, luckily for us, they made free and available for everyone all Bayer Leverkusen&#8217;s games from last season.<\/p>\n<p class=\"wp-block-paragraph\">Here&#8217;s what we will go through today:<\/p>\n<ol class=\"wp-block-list\">\n<li>Introduction to passing networks<\/li>\n<li>Building the network<\/li>\n<li>Metrics and Analysis<\/li>\n<li>Conclusion<\/li>\n<\/ol>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Before going on, I want to share that all the code in this post is mine but has been inspired by the amazing Soccermatics course David Sumpter created. You can find the URL to this extensive education in the Resources section at the end of this post[2].<\/p><\/blockquote>\n<h3 class=\"wp-block-heading\">Introduction to Passing Networks<\/h3>\n<p class=\"wp-block-paragraph\">Passing networks are graphical representations of how players interact with each other during a football match, visualizing the flow of passes between teammates. It contains two main elements:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Nodes &#8211;<\/strong> represent players and are located in the average position where the player either passed or was passed a ball.<\/li>\n<li><strong>Edges<\/strong> (or lines) &#8211; represent the passes being made between the two players (nodes) connected by that line.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">The thickness of the edges usually represents the frequency of passes and the size of the nodes the number of passes made by a player.<\/p>\n<p class=\"wp-block-paragraph\">This visual and analytical tool is increasingly used to assess team shape, player involvement, and tactical patterns in modern football. If we, as data scientists, use additional math to compute other metrics, we get even more advanced insights about the team&#8217;s passing characteristics.<\/p>\n<p class=\"wp-block-paragraph\">Here are some of the specific situations where passing networks can come in handy:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Understand Team Structure<\/strong>. As it contains the average positions where these players were during these passing events, we can get an idea of the team&#8217;s structure and, therefore, how they play. For example, a compact and dense network might indicate that the team prefers short passes and possession-based football, while a more spread-out network could suggest a direct or counter-attacking approach.<\/li>\n<li><strong>Identifying Key Players<\/strong>. A key metric in passing networks is <strong>centrality<\/strong>, which is used to assess whether the network relies mostly on a small subset of players or not. If we see, for example, Pirlo being key to Italy&#8217;s passing network, we might want to put a great defense on him the next time we play against the Italians.<\/li>\n<li><strong>Tactical Analysis<\/strong>. Coaches and analysts can spot tactical strengths and weaknesses. For instance, if a team&#8217;s passing network shows strong connections down the left side, it might indicate a reliance on attacking through that flank. Also, that can be used to compare different matches and analyze how it changed from one opponent to the other.<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\">Building the network<\/h3>\n<p class=\"wp-block-paragraph\">We&#8217;ll use Statsbomb&#8217;s free data and access it through <strong>statsbombpy<\/strong>&#8216;s[3] Python library. Let&#8217;s start by importing the library and retrieving all the events from the Bundesliga 2023\/24 season (as we&#8217;re using free data only, we&#8217;ll receive only info relative to Bayer Leverkusen&#8217;s matches):<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from collections import Counter\n\nfrom mplsoccer import Pitch\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom statsbombpy import sb\n\nevents_df = sb.competition_events(\n    country=&quot;Germany&quot;,\n    division=&quot;1. Bundesliga&quot;,\n    season=&quot;2023\/2024&quot;,\n    gender=&quot;male&quot;\n).sort_values([&#039;match_id&#039;, &#039;minute&#039;, &#039;second&#039;])<\/code><\/pre>\n<p class=\"wp-block-paragraph\">We now have a lot of events and columns in this data frame, so we should filter out unneeded rows. Additionally, we&#8217;re going to perform a little bit of feature engineering by splitting the <em>location<\/em> and _pass_end<em>location<\/em> column into two separate columns (one for the x and the other for the y-axis):<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">passes_df = events_df[\n    (events_df[&#039;type&#039;] == &#039;Pass&#039;) \n    &amp;amp; (events_df[&#039;team&#039;] == &#039;Bayer Leverkusen&#039;)\n    &amp;amp; (events_df[&#039;pass_outcome&#039;].isna()\n)].reset_index(drop=True)[[&#039;match_id&#039;, &#039;location&#039;, &#039;pass_end_location&#039;, &#039;player&#039;, &#039;pass_recipient&#039;]]\n\n# Define start and end positions\nini_locs_df = pd.DataFrame(passes_df[&quot;location&quot;].to_list(), columns=[&#039;x&#039;, &#039;y&#039;])\nend_locs_df = pd.DataFrame(passes_df[&quot;pass_end_location&quot;].to_list(), columns=[&#039;end_x&#039;, &#039;end_y&#039;])\npasses_df = pd.concat([passes_df, ini_locs_df, end_locs_df], axis=1)\n\n# Reshape and rename columns\npasses_df.drop(columns=[&#039;location&#039;, &#039;pass_end_location&#039;], inplace=True)\npasses_df.columns = [&#039;match_id&#039;, &#039;player_name&#039;, &#039;pass_recipient_name&#039;, &#039;x&#039;, &#039;y&#039;, &#039;end_x&#039;, &#039;end_y&#039;]<\/code><\/pre>\n<p class=\"wp-block-paragraph\">After keeping only the columns we&#8217;re interested in, some decisions should be made now. Bayer Leverkusen, like any other team, doesn&#8217;t only have 11 players in their squad. If we want to show a passing network, we only want to show 11 (as if it were a match lineup). But a decision has to be made on the method to exclude players.<\/p>\n<p class=\"wp-block-paragraph\">A good approach could have probably been to use the most-used team&#8217;s formation and choose, for each position, the player who&#8217;s played the most minutes in there. But that seemed too complex for this post and I decided to make it simple: use the most-used lineup (and I mean, the most-used 11-player set of players starting games).<\/p>\n<p class=\"wp-block-paragraph\">Here&#8217;s the code that handles this, previously transforming player names to only show surnames:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># Show surname only\npasses_df[&quot;player_name&quot;] = passes_df[&quot;player_name&quot;].apply(lambda x: str(x).split()[-1])\npasses_df[&quot;pass_recipient_name&quot;] = passes_df[&quot;pass_recipient_name&quot;].apply(lambda x: str(x).split()[-1])\npasses_df.loc[:, [&quot;player_name&quot;, &quot;pass_recipient_name&quot;]] = passes_df.loc[:, [&quot;player_name&quot;, &quot;pass_recipient_name&quot;]].replace(&#039;Garc\u00eda&#039;, &#039;Grimaldo&#039;)\n\n# Select most-used lineup and keep those players\nused_lineups = []\nfor match_id in passes_df.match_id.unique():\n    match_lineup = sb.lineups(\n        match_id=match_id\n    )[&#039;Bayer Leverkusen&#039;]\n\n    match_lineup[&#039;starter&#039;] = match_lineup[&#039;positions&#039;].apply(\n        lambda x: x[0][&#039;start_reason&#039;] == &#039;Starting XI&#039; if x else False\n    )\n\n    match_lineup[&quot;player_name&quot;] = match_lineup[&quot;player_name&quot;].apply(lambda x: str(x).split()[-1])\n    match_lineup.loc[:, &quot;player_name&quot;] = match_lineup.loc[:, &quot;player_name&quot;].replace(&#039;Garc\u00eda&#039;, &#039;Grimaldo&#039;)\n\n    starters = sorted(match_lineup[match_lineup[&#039;starter&#039;]==True].player_name.tolist())\n    used_lineups.append(starters)\n\nmost_used_lineup_players = Counter([&#039;, &#039;.join(c) for c in used_lineups]).most_common()[0][0].split(&quot;, &quot;)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">I ended up reducing the DF&#8217;s dimensionality:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># Show surname only\npasses_df[&quot;player_name&quot;] = passes_df[&quot;player_name&quot;].apply(lambda x: str(x).split()[-1])\npasses_df[&quot;pass_recipient_name&quot;] = passes_df[&quot;pass_recipient_name&quot;].apply(lambda x: str(x).split()[-1])\n\n# Manually correct Grimaldo&#039;s name\npasses_df.loc[:, [&quot;player_name&quot;, &quot;pass_recipient_name&quot;]] = passes_df.loc[:, [&quot;player_name&quot;, &quot;pass_recipient_name&quot;]].replace(&#039;Garc\u00eda&#039;, &#039;Grimaldo&#039;)\n\npasses_df = passes_df[[&#039;x&#039;, &#039;y&#039;, &#039;end_x&#039;, &#039;end_y&#039;, &quot;player_name&quot;, &quot;pass_recipient_name&quot;]]<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This is what the DF looks like at this point:<\/p>\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"efefef\" data-has-transparency=\"false\" style=\"--dominant-color: #efefef;\" loading=\"lazy\" decoding=\"async\" width=\"794\" height=\"300\" src=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1ECv4Q73oIZ56qbdW_i0Zuw.png\" alt=\"Top 5 rows in the passes_df data frame - image by the author\" class=\"wp-image-188972 not-transparent\" srcset=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1ECv4Q73oIZ56qbdW_i0Zuw.png 794w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1ECv4Q73oIZ56qbdW_i0Zuw-300x113.png 300w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1ECv4Q73oIZ56qbdW_i0Zuw-768x290.png 768w\" sizes=\"auto, (max-width: 794px) 100vw, 794px\" \/><figcaption class=\"wp-element-caption\">Top 5 rows in the passes_df data frame &#8211; image by the author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">We now need to get the number of passes per player and their location (node size and location) and the number of passes between pairs of players (edge thickness). Let&#8217;s start with the first one by creating a new DF:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># Create DF with average player positions\nnodes_df = pd.concat([\n    passes_df[[&quot;player_name&quot;, &#039;x&#039;, &#039;y&#039;]], \n    passes_df[[&quot;pass_recipient_name&quot;, &#039;end_x&#039;, &#039;end_y&#039;]].rename(columns={&#039;pass_recipient_name&#039;: &#039;player_name&#039;, &#039;end_x&#039;: &#039;x&#039;, &#039;end_y&#039;: &#039;y&#039;})\n]).groupby(&#039;player_name&#039;).mean().reset_index()\n\nnodes_df = nodes_df[nodes_df[&#039;player_name&#039;].isin(most_used_lineup_players)]\n\n# Add number of passes made\nnodes_df = nodes_df.merge(long_df.groupby(&#039;player_name&#039;).agg(passing_participation=(&#039;x&#039;, &#039;count&#039;)).reset_index())\n\n# Add marker_size column to have it relative to the number of passes made \nnodes_df[&#039;marker_size&#039;] = (nodes_df[&#039;passing_participation&#039;] \/ nodes_df[&#039;passing_participation&#039;].max() * 1500)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">We&#8217;re first creating a DF that contains only three columns (player_name, x, y) and then grouping by the player name to compute the average x and y. After that, we filter out those players not in the most common lineup and we merge the result of this operation with the passes_df previously grouped by player containing the total number of passes in which each player participated. This way, we end up having a DF with one row per player, his average pitch location, and the number of passes made.<\/p>\n<p class=\"wp-block-paragraph\">The last step is used to add the marker size, which will be used later, and this is how it looks like:<\/p>\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"ececec\" data-has-transparency=\"false\" style=\"--dominant-color: #ececec;\" loading=\"lazy\" decoding=\"async\" width=\"906\" height=\"290\" src=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1kB0oNT0EeuB_QZ0Map450w.png\" alt=\"Top 5 rows of nodes_df - image by the author\" class=\"wp-image-188973 not-transparent\" srcset=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1kB0oNT0EeuB_QZ0Map450w.png 906w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1kB0oNT0EeuB_QZ0Map450w-300x96.png 300w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1kB0oNT0EeuB_QZ0Map450w-768x246.png 768w\" sizes=\"auto, (max-width: 906px) 100vw, 906px\" \/><figcaption class=\"wp-element-caption\">Top 5 rows of nodes_df &#8211; image by the author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">Let&#8217;s keep on with the edges DF. We&#8217;ll need to create a new column in _passes<em>df<\/em> to illustrate the pair of players from that event. As we don&#8217;t care about directionality, we&#8217;ll filter out unneeded players and sort those pairs alphabetically:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">edges_df = passes_df.copy()\nedges_df = edges_df[edges_df[&#039;player_name&#039;].isin(most_used_lineup_players)]\n\nedges_df[&#039;player_pair&#039;] = edges_df.apply(\n    lambda x: &quot;-&quot;.join(sorted([x[&quot;player_name&quot;], x[&quot;pass_recipient_name&quot;]])), \n    axis=1)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This will be used as a key for a <em>groupby<\/em> next:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">edges_df = edges_df.groupby([&quot;player_pair&quot;])\n                    .agg(passes_made=(&#039;x&#039;, &#039;count&#039;))\n                    .reset_index()\nfiltered_edges_df = edges_df[edges_df[&#039;passes_made&#039;] &gt; 238]<\/code><\/pre>\n<p class=\"wp-block-paragraph\">To create this _edges<em>df<\/em>, I&#8217;m grouping by this new column we just created and counting the number of passes between each pair of players. Then, we filter out those with less than 238 passes because we want to see only those with an average passing rate of 7 passes per game.<\/p>\n<p class=\"wp-block-paragraph\">This is not accurate because not all players played 34 games so their averages might be higher and still be left out&#8230; But we want to keep things simple.<\/p>\n<p class=\"wp-block-paragraph\">Anyway, let&#8217;s see it:<\/p>\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"ededed\" data-has-transparency=\"false\" style=\"--dominant-color: #ededed;\" loading=\"lazy\" decoding=\"async\" width=\"460\" height=\"306\" src=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1T11F4fVE8mvEHgFO91WBSA.png\" alt=\"Top 5 rows of player-pairs and passes made - image by the author\" class=\"wp-image-188974 not-transparent\" srcset=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1T11F4fVE8mvEHgFO91WBSA.png 460w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1T11F4fVE8mvEHgFO91WBSA-300x200.png 300w\" sizes=\"auto, (max-width: 460px) 100vw, 460px\" \/><figcaption class=\"wp-element-caption\">Top 5 rows of player-pairs and passes made &#8211; image by the author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">We&#8217;re now ready to plot and we&#8217;ll use mplsoccer[4] to display a field to draw upon. The full code taking care of the visualization is shown next:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">pitch = Pitch(line_color=&#039;grey&#039;)\nfig, ax = pitch.grid(grid_height=0.9, title_height=0.06, axis=False,\n                     endnote_height=0.04, title_space=0, endnote_space=0)\npitch.scatter(nodes_df.x, nodes_df.y, s=nodes_df.marker_size, color=&#039;rosybrown&#039;, edgecolors=&#039;lightcoral&#039;, linewidth=1, alpha=1, ax=ax[&quot;pitch&quot;], zorder = 3)\n\nfor i, row in nodes_df.iterrows():\n    pitch.annotate(row.player_name, xy=(row.x, row.y), c=&#039;black&#039;, va=&#039;center&#039;, ha=&#039;center&#039;, weight = &quot;bold&quot;, size=16, ax=ax[&quot;pitch&quot;], zorder = 4)\n\nall_players = nodes_df[&quot;player_name&quot;].tolist()\nfor i, row in filtered_edges_df.iterrows():\n    player1 = row[&quot;player_pair&quot;].split(&#039;-&#039;)[0]\n    player2 = row[&#039;player_pair&#039;].split(&#039;-&#039;)[1]\n\n    if player1 not in all_players or player2 not in all_players:\n        continue\n\n    player1_x, player1_y = nodes_df.loc[nodes_df[&quot;player_name&quot;] == player1, [&#039;x&#039;, &#039;y&#039;]].values[0]\n    player2_x, player2_y = nodes_df.loc[nodes_df[&quot;player_name&quot;] == player2, [&#039;x&#039;, &#039;y&#039;]].values[0]\n\n    line_width = (row[&quot;passes_made&quot;] \/ lines_df[&#039;passes_made&#039;].max() * 10)\n\n    pitch.lines(player1_x, player1_y, player2_x, player2_y,\n                    alpha=1, lw=line_width, zorder=2, color=&quot;lightcoral&quot;, ax = ax[&quot;pitch&quot;])\n\nfig.suptitle(&quot;Bayer Leverkusen&#039;s Passing Network (2023\/24)&quot;, fontsize = 25)\nplt.show()<\/code><\/pre>\n<p class=\"wp-block-paragraph\">And, finally, let&#8217;s see the resulting passing network:<\/p>\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"f8f6f6\" data-has-transparency=\"true\" style=\"--dominant-color: #f8f6f6;\" loading=\"lazy\" decoding=\"async\" width=\"1960\" height=\"1380\" src=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g.png\" alt=\"Bayer Leverkusen&#039;s passing network using the most-used lineup and showing only those connections with mroe than 238 passes - image by the author\" class=\"wp-image-188975 has-transparency\" srcset=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g.png 1960w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g-300x211.png 300w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g-1024x721.png 1024w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g-768x541.png 768w, https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/1WO7V7iuDtg0EPwEG7Ttp6g-1536x1081.png 1536w\" sizes=\"auto, (max-width: 1960px) 100vw, 1960px\" \/><figcaption class=\"wp-element-caption\">Bayer Leverkusen&#8217;s passing network using the most-used lineup and showing only those connections with mroe than 238 passes &#8211; image by the author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">Quite nice, right?<\/p>\n<h3 class=\"wp-block-heading\">Metrics and Analysis<\/h3>\n<p class=\"wp-block-paragraph\">As data scientists, we can&#8217;t stop here. Developing the code and creating the visualization is key, but we actually need to derive some insights from it. Otherwise, it&#8217;s useless.<\/p>\n<p class=\"wp-block-paragraph\">So let&#8217;s define some extra metrics such as network centrality and passing rate.<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>The passing rate<\/strong> refers to the number of successful passes per minute of possession.<\/li>\n<li><strong>Network centrality<\/strong> has already been explained before but it measures how influential a player is within the team&#8217;s passing structure.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Thomas Grund has already done the job for us by inspecting how the passing rate relates to a team&#8217;s probability of scoring more goals. In short, he found out that a team with a passing rate of 5 successful passes per minute of possession had 20% more goals than those teams with a ratio of 3.<\/p>\n<p class=\"wp-block-paragraph\">So it is a good proxy to measure attacking performance (or, at least, goal-scoring probabilities). Let&#8217;s compute that for today&#8217;s case study:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">events_df[&#039;total_minutes&#039;] = (events_df[&#039;second&#039;]\/60) + events_df[&#039;minute&#039;]\nevents_df[&#039;event_duration&#039;] = events_df.groupby(&#039;match_id&#039;)[&#039;total_minutes&#039;].diff().shift(-1)\n\n# Agrupar per equip i sumar\npossession_minutes = events_df.groupby([&#039;possession_team&#039;])[&#039;event_duration&#039;].sum()[&#039;Bayer Leverkusen&#039;]\npassing_rate = len(passes_df)\/possession_minutes<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Here&#8217;s what the previous snippet does:<\/p>\n<ol class=\"wp-block-list\">\n<li>Create a new column with the time in minutes (minutes).<\/li>\n<li>Create a new column to find how many minutes go by between event and event (as a way to measure each event&#8217;s duration)<\/li>\n<li>Compute the total minutes Bayer Leverkusen had the ball throughout the season.<\/li>\n<li>Compute the passing rate using the number of successful passes done by the team (contained in _passes<em>df<\/em>) divided by the minutes with possession.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">And the result is: <strong>10.87<\/strong>.<\/p>\n<p class=\"wp-block-paragraph\">So, if going from 3 to 5 successful passes per minute of possession translated to 20% more goals, imagine if a team has a rate of 10.87. This is how good Leverkusen were.<\/p>\n<p class=\"wp-block-paragraph\">To put it even in more context, do you remember Spain&#8217;s 2012 UEFA Euro team? Busquets, Xavi, Iniesta, Xabi Alonso&#8230; Their passing rate in the game against Croatia was 9.65. Leverkusen&#8217;s sustained average of 10.87 during the whole season is impressive knowing how managed to have better metrics than the team that is considered by many the best team that&#8217;s ever played the game.<\/p>\n<p class=\"wp-block-paragraph\">Xabi Alonso tried to replicate (and succeeded) what he had done already as a player, now as a coach, and with a less-talented team (still a very good team though).<\/p>\n<p class=\"wp-block-paragraph\">Moving on to network centrality, this metric is going to be key as well to know if the game relied on a small subset of players or not. There are many ways to compute it such as using the Degree Centrality metric, the Betweenness Centrality, or the Eigenvector Centrality (to name a few).<\/p>\n<p class=\"wp-block-paragraph\">For simplicity, we&#8217;ll stick to the way David Sumpter computes it in Soccermatics[2] but slightly changed: We sum the difference between the max number of successful passes made\/received by one player and the number of successful passes made\/received by each player, and divide it by the sum of all passes multiplied by the number of nodes in a network minus 1:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">#find one who made most passes\nmax_passing_participations = nodes_df[&#039;passing_participation&#039;].max()\n#calculate the denominator - 10*the total sum of passes\ndenominator = 10*(nodes_df[&#039;passing_participation&#039;].sum()\/2)\n#calculate the nominator\nnominator = (max_passing_participations - nodes_df[&#039;passing_participation&#039;]).sum()\n#calculate the centralization index\ncentralization_index = nominator\/denominator<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Using this approach, we get a centralization index of 0.1988, or 19.88%. The closer to 0, the less centralized it is, so around 20% can still be considered low and that&#8217;s correlated with an 8% increase in the probability of scoring.<\/p>\n<p class=\"wp-block-paragraph\">To add even more context, in the same game mentioned above between Spain and Croatia, the Spanish had a centralization index of 14.6%. So Leverkusen&#8217;s metric, aggregated throughout the season (not just one game like the Euro&#8217;s), is pretty close to that 14.6%.<\/p>\n<h3 class=\"wp-block-heading\">Conclusion<\/h3>\n<p class=\"wp-block-paragraph\">Today we learned the core components of a network &#8211; nodes and edges &#8211; and also what their properties mean (size and width). Additionally, we learned the concept of centrality in a network.<\/p>\n<p class=\"wp-block-paragraph\">We did all this through a real-case scenario, using Bayer Leverkusen&#8217;s 2023\/24 season data by leveraging their passing networks and analyzing them mathematically.<\/p>\n<p class=\"wp-block-paragraph\">To extract some insights, we found their network was that of a team that plays strong possession football, and both metrics we computed were similar to the ones seen in Spain when they faced Croatia in the 2012 Euros.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Thanks for reading the post! \n\nI really hope you enjoyed it and found it insightful. There&#039;s a lot more to \ncome, especially more AI-based posts I&#039;m preparing.\n\nFollow me and subscribe to my mail list for more \ncontent like this one, it helps a lot!\n\n@polmarin<\/code><\/pre>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<h3 class=\"wp-block-heading\">Resources<\/h3>\n<p class=\"wp-block-paragraph\">[1] StatsBomb. (n.d.). <em>Home<\/em>. StatsBomb. Retrieved September 14, 2024, from <a href=\"https:\/\/statsbomb.com\/\">https:\/\/statsbomb.com<\/a><\/p>\n<p class=\"wp-block-paragraph\">[2] Soccermatics. (n.d.). <em>Soccermatics documentation<\/em>. Retrieved September 13, 2024, from <a href=\"https:\/\/soccermatics.readthedocs.io\/\">https:\/\/soccermatics.readthedocs.io<\/a><\/p>\n<p class=\"wp-block-paragraph\">[3] <em>StatsBombPy<\/em>. GitHub. Retrieved September 14, 2024, from <a href=\"https:\/\/github.com\/statsbomb\/statsbombpy\">https:\/\/github.com\/statsbomb\/statsbombpy<\/a><\/p>\n<p class=\"wp-block-paragraph\">[4] mplsoccer. (n.d.). <em>mplsoccer documentation<\/em>. Retrieved September 14, 2024, from <a href=\"https:\/\/mplsoccer.readthedocs.io\/en\/latest\/index.html\">https:\/\/mplsoccer.readthedocs.io\/en\/latest\/index.html<\/a><\/p>\n<p class=\"wp-block-paragraph\">[5] Grund, T. U. (2012). Network structure and team performance: The case of English Premier League soccer teams. <em>Social Networks, 34<\/em>(4), 682\u2013690. <a href=\"https:\/\/doi.org\/10.1016\/j.socnet.2012.08.004\">https:\/\/doi.org\/10.1016\/j.socnet.2012.08.004<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Analyzing Bayer Leverkusen&#8217;s Passing Networks from Last Season<\/p>\n","protected":false},"author":18,"featured_media":180121,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"is_member_only":true,"sub_heading":"Analyzing Bayer Leverkusen's Passing Networks from Last Season","footnotes":""},"categories":[44],"tags":[448,2994,460,1688,1977],"sponsor":[],"coauthors":[30752],"class_list":["post-180120","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-science","tag-data-science","tag-football-analytics","tag-hands-on-tutorials","tag-network","tag-sports-analytics"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Football and Geometry - Passing Networks | Towards Data Science<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Football and Geometry - Passing Networks | Towards Data Science\" \/>\n<meta property=\"og:description\" content=\"Analyzing Bayer Leverkusen&#039;s Passing Networks from Last Season\" \/>\n<meta property=\"og:url\" content=\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\" \/>\n<meta property=\"og:site_name\" content=\"Towards Data Science\" \/>\n<meta property=\"article:published_time\" content=\"2024-09-16T23:54:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-01-13T15:51:43+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1707\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Pol Marin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@TDataScience\" \/>\n<meta name=\"twitter:site\" content=\"@TDataScience\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Pol Marin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\"},\"author\":{\"name\":\"TDS Editors\",\"@id\":\"https:\/\/towardsdatascience.com\/#\/schema\/person\/f9925d336b6fe962b03ad8281d90b8ee\"},\"headline\":\"Football and Geometry &#8211; Passing Networks\",\"datePublished\":\"2024-09-16T23:54:06+00:00\",\"dateModified\":\"2025-01-13T15:51:43+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\"},\"wordCount\":1965,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/towardsdatascience.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\",\"keywords\":[\"Data Science\",\"Football Analytics\",\"Hands On Tutorials\",\"Network\",\"Sports Analytics\"],\"articleSection\":[\"Data Science\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\",\"url\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\",\"name\":\"Football and Geometry - Passing Networks | Towards Data Science\",\"isPartOf\":{\"@id\":\"https:\/\/towardsdatascience.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\",\"datePublished\":\"2024-09-16T23:54:06+00:00\",\"dateModified\":\"2025-01-13T15:51:43+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage\",\"url\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\",\"contentUrl\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg\",\"width\":2560,\"height\":1707,\"caption\":\"Photo by Clint Adair on Unsplash\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/towardsdatascience.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Football and Geometry &#8211; Passing Networks\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/towardsdatascience.com\/#website\",\"url\":\"https:\/\/towardsdatascience.com\/\",\"name\":\"Towards Data Science\",\"description\":\"Publish AI, ML &amp; data-science insights to a global community of data professionals.\",\"publisher\":{\"@id\":\"https:\/\/towardsdatascience.com\/#organization\"},\"alternateName\":\"TDS\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/towardsdatascience.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/towardsdatascience.com\/#organization\",\"name\":\"Towards Data Science\",\"alternateName\":\"TDS\",\"url\":\"https:\/\/towardsdatascience.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/towardsdatascience.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2025\/02\/tds-logo.jpg\",\"contentUrl\":\"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2025\/02\/tds-logo.jpg\",\"width\":696,\"height\":696,\"caption\":\"Towards Data Science\"},\"image\":{\"@id\":\"https:\/\/towardsdatascience.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/TDataScience\",\"https:\/\/www.youtube.com\/c\/TowardsDataScience\",\"https:\/\/www.linkedin.com\/company\/towards-data-science\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/towardsdatascience.com\/#\/schema\/person\/f9925d336b6fe962b03ad8281d90b8ee\",\"name\":\"TDS Editors\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/towardsdatascience.com\/#\/schema\/person\/image\/23494c9101089ad44ae88ce9d2f56aac\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g\",\"caption\":\"TDS Editors\"},\"description\":\"Building a vibrant data science and machine learning community. Share your insights and projects with our global audience: bit.ly\/write-for-tds\",\"url\":\"https:\/\/towardsdatascience.com\/author\/towardsdatascience\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Football and Geometry - Passing Networks | Towards Data Science","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/","og_locale":"en_US","og_type":"article","og_title":"Football and Geometry - Passing Networks | Towards Data Science","og_description":"Analyzing Bayer Leverkusen's Passing Networks from Last Season","og_url":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/","og_site_name":"Towards Data Science","article_published_time":"2024-09-16T23:54:06+00:00","article_modified_time":"2025-01-13T15:51:43+00:00","og_image":[{"width":2560,"height":1707,"url":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg","type":"image\/jpeg"}],"author":"Pol Marin","twitter_card":"summary_large_image","twitter_creator":"@TDataScience","twitter_site":"@TDataScience","twitter_misc":{"Written by":"Pol Marin","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#article","isPartOf":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/"},"author":{"name":"TDS Editors","@id":"https:\/\/towardsdatascience.com\/#\/schema\/person\/f9925d336b6fe962b03ad8281d90b8ee"},"headline":"Football and Geometry &#8211; Passing Networks","datePublished":"2024-09-16T23:54:06+00:00","dateModified":"2025-01-13T15:51:43+00:00","mainEntityOfPage":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/"},"wordCount":1965,"commentCount":0,"publisher":{"@id":"https:\/\/towardsdatascience.com\/#organization"},"image":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage"},"thumbnailUrl":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg","keywords":["Data Science","Football Analytics","Hands On Tutorials","Network","Sports Analytics"],"articleSection":["Data Science"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/","url":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/","name":"Football and Geometry - Passing Networks | Towards Data Science","isPartOf":{"@id":"https:\/\/towardsdatascience.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage"},"image":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage"},"thumbnailUrl":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg","datePublished":"2024-09-16T23:54:06+00:00","dateModified":"2025-01-13T15:51:43+00:00","breadcrumb":{"@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#primaryimage","url":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg","contentUrl":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2024\/09\/0zPSpBZKLj1SCj2Ub-scaled.jpg","width":2560,"height":1707,"caption":"Photo by Clint Adair on Unsplash"},{"@type":"BreadcrumbList","@id":"https:\/\/towardsdatascience.com\/football-and-geometry-passing-networks-6e201ceff6ef\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/towardsdatascience.com\/"},{"@type":"ListItem","position":2,"name":"Football and Geometry &#8211; Passing Networks"}]},{"@type":"WebSite","@id":"https:\/\/towardsdatascience.com\/#website","url":"https:\/\/towardsdatascience.com\/","name":"Towards Data Science","description":"Publish AI, ML &amp; data-science insights to a global community of data professionals.","publisher":{"@id":"https:\/\/towardsdatascience.com\/#organization"},"alternateName":"TDS","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/towardsdatascience.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/towardsdatascience.com\/#organization","name":"Towards Data Science","alternateName":"TDS","url":"https:\/\/towardsdatascience.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/towardsdatascience.com\/#\/schema\/logo\/image\/","url":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2025\/02\/tds-logo.jpg","contentUrl":"https:\/\/towardsdatascience.com\/wp-content\/uploads\/2025\/02\/tds-logo.jpg","width":696,"height":696,"caption":"Towards Data Science"},"image":{"@id":"https:\/\/towardsdatascience.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/TDataScience","https:\/\/www.youtube.com\/c\/TowardsDataScience","https:\/\/www.linkedin.com\/company\/towards-data-science\/"]},{"@type":"Person","@id":"https:\/\/towardsdatascience.com\/#\/schema\/person\/f9925d336b6fe962b03ad8281d90b8ee","name":"TDS Editors","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/towardsdatascience.com\/#\/schema\/person\/image\/23494c9101089ad44ae88ce9d2f56aac","url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","caption":"TDS Editors"},"description":"Building a vibrant data science and machine learning community. Share your insights and projects with our global audience: bit.ly\/write-for-tds","url":"https:\/\/towardsdatascience.com\/author\/towardsdatascience\/"}]}},"distributor_meta":false,"distributor_terms":false,"distributor_media":false,"distributor_original_site_name":"Towards Data Science","distributor_original_site_url":"https:\/\/towardsdatascience.com","push-errors":false,"_links":{"self":[{"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/posts\/180120","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/users\/18"}],"replies":[{"embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/comments?post=180120"}],"version-history":[{"count":0,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/posts\/180120\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/media\/180121"}],"wp:attachment":[{"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/media?parent=180120"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/categories?post=180120"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/tags?post=180120"},{"taxonomy":"sponsor","embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/sponsor?post=180120"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/towardsdatascience.com\/wp-json\/wp\/v2\/coauthors?post=180120"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}