Skip to content
Open

PR #1

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
Package: implied
Type: Package
Title: Convert Bookmaker Odds to Probabilities
Version: 0.3.0
Title: Convert Between Bookmaker Odds and Probabilities
Version: 0.5
Author: Jonas Christoffer Lindstrøm
Maintainer: Jonas Christoffer Lindstrøm <jonaslindstrom88@gmail.com>
Description: Convert bookmaker odds into proper probabilities. Seven different
Description: Convert between bookmaker odds and probabilities. Eight different
algorithms are available, including basic normalization, Shin's method
(Hyun Song Shin, (1992) <doi:10.2307/2234526>), and others.
License: GPL-3
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.0.2
RoxygenNote: 7.1.2
Suggests: testthat (>= 2.0.1), knitr, rmarkdown
VignetteBuilder: knitr
NeedsCompilation: no
Packaged: 2020-04-18 20:55:05 UTC; jonas
Packaged: 2023-06-11 18:14:04 UTC; Jonas Christoffer
Repository: CRAN
Date/Publication: 2020-04-19 00:10:02 UTC
Date/Publication: 2023-06-11 19:50:02 UTC
28 changes: 17 additions & 11 deletions MD5
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
e1c144d5150fe622cb088097d29ffaf7 *DESCRIPTION
75b5ea970258de895ca96f3181b8d226 *NAMESPACE
31329041bcfc965c10df22f350a6374d *NEWS.md
55da476a7af11123d0eb8ec856e198b1 *R/implied_probabilities.R
ed8a77ffdb40f140f6980f99e45a2f4c *DESCRIPTION
d32331dc3ccbe103bf204525d1ff82dd *NAMESPACE
5175f26d9c3bc4f740a5496d4660b7dc *NEWS.md
8a73301aac80eaec88540c50efe4ee62 *R/implied_odds.R
078d65c0870d882ebf7e890a836385cb *R/implied_probabilities.R
c71d10f8db03d579fa1c0c6e76959a7f *R/zzz.R
36fd139695e3baa1e55cba43a4853575 *build/vignette.rds
aa3c1d34d43a0625c11e32d252ef5614 *inst/doc/introduction.R
71fa897a314a5fac9c5cdbe6693f7ab0 *inst/doc/introduction.Rmd
38b8657a46510b13acf377dfb886952c *inst/doc/introduction.html
4bddfd68c8d5de61751fe82910dd1e27 *man/implied_probabilities.Rd
e8bc7681acd8e612eaab83ac4fc77076 *build/vignette.rds
9f3d124e6bbd9e052fef01e69d83ddba *inst/doc/Troubleshooting.R
111e67fb6ca9478877c5ab056380f1e0 *inst/doc/Troubleshooting.Rmd
cce10b1137bac370165e60c135599a9e *inst/doc/Troubleshooting.html
b95af5b478898d4e0b06423d0f827ad9 *inst/doc/introduction.R
8d8c5b2afdcae2048b42605d42ef2a70 *inst/doc/introduction.Rmd
f697ee1b3f19b8d8b4a085a46d84f176 *inst/doc/introduction.html
32101c917ca72f6d3e0d880a57a802c7 *man/implied_odds.Rd
2f28bbc547f1a4eb90cbfa92ac2a2cff *man/implied_probabilities.Rd
bc77ebadaa37370915e00adac9036b01 *tests/testthat.R
970cea6265a2285a1865d398a855dd4a *tests/testthat/test_1.R
71fa897a314a5fac9c5cdbe6693f7ab0 *vignettes/introduction.Rmd
60e9727bd36a19cbb31f8324f0ea3ac1 *tests/testthat/test_1.R
111e67fb6ca9478877c5ab056380f1e0 *vignettes/Troubleshooting.Rmd
8d8c5b2afdcae2048b42605d42ef2a70 *vignettes/introduction.Rmd
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Generated by roxygen2: do not edit by hand

export(implied_odds)
export(implied_probabilities)
40 changes: 33 additions & 7 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
# implied Version 0.3.0
* A new algorithm for Shin's method is included.
* Small bugfix for when Shin's method fails and produces NA's. Instead of crashing, it now raises a warning and flags the result as problematic.


# implied Version 0.2.5
* First version on CRAN.


