Author: zorba138

Alright, after my last post discussing team performance for the 2017 season it’s now time to dig deeper into Stratagem‘s great dataset and have a look at individual players.

As usual, it’s important to remember that Stratagem collects chances, not shots and also this: only one chance per attacking play is recorded. So for example if a team forces a goalkeeper to a series of saves in a single attack, only the highest rated chance (or a goal if it was scored) is recorded – this of course makes much sense as you can only score a maximum of one goal per attack.

Another difference from most data collectors is that whenever a blocked or saved shot rebounds and leads to a new chance, Stratagem credits the original shooter with an assist for his part in ‘creating’ this new chance. It’s important to note though that this only happens if the rebound chance happens to be of a higher quality than the original chance or end up as a goal, due to the above rule of only one chance per attack.

Lastly, when it comes to minutes played I’ve taken some time to try to calculate it as correctly as possible to get a better look at players ‘true’ performance. Sites like Soccerway seems to set their maximum playing time per match to 90 minutes which is of course wrong as there’s usually a lot of injury time to consider, sometimes even in the first half. So for this post with injury time in both halves taken into consideration you’ll see players which have played more than 30 units of 90 minutes and this also means that most players will see their per90 stats slightly diminished.

All data is open play chances, i.e. penalties are excluded for this post.

But enough of that, let’s get to it and have a look at some numbers. As usual I’ll just throw some plots at you together with my spontane thoughts:

Goalscoring

Though sharing honors as the league top scorer at 14 with Magnus Eriksson, the moral winner is Norrköping’s Kalle Holmberg with 13 open play goals while 5 of Eriksson’s goals came from penalties. Eriksson’s 9 open play goals is still very impressive though, seeing him finish joint second together with a group of strong goalscorers, all forwards – while Eriksson has mainly been used in midfield in Özcan Melkemichel’s Djurgården.

Another impressive performance comes from AIK’s Nicolas Stefanelli who managed to reach 9 open play goals despite only arriving during the summer, resulting in him topping the league when it comes to goals scored per 90 minutes. Versatile Bjørn Paulsen‘s 8 goals are equally impressive as he’s been used in both central midfield and defence alongside his starts up front for Hammarby.

Tobias Hysén shows that he’s still to be reckoned with, producing the highest total xG in the league at age 35. I’ve been waiting for his performance to drop for some years now, will he surprise me again next season?

The lack of any real xG per 90 Wizard this season (besides Stefanelli, maybe) sees some surprising names break into the immediate top. Johan Bertilsson, Skhodran Maholli (though he enjoyed an initial strong start to his arrival at Sirius) and Linus Hallenius comes to mind. Impressive of course, but it should be noted that this Allsvenskan season has been lacking the strong goalscoring box-player poacher type like pasts seasons’ Kjartansson, Owoeri and Kujovic. Kalle Holmberg could’ve been that player but IFK Norrköping’s weak end to the season has certainly limited his output to more normal levels.

Eflsborg’s Issam Jebali was the end point of most chances for the season, but when playing time is taken into consideration, AIK’s Nicolas Stefanelli once again reigns supreme.

Comparing goals and xG we see that Stefanelli’s output isn’t that much better than expected, he could very well be the real deal. Another interesting point is that Malmö’s captain Markus Rosenberg continues to underperform against xG.

Looking at how many chances players create and the average quality of those chances should give us at least some sense of their preferred attacking styles. We see here how most strong attacking players tend to cluster around an area of compromise between quality and quantity. In this group, Viktor Prodell, Johan Bertilsson, Henok Goitom and Mohamed Buya Turay tend to rely more on high quality chances (all above 0.20 xG per chance), while David Moberg-Karlsson and Stefanelli prefer to just rack up chance after chance, the latter with some respectable xG per chance as well.

Moses Ogbu is an extreme outlier with over 0.30 xG per chance, explained in part by the fact that he only took part in Sirius’ very strong first half of the season before getting injured. Still a very interesting player, his numbers would likely have dropped a bit had he been fit to play when Sirius struggled (including 7 straight losses) in the second half of the season.

Chance Creation

Elfsborg’s Simon Lundevall provided most assist overall but taking playing time into account, IFK Norrköping’s Niclas Eliasson was Allsvenskan’s main creator this season. Racking up 11 assists in the first half of the season before leaving for Bristol City in the Championship, his departure effectively ended Norrköping’s top 3 ambitions.

Magnus Eriksson, Tobias Hysén and Nahir Besara‘s appearance in the Assists Top 10 really shows their versatility and huge importance to their teams’ overall attack.

Just like seen with goals above, some interesting and perhaps surprising names appear when we account for playing time. I certainly didn’t expected to see Sirius’ Elias Andersson or AFC Eskilstuna’s Andrew Fox here, but there you go.

