Project 1

Author

Youssef M

This project analyzes a dataset of MMA fight decisions to understand how judges score fights. In MMA, three judges evaluate each fight and assign scores to each fighter, but their decisions do not always agree. The dataset includes variables such as the score margins given by each judge, the number of rounds in the fight and the type of decision(unanimous, split or majority). It also uncludes information about which fighter won and how much the judges scores differed. The goal of this project is to explore how factors like score differences, fight length and decision type influence disagreement between judges.

Unanimous decision: All judges agree

Split decision: judges disagree

Majortity decision: two agree, one disagree

Source: ESPN

library(tidyverse)
Warning: package 'ggplot2' was built under R version 4.5.2
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.1     ✔ stringr   1.5.2
✔ ggplot2   4.0.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggfortify)
Warning: package 'ggfortify' was built under R version 4.5.2
mma <- read_csv("mma_decisions.csv")
Rows: 5000 Columns: 32
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (18): event, arena, city, fighter1, fighter2, result_type, judge1, judg...
dbl  (13): judge1_score1, judge1_score2, judge2_score1, judge2_score2, judge...
date  (1): date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(mma)
# A tibble: 6 × 32
  date       event              arena city  fighter1 fighter2 result_type judge1
  <date>     <chr>              <chr> <chr> <chr>    <chr>    <chr>       <chr> 
1 2001-05-04 UFC 31: Locked & … Trum… Atla… DeSouza  Berger   Unanimous   Anduj…
2 2001-06-29 UFC 32: Showdown … Cont… East… Kondo    Matyush… Unanimous   Anduj…
3 2001-09-28 UFC 33: Victory i… Mand… Las … Matyush… Ortiz    Unanimous   Crosby
4 2001-09-28 UFC 33: Victory i… Mand… Las … Hallman  Pulver   Unanimous   Crosby
5 2001-09-28 UFC 33: Victory i… Mand… Las … Bustama… Liddell  Unanimous   Crosby
6 2001-09-28 UFC 33: Victory i… Mand… Las … Iha      Thomas   Unanimous   Mullen
# ℹ 24 more variables: judge1_score1 <dbl>, judge1_score2 <dbl>, judge2 <chr>,
#   judge2_score1 <dbl>, judge2_score2 <dbl>, judge3 <chr>,
#   judge3_score1 <dbl>, judge3_score2 <dbl>, winner <chr>, winner2 <chr>,
#   judge1_margin <dbl>, judge2_margin <dbl>, judge3_margin <dbl>,
#   judge1_perc <chr>, judge2_perc <chr>, judge3_perc <chr>, rounds <dbl>,
#   judge1_dev <dbl>, judge2_dev <dbl>, judge3_dev <dbl>, judge1_out <chr>,
#   judge2_out <chr>, judge3_out <chr>, agreement <chr>
summary(mma)
      date               event              arena               city          
 Min.   :2001-05-04   Length:5000        Length:5000        Length:5000       
 1st Qu.:2012-12-07   Class :character   Class :character   Class :character  
 Median :2016-01-30   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2015-09-26                                                           
 3rd Qu.:2019-03-23                                                           
 Max.   :2021-12-18                                                           
   fighter1           fighter2         result_type           judge1         
 Length:5000        Length:5000        Length:5000        Length:5000       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
 judge1_score1    judge1_score2       judge2          judge2_score1   
 Min.   : 24.00   Min.   : 24.00   Length:5000        Min.   : 24.00  
 1st Qu.: 28.00   1st Qu.: 28.00   Class :character   1st Qu.: 28.00  
 Median : 29.00   Median : 29.00   Mode  :character   Median : 29.00  
 Mean   : 30.14   Mean   : 30.18                      Mean   : 30.16  
 3rd Qu.: 30.00   3rd Qu.: 30.00                      3rd Qu.: 30.00  
 Max.   :119.00   Max.   :119.00                      Max.   :116.00  
 judge2_score2       judge3          judge3_score1    judge3_score2   
 Min.   : 23.00   Length:5000        Min.   : 24.00   Min.   : 23.00  
 1st Qu.: 28.00   Class :character   1st Qu.: 28.00   1st Qu.: 28.00  
 Median : 29.00   Mode  :character   Median : 29.00   Median : 29.00  
 Mean   : 30.17                      Mean   : 30.16   Mean   : 30.18  
 3rd Qu.: 30.00                      3rd Qu.: 30.00   3rd Qu.: 30.00  
 Max.   :118.00                      Max.   :117.00   Max.   :119.00  
    winner            winner2          judge1_margin      judge2_margin    
 Length:5000        Length:5000        Min.   :-10.0000   Min.   :-8.0000  
 Class :character   Class :character   1st Qu.: -2.0000   1st Qu.:-2.0000  
 Mode  :character   Mode  :character   Median :  0.0000   Median : 0.0000  
                                       Mean   : -0.0414   Mean   :-0.0098  
                                       3rd Qu.:  1.0000   3rd Qu.: 2.0000  
                                       Max.   : 10.0000   Max.   : 7.0000  
 judge3_margin     judge1_perc        judge2_perc        judge3_perc       
 Min.   :-10.000   Length:5000        Length:5000        Length:5000       
 1st Qu.: -2.000   Class :character   Class :character   Class :character  
 Median :  0.000   Mode  :character   Mode  :character   Mode  :character  
 Mean   : -0.025                                                           
 3rd Qu.:  2.000                                                           
 Max.   :  8.000                                                           
     rounds         judge1_dev       judge2_dev       judge3_dev   
 Min.   : 3.000   Min.   :0.0000   Min.   :0.0000   Min.   :0.000  
 1st Qu.: 3.000   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.000  
 Median : 3.000   Median :1.0000   Median :1.0000   Median :1.000  
 Mean   : 3.184   Mean   :0.8408   Mean   :0.8386   Mean   :0.875  
 3rd Qu.: 3.000   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.000  
 Max.   :12.000   Max.   :6.0000   Max.   :7.0000   Max.   :6.000  
  judge1_out         judge2_out         judge3_out         agreement        
 Length:5000        Length:5000        Length:5000        Length:5000       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