# implied Version 0.5
* Can now convert odds to probabilities that should sum to other values than 1, using the target_probability argument in implied_probabilities().
* New vignette 'Troubleshooting'.
* Introduction vignette updated with how to use the new target_probability option.
* New option 'uniroot_options' in implied_probabilities, to better control the uniroot solver.
* Fixed many spelling errors in the documentation.


# implied Version 0.4.1
* Small change to how the 'jsd' method in implied_probabilities() works, so that it works in some cases where it used to fail.
* Fixed a link in the Introduction vignette.


# implied Version 0.4.0
* New function implied_odds(), that converts probabilities to odds with a given margin.
* New method = 'jsd' in implied_probabilities(). Check the introductory vignette for more information.

# implied Version 0.3.2
* Fixed wrong formula for the WPO method in the introduction vignette.

# implied Version 0.3.1
* Raises error if the inverse odds sum to less than 1.
* NA's will be returned if there are NA's in the input odds.

# implied Version 0.3.0
* A new algorithm for Shin's method is included.
* Small bugfix for when Shin's method fails and produces NA's. Instead of crashing, it now raises a warning and flags the result as problematic.


# implied Version 0.2.5
* First version on CRAN.
248 changes: 248 additions & 0 deletions R/implied_odds.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@



# The functions xx_func_o(coef, probs) transforms proper probabilities (that sum to 1)
# into improper probabilities as a function of the input coefficient.
# The corresponding functions xx_o_solvefor(coef, probs, margin) are used
# with uniroot to find the coefficient that makes the transformed probabilities
# sum to the desired margin.

# Transform the probabilities using the Shin's method,
# for a given value of the odds ratio cc.
shin_func_o <- function(zz, probs, grossmargin=NULL){

# Eq. 5 in Shin 1993.
yy <- sqrt((zz*probs) + ((1-zz)*probs^2))
res <- yy * sum(yy)

if (!is.null(grossmargin)){
# Eq. 14 in in Fingleton & Waldron 1999
res <- res / (1 - grossmargin)
}

return(res)
}

# the condition that the sum of the probabilites must sum to 1.
# Used with uniroot.
shin_o_solvefor <- function(zz, probs, margin, grossmargin=NULL){
tmp <- shin_func_o(zz, probs, grossmargin)
sum(tmp) - (1 + margin)
}


# Transform the probabilities using the odds ratio method,
# for a given value of the odds ratio cc.
or_func_o <- function(cc, probs){
or_probs <- cc * probs
or_probs / (1 - probs + or_probs)
}

# The condition that the sum of the transformed probabilites
# must sum to 1 + margin.
or_o_solvefor <- function(cc, probs, margin){
tmp <- or_func_o(cc, probs)
sum(tmp) - (1 + margin)
}


# Transform the probabilities using the power method.
pwr_func_o <- function(nn, probs){
probs^(nn)
}

# The condition that the sum of the transformed probabilites
# must sum to 1 + margin.
pwr_o_solvefor <- function(nn, probs, margin){
tmp <- pwr_func_o(nn, probs)
sum(tmp) - (1 + margin)
}