Ken Sema‘s strong finish to the season saw him (besides earning a call-up to ‘Party-‘ Janne Andersson’s national team which beat Italy to advance to the World Cup) top the Expected Assists table at roughly 11, though 3 less than his actual output. Sema has also been performing well in Östersund’s Europa League campagin and is one of many players they’ll have to work hard to keep over the winter transfer window.

Nostalgic as I am, it’s certainly nice to see my boyhood hero Kim Källström racking up some strong numbers placing him in the Top 10 Assists and xA tables, as well as creating most chances in the league overall and 4th most when taking playing time into consideration.

Comparing assists and xA we see how Niclas Eliasson has been outperforming his expected output (likely thanks to some effective scoring from Kalle Homberg) while Ken Sema has been underperforming. Lundevall is closer to his expected output.

Just like with the Attacking Styles, Player Chance Creation Styles are mostly clustered with a lot of creative players combining reasonable quality with quantity. Ken Sema, Elias Andersson and Yoshimar Yotún (who left Malmö for the MLS in the summer) are the extremes when it comes to creation volume, while Andreas Vindheim has created some very good chances for Malmö.

Attacking Production

By combining goals and assists into Attacking Production we see that Besara was the most productive player when it comes to raw numbers, but when factoring in playing time, Stefanelli once again tops the table in both expected and actual output. Prodell has done well considering his playing time, as well as Malmö’s Alexander Jeremejeff who’s second behind Stefanelli in xG+A per 90 minutes.

Djurgården’s both wingers break into the Total Chance Production table, with Othman El Kabir joining Eriksson just below the top trio. Paulinho was the most productive attacking player though, creating over 5 chances per 90 minutes for Häcken.

Looking at actual and expected output, we see how most strong attacking players like Besara, Jebali, Homberg, Eriksson, Hysén and Stefanelli tend to perform close to what we can expected. Eliasson is again overperforming while Rosenberg is doing the opposite. Eric Larsson is worth a special mention here as he has produced some fine numbers for a fullback, with his underperformance coming largely from his teammates in Sundsvall underperforming on the chances he created for them.

Seperating Expected Goals and Expected Assists let us see how the attacking players specialise. Once again we see how this season has really lacked many strong specialist, with only Stefanelli and Sema really standing out on their ends. Most players tend to cluster somewhat here as well, combining creativity with being at the end of chances as well.

Player Profiles

As I now work with StrataData, I’d thought I’d do a total revamp of the popular player maps. The style is more or less shamelessly stolen from a range of other analysts, no names mentioned, and now also include Chance Creation Maps:

As mentioned earlier, Kalle Holmberg was this season’s strongest goalscorer, and from his Chance Map it’s easy to see why: he usually gets into some very good positions just in front of goal, with an average xG of 0.19 per chance. 13 open play goals is strong, but as I’ve also mentioned I think he could’ve done even better had IFK Norrköping’s performance not dropped (and Niclas Eliasson not left).

Operating from Djurgården’s right wing, Magnus Eriksson was another strong goalscorer this season, though a bit more versatile as he also provided a lot of assists for his team. Mostly crosses from the right flank but also two shot rebounds. His Chance Map is a bit different from Holmberg’s with more chances outside the box, which is only natural as he’s after all a midfielder. Though attacking is certainly his main quality, Djurgården will also miss his work ethic, grit and competitiveness now that he’s left for the MLS.

Veteran Tobias Hysén continues to be extremely important to IFK Göteborg’s attack. His Chance Map combines a lot of good chances inside the box with some poorer outside, some of them direct free kick. When it comes to Chance Creation he’s provided some crucial passing inside and into the box, as well as some corners and free kicks.

Örebro’s Nahir Besara was also extremely important to his team’s attack, combining some chances inside the box with a lot of shooting from outside, including one goal from a direct free kick. His creation numbers are boosted by three rebounds who turned into goals, otherwise it’s mostly corners and crosses into the box.

Nicolás Stefanelli arrived at AIK at a crucial time this summer, with the team’s attack struggling during the first half of the season. The Argentinian took some time to adopt but slowly turned into to a real strong presence up front, scoring 9 goals from 14 starts. It will be very interesting indeed to see if he can continue his fine performance come the new season. As a Djurgården supporter, I sure hope not.

Linus Hallenius is an interesting case that’s flown under at least my radar this season. With 7 goals and nearly 10 xG he’s done well for a struggling Sundsvall side that just barely managed to stay up. Most of his chances have been created by Eric Larsson, so it’ll be very interesting to see if Hallenius can continue his fine performance next season with the right back having left for champions Malmö.

Elfsborg’s Simon Lundevall was the assist king this season at 12, with 4 of them coming from corners, curiously with some rather high xA values – 3 of them are above 0.30 xA. Maybe Elfsborg have some corner strong routine going on? Lundevall has also provided some long range passes on the left half of the pitch, which I guess is related to counter-attacking.

