UFCData 0.7.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ufcdata-0.7.0 → ufcdata-0.7.1}/PKG-INFO +1 -1
- {ufcdata-0.7.0 → ufcdata-0.7.1}/README.md +196 -38
- {ufcdata-0.7.0 → ufcdata-0.7.1}/UFCData.egg-info/PKG-INFO +1 -1
- {ufcdata-0.7.0 → ufcdata-0.7.1}/pyproject.toml +1 -1
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/fighter.py +1 -1
- {ufcdata-0.7.0 → ufcdata-0.7.1}/LICENSE.md +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/UFCData.egg-info/SOURCES.txt +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/UFCData.egg-info/dependency_links.txt +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/UFCData.egg-info/requires.txt +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/UFCData.egg-info/top_level.txt +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/setup.cfg +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/__init__.py +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/data.py +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/evaluation.py +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/helper.py +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/ratings.py +0 -0
- {ufcdata-0.7.0 → ufcdata-0.7.1}/ufcdata/search.py +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# UFCData
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
An open-source Python package for accessing, manipulating, and analyzing UFC data.
|
|
4
5
|
|
|
5
6
|
https://pypi.org/project/UFCData/
|
|
6
7
|
|
|
7
8
|
## Table of Contents
|
|
8
9
|
|
|
10
|
+
|
|
11
|
+
|
|
9
12
|
- [Installation](#installation)
|
|
10
13
|
- [Updating the Package](#updating-the-package)
|
|
11
14
|
|
|
@@ -30,7 +33,7 @@ Model Evaluation
|
|
|
30
33
|
- [Train Test Split](#train-test-split)
|
|
31
34
|
- [Get Expanding Window](#get-expanding-window)
|
|
32
35
|
- [Plotting Accuracy](#plotting-accuracy)
|
|
33
|
-
- [Expanding Window Baseline Example](#expanding-window-
|
|
36
|
+
- [Expanding Window Baseline Example](#expanding-window-baseline-example)
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
Data Sources
|
|
@@ -38,6 +41,12 @@ Data Sources
|
|
|
38
41
|
- [Discrepancies in Data](#discrepancies-in-data)
|
|
39
42
|
- [Update Frequency](#update-frequency)
|
|
40
43
|
|
|
44
|
+
<br>
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<br>
|
|
49
|
+
|
|
41
50
|
## Installation
|
|
42
51
|
|
|
43
52
|
```bash
|
|
@@ -50,18 +59,23 @@ pip install ufcdata
|
|
|
50
59
|
pip install --no-cache-dir --upgrade ufcdata
|
|
51
60
|
```
|
|
52
61
|
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
<br>
|
|
65
|
+
|
|
53
66
|
## Loading Data
|
|
54
67
|
|
|
55
68
|
```python
|
|
56
69
|
import ufcdata as ufc
|
|
57
70
|
data = ufc.get_data()
|
|
58
71
|
|
|
59
|
-
## Obtain fighter_bio dataframe
|
|
60
72
|
fighter_bio = data["fighter_bio"].copy()
|
|
61
73
|
```
|
|
62
74
|
|
|
63
75
|
Note that data in here and other functions below reference data that is available prior to the data.
|
|
64
76
|
|
|
77
|
+
<br>
|
|
78
|
+
|
|
65
79
|
## Search
|
|
66
80
|
|
|
67
81
|
Due to various event naming conventions and fighters sharing the same name, the primary keys for the dataframes are links, which can be difficult for humans to interpret. The `search()` function uses fuzzy string matching to make it easier to find fighters, events, and other records.
|
|
@@ -77,28 +91,19 @@ search(data, column, query, matches=1)
|
|
|
77
91
|
* `query` — The name or text you are searching for.
|
|
78
92
|
* `matches` — The number of closest matches to return. Defaults to `1`.
|
|
79
93
|
|
|
94
|
+
### Returns
|
|
95
|
+
The results are returned as a dataframe, with the closest match appearing first.
|
|
96
|
+
|
|
80
97
|
### Example
|
|
81
98
|
|
|
82
99
|
```python
|
|
83
100
|
john_jones = ufc.search(fighter_bio, "Name", "Jon Jones", 3)
|
|
84
101
|
```
|
|
85
102
|
|
|
86
|
-
This searches the `"Name"` column of the `fighter_bio` dataframe for the three closest matches to `"Jon Jones"`.
|
|
87
103
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### Returns
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```text
|
|
94
|
-
Name Nickname Height Weight Reach Stance Total W Total L Fighter Link
|
|
95
|
-
Jon Jones Bones 76.0 248.0 84.0 Orthodox 28 1 ufcstats.com/...
|
|
96
|
-
Roshaun Jones NaN 68.0 135.0 NaN NaN 2 6 ufcstats.com/...
|
|
97
|
-
Mason Jones The Dragon 70.0 155.0 74.0 Orthodox 18 2 ufcstats.com/...
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
This is useful when the exact value stored in the dataset is unknown or when there are multiple similar names. The same function can be used with any dataframe and column containing searchable text.
|
|
104
|
+
---
|
|
101
105
|
|
|
106
|
+
<br>
|
|
102
107
|
|
|
103
108
|
## Elo
|
|
104
109
|
|
|
@@ -123,8 +128,8 @@ ufc.get_elo(fight_df, fighter_df, r=1500, k=30, s=400)
|
|
|
123
128
|
|
|
124
129
|
The function returns a tuple containing:
|
|
125
130
|
|
|
126
|
-
1. `
|
|
127
|
-
2. `
|
|
131
|
+
1. `current_elo` — A dictionary mapping each fighter's UFCStats link to their final Elo rating.
|
|
132
|
+
2. `past_elo` — The original `fights_df` dataframe with `"Fighter 1 Elo"` and `"Fighter 2 Elo"` columns added. These columns contain each fighter's Elo rating immediately prior to the corresponding fight.
|
|
128
133
|
|
|
129
134
|
|
|
130
135
|
### Example
|
|
@@ -143,13 +148,17 @@ current_elo, past_elo = ufc.get_elo(
|
|
|
143
148
|
)
|
|
144
149
|
```
|
|
145
150
|
|
|
146
|
-
The resulting `fight_elo` dataframe can then be used to examine or incorporate pre-fight Elo ratings into analysis and predictive models.
|
|
147
|
-
|
|
148
151
|
|
|
149
152
|
### Important
|
|
150
153
|
|
|
151
154
|
`fight_df` should be sorted from most recent to least recent before being passed to `get_elo()`. The function reverses the dataframe internally to process fights chronologically and returns the resulting data in the original order.
|
|
152
155
|
|
|
156
|
+
<br>
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
<br>
|
|
161
|
+
|
|
153
162
|
## Fighter History
|
|
154
163
|
|
|
155
164
|
The function `get_fighter_history()` retrieves all UFC fights for a specific fighter and formats the results from the fighter's perspective. The requested fighter is always represented as `"Fighter 1"`, regardless of which side of the original fight dataframe they appeared on.
|
|
@@ -198,6 +207,8 @@ jon_jones_history = ufc.get_fighter_history(
|
|
|
198
207
|
|
|
199
208
|
This function is useful for analyzing an individual fighter's career, constructing fighter-level features, or preparing historical data for predictive modeling.
|
|
200
209
|
|
|
210
|
+
<br>
|
|
211
|
+
|
|
201
212
|
## Fighter Statistic
|
|
202
213
|
|
|
203
214
|
|
|
@@ -235,11 +246,7 @@ get_fighter_statistic(
|
|
|
235
246
|
|
|
236
247
|
### Returns
|
|
237
248
|
|
|
238
|
-
The function returns
|
|
239
|
-
|
|
240
|
-
```python
|
|
241
|
-
current_statistic, past_statistic = get_fighter_statistic(...)
|
|
242
|
-
```
|
|
249
|
+
The function returns a tuple containing:
|
|
243
250
|
|
|
244
251
|
#### `current_statistic`
|
|
245
252
|
|
|
@@ -303,9 +310,13 @@ jon_jones_current, jon_jones_past = ufc.get_fighter_statistic(
|
|
|
303
310
|
fights_df,
|
|
304
311
|
rounds_df
|
|
305
312
|
)
|
|
306
|
-
|
|
307
|
-
jon_jones_current
|
|
308
313
|
```
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
<br>
|
|
319
|
+
|
|
309
320
|
## Odds Convert
|
|
310
321
|
|
|
311
322
|
The function `convert_odds()` converts American betting odds from strings into integers or floating point numbers.
|
|
@@ -344,10 +355,18 @@ data = ufc.get_data()
|
|
|
344
355
|
|
|
345
356
|
fights_df = data["past_fights"].copy()
|
|
346
357
|
|
|
347
|
-
fights_df["Fighter 1 Odds"] = fights_df["Fighter 1 Odds"].apply(
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
fights_df["Fighter 1 Odds"] = fights_df["Fighter 1 Odds"].apply(
|
|
359
|
+
ufc.convert_odds
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
fights_df["Fighter 1 Probability"] = fights_df["Fighter 1 Odds"].apply(
|
|
363
|
+
ufc.convert_odds,
|
|
364
|
+
probability=True
|
|
365
|
+
)
|
|
350
366
|
```
|
|
367
|
+
|
|
368
|
+
<br>
|
|
369
|
+
|
|
351
370
|
## Gender Convert
|
|
352
371
|
|
|
353
372
|
The function `convert_gender()` converts gender labels into binary numeric values, with `"Male"` represented as `1` and `"Female"` represented as `0`.
|
|
@@ -375,9 +394,10 @@ data = ufc.get_data()
|
|
|
375
394
|
|
|
376
395
|
fights_df = data["past_fights"].copy()
|
|
377
396
|
fights_df["Gender"] = fights_df["Gender"].apply(ufc.convert_gender)
|
|
378
|
-
fights_df
|
|
379
397
|
```
|
|
380
398
|
|
|
399
|
+
<br>
|
|
400
|
+
|
|
381
401
|
## Weight Convert
|
|
382
402
|
|
|
383
403
|
The function `convert_weight()` converts UFC weight class labels into their corresponding weight limits in pounds.
|
|
@@ -418,10 +438,10 @@ data = ufc.get_data()
|
|
|
418
438
|
|
|
419
439
|
past_fights = data["past_fights"].copy()
|
|
420
440
|
past_fights["Weight Class"] = past_fights["Weight Class"].apply(ufc.convert_weight)
|
|
421
|
-
|
|
422
|
-
past_fights
|
|
423
441
|
```
|
|
424
442
|
|
|
443
|
+
<br>
|
|
444
|
+
|
|
425
445
|
## Mirror
|
|
426
446
|
|
|
427
447
|
The function `mirror()` creates mirrored versions of a DataFrame by swapping corresponding pairs of columns. This is useful when analyzing UFC fights from both fighters' perspectives.
|
|
@@ -453,7 +473,16 @@ import ufcdata as ufc
|
|
|
453
473
|
data = ufc.get_data()
|
|
454
474
|
|
|
455
475
|
fights_df = data["past_fights"].copy()
|
|
456
|
-
fights_df = fights_df[
|
|
476
|
+
fights_df = fights_df[
|
|
477
|
+
[
|
|
478
|
+
"Date",
|
|
479
|
+
"Event Link",
|
|
480
|
+
"Fighter 1",
|
|
481
|
+
"Fighter 1 Odds",
|
|
482
|
+
"Fighter 2",
|
|
483
|
+
"Fighter 2 Odds",
|
|
484
|
+
]
|
|
485
|
+
]
|
|
457
486
|
|
|
458
487
|
cols_1 = [
|
|
459
488
|
"Fighter 1",
|
|
@@ -470,8 +499,6 @@ mirrored_fights = ufc.mirror(
|
|
|
470
499
|
cols_1,
|
|
471
500
|
cols_2
|
|
472
501
|
)
|
|
473
|
-
|
|
474
|
-
mirrored_fights
|
|
475
502
|
```
|
|
476
503
|
|
|
477
504
|
Using `in_order=True` produces rows in the following order:
|
|
@@ -483,7 +510,7 @@ Original Fight 2
|
|
|
483
510
|
Mirrored Fight 2
|
|
484
511
|
...
|
|
485
512
|
```
|
|
486
|
-
Using `in_order=
|
|
513
|
+
Using `in_order=False` produces rows in the following order:
|
|
487
514
|
```text
|
|
488
515
|
Original Fight 1
|
|
489
516
|
Original Fight 2
|
|
@@ -494,6 +521,12 @@ Mirrored Fight 2
|
|
|
494
521
|
|
|
495
522
|
This is useful when creating fighter-level datasets where each fight should be represented from both fighters' perspectives.
|
|
496
523
|
|
|
524
|
+
<br>
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
<br>
|
|
529
|
+
|
|
497
530
|
## Train Test Split
|
|
498
531
|
The function `train_test_split()` splits a UFC fight DataFrame into training and test sets while preserving the chronological order of the data. Unlike `sklearn.model_selection.train_test_split()`, this function does not randomly shuffle the data.
|
|
499
532
|
|
|
@@ -518,9 +551,10 @@ data = ufc.get_data()
|
|
|
518
551
|
|
|
519
552
|
past_fights = data["past_fights"].copy()
|
|
520
553
|
train, test = ufc.train_test_split(past_fights, 0.2)
|
|
521
|
-
train
|
|
522
554
|
```
|
|
523
555
|
|
|
556
|
+
<br>
|
|
557
|
+
|
|
524
558
|
## Get Expanding Window
|
|
525
559
|
The function `get_expanding_window()` creates multiple training and test sets for time-series cross-validation using an expanding training window.
|
|
526
560
|
|
|
@@ -567,6 +601,7 @@ train, test = ufc.train_test_split(past_fights, 0.2)
|
|
|
567
601
|
folds = ufc.get_expanding_window(train, 10, 500)
|
|
568
602
|
folds['Fold 2']['test']
|
|
569
603
|
```
|
|
604
|
+
<br>
|
|
570
605
|
|
|
571
606
|
## Plotting Accuracy
|
|
572
607
|
|
|
@@ -593,8 +628,127 @@ plot_test(accuracy, title="Test Accuracy")
|
|
|
593
628
|
* `accuracy` — The accuracy.
|
|
594
629
|
* `title` — The title of the plot.
|
|
595
630
|
|
|
631
|
+
<br>
|
|
632
|
+
|
|
596
633
|
## Expanding Window Baseline Example
|
|
597
634
|
|
|
635
|
+
In this example, we split the data into 80% for cross-validation and 20% for final testing. The cross-validation set is evaluated using a 15-fold expanding window approach. Using only betting odds, we convert the odds into implied probabilities and predict the fighter with the higher probability as the winner. The example also demonstrates how the model can be deployed.
|
|
636
|
+
|
|
637
|
+
<br>
|
|
638
|
+
|
|
639
|
+
```python
|
|
640
|
+
import ufcdata as ufc
|
|
641
|
+
import numpy as np
|
|
642
|
+
|
|
643
|
+
data = ufc.get_data()
|
|
644
|
+
|
|
645
|
+
## Select rows where outcome is W or L and both fighters have odds associated with the fights.
|
|
646
|
+
past_fights = data["past_fights"].copy()
|
|
647
|
+
past_fights = past_fights[~past_fights["Fighter 1 Outcome"].isin(["NC", "D"])]
|
|
648
|
+
past_fights = past_fights[past_fights[["Fighter 1 Odds", "Fighter 2 Odds"]].notna().all(axis=1)]
|
|
649
|
+
|
|
650
|
+
## Convert odds to probabilities
|
|
651
|
+
past_fights["Fighter 1 Probability"] = (
|
|
652
|
+
past_fights["Fighter 1 Odds"]
|
|
653
|
+
.apply(ufc.convert_odds, probability=True)
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
past_fights["Fighter 2 Probability"] = (
|
|
657
|
+
past_fights["Fighter 2 Odds"]
|
|
658
|
+
.apply(ufc.convert_odds, probability=True)
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
## Create the data for cross validation and final testing
|
|
662
|
+
folds = 15
|
|
663
|
+
fold_names = [f"Fold {i}" for i in range(1, folds + 1)]
|
|
664
|
+
cross_validation, final_test = ufc.train_test_split(past_fights, 0.2)
|
|
665
|
+
fifteen_fold_cv = ufc.get_expanding_window(cross_validation, folds, 500)
|
|
666
|
+
|
|
667
|
+
fold_accuracy = []
|
|
668
|
+
## Loop through each fold to get the accuracy
|
|
669
|
+
for fold in fold_names:
|
|
670
|
+
test = fifteen_fold_cv[fold]['test']
|
|
671
|
+
test["Predicted Winner"] = np.where(
|
|
672
|
+
test["Fighter 1 Probability"] > test["Fighter 2 Probability"],
|
|
673
|
+
test["Fighter 1 Link"],
|
|
674
|
+
test["Fighter 2 Link"])
|
|
675
|
+
accuracy = float((test["Predicted Winner"] == test["Winner"]).mean())
|
|
676
|
+
fold_accuracy.append(accuracy)
|
|
677
|
+
|
|
678
|
+
## Plot cross validation accuracy
|
|
679
|
+
ufc.plot_cv(fold_names, fold_accuracy)
|
|
680
|
+
```
|
|
681
|
+
<br>
|
|
682
|
+
|
|
683
|
+

|
|
684
|
+
|
|
685
|
+
<br>
|
|
686
|
+
|
|
687
|
+
```python
|
|
688
|
+
## Evaluate on final test set
|
|
689
|
+
final_test["Predicted Winner"] = np.where(
|
|
690
|
+
final_test["Fighter 1 Probability"] > final_test["Fighter 2 Probability"],
|
|
691
|
+
final_test["Fighter 1 Link"],
|
|
692
|
+
final_test["Fighter 2 Link"])
|
|
693
|
+
test_accuracy = float((final_test["Predicted Winner"] == final_test["Winner"]).mean())
|
|
694
|
+
ufc.plot_test(test_accuracy)
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
<br>
|
|
698
|
+
|
|
699
|
+

|
|
700
|
+
|
|
701
|
+
<br>
|
|
702
|
+
|
|
703
|
+
```python
|
|
704
|
+
## Deploy the model
|
|
705
|
+
future_fights = data["future_fights"].copy()
|
|
706
|
+
|
|
707
|
+
future_fights["Fighter 1 Probability"] = (
|
|
708
|
+
future_fights["Fighter 1 Odds"]
|
|
709
|
+
.apply(ufc.convert_odds, probability=True)
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
future_fights["Fighter 2 Probability"] = (
|
|
713
|
+
future_fights["Fighter 2 Odds"]
|
|
714
|
+
.apply(ufc.convert_odds, probability=True)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
future_fights["Predicted Winner"] = np.where(
|
|
718
|
+
future_fights["Fighter 1 Probability"] > future_fights["Fighter 2 Probability"],
|
|
719
|
+
future_fights["Fighter 1 Link"],
|
|
720
|
+
future_fights["Fighter 2 Link"])
|
|
721
|
+
|
|
722
|
+
future_fights["Predicted Winner"] = np.where(
|
|
723
|
+
future_fights["Predicted Winner"] == future_fights["Fighter 1 Link"],
|
|
724
|
+
future_fights["Fighter 1"],
|
|
725
|
+
future_fights["Fighter 2"]
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
future_fights = future_fights[
|
|
729
|
+
[
|
|
730
|
+
"Date",
|
|
731
|
+
"Event Link",
|
|
732
|
+
"Fight Link",
|
|
733
|
+
"Fighter 1",
|
|
734
|
+
"Fighter 2",
|
|
735
|
+
"Predicted Winner"
|
|
736
|
+
]
|
|
737
|
+
]
|
|
738
|
+
|
|
739
|
+
future_fights
|
|
740
|
+
|
|
741
|
+
```
|
|
742
|
+
<br>
|
|
743
|
+
|
|
744
|
+

|
|
745
|
+
|
|
746
|
+
<br>
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
<br>
|
|
751
|
+
|
|
598
752
|
## Online Sources
|
|
599
753
|
|
|
600
754
|
Odds and birthplace data was obtained from https://www.tapology.com
|
|
@@ -603,12 +757,16 @@ Venue and attendance data was obtained from https://en.wikipedia.org/wiki/List_o
|
|
|
603
757
|
|
|
604
758
|
All other data was obtained from http://ufcstats.com
|
|
605
759
|
|
|
760
|
+
<br>
|
|
761
|
+
|
|
606
762
|
## Discrepancies in Data
|
|
607
763
|
|
|
608
764
|
UFCStats is treated as the authoritative source for UFCData. When discrepancies exist between UFCStats and other sources, such as Wikipedia or Tapology, the UFCStats data is used.
|
|
609
765
|
|
|
610
766
|
The UFCStats completed events page serves as the authoritative source for event, fight, and round data. Individual fighter profiles may contain fights from organizations or events that are not included in the completed events database, including WEC, Strikeforce, and PRIDE. These events are therefore excluded from UFCData.
|
|
611
767
|
|
|
768
|
+
<br>
|
|
769
|
+
|
|
612
770
|
## Update Frequency
|
|
613
771
|
|
|
614
772
|
The dataset is updated at the start of the scheduled broadcast time for each event. This update captures changes to betting odds, as well as any cancelled, postponed, or otherwise modified fights.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|