#' Implied odds with added margin from probabilities.
#'
#' This functions converts probabilities to odds in decimal format, while adding overround.
#' The function does the inverse of what the function \code{\link{implied_probabilities}} does.
#'
#' @param probabilities A matrix or numeric of probabilities, where each column is an outcome.
#' @param method A string giving the method to use. Valid methods are 'basic', 'shin', 'bb', 'wpo', 'or', 'power' or 'additive'.
#' @param margin numeric. How large margin (aka overround) should be added to the probabilities.
#' @param grossmargin Numeric. Must be 0 or greater. See the details.
#' @param normalize Logical. If TRUE (default), scale the input probabilites to sum to 1.
#'
#' @return A named list. The first component is named 'odds' and contain a matrix of
#' implied odds. The second depends on the method used to compute the probabilities.
#'
#' @export
implied_odds <- function(probabilities, method = 'basic', margin = 0,
grossmargin = NULL, normalize=TRUE){

stopifnot(length(method) == 1,
length(margin) == 1,
tolower(method) %in% c('basic', 'shin', 'bb', 'wpo', 'or', 'power', 'additive'),
all(probabilities >= 0, na.rm=TRUE))



if (!is.matrix(probabilities)){

if ('data.frame' %in% class(probabilities)){
probabilities <- as.matrix(probabilities)
} else {
probabilities <- matrix(probabilities, nrow=1,
dimnames = list(NULL, names(probabilities)))
}
}

# Make sure the probabilities sum to exactly 1.
if (normalize){
probabilities <- probabilities / rowSums(probabilities)
}

# Prepare the list that will be returned.
out <- vector(mode='list', length=1)
names(out) <- c('odds')

# Some useful quantities
n_probs <- nrow(probabilities)
n_outcomes <- ncol(probabilities)

# Missing values
missing_idx <- apply(probabilities, MARGIN = 1,
FUN = function(x) any(is.na(x)))

# inverted_probs <- 1 / probabilities

if (method == 'basic'){

out$odds <- 1 / (probabilities * (1 + margin))

} else if (method == 'shin'){

odds <- matrix(nrow=n_probs, ncol=n_outcomes)
zz <- numeric(n_probs)

for (ii in 1:n_probs){

# Skip rows with missing values.
if (missing_idx[ii] == TRUE){
next
}

if (margin != 0){
res <- stats::uniroot(f=shin_o_solvefor, interval = c(0, 0.4),
probs=probabilities[ii,],
margin = margin, grossmargin = grossmargin)
zz[ii] <- res$root
} else {
zz[ii] <- 0
}

odds[ii,] <- 1 / shin_func_o(zz=zz[ii], probs = probabilities[ii,], grossmargin = grossmargin)
}

out$odds <- odds
out$zvalues <- zz

} else if (method == 'bb'){

if (is.null(grossmargin)){
grossmargin <- 0
} else {
stopifnot(grossmargin >= 0,
length(grossmargin) == 1)
}

zz <- (((1-grossmargin)*(1 + margin)) - 1) / (n_outcomes-1)
out$odds <- 1 / ((1+margin) * (((probabilities*(1-zz)) + zz) / ((n_outcomes-1)*zz + 1)))

out$zvalues <- zz

} else if (method == 'wpo'){
# Margin Weights Proportional to the Odds.
# Method from the Wisdom of the Crowds pdf.
invprob <- 1 / probabilities
out$specific_margins <- (margin * invprob) / n_outcomes
out$odds <- invprob / (1 + out$specific_margins)

} else if (method == 'or'){

odds <- matrix(nrow=n_probs, ncol=n_outcomes)
odds_ratios <- numeric(n_probs)

for (ii in 1:n_probs){

# Skip rows with missing values.
if (missing_idx[ii] == TRUE){
next
}

if (margin != 0){
res <- stats::uniroot(f=or_o_solvefor, interval = c(0.05, 5),
probs=probabilities[ii,], margin = margin)
odds_ratios[ii] <- res$root
} else {
odds_ratios[ii] <- 1
}

odds[ii,] <- 1 / or_func_o(cc=odds_ratios[ii], probs = probabilities[ii,])
}

out$odds <- odds
out$odds_ratios <- odds_ratios

} else if (method == 'power'){

odds <- matrix(nrow=n_probs, ncol=n_outcomes)
exponents <- numeric(n_probs)

for (ii in 1:n_probs){

# Skip rows with missing values.
if (missing_idx[ii] == TRUE){
next
}

if (margin != 0){
res <- stats::uniroot(f=pwr_o_solvefor, interval = c(0.0001, 1.1),
probs=probabilities[ii,], margin = margin)
exponents[ii] <- res$root
} else {
exponents[ii] <- 1
}

odds[ii,] <- 1 / pwr_func_o(nn=exponents[ii], probs = probabilities[ii,])
}

out$odds <- odds
out$exponents <- exponents

} else if (method == 'additive'){

odds <- matrix(nrow=n_probs, ncol=n_outcomes)

for (ii in 1:n_probs){

# Skip rows with missing values.
if (missing_idx[ii] == TRUE){
next
}

odds[ii,] <- 1 / (probabilities[ii,] + (margin / n_outcomes))
}

out$odds <- odds

}

# Make sure the matrix of implied probabilities has column names.
if (!is.null(colnames(probabilities))){
colnames(out$odds) <- colnames(probabilities)
}


return(out)


}
Loading