Niclas Eliasson’s strong first half of a season earned him a move abroad, and as mentioned earlier IFK Norrköping never really looked the same after that. Overperfoming, sure, but he did create some really good chances for his team with his precise crossing from both flanks.

Ken Sema was another creation monster, racking up some really good chances with an average xA per chance of 0.18. It’s clear to see why, as most of his passes was either directly inside the box, or ending up in it – a direct consequence of Östersund’s heavily passing-oriented style of attack.

Though it stopped at just one season before he chose to end his career, Kim Källström’s long-awaited return to Djurgården was (despite some very inconsistent perfomances) instrumental in returning the team he once won the league with in two consecutive seasons at the start of the millenium, back to the top 3. When he was at his best this season, sitting back in his deep-lying playmaker role he dictated much of Djurgården’s attack with his quarter-back ‘Hail Mary’ style of long passing. Interestingly though, all his assists came from set pieces where he got more time to use his precise left foot.

I mentioned Eric Larsson before and looking at his Chance Creation Map we see clearly how strong a player he is. From his right back position at struggling Sundsvall he produced 52 chances and well over 6 xA – more than most midfielders. Though his teammates only managed to score twice on these chances, with his move to Malmö I expect him to get a lot more assists next season.

That’s it, thank you for reading the whole piece. If you want to see any more Player Chance/Creation Maps, just let me know on Twitter.

Share this:

Like this:

With the end of the Swedish football season I’m now on vacation, and thus have time to do some writing. I haven’t written anything for a while and have especially avoided football since I’m biased with my work involving taking bets on the Swedish leagues. If I say Team X is underrated and should have a good chance of picking up some points in the future, why would you believe me, when I profit from your losses? Once the new season starts I’ll likely crawl back under my rock again, but for now, let’s get to it: Allsvenskan 2017 Summary!

Before we start though, I’d like to point out two things: a) most of the graphics shown below are very much inspired by (or more or less copied from) the great Ben Mayhew at Experimental 361, and b) the data used is from my good friends at Stratagem, for which I used to cover Norwegian Eliteserien and collect stats watching matches. You’ll find more information about Stratagem and their products at the bottom of this piece.

So, Allsvenskan 2017 it is then. First let’s have a look at the final table, once again topped by Malmö who managed to defend their title from last season, making it their 5th since 2010.

To my own personal joy, Djurgården finally returned to the top 3 for the first time since 2007, grabbing the last European qualifier spot in the process. Much hyped Östersund also managed to climb from last season’s 8th place, at the same time adding a very impressive run in the Europa League. Häcken have also improved (with new manager Stahre now leaving for the MLS), while Norrköping, IFK Göteborg and Elfsborg have all dropped somewhat. Of the three newcomers, only Sirius managed to stay up, reaching an impressive 7th place after a strong spring and a weaker autumn. Besides Halmstad and AFC Eskilstuna, Jönköpings Södra were also relegated via play-off against the 3rd placed team in Superettan, Trelleborg.

Let’s dig deeper by looking at some scatterplots (note: as I now use data from Stratagem, shots have turned into chances as this is what they collect. For more info, read this blog by Dave Willoughby).

Right away we can see part of why Malmö have dominated the season, as they create far more chances than the rest while at the same time keeping a tidy defense and facing fewest chances in the league. There’s quite some distance to the other top teams and interestingly IFK Göteborg seems to have done better chance-wise than the table suggests.

At the other end of things, AFC Eskilstuna stands out as a really poor team with the lowest number of chances created coupled with the highest number of chances faced per match. Jönköpings Södra also stand out a bit, with quite low numbers on both scales indicating some very boring matches (which I can confirm).

Looking at attacking effectiveness we see how the top teams were efficient with their many chances created, while bottom teams like Sundsvall and relegated Halmstad both struggled to create and to capitalise on their chances. A curious case is Elfsborg who were the most efficient scorers, needing less than 7 chances per goal, while at the same time failing to create enough chance volume to compete with the top teams.

When it comes to defensive effectiveness, AIK and Häcken really stands out with around 13 chances faced per goal conceded, compared to the league average just under 9. Followers of Allsvenskan won’t be surprised to see AIK in the top here as a tight defense has been a cornerstone of the club for a long time. Häcken though, have really been transformed from a care-free attacking-minded side under Peter Gerhardsson, to a more cynic and well-structured defensive side under departing (and former AIK manager) Mikael Stahre. It will be very interesting to see who replaces him and what direction the club will take in the future.

Another interesting point to make is that as affective as they are on the attack, Elfsborg are equally ineffective when defending. With the third most goals scored and most conceded, the Borås side have certainly been entertaining to watch this season.

