UFCData 0.5.0__tar.gz → 0.7.0__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.5.0 → ufcdata-0.7.0}/PKG-INFO +2 -1
- {ufcdata-0.5.0 → ufcdata-0.7.0}/README.md +300 -16
- {ufcdata-0.5.0 → ufcdata-0.7.0}/UFCData.egg-info/PKG-INFO +2 -1
- {ufcdata-0.5.0 → ufcdata-0.7.0}/UFCData.egg-info/SOURCES.txt +1 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/UFCData.egg-info/requires.txt +1 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/pyproject.toml +2 -1
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/__init__.py +2 -1
- ufcdata-0.7.0/ufcdata/evaluation.py +192 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/LICENSE.md +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/UFCData.egg-info/dependency_links.txt +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/UFCData.egg-info/top_level.txt +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/setup.cfg +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/data.py +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/fighter.py +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/helper.py +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/ratings.py +0 -0
- {ufcdata-0.5.0 → ufcdata-0.7.0}/ufcdata/search.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UFCData
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Obtain UFC data and functions to manipulate it
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -9,4 +9,5 @@ Requires-Dist: pandas
|
|
|
9
9
|
Requires-Dist: numpy
|
|
10
10
|
Requires-Dist: glicko2
|
|
11
11
|
Requires-Dist: rapidfuzz
|
|
12
|
+
Requires-Dist: matplotlib
|
|
12
13
|
Dynamic: license-file
|
|
@@ -4,8 +4,6 @@ An open-source Python package for accessing, manipulating, and analyzing UFC dat
|
|
|
4
4
|
|
|
5
5
|
https://pypi.org/project/UFCData/
|
|
6
6
|
|
|
7
|
-
add helper functions like convert odds
|
|
8
|
-
|
|
9
7
|
## Table of Contents
|
|
10
8
|
|
|
11
9
|
- [Installation](#installation)
|
|
@@ -23,13 +21,17 @@ Fighter Functions
|
|
|
23
21
|
- [Fighter Statistic](#fighter-statistic)
|
|
24
22
|
|
|
25
23
|
Helper Functions
|
|
26
|
-
- [Odds Convert](#
|
|
27
|
-
- [Weight Convert](#
|
|
28
|
-
- [Gender Convert](#
|
|
29
|
-
- [Mirror](#
|
|
24
|
+
- [Odds Convert](#odds-convert)
|
|
25
|
+
- [Weight Convert](#weight-convert)
|
|
26
|
+
- [Gender Convert](#gender-convert)
|
|
27
|
+
- [Mirror](#mirror)
|
|
30
28
|
|
|
31
29
|
Model Evaluation
|
|
32
|
-
- [
|
|
30
|
+
- [Train Test Split](#train-test-split)
|
|
31
|
+
- [Get Expanding Window](#get-expanding-window)
|
|
32
|
+
- [Plotting Accuracy](#plotting-accuracy)
|
|
33
|
+
- [Expanding Window Baseline Example](#expanding-window-cv-and-final-test-example)
|
|
34
|
+
|
|
33
35
|
|
|
34
36
|
Data Sources
|
|
35
37
|
- [Online Sources](#online-sources)
|
|
@@ -56,7 +58,6 @@ data = ufc.get_data()
|
|
|
56
58
|
|
|
57
59
|
## Obtain fighter_bio dataframe
|
|
58
60
|
fighter_bio = data["fighter_bio"].copy()
|
|
59
|
-
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
Note that data in here and other functions below reference data that is available prior to the data.
|
|
@@ -101,7 +102,7 @@ This is useful when the exact value stored in the dataset is unknown or when the
|
|
|
101
102
|
|
|
102
103
|
## Elo
|
|
103
104
|
|
|
104
|
-
UFCData provides an Elo rating system for calculating fighter ratings based on their previous fight results. Ratings are calculated chronologically, with each fighter's rating recorded
|
|
105
|
+
UFCData provides an Elo rating system for calculating fighter ratings based on their previous fight results. Ratings are calculated chronologically, with each fighter's rating recorded immediately before each fight.
|
|
105
106
|
|
|
106
107
|
This allows Elo ratings to be used as features for predictive modeling without incorporating information from the fight being predicted.
|
|
107
108
|
|
|
@@ -147,7 +148,7 @@ The resulting `fight_elo` dataframe can then be used to examine or incorporate p
|
|
|
147
148
|
|
|
148
149
|
### Important
|
|
149
150
|
|
|
150
|
-
`fight_df` should be sorted from
|
|
151
|
+
`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.
|
|
151
152
|
|
|
152
153
|
## Fighter History
|
|
153
154
|
|
|
@@ -182,14 +183,12 @@ data = ufc.get_data()
|
|
|
182
183
|
fighter_bio = data["fighter_bio"].copy()
|
|
183
184
|
fights_df = data["past_fights"].copy()
|
|
184
185
|
|
|
185
|
-
# Obtain Jon Jones' UFCStats link
|
|
186
186
|
jon_jones = ufc.search(
|
|
187
187
|
fighter_bio,
|
|
188
188
|
"Name",
|
|
189
189
|
"Jon Jones"
|
|
190
190
|
).iloc[0]["Fighter Link"]
|
|
191
191
|
|
|
192
|
-
# Get Jon Jones' fight history
|
|
193
192
|
jon_jones_history = ufc.get_fighter_history(
|
|
194
193
|
jon_jones,
|
|
195
194
|
fighter_bio,
|
|
@@ -202,7 +201,7 @@ This function is useful for analyzing an individual fighter's career, constructi
|
|
|
202
201
|
## Fighter Statistic
|
|
203
202
|
|
|
204
203
|
|
|
205
|
-
The function `get_fighter_statistic()`, transforms a fighter's UFC fight history into a time-series dataset where each row represents the fighter's statistics
|
|
204
|
+
The function `get_fighter_statistic()`, transforms a fighter's UFC fight history into a time-series dataset where each row represents the fighter's statistics as they were known before a particular fight.
|
|
206
205
|
|
|
207
206
|
This is designed for machine-learning applications where using information from the future would cause data leakage.
|
|
208
207
|
|
|
@@ -266,7 +265,7 @@ This includes:
|
|
|
266
265
|
|
|
267
266
|
A DataFrame containing the same types of statistics for each previous UFC fight.
|
|
268
267
|
|
|
269
|
-
Each row represents the fighter's information
|
|
268
|
+
Each row represents the fighter's information immediately before that fight.
|
|
270
269
|
|
|
271
270
|
For example:
|
|
272
271
|
|
|
@@ -292,14 +291,12 @@ fights_df = data["past_fights"].copy()
|
|
|
292
291
|
rounds_df = data['past_rounds'].copy()
|
|
293
292
|
future_df = data['future_fights']
|
|
294
293
|
|
|
295
|
-
# Obtain Jon Jones' UFCStats link
|
|
296
294
|
jon_jones = ufc.search(
|
|
297
295
|
fighter_bio,
|
|
298
296
|
"Name",
|
|
299
297
|
"Jon Jones"
|
|
300
298
|
).iloc[0]["Fighter Link"]
|
|
301
299
|
|
|
302
|
-
# Get Jon Jones' statistic
|
|
303
300
|
jon_jones_current, jon_jones_past = ufc.get_fighter_statistic(
|
|
304
301
|
jon_jones,
|
|
305
302
|
fighter_bio,
|
|
@@ -309,7 +306,294 @@ jon_jones_current, jon_jones_past = ufc.get_fighter_statistic(
|
|
|
309
306
|
|
|
310
307
|
jon_jones_current
|
|
311
308
|
```
|
|
309
|
+
## Odds Convert
|
|
310
|
+
|
|
311
|
+
The function `convert_odds()` converts American betting odds from strings into integers or floating point numbers.
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
convert_odds(odds, probability)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Parameters
|
|
318
|
+
|
|
319
|
+
* `odds` — American betting odds as a string.
|
|
320
|
+
|
|
321
|
+
### Returns
|
|
322
|
+
|
|
323
|
+
If probability=True, it returns a `float` representing the implied probability of the betting odds.
|
|
324
|
+
|
|
325
|
+
For positive American odds:
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
Probability = 100 / (odds + 100)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
For negative American odds:
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
Probability = -odds / (-odds + 100)
|
|
335
|
+
```
|
|
336
|
+
If probability=False, takes the string and converts it to an integer.
|
|
337
|
+
|
|
338
|
+
### Example
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
import ufcdata as ufc
|
|
342
|
+
|
|
343
|
+
data = ufc.get_data()
|
|
344
|
+
|
|
345
|
+
fights_df = data["past_fights"].copy()
|
|
346
|
+
|
|
347
|
+
fights_df["Fighter 1 Odds"] = fights_df["Fighter 1 Odds"].apply(ufc.convert_odds)
|
|
348
|
+
fights_df["Fighter 1 Probability"] = fights_df["Fighter 1 Odds"].apply(ufc.convert_odds, probability=True)
|
|
349
|
+
fights_df
|
|
350
|
+
```
|
|
351
|
+
## Gender Convert
|
|
352
|
+
|
|
353
|
+
The function `convert_gender()` converts gender labels into binary numeric values, with `"Male"` represented as `1` and `"Female"` represented as `0`.
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
convert_gender(gender)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Parameters
|
|
360
|
+
|
|
361
|
+
* `gender` — Gender label as a string.
|
|
362
|
+
|
|
363
|
+
### Returns
|
|
364
|
+
|
|
365
|
+
Returns `1` for `"Male"` and `0` for `"Female"`.
|
|
366
|
+
|
|
367
|
+
If the gender is missing or does not match either `"Male"` or `"Female"`, it returns `None`.
|
|
368
|
+
|
|
369
|
+
### Example
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
import ufcdata as ufc
|
|
373
|
+
|
|
374
|
+
data = ufc.get_data()
|
|
375
|
+
|
|
376
|
+
fights_df = data["past_fights"].copy()
|
|
377
|
+
fights_df["Gender"] = fights_df["Gender"].apply(ufc.convert_gender)
|
|
378
|
+
fights_df
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Weight Convert
|
|
382
|
+
|
|
383
|
+
The function `convert_weight()` converts UFC weight class labels into their corresponding weight limits in pounds.
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
convert_weight(weight)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Parameters
|
|
390
|
+
|
|
391
|
+
* `weight` — Weight class as a string.
|
|
392
|
+
|
|
393
|
+
### Returns
|
|
394
|
+
|
|
395
|
+
Returns the weight limit in pounds for recognized weight classes.
|
|
396
|
+
|
|
397
|
+
| Weight Class | Weight (lb) |
|
|
398
|
+
| ----------------- | ----------: |
|
|
399
|
+
| Strawweight | 115 |
|
|
400
|
+
| Flyweight | 125 |
|
|
401
|
+
| Bantamweight | 135 |
|
|
402
|
+
| Featherweight | 145 |
|
|
403
|
+
| Lightweight | 155 |
|
|
404
|
+
| Welterweight | 170 |
|
|
405
|
+
| Middleweight | 185 |
|
|
406
|
+
| Light Heavyweight | 205 |
|
|
407
|
+
| Heavyweight | 265 |
|
|
408
|
+
| Super Heavyweight | 265 |
|
|
409
|
+
|
|
410
|
+
For `"Catch Weight"` and `"Open Weight"`, as well as unrecognized or missing values, the function returns `NaN`.
|
|
411
|
+
|
|
412
|
+
### Example
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
import ufcdata as ufc
|
|
416
|
+
|
|
417
|
+
data = ufc.get_data()
|
|
418
|
+
|
|
419
|
+
past_fights = data["past_fights"].copy()
|
|
420
|
+
past_fights["Weight Class"] = past_fights["Weight Class"].apply(ufc.convert_weight)
|
|
421
|
+
|
|
422
|
+
past_fights
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Mirror
|
|
426
|
+
|
|
427
|
+
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.
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
mirror(df, cols_1, cols_2, in_order=True)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Parameters
|
|
434
|
+
|
|
435
|
+
* `df` — The DataFrame to mirror.
|
|
436
|
+
* `cols_1` — List of column names representing the first fighter or side.
|
|
437
|
+
* `cols_2` — List of column names representing the second fighter or side. Each column is swapped with the corresponding column in `cols_1`.
|
|
438
|
+
* `in_order` — Determines the order of the returned rows. Defaults to `True`.
|
|
439
|
+
|
|
440
|
+
If `in_order=True`, each original row is immediately followed by its mirrored row.
|
|
441
|
+
|
|
442
|
+
If `in_order=False`, all original rows are followed by all mirrored rows.
|
|
443
|
+
|
|
444
|
+
### Returns
|
|
445
|
+
|
|
446
|
+
Returns a DataFrame containing both the original and mirrored versions of each row.
|
|
447
|
+
|
|
448
|
+
### Example
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
import ufcdata as ufc
|
|
452
|
+
|
|
453
|
+
data = ufc.get_data()
|
|
454
|
+
|
|
455
|
+
fights_df = data["past_fights"].copy()
|
|
456
|
+
fights_df = fights_df[["Date", "Event Link", "Fighter 1", "Fighter 1 Odds","Fighter 2", "Fighter 2 Odds"]]
|
|
457
|
+
|
|
458
|
+
cols_1 = [
|
|
459
|
+
"Fighter 1",
|
|
460
|
+
"Fighter 1 Odds",
|
|
461
|
+
]
|
|
462
|
+
|
|
463
|
+
cols_2 = [
|
|
464
|
+
"Fighter 2",
|
|
465
|
+
"Fighter 2 Odds",
|
|
466
|
+
]
|
|
467
|
+
|
|
468
|
+
mirrored_fights = ufc.mirror(
|
|
469
|
+
fights_df,
|
|
470
|
+
cols_1,
|
|
471
|
+
cols_2
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
mirrored_fights
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Using `in_order=True` produces rows in the following order:
|
|
478
|
+
|
|
479
|
+
```text
|
|
480
|
+
Original Fight 1
|
|
481
|
+
Mirrored Fight 1
|
|
482
|
+
Original Fight 2
|
|
483
|
+
Mirrored Fight 2
|
|
484
|
+
...
|
|
485
|
+
```
|
|
486
|
+
Using `in_order=True` produces rows in the following order:
|
|
487
|
+
```text
|
|
488
|
+
Original Fight 1
|
|
489
|
+
Original Fight 2
|
|
490
|
+
Mirrored Fight 1
|
|
491
|
+
Mirrored Fight 2
|
|
492
|
+
...
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
This is useful when creating fighter-level datasets where each fight should be represented from both fighters' perspectives.
|
|
496
|
+
|
|
497
|
+
## Train Test Split
|
|
498
|
+
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
|
+
|
|
500
|
+
The input `fights_df` should be ordered from newest to oldest. The most recent fights are assigned to the test set, while the older fights are assigned to the training set. Both returned DataFrames are then reordered from oldest to newest.
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
train_test_split(fights_df, test_size=0.2)
|
|
504
|
+
```
|
|
505
|
+
### Parameters
|
|
506
|
+
|
|
507
|
+
* `fights_df` — UFC fight DataFrame ordered from newest to oldest.
|
|
508
|
+
* `test_size` — Determines the size of the test set. If less than 1, it is interpreted as a proportion of the total number of fights. If greater than or equal to 1, it is interpreted as the exact number of fights to include in the test set. Defaults to 0.2.
|
|
509
|
+
### Returns
|
|
510
|
+
The function returns two dataFrames. The first is a dataFrame containing the older fights used for training, ordered from oldest to newest. The second is a dataFrame containing the most recent fights used for testing, ordered from oldest to newest.
|
|
511
|
+
|
|
512
|
+
### Example
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
import ufcdata as ufc
|
|
516
|
+
|
|
517
|
+
data = ufc.get_data()
|
|
518
|
+
|
|
519
|
+
past_fights = data["past_fights"].copy()
|
|
520
|
+
train, test = ufc.train_test_split(past_fights, 0.2)
|
|
521
|
+
train
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Get Expanding Window
|
|
525
|
+
The function `get_expanding_window()` creates multiple training and test sets for time-series cross-validation using an expanding training window.
|
|
526
|
+
|
|
527
|
+
The input `fights_df` should be ordered from oldest to newest. With each fold, the training set expands to include more historical fights, while the test set remains a fixed size.
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
fold_results = ufc.get_expanding_window(
|
|
531
|
+
fights_df,
|
|
532
|
+
folds=5,
|
|
533
|
+
test_size=500
|
|
534
|
+
)
|
|
535
|
+
```
|
|
536
|
+
### Parameters
|
|
537
|
+
|
|
538
|
+
* `fights_df` — UFC fight DataFrame ordered from oldest to newest.
|
|
539
|
+
* `folds` — The number of train/test folds to create.
|
|
540
|
+
* `test_size` — The number of fights included in each test set.
|
|
541
|
+
|
|
542
|
+
### Returns
|
|
543
|
+
The function returns a dictionary containing the training and test DataFrames for each fold.
|
|
544
|
+
```python
|
|
545
|
+
{
|
|
546
|
+
"Fold 1": {
|
|
547
|
+
"train": train_dataframe,
|
|
548
|
+
"test": test_dataframe
|
|
549
|
+
},
|
|
550
|
+
"Fold 2": {
|
|
551
|
+
"train": train_dataframe,
|
|
552
|
+
"test": test_dataframe
|
|
553
|
+
},
|
|
554
|
+
...
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
### Example
|
|
558
|
+
|
|
559
|
+
```python
|
|
560
|
+
import ufcdata as ufc
|
|
561
|
+
|
|
562
|
+
data = ufc.get_data()
|
|
563
|
+
|
|
564
|
+
past_fights = data["past_fights"].copy()
|
|
565
|
+
train, test = ufc.train_test_split(past_fights, 0.2)
|
|
566
|
+
|
|
567
|
+
folds = ufc.get_expanding_window(train, 10, 500)
|
|
568
|
+
folds['Fold 2']['test']
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Plotting Accuracy
|
|
572
|
+
|
|
573
|
+
The function `plot_cv` plots out a line graph showing the accuracy of each fold along with a line showing the mean.
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
plot_cv(cv_labels, cv_accuracies, title="Cross Validation Accuracy")
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Parameters
|
|
580
|
+
|
|
581
|
+
* `cv_labels` — The list of fold names.
|
|
582
|
+
* `cv_accuracies` — The list of accuracies.
|
|
583
|
+
* `title` — The title of the plot.
|
|
584
|
+
|
|
585
|
+
The function `plot_test` plots out a single bar graph showing the accuracy of a single test.
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
plot_test(accuracy, title="Test Accuracy")
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Parameters
|
|
592
|
+
|
|
593
|
+
* `accuracy` — The accuracy.
|
|
594
|
+
* `title` — The title of the plot.
|
|
312
595
|
|
|
596
|
+
## Expanding Window Baseline Example
|
|
313
597
|
|
|
314
598
|
## Online Sources
|
|
315
599
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UFCData
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Obtain UFC data and functions to manipulate it
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -9,4 +9,5 @@ Requires-Dist: pandas
|
|
|
9
9
|
Requires-Dist: numpy
|
|
10
10
|
Requires-Dist: glicko2
|
|
11
11
|
Requires-Dist: rapidfuzz
|
|
12
|
+
Requires-Dist: matplotlib
|
|
12
13
|
Dynamic: license-file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "UFCData"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Obtain UFC data and functions to manipulate it"
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
|
|
@@ -10,4 +10,5 @@ dependencies = [
|
|
|
10
10
|
"numpy",
|
|
11
11
|
"glicko2",
|
|
12
12
|
"rapidfuzz",
|
|
13
|
+
"matplotlib",
|
|
13
14
|
]
|
|
@@ -2,4 +2,5 @@ from .data import get_data
|
|
|
2
2
|
from .search import search
|
|
3
3
|
from .ratings import get_elo
|
|
4
4
|
from .fighter import get_fighter_history, get_fighter_statistic
|
|
5
|
-
from .helper import convert_odds, convert_gender, convert_weight, mirror
|
|
5
|
+
from .helper import convert_odds, convert_gender, convert_weight, mirror
|
|
6
|
+
from .evaluation import train_test_split, get_expanding_window, plot_cv, plot_test
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
def train_test_split(fights_df, test_size=0.2):
|
|
5
|
+
"""
|
|
6
|
+
Split a UFC fight DataFrame into chronological training and test sets.
|
|
7
|
+
|
|
8
|
+
The input DataFrame must be ordered from the newest fight to the oldest
|
|
9
|
+
fight. The most recent fights are assigned to the test set, while the
|
|
10
|
+
remaining older fights are assigned to the training set. Both returned
|
|
11
|
+
DataFrames are reversed so that they are ordered from oldest to newest.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
fights_df : pandas.DataFrame
|
|
16
|
+
UFC fight DataFrame ordered from newest to oldest.
|
|
17
|
+
test_size : float or int, default=0.2
|
|
18
|
+
Determines the size of the test set. If less than 1, it is interpreted
|
|
19
|
+
as a proportion of the total number of fights. If greater than or
|
|
20
|
+
equal to 1, it is interpreted as the exact number of fights to include
|
|
21
|
+
in the test set.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
tuple of pandas.DataFrame
|
|
26
|
+
A tuple containing:
|
|
27
|
+
|
|
28
|
+
- train : pandas.DataFrame
|
|
29
|
+
Training data consisting of the older fights, ordered from
|
|
30
|
+
oldest to newest.
|
|
31
|
+
- test : pandas.DataFrame
|
|
32
|
+
Test data consisting of the most recent fights, ordered from
|
|
33
|
+
oldest to newest.
|
|
34
|
+
"""
|
|
35
|
+
fights_df = fights_df.copy()
|
|
36
|
+
|
|
37
|
+
fights_df["Winner"] = np.select(
|
|
38
|
+
[
|
|
39
|
+
fights_df["Fighter 1 Outcome"] == "W",
|
|
40
|
+
fights_df["Fighter 2 Outcome"] == "W"
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
fights_df["Fighter 1 Link"],
|
|
44
|
+
fights_df["Fighter 2 Link"]
|
|
45
|
+
],
|
|
46
|
+
default=None
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if test_size < 1:
|
|
50
|
+
n_test = int(len(fights_df) * test_size)
|
|
51
|
+
else:
|
|
52
|
+
n_test = test_size
|
|
53
|
+
|
|
54
|
+
test = fights_df[:n_test]
|
|
55
|
+
train = fights_df[n_test:]
|
|
56
|
+
|
|
57
|
+
test = test.iloc[::-1].reset_index(drop=True)
|
|
58
|
+
train = train.iloc[::-1].reset_index(drop=True)
|
|
59
|
+
|
|
60
|
+
return (train, test)
|
|
61
|
+
|
|
62
|
+
def get_expanding_window(fights_df, folds, test_size):
|
|
63
|
+
"""
|
|
64
|
+
Create expanding training windows with fixed-size test sets for
|
|
65
|
+
time-series cross-validation.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
fights_df : pandas.DataFrame
|
|
70
|
+
UFC fight DataFrame ordered chronologically from oldest to newest.
|
|
71
|
+
folds : int
|
|
72
|
+
Number of train/test folds to create.
|
|
73
|
+
test_size : int
|
|
74
|
+
Number of fights included in each test set.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
dict
|
|
79
|
+
Dictionary containing the training and test DataFrames for each fold.
|
|
80
|
+
Each fold is stored using the key ``"Fold 1"``, ``"Fold 2"``, etc.,
|
|
81
|
+
with the following structure:
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
"train": train_dataframe,
|
|
85
|
+
"test": test_dataframe
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Notes
|
|
89
|
+
-----
|
|
90
|
+
The training window expands with each fold while the test window remains
|
|
91
|
+
fixed at `test_size` rows.
|
|
92
|
+
|
|
93
|
+
For example, the folds follow the structure:
|
|
94
|
+
|
|
95
|
+
Fold 1: [Train] [Test]
|
|
96
|
+
Fold 2: [------ Train ------] [Test]
|
|
97
|
+
Fold 3: [------------ Train ------------] [Test]
|
|
98
|
+
|
|
99
|
+
This approach is suitable for time-series cross-validation because each
|
|
100
|
+
training set only contains observations that occur before its
|
|
101
|
+
corresponding test set.
|
|
102
|
+
"""
|
|
103
|
+
fold_results = {}
|
|
104
|
+
|
|
105
|
+
train_sizes = np.linspace(
|
|
106
|
+
test_size,
|
|
107
|
+
len(fights_df) - test_size,
|
|
108
|
+
folds,
|
|
109
|
+
dtype=int
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
for i, train_size in enumerate(train_sizes, start=1):
|
|
113
|
+
train = fights_df.iloc[:train_size].reset_index(drop=True)
|
|
114
|
+
test = fights_df.iloc[train_size:train_size + test_size].reset_index(drop=True)
|
|
115
|
+
|
|
116
|
+
fold_results[f"Fold {i}"] = {
|
|
117
|
+
"train": train,
|
|
118
|
+
"test": test
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return fold_results
|
|
122
|
+
|
|
123
|
+
def plot_cv(cv_labels, cv_accuracies, title="Cross Validation Accuracy"):
|
|
124
|
+
plt.figure(figsize=(10, 5))
|
|
125
|
+
|
|
126
|
+
plt.plot(
|
|
127
|
+
cv_labels,
|
|
128
|
+
cv_accuracies,
|
|
129
|
+
color="#666666",
|
|
130
|
+
linestyle="-",
|
|
131
|
+
linewidth=2,
|
|
132
|
+
marker="o",
|
|
133
|
+
markersize=7,
|
|
134
|
+
markerfacecolor="#D20A0A", # UFC red dots
|
|
135
|
+
markeredgecolor="#FFFFFF",
|
|
136
|
+
markeredgewidth=1.5,
|
|
137
|
+
label="Fold Accuracy"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Mean accuracy across folds
|
|
142
|
+
mean_accuracy = np.mean(cv_accuracies)
|
|
143
|
+
|
|
144
|
+
plt.axhline(
|
|
145
|
+
mean_accuracy,
|
|
146
|
+
color="#D20A0A",
|
|
147
|
+
linestyle="--",
|
|
148
|
+
linewidth=2,
|
|
149
|
+
label=f"Mean Accuracy: {mean_accuracy:.1%}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
plt.xlabel("Fold")
|
|
154
|
+
plt.ylabel("Accuracy")
|
|
155
|
+
plt.title(title)
|
|
156
|
+
# plt.ylim(0, 1)
|
|
157
|
+
|
|
158
|
+
plt.xticks(rotation=45)
|
|
159
|
+
plt.grid(True, alpha=0.3)
|
|
160
|
+
plt.legend()
|
|
161
|
+
|
|
162
|
+
plt.tight_layout()
|
|
163
|
+
plt.show()
|
|
164
|
+
|
|
165
|
+
def plot_test(accuracy, title="Test Accuracy"):
|
|
166
|
+
plt.figure(figsize=(6, 5))
|
|
167
|
+
|
|
168
|
+
plt.bar(
|
|
169
|
+
0,
|
|
170
|
+
accuracy,
|
|
171
|
+
color="#D20A0A",
|
|
172
|
+
width=0.25
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
plt.ylabel("Accuracy")
|
|
176
|
+
plt.title(title)
|
|
177
|
+
plt.ylim(0, 1)
|
|
178
|
+
|
|
179
|
+
plt.xlim(-0.5, 0.5)
|
|
180
|
+
plt.xticks([0], ["Test Set"])
|
|
181
|
+
|
|
182
|
+
plt.text(
|
|
183
|
+
0,
|
|
184
|
+
accuracy + 0.02,
|
|
185
|
+
f"{accuracy:.1%}",
|
|
186
|
+
ha="center",
|
|
187
|
+
fontweight="bold",
|
|
188
|
+
color="#444444"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
plt.tight_layout()
|
|
192
|
+
plt.show()
|
|
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
|