R at Bet Better › R for Betting-Market Data
Chapter 2. Removing the margin: four methods
20 October 2026. Edward Glush. Code run in R 4.4.1 against the live open data on that day.
Chapter 1 removed the margin by scaling every probability down by the same factor. That is one choice among several. This chapter writes four common methods in base R, runs them on the same market, and checks the code against published results.
Data and licence. No download is needed. The worked market is home 2.10, draw 3.40, away 3.50. The four methods are standard in the literature. For a ready-made package, see implied on CRAN, which covers these and more.
2.1 Why the method matters
Every method below starts from the same implied probabilities and returns fair probabilities that add to 1. They differ in where they take the cut from. The proportional method takes the same share from every outcome. Others take more from the long shots and less from the favourite.
Why would a bookmaker load the long shots? Two reasons are common. Casual bettors like long shots and are less fussy about price. And a book that fears better-informed bettors protects itself most where a single bet can cost the most. The study of this tilt is old. It is called the favourite-longshot bias.
odds <- c(home = 2.10, draw = 3.40, away = 3.50)
implied <- 1 / odds
2.2 Proportional
Divide each implied probability by their sum. This is the method from chapter 1.
remove_proportional <- function(odds) {
p <- 1 / odds
p / sum(p)
}
2.3 Additive
Take the same amount off every outcome: the overround divided by the number of outcomes. In absolute terms each outcome loses the same number of points. In relative terms the long shots lose more. The additive method can return a negative probability for a very long shot in a big field, so check for that if you use it on races.
remove_additive <- function(odds) {
p <- 1 / odds
p - (sum(p) - 1) / length(p)
}
2.4 Power
Raise every implied probability to the same power k, choosing k so the results add to 1. Since every probability is below 1, a power above 1 shrinks small numbers more than large ones. We find k with uniroot(), base R's root finder.
remove_power <- function(odds) {
p <- 1 / odds
f <- function(k) sum(p^k) - 1
k <- uniroot(f, c(1, 10), tol = 1e-12)$root
out <- p^k
attr(out, "k") <- k
out
}
2.5 Shin
Shin's method comes from a model of a bookmaker who knows that some share z of the money comes from insiders who know the result. The bookmaker sets prices to break even against them. Given the prices, you can solve for z and for the fair probabilities at the same time. The formula for one outcome, given z and the booksum B, is below. We again use uniroot() to find the z that makes the fair probabilities add to 1.
remove_shin <- function(odds) {
p <- 1 / odds
B <- sum(p)
shin_p <- function(z) (sqrt(z^2 + 4 * (1 - z) * p^2 / B) - z) / (2 * (1 - z))
z <- uniroot(function(z) sum(shin_p(z)) - 1, c(0, 0.5), tol = 1e-12)$root
out <- shin_p(z)
attr(out, "z") <- z
out
}
Some write-ups use a fixed-point loop for z instead. That loop divides by the number of outcomes minus 2, so it breaks on a two-way market. The root-finder version works for any number of outcomes.
2.6 Compare them
prop <- remove_proportional(odds)
add <- remove_additive(odds)
pow <- remove_power(odds)
shin <- remove_shin(odds)
tab <- rbind(proportional = prop, additive = add,
power = as.numeric(pow), shin = as.numeric(shin))
round(100 * tab, 2)
round(rowSums(tab), 6)
round(attr(pow, "k"), 4)
round(attr(shin, "z"), 4)
## home draw away
## proportional 45.09 27.85 27.06
## additive 45.75 27.54 26.70
## power 45.75 27.54 26.71
## shin 45.58 27.62 26.80
## proportional additive power shin
## 1 1 1 1
## [1] 1.0538
## [1] 0.0281
All four rows add to 1. The proportional method gives the home side 45.09 per cent. The other three give it more, between 45.58 and 45.75, and take the difference from the draw and the away win. That is the long-shot loading at work. Shin's z comes out at about 2.8 per cent: the share of informed money that would explain this margin under his model.
2.7 Check against known results
Before trusting new code, test it against numbers someone else has published for the same input. For this market the known results, in per cent, are below. all.equal() returns TRUE when they match.
known <- rbind(proportional = c(45.09, 27.85, 27.06),
additive = c(45.75, 27.54, 26.70),
power = c(45.75, 27.54, 26.71),
shin = c(45.58, 27.62, 26.80))
all.equal(round(100 * tab, 2), known, check.attributes = FALSE)
## [1] TRUE
And the fair odds each method implies:
round(1 / tab, 2)
## home draw away
## proportional 2.22 3.59 3.70
## additive 2.19 3.63 3.74
## power 2.19 3.63 3.74
## shin 2.19 3.62 3.73
2.8 A two-way market with a big favourite
The methods split further apart when one side is a long shot. Here is a 1.20 favourite against a 4.50 outsider.
long <- c(fav = 1.20, dog = 4.50)
round(100 * rbind(proportional = remove_proportional(long),
additive = remove_additive(long),
power = as.numeric(remove_power(long)),
shin = as.numeric(remove_shin(long))), 2)
## fav dog
## proportional 78.95 21.05
## additive 80.56 19.44
## power 81.50 18.50
## shin 80.56 19.44
The outsider's fair chance runs from 18.5 to 21.05 per cent depending on the method. That gap of two and a half points is larger than most bookmaker margins. Notice too that Shin and additive give the same answer on a two-way market. That is a known property of Shin's model, and a useful extra check on the code.
Look also at the fair odds table in section 2.7. Under the proportional method the home side's fair price is 2.22. Under the other three it is 2.19. A difference of three cents in a fair price sounds small. But if you compare a fair price with a bookmaker's offer to judge whether that offer is good, three cents can decide the answer.
2.9 Which one should you use?
There is no single right answer. The honest way to choose is to test. Take many settled markets, remove the margin with each method, and score each set of fair probabilities against what happened (chapter 4 shows how). The method with the best score on your markets is the one to use for those markets. Studies on soccer often find power or Shin beat proportional, but results vary by sport and by book.
Try it yourself
- Write a function that takes odds and a method name and returns the fair probabilities, so you can call all four in one
sapply().
- Build a ten-runner race with one 2.50 favourite and several 30.00 outsiders. Which method first returns a negative probability as you widen the margin?
- Plot the fair chance of the 4.50 outsider under each method as the favourite's price moves from 1.10 to 1.90.
Data licensed CC BY 4.0, free to reuse with a credit. Model estimates, not advice. 18+. If gambling is causing you harm: gamblinghelponline.org.au.