When it comes to Expected Goals, champions Malmö are closely followed by AIK, with Östersund and Djurgården some distance away. AFC Eskilstuna and Elfsborg were the two poorest defenders with around 2 xG conceded per match. I wonder how Elfsborg would have done without their effective scoring?

Rating the teams by Expected Goal Difference sees really how close AIK were to Malmö, whose ability to win close matches seems to be a big factor in their title win this season. At the bottom AFC Eskilstuna clearly deserved to be relegated with the worst xG difference, as did Halmstad while Jönköpings Södra maybe deserved a better fate than to be relegated via play-off.

That’s it for now, next up I’m hoping to have a look at individual player’s performance.

Like this:

In the last post we used Python code to take a look at a classic gambling situation, the coin flip, to make a point about the importance of choosing the highest odds available to bet at. Today, we’ll again use the coin flipping example to investigate another fundamental principal of successful gambling: stake sizing.

Now, imagine we’re one of the lucky punters from the last post who were allowed to bet on a fair coin flip at odds of 2.03. As I stated then, this is pretty much like a license to print money – but how much of your bankroll should you bet on each flip of the coin? Knowing that the coin was indeed fair and you would be getting the best of it, a natural instinct could be to bet as much as you could possibly cough up, steal and borrow in order to maximize your profit. This is a poor strategy though, as we’ll soon come to see.

The reason for this is that even if we do have come across a profitable proposition, our edge when betting at a (I’ll empasize it again: fair) coin flip at 2.03 odds is only 1.5% – meaning that for each 1 unit bet we are expected to net 0.015 units on average. This conclusion should be absolute basics for anyone interested in serious gambling, but to make sure we’re all on the same page I’ll throw some maths at you:

The Expected Value, or EV, of any bet is, simply put, the sum of all outcomes multiplied by their respective probabilities – indicating the punter’s average profit or loss on each bet. So with our coin flip, we’ll win a net of 1.03 units 50% of the time and lose 1 unit 50% of the time; our EV is therefore 1.03 * 0.5 + (-1 * 0.5) = 0.015, for a positive edge of 1.5% and an average profit of 0.015 units per bet. For these simple types of bets though, an easier way to calculate EV is to divide the given odds by the true odds and subtract 1: 2.03 / 2.0 – 1 = 0.015.

An edge of only 1.5% is nothing to scoff at though, empires has been built on less, so we’ll definitely want to bet something – but how much?

Stake sizing is much down to personal preferences about risk aversion and tolerance of the variance innately involved in gambling, but with some Python code we can at least have a look at some different strategies before we set out to chase riches and glory flipping coins. Just like in the last post I’ll just give you the code with some comments in it, which will hopefully guide you along what’s happening before I briefly explain it.

First off, we’ll modify our original coin_flips function to take our punter’s bankroll and stake size into consideration, setting the bankrupt threshold at the point where a default sized bet can no longer be made. By default, our punter will have an endless stream of 100 unit bankrolls, but if we set the parameter bankrupt to True, the function will cut away any coin flips after his first bankruptcy.

We also want to modify the many_coin_flips function so that it’ll also take bankroll and stake size into consideration, counting up how many of our punters went bankrupt.

We won’t use the compare_odds function here, instead we’ll write a new one to compare stake sizing – but if we ever want to use it again sometime in the future a few minor changes will be needed here as well:

def compare_odds(punters=100,n=10000,odds=[1.97,2.00,2.03]):
'''
Simulates and compare coin flip net winnings
after 10000 flips for 3 groups of punters,
betting at odds of 1.97, 2.00 and 2.03, respectively.
Also plots every punters net winnings
'''
# create figure and ax objects to plot on
fig, ax = plt.subplots()
# set y coordinates for annotating text for each group of punters
ys = [0.25,0.5,0.75]
# assign colors to each group of punters
cs = ['r','y','g']
# loop through the groups of punters, with their respective odds,
# chosen color and y for annotating text
for odd, color, y in zip(odds,cs,ys):
# run coin flip simulation with given odds, plot with chosen color
df = many_coin_flips(punters,n,odd,color=color,plot=True)
# calculate how many punters in the group ended up in profit
winning_punters = df['winning'].mean()
# set a text to annotate
win_text = '%.2f: %.0f%%' %(odd,winning_punters * 100)
# annotate odds and chance of profit for each group of punters
ax.annotate(win_text,xy=(1.02,y),
xycoords='axes fraction', color=color,va='center')
# set title
ax.set_title('Chances of ending up in profit after %s coin flips' %n)
# set x and y axis labels
ax.set_xlabel('Number of flips')
ax.set_ylabel('Net profit')
# add annotation 'legend'
ax.annotate('odds: chance',xy=(1.02,1.0),
xycoords=('axes fraction'),fontsize=10,va='center')
# add horizontal line at breakeven point
plt.axhline(color='k',alpha=0.5)
# set y axis range at some nice number
ax.set_ylim(-450,450)
# show plot
plt.show()