# cheking for NA's
mma_clean <- mma |>
  filter(!is.na(judge1_margin) & !is.na(judge2_margin) & !is.na(judge3_margin) & !is.na(judge1_dev) & !is.na(judge2_dev) & !is.na(judge3_dev)) 
# Create new variables for analysis
mma_clean1 <- mma_clean |>
  mutate(
    avg_dev = (judge1_dev + judge2_dev + judge3_dev)/3,
    total_margin = abs(judge1_margin) +        # here margin(difference between judges score) can be positive or negative  that's why I needed to use  the abs (I google it)
                   abs(judge2_margin) +
                   abs(judge3_margin)
  )
#creating a new variable bt grouping everything into Agree or Disagree 
mma_clean2 <- mma_clean1 |>
  mutate(agreement_simple = ifelse(agreement == "Agree", "Agree", "Disagree"))
# Removing the unnecessary variables
mma_A <- mma_clean2 |>
  select(avg_dev, total_margin, rounds, result_type, agreement_simple)
# Convert variables to factors
mma_final <- mma_A |>
  mutate(
    agreement_simple = as.factor(agreement_simple),
    result_type = as.factor(result_type),
    rounds = as.factor(rounds)
  )
head(mma_final)
# A tibble: 6 × 5
  avg_dev total_margin rounds result_type agreement_simple
    <dbl>        <dbl> <fct>  <fct>       <fct>           
1   1.33             5 3      Unanimous   Agree           
2   0.667           10 3      Unanimous   Agree           
3   0.667           19 5      Unanimous   Agree           
4   0.667            4 5      Unanimous   Agree           
5   0.667            5 3      Unanimous   Agree           
6   0.667            4 3      Unanimous   Agree           
cor(mma_final$total_margin, mma_final$avg_dev)   # cor = correlation
[1] -0.09735689
# Create a multiple linear regression model
model1 <- lm(avg_dev ~ total_margin + rounds + result_type, data = mma_final)
summary(model1)

