R at Bet Better
Turn betting odds into probabilities in R, and see what the bookmaker keeps
28 September 2026. Base R only.
Decimal odds hide two numbers: the bookmaker's probability for each outcome, and the cut they take. Ten lines of R separate them.
1. Implied probability
odds <- c(home = 1.85, away = 2.05)
implied <- 1 / odds
implied
# home away
# 0.541 0.488
The two implied probabilities add to 1.029, not 1. That extra 2.9 per cent is the overround, the margin built into the prices.
2. The margin in cents per dollar
overround <- sum(implied) - 1
margin_pct <- 100 * overround / sum(implied)
round(margin_pct, 2)
# 2.8 (per cent of every dollar staked, across the market)
3. Remove it to get fair probabilities
fair <- implied / sum(implied)
fair
# home away
# 0.526 0.474
fair_odds <- 1 / fair
round(fair_odds, 2)
# 1.90 2.11
This is the simple proportional method. It assumes the margin is spread evenly across outcomes, which it usually is not: bookmakers load more of it onto the longer price. The calibration study measures that tilt across 18,238 settled markets.
4. Do it for a whole file
A clean file of bookmaker prices, one row per priced outcome, is on the Internet Archive under CC BY 4.0:
prices <- read.csv("https://archive.org/download/betbetter-market-prices-2026/betbetter-market-prices-2026.csv")
str(prices)
library(dplyr)
margins <- prices %>%
group_by(sport, game_date, home_team, away_team, market) %>%
summarise(overround = sum(1 / bookmaker_price) - 1,
margin_pct = 100 * overround / sum(1 / bookmaker_price),
sides = n(), .groups = "drop") %>%
filter(sides == 2)
summary(margins$margin_pct)
Group by market, apply the three steps, and you have a margin per market that you can compare by sport, bookmaker or date. That is exactly how the Margin Index is built each day.
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.