Now, with all our previous coin flip functions taking bankroll and stake size into consideration, we can go ahead and evaluate a few stake sizing strategies with a new function:

By default, our new compare_stakes function creates a number of punter groups, all betting on fair coin flips at 2.03 odds with a starting bankroll of a 100 units. For each group and their different staking plan, the function takes note of how many ended up in profit, how many lost and how many went bankrupt.

As we can see on the plot below, the results differ substantially:

Just like last time, I want to remind you that any numbers here are only rough estimates, and increasing the size of each punter group as well as the number of coin flips will get us closer to the true values.

So what can we learn from the above plot? Well, the main lesson is that even if you have a theoretically profitable bet, your edge will account for nearly nothing if you are too bold with your staking. Putting your whole bankroll at risk will see you go bankrupt around 96% of the time, and even if you bet as small as 2 units, you’ll still face a considerable risk of screwing up a lucrative proposition. The truth is that with such a small edge, keeping your bet small as well is the way to go if you want to make it in the long run.

But what if some fool offered us even higher odds, let’s say 2.20? First off, we would have to check if the person was A: mentally stable, and B: rich enough to pay us if (or rather, when) we win, before we go ahead and bet. Here our edge would be 10% (2.2 / 2.0 – 1), nearly 10 times as large as in the 2.03 situation, so we’ll likely be able to bet more – but how much? Well, the functions are written with this in mind, enabling us to play around with different situations and strategies. Specifying the odds parameter of our new function as 2.20, here’s what betting at a fair coin flip at 2.20 odds would look like:

As can be seen from the new plot, with a larger edge we can go ahead and raise our stake size considerably, hopefully boosting our winnings as well. So the main take-away from this small exercise is that even if you have an edge, if you want to make it in the long run you’ll have to be careful with your staking to avoid blowing up your bankroll – but also that the larger your edge, the larger you can afford to bet.

That’s it for now, but I’ll hopefully be back soon with a Part 2 about stake sizing, looking at a staking plan that actually takes your (perceived) edge into account when calculating the optimal stake size: The Kelly Criterion.

As I stated in the previous post, this blog will now focus more on gambling, using Python code to investigate whatever comes to my mind around the subject.

Today I’ll have a look at a classic gambling example – the flip of a coin – but before I go ahead and talk you through the code I want to state a few things that I know some of you will be wondering. Though R seems to be the language preferred by most in the football analytics scene, I have chosen Python simply because I feel it is so much more intuitive and easier to learn. RStudio seems to be the tool of choice for the R folks, but I don’t know of any real dominant counterpart for Python. I use Spyder, available through downloading Anaconda, mainly because it’s easy to use and comes with a lot of useful stuff pre-installed. If you’re thinking about testing it out yourself, I would suggest switching the color scheme of the editor to Zenburn for that dark and cool programming look that really make your code look super important, and run your scripts in the included IPython console.

One final, very important thing: I am not in any way an expert programmer, statistician, mathematician or anything like that. I am simply a gambler looking to use these fields to get an edge. It’s totally OK to simply copy and paste any code I publish here to use yourself and play around with it however you may wish. If you notice any mistakes or if something doesn’t add up, please comment. I’m happy to learn new stuff.

Flipping coins, and the importance of betting at the highest odds

The inspiration for this post came the other day when I noticed that a few hours prior to kick-off in this year’s Super Bowl, the bookmaker Pinnacle offered 1.97 odds on the opening coin flip. A sucker bet, I thought to myself, knowing the true odds of a fair coin to be 2.00. The coin flip is a very popular Super Bowl prop bet though and as it was pointed out to me on Twitter, a few books actually offered the fair odds of 2.00. Choosing the highest odds available is crucial if you want to make money gambling in the long run, so I decided to write up a nice little Python script to visualise my point.

The layout of these blog posts will be that I simply throw a piece of code at you, before explaining it. The comments in the code itself should also help you out, and for those of you who already know Python much will be simple basics, while those who’s completely new to coding or Python will hopefully learn a few things.

Allright, so after importing all the needed modules for this piece, we go ahead and define our first function, coin_flips, which will be used to simulate the coin flips and calculate the net winnings of a single punter. I’ve chosen 10,000 flips and Pinnacle’s odds of 1.97 as our default values here.

Creating a pandas dataframe, we can easily store the result of each coin flip. Now, as we assume that the coin is fair, there’s no need to even consider which side our punter would call each time, instead we can simply go ahead and use numpy to simulate a series of ones and zeros, representing either a win or a loss. Calculating the net result of each flip is also very straightforward as when he wins, our punter will pocket the net end of the offered odds, 0.97, while losing will see his pocket lightened by 1 unit. Calculating the cumulative net winnings is also very easy using pandas’ built-in cumsum function.