Call:
lm(formula = avg_dev ~ total_margin + rounds + result_type, data = mma_final)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5749 -0.4253 -0.3595  0.7647  2.4457 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)           0.985924   0.053090  18.571  < 2e-16 ***
total_margin          0.035852   0.003166  11.326  < 2e-16 ***
rounds5               0.205511   0.035684   5.759 8.96e-09 ***
rounds12              1.042086   0.129738   8.032 1.18e-15 ***
result_typeSplit      0.599320   0.055987  10.705  < 2e-16 ***
result_typeUnanimous -0.668208   0.055251 -12.094  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.6249 on 4994 degrees of freedom
Multiple R-squared:  0.3891,    Adjusted R-squared:  0.3885 
F-statistic: 636.1 on 5 and 4994 DF,  p-value: < 2.2e-16
autoplot(model1, 1:4, nrow = 2, ncol = 2)
Warning: `fortify(<lm>)` was deprecated in ggplot2 4.0.0.
ℹ Please use `broom::augment(<lm>)` instead.
ℹ The deprecated feature was likely used in the ggfortify package.
  Please report the issue at <https://github.com/sinhrks/ggfortify/issues>.
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
ℹ Please use tidy evaluation idioms with `aes()`.
ℹ See also `vignette("ggplot2-in-packages")` for more information.
ℹ The deprecated feature was likely used in the ggfortify package.
  Please report the issue at <https://github.com/sinhrks/ggfortify/issues>.
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
ℹ The deprecated feature was likely used in the ggfortify package.
  Please report the issue at <https://github.com/sinhrks/ggfortify/issues>.

The model has the equation: avg_dev = 0.0359(total_margin) + 0.2055(rounds5) + 1.0421(rounds12) + 0.5993(result_typeSplit) − 0.6682(result_typeUnanimous) + 0.9859

The slope may be interpreted in the following way: For each additional unit increase in total_margin, there is a predicted increase of about 0.036 in average judge deviation, holding all the other variables constant.

model2 <- lm(avg_dev ~ total_margin + result_type, data = mma_final)
summary(model2)

Call:
lm(formula = avg_dev ~ total_margin + result_type, data = mma_final)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0667 -0.4119 -0.3699  0.7468  2.5705 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)           1.020280   0.053444   19.09   <2e-16 ***
total_margin          0.043653   0.003067   14.23   <2e-16 ***
result_typeSplit      0.552011   0.056272    9.81   <2e-16 ***
result_typeUnanimous -0.739347   0.055250  -13.38   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.6305 on 4996 degrees of freedom
Multiple R-squared:  0.3777,    Adjusted R-squared:  0.3773 
F-statistic:  1011 on 3 and 4996 DF,  p-value: < 2.2e-16
autoplot(model2, 1:4, nrow = 2, ncol = 2)

The variable rounds had the highest p-value, so it was removed in the second model. The R-squared decreased from 0.3891 to 0.3777, meaning the model explains slightly less variation without rounds. This shows that rounds still contributes to explaining judge disagreement.

ggplot(mma_final, aes(x = result_type, y = avg_dev, fill = result_type)) +
  geom_boxplot() +
  facet_wrap(~ rounds) +
  labs(
    title = "Judge Disagreement by Decision Type and Fight Length",
    x = "Result Type",
    y = "Average Judge Deviation",
    fill = "Result Type",
    caption = "Source: ESPN"
  ) +
  scale_fill_manual(values = c("darkred", "darkblue", "darkgreen")) +
  theme_minimal(base_size = 12)

unique(mma_final$rounds)
[1] 3  5  12
Levels: 3 5 12
#Working on 3 and 5 rounds
mma_plot1 <- mma_final |>
  filter(rounds %in% c("3", "5"))
ggplot(mma_plot1, aes(x = result_type, y = avg_dev, fill = result_type)) +
  geom_boxplot() +
  facet_wrap(~ rounds) +
  labs(
    title = "Judge Disagreement by Decision Type and Fight Length",
    x = "Result Type",
    y = "Average Judge Deviation",
    fill = "Result Type",
    caption = "ESPN"
  ) +
  scale_fill_manual(values = c("darkred", "darkblue", "darkgreen")) +
  theme_minimal(base_size = 12)

Reflection

The dataset was cleaned by removing rows with missing values in key variables using filter(). New variables were created using mutate(), including avg_dev to measure judge disagreement and total_margin tp represent fight closennes. The agreement variable was simplified into “Agree” and “Disagree” and only fights with 3 or 5 rounds were kept for consistency.

The boxplot shows judge disagreement across decision types and fight lengths. Split decisions have the highest disagreement, majority decisions are moderate, and unanimous decisions have the lowest. Fights with more rounds also tend to show slightly higher disagreement, suggesting that longer fights are harder to judge.

A scatterplot was considered but showed a weak relationship, so a boxplot was used instead. A limitation of this analysis is that it does not include detailed fighter performance data, which could help better explain judge disagreement.