<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>stats and snakeoil</title>
  <icon>https://www.statsandsnakeoil.com/images/logo.png</icon>
  <subtitle>analytical trivia and trivial analysis</subtitle>
  <link href="https://www.statsandsnakeoil.com/atom.xml" rel="self"/>
  
  <link href="https://www.statsandsnakeoil.com/"/>
  <updated>2022-02-03T15:08:36.627Z</updated>
  <id>https://www.statsandsnakeoil.com/</id>
  
  <author>
    <name>Ben Torvaney</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Extensions to xThreat</title>
    <link href="https://www.statsandsnakeoil.com/2022/02/06/extensions-to-xthreat/"/>
    <id>https://www.statsandsnakeoil.com/2022/02/06/extensions-to-xthreat/</id>
    <published>2022-02-06T11:00:00.000Z</published>
    <updated>2022-02-03T15:08:36.627Z</updated>
    
    <content type="html"><![CDATA[<p>Since <a href="https://karun.in/blog/expected-threat.html">Karun Singh’s original introduction</a>, Expected Threat (abbreviated xThreat or xT) hasgained an increasing amount of traction and mindshare in the public soccer analytics sphere.</p><p>I would attribute this to a few main factors:</p><ul><li>An excellent <a href="https://karun.in/blog/expected-threat.html">introductory blog post</a> that clearly explains the motivations, mechanisms, and applications of the model</li><li>The provision of fitted parameters by Karun Singh (linked in the blog post)</li><li>An excellent <a href="https://github.com/ML-KULeuven/socceraction">open-source implementation</a> by the KU Leuven Machine Learning Research Group that can use the fitted parameters</li></ul><p>Altogether, this makes it possible for hobbyists and practitioners alike to use xThreat relatively easily when compared with competing models.</p><p>Nonetheless, I think there’s a few areas where xThreat could be tweaked and potentially improved, without changing the core idea too much. (This post does assume some familiarity with the xThreat model, so if this is new to you it’s probably worth reading Karun’s introduction.)</p><p>I have attempted to credit prior work where relevant; if I’ve missed anything please <a href="https://twitter.com/Torvaney">let me know</a> so I can add any links.</p><br><h2 id="1-Using-different-pitch-divisions…">1. Using different pitch divisions…</h2><p>In general, xThreat implementations divide the pitch into uniform grid, as in this graphic from <a href="https://socceraction.readthedocs.io/en/latest/documentation/xT.html">the socceraction documentation</a>:</p><p><img src="default_xt_grid.png" alt="A pitch with a 12 by 8 grid overlaid"></p><p>However, there’s nothing in the xThreat model that mandates this way of dividing up the pitch. Perhaps there are other divisions that could help…</p><h3 id="…-to-better-reflect-player-behaviour">… to better reflect player behaviour</h3><p>Pitch markings, the laws of the game, and team tactics alter player behaviour around the pitch. The way these factors interact to impact player behaviour is not uniform across the pitch. For example, teams will often set their defensive line just above the penalty area. So should we reconsider splitting the pitch up uniformly in xThreat?</p><p>Perhaps the <a href="https://spielverlagerung.com/2014/11/26/juego-de-posicion-a-short-explanation/">Juego de Posición</a> pitch divisions could be a decent starting point?</p><p><img src="jdp-pitch.png" alt=""></p><p>More granular divisions in the attacking half of the pitch might be desirable. Which brings us to…</p><h3 id="…-to-increase-the-resolution-of-xT-estimates">… to increase the resolution of xT estimates</h3><p>xThreat includes a simple xG model, whereby the probability of scoring from a given zone is estimated. xThreat uses the embedded xG model to propagate value estimates to all zones on the pitch.</p><p>Therefore, the precision of the xThreat estimates depends on the precision of the xG model.</p><p>In the 12 by 8 pitch division, the zones in the attacking penalty area encompass a large range of different xG values. Getting a shot to the penalty spot tends to be much more valuable than getting one to just outside the box, but uniform pitch bins in xThreat treat these shots the same.</p><p>In general, we want to define the pitch zones such that the events within each zone are as similar to one another as possible. Can we increase the precision of the xT model by decreasing the number of bins within high-value areas?</p><p>Maybe something like… this?</p><p><img src="jdp-pitch-xg-grid.png" alt=""></p><p>Granted, this is not the prettiest way to divide the pitch. But it would divide the area where most high value shots come from more equitably than uniform binning.</p><h2 id="2-Use-an-xG-model">2. Use an xG model</h2><p>As mentioned, xThreat embeds a simple xG model within it. But, we already have quite sophisticated xG models that work well. So why not use an existing xG model to assign the initial value of shots from a given zone?</p><p>There’s a few ways one could incorporate the information from xG models, but the simplest would be to take the average shot xG within a given location as the value of a given shot.</p><h2 id="3-Introduce-turnover-states">3. Introduce turnover states</h2><p>The basic xThreat model estimates the probability of moving or shooting from each zone on the pitch. However, we could add an additional set of turnover states. In other words, we also estimate the chance of giving the ball to the opposition in each of the pitch zones.</p><p>This allows us to calculate xT for <em>and</em> against. The xThreat against (xRisk?) at a given location would be the probability of turning over the ball multiplied by the xThreat from that zone, for each pitch zone.</p><p>Having xThreat values for and against would bring xT closer to possession value models like OBV, VAEP, and G+.</p><p>Rob Hickman introduced the idea of incorporating opposition threat to xThreat in his <a href="https://www.youtube.com/watch?v=nzaHaWEa9BA">2019 talk, “Considering Defensive Risk in Expected Threat Models”</a>.</p><br><p>Taking each of these ideas to their logical conclusions ultimately leads towards more sophisticated possession-value models like VAEP, G+, or OBV. However, these models are significantly more complex and are more difficult to develop and maintain than Expected Threat.</p><p>Small tweaks like the ones listed above may be able to improve xThreat while keeping the model complexity manageable, and keeping the benefits of all the public work that has gone into xThreat so far.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Since &lt;a href=&quot;https://karun.in/blog/expected-threat.html&quot;&gt;Karun Singh’s original introduction&lt;/a&gt;, Expected Threat (abbreviated xThreat </summary>
      
    
    
    
    
    <category term="Expected threat" scheme="https://www.statsandsnakeoil.com/tags/expected-threat/"/>
    
  </entry>
  
  <entry>
    <title>How much do Newcastle need to improve in January?</title>
    <link href="https://www.statsandsnakeoil.com/2022/01/02/how-much-do-newcastle-need-to-improve-in-january/"/>
    <id>https://www.statsandsnakeoil.com/2022/01/02/how-much-do-newcastle-need-to-improve-in-january/</id>
    <published>2022-01-02T20:00:00.000Z</published>
    <updated>2022-09-26T13:37:03.791Z</updated>
    
    <content type="html"><![CDATA[<p>Right now, Newcastle are in an unusual position. They sit close to the bottom of the table and looklike one of the favourites to get relegated. However, following their recent takeover by the <a href="https://theathletic.com/news/newcastles-saudi-arabia-backed-takeover-completed/78ACTsk9faaB/">sovereign wealth fundof Saudi Arabia</a>,they have the ability to spend vast sums of money in January, and a season in the Championship would be damaging to the PIF’ssportswashing project.</p><p>So, exactly how much do Newcastle need to improve to have a fighting chance of remaining in the Premier League?</p><h2 id="What-are-Newcastle’s-current-relegation-chances">What are Newcastle’s current relegation chances?</h2><p>At the time of writing, things look pretty grim for Newcastle. Based on current form,<a href="https://projects.fivethirtyeight.com/soccer-predictions/premier-league/">538 give them a 75% chance of going down</a>.Using the combined xG-and-goals model described <a href="https://www.statsandsnakeoil.com/2021/06/09/does-xg-really-tell-all/">here</a>,we get a similar estimate of around 80%.</p><p>Of course, Newcastle are much better placed than their rivals to improve their team. With a new coach and an effectively limitless pile of cash, anything is possible… right?</p><p>The betting markets seem to think so. Odds on Betfair’s exchange give Newcastle an implied relegation chance of around 45%, significantly lower than the models.</p><h2 id="Estimating-relegation-chance">Estimating relegation chance</h2><p>To understand how team quality impacts relegation chance, we need to look at how I estimated Newcastle’s current, “naive” relegation chance.</p><p>First, I used a model to estimate the attacking and defensive strength of each team in the Premier League, based on both their expected goals for and against and their actual goals scored and conceded. I have written <a href="https://www.statsandsnakeoil.com/tags/dixon-coles/">a few posts on these types of models</a> if you are interested in more details on how exactly they estimate those strengths.</p><p>At the time of writing, the estimates for each team’s ability is like so:</p><p><img src="team-strengths.jpg" alt=""></p><p>The axes refer to each team’s predicted goals (for/against) per game when playing against a hypothetical league average team, without home advantage.</p><p>Newcastle aren’t in a great spot. They’re estimated to score about 1.0 and concede 1.6 goals per game against an average Premier League side.</p><p>Using these estimates, we make predictions for each unplayed match in the rest of the season. For example, the model might predict thatBrentford vs Leeds has a 5% of ending 0-0, a 6% chance of ending 0-1, … and so on for each possible scoreline.</p><p>With the predictions, we can simulate the remainder of the season by picking a random scoreline for each unplayed match, based on how likely that scoreline is to occur. Taking both the played matches and the simulated matches, we can work out what the table would look like at the end of this simulated season.</p><p>To get the relegation probability, we simulate the rest of the season over and over again and find the proportion of simulations that ended with Newcastle in the bottom 3.</p><h2 id="How-does-team-improvement-affect-relegation-chances">How does team improvement affect relegation chances?</h2><p>To understand how much Newcastle might need to improve (or how much the betting markets think they’ll improve), we can use the above method, but with a twist.</p><p>Instead of just estimating the relegation probability for Newcastle’s current ability, we estimate the relegation chance for Newcastle at a range of different abilities. In other words, before making the predictions, we modify Newcastle’s attack and defence estimates. This creates a set of “what if” scenarios, with associated relegation probabilities.</p><p>With this, we can see the probability of relegation at a range of different team strengths. The following heatmap shows the estimated probability of relegation if Newcastle played the rest of the season at a given level.</p><p>I have overlaid the current team strength estimates for all teams for context. Although, it should be noted that the colour of the heatmap at each point doesn’t refer to <em>that team’s</em> relegation chance. Rather it refers to the relegation chance that <em>Newcastle</em> would have if they instantly became as good as that team.</p><p><img src="relegation-heatmap.jpg" alt=""></p><p>So, if Newcastle managed to become as good as Manchester City tomorrow, we would expect them to have almost no chance of being relegated. If they became as good as Arsenal or Tottenham, it would be more like 10-20%.</p><p>Betfair’s estimate of 45% puts implies an improvement in January to somewhere around the level of Aston Villa or Wolves. That would put Newcastle at an improvement of around 0.4 goals per game better in defence, and 0.1 goals per game better in attack.</p><p>Or, a (roughly) 0.5 GD per game improvement.</p><p>In the heatmap, the colour gradient is arranged diagonally. This implies that goal <em>difference</em> per game is more important to performance than the specific value of the team’s attack or defence.</p><p>Wolves, Southampton, Everton and Leeds all have similar associated relegation probabilities in the chart above. This is because their estimated goal difference per game is very similar, even though they achieve this through very different means. Wolves have solid defence but a poor attack. Leeds have a decent attack but a poor defence.</p><h2 id="Some-caveats">Some caveats</h2><p>This method of season simulation is quite crude, and the resulting estimates are approximate. I can think of a few caveats which might shift the perception of the challenge facing Newcastle one way or another:</p><ul><li><p>New signings require time to gel and learn systems. The simulation method assumes that the change to Newcastle’s ability will be instant. But if there’s an adjustment period for new players, the team will actually need to <em>overshoot</em> in terms of player talent in order to get an average performance akin to the target. To get Leicester-quality performance over the rest of the season, you might need to bring in players slightly <em>better</em> than Leicester’s to account for the new signings settling in and gelling.</p></li><li><p>Eddie Howe has only been head coach for a short while. The model is unlikely to fully account for any improvement to the team since his arrival, so the current estimate of Newcastle’s ability (and therefore relegation chance) might be a little pessimistic.</p></li><li><p>The competition simulation assumes all other teams will remain at their current level. This is clearly untrue. Other teams could get better or worse throughout the season. New signings, injuries, and <a href="https://www.statsandsnakeoil.com/2017/12/18/tracking-team-strength-over-time/#Motivation">motivation</a> will affect teams.</p></li></ul><h2 id="How-might-this-impact-decision-making">How might this impact decision making?</h2><p>Having numerical estimates gives you a feel for the scale of the challenge that Newcastle are up against. According to this model, improving the team to Arsenal/West Ham/Tottenham’s level leaves you with a 10-20% relegation chance. Even after such a huge increase in quality, there’s still a very realistic chance of relegation.</p><p>With this in mind, I would argue that laying foundations that will function in either the Premier League or the Championship would be a better strategy than going all-out for Premier League survival.Crystal Palace’s last 2 years are an excellent example of good decision- and deal-making in the face of uncertainty. Brighton, Brentford and Wolves have also shown that it’s possible to build a competitive Premier League squad in the Championship.</p><p>Having a rough idea of the relationship between improvement (in GD per game) relegation chance helps plan <em>how</em> to minimise relegation chance. For example, if you can think you can attract players who improve the team by around 0.1 GD per game over the course of the season (compared to their counterparts), then you’d need to sign 4-5 of these players to get to 40-50% chance of relegation. To get down to 15-20%, that would be around 8 players.</p><p>We already saw that improving the defence has a similar effect to improving the attack. This should give decision-makers confidence to focus on whichever area they believe is easier to improve.</p><p>Decisions and strategy are hard, especially when the stakes are so high. Having empirical methods like this can help focus decisions and reduce the risk of mistakes, making them a valuable part of an executives’ toolkit.</p><hr><p><em>Code for this analysis is <a href="https://github.com/Torvaney/wingback/blob/newcastle-improvement-needed/nbs/competition-forecast.ipynb">available on GitHub</a>.</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Right now, Newcastle are in an unusual position. They sit close to the bottom of the table and look
like one of the favourites to get rel</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="Python" scheme="https://www.statsandsnakeoil.com/tags/python/"/>
    
    <category term="mezzala" scheme="https://www.statsandsnakeoil.com/tags/mezzala/"/>
    
    <category term="Understat-db" scheme="https://www.statsandsnakeoil.com/tags/understat-db/"/>
    
    <category term="Newcastle" scheme="https://www.statsandsnakeoil.com/tags/newcastle/"/>
    
  </entry>
  
  <entry>
    <title>Should we stop using moving averages?</title>
    <link href="https://www.statsandsnakeoil.com/2021/11/20/stop-using-moving-averages/"/>
    <id>https://www.statsandsnakeoil.com/2021/11/20/stop-using-moving-averages/</id>
    <published>2021-11-20T10:00:00.000Z</published>
    <updated>2022-09-26T13:37:12.217Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I’ve seen a few posts looking at rolling averages for some team and player metrics. I think that rolling averages have some flaws. Maybe exponentially-weighted averages are a bit better?</p><h2 id="What-is-bad-about-moving-averages">What is bad about moving averages?</h2><p>In short, when taking a moving average of a given metric, you end up estimating that metric for a date in the past.</p><p>Let me explain:</p><p><img src="rolling-windows-diagrams.001.jpeg" alt="A rolling average estimates a given metric at the center of the time-window. For instance, an 8-game rolling xGD average will best estimate team strength between games 4-5, not at the end of the window."></p><p>So, when using a rolling average, the time window should enclose the date of interest, not end at the date of interest.</p><p>However, this presents a problem when the date of interest is close to the current date:</p><p><img src="rolling-windows-diagrams.002.jpeg" alt="So, a window ending at the current date, will actually be estimating a metric at a date in the past."></p><p><img src="rolling-windows-diagrams.003.jpeg" alt="We could move the window so that the center is at the current date… But this would halve the amount of data used, and the effective window midpoint would still be in the past (between games 2 and 3)."></p><h2 id="What-can-we-do-to-fix-this">What can we do to fix this?</h2><p>Part of the problem is that all games within the window are given an equal weighting.To shift the center of the window towards the date of interest, matches close to that date should be given a higher weighting than those far away.</p><p>Once we start looking at this as a <em>weighted</em> average, I think it is a little more intuitive to weight things in this manner than it is to assign games a weight of either 0.0 or 1.0 depending on whether they fall into the time window.</p><p>So what exactly should this weighting look like?</p><p>Recently, I’ve been interested in how modelling can inform simpler, more heuristic approaches. In this case, I think we can learn from the<a href="/2021/06/17/xg-vs-goals-time-discounting">time-discounting methods</a> that are used in modelling football teams’ strength.</p><p>Team strength models share a goal with moving averages: estimating something at a given date. The most common approach I have seenfor weighting input data in team-strength models is an exponential weighted average:</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -0.566ex" xmlns="http://www.w3.org/2000/svg" width="19.555ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 8643.2 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(716,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(1182,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1527,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(2004,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(2580,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(3218.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(4274.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(4740.6,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mi" transform="translate(5312.6,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mo" transform="translate(5815.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mo" transform="translate(6204.6,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(6982.6,0)"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g><g data-mml-node="mo" transform="translate(7448.6,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="mi" transform="translate(7893.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(8254.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></p><p>where “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container>” is how long ago that game took place (e.g. in days or weeks) and “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” (“epsilon”) is a hyperparameter that determines how strongly recent games are weighted compared to past games.</p><p>If we applied this to replace a moving average, the weighting would look something like this:</p><p><img src="rolling-windows-diagrams.004.jpeg" alt="With variable weighting, we can give more influence to games near the target date and in one-sided windows, the effective centre of the window is closer to the target date"></p><p>Note that with an exponentially decaying weight, the weight will never drop down to zero. In practice, picking a cutoff beyond which games aren’t included might be useful. This cutoff could be in the form of a time window (e.g. 6 months from the target date) or in the form of a minimum weight (e.g. “games where weight &gt;= 0.01”)</p><h2 id="So-what-difference-does-it-make">So, what difference does it make?</h2><p>For no particular reason, let’s look at the rolling non-penalty xGD of Manchester United:</p><p><img src="manchester-united-rolling.png" alt=""></p><p>Here, the blue line shows a 21-game moving average, centered on the match of interest. In other words,for each match, we take the 10 matches before and the 10 matches after, and construct an average.</p><p>The orange line shows the exponentially-weighted moving average. Here, I used an “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” of 0.016,which gives matches from 6-months ago a weighting of approximately 5% (the target date will be weighted at 100%). This is a significantly more aggressive weighting than what is <a href="/2021/06/17/xg-vs-goals-time-discounting">optimal for predictive purposes</a>, but I think this is defensible as a rough comparison to the 21-game rolling average. I didn’t apply a cutoff, so all data between 2016/17 to present was used with the appropriate weighting.</p><p>In both cases, the moving averages use both past and future data where available. I haven’t shown the uncentered “last 20 games” style of moving average, since it is almost identical to the blue line but shunted 10 games to the right.</p><p>Although the overall shape of the curves is the same, there are 2 main differences between the 2 moving averages shown above.</p><p>The main difference is that the <strong>exponential weighting gives a smoother curve</strong>. This is because games in the past have incrementally less influence on the average, instead of going from being given equal weight to being not included at all.</p><p>I’m not entirely sure how many sharp changes we should expect to see in an abstract quantity like team ability (that xGD is serving as an indicator for). In general, I would guess that changes should be smooth, but I can see how things like player injuries and coach changes, for example, could reasonably cause discontinuities. However, I don’t trust that the sharp peaks and troughs we see in the moving average are anything other than noise. The whole point of a moving average is to smooth out the noise to more clearly indicate trends, and I think it’s reasonable to say that the exponential weighting is doing this a bit better.</p><p>The other difference we can see in this chart is that <strong>the exponential curve has much bigger jumps at the start/end of each season</strong>. This is because I calculated the weighting on the basis of <em>time</em> rather than on the basis of “how many matches ago”. At the start of the season, the matches from the end of last season took place a reasonably long time ago, so the current-season games have much more influence on the weighted average.</p><p>You could argue this makes for a slightly unfair comparison between the methods. Perhaps using a weighting function where “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container>” is “number of matches ago” would be fairer. Or using a moving average over <em>n</em> months, rather than <em>n</em> matches. In the end, I chose this comparison because, I haven’t seen time-based moving averages used much in this context, and I think using a time-based weighting is more intuitive in this case (where matches aren’t evenly spaced) than a match-indexed one. That said, <a href="https://en.wikipedia.org/wiki/Exponential_smoothing">simple exponential smoothing</a> is an option.</p><p>The biggest individual difference between the two methods can be seen towards the end of the 2019/20 season.</p><p><img src="manchester-united-rolling-focus.jpg" alt=""></p><p>In this case, the moving average shifts to include two games from 2020/21 with large negative xGD tallies:</p><ul><li>Brighton 1.56 - 0.27 Manchester United</li><li>Manchester United 0.16 - 2.91 Tottenham Hotspur</li></ul><p>This results in the moving average sharply dropping down.</p><p>However, because those matches take place next season, and because of the Covid-induced mid-season break lowering the weight of earlier games, the exponentially-weighted average gives more influence to the stronger performances at the end of 2019/20.</p><p>I don’t think either of these approaches can be considered “correct” in any meaningful sense. But I do think that the exponentially-weighted average better reflects the performances of the team at the time in question. How much should our opinion of Manchester United at the end of 2019/20 be affected by a thrashing at the start of 2020/21? Probably a bit, but not as much as performances in 2019/20.</p><h2 id="Not-just-team-metrics">Not just team metrics</h2><p>This approach doesn’t have to be restricted to just team-level metrics. We can easily apply the same logic toplayer stats.</p><p>Take Harry Kane’s xG scored from open play p90:</p><p><img src="harry-kane-rolling.png" alt=""></p><p>Again, the shape is basically the same, but the smoothing has improved.</p><h2 id="Conclusion">Conclusion</h2><p>I’ve suggested 3 changes to try and make rolling-averages better:</p><ul><li>Center the average on the date of interest, instead of using the last <em>n</em> games (or similar)</li><li>Use an (exponential) weighting instead of a moving window</li><li>Calculate weighting using time instead of match index (“how many matches ago”)</li></ul><p>Ultimately, if you follow the logic of improving a moving average far enough, you’ll end up at statistical modelling. But this is a step up in complexity and is often harder to explain than a weighted average.An approach like this can yield some improvement without too much additional complexity.</p><p>It’s a little more work, but maybe it’s worth it?</p><p>What do you think?</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently, I’ve seen a few posts looking at rolling averages for some team and player metrics. I think that rolling averages have some fla</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Moving averages" scheme="https://www.statsandsnakeoil.com/tags/moving-averages/"/>
    
    <category term="Manchester United" scheme="https://www.statsandsnakeoil.com/tags/manchester-united/"/>
    
    <category term="Harry Kane" scheme="https://www.statsandsnakeoil.com/tags/harry-kane/"/>
    
  </entry>
  
  <entry>
    <title>How to estimate team strength from xG totals</title>
    <link href="https://www.statsandsnakeoil.com/2021/06/24/estimating-team-strength-from-xg-totals/"/>
    <id>https://www.statsandsnakeoil.com/2021/06/24/estimating-team-strength-from-xg-totals/</id>
    <published>2021-06-24T09:00:00.000Z</published>
    <updated>2022-09-26T13:36:54.612Z</updated>
    
    <content type="html"><![CDATA[<p>I’ve written before about a <s>hack</s> <a href="/tags/dixon-coles/">method to use xG values within a Poisson-based match prediction model</a>. This is useful for both predicting matches and understanding football teams’ offensive and defensive strength over time (along with other factors like home advantage). However, to use this method you need to have access to an xG value for every shot within a given match. This kind of data isn’t widely available to the public. Where it is available, it often requires some technical knowledge of web scraping to get at it.</p><p>But what <em>is</em> often available is xG <em>totals</em>. <a href="https://twitter.com/Caley_graphics">They</a> <a href="https://twitter.com/XGEEFAX">are</a> <a href="https://twitter.com/xGPhilosophy">everywhere</a>.</p><p>So, in this post, I’d like to present a method for building a match prediction model using xG <em>totals</em>. This uses data that is much more readily accessible to the average fan, while only sacrificing a little prediction quality.</p><h2 id="How-does-it-work">How does it work?</h2><p>First, we need to understand how the model using individual shot xG works. You can read in detail on a <a href="/2018/06/22/dixon-coles-and-xg-together-at-last/">previous post</a>, but in short:</p><p><img src="xg-totals-diagrams.001.jpeg" alt=""></p><ol><li>The original D-C model estimates each team’s offensive and defensive strength, and home advantage from match scorelines</li><li>xG D-C simulates match scorelines using the xG values from each shot in the match (<a href="https://danny.page/expected_goals.html">see also</a>)</li></ol><p>The key insight here is that we can look at this class of models as the composition of two distinct functions:</p><ul><li>A scoreline generation function<ul><li>This is a function that, for each match, will give a list of match scorelines, with weights, for each match</li><li>In the classic case, this just returns the actual match scoreline (with a weight of 1.0)</li><li>In the xG-DC, this returns the simulated match scorelines (with a weight of <code>probability</code>)</li></ul></li><li>The Dixon-Coles model fit</li></ul><p>We don’t <em>need</em> to be limited to goals, xG or resimulation, or… anything at all, really. All we need is a list of weighted scorelines for each match.<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>So, to make the model work for xG <em>totals</em>, we just need to find a way to make xG totals into weighted scorelines. I think the simplest way is to look back at the model assumptions.</p><p>The basic Dixon-Coles model is, at its heart, a modified Poisson model. The <a href="https://en.wikipedia.org/wiki/Poisson_distribution">Poisson distribution</a> takes a single rate parameter (in our example, this is the average goals scored per game) and gives back the probability of seeing 0 goals, 1 goal, 2 goals, and so on. So, if we assume the xG totals are equivalent to the average goals per game for that match (given the team’s performance on the day), then we can simulate match outcomes using the Poisson distribution.</p><p>We can then feed the simulated scorelines into the Dixon-Coles model, with the probability as the weight. In this way, we can get a model that predicts matches in terms of <em>real</em> goals, trained using xG totals.</p><h2 id="What-are-the-downsides">What are the downsides?</h2><ul><li>You lose xG-per-shot information. Even when the total xG is the same it’s <a href="/2021/01/24/are-bad-shots-actually-good/">generally better to take high-quality shots</a> (which the best teams generally do). The xG-totals method presented here lacks any information about the distribution of xG across shots. This makes the model perform worse when making predictions (more on this in a second).</li><li>In the proposed model, we don’t actually need to go through the Poisson simulations to get the exact same result. The whole problem simplifies down to a simpler regression problem. There might be some benefit to doing everything through a common framework (it certainly made <a href="https://github.com/Torvaney/wingback">backtesting</a> it easier for me). I think the main benefit of talking about this is to further our intuition of how these models works.</li><li>The scoreline simulation assumes independence of home and away goals (which is not true in reality). The xG-resimulation approach has the same issue, but it’s worth being aware of. You can <em>sort of</em> get around this somewhat by “transplanting” the dependence parameter (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.489ex" xmlns="http://www.w3.org/2000/svg" width="1.17ex" height="1.489ex" role="img" focusable="false" viewBox="0 -442 517 658"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D70C" d="M58 -216Q25 -216 23 -186Q23 -176 73 26T127 234Q143 289 182 341Q252 427 341 441Q343 441 349 441T359 442Q432 442 471 394T510 276Q510 219 486 165T425 74T345 13T266 -10H255H248Q197 -10 165 35L160 41L133 -71Q108 -168 104 -181T92 -202Q76 -216 58 -216ZM424 322Q424 359 407 382T357 405Q322 405 287 376T231 300Q217 269 193 170L176 102Q193 26 260 26Q298 26 334 62Q367 92 389 158T418 266T424 322Z"></path></g></g></g></svg></mjx-container>) from a “real” D-C model, but it is a hack. Short of designing a better class of model, that might be the best you can do. Let me know if you have any better ideas on this front.</li></ul><h3 id="What-is-the-performance-difference">What is the performance difference?</h3><p>Exactly how much worse does the model perform compared to the xG-resimulation approach (i.e. without the xG per shot information)?</p><p>As <a href="/2021/06/09/does-xg-really-tell-all">before</a>, I ran a backtest to evaluate model performance. In a backtest, we train each model for each matchday in the past. We then make predictions using those models and see how well they would have performed, using the information they had available at the time. The following chart shows the backtest results for the English Premier League between 2016/17 and 2020/21, inclusive.</p><p><img src="xg-totals-performance.jpg" alt=""></p><p>When looking at outcomes (home/draw/away), the totals-only model performs very slightly worse than the model with full xG-resimulations. This makes sense; it has access to less information about teams’ shooting patterns. However, it still performs a lot better than using goals.</p><p>Interestingly, the xG-totals approach performs much worse relatively on total goals than on per-match goal difference. For each match prediction, I took each model’s average predicted home and away goals. I then calculated total goals (home + away) and goal difference (home - away). Over the whole backtest dataset, I took the mean squared error for these two quantities.</p><p><img src="xg-totals-mse.png" alt=""></p><p>The xG-totals model performs almost exactly as well as the xG-resimulation model in predicting match goal difference. However, it performs markedly worse on total goals predictions (still much better than the goals-based model).</p><p>This suggests that the xG-totals method almost as good at estimating the relative strength of each team as the xG-resimulation approach. However, it is worse at predicting match outcomes because of some bias introduced; the xG-totals will underestimate the total number of goals being scored in any given match.</p><p>My hunch is that you could reduce some of this underperformance by using the number of shots in a game (also widely available) to get a mean xG per shot value. You could then use this xG per shot with the number of shots to simulate the match outcomes as though you had the individual shot data.</p><p>You could also gain some extra performance by <a href="/2021/06/09/does-xg-really-tell-all">ensembling a xG-totals model with a Goals model</a>.</p><p>But at the end of the day, if this kind of performance drop is a big issue for you, it’s probably worth investing in a <a href="https://kwiatkowski.io/rethinking-shots">model with better foundations</a>.</p><h2 id="Conclusion">Conclusion</h2><ul><li>You can make decent team strength models using only xG totals (but its predictions are not as good as with individual shot xGs)</li><li>You don’t need the Dixon-Coles framework to do this (but it can be useful to have a common basis for comparison and model ensembles)</li><li>You can apply this framework to incorporate many different data sets or information sources into a match prediction model. You just need a weighted-scoreline generation function.</li></ul><h2 id="Code-etc">Code etc</h2><p>If you would like to reproduce this analysis, or dig a bit more into the technical details, the code can be found in my <a href="https://github.com/torvaney/wingback"><code>wingback</code></a> repository.</p><p>This repo is built on top of <a href="https://github.com/Torvaney/understat-db"><code>understat-db</code></a>, a project for pulling data from <a href="http://Understat.com">Understat.com</a> into a database. It uses a Python library called <a href="https://github.com/Torvaney/mezzala"><code>mezzala</code></a> for modelling team-strengths and match predictions.</p><p><em>I still have some <a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20">Liverpool 2019/20 “data scarves”</a> left for sale (shipping to the UK). If football+data is your thing, take a look</em></p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>This is something I’ve <a href="/2018/10/13/how-to-predict-a-football-match-slides/">touched on</a> before, and I think there’s a lot of potential for incorporating different data sources, like non-shot xG, post-shot xG, betting odds, and so on, into the model. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;I’ve written before about a &lt;s&gt;hack&lt;/s&gt; &lt;a href=&quot;/tags/dixon-coles/&quot;&gt;method to use xG values within a Poisson-based match prediction mode</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="Python" scheme="https://www.statsandsnakeoil.com/tags/python/"/>
    
    <category term="mezzala" scheme="https://www.statsandsnakeoil.com/tags/mezzala/"/>
    
    <category term="Understat-db" scheme="https://www.statsandsnakeoil.com/tags/understat-db/"/>
    
  </entry>
  
  <entry>
    <title>What can time-discount rates tell us about xG and Goals?</title>
    <link href="https://www.statsandsnakeoil.com/2021/06/17/xg-vs-goals-time-discounting/"/>
    <id>https://www.statsandsnakeoil.com/2021/06/17/xg-vs-goals-time-discounting/</id>
    <published>2021-06-17T09:00:00.000Z</published>
    <updated>2021-06-19T14:47:22.849Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I looked at <a href="/2021/06/09/does-xg-really-tell-all">backtesting and ensembling match prediction models</a>. While working on this, I found a nice result regarding <strong>time discounting</strong> that I wanted to share.</p><h2 id="What-is-time-discounting">What is time-discounting?</h2><p>Match prediction models tend to produce estimates of teams’ attacking and defensive strength. The more accurate these estimates are, the more accurate the predictions. Factors like teams’ performance levels (or even home-field advantage) are dynamic and change over time. New players come into the team, team tactics are tweaked, and so on. Because of this dynamism, data (matches) from, say, a year ago tends to be less relevant than data from last week.</p><p>Time-discounting is a way of weighting input data in match prediction models such that more recent matches are given a higher weight (and therefore more influence on the model’s parameter estimates) than those in the distant past.</p><h2 id="Signal-vs-staleness">Signal vs staleness</h2><p>If the most relevant information is data from the last week, the ideal scenario would be to use <em>only</em> the latest match for each team. But, we know that in practice that’s unlikely to work. A single match is quite noisy; <a href="https://uk.soccerway.com/matches/2015/05/24/england/premier-league/stoke-city-fc/liverpool-fc/1704253/">anything</a> <a href="https://uk.soccerway.com/matches/2019/09/14/england/premier-league/norwich-city-fc/manchester-city-football-club/3029118/">can</a> <a href="https://uk.soccerway.com/matches/2021/04/03/england/premier-league/chelsea-football-club/west-bromwich-albion-football-club/3344424/">happen</a>.</p><p>So to combat the noise in a single game, we have to add more data from further back in the past. But that historical data isn’t quite as relevant as the data from last week. How a team played a long time ago doesn’t tell us that much about the team’s performance level now.</p><p>So there’s a tension between having more data, and each incremental bit of data being progressively more out of date. Too much old data, and you’re feeding misleading information into the model. Not enough data, and the model can’t separate the signal from the noise.</p><h2 id="More-signal-less-data">More signal = less data</h2><p>If we use data that has less noise, we don’t have to use as much to extract a reliable signal. So instead of using <em>more</em> data, we can try to use better data.</p><p>This is broadly the logic of using expected goals (xG) instead of goals to evaluate teams and players. Goals are really noisy, so over the course of a season, there is still more signal in xG. To reliably evaluate teams on goals with the same confidence, you’d need years of data, at which point the coach has changed, the squad has changed… you’re comparing <a href="https://en.wikipedia.org/wiki/Ship_of_Theseus">multiple different teams</a>!</p><h2 id="Exponential-weighting">Exponential weighting</h2><p>The most common form<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> of time-discount is exponential weighting, where each data point is weighted according to the formula:</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -0.566ex" xmlns="http://www.w3.org/2000/svg" width="19.555ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 8643.2 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(716,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(1182,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1527,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(2004,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(2580,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(3218.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(4274.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(4740.6,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mi" transform="translate(5312.6,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mo" transform="translate(5815.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mo" transform="translate(6204.6,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(6982.6,0)"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g><g data-mml-node="mo" transform="translate(7448.6,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="mi" transform="translate(7893.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(8254.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></p><p>where “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container>” is how long ago that match took place (usually in days) and “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” (“epsilon”) is a hyperparameter.</p><p><img src="time-decay.png" alt=""></p><p>Because of the nature of exponential decay, a high discount rate (yellow line) results in a very low weight for matches a long time in the past compared to a low discount rate (green line).</p><h3 id="How-do-you-find-the-right-value-of-“-varepsilon-”">How do you find the right value of “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>”?</h3><p>The simplest solution is to use <strong>backtesting</strong>. In short, this means training the model for a variety of different values of epsilon and seeing which one results in the best predictions.</p><h2 id="Time-discounting-with-different-models">Time-discounting with different models</h2><p>If we backtest both Goals-based and xG-based models for a variety of “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” values, we can compare results.</p><p>On the x-axis, I have plotted the weight at day 365 (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex" xmlns="http://www.w3.org/2000/svg" width="14.472ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 6396.8 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(1055.8,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(1521.8,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mi" transform="translate(2093.8,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mo" transform="translate(2596.8,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mo" transform="translate(2985.8,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(3763.8,0)"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g><g data-mml-node="mn" transform="translate(4229.8,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path><path data-c="33" d="M127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463Z" transform="translate(278,0)"></path><path data-c="36" d="M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z" transform="translate(778,0)"></path><path data-c="35" d="M164 157Q164 133 148 117T109 101H102Q148 22 224 22Q294 22 326 82Q345 115 345 210Q345 313 318 349Q292 382 260 382H254Q176 382 136 314Q132 307 129 306T114 304Q97 304 95 310Q93 314 93 485V614Q93 664 98 664Q100 666 102 666Q103 666 123 658T178 642T253 634Q324 634 389 662Q397 666 402 666Q410 666 410 648V635Q328 538 205 538Q174 538 149 544L139 546V374Q158 388 169 396T205 412T256 420Q337 420 393 355T449 201Q449 109 385 44T229 -22Q148 -22 99 32T50 154Q50 178 61 192T84 210T107 214Q132 214 148 197T164 157Z" transform="translate(1278,0)"></path></g><g data-mml-node="mo" transform="translate(6007.8,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>), instead of just “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” because think this is more intuitive. The raw “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” values are shown in gray next to each point.</p><p><img src="backtest-time-discount.png" alt=""></p><p>Both plots show the same overall shape: a very low weight at day 365 (i.e. a very high discount weight), leads to very poor performance followed by a sharp rise, a peak, and a slower decline as the discount rate increases (and old data is given more importance).</p><p>The peak model performance for the xG-based model comes at with a weight at day 365 of around 25%. On the other hand, the peak performance for the goals-based model comes at around 50% weight-at-day-365.</p><p>This goes back to what I mentioned earlier about signal-vs-noise. A goal-based model needs to give matches from the past a higher weight because goals are a noisy data source. Whereas the xG-based model performs best with a higher discount rate, because it’s able to extract more information from each match. Or, to put it another way, it requires less information before the staleness of additional data-points starts to harm the model’s performance.</p><p>We already knew xG was a better way to evaluate teams both intuitively and <a href="/2018/07/15/what-a-diff-rence-xg-makes/">empirically</a>, but I think looking at the comparison of goals and xG through the time-discounting is a nice way to develop our intuition about <em>why</em> xG is better.</p><h2 id="Appendix-I-A-more-entrenched-elite">Appendix I: <s>A more entrenched elite?</s></h2><p>EDIT (2020-07-19): Originally, I speculated that a change in “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” since could be evidence of a more entrenched elite at the top of English football. However, in the original Dixon-Coles paper, they use half-weeks as their time unit (instead of days as I have). Once correcting for this, the difference in “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” values is significantly reduced.</p><p>Thanks to <a href="https://twitter.com/strayinghere">Diogo</a> for pointing this out to me on Twitter.</p><details>  <summary><i>Original text (click to expand)</i></summary><p>As a final note, the original Dixon-Coles paper from 1997, they estimate an optimal value of “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>” at around 0.0065, using a goals-based model on Football League data. The above analysis (using Premier League matches between 2016-2021) suggests an optimal value around 0.0023, a less aggressive time-discount.</p><p>Assuming I’ve correctly reproduced their method, what might be the cause of this change? Could it be that a more entrenched elite at the top of the Premier League has meant that old (e.g. 6+ months ago) results have more information about the present?</p><p>We talk a lot about how the economics of the game have changed and made it harder for small clubs to compete, so I think this is an interesting hypothesis. Especially as this could give us an empirical way to measure those changes. I don’t think we can evaluate from this analysis alone whether it this is driving the change in “<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex" xmlns="http://www.w3.org/2000/svg" width="1.054ex" height="1.072ex" role="img" focusable="false" viewBox="0 -452 466 474"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D700" d="M190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22Z"></path></g></g></g></svg></mjx-container>”, but this might be an idea worth coming back to…</p></details><h2 id="Appendix-II-Code-etc">Appendix II: Code etc</h2><p>If you would like to reproduce this analysis, or dig a bit more into the technical details, the code can be found in my <a href="https://github.com/torvaney/wingback"><code>wingback</code></a> (because it’s concerned with <em>back</em>-testing models… I couldn’t help myself, I’m sorry) repository.</p><p>This repo is built on top of <a href="https://github.com/Torvaney/understat-db"><code>understat-db</code></a>, a project for pulling data from <a href="http://Understat.com">Understat.com</a> into a database. It uses a Python library called <a href="https://github.com/Torvaney/mezzala"><code>mezzala</code></a> for modelling team-strengths and match predictions.</p><h2 id="Appendix-III-Data-scarves">Appendix III: Data scarves</h2><p>I still have some <a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20">Liverpool 2019/20 “data scarves”</a> left for sale (shipping to the UK). If football+data is your thing, take a look:</p><a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20" style="padding-left: 0px;"><img src="/images/lfc-scarf.png" width="50%" alt="Liverpool data-scarf preview"></a><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>The most common form <em>that I know of</em>. If you know of any others that you think might work better, <a href="twitter.com/torvaney">let me know</a>! I’d love to try them out. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently, I looked at &lt;a href=&quot;/2021/06/09/does-xg-really-tell-all&quot;&gt;backtesting and ensembling match prediction models&lt;/a&gt;. While working</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="Python" scheme="https://www.statsandsnakeoil.com/tags/python/"/>
    
    <category term="mezzala" scheme="https://www.statsandsnakeoil.com/tags/mezzala/"/>
    
    <category term="Understat-db" scheme="https://www.statsandsnakeoil.com/tags/understat-db/"/>
    
  </entry>
  
  <entry>
    <title>Does xG really tell us everything about team performance?</title>
    <link href="https://www.statsandsnakeoil.com/2021/06/09/does-xg-really-tell-all/"/>
    <id>https://www.statsandsnakeoil.com/2021/06/09/does-xg-really-tell-all/</id>
    <published>2021-06-09T09:15:00.000Z</published>
    <updated>2022-09-26T13:37:01.191Z</updated>
    
    <content type="html"><![CDATA[<p>Following Liverpool’s decisive title win the end of 2019/20, there was discussion that they <a href="https://www.theguardian.com/football/2020/aug/09/liverpool-xg-jurgen-klopp">might have found a way to “beat” the xG models</a>. The xG tallies suggested that Man City were as good, if not better than Liverpool, but they were left in the dust in the real league table.</p><p>Some speculated that it was just random variation, and that Liverpool couldn’t be expected to maintain the over-performance. Others suggested tht their attacking patterns meant that they could create chances better than xG could capture.</p><p>This raises the question - how much does xG really tell us? Does it really tell us everything we need to know?</p><h2 id="Formulating-the-question">Formulating the question</h2><p>Let’s start by looking at the two positions at either end of the spectrum:</p><p><img src="diagrams.001.jpg" alt="  Most extreme on one side: 'xG tells you everything you need to know; all over/underperformance is just random variation that can't be expected to last'.  Most extreme on the other side: 'Goals are the best indicator of performance; xG is too far removed from the real world to add relevant information'"></p><p>If we had to choose one these two extremes, <a href="/2018/07/15/what-a-diff-rence-xg-makes">we already know that only-xG outperforms only-Goals</a>. For a previous post, I created two team-strength models, one using xG data, the other using goals data. I then evaluated the models based on how well each one made predictions about future matches; the xG-based model came out on top.</p><p>But there’s a whole lot of space in between the extremes. As <a href="https://www.theguardian.com/football/2020/aug/09/liverpool-xg-jurgen-klopp">Ted Knutson said on Liverpool</a>, “Part of [Liverpool’s 2018-2020 overperformance vs xG] is a little bit of luck. But I think there’s stuff Liverpool do that’s not in the expected goals model”.</p><p>So where does the truth lie? If it’s <em>somewhere</em> between the extremes, can we be more specific? And can we prove it?</p><h2 id="Ensembles-and-composing-models">Ensembles and composing models</h2><p>The key for us is a technique called <strong>ensemble modelling</strong>. Ensemble modelling is a technique whereby multiple different models are used to create a single prediction. The intuition is similar to that of <a href="https://en.wikipedia.org/wiki/Wisdom_of_the_crowd">wisdom-of-the-crowd</a>; as long as each model is bringing new information that the other model(s) don’t have, then they can improve the prediction accuracy. Thus, a diverse collection of models can perform better than any of the individual constituent models.</p><p>To answer our question, we can flip this logic around. We can create a bunch of models across the spectrum of xG-Goals:</p><p><img src="diagrams.002.jpg" alt=""></p><p>Then, <strong>if there’s a combined xG+Goals model that performs better than either xG-only, then goalscoring must be adding information on top of what we already get from xG.</strong></p><p>If we plot the model performance for each of these models, we should see one of two shapes<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>, depending on which hypothesis is correct:</p><p><img src="diagrams.003.jpg" alt="  If xG contains all the required information, we should see a slope with the highest-performing model being the xG-only model.  If Goals contains some additional information, we should see an inverted-U shape curve, with the highest performing model somewhere in-between xG-only and Goals-only."></p><p>In other words, the location of the best model will tell us where along the spectrum the “truth” lies. If the peak is somewhere in the middle, it would suggest that top performing teams like Liverpool <em>do</em> find some way to outperform the xG models.</p><details><summary><strong>How to ensemble team-strength models: technical notes</strong> (click to expand)</summary><!-- <div style="margin-left: 20px;"> --><p>You might still be wondering how exactly the two models are combined. This requires a little bit of knowledge of how Poisson-based team-strength models work. I think it’s interesting and worth sharing, but feel free to skip ahead if it’s not your bag.</p><p>In short, this class of team-strength models contend that the goals scored in a given match are Poisson distributed according to a given rate. (Well, two rates - a home rate for home goals, and an away rate for away goals.)</p><p>This rate is the product of a variety of factors: the attacking team’s offensive ability (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex" xmlns="http://www.w3.org/2000/svg" width="1.448ex" height="1.025ex" role="img" focusable="false" viewBox="0 -442 640 453"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g></g></g></svg></mjx-container>), the defending team’s defensive ability (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex" xmlns="http://www.w3.org/2000/svg" width="1.281ex" height="2.034ex" role="img" focusable="false" viewBox="0 -705 566 899"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g></g></g></svg></mjx-container>), and home advantage (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.489ex" xmlns="http://www.w3.org/2000/svg" width="1.229ex" height="1.486ex" role="img" focusable="false" viewBox="0 -441 543 657"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g></g></g></svg></mjx-container>, used in home rate only):</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -2.364ex" xmlns="http://www.w3.org/2000/svg" width="24.955ex" height="5.86ex" role="img" focusable="false" viewBox="0 -1545 11030 2589.9"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mtable"><g data-mml-node="mtr" transform="translate(0,795)"><g data-mml-node="mtd"><g data-mml-node="mi"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(451,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(980,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="msub" transform="translate(1341,0)"><g data-mml-node="mi"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="TeXAtom" transform="translate(499,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(3868.4,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(4924.1,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="TeXAtom" transform="translate(673,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(7347.7,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(7792.4,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="TeXAtom" transform="translate(599,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(10042.3,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="mi" transform="translate(10487,0)"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g></g></g><g data-mml-node="mtr" transform="translate(0,-750)"><g data-mml-node="mtd" transform="translate(543.7,0)"><g data-mml-node="mi"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(451,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(980,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="msub" transform="translate(1341,0)"><g data-mml-node="mi"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="TeXAtom" transform="translate(499,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(3768.7,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(4824.4,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="TeXAtom" transform="translate(673,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(7148.3,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(7593,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="TeXAtom" transform="translate(599,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g></g></g></g></g></g></svg></mjx-container></p><p>There’s a few more details, which I’ll skip for now (some of my <a href="/tags/dixon-coles/">earlier posts</a> go into this a bit more), but this is the most salient part.</p><p>The way I see it, there are a few different ways we could combine match prediction models.</p><ol><li><strong>Combine predictions</strong> - Take the predicted scoreline probabilities and take a weighted average. The relative weighting applied to each prediction determines the position along the xG-Goals spectrum.</li><li><strong>Combine home/away rates</strong> - Similar to (1), but the average is taken on the home and away rates of each model, not the scorelines themselves</li><li><strong>Combine parameters</strong> - Similar to (1) and (2), but instead of averaging the predictions, take the average of the parameter estimates themselves.</li><li><strong>Boost the weighting of actual results (goals data) in the input data</strong> - Instead of combining the fitted models (as in 1, 2, and 3), fit the model on combined input data. See <a href="2018/06/22/dixon-coles-and-xg-together-at-last/">this post</a> for more on how input weighting can be used to train team strength models on xG data.</li></ol><p>Each of these approaches has some merit. (1) is extremely flexible and could be used to ensemble <em>any</em> match prediction, regardless of it’s internal machinery (random forest, neural networks, and so on). (2) is slightly less flexible, but could be used to combine any Poisson-like models. (3) is more restrictive, since it requires each of the ensembled models to have the same parameters and merely differ in what the estimates of those parameters are. However, this is the method that gives the most insight, since we can inspect the results on a parameter-level. (4) also has the benefit of insight; we could inspect the individual parameter estimates of the new fitted model.</p><p>We’re interested in insight and model interpretation, so I figured (3) and (4) were the best fits for this use-case. Of those two, I <a href="https://github.com/Torvaney/wingback/blob/master/nbs/team-strength.ipynb">chose (3)</a>, since aggregating the parameter estimates proved to be significantly quicker than fitting an additional set of models.<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p><!-- </div> --></details><h2 id="Results">Results</h2><p>The method to evaluate the different models went as follows:</p><ul><li>For each Premier League matchday from the start of 16/17, fit an xG-based model and a Goals-based model on past data (i.e. not including the matchday in question).</li><li>For each matchday, create the combined models, with varying degrees of mixing across the spectrum between xG-only and Goals-only.</li><li>For each model (including the combined models), predict each match taking place on that matchday</li></ul><p>This gives a set of 1,860 match predictions with which to evaluate our models. We can evaluate each model’s predictions to get an overall idea of the model’s quality:</p><p><img src="ensemble-performance.png" alt=""></p><p>Using log-likelihood as our metric, we get a <strong>peak in performance for models with a roughly 30% goals/70% xG component</strong>. The exact location of this peak varies depending on how you evaluate the model predictions, but 30% seems like the right approximate figure<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>. Nonetheless, this suggests that <strong>teams with large differences between their xG tallies and actual goals <em>are</em> doing good/bad things that the xG models aren’t picking up on.</strong></p><p>The ensemble approach cannot tell us <em>what</em> these unaccounted-for good/bad things are. My hunch is that it’s some combination of:</p><ul><li>Good teams creating more chances with low defensive pressure/clear sight of goal (and vice versa)</li><li>Player finishing skill</li></ul><p>Neither of which are included in the xG values used in this analysis.</p><p>I used xG data from <a href="http://Understat.com">Understat.com</a>, as that seems to be the defacto public xG model. It’s likely that better xG models, like Statsbomb’s, squeeze the optimal mix of 30% down (more on this later). Although, I think it’s unlikely that it goes all the way to 0%.</p><h2 id="What-does-this-look-like-for-individual-teams">What does this look like for individual teams?</h2><p>Let’s look at the team-strength ratings for the top 6 at the end of 2018/19. This is right in the middle of Liverpool’s red-hot two-year stretch where they recorded the second and fourth-best ever Premier League points tallies.</p><p>The team strength ratings are presented in the form of “predicted goals versus an average Premier League team”. In other words, if you played each of these teams against a (hypothetical) perfectly average team over and over again, how many goals would they score and concede on average.</p><p><img src="top-6-ratings-18-19.jpg" alt=""></p><p>We can see that every one of the top 6 teams was out-performing their xG in attack this season; the goals-only estimate for goals scored (“Offence”) vs average is higher in every case. I think it’s unreasonable to suggest this is just random variation or “luck”. We’re seeing that there’s something missing from the xG values used in this investigation. This suggests that a better xG model (perhaps one built on more detailed data) that helps close the gap between the xG-only and Goals-only models, would have a smaller percentage of Goals-only in the optimal model ensemble.</p><p>This doesn’t tell us about whether individual teams are doing things that help them “beat” xG. It doesn’t take into account team tactics, or individual players’ finishing skill. However, it does shift our default expectation of a team’s quality to about 30% of the way between xG and goals.<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup></p><h3 id="2020-21-ratings">2020/21 ratings</h3><p>I don’t have anything clever to say, but it’s always fun to look at the most up-to-date ratings:</p><p><img src="team-ratings-20-21.png" alt=""></p><h2 id="Conclusions">Conclusions</h2><p>In general, our best estimate of PL teams’ quality is around 30% between their xG and their Goals, when using xG from <a href="http://Understat.com">Understat.com</a>. Overall this strongly suggests that there is information contained within teams’ goalscoring performance that is not captured by xG totals alone.</p><p>On a technical note, this investigation presents a method for combining Poisson-based match prediction models, whilst preserving interpretability of the model parameters. This is useful because the parameter estimates, especially those of team attack and defence ratings, are often of particular interest.</p><p><em>With thanks to <a href="https://kwiatkowski.io">Marek Kwiatkowski</a>, <a href="https://twitter.com/Worville/">Tom Worville</a> and Bobby Gardiner for providing feedback on an earlier draft of this piece.</em></p><p><em>If you enjoyed this article, I made some <a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20">Liverpool 2019/20 “data scarves”</a>, which you can buy (if you’re in the UK). I think they’re fun and you might too.</em></p><h2 id="Code-etc">Code etc</h2><p>If you would like to reproduce this analysis, or dig a bit more into the technical details, the code can be found in my <a href="https://github.com/torvaney/wingback"><code>wingback</code></a> (because it’s concerned with <em>back</em>-testing models… I couldn’t help myself, I’m sorry) repository.</p><p>This repo is built on top of <a href="https://github.com/Torvaney/understat-db"><code>understat-db</code></a>, a project for pulling data from <a href="http://Understat.com">Understat.com</a> into a database. It uses a Python library called <a href="https://github.com/Torvaney/mezzala"><code>mezzala</code></a> for modelling team-strengths and match predictions.</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>I’ve excluded a third scenario where the Goals-only model performs best (a mirror image of xG-only performs best) <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>A more patient version of me would have done both (3) and (4) and seen what difference it made (if any). Perhaps something for a future post? <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p>I looked at log-likelihood on outcomes, mean-squared error (MSE) on per-match total goals and MSE on per-match goal-difference. Each of these gave slightly different results on either side of 30%. <a href="#fnref3" class="footnote-backref">↩︎</a></p></li><li id="fn4" class="footnote-item"><p>Since modelled estimates of team quality <a href="/2018/06/22/dixon-coles-and-xg-together-at-last/#What-about-xG-totals">correlate very closely with total xG/goals scored or conceded</a>, perhaps we could apply this 30/70% rule to xG/Goals <em>totals</em> from Understat, too? <a href="#fnref4" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Following Liverpool’s decisive title win the end of 2019/20, there was discussion that they &lt;a href=&quot;https://www.theguardian.com/football</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="Python" scheme="https://www.statsandsnakeoil.com/tags/python/"/>
    
    <category term="mezzala" scheme="https://www.statsandsnakeoil.com/tags/mezzala/"/>
    
    <category term="Understat-db" scheme="https://www.statsandsnakeoil.com/tags/understat-db/"/>
    
  </entry>
  
  <entry>
    <title>Data-driven football scarves</title>
    <link href="https://www.statsandsnakeoil.com/2021/06/01/data-driven-football-scarves/"/>
    <id>https://www.statsandsnakeoil.com/2021/06/01/data-driven-football-scarves/</id>
    <published>2021-06-01T16:00:00.000Z</published>
    <updated>2022-09-26T13:37:09.938Z</updated>
    
    <content type="html"><![CDATA[<p>Last year, I produced some <a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20">football scarves</a>, patterned with what is (as far as I can tell) a totally original design.</p><p>The design riffs on the <a href="https://www.google.com/search?q=striped+football+scarves&amp;tbm=isch">classic striped scarf</a>, but with an additional, data-driven twist. The colour of each stripe encodes the team’s record (win, lose, or draw) within a given season:</p><p><img src="team-legend-liverpool-2019.jpg" alt="A schematic legend showing how each line of the scarf pattern lines with a match from Liverpool's season. The winning matches correspond to a red stripe, the draws correspond to a yellow stripe, and each loss corresponds to a white stripe."></p><p>And here’s how it looks in the flesh:</p><p><img src="scarf-irl.jpg" alt=""></p><p>These are available for sale <a href="https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20">here</a> (the website only supports UK sales at the moment, but DM me <a href="https://twitter.com/Torvaney">on twitter</a> if you’re non-UK and want to pick one up). Likewise, feel free to get in touch if you want to arrange for some custom scarves to be developed (min. 20 scarves).</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Last year, I produced some &lt;a href=&quot;https://statsandsnakeoil.bigcartel.com/product/liverpool-2019-20&quot;&gt;football scarves&lt;/a&gt;, patterned wit</summary>
      
    
    
    
    
    <category term="data visualisation" scheme="https://www.statsandsnakeoil.com/tags/data-visualisation/"/>
    
  </entry>
  
  <entry>
    <title>Tools for tiny teams</title>
    <link href="https://www.statsandsnakeoil.com/2021/05/28/tools-for-tiny-teams/"/>
    <id>https://www.statsandsnakeoil.com/2021/05/28/tools-for-tiny-teams/</id>
    <published>2021-05-28T10:00:00.000Z</published>
    <updated>2022-09-26T13:37:03.910Z</updated>
    
    <content type="html"><![CDATA[<p>Sometimes, tech teams are tiny. This seems to happen a lot in sports, with oneor two people being expected to cover a lot of different functions from data analysisand reporting to data engineering and web development.</p><p>As a result, tiny teams often need to be very productive, returning great slabs ofbusiness value in order to stick around (or gain further buy-in and influence). There’sbeen plenty of discussion of the strategies people can use to provide valueand win trust (see any one of the <a href="https://gist.github.com/Torvaney/4a2aaa8ab34be50cc9293ed1a44d927e">guest talks from the Opta Pro Forums</a>,for example), and I don’t have much to add on that front. Instead,I’d like to talk about some of the tools that I’ve found useful when trying tocover a lot of ground quickly.</p><p>In general, a lot of the ideas here are covered more succinctly and eloquently by Dan McKinley in his<a href="http://boringtechnology.club/">talk</a>/<a href="https://mcfunley.com/choose-boring-technology">essay</a> “Choose Boring Technology”. Hopefully, I can add <em>some</em> value by talking about some specific technologies and choices I’ve made.</p><h2 id="Databases-SQL">Databases &amp; SQL</h2><p>I have found that having a database, even as a tiny team, is essential since it allows you to move from ad-hoc, manual processes to automated ones. This is important if you want to keep delivering new projects, rather than getting bogged down in manual and maintenance work.<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>And you can’t talk about databases without talking about SQL. SQL might be the most Boring Technology around. It’s everywhere, declarative code is really good, and when you use it, you’re standing on a giant heap of decades of accumulated knowledge. As a result, investing the time in learning to query and manage data with SQL can payoff massively.</p><p>In particular, I’ve gravitated towards two bits of software for managing analytics databases: <a href="https://www.postgresql.org">Postgres</a> and <a href="https://docs.getdbt.com/">dbt</a>.</p><h3 id="Postgres">Postgres</h3><p>I’ve been using Postgres as my default choice for a few years now, and I haven’t ever felt the need to switch.</p><p>Here are some of the things I’ve been particularly pleased with:</p><ul><li>Inlined <a href="https://www.postgresql.org/docs/12/queries-with.html">CTEs</a> (requires Postgres 12 or later) allows you to prioritise readability without paying a performance penalty</li><li>Good extensions<ul><li><a href="https://postgis.net/">PostGIS</a> for spatial queries (e.g. on x,y event data) is very cool and always leaves me looking for more excuses to use it</li><li>As an English-speaker, <a href="https://www.postgresql.org/docs/12/unaccent.html">unaccent</a> is a life-saver for searching accented player and team names</li></ul></li><li>The <a href="https://www.postgresql.org/docs/">official documentation</a> is really good. It’s very rare that I’ve had to resort to googling or stack-overflow instead of just navigating to the appropriate section of the docs.</li><li>It’s supported by dbt (see next point)</li><li>Easy deployment options - more or less every cloud provider has a managed Postgres offering.<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></li></ul><p>I would consider moving to something designed specifically for data-warehousing (e.g. Snowflake) aspart of a merely <em>small</em> team, but advantages of Postgres make it more attractive to me in a tech team of 1-2.<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p><h3 id="dbt">dbt</h3><p>The <a href="https://docs.getdbt.com">dbt docs</a> are great, and state that:</p><blockquote><p>dbt (data build tool) enables analytics engineers to transform data in their warehouses by simply writing select statements. dbt handles turning these select statements into tables and views.</p><p>dbt does the T in ELT (Extract, Load, Transform) processes – it doesn’t extract or load data, but it’s extremely good at transforming data that’s already loaded into your warehouse. [See <a href="https://fivetran.com/blog/etl-vs-elt">this article</a> for a breakdown of the difference between ETL and ELT.]</p><p>The role of dbt within a modern data stack is discussed in more detail <a href="https://blog.getdbt.com/what--exactly--is-dbt-/">here</a>.</p><p>dbt also enables analysts to work more like software engineers, in line with the dbt <a href="https://docs.getdbt.com/docs/about/viewpoint">Viewpoint</a>.</p></blockquote><p>In short, dbt enables you to manage your data using only SQL select statements with <a href="https://jinja.palletsprojects.com/en/3.0.x/">jinja</a> templating.</p><p>I recommend investigating the links above for more detail and examples, but here are some of the reasons I love using it:</p><p>dbt…</p><ul><li>Leverages extremely common tech in the form of SQL, so it’s relatively easy to debug, get advice and onboard new people onto a project compared to an equivalent ETL pipeline</li><li>Makes it easy to test your data, giving you confidence that your calculations are correct</li><li>Has an active and helpful community - if you’re stuck on something, it’s likely that someone else has an answer</li><li>Works with Postgres (but can be ported without too much hassle to a cloud data platform if you outgrow Postgres)</li><li>Uses Jinja, which has nice synergy with Flask, where it’s used for HTML templating. A small win, admittedly, but nice nonetheless</li></ul><p>But perhaps the most useful thing of all is how it flattens what I’ll call “the gradient of automation”<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>. If you use SQL queries for all of…</p><ul><li>One-off queries and ad-hoc analyses</li><li>Automated scripts, reports, etc</li><li>Data transformations (ELT)</li></ul><p>… it makes it very easy to move from a query “up” a level of automation. A query used to validate a specific question as a one-off might prove useful enough to add to a weekly report. The information might then prove to be worth staging as a pre-calculated table so that it can be used in other analyses, or joined to other data sources for more insights.</p><p>Like Postgres, dbt is open-source, but there is a <a href="https://cloud.getdbt.com">hosted service</a> that you can use to manage your dbt deployment (scheduling and monitoring jobs, etc).</p><p>Overall, I have found dbt to be an incredibly productive tool that Just Works, and I am very happy using it.</p><h3 id="Records-PugSQL">Records/PugSQL</h3><p>Remember how I said I liked using SQL for everything? That extends to using SQL for querying within apps, reports, and so on.</p><p>There’s a family of libraries out there that enable you to expose SQL queries as functions within your programming language of choice. In Python, there’s <a href="https://pugsql.org/">PugSQL</a> and the more minimalist <a href="https://github.com/kennethreitz/records">Records</a>.</p><p>I appreciate it may not be everyone’s cup of tea, but maximising the use of SQL reduces the number of different things I need to know while being productive.</p><h3 id="Learning-SQL">Learning SQL</h3><p>If you’re looking for a place to start learning SQL, <a href="https://www.executeprogram.com/courses">Execute Program</a> has a course (first 16 lessons are free, after that there’s a paid subscription) which incorporates spaced repetition. I haven’t used it (yet!), but <a href="https://mike.place/2020/executeprogram/">other people seem to like it</a>.</p><p>I’ve also developed a small project for <a href="/2021/05/18/understat-db-a-database-of-ones-own/">creating a database of Understat data</a>, that might be worth checking out if you want toget a bit more experience querying and working with relational databases. This project uses both Postgres and dbt, and is designed to be altered, extended, and remixed.</p><h2 id="Web-applications">Web applications</h2><p>Being able to deploy to the web is extremely useful. Having a platform to expose reports, models, or input data, even if it’s just for your own use, can go a really long way.<sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup></p><p>However, I think web development can be a bit of a trap for non-experts (like me). For this reason, I tend to be extremely Javascript-averse and choose the least exciting options when developing anything web-related<sup class="footnote-ref"><a href="#fn6" id="fnref6">[6]</a></sup>. While my web apps will never have the sickest UIs, I can keep the maintenance cost manageable, and get more work done overall.</p><h3 id="Flask-Django">Flask/Django</h3><p>As part of my Boring Web Apps, I tend to develop server-side rendered applications. In Python, the ones I have most familiarity with are Flask and Django.</p><p>Both are extremely widely used which means there’s lots of support and documentation available online.Both use HTML templating, which is another old/Boring technology. Having decent knowledge of HTML can also come in handy with any web-scraping projects.</p><p>There are also many managed hosting options for app deployment that I have found very useful.</p><h3 id="Bootstrap">Bootstrap</h3><p>Bootstrap is “the world’s most popular framework for building responsive, mobile-first sites”.</p><p>I might not mind a website looking like it’s from 2010, but I would at least like it to look like a <em>nice</em> website from 2010. That’s what I use <a href="https://getbootstrap.com/">Bootstrap</a> for.</p><p>While I’m sure there’s myriad other options available that fill the same niche, Bootstrap is the one I’ve been using for years, and I haven’t felt a burning need to switch. If you think I’m really missing out on something <a href="https://twitter.com/Torvaney">let me know!</a></p><h2 id="General-themes">General themes</h2><p>I think there are a few themes running through the decisions I’ve made on these types of teams:</p><ul><li>As part of a tiny tech team, time is likely to be your biggest constraint<ul><li>Paying attention to the time required to maintain your work if essential to remaining productive beyond the short-term</li></ul></li><li>Embrace frameworks for high-reward given time/effort<ul><li>Tools that are good enough (even if not perfect) can allow you to spendmore time on your core work</li><li>Using popular frameworks can also make it easier to hand-off to and/or integrate new team members</li></ul></li><li>Invest in core skills/tools that can be applied widely and robustly<ul><li>Established tech is more likely to be well tested, and is easier to find learning resources,documentation, and help online ~if~ when something goes wrong</li><li>Being comfortable with boring, ubiquitous tech like relational databases and simpleweb-applications allows you to have a big impact across a range of domains</li><li>Or, to steal from Dan McKinley, <a href="http://boringtechnology.club/">choose boring technology</a>. There’s so much good stuff in this <a href="http://boringtechnology.club/">talk</a>/<a href="https://mcfunley.com/choose-boring-technology">essay</a>.</li></ul></li><li>Learn to love problems, not solutions<ul><li>Focusing on problems to solve (as opposed to a specific solution)enables you to iterate effectively and not get stuck on getting anelegant idea to work, or endlessly refining a product that’s good enough for now</li></ul></li></ul><p>I <em>think</em> these things amount to okay advice. But I am still early on in my career, and the trade-offs for small, medium and large teams can be quite different. In an industry where lots of people are trying to get hired, we don’t often talk about the boring stuff. I think that’s a shame, because the boring stuff is often what makes you productive.</p><p>As the industry matures, and more organisations build enough trust to invest in full tech and data teams, I think we’ll see these kinds of “tech+data generalist” roles start to die out. However, for the time being, these roles exist and these are some of the bits and pieces I’ve been using to get (some) things done.</p><p>If you think I’m missing out on something great that you’ve been using, or if your experiences in a similar role have been different, you can reach me <a href="https://twitter.com/Torvaney">on twitter</a> - I’d love to hear from you!</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Knowing when not to bother automating something has been a pretty useful skill as well, to be fair. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>It can be cheaper to manage your own database, but I have generally opted for a managed solution. I’ve been really grateful for the ease of adding monitoring, alerting, backups, replicas etc and so I’m happy to pay a bit more for the privilege in a tiny-team environment. <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p>See some discussion on Postgres for data warehousing on <a href="https://softwareengineeringdaily.com/2021/03/02/postgres-data-warehouse-with-josh-drake-and-thomas-richter/">SE Daily</a> and <a href="https://www.narrator.ai/blog/using-postgresql-as-a-data-warehouse/">narrator.ai</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p></li><li id="fn4" class="footnote-item"><p>I promise I tried to think of a term for this. But I couldn’t do it - I’m sorry. <a href="#fnref4" class="footnote-backref">↩︎</a></p></li><li id="fn5" class="footnote-item"><p>If I were building a new tech department in a sports team, a web developer would probably be one of my first hires <a href="#fnref5" class="footnote-backref">↩︎</a></p></li><li id="fn6" class="footnote-item"><p>In cases where interactivity has been really important, I have had great experiences with <a href="https://elm-lang.org/">Elm</a>. I’ve evangelised about Elm to plenty of people in-person and heartily recommend anyone reading to try it out. However, for truly tiny teams, I have generally opted for server-side rendering ahead of an Elm SPA (single-page app). <a href="#fnref6" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Sometimes, tech teams are tiny. This seems to happen a lot in sports, with one
or two people being expected to cover a lot of different f</summary>
      
    
    
    
    
    <category term="sql" scheme="https://www.statsandsnakeoil.com/tags/sql/"/>
    
    <category term="tools" scheme="https://www.statsandsnakeoil.com/tags/tools/"/>
    
    <category term="dbt" scheme="https://www.statsandsnakeoil.com/tags/dbt/"/>
    
    <category term="postgres" scheme="https://www.statsandsnakeoil.com/tags/postgres/"/>
    
    <category term="flask" scheme="https://www.statsandsnakeoil.com/tags/flask/"/>
    
    <category term="bootstrap" scheme="https://www.statsandsnakeoil.com/tags/bootstrap/"/>
    
  </entry>
  
  <entry>
    <title>understat-db: creating a database of one&#39;s own</title>
    <link href="https://www.statsandsnakeoil.com/2021/05/18/understat-db-a-database-of-ones-own/"/>
    <id>https://www.statsandsnakeoil.com/2021/05/18/understat-db-a-database-of-ones-own/</id>
    <published>2021-05-18T10:00:00.000Z</published>
    <updated>2022-09-26T13:36:55.138Z</updated>
    
    <content type="html"><![CDATA[<h2 id="What-is-it">What is it?</h2><p><code>understat-db</code> is a project for creating your own database of football (soccer) datafrom <a href="understat.com">understat.com</a>.</p><p>You can find the project’s source code <a href="https://github.com/Torvaney/understat-db">on Github</a>.</p><p><img src="screenshot.jpg" alt="A screenshot showing the project's github page, a terminal window with the projects help text and a SQL query (with results) run against the project database."></p><h2 id="Why-should-you-use-it">Why should you use it?</h2><p>This project isn’t prescriptive, so there’s no “correct” (or “incorrect”) way to use it. But there are two main ways in which I think it might be useful:</p><ul><li>As a project to create a standalone database of football data<ul><li>For example, using the data to create one-off visualisations in a separate project</li><li>Or, as a way to get started with learning SQL, without having to create a database</li></ul></li><li>As a base project to modify and extend to suit your needs<ul><li>For example, if you wanted to store additional data or views in the database</li></ul></li></ul><h2 id="Why-did-I-make-it">Why did I make it?</h2><p>There are a few blog posts/experiments I’d like to write up, and I generally like to make my posts/projects repeatable. This means including any code + data required to reproduce the results. In the past, I’ve tended to embed the code inside the post notebook-style. However, this has 2 drawbacks for me.</p><p>Firstly, it makes it difficult to build upon previous work - you have to go overany required code <em>again</em>, or ask the reader to refer back to a past post. Neither of these strike me as great options.</p><p>Secondly, the linear format of a blog-post or notebook isn’t always the clearest way to communicate. Especially when dealing with concepts in code, which doesn’t necessarily have a clear start, middle, and end.</p><p>Hopefully, having some form of packaging/code distribution will help separate the blog content from the code. One or two of these future projects will build on top of <code>understat-db</code>, but I thought it would be useful enough to release as a standalone project.</p><p>(I also wanted to try out <a href="https://github.com/fastai/nbdev">nbdev</a> and this seemed like a good fit (see <a href="#nbdev">“nbdev”</a>).)</p><h2 id="What’s-next">What’s next?</h2><p>If this project is useful to enough people, I’d like to flesh out the documentation and write one or two tutorials. If this sounds good to you, or (even better), you’d like to help - <a href="https://twitter.com/Torvaney">let me know</a>!</p><p>I’d also like to release something similar for working with Statsbomb’s public data, but that’s a little further down my list at the moment. In any case, if that sounds interesting to you, watch this space.</p><h2 id="Other-info">Other info</h2><p>This project uses a few different bits of software:</p><ul><li>Python (3.6+)</li><li>Postgres (12+ recommended)</li><li><a href="https://github.com/fastai/nbdev">nbdev</a></li><li><a href="https://docs.getdbt.com">dbt</a></li><li>Docker (optional - used for the database)</li></ul><p>You don’t necessarily need to know each of these tools to use the project, but it’s worth being aware of them if you want to extend or fork the project.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;What-is-it&quot;&gt;What is it?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;understat-db&lt;/code&gt; is a project for creating your own database of football (soccer) data
from</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Python" scheme="https://www.statsandsnakeoil.com/tags/python/"/>
    
    <category term="mezzala" scheme="https://www.statsandsnakeoil.com/tags/mezzala/"/>
    
    <category term="Understat-db" scheme="https://www.statsandsnakeoil.com/tags/understat-db/"/>
    
    <category term="tools" scheme="https://www.statsandsnakeoil.com/tags/tools/"/>
    
  </entry>
  
  <entry>
    <title>The dumbest xG model</title>
    <link href="https://www.statsandsnakeoil.com/2021/04/25/the-dumbest-xg-model/"/>
    <id>https://www.statsandsnakeoil.com/2021/04/25/the-dumbest-xg-model/</id>
    <published>2021-04-25T09:23:51.000Z</published>
    <updated>2022-09-26T13:37:05.724Z</updated>
    
    <content type="html"><![CDATA[<p>A couple of months ago, I experimented with building an xG model by fine-tuning an image classifier (CNN) originally trained on ImageNet. I used Statsbomb freeze-frames to generate images and fine-tuned the model on those images. The model managed to learn some of the basic dynamics of goal probability (distance, angle) from simple representations of shots, but performed worse on images containing opposition player locations.</p><p><a href="https://twitter.com/openGoalCharles">Charles William</a> used a <a href="https://www.opengoalapp.com/xg-with-cnns-full-study">more thoughtful approach</a> when experimenting with using CNNs to derive xG estimates. This approach yielded much better results, and represents a much better starting point for anyone wanting to pursue these ideas seriously.</p><p>Nonetheless, I had some fun exploring these ideas and I thought it was worth throwing the results up here in case anyone else is interested.</p><h2 id="Background">Background</h2><p>Recently, I’ve been working through the <a href="https://github.com/fastai/fastbook">fastai book</a>. The iterative, application-focused style of the book gives it a smooth learning curve, and if you have an interest in deep learning I would happily recommend it.</p><p>The fastai approach aims to be pragmatic and falls more on the side of “ML for product development” (as opposed to what I’d call “ML for insight”). In particular, it takes an end-to-end approach to teaching machine learning that frequently leverages <a href="https://en.wikipedia.org/wiki/Transfer_learning">transfer learning</a>:</p><blockquote><p>Transfer learning (TL) is a research problem in machine learning (ML) that focuses on storing knowledge gained while solving one problem and applying it to a different but related problem. For example, knowledge gained while learning to recognize cars could apply when trying to recognize trucks.</p></blockquote><p>The book stresses that this can be a good choice for real-world applications and that you don’t need a PhD to achieve excellent (even state-of-the-art) results with creative applications of transfer learning. For example, <a href="https://towardsdatascience.com/audio-classification-using-fastai-and-on-the-fly-frequency-transforms-4dbe1b540f89">using image classifiers on spectograms to classify sounds</a>.</p><p>This got me thinking - could we use transfer learning to train an xG model?</p><h2 id="Motivation">Motivation</h2><p>Part of my motivation (the main part) for this project was to try out some elements of the fastai library that I’ve read about in the book.</p><p>However, I think there are the kernels of a few ideas that might be interesting, too:</p><h3 id="Training-on-small-datasets">Training on small datasets</h3><p>I am often asked about xG models by practitioners in under-served areas (for example, lower-leagues). In many cases, people want (or have been asked by their boss) to calculate xG values for their games, where the only data they have is manually coded by themselves.</p><p>I’m generally reluctant to advise use of a specific xG model. The dynamics of goal-scoring (and the game more generally) in the Premier League are going to be different to those in the lower leagues. Furthermore, hand-collected data will be very different to that of a major data provider (the <a href="https://www.americansocceranalysis.com/home/2019/9/30/shots-in-the-dark-how-data-providers-tell-us-different-versions-of-what-happened">major data providers can’t even agree amongst themselves</a>). For these reasons, a model trained on industry-standard top-league data may not generalise well to these domains<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p><p>But maybe being able to fine-tune a pre-trained xG model on your hand-collected data could be useful?</p><h3 id="Sharing-and-extending-models">Sharing and extending models</h3><p>Similar to above, if xG models had a common interface, then maybe we could share/compare/etc xG models more easily? This would make fine-tuning easier, too. The idiomatic way to do this with fastai seems be to use images as that interface, and so that is what I did.</p><p>An alternative solution might be to agree a standard schema for representing shots like the folks at KU Leuven have done with <a href="https://dtai.cs.kuleuven.be/sports/blog/introducing-atomic-spadl-a-new-way-to-represent-event-stream-data">event data and SPADL</a>. However, images do have one advantage as a model input that a more structured input lacks.</p><p>Perhaps, using images would enable you to add <em>new</em> features to the images before fine-tuning? Since images <em>are</em> a shared representation of sorts?</p><p>Yeah, this makes me cringe a bit, too. But, like I said, <em>kernels</em> of ideas that <em>might</em> be interesting.</p><h2 id="Method">Method</h2><p>At a high level, the method went something like:</p><ul><li>Generate images representing shots</li><li>Train a classifier (fine-tuned <a href="https://www.kaggle.com/pytorch/resnet34">resnet34</a>) for whether the shot resulted in a goal or not (xG)<ul><li>I ended up training on 4000 shots for this experiment. This strains the “small dataset” point a bit, but I figured I’d see if it worked on a larger dataset, and then see how dataset size affected performance as a second step.</li></ul></li><li>Test xG on out-of-sample images</li></ul><p>The code is <a href="https://github.com/Torvaney/image-xg">on GitHub</a> if you want to give it a spin for yourself.</p><p>I experimented with a few image types to feed into the model. For this initial experiment, I thought it would be fun to keep the images as “human-readable” as possible, and ended up with the following:</p><ul><li>“Triangle” plot - taking the triangle made between the shot location and the goalposts: <img src="triangle.png" alt="A pitch-plot with the triangle made between the shooter's location and each goalpost"></li><li>Minimal voronoi - A voronoi chart using only the goalkeeper and shooter’s location: <img src="minimal_voronoi.png" alt="A voronoi chart using only the "></li><li>Basic freeze-frame - a simple pitch-plot with each opposition player and goalkeeper shown as a single point. The shot triangle is overlain as well: <img src="basic.png" alt="A freeze-frame plot, showing each opposition player as a point on top of a pitch-plot"></li><li>Voronoi freeze-frame - A voronoi chart using the location of all opposition players and the shooter, with shot triangle overlain: <img src="voronoi.png" alt="A voronoi chart with shot triangle on a pitch-plot"></li></ul><h2 id="Results">Results</h2><h3 id="Root-Mean-Squared-Error">Root Mean-Squared Error</h3><p>Let’s look at the Root Mean-Squared Error (RMSE) of the various models. The RMSE is calculated as <code>sqrt(mean( (xG - is_goal)^2 ))</code>, where <code>is_goal</code> is <code>1</code> for a goal, and <code>0</code> for no goal. Lower is better.</p><p>The RMSE for each of the image models, an “ensemble” (the average of the xG estimates from the <code>minimal_voronoi</code> and <code>triangle</code> models), and Statsbomb’s own xG is shown below.</p><p>I also added a benchmark “model”, which uses the average conversion rate of all shots as its xG estimates. This represents the no-information scenario, which all models should beat.</p><p><img src="model-rmse.jpg" alt=""></p><p>There are a couple of things that jump out immediately:</p><ul><li>All the models do much worse than the Statsbomb xG</li><li>The simpler images (<code>triangle</code>, <code>minimal_voronoi</code>) performed better than those that used the full freeze-frame data<ul><li>The more “complex” images were prone to over-fitting on the training data <em>before</em> reaching the accuracy of the simpler models</li></ul></li></ul><p>However, I don’t think the results (for simple models) are <em>terrible</em>. An RMSE of about 0.29 is around the same ballpark as a couple of other models (logistic regression, random forest) that I threw at the same dataset, trained on “simple” (non freeze-frame) features.</p><h3 id="Distribution-of-predictions">Distribution of predictions</h3><p><img src="xg-histogram.png" alt=""></p><p>Looking at the distribution of predicted xG, we can see that the “complex” models (<code>basic</code>, <code>voronoi</code>) tend to be much more conservative than the simple models (<code>triangle</code>, <code>minimal_voronoi</code>); they refrain from making very low and very high xG estimates.</p><p>Although the simple models are better in this respect, their xG distributions still show some clumping when compared to Statsbomb’s estimates.</p><p>For the simple models, I think this is understandable on the grounds that they don’t actually have access to more advanced features. However, it’s disappointing to see from the complex models which <em>could</em> (in theory) pick up on this.</p><p><strong>So overall, not great.</strong></p><p>Perhaps the clean, geometric nature of the shot images was too different from the type of images it was trained on (imagenet)? Could more idiomatic (i.e. computer-friendly, rather than human-friendly) images help close the gap to Statsbomb’s model?</p><h2 id="Further-developments">Further developments</h2><p>Is it worth taking this idea further? I’m on the fence.</p><p>On the one hand, the top-level results for “simple” images are relatively promising. Maybe there’s a better representation of freeze-frames that the model can more effectively learn from?</p><p>On the other hand, there are so many practical pitfalls and flaws with this method. Without state-of-the-art results, I don’t see why you’d persist with this over a more reliable, easily explainable method<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>. For serious applications, I think careful, methodical investigations are going to be more appropriate. One such case is <a href="https://twitter.com/openGoalCharles">Charles William’s</a> work, <a href="https://www.opengoalapp.com/xg-with-cnns-full-study">“Evaluating the Performance of Convolutional Neural Network Based xG Models”</a>.</p><p>So I’m not sure. I think I’ll leave it for the time being, but if I might revisit it if I can think of a better way to show the full freeze-frames in image format. It would be fun to get somewhere close to Statsbomb’s error rate with a relatively wacky idea like this.</p><h2 id="Code-etc">Code etc</h2><p>The code for this project is available <a href="https://github.com/Torvaney/image-xg">on GitHub</a>.</p><p>I used the <a href="https://drivendata.github.io/cookiecutter-data-science/">cookiecutter data science template</a> for this project. I used this partly out of curiosity, but also in the hope it might make it easier for people to modify or experiment with.</p><p>What do you think? <a href="https://twitter.com/torvaney">let me know</a>.</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>As far as I’m aware, this has has not been well-studied. Maybe something for a future post? <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>Even then, I’d happily trade-off some top-level performance in exchange for reliability and explainability <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;A couple of months ago, I experimented with building an xG model by fine-tuning an image classifier (CNN) originally trained on ImageNet.</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
  </entry>
  
  <entry>
    <title>An experiment with auto-highlights</title>
    <link href="https://www.statsandsnakeoil.com/2021/04/11/semi-autogenerated-highlights-with-youtube-shorts/"/>
    <id>https://www.statsandsnakeoil.com/2021/04/11/semi-autogenerated-highlights-with-youtube-shorts/</id>
    <published>2021-04-11T16:30:49.000Z</published>
    <updated>2021-04-26T12:51:44.905Z</updated>
    
    <content type="html"><![CDATA[<p>Back in February, I came across a video promising an easy way to game the YouTube algorithm:</p><style>.embed-container {    position: relative;    padding-bottom: 56.25%;    height: 0;    overflow: hidden;    max-width: 100%;  }  .embed-container iframe, .embed-container object, .embed-container embed {    position: absolute;    top: 0;    left: 0;    width: 100%;    height: 100%;  }  </style><div class="embed-container"><iframe src="https://www.youtube.com/embed/V3EyAlFZ-ok" allowfullscreen frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe></div><br><details>  <summary><i style="color: gray">Video description</i></summary><blockquote><p>Youtube is the greatest video sharing platform on the web and every now and again everyone finds a new way to exploit the website to glitch and bug the algorithm to become the largest channels on the website. In the 2010s we saw gaming channels accidentally use an algorithm glitch to become the only things youtube recommended. Now however youtube has a new secret exploit! There are many ways of explaining how to grow on youtube or how to get more views or how to get subscribers. Today however we will be using an algorithm glitch to gain more views than we should ever get as well as thousands of subscribers and preferential promotion in the algorithm. Have you ever wandered how the youtube algorithm works well guess what the new youtube algorithm 2021 edition is completely broken and full of bugs, glitches and exploits! If you ever wanted to know how to grow on youtube then you probably thought the answer was to make more videos but no! instead the spiffing brit will explain the youtube algorithm to show how dream exploited the youtube algorithm 2020 to gain millions of subscribers.</p></blockquote><blockquote><p>So today I show you how to Break the youtube algorithm by beating it by creating shorts that overpower the youtube short shelf and get millions of views!</p></blockquote></details><p>In short, the video states that you can “[use] an algorithm glitch to gain … thousands of subscribers” by uploading Shorts (vertical format, &lt;1min long Youtube videos).</p><p>I would probably call this “promoting Youtube’s latest new feature” rather than a “glitch”. It seems a bit like calling Netflix giving preferential treatment to its Original series, “gaming the algorithm”.</p><p>But still, I was curious. Could it really be that easy?</p><h2 id="The-plan">The plan?</h2><ul><li>Autogenerate clips of spectacular goals and woodwork strikes (that is, from shots with the lowest xG). I already had some code that I could re-purpose to this end from <a href="http://www.statsandsnakeoil.com/2020/10/15/fully-automated-luxury-commentary/">Fully Automated Luxury Commentary</a>.</li><li>Upload them to Youtube in Shorts format</li><li>???</li><li>Profit</li></ul><h2 id="The-results">The results</h2><p>It went terribly! Didn’t work at all!</p><p>I uploaded 28 videos (scheduled at a rate 1-2 per day) which received 101 views total.</p><h3 id="Some-things-which-seemed-to-have-some-positive-effect-on-the-number-of-views">Some things which seemed to have some (positive) effect on the number of views:</h3><ul><li>Watching the video back on Youtube after uploading - I’m guessing that the initial boost to the views number is favourable to the algorithm</li><li>Videos with household name players or top teams in the title</li><li>Clickbaity titles - unsurprisingly ambiguous ones tended to result in more views than ones which clearly stated what the video was about</li></ul><h3 id="Some-things-which-didn’t-seem-to-have-an-effect-for-me">Some things which didn’t seem to have an effect (for me):</h3><ul><li>Manually picking thumbnails (in fairness, I put very little effort into the custom thumbnails)</li><li>Uploading 2 videos in a day (vs 1 video)</li></ul><p>So far, pretty unsurprising and pretty uninspiring.</p><h2 id="So-what-did-I-learn">So what did I learn?</h2><p>This project had little intrinsic value to me; I don’t tend to watch short-format videos on any platform. And if <em>I’</em> not that interested in my videos, I’m not sure why I could persuade anyone else to be interested<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. Perhaps if my primary motivation was fame or money, it would be different?</p><p>On reflection, I think the project was sort of doomed to fail from the start. The idea that short football clips might have widespread appeal was misguided; it wasn’t based on any theory or understanding of why we like the sport. Sure, some moments of football may have an intrinsic aesthetic appeal, but the clips I used fell completely flat when stripped of context and culture.</p><p>Ultimately, I think the values of what I created was at odds with the values of football. I think it’s very difficult to make something like that succeed (we don’t have a European Super League yet, for instance), and I don’t particularly want to try.</p><p>So, lesson learned<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> - stick to making things I don’t hate.</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>That’s not to say these are inherently uninteresting. There are many talented artists, educators, and entertainers using these platforms. I’m just not able to tap into any internal sense of value as a viewer, resulting in flat, uninteresting ideas like the one described here. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>Lesson confirmed? It’s not exactly a new idea, is it? <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Back in February, I came across a video promising an easy way to game the YouTube algorithm:&lt;/p&gt;
&lt;style&gt;.embed-container {
    position: </summary>
      
    
    
    
    
    <category term="Auto-highlights" scheme="https://www.statsandsnakeoil.com/tags/auto-highlights/"/>
    
  </entry>
  
  <entry>
    <title>Are bad shots actually good?</title>
    <link href="https://www.statsandsnakeoil.com/2021/01/24/are-bad-shots-actually-good/"/>
    <id>https://www.statsandsnakeoil.com/2021/01/24/are-bad-shots-actually-good/</id>
    <published>2021-01-24T17:21:17.000Z</published>
    <updated>2022-09-26T13:37:02.367Z</updated>
    
    <content type="html"><![CDATA[<p>Last month, I saw <a href="https://twitter.com/HemmenKees/status/1339990314606620674">an interesting tweet</a> from <a href="https://twitter.com/HemmenKees">Kees van Hemmen</a> (emphasis is my own):</p><blockquote><p>Correct me if I’m wrong here but, <strong>if you’re a low quality side in a league, you’d actually prefer a dozen long shots</strong> (lets say each of 0.05 xG) to one great chance (0.6 xG) because there would be a higher variability of outcomes, which would mean you’d have a <strong>higher expected points from a given match</strong>.</p></blockquote><p>That teams should prioritise high-quality shots has been a common refrain in the public analytics sphere for years. The fact that fewer, high-quality shots can increase win % is even the default example in Danny Page’s <a href="https://danny.page/expected_goals.html">Match Expected Goals Simulator</a>.</p><p>Over time, there’s been an increasing number of counter-arguments that poor-quality shots (long-shots especially) may have some utility. For example, by keeping a team’s attack from being too predictable, or being a <a href="https://twitter.com/NathanAClark/status/1318665017944952832">generally positive expected-value way to dispose of a possession</a>.</p><p>However, Kees’s hypothesis is particularly interesting because he’s saying that, under certain circumstances (a very bad defence), teams should prefer lots of poor-quality shots, for the <em>same reason</em> that analysts were arguing for high-quality shots: it gets you more points.</p><h2 id="What’s-the-intuition-behind-this-idea">What’s the intuition behind this idea?</h2><p>Let’s say we have two teams, who both create 0.95 xG. One of those teams takes 32 shots at about 0.03 xG each (i.e. a 3% chance of scoring each shot). The second team takes just 3 shots (0.32 xG each, 32% chance of scoring each shot). How many goals is each team expected to score?</p><p><img src="kees-explained.png" alt=""></p><details><summary><i style="color: gray">Expand for R code</i></summary><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line"></span><br><span class="line">total_xg_for &lt;- 0.95</span><br><span class="line"></span><br><span class="line">shot_quality &lt;- tibble(</span><br><span class="line">  label = <span class="built_in">c</span>(<span class="string">&quot;Very bad&quot;</span>, <span class="string">&quot;Bad&quot;</span>, <span class="string">&quot;Okay&quot;</span>, <span class="string">&quot;Good&quot;</span>, <span class="string">&quot;Very good&quot;</span>) %&gt;%</span><br><span class="line">    paste(<span class="string">&quot;chances&quot;</span>) %&gt;%</span><br><span class="line">    as_factor(),</span><br><span class="line">  xg = total_xg_for,</span><br><span class="line">  n_shots = <span class="built_in">c</span>(<span class="number">35</span>, <span class="number">20</span>, <span class="number">10</span>, <span class="number">5</span>, <span class="number">3</span>),</span><br><span class="line">  xg_per_shot = xg / n_shots,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">get_goal_probabilities &lt;- <span class="keyword">function</span>(n_shots, xg_per_shot) &#123;</span><br><span class="line">  tibble(</span><br><span class="line">    goals = <span class="number">0</span>:n_shots,</span><br><span class="line">    prob  = dbinom(<span class="number">0</span>:n_shots, n_shots, xg_per_shot)</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">shot_quality_goals &lt;-</span><br><span class="line">  shot_quality %&gt;%</span><br><span class="line">  mutate(probs = map2(n_shots, xg_per_shot, get_goal_probabilities)) %&gt;%</span><br><span class="line">  unnest(cols = <span class="built_in">c</span>(probs))</span><br><span class="line"></span><br><span class="line">shot_quality_goals %&gt;%</span><br><span class="line">  filter(label %in% <span class="built_in">c</span>(<span class="string">&quot;Very bad chances&quot;</span>, <span class="string">&quot;Very good chances&quot;</span>)) %&gt;%</span><br><span class="line">  ggplot(aes(x = goals, y = prob)) +</span><br><span class="line">  geom_col() +</span><br><span class="line">  geom_vline(xintercept = <span class="number">2.5</span>, linetype = <span class="string">&quot;dashed&quot;</span>, colour = <span class="string">&quot;red&quot;</span>) +</span><br><span class="line">  scale_x_continuous(limits = <span class="built_in">c</span>(-<span class="number">0.5</span>, <span class="number">5.5</span>), breaks = <span class="number">0</span>:<span class="number">10</span>) +</span><br><span class="line">  scale_y_continuous(labels = scales::percent_format(accuracy = <span class="number">1</span>)) +</span><br><span class="line">  facet_wrap(vars(label, xg_per_shot),</span><br><span class="line">             labeller = labeller(</span><br><span class="line">               label = label_value,</span><br><span class="line">               xg_per_shot = as_labeller(<span class="keyword">function</span>(x) &#123;</span><br><span class="line">                 paste0(<span class="string">&quot;xG per shot: &quot;</span>, scales::number(<span class="built_in">as.numeric</span>(x), accuracy = <span class="number">0.01</span>))</span><br><span class="line">               &#125;)</span><br><span class="line">             )) +</span><br><span class="line">  labs(title = <span class="string">&quot;Bad chances are better if you&#x27;re guaranteed to concede 3 goals?&quot;</span>,</span><br><span class="line">       subtitle = str_glue(<span class="string">&quot;Probability of _ goals from &#123;total_xg_for&#125; xG&quot;</span>),</span><br><span class="line">       x = <span class="string">&quot;Goals scored&quot;</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(plot.title = element_text(face = <span class="string">&quot;bold&quot;</span>),</span><br><span class="line">        panel.grid.major.x = element_blank(),</span><br><span class="line">        panel.grid.minor.x = element_blank())</span><br></pre></td></tr></table></figure></details><p>The very bad shots have a greater chance of scoring no goals, and a lower chance of scoring 1 or 2 goals than the very good shots. However, they also have a greater chance of scoring 3 or more goals.</p><p>In matches where they concede 2 goals, the first team has a better chance of winning than the second team, despite being less likely to draw. And in matches where 3 goals are conceded, the second team would never do better than a draw! At the bottom end of the table, those extra points can be vital.</p><p><strong>So, we can see that there are extreme scenarios where this argument <em>does</em> hold water.</strong></p><p>At this point, I should point out that this scenario is adapted from a point that Kees himself made in a follow-up to the original tweet:</p><blockquote><p>One 0.6 xG shot can yield at most one goal, whereas 12 0.05 xG shots can yield many goals but also are more likely to yield none.</p></blockquote><p>But this isn’t an <em>entirely</em> realistic scenario, is it?</p><p>In real life, even bad teams keep clean sheets occasionally. This is because thenumber of goals conceded in a given match follows a distribution. No team consistently concedes 3 goals per game, and even very bad teams keep clean sheets occasionally.</p><h2 id="Can-we-test-in-a-more-realistic-setting">Can we test in a more realistic setting?</h2><p>I think so! My idea for investigating these dynamics further looks like this:</p><ul><li><p>Take <strong>5 chance quality scenarios</strong>, where the total xG generated is a constant 0.95 per game:</p><table><thead><tr><th>Chance quality</th><th>xG per shot</th></tr></thead><tbody><tr><td>Very bad chances</td><td>0.03</td></tr><tr><td>Bad chances</td><td>0.05</td></tr><tr><td>Okay chances</td><td>0.10</td></tr><tr><td>Good chances</td><td>0.19</td></tr><tr><td>Very good chances</td><td>0.32</td></tr></tbody></table></li><li><p>And <strong>5 defence quality scenarios</strong>, assuming goals conceded per game follow a Poisson distribution:</p><table><thead><tr><th>Defence quality</th><th>Avg. goals conceded</th></tr></thead><tbody><tr><td>Very bad defence</td><td>2.50</td></tr><tr><td>Bad defence</td><td>1.80</td></tr><tr><td>Okay defence</td><td>1.30</td></tr><tr><td>Good defence</td><td>0.90</td></tr><tr><td>Very good defence</td><td>0.70</td></tr></tbody></table></li><li><p>Then, see <strong>how teams’ chances of getting a result vary for each combination of defence/chance quality</strong>, by simulating the goals scored and conceded.</p></li></ul><p>This is, of course, a gross simplification of what the real world looks like. In particular:</p><ul><li>Goals conceded over a season doesn’t actually follow a Poisson distribution. This is driven by variation in the goalscoring rate from match-to-match and within matches.</li><li>We have assumed that goalscoring and goal concession occur independently. In real life, teams’ tactics and motivation is influenced by the scorline.</li></ul><p>However, I think this rough approximation is good enough to improve our understanding of the trade-off between attacking chance-quality and results. A more sophisticated model of shot generation developed by Marek Kwiatkowski can be found <a href="http://www.kwiatkowski.io/rethinking-shots">here</a>, and could form the basis of a more thorough analysis of these trade-offs.</p><h2 id="Results">Results</h2><p>With these assumptions, we can see a <em>very</em> slight uplift to win chance for teams creating 0.95 xG across lower quality chances, when their defence is expected to concede more than about 1.8 goals:</p><p><img src="win-prob-grid.png" alt=""></p><details><summary><i style="color: gray">Expand for R code</i></summary><figure class="highlight r"><table><tr><td class="code"><pre><span class="line"><span class="comment"># NB: continues from previous code section</span></span><br><span class="line"></span><br><span class="line">defence_quality &lt;- tibble(</span><br><span class="line">  label = <span class="built_in">c</span>(<span class="string">&quot;Very bad&quot;</span>, <span class="string">&quot;Bad&quot;</span>, <span class="string">&quot;Okay&quot;</span>, <span class="string">&quot;Good&quot;</span>, <span class="string">&quot;Very good&quot;</span>) %&gt;%</span><br><span class="line">    paste(<span class="string">&quot;defence&quot;</span>) %&gt;%</span><br><span class="line">    as_factor(),</span><br><span class="line">  xg = <span class="built_in">c</span>(<span class="number">2.5</span>, <span class="number">1.8</span>, <span class="number">1.3</span>, <span class="number">0.9</span>, <span class="number">0.7</span>)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">defence_quality_goals &lt;-</span><br><span class="line">  defence_quality %&gt;%</span><br><span class="line">  mutate(probs = map(xg, <span class="keyword">function</span>(x) &#123;</span><br><span class="line">    tibble(goals = <span class="number">0</span>:<span class="number">20</span>,</span><br><span class="line">           prob  = dpois(<span class="number">0</span>:<span class="number">20</span>, x))</span><br><span class="line">  &#125;)) %&gt;%</span><br><span class="line">  unnest(cols = <span class="built_in">c</span>(probs))</span><br><span class="line"></span><br><span class="line">results &lt;-</span><br><span class="line">  full_join(</span><br><span class="line">    shot_quality_goals,</span><br><span class="line">    defence_quality_goals,</span><br><span class="line">    by = character(),</span><br><span class="line">    suffix = <span class="built_in">c</span>(<span class="string">&quot;_shot&quot;</span>, <span class="string">&quot;_defence&quot;</span>)</span><br><span class="line">  ) %&gt;%</span><br><span class="line">    mutate(prob = prob_shot * prob_defence) %&gt;%</span><br><span class="line">    group_by(label_shot, label_defence) %&gt;%</span><br><span class="line">    summarise(w = <span class="built_in">sum</span>(ifelse(goals_shot &gt; goals_defence, prob, <span class="number">0</span>)),</span><br><span class="line">              d = <span class="built_in">sum</span>(ifelse(goals_shot == goals_defence, prob, <span class="number">0</span>)),</span><br><span class="line">              l = <span class="built_in">sum</span>(ifelse(goals_shot &lt; goals_defence, prob, <span class="number">0</span>)),</span><br><span class="line">              pts = w*<span class="number">3</span> + d) %&gt;%</span><br><span class="line">    ungroup()</span><br><span class="line"></span><br><span class="line">results %&gt;%</span><br><span class="line">  ggplot(aes(x = label_shot, y = label_defence)) +</span><br><span class="line">  geom_tile(aes(fill = w), width = <span class="number">0.75</span>, height = <span class="number">0.75</span>) +</span><br><span class="line">  geom_text(aes(label = scales::percent(w, accuracy = <span class="number">0.1</span>)), colour = <span class="string">&quot;white&quot;</span>) +</span><br><span class="line">  labs(title = <span class="string">&quot;Bad chances could provide a (tiny) uplift to results,\nfor very bad defences &quot;</span>,</span><br><span class="line">       subtitle = <span class="string">&quot;Win probability&quot;</span>,</span><br><span class="line">       x = <span class="literal">NULL</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  scale_fill_continuous(guide = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(plot.title = element_text(face = <span class="string">&quot;bold&quot;</span>),</span><br><span class="line">        panel.grid.major = element_blank(),</span><br><span class="line">        panel.grid.minor = element_blank())</span><br></pre></td></tr></table></figure></details><p>However, the uplift to win and draw chance is very small; it is only worth about 0.02 additional expected points. If you’re conceding 3 goals a game, you’re still getting relegated no matter how many long-shots you take.</p><p>And, as initially noted, there is an advantage gained from having fewer, high-quality chances when generating <em>more</em> xG than your opponent.</p><p>This advantage appears to be much more pronounced than the inverse effect (where low-quality chances have the advantage). Teams who are better than their opponents get much more of an uplift from high-quality chances than bad teams get from increasing their variance with low-quality chances.</p><p><img src="win-prob-swing.png" alt=""></p><details><summary><i style="color: gray">Expand for R code</i></summary><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">annotate_background_text &lt;- partial(</span><br><span class="line">  annotate, geom = <span class="string">&quot;label&quot;</span>, size = <span class="number">6</span>, fontface = <span class="string">&quot;bold&quot;</span>, colour = <span class="string">&quot;gray&quot;</span>,</span><br><span class="line">  label.size = <span class="number">0</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">results %&gt;%</span><br><span class="line">  pivot_wider(</span><br><span class="line">    id_cols = label_defence,</span><br><span class="line">    names_from = label_shot,</span><br><span class="line">    values_from = w,</span><br><span class="line">    names_repair = janitor::make_clean_names</span><br><span class="line">  ) %&gt;%</span><br><span class="line">  left_join(defence_quality, by = <span class="built_in">c</span>(<span class="string">&quot;label_defence&quot;</span> = <span class="string">&quot;label&quot;</span>)) %&gt;%</span><br><span class="line">  mutate(uplift = very_good_chances - very_bad_chances) %&gt;%</span><br><span class="line">  ggplot(aes(x = total_xg_for - xg, y = uplift)) +</span><br><span class="line">  geom_hline(yintercept = <span class="number">0</span>, colour = <span class="string">&quot;dimgray&quot;</span>, linetype = <span class="string">&quot;dashed&quot;</span>) +</span><br><span class="line">  annotate_background_text(</span><br><span class="line">    label = <span class="string">&quot;High quality chances are better&quot;</span>,</span><br><span class="line">    x = -<span class="number">1.75</span>,</span><br><span class="line">    y =  <span class="number">0.02</span>,</span><br><span class="line">    hjust = <span class="number">0</span></span><br><span class="line">  ) +</span><br><span class="line">  annotate(</span><br><span class="line">    <span class="string">&quot;segment&quot;</span>,</span><br><span class="line">    x = -<span class="number">1.75</span>, xend = -<span class="number">1.75</span>, y = <span class="number">0.0125</span>, yend = <span class="number">0.0275</span>,</span><br><span class="line">    colour = <span class="string">&quot;gray&quot;</span>,</span><br><span class="line">    arrow = arrow(type = <span class="string">&quot;closed&quot;</span>, angle = <span class="number">20</span>, <span class="built_in">length</span> = unit(<span class="number">0.02</span>, <span class="string">&quot;npc&quot;</span>))</span><br><span class="line">  ) +</span><br><span class="line">  annotate_background_text(</span><br><span class="line">    label = <span class="string">&quot;Low quality chances are better&quot;</span>,</span><br><span class="line">    x = <span class="number">0.5</span>,</span><br><span class="line">    y = -<span class="number">0.01</span>,</span><br><span class="line">    hjust = <span class="number">1</span></span><br><span class="line">  ) +</span><br><span class="line">  annotate(</span><br><span class="line">    <span class="string">&quot;segment&quot;</span>,</span><br><span class="line">    x = <span class="number">0.5</span>, xend = <span class="number">0.5</span>, y = -<span class="number">0.005</span>, yend = -<span class="number">0.015</span>,</span><br><span class="line">    colour = <span class="string">&quot;gray&quot;</span>,</span><br><span class="line">    arrow = arrow(type = <span class="string">&quot;closed&quot;</span>, angle = <span class="number">20</span>, <span class="built_in">length</span> = unit(<span class="number">0.02</span>, <span class="string">&quot;npc&quot;</span>))</span><br><span class="line">  ) +</span><br><span class="line">  geom_point() +</span><br><span class="line">  geom_text(aes(label = label_defence), vjust = <span class="number">1.5</span>) +</span><br><span class="line">  scale_y_continuous(</span><br><span class="line">    expand = expansion(add = <span class="number">0.0025</span>),</span><br><span class="line">    labels = scales::percent_format(accuracy = <span class="number">1</span>)</span><br><span class="line">  ) +</span><br><span class="line">  labs(</span><br><span class="line">    title = <span class="string">&quot;Swing in win % is much bigger when xGD is positive&quot;</span>,</span><br><span class="line">    subtitle = <span class="string">&quot;Absolute change in win %, between very good and very bad chances&quot;</span>,</span><br><span class="line">    x = <span class="string">&quot;xGD&quot;</span>,</span><br><span class="line">    y = <span class="literal">NULL</span></span><br><span class="line">  ) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(plot.title = element_text(face = <span class="string">&quot;bold&quot;</span>))</span><br></pre></td></tr></table></figure></details><h2 id="How-should-this-affect-teams-decisions">How should this affect teams decisions?</h2><p>I don’t think this effect should be a major factor in weak teams’ tactics. The theoretical advantage to weak teams taking low-quality, high-variance shots is very small (maxes out at around 1%). The assumptions made in the above test are rough enough that I don’t think we can sure that such a small edge would even translate into real life.</p><p>Moreover, in real matches, you don’t get to distribute the xG of your chances like a character-creation slider in a video game. The strongest case for favouring high-quality chances has always been that, in many cases, playing the right pass will result in a better chance than taking an early shot (even accounting for the fact that the move may break down before a shot can be taken). <strong>Does anything we’ve learned here suggest we should move away from a heuristic of “maximise the total xG of each possession sequence”?</strong></p><p>For all but the most dominant teams, <strong>I would say that it does not</strong>.</p><p>In matches where a team is dominant, there may be a slight, real advantage gained by prioritising high-quality chances, even at a slight cost to total xG. The method above returns an approximate increase around 4-5% for dominant teams (+1.5 xGD) prioritising very good chances (~0.3 xG per shot), so I think there’s a slightly stronger case to tweak your approach in these circumstances, especially in matches where you’re going to spend a lot of time facing an organised defence.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Last month, I saw &lt;a href=&quot;https://twitter.com/HemmenKees/status/1339990314606620674&quot;&gt;an interesting tweet&lt;/a&gt; from &lt;a href=&quot;https://twit</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
  </entry>
  
  <entry>
    <title>Fully Automated Luxury Commentary</title>
    <link href="https://www.statsandsnakeoil.com/2020/10/15/fully-automated-luxury-commentary/"/>
    <id>https://www.statsandsnakeoil.com/2020/10/15/fully-automated-luxury-commentary/</id>
    <published>2020-10-15T22:00:00.000Z</published>
    <updated>2021-04-11T15:33:00.285Z</updated>
    
    <content type="html"><![CDATA[<style>.embed-container {    position: relative;    padding-bottom: 56.25%;    height: 0;    overflow: hidden;    max-width: 100%;  }  .embed-container iframe, .embed-container object, .embed-container embed {    position: absolute;    top: 0;    left: 0;    width: 100%;    height: 100%;  }  </style><div class="embed-container"><iframe src="https://www.youtube.com/embed/Ba5f4IBO6vY" allowfullscreen frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe></div><h2 id="What-is-it">What is it?</h2><p>Automatically generate commentary for football matches, using clips fromGary Bloom’s Sega Worldwide Soccer 97 and Statsbomb’s event data.</p><h2 id="Why-is-it">Why is it?</h2><p>The idea that you can generate commentary purely from event data is relatively mundane (it has been implemented in basically every soccer video game for the past 20+ years), but has always seemed fun to me. The increasing desire to package football up as a clean entertainment product, along with the addition of synthetic crowd noise to closed-doors games brought it back to the forefront of my mind over this past Summer.</p><p>Alongside this, the various releases of Stasbomb’s free event data finally made it possible to make a widely accessible auto-commentary tool, rather than making something only for the small number of people with access toofficial data streams.</p><h2 id="How-does-it-work">How does it work?</h2><p>At a high level, the implementation is as follows:</p><ul><li>For a given match, we can get the event data from Statsbomb as a list of events</li><li>We also have a set of audio clips from Sega Worldwide Soccer 97</li><li>Each audio clip can be linked to a set of conditions that must be true for theclip to be relevant. For example:<ul><li>Clip X is a goal</li><li>Clip Y is a pass <em>and</em> the pass is successful <em>and</em> the pass ends inside the penalty area</li><li>(etc…)</li></ul></li><li>For each event in the list:<ul><li>We can check the audio clips to find any that are relevant. In other words, clips where the event in question passes all the linked conditions</li><li>And then pick one at random</li></ul></li><li>Once we have the clips, we can stitch them together at the appropriate timestamp to create a single synthetic audio track</li></ul><p>If you want a lower-level look, you can find the code <a href="https://github.com/Torvaney/fully-automated-luxury-commentary">on github</a>.</p><p>There’s loads of potential improvements that could be made both to the code and to the tool, but I’ve been sitting on this for a few months, running out of motivation. I figured releasing <em>something</em> was better than having it languish on my computer at home.</p><h2 id="Is-it-really-fully-automated">Is it really <em>fully</em> automated?</h2><p>Not entirely. I had to bring the clips, footage and crowd noise in a video editor to make the video at the top. Mostly, this name sounds better than “pypundit” or “AI McCoist”, which was all I had before.</p><h2 id="Can-I-try-it-out-too">Can I try it out, too?</h2><p>Sure! The code is all on github <a href="https://github.com/Torvaney/fully-automated-luxury-commentary">here</a>, so you can clone, run, and submit issues or improvements.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;style&gt;.embed-container {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    max-width: 100%;
  }
</summary>
      
    
    
    
    
    <category term="Auto-highlights" scheme="https://www.statsandsnakeoil.com/tags/auto-highlights/"/>
    
  </entry>
  
  <entry>
    <title>Diagonal gridlines in 2 ways</title>
    <link href="https://www.statsandsnakeoil.com/2020/10/07/diagonal-gridlines/"/>
    <id>https://www.statsandsnakeoil.com/2020/10/07/diagonal-gridlines/</id>
    <published>2020-10-07T13:31:06.000Z</published>
    <updated>2022-09-26T13:37:05.259Z</updated>
    
    <content type="html"><![CDATA[<p>Gridlines are a pretty standard part of any plot. As such, we often don’t think much about gridlines beyond whether to include them, and/or their visual appearance (colour, style, etc.).</p><p>However, this is a missed opportunity. In certain cases, using nonstandard gridlinescan enhance the legibility of a graphic and its take-home message. In this post, I willlook at a couple of different ways that diagonal gridlines can be used to this effect.</p><h2 id="1-For-ratios-HFA-disruption-plot">1: For ratios (HFA disruption plot)</h2><p>The first example comes from <a href="http://www.statsandsnakeoil.com/2020/07/27/estimating-the-disruption-to-home-advantage/">a previous post</a>, investigating potential disruption to homeadvantage pre/post-covid:</p><p><img src="1-hfa.jpg" alt=""></p><p>(See the end of <a href="http://www.statsandsnakeoil.com/2020/07/27/estimating-the-disruption-to-home-advantage/">the previous post</a> for the ggplot2 implementation.)</p><p>In general, this kind of approach can be useful when the ratio between the x- and y-measuresis meaningful.</p><p>Another potential place to apply this is the “p90 scatter” charts that are commonon social media. These charts often consist of a scatter plot showing a set of football players’score in 2 correlated metrics (for example, xGp90 and Goals p90), with the outliers annotated (these being noteworthy players).</p><p>In many cases, the <em>ratio</em> of y/x is useful information. For a scatter of xGp90 against Goals p90, the ratio would be a measure of over/underperformance against xG. On other charts, it may be a success or conversion rate.</p><p>This technique can be applied broadly, as long as the ratio of y/x is meaningful.For instance, see how adding diagonal lines to this plot highlights that Adama Traoréand Allan Saint-Maximin had almost the same rate of progressive distance per carry in 2019/20:</p><p><img src="1-carrying-lines.png" alt=""></p><details><summary><i style="color: gray">Expand for ggplot2 implementation</i></summary><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line">library(rvest)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Fetch the FBref data from acciotables</span></span><br><span class="line">epl_possession_data &lt;-</span><br><span class="line">  read_html(<span class="string">&quot;http://acciotables.herokuapp.com/?page_url=https://fbref.com/en/comps/9/3232/possession/2019-2020-Premier-League-Stats&amp;content_selector_id=%23stats_possession&quot;</span>) %&gt;%</span><br><span class="line">  html_table() %&gt;%</span><br><span class="line">  .[[<span class="number">1</span>]] %&gt;%</span><br><span class="line">  janitor::row_to_names(row_number = <span class="number">1</span>) %&gt;%</span><br><span class="line">  filter(Rk != <span class="string">&quot;Rk&quot;</span>) %&gt;%</span><br><span class="line">  readr::type_convert()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">selected_players &lt;- <span class="built_in">c</span>(</span><br><span class="line">  <span class="string">&quot;Adama Traoré&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Allan Saint-Maximin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">selected_player_data &lt;-</span><br><span class="line">  filter(epl_possession_data, Player %in% selected_players)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">epl_possession_data %&gt;%</span><br><span class="line">  <span class="comment"># Get the midfielders with at least 900 minutes played in 19/20</span></span><br><span class="line">  filter(`<span class="number">90</span>s` &gt; 10,</span><br><span class="line">         str_detect(Pos, &quot;MF&quot;)) %&gt;%</span><br><span class="line">  ggplot(aes(x = Carries/`90s`, y = PrgDist/`90s`)) +</span><br><span class="line"></span><br><span class="line">  # Add the diagonal guidelines and their labels</span><br><span class="line">  imap(seq(1, 8, by = 1), function(slope, i) &#123;</span><br><span class="line"></span><br><span class="line">    # Calculate the position of the labels, such that</span><br><span class="line">    # they run along the top horizontally, beyond a</span><br><span class="line">    # maximum y value</span><br><span class="line">    max_x &lt;- 70</span><br><span class="line">    max_y &lt;- 350</span><br><span class="line">    label_x &lt;- ifelse(slope*max_x &lt;= max_y, max_x, (max_y / slope))</span><br><span class="line">    label_y &lt;- slope*label_x</span><br><span class="line"></span><br><span class="line">    # Only show the full label for the first annotation</span><br><span class="line">    label &lt;- str_glue(&quot;&#123;slope&#125; yds&quot;)</span><br><span class="line">    if (i == 1) &#123;</span><br><span class="line">      label &lt;- str_glue(&quot;&#123;slope&#125; progressive yard per carry&quot;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    # Return the layers</span><br><span class="line">    list(</span><br><span class="line">      geom_segment(x = 0, y = 0, xend = max_x*2, yend = slope*max_x*2, linetype = &quot;dashed&quot;, colour = &quot;lightgray&quot;),</span><br><span class="line">      annotate(geom = &quot;label&quot;, x = label_x, label_y,</span><br><span class="line">               label = label, hjust = 1,</span><br><span class="line">               colour = &quot;dimgray&quot;, label.size = 0)</span><br><span class="line">    )</span><br><span class="line">  &#125;) +</span><br><span class="line"></span><br><span class="line">  geom_point(colour = &quot;lightskyblue&quot;) +</span><br><span class="line">  geom_text(aes(label = Player), hjust = 1.05,</span><br><span class="line">            data = selected_player_data) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  coord_cartesian(xlim = c(0, NA), ylim = c(0, NA)) +</span><br><span class="line">  scale_x_continuous(labels = scales::number_format(1)) +</span><br><span class="line">  scale_y_continuous(labels = scales::number_format(1)) +</span><br><span class="line">  labs(title = &quot;Adama and Saint-Maximin are more direct with the ball than anyone else&quot;,</span><br><span class="line">       subtitle = &quot;You probably didn&#x27;t need stats to tell you that&quot;,</span><br><span class="line">       x = &quot;Carries p90&quot;,</span><br><span class="line">       y = &quot;Progressive distance carried p90&quot;,</span><br><span class="line">       caption = &quot;Data by StatsBomb via fbref.com&quot;)</span><br></pre></td></tr></table></figure></details><br/><h3 id="Diagonal-gradients">Diagonal gradients</h3><p>This specific technique has been extended by Ben Mayhew (<a href="https://twitter.com/experimental361/">@experimental361</a>) quite distinctively by augmenting the diagonal gridlines with a colour gradient, like so:</p><p><img src="1-mayhew.png" alt=""></p><p>(Taken from <a href="https://twitter.com/experimental361/status/1287746116721795074">https://twitter.com/experimental361/status/1287746116721795074</a>)</p><details><summary><i style="color: gray">Expand for ggplot2 implementation</i></summary><p>I haven’t got a direct ggplot implementation of Mayhew’s colour gradient. However,you can achieve a single-colour gradient by drawing successive semi-transparent polygons over the plot:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line">library(regista)</span><br><span class="line"></span><br><span class="line"><span class="comment"># You may need to</span></span><br><span class="line"><span class="comment"># devtools::install_github(&quot;torvaney/footballdatr&quot;)</span></span><br><span class="line"></span><br><span class="line">epl_match_results &lt;-</span><br><span class="line">  footballdatr::fetch_data(<span class="string">&quot;england&quot;</span>, division = <span class="number">0</span>, season = <span class="number">2018</span>) %&gt;%</span><br><span class="line">  factor_teams(<span class="built_in">c</span>(<span class="string">&quot;home&quot;</span>, <span class="string">&quot;away&quot;</span>))</span><br><span class="line"></span><br><span class="line">model &lt;- dixoncoles(hgoal, agoal, home, away, data = epl_match_results)</span><br><span class="line"></span><br><span class="line">team_parameters &lt;-</span><br><span class="line">  broom::tidy(model) %&gt;%</span><br><span class="line">  filter(parameter %in% <span class="built_in">c</span>(<span class="string">&quot;off&quot;</span>, <span class="string">&quot;def&quot;</span>)) %&gt;%</span><br><span class="line">  mutate(value = <span class="built_in">exp</span>(value)) %&gt;%</span><br><span class="line">  spread(parameter, value)</span><br><span class="line"></span><br><span class="line">team_parameters %&gt;%</span><br><span class="line">  ggplot(aes(x = off, y = def)) +</span><br><span class="line">  <span class="comment"># THIS IS THE IMPORTANT BIT!</span></span><br><span class="line">  <span class="comment"># Add the &quot;gradient&quot; by layering translucent polygons</span></span><br><span class="line">  <span class="comment"># NB: This has to go before any plot elements that we</span></span><br><span class="line">  <span class="comment">#     actually want to see, so that we don&#x27;t draw over them...</span></span><br><span class="line">  purrr::map(seq(<span class="number">0</span>, <span class="number">3</span>, length.out = <span class="number">10</span>), <span class="keyword">function</span>(m) &#123;</span><br><span class="line">    annotate(<span class="string">&quot;polygon&quot;</span>,</span><br><span class="line">             x = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">5</span>, <span class="number">5</span>, <span class="number">0</span>),</span><br><span class="line">             y = <span class="built_in">c</span>(<span class="number">0</span>, m*<span class="number">5</span>, <span class="number">5</span>, <span class="number">5</span>),</span><br><span class="line">             alpha = <span class="number">1</span>/<span class="number">5</span>,</span><br><span class="line">             <span class="comment"># Official brand colours from https://www.color-hex.com/color-palette/44426</span></span><br><span class="line">             fill = <span class="string">&quot;#e90052&quot;</span>)</span><br><span class="line">  &#125;) +</span><br><span class="line">  <span class="comment"># We need to manually set the axis limits</span></span><br><span class="line">  coord_cartesian(<span class="built_in">c</span>(<span class="number">1</span>/<span class="number">2</span>, <span class="number">2</span>), <span class="built_in">c</span>(<span class="number">1</span>/<span class="number">2</span>, <span class="number">2</span>)) +</span><br><span class="line">  <span class="comment"># No source for the official brand font - I guess Roboto will do?</span></span><br><span class="line">  ggrepel::geom_text_repel(aes(label = team),</span><br><span class="line">                           colour = <span class="string">&quot;#38003c&quot;</span>,</span><br><span class="line">                           family = <span class="string">&quot;Roboto&quot;</span>) +</span><br><span class="line">  geom_point(alpha = <span class="number">0.5</span>, colour = <span class="string">&quot;#38003c&quot;</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(panel.grid = element_blank(),</span><br><span class="line">        text = element_text(family = <span class="string">&quot;Roboto&quot;</span>)) +</span><br><span class="line">  labs(title = <span class="string">&quot;Team strength estimates&quot;</span>,</span><br><span class="line">       subtitle = <span class="string">&quot;Premier League 2017/18&quot;</span>,</span><br><span class="line">       x = <span class="string">&quot;Attack&quot;</span>,</span><br><span class="line">       y = <span class="string">&quot;Defence&quot;</span>)</span><br></pre></td></tr></table></figure><p><img src="1-mayhew-ggplot2.jpg" alt=""></p></details><h2 id="2-For-comparable-units-NPG-A-plot">2: For comparable units (NPG+A plot)</h2><p>In rarer cases, the x- and y-measures have comparable units. That is, you could meaningfully add the x and y values together. For example, goals and assists are often counted together to create overall “goal contribution” (likewise shots, key passes, and “shot contribution”).</p><p>In cases where the sum of the x and y values is useful, a different kind of diagonal grid can be drawn:</p><p><img src="2-x-goal-contribution.png" alt=""></p><p>The diagonal gridlines here show the overall expected goal contribution (npxG + xA).Two players on the same line will have the same npxG + xA, despite having differentnpxG or xA figures.</p><p>This stratifies the plot elements more meaningfully than standard gridlines and helps readers draw useful comparisons between players.</p><p>If we compare to the same plot without gridlines, it is easy to see Di María and Mbappéas outliers and put them in the same tier.</p><p><img src="2-x-goal-contribution-no-lines.jpg" alt=""></p><p>However, this is not true. Mbappé’s goal contribution is a level above that of Di María,Neymar, Slimani, or Icardi; referring back to the first plot, we can see that the latter group all sit withinthe same tranche.</p><p>There is a minor injustice here that xA is harder to come by than xG. Di María’s achievementis rarer and thus perhaps more impressive than Neymar’s (likewise Payet:Cardona, and so on). However, I think that on-balance, the benefits to plot legibility are worth it.</p><details><summary><i style="color: gray">Expand for ggplot2 implementation</i></summary><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line">library(rvest)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">format_colnames &lt;-</span><br><span class="line">  . %&gt;%</span><br><span class="line">  str_to_lower() %&gt;%</span><br><span class="line">  str_replace_all(<span class="string">&quot;\\s+&quot;</span>, <span class="string">&quot;_&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">get_fbref_colnames &lt;- <span class="keyword">function</span>(data, row_number = <span class="number">1</span>) &#123;</span><br><span class="line">  row_data &lt;- unlist(data[row_number, ])</span><br><span class="line"></span><br><span class="line">  imap_chr(row_data, <span class="keyword">function</span>(col_name, parent_name) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parent_name == <span class="string">&quot;&quot;</span>) &#123;</span><br><span class="line">      <span class="built_in">return</span>(col_name)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    paste0(parent_name, <span class="string">&quot;_&quot;</span>, col_name)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fbref_row_to_colnames &lt;- <span class="keyword">function</span>(data, row_number = <span class="number">1</span>) &#123;</span><br><span class="line">  new_colnames &lt;- get_fbref_colnames(data, row_number)</span><br><span class="line"></span><br><span class="line">  data %&gt;%</span><br><span class="line">    magrittr::set_colnames(new_colnames) %&gt;%</span><br><span class="line">    <span class="comment"># Rownames are recycled periodically. Remove these</span></span><br><span class="line">    filter(Rk != <span class="string">&quot;Rk&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">l1_player_data &lt;-</span><br><span class="line">  read_html(<span class="string">&quot;http://acciotables.herokuapp.com/?page_url=https://fbref.com/en/comps/13/3243/stats/2019-2020-Ligue-1-Stats&amp;content_selector_id=%23stats_standard&quot;</span>) %&gt;%</span><br><span class="line">  html_table() %&gt;%</span><br><span class="line">  .[[<span class="number">1</span>]] %&gt;%</span><br><span class="line">  fbref_row_to_colnames() %&gt;%</span><br><span class="line">  rename_with(format_colnames) %&gt;%</span><br><span class="line">  readr::type_convert()</span><br><span class="line"></span><br><span class="line">l1_player_data %&gt;%</span><br><span class="line">  filter(playing_time_min &gt;= <span class="number">10</span>*<span class="number">90</span>,</span><br><span class="line">         `per_90_minutes_npxg+xa` &gt;= <span class="number">0.2</span>) %&gt;%</span><br><span class="line">  ggplot(aes(x = per_90_minutes_npxg, y = per_90_minutes_xa)) +</span><br><span class="line"></span><br><span class="line">  <span class="comment"># Add the diagonal gridlines, bounded by the x- and y- axes</span></span><br><span class="line">  map(seq(<span class="number">0</span>, <span class="number">2</span>, by = <span class="number">0.1</span>), <span class="keyword">function</span>(intercept) &#123;</span><br><span class="line">    geom_segment(x = <span class="number">0</span>, y = intercept, xend = intercept, yend = <span class="number">0</span>, linetype = <span class="string">&quot;dotted&quot;</span>, colour = <span class="string">&quot;lightgray&quot;</span>)</span><br><span class="line">  &#125;) +</span><br><span class="line"></span><br><span class="line">  geom_point(colour = <span class="string">&quot;lightskyblue&quot;</span>, alpha = <span class="number">0.9</span>) +</span><br><span class="line">  geom_text(aes(label = player), check_overlap = <span class="literal">TRUE</span>, vjust = <span class="number">1.05</span>, size = <span class="number">3</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(panel.grid.major = element_line(size = <span class="number">0.2</span>),</span><br><span class="line">        panel.grid.minor = element_blank()) +</span><br><span class="line">  coord_cartesian(xlim = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">1.1</span>), ylim = <span class="built_in">c</span>(<span class="number">0</span>, <span class="literal">NA</span>)) +</span><br><span class="line">  scale_x_continuous(breaks = seq(<span class="number">0</span>, <span class="number">2</span>, by = <span class="number">0.1</span>), labels = scales::number_format(<span class="number">0.1</span>)) +</span><br><span class="line">  scale_y_continuous(breaks = seq(<span class="number">0</span>, <span class="number">2</span>, by = <span class="number">0.1</span>), labels = scales::number_format(<span class="number">0.1</span>)) +</span><br><span class="line">  labs(title = <span class="string">&quot;Mbappé contributes goals like nobody else in Ligue 1&quot;</span>,</span><br><span class="line">       subtitle = <span class="string">&quot;Again... it maybe didn&#x27;t need stats to convince you&quot;</span>,</span><br><span class="line">       x = <span class="string">&quot;xG p90&quot;</span>,</span><br><span class="line">       y = <span class="string">&quot;xA p90&quot;</span>,</span><br><span class="line">       caption = <span class="string">&quot;Data by StatsBomb via fbref.com&quot;</span>)</span><br></pre></td></tr></table></figure></details><h3 id="Minor-algebraic-aside-you-can-skip-this-bit">Minor algebraic aside (you can skip this bit)</h3><p>The formula of a straight line is <code>y = mx + c</code>. Given an x-coordinate, you can find the corresponding y-coordinate on that line by multiplying it by <code>m</code> (the slope) and adding <code>c</code> (the intercept).</p><p>You can therefore view the first set of examples as diagonal gridlines where the slope is of interest.In other words, we set gridlines with varying values of <code>m</code>, and a fixed intercept (generally, <code>c</code> will be 0).</p><p>In the second category, we are looking at varying the intercept (<code>c</code>), while keeping the slope constant.In our example, <code>m</code> is fixed to -1. This is because we are taking the sum of the x- and y- measures,and re-arranging the formula <code>y + x = c</code> gives us <code>y = (-1)x + c</code>.</p><p>If you were interested in the <em>difference</em> between the y- and x- measures, you would draw diagonal gridlines with varying slope and a gradient of <em>+1</em>.</p><h2 id="Conclusion">Conclusion</h2><p>You can use diagonal gridlines liek these to highlight key features of your data in a couple of different cases:</p><ul><li>When the <strong>ratio</strong> between x and y is meaningful, you can draw gridlines with <strong>varying slope</strong></li><li>When the x- and y-axes have <strong>comparable units</strong>, you can draw gridlines with a <strong>varying intercept</strong></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Gridlines are a pretty standard part of any plot. As such, we often don’t think much about gridlines beyond whether to include them, and/</summary>
      
    
    
    
    
    <category term="Data-visualisation" scheme="https://www.statsandsnakeoil.com/tags/data-visualisation/"/>
    
  </entry>
  
  <entry>
    <title>Three things to improve football coverage</title>
    <link href="https://www.statsandsnakeoil.com/2020/08/22/three-things-to-improve-football-coverage/"/>
    <id>https://www.statsandsnakeoil.com/2020/08/22/three-things-to-improve-football-coverage/</id>
    <published>2020-08-22T04:04:33.000Z</published>
    <updated>2022-02-01T22:25:04.348Z</updated>
    
    <content type="html"><![CDATA[<p>Over recent years, there has been, at least at surface level, increasing competitionfor football coverage. The arrival of BT Sport’s Premier League coverage in 2013 brokeSky’s domestic monopoly and since then an increasing number of streaming platformshave arrived on the scene for foreign (to the UK) league.</p><p>Despite this, the format of football coverage in the UK has largely stayed the same for years.While I would welcome more radical experimentation, I have three specific suggestionsthat I think would improve football coverage in the UK.</p><h2 id="1-No-more-close-ups-and-replays-while-the-ball-is-in-play">1. No more close-ups and replays while the ball is in play</h2><p>Or replays for that matter. As far as I can tell, no-one who actually likesfootball would rather watch close-ups and replays when we could bewatching <em>the actual match being played</em>.</p><p>I don’t have an issue with replays themselves. It’s just that there’s plenty oftime to show any key highlights at half-time or full-time.</p><p>As for close-ups, you don’t have to go full tactical-cam for the whole match. However, keeping the pitch and match context visible helps to understand what’s going on. Much of the vaunted beauty of football happens in the interaction between the players and spaces throughout a given play. You don’t have to be a mathematician to see that with 22 players and 1 ball, some of this must happen off-the-ball.</p><p>Finally, when I say “in-play”, I am including the run-up to set pieces. Just because the ball hasn’t been thrown/kicked yet, doesn’t mean we’re not interested in what’s happening on the pitch.</p><p>So why does this happen? In 2017, I had a chance encounter with a sports cameraperson (is thatthe right terminology?) that could shed some light on this/be an entirely unrepresentative anecdote (delete as appropriate). They said they were increasingly being asked to shoot sports more like an action film. While I cansee the appeal of this as a technical challenge, as a sports viewer I don’t want to see anaction film. I want to watch the sports. It’s literally what I’m here for. Just show the sports,please.</p><h2 id="2-Give-us-the-option-of-no-commentary">2. Give us the option of no commentary</h2><p>For matches with no crowds, BT have offered coverage the option of coverage with/without fakecrown noise on separate channels. I have appreciated having this option and am grateful for BT’sflexibility. I miss the organic crowd noise and for me, the canned stuff is a poor substitutethat I’d rather do without.</p><p>With the ever-increasing digitisation of well, literally everything, having limited channels to broadcast on is surely becoming less of an issue. So why not take advantage of that and give us the option of watching without commentary at all?</p><p>This wouldn’t have to be available on a TV channel, per-se. As a niche option, this could be offered only as an option on BT’s streaming service (i.e. via the website and through their apps).</p><h2 id="3-No-more-discussion-of-refereeing-decisions">3. No more discussion of refereeing decisions</h2><p>I would love to see broadcasters outlaw discussion of refereeing incidents. Yes, it’s an important part of the game. But all too often it turns the entire half-time and/or post-match discussion into a quagmire of unsubstantiated back and forth. These discussions tend not to be based on any hard evidence, which means that they have no clear end point. and if you’vewatched more than about 10 matches in the last few seasons you’ve probably seen anextremely similar incident (and ensuing discussion) once or twice already. In my view, the result is that talking about refereeing decisions in the studio adds very little to the viewer’s experience or understanding.</p><p>The opportunity cost of these debates is high. Particularly after the first half when there’snot a huge amount of time available. Couldn’t we use that time to talk about how the team’s set ups left their defenders more isolated in the box, instead of arguing about whether the defender’s challenge was poorly timed enough to be a penalty?</p><p>Recent experiments in this area have not helped matters. The addition of celebrity referees to the broadcast team to have the final word was an extremely reactive decision that to me points to a lack of a clear direction in matchday football coverage. In short, the decision to add referee-pundits attempts to find an answer to any given argument, rather than asking whether the argument is a worthwhile part of football coverage at all.</p><h2 id="Tying-it-all-together">Tying it all together</h2><p>I think what brings these points together is that they all address problems that (in my opinion) stem from a lack of vision from the major UK matchday broadcasters at the moment.</p><p>This has become especially clear since the mid-pandemic season restart. While this has lead to increased experimentation in sports coverage, much of the experimentation has simply been to try and recreate the 2019 sports experience. With a specific direction and viewer experience in mind,this could be a time for more radical innovation (for UK broadcasters, at least. I’m relatively unaware of what has/n’t been tried elsewhere - but I would love to <a href="https://twitter.com/torvaney">find out</a>).</p><p>So how does such an expensive product end up so bland? I don’t have any personalexperience in sports media, so I could be wrong, but my guess is something like this:</p><p>The key decision makers, even if they like and follow football, don’t have enoughexecutive control and/or aren’t opinionated enough to make bold choices. Instead,they lean heavily on market research and the like to understand what people wantfrom their coverage. As a result, they end up with a wide array of perceived needsthat they feel they have to meet. Some people want to have a debate, others want analysis.Others want something bombastic. So what we get is something like death-by-focus-group.</p><p>This would also explain why smaller football programmes have no problem fitting into aspecific niche. TalkSport and radio call-in shows might not be for everyone, butthey have a clear vision that they try to execute.</p><p>I appreciate that football’s popularity makes it hard to meet everyone’s preferences.However, I believe that by having a clear strategy executed well, broadcasters canalso work to shape demand. For inspiration, look no further than Michael Cox’s<a href="http://www.zonalmarking.net/2010/05/18/barcelona-champions-2009-10-tactics/">work in the early 2010s</a>, or the rapid expansion of analytics contentover the past 3-4 years.</p><p>Heck, maybe we don’t even have to choose? As with providing coverage both with and withoutcrowd noise, new forms of media could enable a single broadcaster to target many differenttypes of fan. Sure, that would split the budget, but by increasing focus perhaps you reduce theneed for big name stars to paper over the cracks.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Over recent years, there has been, at least at surface level, increasing competition
for football coverage. The arrival of BT Sport’s Pre</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Finding the most versatile player</title>
    <link href="https://www.statsandsnakeoil.com/2020/08/10/finding-the-most-versatile-player/"/>
    <id>https://www.statsandsnakeoil.com/2020/08/10/finding-the-most-versatile-player/</id>
    <published>2020-08-10T08:00:00.000Z</published>
    <updated>2022-09-26T13:37:11.960Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, <a href="https://twitter.com/EveryTeam_Mark">Mark Thompson</a>, <a href="https://twitter.com/EveryTeam_Mark/status/1292414831639896065">tweeted</a> out the following question:</p><blockquote><p>Apart from James Milner, who are the most positionally versatile players in football atm?</p></blockquote><p>Many of the responses to the inital tweet are very good, and it’s interestingto see the slightly different interpretations of “positionally versatile” filteredthrough the lense of player suggestions. If you haven’t already, I suggest you takea look at both the thread and Mark’s newsletter, <a href="https://getgoalside.substack.com/">Get Goalside</a>.</p><p>If we take a very simplistic interpretation of “positionally versatile”,the question becomes well-suited to a simple, large-scale search like a databasequery.</p><h2 id="Defining-the-search">Defining the search</h2><p>To perform a search like this, we need to ~crudely misinterpret~ hammer out aclear-cut definition for the term “positionally versatile”, as used in Mark’sinitial question.</p><p>I think it’s reasonable to say that someone who’s versatile can be expected toactually play in a large number of different positions, each requiring multipleskillsets. Therefore, we should be able to find some of the most versatile playersby looking at who has played in the most different positions.</p><p>Some positions require rather similar skillsets, and so might not count stronglytowards any measure of versatility. For example, it is relatively common for playersto move between left back and left wing back in different teams or systems.Whereas few players play both as a left back and as a number 10; not everyone canbe Marc Mateu.</p><p>To get around this, I have re-categorised the playing positions marked by Statsbombinto the following groups:</p><ul><li>Full back (including wing back roles)</li><li>Centre back</li><li>Central defensive midfielder</li><li>Central midfielder</li><li>Central attacking midfielder</li><li>Wide attacker</li><li>Striker</li></ul><p>Now, this grouping smooths over all manner of different roles, particularly incentral midfield. For example, according to this grouping, both Jorginho andRani Khedira play in the same position. However, Jorginho is clearly quite a different player from Khedira. In spite of this, I think it’s probably good enough for a first pass, but this is something to bear in mind in case we want to iterate on this idea at a laterdate.</p><p>Another flaw in this method is that we’ll miss out on some players who have the <em>ability</em>play in many different positions, but that haven’t actually been deployed there.I think it would be very interesting to approach the problem from this angle. Butin order to do so, I think you’d need a trustworthy model to evaluate each differentskill for each player, along with some mapping of skills to playing roles.Whether that model is just sitting on Wyscout for a few ~days~ months, churningthrough players, or a statistical model, it’s way beyond the time I’mwilling to invest for this one blog post. So you’ll have to bear with me and my simple method for now.</p><p>Putting all this together, we can look for the most versatile player, being the onewho has played in the most distinct position groups, with a minimum of 270 minutesin position. 270 minutes is an entirely arbitrary cutoff. It’s the equivalent of about 3 full matches, which felt about right to me, and the results don’t change too much if youincrease or decrease the threshold.</p><h2 id="Most-versatile-players">Most versatile players</h2><p>Using this to count the “versatility” of each player in the top 5 Europeanleagues over the past 3 seasons, we find that the top 3 most versatile playersare Weston McKennie, Julian Brandt, and Leonardo Bittencourt. Each of whom hasplayed at least 270 minutes in 6 different position groups:</p><p><img src="most-versatile.jpg" alt="Plot showing the distribution of minutes played per position for the top 3 players"></p><p>I have to say, out of these 3, McKennie feels like the ‘winner’ in spirit.Brandt’s minutes at wing back counting towards FB feels a little bit generous.And both Brandt and Bittencourt benefit from wide attacking roles being countedseparately from central attacking midfield ones, when the required skillset may notactually be all that different, depending on the tactical setup.</p><p>Furthermore, McKennie comes very close to meeting our abitrary 270 minutes cutoffin a wide attacking slot (and in fact does, if you include European, cup and international minutes), which would put him into the #1 slot undisputed.</p><p>As it happens John Muller got to <a href="https://twitter.com/johnspacemuller/status/1292429952596549633">the same answer</a>just an hour after the original tweet was posted. So, I suppose we never needed all this analysis, after all!</p><p>Honourable mentions go to Saúl Ñíguez, who would draw level with these 3 withEuropean and cup minutes included, but narrowly misses out as it is,and Jonny Howson, who wasn’t initially counted because he plays in the Championship:</p><p><img src="nearly-most-versatile.jpg" alt="Plot showing the distribution of minutes played per position for the Jonny Howson and Saúl Ñíguez"></p><p>Thanks in part to the lower number of games in the dataset, there aren’t any playersfrom the WSL or NWSL who come out at quite the same level of versatility as McKennie et al;however, Giorgia Stanway and Gunnhildur Yrsa Jónsdóttir both come close:</p><p><img src="w-most-versatile.jpg" alt="Plot showing the distribution of minutes played per position for Giorgia Stanway and Gunnhildur Yrsa Jónsdóttir"></p><h2 id="Most-versatile…-league">Most versatile… league?</h2><p>You may have noted already that <em>all</em> of the top 3 most versatile players by themeasure we’ve created play in the Bundesliga. Is this a blip or a trend?</p><p>The distribution of our versatility measure across the top 5 leagues combinedlooks like this:</p><p><img src="versatile-distribution-benchmark.png" alt=""></p><p>Most outfield players (around 55%) play in only one position group. A reasonableproportion (a bit under 30%) have significant minutes in two position groups, and so on.</p><p>Let’s compare the league-specific distributions (gray) to this benchmark (blue):</p><p><img src="versatile-distribution-by-competition.png" alt=""></p><p>Sure enough, the Bundesliga comes out as the league in which footballers are mostlikely to be positioned in various different roles. La Liga comes out at the oppositeend of the spectrum, with the rest of the Big 5 somewhere in-between.</p><p>Is this because Bundesliga players are genuinely more versatile, due to coaching andrecruitment practices? Or are the tactics of the league such that the range of requiredskills is somehow narrower (not in a disparaging way) than in the other leagues?</p><p>I would like to go for the coward’s answer of “a bit of both”, but this isn’t aparticularly informative answer. So, if pushed, I would lean towards the latter. My hunchis that the tactical inclinations of Bundesliga teams, especially when attempting to winthe ball back, allow players with good physical attributes and tactical awareness tofulfil a broader variety of roles without the team’s structure being compromised. In general, this is a nice strategy, as it broadens the pool of players you caneffectively bring into the first team. This hunch could be an interesting route for some further investigation and validation.</p><p>If you have any thoughts or questions the ideas presented here, please don’t hesitate to<a href="https://twitter.com/Torvaney">reach out</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently, &lt;a href=&quot;https://twitter.com/EveryTeam_Mark&quot;&gt;Mark Thompson&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/EveryTeam_Mark/status/129241483163</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Estimating the disruption to home advantage</title>
    <link href="https://www.statsandsnakeoil.com/2020/07/26/estimating-the-disruption-to-home-advantage/"/>
    <id>https://www.statsandsnakeoil.com/2020/07/26/estimating-the-disruption-to-home-advantage/</id>
    <published>2020-07-26T23:16:35.000Z</published>
    <updated>2022-09-26T13:37:05.467Z</updated>
    
    <content type="html"><![CDATA[<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML" async=""></script><p>How much has home-field advantage (HFA) been affected by theclosed-doors games post-restart?</p><p>There have been a few good articles looking at how HFA has been affectedin different competitions since returning without crowds. For example,<a href="https://www.economist.com/graphic-detail/2020/07/25/empty-stadiums-have-shrunk-football-teams-home-advantage">this article in theEconomist</a>showing some analysis by <a href="https://www.21stclub.com/">21st Club</a>. Thearticle shows some charts showing the % of total league points won byhome teams before and after the restart. These charts are repeated forshots, fouls and some other metrics, with the conclusion:</p><blockquote><p>[T]he lack of help from referees has merely reduced home sides’advantage, rather than eliminating it… With crowds watching, hometeams gained 58% of points; without them, hosts have still earned 56%.In other words, three-quarters of home overperformance remains intact.</p></blockquote><p>In this post, I want to show how to perform a similar analysis using aDixon-Coles model and the regista R package.</p><p>The core of a Dixon-Coles model looks like this:</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -4.061ex" xmlns="http://www.w3.org/2000/svg" width="23.534ex" height="9.253ex" role="img" focusable="false" viewBox="0 -2295 10402 4089.9"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mtable"><g data-mml-node="mtr" transform="translate(0,1545)"><g data-mml-node="mtd"><g data-mml-node="mi"><path data-c="1D434" d="M208 74Q208 50 254 46Q272 46 272 35Q272 34 270 22Q267 8 264 4T251 0Q249 0 239 0T205 1T141 2Q70 2 50 0H42Q35 7 35 11Q37 38 48 46H62Q132 49 164 96Q170 102 345 401T523 704Q530 716 547 716H555H572Q578 707 578 706L606 383Q634 60 636 57Q641 46 701 46Q726 46 726 36Q726 34 723 22Q720 7 718 4T704 0Q701 0 690 0T651 1T578 2Q484 2 455 0H443Q437 6 437 9T439 27Q443 40 445 43L449 46H469Q523 49 533 63L521 213H283L249 155Q208 86 208 74ZM516 260Q516 271 504 416T490 562L463 519Q447 492 400 412L310 260L413 259Q516 259 516 260Z"></path></g><g data-mml-node="mi" transform="translate(750,0)"><path data-c="1D463" d="M173 380Q173 405 154 405Q130 405 104 376T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Q21 294 29 316T53 368T97 419T160 441Q202 441 225 417T249 361Q249 344 246 335Q246 329 231 291T200 202T182 113Q182 86 187 69Q200 26 250 26Q287 26 319 60T369 139T398 222T409 277Q409 300 401 317T383 343T365 361T357 383Q357 405 376 424T417 443Q436 443 451 425T467 367Q467 340 455 284T418 159T347 40T241 -11Q177 -11 139 22Q102 54 102 117Q102 148 110 181T151 298Q173 362 173 380Z"></path></g><g data-mml-node="mi" transform="translate(1235,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(1701,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(2152,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(2681,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(3158,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mtext" transform="translate(3624,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(3874,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(4351,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(4836,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(5365,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path></g><g data-mml-node="mi" transform="translate(5663,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mtext" transform="translate(6132,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(6382,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(6885,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(7351,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mtext" transform="translate(7802,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(8052,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(8529,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(9058,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(9936,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mtr" transform="translate(0,45)"><g data-mml-node="mtd" transform="translate(278.8,0)"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mo" transform="translate(2682.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(3738.6,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="TeXAtom" transform="translate(673,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(6162.1,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(6606.8,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="TeXAtom" transform="translate(599,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(8856.7,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="mi" transform="translate(9301.4,0)"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g></g></g><g data-mml-node="mtr" transform="translate(0,-1500)"><g data-mml-node="mtd" transform="translate(843.1,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(2541.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(3597.6,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="TeXAtom" transform="translate(673,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(5921.4,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(6366.1,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="TeXAtom" transform="translate(599,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g></g></g></g></g></g></svg></mjx-container></p><p>where</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -3.959ex" xmlns="http://www.w3.org/2000/svg" width="20.424ex" height="9.05ex" role="img" focusable="false" viewBox="0 -2250 9027.6 4000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mtable"><g data-mml-node="mtr" transform="translate(0,1500)"><g data-mml-node="mtd" transform="translate(245.5,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="mo" transform="translate(917.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(1973.6,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(2458.6,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mi" transform="translate(3008.6,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mi" transform="translate(3558.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(4024.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(4624.6,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path></g><g data-mml-node="mi" transform="translate(5057.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mtext" transform="translate(5523.6,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(5773.6,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(6224.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(6753.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mi" transform="translate(7114.6,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(7459.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(8059.6,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g></g></g><g data-mml-node="mtr"><g data-mml-node="mtd" transform="translate(307,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="mo" transform="translate(843.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(1899.6,0)"><path data-c="1D451" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mi" transform="translate(2419.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(2885.6,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mi" transform="translate(3435.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(3901.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(4501.6,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path></g><g data-mml-node="mi" transform="translate(4934.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mtext" transform="translate(5400.6,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(5650.6,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(6101.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(6630.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mi" transform="translate(6991.6,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(7336.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(7936.6,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g></g></g><g data-mml-node="mtr" transform="translate(0,-1500)"><g data-mml-node="mtd"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g><g data-mml-node="mo" transform="translate(820.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(1876.6,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(2452.6,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(2937.6,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(3815.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mtext" transform="translate(4281.6,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(4531.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(5060.6,0)"><path data-c="1D451" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mi" transform="translate(5580.6,0)"><path data-c="1D463" d="M173 380Q173 405 154 405Q130 405 104 376T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Q21 294 29 316T53 368T97 419T160 441Q202 441 225 417T249 361Q249 344 246 335Q246 329 231 291T200 202T182 113Q182 86 187 69Q200 26 250 26Q287 26 319 60T369 139T398 222T409 277Q409 300 401 317T383 343T365 361T357 383Q357 405 376 424T417 443Q436 443 451 425T467 367Q467 340 455 284T418 159T347 40T241 -11Q177 -11 139 22Q102 54 102 117Q102 148 110 181T151 298Q173 362 173 380Z"></path></g><g data-mml-node="mi" transform="translate(6065.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(6594.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(7194.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mi" transform="translate(7555.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(8084.6,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(8561.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g></g></g></g></svg></mjx-container></p><p>You can read more about it in <a href="http://www.statsandsnakeoil.com/tags/dixon-coles/">theseposts</a>.</p><p>We can make a simple adjustment to this model to capture the HFA beforeand after the seasons’ interruption: using <em>two separate home advantageparameters</em>. One for pre-interruption, and another for post-interruption.</p><p>This requires adjusting the above model to something like</p><p><mjx-container class="MathJax" jax="SVG" display="true"><svg style="vertical-align: -0.667ex" xmlns="http://www.w3.org/2000/svg" width="29.949ex" height="2.262ex" role="img" focusable="false" viewBox="0 -705 13237.6 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mo" transform="translate(2682.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(3738.6,0)"><g data-mml-node="mi"><path data-c="1D6FC" d="M34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26Z"></path></g><g data-mml-node="TeXAtom" transform="translate(673,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(576,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(1061,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1939,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(6162.1,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(6606.8,0)"><g data-mml-node="mi"><path data-c="1D6FD" d="M29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431Z"></path></g><g data-mml-node="TeXAtom" transform="translate(599,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(529,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1245,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1774,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(8856.7,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(9301.4,0)"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g><g data-mml-node="TeXAtom" transform="translate(551,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(503,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(954,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(10906.5,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z"></path></g><g data-mml-node="msub" transform="translate(11351.1,0)"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g><g data-mml-node="TeXAtom" transform="translate(551,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(503,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(988,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(1457,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></g></g></svg></mjx-container></p><p>where <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.65ex" xmlns="http://www.w3.org/2000/svg" width="3.631ex" height="1.647ex" role="img" focusable="false" viewBox="0 -441 1605.1 728.2"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g><g data-mml-node="TeXAtom" transform="translate(551,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(503,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(954,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g></g></g></svg></mjx-container> is the home advantage parameter for pre-interruption games.For post-restart games, this parameter is fixed to 1, and thus has no effect. Theinverse is true for <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.65ex" xmlns="http://www.w3.org/2000/svg" width="4.268ex" height="1.647ex" role="img" focusable="false" viewBox="0 -441 1886.5 728.2"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D6FE" d="M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z"></path></g><g data-mml-node="TeXAtom" transform="translate(551,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(503,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(988,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(1457,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></g></g></svg></mjx-container>.</p><h2 id="Loading-the-data">Loading the data</h2><p>First off, we need some data from before and after the COVID-enforcedbreak. I am using data for the 2019/20 season in the Premier League,Serie A, Bundesliga, and La Liga.</p><p>In this post, I’m using a modified approach to the standard Dixon-Colesmodel that allows the use of <a href="http://www.statsandsnakeoil.com/2018/06/22/dixon-coles-and-xg-together-at-last/">xG as an input to teamstrengths</a>.To do this, we need to load in xG value of each shot in any relevantmatches.</p><p>The data used in this post comes from Understat. Since obtaining thisdata is not the core message of this post, I have left the code for thispart out. However, you can find the R code used to scrape the data atthe <a href="https://gist.github.com/Torvaney/42cd82addb3ba2c4f33ec3247e66889c">thislink</a>.</p><p>If you have access to a broader dataset, or data from a differentprovider, I encourage you to experiment with re-running the analysis onthat data. The only code chunk you should need to edit is the followingone. The rest of the chunks should be plug and play, provided the columnnames of the <code>shots</code> dataframe remain the same as the ones below.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line"></span><br><span class="line">shots &lt;-</span><br><span class="line">  <span class="comment"># EDIT HERE</span></span><br><span class="line">  <span class="comment"># Each row of this CSV refers to a single shot</span></span><br><span class="line">  read_csv(here::here(<span class="string">"data"</span>, <span class="string">"understat-shots.csv"</span>)) %&gt;%</span><br><span class="line">  <span class="comment"># If using your own data source, select and rename your own column names</span></span><br><span class="line">  <span class="comment"># below for the rest of the analysis to run</span></span><br><span class="line">  transmute(</span><br><span class="line">    match_id,</span><br><span class="line">    league_name,</span><br><span class="line">    match_date = lubridate::as_date(datetime),</span><br><span class="line">    is_home    = h_a == <span class="string">"h"</span> ,</span><br><span class="line">    home_team  = h_team,</span><br><span class="line">    away_team  = a_team,</span><br><span class="line">    home_goals = h_goals,</span><br><span class="line">    away_goals = a_goals,</span><br><span class="line">    xG</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line"><span class="comment"># Preview the data</span></span><br><span class="line">sample_n(shots, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 10 x 9##    match_id league_name match_date is_home home_team           away_team      home_goals away_goals     xG##       &lt;dbl&gt; &lt;chr&gt;       &lt;date&gt;     &lt;lgl&gt;   &lt;chr&gt;               &lt;chr&gt;               &lt;dbl&gt;      &lt;dbl&gt;  &lt;dbl&gt;##  1    12702 Bundesliga  2020-06-27 TRUE    Borussia M.Gladbach Hertha Berlin           2          1 0.110##  2    13246 Serie A     2019-12-15 FALSE   AC Milan            Sassuolo                0          0 0.0128##  3    13194 Serie A     2019-11-03 TRUE    Lecce               Sassuolo                2          2 0.108##  4    12336 La liga     2020-06-28 FALSE   Espanyol            Real Madrid             0          1 0.469##  5    13252 Serie A     2020-02-05 TRUE    Lazio               Verona                  0          0 0.0684##  6    12705 Bundesliga  2020-06-27 FALSE   Werder Bremen       FC Cologne              6          1 0.422##  7    11818 EPL         2019-12-21 FALSE   Newcastle United    Crystal Palace          1          0 0.0456##  8    11842 EPL         2019-12-28 FALSE   West Ham            Leicester               1          2 0.761##  9    11838 EPL         2019-12-28 FALSE   Newcastle United    Everton                 1          2 0.528## 10    13344 Serie A     2020-02-29 TRUE    Napoli              Torino                  2          1 0.0753</code></pre><h2 id="Preparing-the-data">Preparing the data</h2><p>The next step is to split the shots taken by the home and away team intoseparate groups:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">match_xgs &lt;-</span><br><span class="line">  shots %&gt;%</span><br><span class="line">  group_by(match_id, league_name, match_date, home_team, away_team, home_goals, away_goals) %&gt;%</span><br><span class="line">  summarise(home_xgs = <span class="built_in">list</span>(discard(ifelse(is_home, xG, <span class="literal">NA</span>), <span class="built_in">is.na</span>)),</span><br><span class="line">            away_xgs = <span class="built_in">list</span>(discard(ifelse(!is_home, xG, <span class="literal">NA</span>), <span class="built_in">is.na</span>))) %&gt;%</span><br><span class="line">  ungroup()</span><br><span class="line"></span><br><span class="line">match_xgs</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 1,407 x 9##    match_id league_name match_date home_team         away_team               home_goals away_goals home_xgs   away_xgs##       &lt;dbl&gt; &lt;chr&gt;       &lt;date&gt;     &lt;chr&gt;             &lt;chr&gt;                        &lt;dbl&gt;      &lt;dbl&gt; &lt;list&gt;     &lt;list&gt;##  1    11643 EPL         2019-08-09 Liverpool         Norwich                          4          1 &lt;dbl [15]&gt; &lt;dbl [13]&gt;##  2    11644 EPL         2019-08-10 West Ham          Manchester City                  0          5 &lt;dbl [5]&gt;  &lt;dbl [14]&gt;##  3    11645 EPL         2019-08-10 Bournemouth       Sheffield United                 1          1 &lt;dbl [13]&gt; &lt;dbl [8]&gt;##  4    11646 EPL         2019-08-10 Burnley           Southampton                      3          0 &lt;dbl [10]&gt; &lt;dbl [11]&gt;##  5    11647 EPL         2019-08-10 Crystal Palace    Everton                          0          0 &lt;dbl [6]&gt;  &lt;dbl [10]&gt;##  6    11648 EPL         2019-08-10 Watford           Brighton                         0          3 &lt;dbl [12]&gt; &lt;dbl [5]&gt;##  7    11649 EPL         2019-08-10 Tottenham         Aston Villa                      3          1 &lt;dbl [31]&gt; &lt;dbl [7]&gt;##  8    11650 EPL         2019-08-11 Newcastle United  Arsenal                          0          1 &lt;dbl [9]&gt;  &lt;dbl [8]&gt;##  9    11651 EPL         2019-08-11 Leicester         Wolverhampton Wanderers          0          0 &lt;dbl [16]&gt; &lt;dbl [8]&gt;## 10    11652 EPL         2019-08-11 Manchester United Chelsea                          4          0 &lt;dbl [11]&gt; &lt;dbl [18]&gt;## # … with 1,397 more rows</code></pre><p>From here, we can generate the features for an “xG-Dixon-Coles” model.This involves “re-simulating” each match based on the shots taken withinit, to get a set of weighted scorelines. For more on this method, see<a href="http://www.statsandsnakeoil.com/2018/06/22/dixon-coles-and-xg-together-at-last/">thispost</a>.</p><p>We also generate two new variables corresponding to the two homeadvantage parameters: <code>pre_restart</code> and <code>post_restart</code>.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulate_shots &lt;- <span class="keyword">function</span>(xgs) {</span><br><span class="line">  tibble::tibble(goal = <span class="number">0</span>:<span class="built_in">length</span>(xgs),</span><br><span class="line">                 prob = poisbinom::dpoisbinom(<span class="number">0</span>:<span class="built_in">length</span>(xgs), xgs))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">simulate_game &lt;- <span class="keyword">function</span>(home_xgs, away_xgs) {</span><br><span class="line">  home_probs &lt;- simulate_shots(home_xgs) %&gt;% dplyr::rename_all(<span class="keyword">function</span>(x) paste0(<span class="string">"h"</span>, x))</span><br><span class="line">  away_probs &lt;- simulate_shots(away_xgs) %&gt;% dplyr::rename_all(<span class="keyword">function</span>(x) paste0(<span class="string">"a"</span>, x))</span><br><span class="line"></span><br><span class="line">  tidyr::crossing(home_probs, away_probs) %&gt;%</span><br><span class="line">    dplyr::mutate(prob = .data$hprob * .data$aprob)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">simulated_games &lt;-</span><br><span class="line">  match_xgs %&gt;%</span><br><span class="line">  mutate(simulated_probabilities = map2(home_xgs, away_xgs, simulate_game),</span><br><span class="line">         pre_restart  = match_date &lt; <span class="string">"2020-05-01"</span>,</span><br><span class="line">         post_restart = !pre_restart) %&gt;%</span><br><span class="line">  unnest(cols = <span class="built_in">c</span>(simulated_probabilities)) %&gt;%</span><br><span class="line">  filter(prob &gt; <span class="number">0.01</span>)</span><br><span class="line"></span><br><span class="line">simulated_games</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 20,694 x 16##    match_id league_name match_date home_team away_team home_goals away_goals home_xgs away_xgs hgoal  hprob agoal  aprob##       &lt;dbl&gt; &lt;chr&gt;       &lt;date&gt;     &lt;chr&gt;     &lt;chr&gt;          &lt;dbl&gt;      &lt;dbl&gt; &lt;list&gt;   &lt;list&gt;   &lt;int&gt;  &lt;dbl&gt; &lt;int&gt;  &lt;dbl&gt;##  1    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     0 0.0556     0 0.398##  2    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     0 0.0556     1 0.405##  3    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     1 0.230      0 0.398##  4    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     1 0.230      1 0.405##  5    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     1 0.230      2 0.158##  6    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     2 0.328      0 0.398##  7    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     2 0.328      1 0.405##  8    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     2 0.328      2 0.158##  9    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     2 0.328      3 0.0337## 10    11643 EPL         2019-08-09 Liverpool Norwich            4          1 &lt;dbl [1… &lt;dbl [1…     3 0.241      0 0.398## # … with 20,684 more rows, and 3 more variables: prob &lt;dbl&gt;, pre_restart &lt;lgl&gt;, post_restart &lt;lgl&gt;</code></pre><h2 id="Estimating-the-home-advantage">Estimating the home advantage</h2><p>For each league, we are going to fit the aforementioned model with twoseparate home advantage parameters.</p><p>Because we are extending the core Dixon-Coles model, we must use themore flexible <code>dixoncoles_ext</code> function. This function allows us to specifycustom formulas for the home (<code>f1</code>) and away (<code>f2</code>) goals estimates withextra parameters, such as our <code>pre_/post_restart</code> HFA parameters.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(regista)</span><br><span class="line"></span><br><span class="line">fit_model &lt;- partial(</span><br><span class="line">  dixoncoles_ext,</span><br><span class="line">  f1 = hgoal ~ off(home_team) + def(away_team) + pre_restart + post_restart + <span class="number">0</span>,</span><br><span class="line">  f2 = agoal ~ off(away_team) + def(home_team) + <span class="number">0</span>,</span><br><span class="line">  weights = prob</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">models &lt;-</span><br><span class="line">  simulated_games %&gt;%</span><br><span class="line">  group_by(league_name) %&gt;%</span><br><span class="line">  nest() %&gt;%</span><br><span class="line">  mutate(model = map(data, fit_model)) %&gt;%</span><br><span class="line">  ungroup()</span><br><span class="line"></span><br><span class="line">models</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 4 x 3##   league_name data                  model##   &lt;chr&gt;       &lt;list&gt;                &lt;list&gt;## 1 EPL         &lt;tibble [5,456 × 15]&gt; &lt;dixoncls&gt;## 2 La liga     &lt;tibble [5,110 × 15]&gt; &lt;dixoncls&gt;## 3 Bundesliga  &lt;tibble [4,804 × 15]&gt; &lt;dixoncls&gt;## 4 Serie A     &lt;tibble [5,324 × 15]&gt; &lt;dixoncls&gt;</code></pre><p>We can extract the parameter estimates from the models with<code>broom::tidy</code>. While the model will estimate attack and defencestrengths for each team too, we specifically care about the estimatesfor <code>pre_restart</code> and <code>post_restart</code>.</p><p>So, let’s take these and compare the before and after estimates:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">model_parameters &lt;-</span><br><span class="line">  models %&gt;%</span><br><span class="line">  mutate(params = map(model, broom::tidy)) %&gt;%</span><br><span class="line">  select(league_name, params) %&gt;%</span><br><span class="line">  unnest(params)</span><br><span class="line"></span><br><span class="line">hfa_comparison &lt;-</span><br><span class="line">  model_parameters %&gt;%</span><br><span class="line">  mutate(value = <span class="built_in">exp</span>(value)) %&gt;%  <span class="comment"># Get the value out of log space...</span></span><br><span class="line">  filter(str_detect(parameter, <span class="string">"(pre|post)_restart"</span>)) %&gt;%</span><br><span class="line">  pivot_wider(id_cols = league_name, names_from = parameter, values_from = value)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">hfa_comparison %&gt;%</span><br><span class="line">  ggplot(aes(x = pre_restart - <span class="number">1</span>, y = post_restart - <span class="number">1</span>)) +</span><br><span class="line"></span><br><span class="line">  map(seq(<span class="number">0</span>, <span class="number">1</span>, by = <span class="number">0.25</span>), <span class="keyword">function</span>(slope) {</span><br><span class="line">    <span class="built_in">list</span>(</span><br><span class="line">      geom_segment(x = <span class="number">0</span>, y = <span class="number">0</span>, xend = <span class="number">1.5</span>, yend = slope*<span class="number">1.5</span>, linetype = <span class="string">"dashed"</span>, colour = <span class="string">"lightgray"</span>),</span><br><span class="line">      annotate(geom = <span class="string">"label"</span>, x = <span class="number">0.45</span>, y = slope*<span class="number">0.45</span>,</span><br><span class="line">               label = str_glue(<span class="string">"{scales::percent(1 - slope, 1)} reduction"</span>),</span><br><span class="line">               colour = <span class="string">"lightgray"</span>, label.size = <span class="number">0</span>)</span><br><span class="line">    )</span><br><span class="line">  }) +</span><br><span class="line"></span><br><span class="line">  ggrepel::geom_text_repel(aes(label = league_name)) +</span><br><span class="line">  geom_point(aes(colour = (post_restart / pre_restart) &lt; <span class="number">0.97</span>)) +</span><br><span class="line">  scale_x_continuous(limits = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">0.5</span>), labels = scales::percent_format(accuracy = <span class="number">1</span>)) +</span><br><span class="line">  scale_y_continuous(limits = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">0.5</span>), labels = scales::percent_format(accuracy = <span class="number">1</span>)) +</span><br><span class="line">  scale_colour_manual(values = <span class="built_in">c</span>(<span class="string">"gray"</span>, <span class="string">"red"</span>), guide = <span class="literal">FALSE</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(panel.grid.major = element_line(linetype = <span class="string">"dotted"</span>),</span><br><span class="line">        panel.grid.minor = element_line(linetype = <span class="string">"dotted"</span>)) +</span><br><span class="line">  labs(title = <span class="string">"How much was HFA reduced in closed-door games?"</span>,</span><br><span class="line">       subtitle = <span class="string">"HFA measured by % increase in goalscoring at home"</span>,</span><br><span class="line">       x = <span class="string">"HFA before the restart"</span>,</span><br><span class="line">       y = <span class="string">"HFA after the restart"</span>)</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-5-1.jpg" alt=""></p>]]></content>
    
    
      
      
    <summary type="html">&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;
&lt;p&gt;How much has hom</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="R" scheme="https://www.statsandsnakeoil.com/tags/r/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="regista" scheme="https://www.statsandsnakeoil.com/tags/regista/"/>
    
  </entry>
  
  <entry>
    <title>How to count minutes played</title>
    <link href="https://www.statsandsnakeoil.com/2020/06/25/how-to-count-minutes-played/"/>
    <id>https://www.statsandsnakeoil.com/2020/06/25/how-to-count-minutes-played/</id>
    <published>2020-06-25T23:03:11.000Z</published>
    <updated>2022-09-26T13:37:13.524Z</updated>
    
    <content type="html"><![CDATA[<p>What is the best way to count minutes played based on event-data, like you mightget from <s>Opta</s> StatsPerform, Wyscout, or StatsBomb?</p><p>It may seem like a trivial task, but it’s also a fundamental task whichunderpins both basic and sophisticated analytics. Mistakes in minutes played calculations can be particularly perniciousbecause:</p><ul><li>They aren’t always obvious</li><li>They propagate to downstream calculations (e.g. p90 stats)</li><li>They can be difficult to identify automatically and debug</li></ul><p>With this in mind, when I recently has a chance to revisit some minutes played calculations, I wanted to take the time to find The Right Way to do it.I am happy with the solution I have found; although I would love to hear feedbackfrom other sports analytics practitioners to see how this method might be improved.</p><h2 id="The-method">The method</h2><p>Let’s say we want to calculate minutes played for each player, with subtotals fortime played in each position. The basic outline of the proposed method goessomething like this:</p><p>First, <strong>arrange the events from a given match in sequence order</strong>:</p><p><img src="count-mins-played-diagrams.001.jpg" alt=""></p><p>I have shown diagrams in both a “<strong>schematic</strong>” and “<strong>tabular</strong>” view. These areequivalent views on the same idea. In the schematic diagram I am trying to conveythe basic concept. In the tabular form, I am trying to show something alittle closer to how you might do such a transformation in a relational database,or with pandas, dplyr, or such like.</p><p>Next, <strong>identify any events that result in a change to one of the relevant match conditions</strong>.For example, a change in formation, or a goal.</p><p><img src="count-mins-played-diagrams.002.png" alt=""></p><p>…And filter out any irrelevant events</p><p><img src="count-mins-played-diagrams.003.png" alt=""></p><p>Then, <strong>construct time-windows, using the successive events as start and end points</strong></p><p><img src="count-mins-played-diagrams.004.png" alt=""></p><p>In practice this will probably require you to infer some information from precedingor following rows. For example, you may have to infer that a player started thesecond half from the fact that they started the match and were not substitutedor sent off.</p><p><img src="count-mins-played-diagrams.005.jpg" alt=""></p><p>Finally, <strong>aggregate the time within each window</strong> (grouped by player and match condition).Window #5 is ignored (<code>duration is NULL</code>) because it takes place after the player was substituted.</p><p><img src="count-mins-played-diagrams.006.jpg" alt=""></p><p>This method can be extended to calculate minutes played according to anymatch condition you might be interested in. For example, each player’s minutesplayed in a certain position, or minutes played while the team is losing.</p><h2 id="What’s-so-good-about-this-method">What’s so good about this method?</h2><p>You may still think that minutes played is a trivial calculation; just find the differencebetween the time a player came off the pitch, and the time a player came onto the pitch.And yes, this is basically true. However, going through the hassle of calculatingtime windows like this has a few significant benefits:</p><ul><li>We can slice minutes played to any grain that we want</li><li>The intermediate time windows are useful - they allow you to get all the eventsfrom those windows, too</li><li>It works with any event-data provider and isn’t beholden to the quirks of any oneparticular data collection process</li></ul><p>An additional nice feature of this method, is that if you feel comfortable withwindow functions, it can be expressed relatively cleanly in pure SQL(if that’s your thing).</p><p>Let’s explore these in a little more detail.</p><h3 id="Slicing-to-any-grain">Slicing to any grain</h3><p>Counting minutes played is good because it allows you to rate-adjust your countingstats. Going from goals per game to goals per 90 is a nice improvement. However,inevitably, you will want to go further.</p><p>For example, we know that teams set up differently when they are winning or losing,compared to when the scorelines are level. So perhaps you want to adjust for this.Well, to do any adjustments, you need to know how much time was spent at winning,drawing, or losing.</p><p>The “find the difference” approach struggles to do this elegantly, and is difficultto extend to multiple such conditions. For example, time played in a given position<em>and</em> while winning.</p><p>The windowed approach, on the other hand, can extend to any level of detail justby adding a new condition to divide windows by.</p><h3 id="Using-the-windows">Using the windows</h3><p>Another nice properties of this approach is that the windows created are a usefulasset in their own right.</p><p>We already have a way to find the minutes played in a given position. However,what if you wanted to get the individual <em>actions</em> the player made in said position.Or calculate the actions per 90.</p><p>Handily, you can use the intermediate windows to fetch the events within them, too.</p><h3 id="Easy-to-translate-across-data-providers">Easy to translate across data providers</h3><p>Even if we just wanted to use the simple difference method, there are a few thingswe need to pay close attention to:</p><ul><li>Injury time</li><li>Extra time</li><li>Red cards and dismissals</li><li>Substitutions and injuries</li><li>Players leaving the field temporarily for physio treatment (if this is recorded at all)</li></ul><p>These are are pretty standard concerns, but there are differences between themajor providers in how they are represented as part of the event stream. Any methodclaiming to be the right one for calculating minutes played cannot betightly coupled to any one provider’s specification.</p><p>For this reason, I have talked about this method in rather general, conceptual terms.The key part of applying this method to your specific dataset comes in step 1 and 2(collating and ordering the events; tagging specific events) when reshaping thedata; the operations applied remain the same.</p><p>Because the reshaping keeps the data in an event-first structure, itis (in my opinion) both less onerous and more similar across providers than thereshaping required for the simple difference method.</p><h2 id="Are-there-any-downsides">Are there any downsides?</h2><p>I think any downsides are far outweighed by the benefits of using this method; however, thereare a couple of sticking points that you will need to navigate when implementingsomething like this:</p><ul><li><strong>Inferring missing info (e.g. position)</strong> when tracking multiple matchconditions. For example, when grouping by player position and scoreline, you willneed to infer the players’ positions for events which change the scoreline, since thisisn’t part of the base event data. In Postgres, window functions like<code>first_value</code> to handle this very well, but it is something to be aware of nonetheless.</li><li><strong>Multiple potential start and end-points within the match</strong> for each player.In our worked example we had to ignore window #5 because it took place after theplayer was substituted off. Likewise, if a player leaves the match for whateverreason in the first half, we shouldn’t infer their presence in the second half.Again, window function can handle these permanent entrance/exits well.</li></ul><h2 id="That’s-all-folks">That’s all, folks</h2><p>If you can see any improvements to this, or if you have any better ways of gettingthe same result, I’d love to hear it. You can let me know <a href="https://twitter.com/torvaney">on twitter</a>or by email (<my twitter handle>[at]<a href="http://protonmail.com">protonmail.com</a>)</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;What is the best way to count minutes played based on event-data, like you might
get from &lt;s&gt;Opta&lt;/s&gt; StatsPerform, Wyscout, or StatsBomb</summary>
      
    
    
    
    
    <category term="sql" scheme="https://www.statsandsnakeoil.com/tags/sql/"/>
    
  </entry>
  
  <entry>
    <title>Predicting the Premier League with Dixon-Coles and xG</title>
    <link href="https://www.statsandsnakeoil.com/2019/01/06/predicting-the-premier-league-with-dixon-coles-and-xg/"/>
    <id>https://www.statsandsnakeoil.com/2019/01/06/predicting-the-premier-league-with-dixon-coles-and-xg/</id>
    <published>2019-01-06T15:14:46.000Z</published>
    <updated>2022-09-26T13:37:00.042Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I ran through <a href="http://www.statsandsnakeoil.com/2019/01/01/predicting-the-premier-league-with-dixon-coles/">Predicting the Premier League withDixon-Coles</a>.I was interested in seeing how these predictions change when we use xG,rather than observed goals. So I’ve run some quick comparisons using xGdata from <a href="http://understat.com">understat.com</a>.</p><p>The method will be largely the same as the previous post, so I’m goingto keep comments on the code relatively light. If anything remainsunclear, I’d suggest running through the code yourself (it should bereproducible on your computer - <a href="https://twitter.com/Torvaney">let meknow</a> if it’s not), or referring to theprevious post.</p><h2 id="Fetch-the-data">Fetch the data</h2><p>I’ll be using from understat (pasted onto GitHub), which you can access via the shortlink below:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line">library(regista)</span><br><span class="line"></span><br><span class="line">data &lt;-</span><br><span class="line">  read_csv(<span class="string">&quot;https://git.io/fhsnd&quot;</span>) %&gt;%</span><br><span class="line">  nest(side, xg, .key = <span class="string">&quot;shots&quot;</span>) %&gt;%</span><br><span class="line">  factor_teams(<span class="built_in">c</span>(<span class="string">&quot;home&quot;</span>, <span class="string">&quot;away&quot;</span>)) %&gt;%</span><br><span class="line">  rename(hgoal = hgoals,</span><br><span class="line">         agoal = agoals)  <span class="comment"># Use names consistent with the previous post...</span></span><br><span class="line"></span><br><span class="line">data</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 210 x 9##    match_id date                home  away  hgoal agoal league season shots##       &lt;dbl&gt; &lt;dttm&gt;              &lt;fct&gt; &lt;fct&gt; &lt;dbl&gt; &lt;dbl&gt; &lt;chr&gt;   &lt;dbl&gt; &lt;lis&gt;##  1     9197 2018-08-10 22:00:00 Manc… Leic…     2     1 EPL      2018 &lt;tib…##  2     9198 2018-08-11 14:30:00 Newc… Tott…     1     2 EPL      2018 &lt;tib…##  3     9199 2018-08-11 17:00:00 Watf… Brig…     2     0 EPL      2018 &lt;tib…##  4     9200 2018-08-11 17:00:00 Hudd… Chel…     0     3 EPL      2018 &lt;tib…##  5     9201 2018-08-11 17:00:00 Fulh… Crys…     0     2 EPL      2018 &lt;tib…##  6     9202 2018-08-11 17:00:00 Bour… Card…     2     0 EPL      2018 &lt;tib…##  7     9203 2018-08-11 19:30:00 Wolv… Ever…     2     2 EPL      2018 &lt;tib…##  8     9204 2018-08-12 15:30:00 Sout… Burn…     0     0 EPL      2018 &lt;tib…##  9     9205 2018-08-12 15:30:00 Live… West…     4     0 EPL      2018 &lt;tib…## 10     9206 2018-08-12 18:00:00 Arse… Manc…     0     2 EPL      2018 &lt;tib…## # ... with 200 more rows</code></pre><p>Again, this next code block is copied verbatim from another post, <a href="http://www.statsandsnakeoil.com/2018/06/22/dixon-coles-and-xg-together-at-last/">DixonColes and xG: together atlast</a>,and generates simulated scoreline probabilities from individual shotxGs.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">add_if_missing &lt;- <span class="keyword">function</span>(data, col, fill = <span class="number">0.0</span>) &#123;</span><br><span class="line">  <span class="comment"># Add column if not found in a dataframe</span></span><br><span class="line">  <span class="comment"># We need this in cases where a team has 0 shots (!)</span></span><br><span class="line">  <span class="keyword">if</span> (!(col %in% colnames(data))) &#123;</span><br><span class="line">    data[, col] &lt;- fill</span><br><span class="line">  &#125;</span><br><span class="line">  data</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">team_goal_probs &lt;- <span class="keyword">function</span>(xgs, side) &#123;</span><br><span class="line">  <span class="comment"># Find P(Goals=G) from a set of xGs by the poisson-binomial distribution</span></span><br><span class="line">  <span class="comment"># Use tidyeval to prefix column names with the team&#x27;s side (&quot;h&quot;ome or &quot;a&quot;way)</span></span><br><span class="line">  tibble(!!str_c(side, <span class="string">&quot;goal&quot;</span>) := <span class="number">0</span>:<span class="built_in">length</span>(xgs),</span><br><span class="line">         !!str_c(side, <span class="string">&quot;prob&quot;</span>)  := poisbinom::dpoisbinom(<span class="number">0</span>:<span class="built_in">length</span>(xgs), xgs))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">simulate_game &lt;- <span class="keyword">function</span>(shot_xgs) &#123;</span><br><span class="line">  shot_xgs %&gt;%</span><br><span class="line">    split(.$side) %&gt;%</span><br><span class="line">    imap(~ team_goal_probs(.x$xg, .y)) %&gt;%</span><br><span class="line">    reduce(crossing) %&gt;%</span><br><span class="line">    <span class="comment"># If there are no shots, give that team a 1.0 chance of scoring 0 goals</span></span><br><span class="line">    add_if_missing(<span class="string">&quot;hgoal&quot;</span>, <span class="number">0</span>) %&gt;%</span><br><span class="line">    add_if_missing(<span class="string">&quot;hprob&quot;</span>, <span class="number">1</span>) %&gt;%</span><br><span class="line">    add_if_missing(<span class="string">&quot;agoal&quot;</span>, <span class="number">0</span>) %&gt;%</span><br><span class="line">    add_if_missing(<span class="string">&quot;aprob&quot;</span>, <span class="number">1</span>) %&gt;%</span><br><span class="line">    mutate(prob = hprob * aprob) %&gt;%</span><br><span class="line">    select(hgoal, agoal, prob)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">simulated_games &lt;-</span><br><span class="line">  data %&gt;%</span><br><span class="line">  mutate(simulated_probabilities = map(shots, simulate_game)) %&gt;%</span><br><span class="line">  select(match_id, home, away, simulated_probabilities) %&gt;%</span><br><span class="line">  unnest() %&gt;%</span><br><span class="line">  filter(prob &gt; <span class="number">0.001</span>)  <span class="comment"># Keep the number of rows vaguely reasonable</span></span><br><span class="line"></span><br><span class="line">simulated_games</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 5,030 x 6##    match_id home              away      hgoal agoal    prob##       &lt;dbl&gt; &lt;fct&gt;             &lt;fct&gt;     &lt;int&gt; &lt;int&gt;   &lt;dbl&gt;##  1     9197 Manchester United Leicester     0     0 0.00222##  2     9197 Manchester United Leicester     1     0 0.00946##  3     9197 Manchester United Leicester     2     0 0.00832##  4     9197 Manchester United Leicester     3     0 0.00227##  5     9197 Manchester United Leicester     0     1 0.0414##  6     9197 Manchester United Leicester     1     1 0.177##  7     9197 Manchester United Leicester     2     1 0.155##  8     9197 Manchester United Leicester     3     1 0.0424##  9     9197 Manchester United Leicester     4     1 0.00539## 10     9197 Manchester United Leicester     0     2 0.0378## # ... with 5,020 more rows</code></pre><h2 id="Predict-the-unplayed-games">Predict the unplayed games</h2><h3 id="Work-out-the-unplayed-games">Work out the unplayed games</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">teams &lt;- factor(levels(data$home), levels = levels(data$home))</span><br><span class="line"></span><br><span class="line">unplayed_games &lt;-</span><br><span class="line">  crossing(home = teams,</span><br><span class="line">           away = teams) %&gt;%</span><br><span class="line">  filter(home != away) %&gt;%</span><br><span class="line">  anti_join(data, by = <span class="built_in">c</span>(<span class="string">&quot;home&quot;</span>, <span class="string">&quot;away&quot;</span>))</span><br><span class="line"></span><br><span class="line">unplayed_games</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 170 x 2##    home              away##    &lt;fct&gt;             &lt;fct&gt;##  1 Manchester United Watford##  2 Manchester United Southampton##  3 Manchester United Liverpool##  4 Manchester United Cardiff##  5 Manchester United West Ham##  6 Manchester United Chelsea##  7 Manchester United Manchester City##  8 Manchester United Burnley##  9 Manchester United Brighton## 10 Newcastle United  Huddersfield## # ... with 160 more rows</code></pre><h3 id="Make-predictions-for-those-unplayed-games">Make predictions for those unplayed games</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">models &lt;- <span class="built_in">list</span>(</span><br><span class="line">  obs_goals = dixoncoles(hgoal, agoal, home, away, data = data),</span><br><span class="line">  exp_goals = dixoncoles(hgoal, agoal, home, away, weights = prob, data = simulated_games)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">models</span><br></pre></td></tr></table></figure><pre><code>## $obs_goals#### Dixon-Coles model with specification:#### Home goals: hgoal ~ off(home) + def(away) + hfa + 0## Away goals: agoal ~ off(away) + def(home) + 0## Weights   : 1###### $exp_goals#### Dixon-Coles model with specification:#### Home goals: hgoal ~ off(home) + def(away) + hfa + 0## Away goals: agoal ~ off(away) + def(home) + 0## Weights   : prob</code></pre><p>Compare the team attack and defence parameters:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">get_team_params &lt;- <span class="keyword">function</span>(model) &#123;</span><br><span class="line">  broom::tidy(model) %&gt;%</span><br><span class="line">    filter(parameter %in% <span class="built_in">c</span>(<span class="string">&quot;off&quot;</span>, <span class="string">&quot;def&quot;</span>)) %&gt;%</span><br><span class="line">    mutate(value = <span class="built_in">exp</span>(value))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">bind_rows(</span><br><span class="line">  get_team_params(models$obs_goals) %&gt;% mutate(type = <span class="string">&quot;obs&quot;</span>),</span><br><span class="line">  get_team_params(models$exp_goals) %&gt;% mutate(type = <span class="string">&quot;exp&quot;</span>)</span><br><span class="line">) %&gt;%</span><br><span class="line">  spread(type, value) %&gt;%</span><br><span class="line">  ggplot(aes(x = <span class="built_in">exp</span>, y = obs)) +</span><br><span class="line">  geom_point(alpha = <span class="number">0.5</span>) +</span><br><span class="line">  ggrepel::geom_text_repel(aes(label = team)) +</span><br><span class="line">  geom_abline(slope = <span class="number">1</span>, intercept = <span class="number">0</span>, linetype = <span class="string">&quot;dotted&quot;</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  labs(title = <span class="string">&quot;Team strength estimates&quot;</span>,</span><br><span class="line">       x = <span class="string">&quot;Expected Goals&quot;</span>,</span><br><span class="line">       y = <span class="string">&quot;Observed Goals&quot;</span>) +</span><br><span class="line">  facet_wrap(~ parameter)</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-175-1.jpg" alt=""></p><p>The big changes seem to be Arsenal’s attack, and Lverpool’s defence bothlooking a lot worse by xG.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">unplayed_scorelines &lt;- imap_dfr(models, <span class="keyword">function</span>(model, name) &#123;</span><br><span class="line">  broom::augment(model, unplayed_games, type.predict = <span class="string">&quot;scorelines&quot;</span>) %&gt;%</span><br><span class="line">    unnest() %&gt;%</span><br><span class="line">    mutate(model = name)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">unplayed_scorelines</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 37,662 x 6##    home              away    hgoal agoal     prob model##    &lt;fct&gt;             &lt;fct&gt;   &lt;int&gt; &lt;int&gt;    &lt;dbl&gt; &lt;chr&gt;##  1 Manchester United Watford     0     0 0.0304   obs_goals##  2 Manchester United Watford     1     0 0.0435   obs_goals##  3 Manchester United Watford     2     0 0.0602   obs_goals##  4 Manchester United Watford     3     0 0.0468   obs_goals##  5 Manchester United Watford     4     0 0.0272   obs_goals##  6 Manchester United Watford     5     0 0.0127   obs_goals##  7 Manchester United Watford     6     0 0.00492  obs_goals##  8 Manchester United Watford     7     0 0.00164  obs_goals##  9 Manchester United Watford     8     0 0.000477 obs_goals## 10 Manchester United Watford     9     0 0.000123 obs_goals## # ... with 37,652 more rows</code></pre><h2 id="Simulate-the-rest-of-the-season">Simulate the rest of the season</h2><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">played_scorelines &lt;-</span><br><span class="line">  data %&gt;%</span><br><span class="line">  select(home, away, hgoal, agoal) %&gt;%</span><br><span class="line">  mutate(prob = <span class="number">1.0</span>) %&gt;%</span><br><span class="line">  crossing(model = unique(unplayed_scorelines$model))</span><br><span class="line"></span><br><span class="line">scorelines &lt;- bind_rows(</span><br><span class="line">  played_scorelines,</span><br><span class="line">  unplayed_scorelines</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">simulate_games &lt;- <span class="keyword">function</span>(scoreline_probabilities) &#123;</span><br><span class="line">  scoreline_probabilities %&gt;%</span><br><span class="line">    nest(hgoal, agoal, prob, .key = <span class="string">&quot;scorelines&quot;</span>) %&gt;%</span><br><span class="line">    mutate(sampled = map(scorelines, ~ sample_n(., <span class="number">1</span>, weight = prob))) %&gt;%</span><br><span class="line">    select(-scorelines) %&gt;%</span><br><span class="line">    unnest()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Show 1 simulation as an example</span></span><br><span class="line">single_simulation &lt;- simulate_games(scorelines)</span><br><span class="line">sample_n(single_simulation, <span class="number">5</span>)</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 5 x 6##   home         away              model     hgoal agoal    prob##   &lt;fct&gt;        &lt;fct&gt;             &lt;chr&gt;     &lt;dbl&gt; &lt;dbl&gt;   &lt;dbl&gt;## 1 Southampton  Everton           obs_goals     1     0 0.0555## 2 Leicester    Burnley           obs_goals     0     0 1## 3 Huddersfield Manchester City   exp_goals     1     3 0.0605## 4 Watford      Tottenham         exp_goals     2     1 1## 5 Arsenal      Manchester United obs_goals     1     5 0.00711</code></pre><h3 id="Calculating-the-league-table">Calculating the league table</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">calculate_table &lt;- <span class="keyword">function</span>(games) &#123;</span><br><span class="line">  games_augmented &lt;-</span><br><span class="line">    games %&gt;%</span><br><span class="line">    mutate(</span><br><span class="line">      hpoints = case_when(</span><br><span class="line">        hgoal &gt; agoal  ~ <span class="number">3</span>,</span><br><span class="line">        hgoal == agoal ~ <span class="number">1</span>,</span><br><span class="line">        agoal &gt; hgoal  ~ <span class="number">0</span></span><br><span class="line">      ),</span><br><span class="line">      apoints = case_when(</span><br><span class="line">        hgoal &gt; agoal  ~ <span class="number">0</span>,</span><br><span class="line">        hgoal == agoal ~ <span class="number">1</span>,</span><br><span class="line">        agoal &gt; hgoal  ~ <span class="number">3</span></span><br><span class="line">      )</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  games_home &lt;-</span><br><span class="line">    games_augmented %&gt;%</span><br><span class="line">    select(</span><br><span class="line">      team   = home,</span><br><span class="line">      gf     = hgoal,</span><br><span class="line">      ga     = agoal,</span><br><span class="line">      points = hpoints</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  games_away &lt;-</span><br><span class="line">    games_augmented %&gt;%</span><br><span class="line">    select(</span><br><span class="line">      team   = away,</span><br><span class="line">      gf     = agoal,</span><br><span class="line">      ga     = hgoal,</span><br><span class="line">      points = apoints</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  bind_rows(games_home, games_away) %&gt;%</span><br><span class="line">    group_by(team) %&gt;%</span><br><span class="line">    summarise(w  = <span class="built_in">sum</span>(gf &gt; ga),</span><br><span class="line">              d  = <span class="built_in">sum</span>(gf == ga),</span><br><span class="line">              l  = <span class="built_in">sum</span>(gf &lt; ga),</span><br><span class="line">              gf = <span class="built_in">sum</span>(gf),</span><br><span class="line">              ga = <span class="built_in">sum</span>(ga),</span><br><span class="line">              gd = gf - ga,</span><br><span class="line">              points = <span class="built_in">sum</span>(points)) %&gt;%</span><br><span class="line">    arrange(desc(points), desc(gd), desc(gf)) %&gt;%</span><br><span class="line">    mutate(position = row_number())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Running-the-simulations">Running the simulations</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">n_simulations &lt;- 1000</span><br><span class="line"></span><br><span class="line">simulated_tables &lt;-</span><br><span class="line">  rerun(n_simulations, simulate_games(scorelines)) %&gt;%</span><br><span class="line">  <span class="comment"># We need to nest the data so that we can calculate tables for</span></span><br><span class="line">  <span class="comment"># xG and G separately</span></span><br><span class="line">  map(~ nest(., -model, .key = <span class="string">&quot;scoreline&quot;</span>)) %&gt;%</span><br><span class="line">  bind_rows(.id = <span class="string">&quot;simulation_id&quot;</span>) %&gt;%</span><br><span class="line">  mutate(table = map(scoreline, calculate_table)) %&gt;%</span><br><span class="line">  unnest(table, .drop = <span class="literal">TRUE</span>)</span><br><span class="line"></span><br><span class="line">simulated_tables</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 40,000 x 11##    simulation_id model team      w     d     l    gf    ga    gd points##    &lt;chr&gt;         &lt;chr&gt; &lt;fct&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;dbl&gt; &lt;dbl&gt; &lt;dbl&gt;  &lt;dbl&gt;##  1 1             exp_… Live…    29     7     2    79    24    55     94##  2 1             exp_… Manc…    29     5     4   100    31    69     92##  3 1             exp_… Tott…    25     4     9    78    41    37     79##  4 1             exp_… Chel…    20    10     8    60    32    28     70##  5 1             exp_… Arse…    20     7    11    73    54    19     67##  6 1             exp_… Manc…    19    10     9    79    62    17     67##  7 1             exp_… Ever…    17     8    13    60    51     9     59##  8 1             exp_… Wolv…    16    10    12    41    37     4     58##  9 1             exp_… Watf…    12    12    14    53    59    -6     48## 10 1             exp_… Bour…    14     6    18    53    69   -16     48## # ... with 39,990 more rows, and 1 more variable: position &lt;int&gt;</code></pre><h2 id="Results">Results</h2><h3 id="Position-probabilities">Position probabilities</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">n_teams &lt;- <span class="built_in">length</span>(teams)</span><br><span class="line"></span><br><span class="line">simulated_tables %&gt;%</span><br><span class="line">  count(model, team, position) %&gt;%</span><br><span class="line">  mutate(p = n / n_simulations) %&gt;%</span><br><span class="line">  select(model, team, position, p) %&gt;%</span><br><span class="line">  spread(model, p) %&gt;%</span><br><span class="line">  arrange(desc(<span class="built_in">abs</span>(exp_goals - obs_goals))) %&gt;%</span><br><span class="line">  ggplot(aes(x = position)) +</span><br><span class="line">  geom_segment(aes(xend = position, y = obs_goals, yend = exp_goals), colour = <span class="string">&quot;dimgray&quot;</span>) +</span><br><span class="line">  geom_point(aes(y = exp_goals)) +</span><br><span class="line">  facet_wrap(~ reorder(team, position)) +</span><br><span class="line">  scale_x_continuous(breaks = <span class="number">1</span>:n_teams) +</span><br><span class="line">  scale_y_continuous(labels = scales::percent_format(),</span><br><span class="line">                     limits = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">1</span>)) +</span><br><span class="line">  labs(title = <span class="string">&quot;Position probabilities&quot;</span>,</span><br><span class="line">       subtitle = <span class="string">&quot;xG vs Goals&quot;</span>,</span><br><span class="line">       x = <span class="literal">NULL</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(title       = element_text(size = <span class="number">30</span>),</span><br><span class="line">        strip.text  = element_text(size = <span class="number">25</span>),</span><br><span class="line">        axis.text.y = element_text(size = <span class="number">15</span>),</span><br><span class="line">        axis.text.x = element_text(size = <span class="number">10</span>),</span><br><span class="line">        panel.grid.major.y = element_line(linetype = <span class="string">&quot;dotted&quot;</span>),</span><br><span class="line">        panel.grid.major.x = element_blank(),</span><br><span class="line">        panel.grid.minor   = element_blank())</span><br></pre></td></tr></table></figure><pre><code>## Warning: Removed 29 rows containing missing values (geom_segment).## Warning: Removed 1 rows containing missing values (geom_point).</code></pre><p><img src="unnamed-chunk-180-1.png" alt=""></p><p>Liverpool’s chances of winning the league, look much more realistic(lower, sorry Liverpool fans) when we use xG, rather than just goals:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(model, team) %&gt;%</span><br><span class="line">  summarise(p = mean(position == <span class="number">1</span>)) %&gt;%</span><br><span class="line">  filter(p &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  spread(model, p) %&gt;%</span><br><span class="line">  arrange(desc(exp_goals))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 2 x 3##   team            exp_goals obs_goals##   &lt;fct&gt;               &lt;dbl&gt;     &lt;dbl&gt;## 1 Liverpool           0.65      0.829## 2 Manchester City     0.346     0.166</code></pre><p>There are fewer big changes in the relegation probablities, but Cardiffand Southampton are probably the biggest winners, while things look moreprecarious for Burnley and Newcastle:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(model, team) %&gt;%</span><br><span class="line">  summarise(p = mean(position &gt;= <span class="number">18</span>)) %&gt;%</span><br><span class="line">  filter(p &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  spread(model, p) %&gt;%</span><br><span class="line">  arrange(desc(exp_goals))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 6 x 3##   team             exp_goals obs_goals##   &lt;fct&gt;                &lt;dbl&gt;     &lt;dbl&gt;## 1 Huddersfield         0.993     0.994## 2 Fulham               0.829     0.882## 3 Burnley              0.505     0.469## 4 Newcastle United     0.386     0.164## 5 Cardiff              0.216     0.308## 6 Southampton          0.06      0.177</code></pre><p>We can look at the race for the Champion’s League, too:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(model, team) %&gt;%</span><br><span class="line">  summarise(p = mean(position &lt;= <span class="number">4</span>)) %&gt;%</span><br><span class="line">  filter(p &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  spread(model, p) %&gt;%</span><br><span class="line">  arrange(desc(exp_goals))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 6 x 3##   team              exp_goals obs_goals##   &lt;fct&gt;                 &lt;dbl&gt;     &lt;dbl&gt;## 1 Liverpool             1         1## 2 Manchester City       1         1## 3 Tottenham             0.893     0.953## 4 Chelsea               0.882     0.776## 5 Arsenal               0.164     0.239## 6 Manchester United     0.06      0.032</code></pre><h3 id="Points-totals">Points totals</h3><p>We can see how the distribution of points totals changes. The whitedistribution represents the estimates based on goals, while the grey onerefers to those based on xG.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">ggplot(data = simulated_tables, aes(x = points, y = reorder(team, points))) +</span><br><span class="line">  ggridges::geom_density_ridges(data = simulated_tables %&gt;% filter(model == <span class="string">&quot;obs_goals&quot;</span>),</span><br><span class="line">                                bandwidth = <span class="number">1.0</span>,</span><br><span class="line">                                rel_min_height = <span class="number">0.01</span>,</span><br><span class="line">                                scale = <span class="number">0.75</span>,</span><br><span class="line">                                fill = <span class="string">&quot;white&quot;</span>) +</span><br><span class="line">  ggridges::geom_density_ridges(data = simulated_tables %&gt;% filter(model == <span class="string">&quot;exp_goals&quot;</span>),</span><br><span class="line">                                bandwidth = <span class="number">1.0</span>,</span><br><span class="line">                                rel_min_height = <span class="number">0.01</span>,</span><br><span class="line">                                scale = <span class="number">0.75</span>,</span><br><span class="line">                                alpha = <span class="number">0.5</span>) +</span><br><span class="line">  labs(title = <span class="string">&quot;Predicted points total distribution&quot;</span>,</span><br><span class="line">       subtitle = <span class="string">&quot;Grey = xG, White = Goals&quot;</span>,</span><br><span class="line">       x = <span class="literal">NULL</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal()</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-184-1.jpg" alt=""></p><p>The xG-based model is less keen on Liverpool’s chances of meeting 100points. The goal-based model’s dropped its probability by about 10%points <a href="http://www.statsandsnakeoil.com/2019/01/01/predicting-the-premier-league-with-dixon-coles/">since before the Man Citygame</a></p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(model, team) %&gt;%</span><br><span class="line">  summarise(p = mean(points &gt;= <span class="number">100</span>)) %&gt;%</span><br><span class="line">  filter(p &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  spread(model, p) %&gt;%</span><br><span class="line">  arrange(desc(exp_goals))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 1 x 3##   team      exp_goals obs_goals##   &lt;fct&gt;         &lt;dbl&gt;     &lt;dbl&gt;## 1 Liverpool     0.077      0.31</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently, I ran through &lt;a href=&quot;http://www.statsandsnakeoil.com/2019/01/01/predicting-the-premier-league-with-dixon-coles/&quot;&gt;Predicting t</summary>
      
    
    
    
    
    <category term="xG" scheme="https://www.statsandsnakeoil.com/tags/xg/"/>
    
    <category term="Expected goals" scheme="https://www.statsandsnakeoil.com/tags/expected-goals/"/>
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="regista" scheme="https://www.statsandsnakeoil.com/tags/regista/"/>
    
    <category term="Premier League" scheme="https://www.statsandsnakeoil.com/tags/premier-league/"/>
    
  </entry>
  
  <entry>
    <title>Predicting the Premier League with Dixon-Coles</title>
    <link href="https://www.statsandsnakeoil.com/2019/01/01/predicting-the-premier-league-with-dixon-coles/"/>
    <id>https://www.statsandsnakeoil.com/2019/01/01/predicting-the-premier-league-with-dixon-coles/</id>
    <published>2019-01-01T19:45:22.000Z</published>
    <updated>2022-09-26T13:37:03.278Z</updated>
    
    <content type="html"><![CDATA[<p>In this post, I’m going to walk through one way of predicting the restof the Premier League season with regista’s implementation of theDixon-Coles model. I am going to demonstrate one method of using <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">MonteCarlo</a> simulation topredict various outcomes in the Premier League season.</p><p>In practice, this just means:</p><ul><li>Predicting the unplayed games,</li><li>Sampling from those individual game predictions over and over again(“simulating” the rest of the season)</li><li>Aggregate simulated games to get outcome probabilities</li></ul><p>If the Dixon-Coles model is new to you, there are some previousexplorations of the model on <a href="http://www.statsandsnakeoil.com/tags/regista/">thissite</a>, and the <a href="http://web.math.ku.dk/~rolf/teaching/thesis/DixonColes.pdf">originalpaper</a> isvery approachable.</p><h2 id="Fetch-the-data">Fetch the data</h2><p>I’ll be using the data provided by<a href="http://www.football-data.co.uk">football-data.co.uk</a>.</p><p>To do this I have created a (very) minimal R package for downloading thedata from the site, called<a href="https://github.com/Torvaney/footballdatr">footballdatr</a>.</p><p>You can install it from github like so:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">devtools::install_github(<span class="string">&quot;torvaney/footballdatr&quot;</span>)</span><br></pre></td></tr></table></figure><p>It’s still very early, and so probably (definitely) has some bugs. Ifyou do find anything, please don’t hesitate to let me know on<a href="https://twitter.com/torvaney">twitter</a> or on<a href="https://github.com/Torvaney/footballdatr/issues">GitHub</a>.</p><p>Nonetheless, we can use this to get the data for the 2018/19 EnglishPremier League season.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">library(tidyverse)</span><br><span class="line">library(regista)</span><br><span class="line"></span><br><span class="line">data &lt;-</span><br><span class="line">  footballdatr::fetch_data(<span class="string">&quot;england&quot;</span>, division = <span class="number">0</span>, season = <span class="number">2018</span>) %&gt;%</span><br><span class="line">  factor_teams(<span class="built_in">c</span>(<span class="string">&quot;home&quot;</span>, <span class="string">&quot;away&quot;</span>))</span><br><span class="line"></span><br><span class="line">data</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 200 x 23##    competition date       home  away  hgoal agoal result hgoal_ht agoal_ht##    &lt;chr&gt;       &lt;date&gt;     &lt;fct&gt; &lt;fct&gt; &lt;int&gt; &lt;int&gt; &lt;chr&gt;     &lt;int&gt;    &lt;int&gt;##  1 E0          2018-08-10 Man … Leic…     2     1 H             1        0##  2 E0          2018-08-11 Bour… Card…     2     0 H             1        0##  3 E0          2018-08-11 Fulh… Crys…     0     2 A             0        1##  4 E0          2018-08-11 Hudd… Chel…     0     3 A             0        2##  5 E0          2018-08-11 Newc… Tott…     1     2 A             1        2##  6 E0          2018-08-11 Watf… Brig…     2     0 H             1        0##  7 E0          2018-08-11 Wolv… Ever…     2     2 D             1        1##  8 E0          2018-08-12 Arse… Man …     0     2 A             0        1##  9 E0          2018-08-12 Live… West…     4     0 H             2        0## 10 E0          2018-08-12 Sout… Burn…     0     0 D             0        0## # ... with 190 more rows, and 14 more variables: result_ht &lt;chr&gt;,## #   hshot &lt;int&gt;, ashot &lt;int&gt;, hshot_on_target &lt;int&gt;,## #   ashot_on_target &lt;int&gt;, hfoul &lt;int&gt;, afoul &lt;int&gt;, hcorner &lt;int&gt;,## #   acorner &lt;int&gt;, hyellow &lt;int&gt;, ayellow &lt;int&gt;, hred &lt;int&gt;, ared &lt;int&gt;,## #   closing_odds &lt;list&gt;</code></pre><h2 id="Predict-the-unplayed-games">Predict the unplayed games</h2><h3 id="Work-out-the-unplayed-games">Work out the unplayed games</h3><p>Fortunately, the Premier League’s schedule is relativelystraightforward. Each team plays every other team at home and away.There are no playoffs, league splits or whatever they do <a href="https://en.wikipedia.org/wiki/Belgian_First_Division_A#Competition_format_and_naming">inBelgium</a>.</p><p>This means that we can find the unplayed games pretty easily. We canjust take all combinations of teams, and filter out ones that have beenplayed, or that feature a team playing itself.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">teams &lt;- factor(levels(data$home), levels = levels(data$home))</span><br><span class="line"></span><br><span class="line">unplayed_games &lt;-</span><br><span class="line">  crossing(home = teams,</span><br><span class="line">           away = teams) %&gt;%</span><br><span class="line">  filter(home != away) %&gt;%</span><br><span class="line">  anti_join(data, by = <span class="built_in">c</span>(<span class="string">&quot;home&quot;</span>, <span class="string">&quot;away&quot;</span>))</span><br><span class="line"></span><br><span class="line">unplayed_games</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 180 x 2##    home        away##    &lt;fct&gt;       &lt;fct&gt;##  1 Man United  Watford##  2 Man United  Liverpool##  3 Man United  Southampton##  4 Man United  Cardiff##  5 Man United  Chelsea##  6 Man United  West Ham##  7 Man United  Brighton##  8 Man United  Burnley##  9 Man United  Man City## 10 Bournemouth Fulham## # ... with 170 more rows</code></pre><h3 id="Make-predictions-for-those-unplayed-games">Make predictions for those unplayed games</h3><p>First, we have to fit a model that we can use to estimate how good eachteam is, and the effect of home advantage.</p><p>We can do this with regista’s implementation of the Dixon-Coles model.For the purposes of this post, we will just fit the model on the gamesplayed so far this season. However, a more comprehensive model mayinclude matches from previous seasons, time-discounting, or even<a href="http://www.statsandsnakeoil.com/2018/06/22/dixon-coles-and-xg-together-at-last/">expectedgoals</a>.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">model &lt;- dixoncoles(hgoal, agoal, home, away, data = data)</span><br><span class="line"></span><br><span class="line">model</span><br></pre></td></tr></table></figure><pre><code>#### Dixon-Coles model with specification:#### Home goals: hgoal ~ off(home) + def(away) + hfa + 0## Away goals: agoal ~ off(away) + def(home) + 0## Weights   : 1</code></pre><p>We can sense-check the model by looking at the team attack and defenceparameters:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">team_parameters &lt;-</span><br><span class="line">  broom::tidy(model) %&gt;%</span><br><span class="line">  filter(parameter %in% <span class="built_in">c</span>(<span class="string">&quot;off&quot;</span>, <span class="string">&quot;def&quot;</span>)) %&gt;%</span><br><span class="line">  mutate(value = <span class="built_in">exp</span>(value)) %&gt;%</span><br><span class="line">  spread(parameter, value)</span><br><span class="line"></span><br><span class="line">team_parameters %&gt;%</span><br><span class="line">  ggplot(aes(x = def, y = off)) +</span><br><span class="line">  geom_point(alpha = <span class="number">0.5</span>) +</span><br><span class="line">  ggrepel::geom_text_repel(aes(label = team)) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  labs(title = <span class="string">&quot;Team strength estimates&quot;</span>,</span><br><span class="line">       y = <span class="string">&quot;Attack&quot;</span>,</span><br><span class="line">       x = <span class="string">&quot;Defence&quot;</span>)</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-6-1.jpg" alt=""></p><p>It’s tempting to just calculate the match outcome (Home win/Draw/Awaywin) probabilities and go from there. It’s pretty easy to do withregista as well:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">match_probabilities &lt;-</span><br><span class="line">  broom::augment(model, unplayed_games, type.predict = <span class="string">&quot;outcomes&quot;</span>) %&gt;%</span><br><span class="line">  unnest() %&gt;%</span><br><span class="line">  spread(outcome, prob)</span><br><span class="line"></span><br><span class="line">match_probabilities</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 180 x 5##    home        away        away_win  draw home_win##    &lt;fct&gt;       &lt;fct&gt;          &lt;dbl&gt; &lt;dbl&gt;    &lt;dbl&gt;##  1 Man United  Watford       0.232  0.220   0.549##  2 Man United  Liverpool     0.770  0.165   0.0659##  3 Man United  Southampton   0.124  0.166   0.710##  4 Man United  Cardiff       0.0976 0.153   0.749##  5 Man United  Chelsea       0.522  0.239   0.240##  6 Man United  West Ham      0.208  0.209   0.582##  7 Man United  Brighton      0.201  0.236   0.562##  8 Man United  Burnley       0.0843 0.136   0.780##  9 Man United  Man City      0.676  0.177   0.147## 10 Bournemouth Fulham        0.152  0.206   0.642## # ... with 170 more rows</code></pre><p>This is enough to simulate match outcomes, and points scored. However,in cases where point totals at the end of the season are equal, we needgoal-difference too.</p><p>Fortunately, this is something the Dixon-Coles model can handle. We canpredict the probability of each scoreline for the unplayed games.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">unplayed_scorelines &lt;-</span><br><span class="line">  broom::augment(model, unplayed_games, type.predict = <span class="string">&quot;scorelines&quot;</span>) %&gt;%</span><br><span class="line">  unnest()</span><br><span class="line"></span><br><span class="line">unplayed_scorelines</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 19,563 x 5##    home       away    hgoal agoal     prob##    &lt;fct&gt;      &lt;fct&gt;   &lt;int&gt; &lt;int&gt;    &lt;dbl&gt;##  1 Man United Watford     0     0 0.0348##  2 Man United Watford     1     0 0.0468##  3 Man United Watford     2     0 0.0634##  4 Man United Watford     3     0 0.0475##  5 Man United Watford     4     0 0.0267##  6 Man United Watford     5     0 0.0120##  7 Man United Watford     6     0 0.00448##  8 Man United Watford     7     0 0.00144##  9 Man United Watford     8     0 0.000404## 10 Man United Watford     9     0 0.000101## # ... with 19,553 more rows</code></pre><h2 id="Simulate-the-rest-of-the-season">Simulate the rest of the season</h2><h3 id="Join-played-game-and-unplayed-games">Join played game and unplayed games</h3><p>There’s an added benefit to looking at the unplayed games like asscoreline-probabilities. We can look at the <em>played</em> games in exactlythe same way. Any game that has already been played is simply a gamewhere there is a single scoreline with 100% probability. This simplifiesour simulation step, since we can treat both played and unplayed gamesthe same.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">played_scorelines &lt;-</span><br><span class="line">  data %&gt;%</span><br><span class="line">  select(home, away, hgoal, agoal) %&gt;%</span><br><span class="line">  mutate(prob = <span class="number">1.0</span>)</span><br><span class="line"></span><br><span class="line">scorelines &lt;- bind_rows(</span><br><span class="line">  played_scorelines,</span><br><span class="line">  unplayed_scorelines</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">scorelines</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 19,763 x 5##    home         away           hgoal agoal  prob##    &lt;fct&gt;        &lt;fct&gt;          &lt;int&gt; &lt;int&gt; &lt;dbl&gt;##  1 Man United   Leicester          2     1     1##  2 Bournemouth  Cardiff            2     0     1##  3 Fulham       Crystal Palace     0     2     1##  4 Huddersfield Chelsea            0     3     1##  5 Newcastle    Tottenham          1     2     1##  6 Watford      Brighton           2     0     1##  7 Wolves       Everton            2     2     1##  8 Arsenal      Man City           0     2     1##  9 Liverpool    West Ham           4     0     1## 10 Southampton  Burnley            0     0     1## # ... with 19,753 more rows</code></pre><h3 id="Simulate-games-by-Monte-Carlo">Simulate games by Monte Carlo</h3><p>We can simulate the result of each game by picking a potential scorelineat random, where each pick is weighted by the estimated probability.</p><p>This gives us a single instance of a simulated season. We can repeatthis over and over again to estimate the overall season probabilities.This is the <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">Monte Carlomethod</a> of simulation.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulate_season &lt;- <span class="keyword">function</span>(scoreline_probabilities) &#123;</span><br><span class="line">  scoreline_probabilities %&gt;%</span><br><span class="line">    nest(hgoal, agoal, prob, .key = <span class="string">&quot;scorelines&quot;</span>) %&gt;%</span><br><span class="line">    mutate(sampled = map(scorelines, ~ sample_n(., <span class="number">1</span>, weight = prob))) %&gt;%</span><br><span class="line">    select(-scorelines) %&gt;%</span><br><span class="line">    unnest()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Show 1 simulation as an example</span></span><br><span class="line">single_simulation &lt;- simulate_season(scorelines)</span><br><span class="line">sample_n(single_simulation, <span class="number">5</span>)</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 5 x 5##   home        away      hgoal agoal   prob##   &lt;fct&gt;       &lt;fct&gt;     &lt;int&gt; &lt;int&gt;  &lt;dbl&gt;## 1 Bournemouth Liverpool     0     4 1## 2 West Ham    Arsenal       2     1 0.0610## 3 Newcastle   Everton       0     2 0.0987## 4 Liverpool   Watford       4     0 0.0959## 5 Wolves      Liverpool     0     2 1</code></pre><p>There are a few flawed assumptions being made in this simulation. Ithink it’s worth acknowledging a couple of them quickly:</p><ul><li>There’s no accounting for the possibility team-strengths changing<ul><li>From transfers</li><li>From injuries</li><li>Coaching changes</li><li>etc</li></ul></li><li>There’s no accounting for motivation effects towards the end of theseason<ul><li>Teams with nothing to play for are less likely to perform aswell as others</li></ul></li></ul><p>These kinds of sources of model error should be considered whenevaluating the model’s predictions, and when thinking about how youmight improve the simulation method.</p><h3 id="Calculating-the-league-table">Calculating the league table</h3><p>We care much more about overall table outcomes (Champion, Relegation,Top 4) than we do about individual results. So we need a way tocalculate the table from a set of games:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">calculate_table &lt;- <span class="keyword">function</span>(games) &#123;</span><br><span class="line">  games_augmented &lt;-</span><br><span class="line">    games %&gt;%</span><br><span class="line">    mutate(</span><br><span class="line">      hpoints = case_when(</span><br><span class="line">        hgoal &gt; agoal  ~ <span class="number">3</span>,</span><br><span class="line">        hgoal == agoal ~ <span class="number">1</span>,</span><br><span class="line">        agoal &gt; hgoal  ~ <span class="number">0</span></span><br><span class="line">      ),</span><br><span class="line">      apoints = case_when(</span><br><span class="line">        hgoal &gt; agoal  ~ <span class="number">0</span>,</span><br><span class="line">        hgoal == agoal ~ <span class="number">1</span>,</span><br><span class="line">        agoal &gt; hgoal  ~ <span class="number">3</span></span><br><span class="line">      )</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  games_home &lt;-</span><br><span class="line">    games_augmented %&gt;%</span><br><span class="line">    select(</span><br><span class="line">      team   = home,</span><br><span class="line">      gf     = hgoal,</span><br><span class="line">      ga     = agoal,</span><br><span class="line">      points = hpoints</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  games_away &lt;-</span><br><span class="line">    games_augmented %&gt;%</span><br><span class="line">    select(</span><br><span class="line">      team   = away,</span><br><span class="line">      gf     = agoal,</span><br><span class="line">      ga     = hgoal,</span><br><span class="line">      points = apoints</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">  bind_rows(games_home, games_away) %&gt;%</span><br><span class="line">    group_by(team) %&gt;%</span><br><span class="line">    summarise(w  = <span class="built_in">sum</span>(gf &gt; ga),</span><br><span class="line">              d  = <span class="built_in">sum</span>(gf == ga),</span><br><span class="line">              l  = <span class="built_in">sum</span>(gf &lt; ga),</span><br><span class="line">              gf = <span class="built_in">sum</span>(gf),</span><br><span class="line">              ga = <span class="built_in">sum</span>(ga),</span><br><span class="line">              gd = gf - ga,</span><br><span class="line">              points = <span class="built_in">sum</span>(points)) %&gt;%</span><br><span class="line">    arrange(desc(points), desc(gd), desc(gf)) %&gt;%</span><br><span class="line">    mutate(position = row_number())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We can check that this works as expected by checking the current table:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">calculate_table(data)</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 20 x 9##    team               w     d     l    gf    ga    gd points position##    &lt;fct&gt;          &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt;  &lt;dbl&gt;    &lt;int&gt;##  1 Liverpool         17     3     0    48     8    40     54        1##  2 Man City          15     2     3    54    16    38     47        2##  3 Tottenham         15     0     5    43    21    22     45        3##  4 Chelsea           13     4     3    38    16    22     43        4##  5 Arsenal           11     5     4    42    30    12     38        5##  6 Man United        10     5     5    41    32     9     35        6##  7 Wolves             8     5     7    23    23     0     29        7##  8 Leicester          8     4     8    24    23     1     28        8##  9 Watford            8     4     8    27    28    -1     28        9## 10 Everton            7     6     7    31    30     1     27       10## 11 West Ham           8     3     9    27    30    -3     27       11## 12 Bournemouth        8     2    10    28    37    -9     26       12## 13 Brighton           7     4     9    22    27    -5     25       13## 14 Crystal Palace     5     4    11    17    26    -9     19       14## 15 Newcastle          4     6    10    15    27   -12     18       15## 16 Cardiff            5     3    12    19    38   -19     18       16## 17 Southampton        3     6    11    21    38   -17     15       17## 18 Burnley            4     3    13    19    41   -22     15       18## 19 Fulham             3     5    12    18    43   -25     14       19## 20 Huddersfield       2     4    14    12    35   -23     10       20</code></pre><p>Which also works on the simulated table:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">calculate_table(single_simulation)</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 20 x 9##    team               w     d     l    gf    ga    gd points position##    &lt;fct&gt;          &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt;  &lt;dbl&gt;    &lt;int&gt;##  1 Liverpool         32     6     0   102    10    92    102        1##  2 Man City          28     5     5    97    31    66     89        2##  3 Tottenham         27     5     6    83    36    47     86        3##  4 Chelsea           25     4     9    69    33    36     79        4##  5 Arsenal           22     7     9    84    54    30     73        5##  6 Man United        18     6    14    75    65    10     60        6##  7 Everton           13    13    12    52    50     2     52        7##  8 Leicester         14     9    15    41    47    -6     51        8##  9 Wolves            14     7    17    41    43    -2     49        9## 10 Bournemouth       12    10    16    55    65   -10     46       10## 11 West Ham          13     7    18    52    62   -10     46       11## 12 Brighton          12    10    16    40    51   -11     46       12## 13 Cardiff           14     4    20    48    68   -20     46       13## 14 Watford           12     9    17    48    56    -8     45       14## 15 Newcastle         12     8    18    32    53   -21     44       15## 16 Crystal Palace    10     9    19    31    51   -20     39       16## 17 Southampton        9     9    20    42    71   -29     36       17## 18 Fulham             7     7    24    32    86   -54     28       18## 19 Burnley            6     8    24    30    78   -48     26       19## 20 Huddersfield       4     9    25    24    68   -44     21       20</code></pre><h3 id="Running-the-simulations">Running the simulations</h3><p>Now we have everything we need to generate as many simulated leaguetables as we want.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">n_simulations &lt;- 1000</span><br><span class="line"></span><br><span class="line">simulated_tables &lt;-</span><br><span class="line">  rerun(n_simulations, simulate_season(scorelines)) %&gt;%</span><br><span class="line">  map(calculate_table) %&gt;%</span><br><span class="line">  bind_rows(.id = <span class="string">&quot;simulation_id&quot;</span>)</span><br><span class="line"></span><br><span class="line">simulated_tables</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 20,000 x 10##    simulation_id team       w     d     l    gf    ga    gd points position##    &lt;chr&gt;         &lt;fct&gt;  &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;int&gt;  &lt;dbl&gt;    &lt;int&gt;##  1 1             Liver…    35     3     0    96    12    84    108        1##  2 1             Man C…    27     6     5   101    32    69     87        2##  3 1             Totte…    26     4     8    94    48    46     82        3##  4 1             Chels…    21     9     8    67    31    36     72        4##  5 1             Arsen…    22     5    11    82    61    21     71        5##  6 1             Man U…    20     9     9    82    58    24     69        6##  7 1             Leice…    18     8    12    52    43     9     62        7##  8 1             Evert…    13    13    12    62    63    -1     52        8##  9 1             Wolves    13    12    13    45    46    -1     51        9## 10 1             West …    15     6    17    55    66   -11     51       10## # ... with 19,990 more rows</code></pre><h2 id="Results">Results</h2><p>With the results in hand, we can move onto the fun parts. That is,sifting through our model’s predictions of the future.</p><h3 id="Position-probabilities">Position probabilities</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">n_teams &lt;- <span class="built_in">length</span>(teams)</span><br><span class="line">plot_colour &lt;- <span class="string">&quot;#1a4990&quot;</span></span><br><span class="line"></span><br><span class="line">simulated_tables %&gt;%</span><br><span class="line">  count(team, position) %&gt;%</span><br><span class="line">  ggplot(aes(x = position, y = n / n_simulations)) +</span><br><span class="line">  geom_col(fill = plot_colour) +</span><br><span class="line">  facet_wrap(~ reorder(team, position)) +</span><br><span class="line">  scale_x_continuous(breaks = <span class="number">1</span>:n_teams) +</span><br><span class="line">  scale_y_continuous(labels = scales::percent_format(),</span><br><span class="line">                     limits = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">1</span>)) +</span><br><span class="line">  labs(title = <span class="string">&quot;Position probabilities&quot;</span>,</span><br><span class="line">       x = <span class="literal">NULL</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(title       = element_text(size = <span class="number">30</span>),</span><br><span class="line">        strip.text  = element_text(size = <span class="number">25</span>),</span><br><span class="line">        axis.text.y = element_text(size = <span class="number">15</span>),</span><br><span class="line">        axis.text.x = element_text(size = <span class="number">10</span>),</span><br><span class="line">        panel.grid.major.y = element_line(linetype = <span class="string">&quot;dotted&quot;</span>),</span><br><span class="line">        panel.grid.major.x = element_blank(),</span><br><span class="line">        panel.grid.minor   = element_blank())</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-15-1.png" alt=""></p><p>It turns out the model is extremely bullish on Liverpool’s chances ofwinning the league:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(team) %&gt;%</span><br><span class="line">  summarise(p_win = mean(position == <span class="number">1</span>)) %&gt;%</span><br><span class="line">  filter(p_win &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  arrange(desc(p_win))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 2 x 2##   team      p_win##   &lt;fct&gt;     &lt;dbl&gt;## 1 Liverpool 0.948## 2 Man City  0.052</code></pre><p>I think this is partly a reflection of the flaws in the simulationmethod. However, I think a bigger factor is using goals as our onlyinput to team strength. As we know, the table <a href="https://engineering.footballradar.com/does-the-league-table-lie/">does sometimeslie</a>,and sometimes goals can deceive, too.</p><p>Both of these factors result in Liverpool’s chances being overestimatedby the model. For comparison, the <a href="https://www.oddschecker.com/football/english/premier-league/winner">bettingmarket</a>has Liverpool at around 60% to win the league at the time of writing.</p><p>At the other end of the table, things are a bit more interesting;although Huddersfield and Fulham don’t look great.</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(team) %&gt;%</span><br><span class="line">  summarise(p_rel = mean(position &gt;= <span class="number">18</span>)) %&gt;%</span><br><span class="line">  filter(p_rel &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  arrange(desc(p_rel))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 7 x 2##   team           p_rel##   &lt;fct&gt;          &lt;dbl&gt;## 1 Huddersfield   0.981## 2 Fulham         0.866## 3 Burnley        0.569## 4 Cardiff        0.24## 5 Southampton    0.21## 6 Newcastle      0.12## 7 Crystal Palace 0.014</code></pre><p>We can look at the race for the Champion’s League, too:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(team) %&gt;%</span><br><span class="line">  summarise(p_top4 = mean(position &lt;= <span class="number">4</span>)) %&gt;%</span><br><span class="line">  filter(p_top4 &gt; <span class="number">0.01</span>) %&gt;%</span><br><span class="line">  arrange(desc(p_top4))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 5 x 2##   team      p_top4##   &lt;fct&gt;      &lt;dbl&gt;## 1 Liverpool  1## 2 Man City   0.998## 3 Tottenham  0.944## 4 Chelsea    0.872## 5 Arsenal    0.178</code></pre><h3 id="Points-totals">Points totals</h3><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  count(team, points) %&gt;%</span><br><span class="line">  ggplot(aes(x = points, y = reorder(team, points))) +</span><br><span class="line">  geom_tile(aes(alpha = n / n_simulations),</span><br><span class="line">            fill = plot_colour) +</span><br><span class="line">  scale_alpha_continuous(<span class="built_in">range</span> = <span class="built_in">c</span>(<span class="number">0</span>, <span class="number">1</span>)) +</span><br><span class="line">  labs(title = <span class="string">&quot;Points total probabilities&quot;</span>,</span><br><span class="line">       x = <span class="literal">NULL</span>,</span><br><span class="line">       y = <span class="literal">NULL</span>) +</span><br><span class="line">  theme_minimal() +</span><br><span class="line">  theme(legend.position = <span class="string">&quot;none&quot;</span>)</span><br></pre></td></tr></table></figure><p><img src="unnamed-chunk-19-1.png" alt=""></p><p>The model even seems to think that Liverpool’s chances of breaking 100points are pretty good:</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(team) %&gt;%</span><br><span class="line">  summarise(p = mean(points &gt;= <span class="number">100</span>)) %&gt;%</span><br><span class="line">  filter(p &gt;= <span class="number">0.001</span>) %&gt;%</span><br><span class="line">  arrange(desc(p))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 1 x 2##   team          p##   &lt;fct&gt;     &lt;dbl&gt;## 1 Liverpool 0.545</code></pre><p>And even that they’ve got a solid chance of going unbeaten!</p><figure class="highlight r"><table><tr><td class="code"><pre><span class="line">simulated_tables %&gt;%</span><br><span class="line">  group_by(team) %&gt;%</span><br><span class="line">  summarise(p = mean(l == <span class="number">0</span>)) %&gt;%</span><br><span class="line">  filter(p &gt;= <span class="number">0.001</span>) %&gt;%</span><br><span class="line">  arrange(desc(p))</span><br></pre></td></tr></table></figure><pre><code>## # A tibble: 1 x 2##   team          p##   &lt;fct&gt;     &lt;dbl&gt;## 1 Liverpool 0.386</code></pre><h2 id="Wrap-up">Wrap-up</h2><p>This is one simple way of getting season-level predictions from a simplematch-level model, using a relatively small amount of new code. Beingable to quickly generate and investigate predictions is an importantpart of the <a href="https://r4ds.had.co.nz/explore-intro.html">dataexploration</a> process. Havingboth game and season-level views onto your model’s predictions, allowsfor increased insight into a model’s shortcomings and how it might beimproved.</p><p>There are a few simple extensions to this analysis that could be nicejumping off points for improving the model.</p><ul><li>Time-discount factor<ul><li>What do the predictions look like if you incorporate more datafrom the past, with a time-discounting factor? (See the<code>weights</code> argument to <code>regista::dixoncoles</code>.)</li></ul></li><li>Other leagues<ul><li>How does this analysis look when you try other leagues avaliablefrom <a href="http://football-data.co.uk">football-data.co.uk</a>? (Hint: change the first code chunk andtry re-running the notebook)</li></ul></li><li>xG/Dixon-Coles<ul><li>How do the predictions look when you estimate team strength fromxG rather than just goals? (See <a href="http://www.statsandsnakeoil.com/2018/06/22/dixon-coles-and-xg-together-at-last/">Dixon Coles and xG: together atlast</a>.)</li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;In this post, I’m going to walk through one way of predicting the rest
of the Premier League season with regista’s implementation of the
</summary>
      
    
    
    
    
    <category term="Dixon-Coles" scheme="https://www.statsandsnakeoil.com/tags/dixon-coles/"/>
    
    <category term="regista" scheme="https://www.statsandsnakeoil.com/tags/regista/"/>
    
    <category term="Premier League" scheme="https://www.statsandsnakeoil.com/tags/premier-league/"/>
    
  </entry>
  
</feed>