For coding reasons, the function is set to return the dataframe so calling it will simply make a lot of numbers pop up, but running the coin_flips()[‘cum_net’].plot() command in the IPython console will let you simulate a punter’s coin flips, and also plot his cumulative net winnings like this:

Every time you run the command another simulation will run with a new, different result. Doing this a couple of times, you’ll likely understand why I described this as a sucker bet. Sure, you can get lucky and win, even a couple of times in a row – but betting with the odds against you, you’ll find it very hard to make a profit long term.

But that single punter flipping coins 10,000 times actually doesn’t say that much, maybe he just got unlucky? To dig deeper we want to know just how likely you are to end up with a profit after 10,000 coin flips. So we write another function, using the previous one to simulate the results of many more punters betting on 10,000 coin flips. How many do you think will end up in profit?

The slightly more complicated many_coin_flips function uses the earlier coin_flips to loop through a group of punters, 100 by default, and save their results into a new pandas dataframe, punter_df, where we’ll assign a 1 to all punters who ended up in profit while all the losers get a 0. We also plot each punters cumulative net winnings with a nice red color to symbolise their (very) likely bankruptcy.

This function also returns a dataframe so running it will again make a lot of numbers pop up in the console, but it also plots out the financial fate of each punter, like this:

As we can see, there actually are a few of our 100 punters who got lucky enough to end up winning after 10,000 coin flips. But most of them ended up way below the break-even point, losing a lof of money. If this was a real group of punters we can only hope that even if they were stupid enough to set out betting on 10,000 coin flips at these odds, they’ll at least at some point realise their mistake and quit.

But how about if we change the offered odds? As I mentioned earlier, some books actually put up the fair odds of 2.00. How would 100 punters do after 10,000 coin flips betting at those odds? Well, we’ll have to write a new function for that. Also, just for fun (or to make a point) I’ve included an additional group of 100 punters lucky enough to be allowed to bet on the coin flips at odds of 2.03 – literally a license to print money.

def compare_odds(punters=100,n=10000,odds=[1.97,2.00,2.03]):
'''
Simulates and compare coin flip net winnings
after 10000 flips for 3 groups of punters,
betting at odds of 1.97, 2.00 and 2.03, respectively.
Also plots every punters net winnings
'''
# create figure and ax objects to plot on
fig, ax = plt.subplots()
# set y coordinates for annotating text for each group of punters
ys = [0.25,0.5,0.75]
# assign colors to each group of punters
cs = ['r','y','g']
# loop through the groups of punters, with their respective odds,
# chosen color and y for annotating text
for odd, color, y in zip(odds,cs,ys):
# run coin flip simulation with given odds, plot with chosen color
df = many_coin_flips(punters,n,odd,color)
# calculate how many punters in the group ended up in profit
winning_punters = df['winning'].mean()
# set a text to annotate
win_text = '%.2f: %.0f%%' %(odd,winning_punters * 100)
# annotate odds and chance of profit for each group of punters
ax.annotate(win_text,xy=(1.02,y),
xycoords='axes fraction', color=color,va='center')
# set title
ax.set_title('Chances of ending up in profit after %s coin flips' %n)
# set x and y axis labels
ax.set_xlabel('Number of flips')
ax.set_ylabel('Net profit')
# add annotation 'legend'
ax.annotate('odds: chance',xy=(1.02,1.0),
xycoords=('axes fraction'),fontsize=10,va='center')
# add horizontal line at breakeven point
plt.axhline(color='k',alpha=0.5)
# set y axis range at some nice number
ax.set_ylim(-450,450)
# show plot
plt.show()

This last function makes use of the two previous ones to simulate the coin flips of our three groups of punters, plotting their total net winnings all on the same ax object, which we later make use of to add a title and some nice labels to the axes. We also add a horizontal line to be able to better compare the punters’ winnings with the break-even point, as well as some text annotation to explain the colors of the three groups.

Now, running the compare_odds() function in the IPython console will hopefully result in something like this:

Here we clearly see just how important betting at the highest odds really is. Have in mind though that the numbers to the right are only rough estimates. As you can see, the yellow group of punters who bet at the fair odds of 2.00 did not win exactly 50% of the time, but close enough. I actually had to re-run the function a few times to get this close. But it’s only natural since we only had 100 punters, a very small number in this context, in each of our groups. The more punters and coin flips we use in our simulations, the closer we’ll come to the real win percentages – but here speed is more important than super accuracy.

So as we clearly see in the above plot, betting on the coin flip at Pinnacle’s 1.97 odds really is a sucker bet, albeit an entertaining one if you were planning to watch the Super Bowl. But if you hope to make a profit from your betting, finding the highest available odds to bet on is crucial, as is shown by the green group of punters who were allowed to bet at odds of 2.03. It’s only a difference of 0.06, but it makes all the difference in the long run. The margins in betting are tiny, but they add up over time.

