Mätresultat i fotboll är i stort identiskt med mätresultat från många industriella processer eller resultat i vården eller i trafiken. Det centrala är händelser i tiden alltså ’mål per match’, ’antal reklamationer per månad’, ’antal olyckor per år’, etc. Men man kan också räkna ’antal per yta’ (älgar per hektar), eller ’antal per volym’ (’bakterier per milliter blod’), etc.
Den självklara statistiska modellen för ‘antalsdata’ är Poissonfördelningen. Lägsta värdet för ett dylika mätresultat är naturligtvis 0 men det finns i teorin inget högsta antal.
Poissonfördelningen har praktiskt nog bara en parameter, ett tal som är större än 0 och brukar kallas ‘intensiteten’ och betecknas med grekiska bokstaven ‘lambda’ (λ).
Mer info: https://ovn.ing-stat.se [7], [20], [OE27], [OE28].
En analys av händelsedata är alltså en jakt på värdet λ. Ändrar sig värdet över tid (antal kunder per tidsenhet varierar antagligen systematiskt över en vecka)? Eller är värdet olika mellan maskiner eller mellan andra processer?, etc.
Inom fotbollsintresset, men även processer enligt ovan, finns det alltid
en mängd tvärsäkra men ogrundade påstående om sambanden. I tider av
stora fotbollsturneringar svämmar tidningarna över av påstående om det
ena eller andra typ ‘den som lägger första straffen vinner matchen’ och
med tillägget ‘…visar statistiken’. Skrapar man på ytan finns det oftast
ingen grund för uppfattningen. Detta är kanske inte så viktigt i
underhållsbranschen men kan ju vara katastofalt inom t.ex. vården.
Att lära att förstå och hantera händelsedata och Poissonfördelningen ger
alltså stora fördelar.
Datamängden. Datamängden som analyseras nedan kommer från en hemsida. Den innehåller en mängd uppgifter såsom tidpunkter för gula och röda kort, tidpunkt för byten, m.m. Här används dock endast tidpunkt för mål för respektive lag och endast resultatet efter full tid. Resultat från straffläggning är alltså inte med.
1. Antal mål per match - utfall och teoretisk fördelning
2. Antal mål per 4-minuters intervall
3. Tid till matchens första mål
4. ‘Probability plot’ för tid fram till första mål
5. Teoretisk fördelning för skillnad mellan antal mål
6. Absolut skillnad i antal mål - utfall och teoretisk fördelning
1. Antal mål per match - utfall och teoretisk fördelning
De två histogrammen visar summan av antal mål per match för cirka 100
matcher. Den blå fördelningen av resultat är Poissonfördelningen där
intensiteten lambda (λ) skattats från data och den röda
fördelningen är utfallet från VM-matcherna.
Det tycks inte vara någon
dramatisk skillnad så en slutsats är att antal mål per match kan anses
vara en slumpprocess som följer en Poissonfördelning:
library("tidyverse") # Load tidyverse packages
library("dplyr")
library("ggplot2")
VM2026 <- read.csv("~/Desktop/Ing-Stat/VMfotboll 2026 rev D.csv", sep=";")
# View(VM2026)
Målminut <- VM2026$Minut
Match <- VM2026$Match
antalNoll <- sum(is.na(Målminut))
cellbredd <- 4 # Minuter
cellnummer <- ceiling(Målminut/cellbredd)
Medelmaxminuter <- mean(Målminut[Målminut > 90], na.rm = TRUE)
listamax <- sort(Målminut[Målminut > 90])
antalMatcher <- max(Match, na.rm = TRUE)
antalMål <- length(Målminut) - antalNoll # Varje rad är ett mål, utom '0-0'
totlambda <- antalMål/antalMatcher
målres <- VM2026 %>% # Beräknar antal mål per match.
count(Match) %>%
group_by(Match) %>%
mutate()
antalMål <- append(målres$n, rep(0, antalNoll))
mållista <- data.frame(antalMål)
res <- mållista %>% # Beräknar antal mål per match.
count(antalMål) %>%
group_by(antalMål) %>%
mutate()
# ------------------------- Nedan: skapa två stapeldiagram
xPoisson <- c(0:8)
sannPoisson <- dpois(xPoisson, totlambda)
ggplot() +
geom_bar(aes(x = res$antalMål, y = res$n/sum(res$n)), stat = 'identity', width = 0.6, alpha = 0.40, fill = 'red') +
scale_x_continuous(breaks = res$antalMål) + xlab('Antal mål i fotbollsmatcher (VM 2026)') + ylab('') + theme(legend.position="none") +
annotate("text", x = 6, y = Inf, label="Röd: resultatet från VM", vjust = 4.5, size = 5.5, colour = "red") +
annotate("text", x = 6, y = Inf, label="Blå: Poissonfördelning", vjust = 6.5, size = 5.5, colour="blue") +
geom_bar(aes(x = xPoisson, y = sannPoisson), stat='identity', colour='black', fill = 'blue', width=0.3, alpha=0.30)
2. Antal mål per 4-minuters intervall
Tidpunkten för varje mål finns i datamängden och grafen är antal mål fördelade i 4-minutersintervaller. Detta blir då en ny Poissonprocess:
# -------------------------------------------- Nedan graf för antal mål i 4-min perioder
cellbredd <- 4 # Mniuter
cellnummer <- ceiling(Målminut/cellbredd)
cell_lista <- data.frame(cellnummer)
listaTidslot <- cell_lista %>% # Beräknar antal mål per tidslot.
count(cellnummer) %>%
group_by(cellnummer) %>%
mutate()
listaTidslot <- na.omit(listaTidslot)
listaTidslot <- subset(listaTidslot, listaTidslot$cellnummer < 25) # Tar bort celler över 25.
tidSlot <- listaTidslot$cellnummer
antMålperSlot <- listaTidslot$n
#lambda <- totlambda/96 * cellbredd
lamEst <- mean(antMålperSlot)
minAntMål <- min(antMålperSlot)
maxAntMål <- max(antMålperSlot)
tv <- round(ppois(minAntMål, lamEst), 3)
th <- round(ppois(maxAntMål, lamEst, lower.tail = FALSE), 3)
minLabel <- paste0("Sannolikhet under linjen: ", tv)
maxLabel <- paste0("Sannolikhet över linjen: ", th)
målPerTid <- ggplot() + ylim(0, NA) + geom_hline(yintercept=minAntMål) + geom_hline(yintercept=maxAntMål) +
geom_label(x=5, y=minAntMål, aes(label = minLabel)) + ylab("Antal mål") + xlab("Tidsintervall om 4 minuter") +
geom_label(x=5, y=maxAntMål, aes(label = maxLabel)) +
geom_point(aes(tidSlot, antMålperSlot), size=2, col="red") + geom_line(aes(tidSlot, antMålperSlot), linewidth=0.5, col="blue")
målPerTid
3. Tid till matchens första mål
En större svensk dagstidning uppmärksammar varje år tiden till allsvenskans första mål (se https://ovn.ing-stat.se [OE4]). Histogrammet nedan visar samma sak för VMs alla matcher. Här är mätningen tiden fram till första händelsen dvs första målet i matchen. Tidsdata brukar modelleras med den s.k. Weibullfördelningen och som anges med den blå kurvan. (Se https://ovn.ing-stat.se [OE7-OE10], [OE16], [7], [53]):
# ------------------------------------------------------- Grafer 'första målet'
library(weibullness)
library(MASS)
matchOchMål <- VM2026[, c('Match', 'Minut')]
matchOchMål <- na.omit(matchOchMål)
Mål_1 <- matchOchMål %>%
group_by(Match) %>%
summarize(min(Minut))
första <- as.vector(Mål_1[['min(Minut)']]) # Tid för första målet som numersik kolumn (vector).
arg <- fitdistr(första, "weibull") # Beräknar parameterskattningar.
shape <-arg$estimate[1] #
scale <-arg$estimate[2]
forstamal <- data.frame(första)
medel <- mean(första)
hist <- ggplot(forstamal, aes(x = första)) + geom_histogram(binwidth=6, color='black', alpha = 0.5,
boundary = 0, fill = "yellow", position = 'identity', aes(y = after_stat(density)))
hist <- hist + geom_function(fun=dweibull, linewidth=1.5, color='blue', args=list(shape, scale))
hist <- hist + labs(x = "Minuter till första mål i VM-matcher 2026", y = " ")
hist <- hist + theme(axis.text.x = element_text(size = 15), axis.text.y = element_text(size = 15),
axis.title.x = element_text(size = 15), axis.title.y = element_text(size = 15))
hist <- hist + geom_point(x = medel, y = 0, shape = 17, colour = "red", size = 5.5)
hist <- hist + annotate("text", x = -Inf, y = Inf, label="Tid till VM-matchers första mål", vjust = 2.8,
hjust = -0.80, size = 5.5, colour = "blue" )
hist
4. ‘Probability plot’ för tid fram till första mål
Diagrammet nedan är en avbildning av Weibullfördelningens s.k. fördelningsfunktion men för läsbarhetens skull har X- och Y-axlarna manipulerats så att den teoretiska funktionen blir en rät linje i diagrammet. Punkterna är datamängden och dessa överensstämmer bra med den teoretiska fördelningen. Observera att Y-skalan är annorlunda nederst till vänster vilket gör att punkterna tycks avvika mer än övriga:
(I mer seriösa sammanhang ger kunskap om tidsmodellen många möjligheter: man kan använda den för att prediktera eller för att se om påstådda förändringar ger kortare leveranstider, längre tid mellan reklamationer, kortare kötider, etc i stället för att gissa eller lyssna på den som talar högst.)
# -------------------------------------------------------------------------------------
# Weibull probability plot
n <- length(första) # Antal data.
data <- första
data <- sort(data)
data <- data + runif(n, 0, 1)
i <- c(1:n)
y <- i/n # Simulerad F(x)
logY <- -log10(1-y) # Log Y-axel.
ticks <- c(0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95)
tickstext <- as.character(100*ticks)
ticks <- -log10(1-ticks)
para <- fitdistr(data, "weibull")
form <- paste("Shape: ", round(para$estimate[1], 2))
skala <- paste("Scale: ", round(para$estimate[2], 2))
min <- min(data)
pmin <- pweibull(min, para$estimate[1], para$estimate[2])
max <- max(data)
pmax <- pweibull(max, para$estimate[1], para$estimate[2])
VMlabel <- "Tid till VM-matchers första mål"
Weibgraf <- ggplot() + xlab("Tid till VM-matchers första mål (min)") +
geom_point(aes(data, logY), size = 2, col="red") + scale_x_log10() + scale_y_log10(breaks=ticks, labels=tickstext) + ylab("Procent") +
annotate("text", x = Inf, y = Inf, label="Weibull - a probability plot", hjust = 1.7, vjust = 1.8, fontface = "bold", color = "blue", size = 5) +
annotate("text", x = Inf, y = Inf, label=form, hjust = 3.95, color = "red", size = 4.5, vjust = 4.5) +
annotate("text", x = Inf, y = Inf, label=skala, hjust = 3.85, color = "red", size = 4.5, vjust = 6.5) +
annotate("text", x = 8, y = 0.5, label=VMlabel, color = "blue", size = 5.) +
geom_segment(aes(x = min, y = -log10(1-pmin), xend = max, yend = -log10(1-pmax)), color = "grey")
Weibgraf
5. Teoretisk fördelning för skillnad mellan antal mål
Summan av flera Poissonprocesser är också en Poissonprocess. T.ex.
kundströmmar från olika håll, reklamationsärenden från olika
kundcentrum, etc. Men för skillnaden mellan två Poissonprocesser
finns ingen teoretisk motsvarighet.
För att hitta den teoretiska
fördelningen för skillnaden i antal mål per match måste man därför utgå
ifrån grunderna. Det räcker dock med kunskap från grundskolans
högstadium – enkel matematik och grunder i sannolikhet – för att göra
detta:
# ------------------------------------------------------------- Skillnad i mål
hemma <- rep.int(c(0:10), 11)
borta <- rep(0:10, each = 11)
diff <- hemma-borta
absDiff <- abs(diff)
lam <- totlambda
pH <- round(dpois(hemma, lam), 4)
pB <- round(dpois(borta, lam), 4)
pDiff <- round(pH*pB, 4)
HB <- data.frame(hemma, borta, diff, absDiff, pH, pB, pDiff)
distr <- HB %>%
group_by(diff) %>%
summarize(sum(pDiff))
xvarde <- distr$diff
yvarde <- distr$'sum(pDiff)'
Label1 <- "Teoretisk sannolikhetsfördelning"
Label2 <- "Målskillnad mellan lagen"
diaDiff <- ggplot() +
scale_x_continuous(breaks = xvarde) + xlab('Målskillnad mellan lagen, fotbollsmatcher VM 2026') + ylab('Sannolikhet') +
theme(legend.position="none") + geom_text(x=-5, y=0.17, aes(label = Label1), color="blue", size=4.5) +
geom_text(x=-5, y=0.16, aes(label = Label2), color="red", size=4) +
geom_bar(aes(x = xvarde, y = yvarde), stat='identity', colour='black', fill = 'blue', width=0.5, alpha=0.30)
diaDiff
6. Absolut skillnad i antal mål - utfall och teoretisk fördelning
Under punkt 5 ovan finns den teoretiska fördelningen för skillnad i antal. Denna skillnad kan ju vara både negativ och positiv. Om man i stället beräknar absolut skillnad, dvs alla värden är då positiva, erhålles den teoretiska fördelningen nedan, i blått. Utfallet från VMs matcher visas i den röda fördelningen med god överensstämmelse:
# ------------------------------------------------------------- Absolut skillnad i mål
antMål <- HB %>%
group_by(absDiff) %>%
summarize(sum(pDiff))
xvarde <- antMål$absDiff
yvarde <- antMål$'sum(pDiff)'
absMål <- data.frame(match = VM2026$Match, HB = VM2026$H.B, VM2026$Minut)
antalNoll <- sum(is.na(VM2026$Minut))
absMål <- na.omit(absMål) # Tar bort 'missing values' ur data.
a <- ifelse(absMål$HB == 'H', 1, -1)
absMål <- data.frame(absMål, a)
Mål00 <- absMål %>% # Målskillnad (absolut)
group_by(match) %>%
summarize(diffAntMål = abs(sum(a)))
res00 <- Mål00 %>% # Antal matcher med 0, 1, 2,... mål.
count(diffAntMål) %>%
group_by(diffAntMål) %>%
mutate()
#res00$n[1] <- res00$n[1] + antalNoll # Adderar antal oavgjorda.
Label3 <- "Absolut målskillnad mellan lagen"
Label4 <- "Resultat från VM-matcher"
ggplot() +
geom_bar(aes(x = res00$diffAntMål, y = res00$n/sum(res00$n)), stat = 'identity', width = 0.6, alpha = 0.40, fill = 'red') +
scale_x_continuous(breaks = res00$diffAntMål) + xlab('Skillnad antal mål i fotboll') + ylab('Sannolikhet') + theme(legend.position="none") +
annotate("text", x = 6, y = Inf, label=Label3, vjust = 4.5, size = 4.5, colour = "blue") +
annotate("text", x = 6, y = Inf, label=Label4, vjust = 6.9, size = 4.0, colour="red") +
geom_bar(aes(x = xvarde, y = yvarde), stat='identity', colour='black', fill = 'blue', width=0.3, alpha=0.30)
(The analyses have been performed by the ‘R’-software and with the graphical interface ‘R’-studio. Both these are available on the net. See https://www.indstat.se and the button [Statistikprogram - R] for installation.)
(See https://ovn.ing-stat.se for many more simulations and animations.)