The lessons learned here can easily be transferred to sports betting in general and football betting in particular, were the Asian Handicaps and Over/Under markets focus on odds around even money. The coin flip example is special though as we knew the true odds of the bet beforehand, something you’ll never be able to know betting on football. But as shown in the last plot, by consistently betting at the highest available odds, you at least give yourself a much better chance of ending up in profit.

As you may have noticed, I haven’t written anything in months. There’s two reasons for this, one being of course that the Swedish football season I’ve primarily focused on ended in November, but it’s also because I’ve taken on a new job. Working full time for the first time in my life has simply left me with little time to do any writing. (Yes, I did use the word time three times in that short sentence.)

But now, having settled in at the new job I’m anxious to get back to writing again. There’s one thing though: as I now work with compiling odds on Swedish football I wouldn’t feel comfortable publishing football analytics about Allsvenskan, telling you which teams are underrated and who’ll win the league title. And knowing I set the odds, and potentially profit from your mistakes, why would you believe anything I said?

So this blog will take on a slightly new focus: gambling. I originally set up the blog intending to write about this topic as well as football analytics, using maths, statistics, probability and psychology to discuss interesting things related to gambling, but the football part soon took over completely.

As I’ve published my football work on the blog I’ve now and then gotten some questions about programming, so I’ve taken the decision to include Python code whenever applicable. Learning to code has made a huge difference for me both in my gambling and football analytics endeavours, and though the blog won’t turn into a Python tutorial per se, if any of you who are new to programming should learn a new thing or two through my writing, I’d be glad.

A week ago I published the first part of – hopefully – three Allsvenskan 2016 summaries, then focusing on team performance. Now it’s time to have a look at individual players, much like I did back in July. Though there now exists detailed Opta data for Allsvenskan, my work on this site has mostly been based on the older, less detailed data sources focused on shots and thus this summary will only look at attacking players.

I’ve again had a look at Goal Contribution (goals+assists) and Expected Goals, dividing all players into three age groups, and also had a closer look at a few interesting players:

The Goal Contribution chart is unsurprisingly headed by Häcken’s John Owoeri who clinched the title as the league’s top scorer with his 4 goals against Falkenberg in the last round of the season. Interestingly, Owoeri only came alive in the second half of the season, scoring 15 of his 17 goals after the summer break.

Assist monster Magnus Wolff Eikrem sits in second, with his 0.71 assists per 90 minutes playing a big part in Malmö retaking the title. Of the other top players, Antonsson, Kjartansson and Nyman left the league during the summer transfer window but still impressed enough during the spring to remain in the top 10.

Djurgården’s Michael Olunga sits top among the players aged 20-23. Dubbed ‘The Engineer’ for his ongoing studies, Olunga just like Owoeri needed time to get going, scoring all of his 12 goals during the last 13 games when Mark Dempsey came in to steer Djurgården away from the relegation battle.

Comparing Owoeri and Olunga, it’s clear from the shot maps why Owoeri was the superior goalscorer this season. He only shoots slightly more than Olunga, but does so from far better locations closer to goal, with his average xG per shot at 0.16 while Olunga at 0.12 rely more on his finishing skill from longer range. If ‘The Engineer’ can work on his shot selection for next season I really think he can challenge for the top scorer title.

AIK’s Alexander Isak reign supreme among the youngest players, with his 0.62 G+A90 very impressive for a player who only turned 17 late in the season. He’s quite good at getting into good shot locations as well, with 5 of his 10 goals coming from a sweet spot just in front of goal.

There’s been plenty of rumours of an upcoming big transfer during the winter window and looking very much like the real deal, Isak could very well break Zlatan Ibrahimovic’s transfer record from 2001. Here’s a nice radar plot from Ted Knutson showing Isak’s skills:

Malmö’s Vidar Kjartansson was the king of xG this season, and the club impressingly still managed to secure the title after selling him during the summer transfer window. Kjartansson combined both quantity with quality, taking most of his shots from very good locations with an average xG per shot of 0.2.

Östersund’s Abdullahi Gero was a bit of a surprise for me, but his shot locations are good with an average xG per shot close to Kjartansson at 0.19. He could very well go on to score more next season if given the chance in Graham Potter’s Östersund side which have done so well xG-wise this season – actually finishing 4th in xG Difference per game!

As a Djurgården supporter I’m glad to see 20-year old Tino Kadewere’s development this season. Though his 793 minutes played was less than the 900 needed to be included above, he racked up an impressing 0.79 G+A90 which would see him sit 8th overall, just above Olunga, and top the players aged 20-23 if the cut-off would have been 1/4 of the league minutes played instead of 1/3. Focusing more on assists than Olunga, the two could form a dynamic partnership for Djurgården if they get the chance next season.

That’s it for now, but if you want to see more shot maps, just give me a shout on twitter. If I’ll find the time, I’ll also write a third summary looking at how my predictions have done over the season and how my model did against the betting markets.

Advertisements

Share this:

Like this:

With the season ending more than a week ago, I finally have enough time to sit down and write a summary. I’ll split it in parts, with the first two looking at team and player performance respectively, and hopefully I’ll get around to writing a third part in which I look at how my predictions have done, and the model’s performance on the betting markets.

These kind of updates likely won’t return for next season when I’ll be taking on a new job compiling odds on Swedish football. I’m hoping to continue writing in some form though.

But enough about that, let’s get to it:

As most expected, Malmö bounced back from last season’s 5th place to reclaim the title from Norrköping. Luckily they did so without the need of the extra win awarded to them after the abandonded game against Göteborg where the home fans threw pyrotechnics against the Malmö players – and Tobias Sana responded with a spear throw.

At the other end of the table, Gefle were finally relegated after several years of clinging to their place in the top flight. Falkenberg’s extremely poor season saw them relegated as well, while Helsingborg will have to face third placed Superettan side Halmstad in a two-leg relegation play-off.

Take a look at Djurgården’s row of results by the way, only one draw!

Malmö were the best side in terms of shots taken and conceded as well, while bottom duo Gefle and Falkenberg really struggled together with Sundsvall, whose good start to the season saw them able to avoid the relegation battle despite only picking up two wins after the summer break. Örebro was an outlier throughout the season, usually producing some high-shooting games.

Göteborg were the most efficient attacking side during the season, but their low shot volume saw them unable to compete with the real top sides. AIK and Malmö relied on pure shot volume instead, probably a result of their ability to dominate games. Falkenberg on the other hand really struggled with both volume and effectiveness, usually needing nearly 11 shots to score.

At the other end of the pitch we see partly why Malmö were the superior side this season, and why AIK finally overtook Norrköping in second place: they both enjoyed some very efficient defending, clearly outperforming their opponents. Falkenberg struggled here as well, conceding a goal about every 5th shot, while Örebro actually did well efficiency-wise despite conceding a lot of shots.

Champions Malmö were the best side in terms of both xG and xG conceded, while Falkenberg’s poor defence was a big factor in their relegation. Djurgården, Hammarby and Östersund did better defensively than their final positions in the table might suggest, unable to break into the top mostly because of their weaker attacking output.

Ranking the teams by Expected Goals Difference did well to explain both ends of the table, getting the top 3 and bottom 2 correct. As mentioned, Sundsvall’s ‘lucky’ results at the start of the season saw them avoid the relegation battle, while Östersund, Hammarby and Djurgården formed an underperfoming trio just below the top sides.

Simulating every game based on the shots taken and their xG values, we can give the teams ‘Expected Points’. This is very close to the xGD rankings above but we can see some differences, like Falkenberg ‘earning’ almost as many Expected Points as Gefle, which they were no way near in reality.

Let’s see how the teams actually did compared to their Expected Points:

What we see is that typically the winning sides overperform against Expected Points, while the losing sides underperform. This is to be expected as you’ll very rarely (or never) dominate a game enough for your expected points to match the three actual points awarded for a win. The same goes for losing, since you’ll pretty much always ‘earn’ more than zero Expected Points.

There’s always exceptions to the rule though, and this season Gefle stands out as having picked up pretty much exactly the points expected from them which I would say is rare for a losing side, while Falkenberg look to have been very unlucky to pick up so few points.

Looking at the Expected Points distributions for the teams, we really see just how ‘unlucky’ Falkenberg have been. As mentioned above, losing sides will very often underperform against Expected Points but Falkenberg really stands out with a 100% chance of picking up at least the 10 points they ended up with, implied by the 10,000 seasons I ran through my simulation.

Djurgården

As I’ve done in a few updates, I’ll end this one looking at Djurgården. As a lifelong supporter I’ve become used to the ups and downs but despite that and the underlying good numbers I was still a bit concerned this season.

Luckily though, Mark Dempsey, the right man at the right time, stepped in and turned things around much like his former mentor Per-Mathias Høgmo did in 2013. Just like I’ve seen him do in Norway, Dempsey focused on a very direct attack which worked well to improve shot numbers and level out Djurgården’s dropping xGD, while at the same time crucially also getting some real results.

Defence continued to struggle though, and no real tactics but ‘get the ball up to the big boys up front’ was clearly visible – a decent game plan to get them out of the hole they’ve dug themselves into but not something to build on for the future so the club’s decision to not give Dempsey a new contract looks reasonable.

That’s it for now, in a few days I’ll be looking closer at individual player performance.