UFCData 0.2.0__tar.gz → 0.4.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.2.0 → ufcdata-0.4.0}/PKG-INFO +1 -1
- ufcdata-0.4.0/README.md +379 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/UFCData.egg-info/PKG-INFO +1 -1
- {ufcdata-0.2.0 → ufcdata-0.4.0}/UFCData.egg-info/SOURCES.txt +1 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/pyproject.toml +1 -1
- ufcdata-0.4.0/ufcdata/__init__.py +4 -0
- ufcdata-0.4.0/ufcdata/fighter.py +697 -0
- ufcdata-0.2.0/README.md +0 -173
- ufcdata-0.2.0/ufcdata/__init__.py +0 -3
- {ufcdata-0.2.0 → ufcdata-0.4.0}/LICENSE.md +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/UFCData.egg-info/dependency_links.txt +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/UFCData.egg-info/requires.txt +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/UFCData.egg-info/top_level.txt +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/setup.cfg +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/ufcdata/data.py +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/ufcdata/ratings.py +0 -0
- {ufcdata-0.2.0 → ufcdata-0.4.0}/ufcdata/search.py +0 -0
ufcdata-0.4.0/README.md
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# UFCData
|
|
2
|
+
|
|
3
|
+
An open-source Python package for accessing, manipulating, and analyzing UFC data.
|
|
4
|
+
|
|
5
|
+
https://pypi.org/project/UFCData/
|
|
6
|
+
|
|
7
|
+
add helper functions like convert odds
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
|
|
13
|
+
Accessing Data
|
|
14
|
+
- [Loading Data](#loading-data)
|
|
15
|
+
- [Search](#search)
|
|
16
|
+
|
|
17
|
+
Rating Function
|
|
18
|
+
- [Elo](#elo)
|
|
19
|
+
|
|
20
|
+
Fighter Functions
|
|
21
|
+
- [Fighter History](#fighter-history)
|
|
22
|
+
- [Fighter Statistic](#fighter-statistics)
|
|
23
|
+
|
|
24
|
+
Helper Functions
|
|
25
|
+
- [Odds Convert](#elo)
|
|
26
|
+
- [Weight Convert](#elo)
|
|
27
|
+
- [Gender Convert](#elo)
|
|
28
|
+
- [Mirror](#elo)
|
|
29
|
+
|
|
30
|
+
Model Evaluation
|
|
31
|
+
- [Odds Baseline Example](#elo)
|
|
32
|
+
|
|
33
|
+
Data Sources
|
|
34
|
+
- [Online Sources](#online-sources)
|
|
35
|
+
- [Discrepancies in Data](#discrepancies-in-data)
|
|
36
|
+
- [Update Frequency](#update-frequency)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install ufcdata
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Loading Data
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import ufcdata as ufc
|
|
48
|
+
data = ufc.get_data()
|
|
49
|
+
|
|
50
|
+
## Obtain fighter_bio dataframe
|
|
51
|
+
fighter_bio = data["fighter_bio"].copy()
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Note that data in here and other functions below reference data that is available prior to the data.
|
|
56
|
+
|
|
57
|
+
## Search
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
search(data, column, query, matches=1)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Parameters
|
|
66
|
+
|
|
67
|
+
* `data` — The dataframe you are searching.
|
|
68
|
+
* `column` — The name of the column to search.
|
|
69
|
+
* `query` — The name or text you are searching for.
|
|
70
|
+
* `matches` — The number of closest matches to return. Defaults to `1`.
|
|
71
|
+
|
|
72
|
+
### Example
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
john_jones = ufc.search(fighter_bio, "Name", "Jon Jones", 3)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This searches the `"Name"` column of the `fighter_bio` dataframe for the three closest matches to `"Jon Jones"`.
|
|
79
|
+
|
|
80
|
+
The results are returned as a dataframe, with the closest match appearing first.
|
|
81
|
+
|
|
82
|
+
### Returns
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
Name Nickname Height Weight Reach Stance Total W Total L Fighter Link
|
|
87
|
+
Jon Jones Bones 76.0 248.0 84.0 Orthodox 28 1 ufcstats.com/...
|
|
88
|
+
Roshaun Jones NaN 68.0 135.0 NaN NaN 2 6 ufcstats.com/...
|
|
89
|
+
Mason Jones The Dragon 70.0 155.0 74.0 Orthodox 18 2 ufcstats.com/...
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Elo
|
|
96
|
+
|
|
97
|
+
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**.
|
|
98
|
+
|
|
99
|
+
This allows Elo ratings to be used as features for predictive modeling without incorporating information from the fight being predicted.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
ufc.get_elo(fight_df, fighter_df, r=1500, k=30, s=400)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Parameters
|
|
107
|
+
|
|
108
|
+
* `fight_df` — The UFC fight dataframe, sorted from most recent to least recent.
|
|
109
|
+
* `fighter_df` — The fighter information dataframe containing a `"Fighter Link"` column.
|
|
110
|
+
* `r` — Initial Elo rating for each fighter. Defaults to `1500`.
|
|
111
|
+
* `k` — K-factor controlling how much ratings change after each fight. Defaults to `30`.
|
|
112
|
+
* `s` — Scaling factor used when calculating expected scores. Defaults to `400`.
|
|
113
|
+
|
|
114
|
+
### Returns
|
|
115
|
+
|
|
116
|
+
The function returns a tuple containing:
|
|
117
|
+
|
|
118
|
+
1. `fighter_elo` — A dictionary mapping each fighter's UFCStats link to their final Elo rating.
|
|
119
|
+
2. `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.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### Example
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import ufcdata as ufc
|
|
126
|
+
|
|
127
|
+
data = ufc.get_data()
|
|
128
|
+
|
|
129
|
+
past_fights = data["past_fights"].copy()
|
|
130
|
+
fighter_bio = data["fighter_bio"].copy()
|
|
131
|
+
|
|
132
|
+
current_elo, past_elo = ufc.get_elo(
|
|
133
|
+
past_fights,
|
|
134
|
+
fighter_bio
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The resulting `fight_elo` dataframe can then be used to examine or incorporate pre-fight Elo ratings into analysis and predictive models.
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
### Important
|
|
142
|
+
|
|
143
|
+
`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.
|
|
144
|
+
|
|
145
|
+
## Fighter History
|
|
146
|
+
|
|
147
|
+
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.
|
|
148
|
+
|
|
149
|
+
The function also calculates the age of both fighters at the time of each fight and assigns a `"UFC Fight"` number to each fight.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
ufc.get_fighter_history(fighter_link, fighter_bio, fights_df)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Parameters
|
|
157
|
+
|
|
158
|
+
* `fighter_link` — The UFCStats link identifying the fighter.
|
|
159
|
+
* `fighter_bio` — The fighter information dataframe returned by `get_data()`.
|
|
160
|
+
* `fights_df` — The fight dataframe containing UFC fight history.
|
|
161
|
+
|
|
162
|
+
### Returns
|
|
163
|
+
|
|
164
|
+
A dataframe containing the fighter's fight history from most recent to least recent. The requested fighter is always `"Fighter 1"`, with fighter ages and `"UFC Fight"` numbers included.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
### Example
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import ufcdata as ufc
|
|
172
|
+
|
|
173
|
+
data = ufc.get_data()
|
|
174
|
+
|
|
175
|
+
fighter_bio = data["fighter_bio"].copy()
|
|
176
|
+
fights_df = data["past_fights"].copy()
|
|
177
|
+
|
|
178
|
+
# Obtain Jon Jones' UFCStats link
|
|
179
|
+
jon_jones = ufc.search(
|
|
180
|
+
fighter_bio,
|
|
181
|
+
"Name",
|
|
182
|
+
"Jon Jones"
|
|
183
|
+
).iloc[0]["Fighter Link"]
|
|
184
|
+
|
|
185
|
+
# Get Jon Jones' fight history
|
|
186
|
+
jon_jones_history = ufc.get_fighter_history(
|
|
187
|
+
jon_jones,
|
|
188
|
+
fighter_bio,
|
|
189
|
+
fights_df
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This function is useful for analyzing an individual fighter's career, constructing fighter-level features, or preparing historical data for predictive modeling.
|
|
194
|
+
|
|
195
|
+
## Fighter Statistics
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
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**.
|
|
199
|
+
|
|
200
|
+
This is designed for machine-learning applications where using information from the future would cause data leakage.
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
get_fighter_statistic(
|
|
205
|
+
fighter_link,
|
|
206
|
+
fighter_bio,
|
|
207
|
+
fights_df,
|
|
208
|
+
rounds_df,
|
|
209
|
+
r=1500,
|
|
210
|
+
k=30,
|
|
211
|
+
s=400
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
### Parameters
|
|
218
|
+
|
|
219
|
+
| Parameter | Type | Description |
|
|
220
|
+
| -------------- | ----------------------- | -------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `fighter_link` | `str` | UFCStats link or identifier for the fighter. |
|
|
222
|
+
| `fighter_bio` | `pd.DataFrame` | Fighter biography DataFrame containing fighter names and links. |
|
|
223
|
+
| `fights_df` | `pd.DataFrame` | Fight-level UFC data containing fight results and metadata. |
|
|
224
|
+
| `rounds_df` | `pd.DataFrame` | Round-level UFC statistics. |
|
|
225
|
+
| `future_df` | `pd.DataFrame` | DataFrame containing the upcoming fight. The first row is used to determine the future fight date. |
|
|
226
|
+
| `r` | `float`, default `1500` | Initial Elo rating. |
|
|
227
|
+
| `k` | `float`, default `30` | Elo K-factor controlling the magnitude of rating updates. |
|
|
228
|
+
| `s` | `float`, default `400` | Elo scaling factor used when calculating expected scores. |
|
|
229
|
+
|
|
230
|
+
### Returns
|
|
231
|
+
|
|
232
|
+
The function returns two DataFrames:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
current_statistic, past_statistic = get_fighter_statistic(...)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### `current_statistic`
|
|
239
|
+
|
|
240
|
+
A one-row DataFrame containing the fighter's current statistic.
|
|
241
|
+
|
|
242
|
+
This includes:
|
|
243
|
+
|
|
244
|
+
* UFC fight number
|
|
245
|
+
* UFC record
|
|
246
|
+
* UFC win rate
|
|
247
|
+
* Elo rating
|
|
248
|
+
* Cumulative fight time
|
|
249
|
+
* Significant strikes landed per minute (`SLpM`)
|
|
250
|
+
* Significant strike accuracy (`Str Acc`)
|
|
251
|
+
* Significant strikes absorbed per minute (`SApM`)
|
|
252
|
+
* Significant strike defense (`Str Def`)
|
|
253
|
+
* Takedown average (`TD Avg`)
|
|
254
|
+
* Takedown accuracy (`TD Acc`)
|
|
255
|
+
* Takedown defense (`TD Def`)
|
|
256
|
+
* Submission attempts per 15 minutes (`Sub Avg`)
|
|
257
|
+
|
|
258
|
+
#### `past_statistic`
|
|
259
|
+
|
|
260
|
+
A DataFrame containing the same types of statistics for each previous UFC fight.
|
|
261
|
+
|
|
262
|
+
Each row represents the fighter's information **immediately before that fight**.
|
|
263
|
+
|
|
264
|
+
For example:
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
Fight 1 → statistics before Fight 1
|
|
268
|
+
Fight 2 → statistics before Fight 2
|
|
269
|
+
Fight 3 → statistics before Fight 3
|
|
270
|
+
...
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
This makes the data suitable for constructing historical features for a predictive model.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
### Example
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
current_statistic, past_statistic = get_fighter_statistic(
|
|
280
|
+
fighter_link=fighter_link,
|
|
281
|
+
fighter_bio=fighter_bio,
|
|
282
|
+
fights_df=fights_df,
|
|
283
|
+
rounds_df=rounds_df,
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
## Data Requirements
|
|
289
|
+
|
|
290
|
+
The function expects the input DataFrames to contain the relevant fighter, fight, and round-level information.
|
|
291
|
+
|
|
292
|
+
### `fighter_bio`
|
|
293
|
+
|
|
294
|
+
Must contain at least:
|
|
295
|
+
|
|
296
|
+
```text
|
|
297
|
+
Name
|
|
298
|
+
Fighter Link
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### `fights_df`
|
|
302
|
+
|
|
303
|
+
Must contain fighter links, outcomes, fight dates, and fight metadata required by the function.
|
|
304
|
+
|
|
305
|
+
### `rounds_df`
|
|
306
|
+
|
|
307
|
+
Must contain round-level striking, takedown, submission, and control statistics.
|
|
308
|
+
|
|
309
|
+
### `future_df`
|
|
310
|
+
|
|
311
|
+
Must contain:
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
Date
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The first row is used to determine the date of the upcoming fight.
|
|
318
|
+
|
|
319
|
+
## Design
|
|
320
|
+
|
|
321
|
+
The function follows this general pipeline:
|
|
322
|
+
|
|
323
|
+
```text
|
|
324
|
+
Fighter
|
|
325
|
+
│
|
|
326
|
+
▼
|
|
327
|
+
Retrieve fight history
|
|
328
|
+
│
|
|
329
|
+
▼
|
|
330
|
+
Construct chronological fight history
|
|
331
|
+
│
|
|
332
|
+
▼
|
|
333
|
+
Calculate UFC record
|
|
334
|
+
│
|
|
335
|
+
▼
|
|
336
|
+
Calculate Elo ratings
|
|
337
|
+
│
|
|
338
|
+
▼
|
|
339
|
+
Calculate cumulative fight statistics
|
|
340
|
+
│
|
|
341
|
+
├── Striking
|
|
342
|
+
├── Takedowns
|
|
343
|
+
└── Submissions
|
|
344
|
+
│
|
|
345
|
+
▼
|
|
346
|
+
Generate pre-fight statistics
|
|
347
|
+
│
|
|
348
|
+
├── Past fights
|
|
349
|
+
└── Upcoming fight
|
|
350
|
+
│
|
|
351
|
+
▼
|
|
352
|
+
Return current_statistic, past_statistic
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The resulting data can then be combined with opponent statistics and other fight-level features to create inputs for UFC fight prediction models.
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
## Online Sources
|
|
360
|
+
|
|
361
|
+
Odds and birthplace data was obtained from https://www.tapology.com
|
|
362
|
+
|
|
363
|
+
Venue and attendance data was obtained from https://en.wikipedia.org/wiki/List_of_UFC_events
|
|
364
|
+
|
|
365
|
+
All other data was obtained from http://ufcstats.com
|
|
366
|
+
|
|
367
|
+
## Discrepancies in Data
|
|
368
|
+
|
|
369
|
+
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.
|
|
370
|
+
|
|
371
|
+
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.
|
|
372
|
+
|
|
373
|
+
## Update Frequency
|
|
374
|
+
|
|
375
|
+
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.
|
|
376
|
+
|
|
377
|
+
A second update occurs 24 hours after the start of the broadcast. This update captures the finalized event, fight, and round data, as well as information on upcoming events and fights.
|
|
378
|
+
|
|
379
|
+
Changes occurring between these scheduled updates are not automatically captured. Users are responsible for manually updating the dataset if they require the most current data for analysis or prediction.
|
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from datetime import date
|
|
4
|
+
from .ratings import get_elo, expected_score, update_rating
|
|
5
|
+
|
|
6
|
+
def _mirror_helper(df, cols_1, cols_2, in_order=True):
|
|
7
|
+
df = df.copy()
|
|
8
|
+
df_original = df.copy()
|
|
9
|
+
|
|
10
|
+
# Swap each pair of columns
|
|
11
|
+
for c1, c2 in zip(cols_1, cols_2):
|
|
12
|
+
temp = df[c1].copy()
|
|
13
|
+
df[c1] = df[c2]
|
|
14
|
+
df[c2] = temp
|
|
15
|
+
|
|
16
|
+
if not in_order:
|
|
17
|
+
return pd.concat([df_original, df], ignore_index=True)
|
|
18
|
+
|
|
19
|
+
rows = []
|
|
20
|
+
for r1, r2 in zip(df_original.itertuples(index=False), df.itertuples(index=False)):
|
|
21
|
+
rows.append(r1)
|
|
22
|
+
rows.append(r2)
|
|
23
|
+
|
|
24
|
+
return pd.DataFrame(rows, columns=df.columns)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_fighter_history(fighter_link, fighter_bio, fights_df):
|
|
28
|
+
"""
|
|
29
|
+
Retrieves the fight history of a UFC fighter.
|
|
30
|
+
|
|
31
|
+
The returned DataFrame contains all fights involving the specified
|
|
32
|
+
fighter, with the fighter consistently represented as "Fighter 1".
|
|
33
|
+
The function also calculates the age of both fighters at the time
|
|
34
|
+
of each fight and assigns a chronological UFC fight number.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
fighter_link : str
|
|
39
|
+
UFCStats link identifying the fighter whose history is being
|
|
40
|
+
retrieved.
|
|
41
|
+
fighter_bio : pandas.DataFrame
|
|
42
|
+
DataFrame containing fighter information. Must contain
|
|
43
|
+
"Fighter Link" and "DOB" columns.
|
|
44
|
+
fights_df : pandas.DataFrame
|
|
45
|
+
DataFrame containing UFC fight data. Must contain fighter links,
|
|
46
|
+
fight dates, outcomes, and the other fight information required
|
|
47
|
+
by the function.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
pandas.DataFrame
|
|
52
|
+
DataFrame containing the fighter's complete fight history.
|
|
53
|
+
The requested fighter is represented as "Fighter 1" in every
|
|
54
|
+
row. The DataFrame includes the ages of both fighters at the
|
|
55
|
+
time of each fight and a "UFC Fight" column numbering the
|
|
56
|
+
fighter's fights chronologically.
|
|
57
|
+
|
|
58
|
+
Notes
|
|
59
|
+
-----
|
|
60
|
+
Fighter ages are calculated using 365.25 days per year. The
|
|
61
|
+
"UFC Fight" column counts fights from the most recent fight
|
|
62
|
+
backwards, with the most recent fight numbered 1.
|
|
63
|
+
|
|
64
|
+
Examples
|
|
65
|
+
--------
|
|
66
|
+
>>> fighter_history = get_fighter_history(
|
|
67
|
+
... fighter_link,
|
|
68
|
+
... fighter_bio,
|
|
69
|
+
... fights_df
|
|
70
|
+
... )
|
|
71
|
+
>>> fighter_history[["Date", "UFC Fight", "Fighter 1",
|
|
72
|
+
... "Fighter 1 Age"]].head()
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
fighter_history = fights_df[
|
|
77
|
+
(fights_df["Fighter 1 Link"] == fighter_link) |
|
|
78
|
+
(fights_df["Fighter 2 Link"] == fighter_link)]
|
|
79
|
+
|
|
80
|
+
fighter_history = _mirror_helper(fighter_history, ['Fighter 1', 'Fighter 1 Odds', 'Fighter 1 Link',
|
|
81
|
+
'Fighter 1 Outcome', 'Fighter 1 Bonus'],['Fighter 2', 'Fighter 2 Odds',
|
|
82
|
+
'Fighter 2 Link', 'Fighter 2 Outcome', 'Fighter 2 Bonus'])
|
|
83
|
+
|
|
84
|
+
fighter_history = fighter_history[
|
|
85
|
+
(fighter_history["Fighter 1 Link"] == fighter_link)]
|
|
86
|
+
|
|
87
|
+
fighter_history = fighter_history.merge(
|
|
88
|
+
fighter_bio[["Fighter Link", "DOB"]],
|
|
89
|
+
left_on="Fighter 1 Link",
|
|
90
|
+
right_on="Fighter Link",
|
|
91
|
+
how="left")
|
|
92
|
+
fighter_history = fighter_history.rename(columns={"DOB": "Fighter 1 Age"})
|
|
93
|
+
fighter_history = fighter_history.drop(columns=["Fighter Link"])
|
|
94
|
+
fighter_history["Fighter 1 Age"] = (pd.to_datetime(fighter_history["Date"]) - pd.to_datetime(fighter_history["Fighter 1 Age"])).dt.days / 365.25
|
|
95
|
+
|
|
96
|
+
fighter_history = fighter_history.merge(
|
|
97
|
+
fighter_bio[["Fighter Link", "DOB"]],
|
|
98
|
+
left_on="Fighter 2 Link",
|
|
99
|
+
right_on="Fighter Link",
|
|
100
|
+
how="left")
|
|
101
|
+
fighter_history = fighter_history.rename(columns={"DOB": "Fighter 2 Age"})
|
|
102
|
+
fighter_history = fighter_history.drop(columns=["Fighter Link"])
|
|
103
|
+
fighter_history["Fighter 2 Age"] = (pd.to_datetime(fighter_history["Date"]) - pd.to_datetime(fighter_history["Fighter 2 Age"])).dt.days / 365.25
|
|
104
|
+
fighter_history["UFC Fight"] = range(len(fighter_history), 0, -1)
|
|
105
|
+
fighter_history = fighter_history[['Date', "UFC Fight", 'Event Link', 'Fight Link', 'Weight Class',
|
|
106
|
+
'Gender', 'Title', 'Fighter 1', 'Fighter 1 Odds', 'Fighter 1 Age', 'Fighter 1 Link',
|
|
107
|
+
'Fighter 1 Outcome', 'Fighter 1 Bonus', 'Fighter 2', 'Fighter 2 Odds','Fighter 2 Age',
|
|
108
|
+
'Fighter 2 Link', 'Fighter 2 Outcome', 'Fighter 2 Bonus', 'Method',
|
|
109
|
+
'Round', 'Time', 'Time Format', 'Referee', 'Details']]
|
|
110
|
+
|
|
111
|
+
return fighter_history
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_fighter_statistic(fighter_link, fighter_bio, fights_df, rounds_df,
|
|
116
|
+
r=1500, k=30, s=400):
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
Generate historical and current statistics for a UFC fighter.
|
|
120
|
+
|
|
121
|
+
The function retrieves the fighter's UFC fight history and constructs
|
|
122
|
+
fight-level statistics from the fighter's perspective. It calculates
|
|
123
|
+
the fighter's UFC record, win rate, Elo rating, cumulative fight time,
|
|
124
|
+
striking statistics, takedown statistics, and submission averages using
|
|
125
|
+
only information available before each fight.
|
|
126
|
+
|
|
127
|
+
For fighters with no previous UFC fights, a baseline row is returned
|
|
128
|
+
with an initial Elo rating and zero UFC wins, losses, draws, and
|
|
129
|
+
no-contests. Historical performance statistics are left as NaN because
|
|
130
|
+
no prior fight data is available.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
fighter_link : str
|
|
135
|
+
URL or unique identifier for the fighter.
|
|
136
|
+
fighter_bio : pandas.DataFrame
|
|
137
|
+
DataFrame containing fighter biographical information, including
|
|
138
|
+
fighter names and links.
|
|
139
|
+
fights_df : pandas.DataFrame
|
|
140
|
+
DataFrame containing UFC fight-level results.
|
|
141
|
+
rounds_df : pandas.DataFrame
|
|
142
|
+
DataFrame containing round-level UFC statistics.
|
|
143
|
+
r : float, default=1500
|
|
144
|
+
Initial Elo rating assigned to fighters with no previous Elo history.
|
|
145
|
+
k : float, default=30
|
|
146
|
+
Elo update factor.
|
|
147
|
+
s : float, default=400
|
|
148
|
+
Elo scaling factor used when calculating expected scores.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
current_statistic : pandas.DataFrame
|
|
153
|
+
One-row DataFrame containing the fighter's statistics immediately
|
|
154
|
+
before the upcoming fight. Historical statistics are calculated
|
|
155
|
+
using only fights occurring before the upcoming fight date.
|
|
156
|
+
|
|
157
|
+
past_statistic : pandas.DataFrame
|
|
158
|
+
DataFrame containing the fighter's historical statistics for each
|
|
159
|
+
previous UFC fight, with statistics calculated using only fights
|
|
160
|
+
occurring before each respective fight.
|
|
161
|
+
|
|
162
|
+
Notes
|
|
163
|
+
-----
|
|
164
|
+
The function calculates cumulative statistics rather than statistics
|
|
165
|
+
from a single fight. This prevents information from a future fight from
|
|
166
|
+
being used when generating features for an earlier fight.
|
|
167
|
+
|
|
168
|
+
Historical statistics include:
|
|
169
|
+
- UFC record and win rate
|
|
170
|
+
- Elo rating
|
|
171
|
+
- Total fight time
|
|
172
|
+
- Significant strikes landed per minute (SLpM)
|
|
173
|
+
- Significant strike accuracy and defense
|
|
174
|
+
- Significant strikes absorbed per minute (SApM)
|
|
175
|
+
- Takedown average, accuracy, and defense
|
|
176
|
+
- Submission attempts per 15 minutes
|
|
177
|
+
|
|
178
|
+
Fighters with no previous UFC fights receive an initial Elo rating of
|
|
179
|
+
`r`, while statistics requiring historical fight data are set to NaN.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
record = get_fighter_history(fighter_link, fighter_bio, fights_df)
|
|
183
|
+
|
|
184
|
+
date = ("2001-01-01")
|
|
185
|
+
|
|
186
|
+
if len(record) == 0:
|
|
187
|
+
columns = [
|
|
188
|
+
'Date', 'UFC Fight', 'Weight Class', 'Gender', 'Title', 'Fighter',
|
|
189
|
+
'Fighter Link', 'Fighter Outcome', 'UFC W', 'UFC L', 'UFC D', 'UFC NC',
|
|
190
|
+
'UFC Win Rate', 'Fighter Elo',
|
|
191
|
+
'Fight Time', 'SLpM', 'Str Acc', 'SApM',
|
|
192
|
+
'Str Def', 'TD Avg', 'TD Acc', 'TD Def', 'Sub Avg'
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
fighter_name = fighter_bio.loc[
|
|
196
|
+
fighter_bio["Fighter Link"] == fighter_link,
|
|
197
|
+
"Name"
|
|
198
|
+
].iloc[0]
|
|
199
|
+
|
|
200
|
+
df = pd.DataFrame([{
|
|
201
|
+
"Date": date,
|
|
202
|
+
"UFC Fight": 1,
|
|
203
|
+
"Weight Class": None,
|
|
204
|
+
"Gender": None,
|
|
205
|
+
"Title": None,
|
|
206
|
+
"Fighter": fighter_name,
|
|
207
|
+
"Fighter Link": fighter_link,
|
|
208
|
+
"Fighter Outcome": None,
|
|
209
|
+
"UFC W": 0,
|
|
210
|
+
"UFC L": 0,
|
|
211
|
+
"UFC D": 0,
|
|
212
|
+
"UFC NC": 0,
|
|
213
|
+
"UFC Win Rate": None,
|
|
214
|
+
"Fighter Elo": r,
|
|
215
|
+
"Fight Time": None,
|
|
216
|
+
"SLpM": None,
|
|
217
|
+
"Str Acc": None,
|
|
218
|
+
"SApM": None,
|
|
219
|
+
"Str Def": None,
|
|
220
|
+
"TD Avg": None,
|
|
221
|
+
"TD Acc": None,
|
|
222
|
+
"TD Def": None,
|
|
223
|
+
"Sub Avg": None
|
|
224
|
+
}], columns=columns)
|
|
225
|
+
|
|
226
|
+
df = df.astype(object).where(pd.notna(df), np.nan)
|
|
227
|
+
|
|
228
|
+
df["UFC Fight"] = df["UFC Fight"].astype("Int64")
|
|
229
|
+
df[["UFC W", "UFC L", "UFC D", "UFC NC"]] = (
|
|
230
|
+
df[["UFC W", "UFC L", "UFC D", "UFC NC"]].astype("Int64")
|
|
231
|
+
)
|
|
232
|
+
df.iloc[0, 0] = np.nan
|
|
233
|
+
|
|
234
|
+
return (df, df)
|
|
235
|
+
|
|
236
|
+
statistic = record[
|
|
237
|
+
['Date', 'UFC Fight', 'Weight Class',
|
|
238
|
+
'Gender', 'Title', 'Fighter 1',
|
|
239
|
+
'Fighter 1 Link', 'Fighter 1 Outcome']
|
|
240
|
+
].copy()
|
|
241
|
+
|
|
242
|
+
cols = [
|
|
243
|
+
"Date", "UFC Fight", "Weight Class", "Gender",
|
|
244
|
+
"Title", "Fighter 1", "Fighter 1 Link",
|
|
245
|
+
"Fighter 1 Outcome"
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
statistic[cols] = statistic[cols].iloc[::-1].to_numpy()
|
|
249
|
+
|
|
250
|
+
last = statistic.iloc[-1]
|
|
251
|
+
new_row = last.copy()
|
|
252
|
+
new_row[:] = pd.NA
|
|
253
|
+
|
|
254
|
+
new_row["Date"] = date
|
|
255
|
+
new_row["UFC Fight"] = last["UFC Fight"] + 1
|
|
256
|
+
new_row["Gender"] = last["Gender"]
|
|
257
|
+
new_row["Fighter 1"] = last["Fighter 1"]
|
|
258
|
+
new_row["Fighter 1 Link"] = last["Fighter 1 Link"]
|
|
259
|
+
|
|
260
|
+
statistic = pd.concat(
|
|
261
|
+
[statistic, new_row.to_frame().T],
|
|
262
|
+
ignore_index=True
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
statistic["Date"] = pd.to_datetime(statistic["Date"]).dt.date
|
|
266
|
+
|
|
267
|
+
statistic["UFC W"] = 0
|
|
268
|
+
statistic["UFC L"] = 0
|
|
269
|
+
statistic["UFC D"] = 0
|
|
270
|
+
statistic["UFC NC"] = 0
|
|
271
|
+
|
|
272
|
+
for i in range(1, len(statistic)):
|
|
273
|
+
|
|
274
|
+
# Carry forward previous totals
|
|
275
|
+
statistic.loc[i, "UFC W"] = statistic.loc[i-1, "UFC W"]
|
|
276
|
+
statistic.loc[i, "UFC L"] = statistic.loc[i-1, "UFC L"]
|
|
277
|
+
statistic.loc[i, "UFC D"] = statistic.loc[i-1, "UFC D"]
|
|
278
|
+
statistic.loc[i, "UFC NC"] = statistic.loc[i-1, "UFC NC"]
|
|
279
|
+
|
|
280
|
+
# Update based on previous fight result
|
|
281
|
+
result = statistic.loc[i-1, "Fighter 1 Outcome"]
|
|
282
|
+
|
|
283
|
+
if result == "W":
|
|
284
|
+
statistic.loc[i, "UFC W"] += 1
|
|
285
|
+
elif result == "L":
|
|
286
|
+
statistic.loc[i, "UFC L"] += 1
|
|
287
|
+
elif result == "D":
|
|
288
|
+
statistic.loc[i, "UFC D"] += 1
|
|
289
|
+
elif result == "NC":
|
|
290
|
+
statistic.loc[i, "UFC NC"] += 1
|
|
291
|
+
|
|
292
|
+
statistic = statistic[::-1]
|
|
293
|
+
|
|
294
|
+
statistic[
|
|
295
|
+
["UFC W", "UFC L", "UFC D", "UFC NC"]
|
|
296
|
+
] = (
|
|
297
|
+
statistic[
|
|
298
|
+
["UFC W", "UFC L", "UFC D", "UFC NC"]
|
|
299
|
+
].iloc[::-1].reset_index(drop=True)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
statistic["UFC Win Rate"] = (
|
|
303
|
+
statistic["UFC W"] /
|
|
304
|
+
(
|
|
305
|
+
statistic["UFC L"]
|
|
306
|
+
+ statistic["UFC D"]
|
|
307
|
+
+ statistic["UFC NC"]
|
|
308
|
+
+ statistic["UFC W"]
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Elo
|
|
313
|
+
current_elo,elo = get_elo(fights_df, fighter_bio, r, k, s)
|
|
314
|
+
|
|
315
|
+
elo = elo[
|
|
316
|
+
(elo["Fighter 1 Link"] == fighter_link) |
|
|
317
|
+
(elo["Fighter 2 Link"] == fighter_link)
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
elo = elo[
|
|
321
|
+
[
|
|
322
|
+
"Fighter 1", "Fighter 1 Elo", "Fighter 1 Outcome",
|
|
323
|
+
"Fighter 1 Link",
|
|
324
|
+
"Fighter 2", "Fighter 2 Elo", "Fighter 2 Outcome",
|
|
325
|
+
"Fighter 2 Link"
|
|
326
|
+
]
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
elo = _mirror_helper(
|
|
330
|
+
elo,
|
|
331
|
+
[
|
|
332
|
+
"Fighter 1", "Fighter 1 Elo",
|
|
333
|
+
"Fighter 1 Outcome", "Fighter 1 Link"
|
|
334
|
+
],
|
|
335
|
+
[
|
|
336
|
+
"Fighter 2", "Fighter 2 Elo",
|
|
337
|
+
"Fighter 2 Outcome", "Fighter 2 Link"
|
|
338
|
+
]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
elo = elo[
|
|
342
|
+
elo["Fighter 1 Link"] == fighter_link
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
elo = elo.reset_index(drop=True)
|
|
346
|
+
|
|
347
|
+
new_row = pd.DataFrame(
|
|
348
|
+
[[np.nan] * len(elo.columns)],
|
|
349
|
+
columns=elo.columns
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
elo = pd.concat(
|
|
353
|
+
[new_row, elo],
|
|
354
|
+
ignore_index=True
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
fighter_elo = elo.at[1, "Fighter 1 Elo"]
|
|
358
|
+
opponent_elo = elo.at[1, "Fighter 2 Elo"]
|
|
359
|
+
fighter_outcome = elo.at[1, "Fighter 1 Outcome"]
|
|
360
|
+
|
|
361
|
+
if fighter_outcome == "W":
|
|
362
|
+
expected_1 = expected_score(
|
|
363
|
+
fighter_elo,
|
|
364
|
+
opponent_elo,
|
|
365
|
+
s
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
elo_1_new = update_rating(
|
|
369
|
+
fighter_elo,
|
|
370
|
+
k,
|
|
371
|
+
1,
|
|
372
|
+
expected_1
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
elo.at[0, "Fighter 1 Elo"] = elo_1_new
|
|
376
|
+
|
|
377
|
+
elif fighter_outcome == "L":
|
|
378
|
+
expected_1 = expected_score(
|
|
379
|
+
fighter_elo,
|
|
380
|
+
opponent_elo,
|
|
381
|
+
s
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
elo_1_new = update_rating(
|
|
385
|
+
fighter_elo,
|
|
386
|
+
k,
|
|
387
|
+
0,
|
|
388
|
+
expected_1
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
elo.at[0, "Fighter 1 Elo"] = elo_1_new
|
|
392
|
+
|
|
393
|
+
elif fighter_outcome == "D":
|
|
394
|
+
expected_1 = expected_score(
|
|
395
|
+
fighter_elo,
|
|
396
|
+
opponent_elo,
|
|
397
|
+
s
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
elo_1_new = update_rating(
|
|
401
|
+
fighter_elo,
|
|
402
|
+
k,
|
|
403
|
+
0.5,
|
|
404
|
+
expected_1
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
elo.at[0, "Fighter 1 Elo"] = elo_1_new
|
|
408
|
+
|
|
409
|
+
elif fighter_outcome == "NC":
|
|
410
|
+
elo.at[0, "Fighter 1 Elo"] = fighter_elo
|
|
411
|
+
|
|
412
|
+
elo = elo["Fighter 1 Elo"]
|
|
413
|
+
elo = elo.iloc[::-1].reset_index(drop=True)
|
|
414
|
+
|
|
415
|
+
statistic = pd.concat(
|
|
416
|
+
[statistic, elo],
|
|
417
|
+
axis=1
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Get fight statistics
|
|
421
|
+
fight_time = get_fighter_history(
|
|
422
|
+
fighter_link,
|
|
423
|
+
fighter_bio,
|
|
424
|
+
fights_df
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
fight_time["new_time"] = fight_time["Time"].apply(
|
|
428
|
+
lambda x: int(x.split(":")[0]) * 60
|
|
429
|
+
+ int(x.split(":")[1])
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
fight_time["new_round"] = (
|
|
433
|
+
(fight_time["Round"] - 1) * 60 * 5
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
fight_time["Fight Time"] = (
|
|
437
|
+
fight_time["new_time"]
|
|
438
|
+
+ fight_time["new_round"]
|
|
439
|
+
) / 60
|
|
440
|
+
|
|
441
|
+
statistic["Fight Time"] = statistic["Date"].apply(
|
|
442
|
+
lambda d: fight_time.loc[
|
|
443
|
+
fight_time["Date"].dt.date < d,
|
|
444
|
+
"Fight Time"
|
|
445
|
+
].sum()
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
rounds = rounds_df
|
|
449
|
+
|
|
450
|
+
rounds = rounds[
|
|
451
|
+
(rounds["Fighter 1 Link"] == fighter_link) |
|
|
452
|
+
(rounds["Fighter 2 Link"] == fighter_link)
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
rounds = _mirror_helper(
|
|
456
|
+
rounds,
|
|
457
|
+
[
|
|
458
|
+
"Fighter 1", "Fighter 1 Link",
|
|
459
|
+
"Fighter 1 TD", "Fighter 1 Sub Att",
|
|
460
|
+
"Fighter 1 Rev", "Fighter 1 Ctrl",
|
|
461
|
+
"Fighter 1 KD", "Fighter 1 Total SS"
|
|
462
|
+
],
|
|
463
|
+
[
|
|
464
|
+
"Fighter 2", "Fighter 2 Link",
|
|
465
|
+
"Fighter 2 TD", "Fighter 2 Sub Att",
|
|
466
|
+
"Fighter 2 Rev", "Fighter 2 Ctrl",
|
|
467
|
+
"Fighter 2 KD", "Fighter 2 Total SS"
|
|
468
|
+
]
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
rounds = rounds[
|
|
472
|
+
rounds["Fighter 1 Link"] == fighter_link
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
rounds["Sig landed"] = (
|
|
476
|
+
rounds["Fighter 1 Total SS"]
|
|
477
|
+
.str.split()
|
|
478
|
+
.str[0]
|
|
479
|
+
.astype(int)
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
rounds["Sig att"] = (
|
|
483
|
+
rounds["Fighter 1 Total SS"]
|
|
484
|
+
.str.split()
|
|
485
|
+
.str[2]
|
|
486
|
+
.astype(int)
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
rounds["Strike acc"] = (
|
|
490
|
+
rounds["Sig landed"] / rounds["Sig att"]
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
rounds["Strikes absorbed"] = (
|
|
494
|
+
rounds["Fighter 2 Total SS"]
|
|
495
|
+
.str.split()
|
|
496
|
+
.str[0]
|
|
497
|
+
.astype(int)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
rounds["Opp sig att"] = (
|
|
501
|
+
rounds["Fighter 2 Total SS"]
|
|
502
|
+
.str.split()
|
|
503
|
+
.str[2]
|
|
504
|
+
.astype(int)
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
statistic["SLpM"] = statistic["Date"].apply(
|
|
508
|
+
lambda d: rounds.loc[
|
|
509
|
+
rounds["Date"].dt.date < d,
|
|
510
|
+
"Sig landed"
|
|
511
|
+
].sum()
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
statistic["SLpM"] = (
|
|
515
|
+
statistic["SLpM"] / statistic["Fight Time"]
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
statistic["Sig landed"] = statistic["Date"].apply(
|
|
519
|
+
lambda d: rounds.loc[
|
|
520
|
+
rounds["Date"].dt.date < d,
|
|
521
|
+
"Sig landed"
|
|
522
|
+
].sum()
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
statistic["Sig att"] = statistic["Date"].apply(
|
|
526
|
+
lambda d: rounds.loc[
|
|
527
|
+
rounds["Date"].dt.date < d,
|
|
528
|
+
"Sig att"
|
|
529
|
+
].sum()
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
statistic["Str Acc"] = (
|
|
533
|
+
statistic["Sig landed"] /
|
|
534
|
+
statistic["Sig att"]
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
statistic["SApM"] = statistic["Date"].apply(
|
|
538
|
+
lambda d: rounds.loc[
|
|
539
|
+
rounds["Date"].dt.date < d,
|
|
540
|
+
"Strikes absorbed"
|
|
541
|
+
].sum()
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
statistic["SApM"] = (
|
|
545
|
+
statistic["SApM"] / statistic["Fight Time"]
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
statistic["Strikes absorbed"] = statistic["Date"].apply(
|
|
549
|
+
lambda d: rounds.loc[
|
|
550
|
+
rounds["Date"].dt.date < d,
|
|
551
|
+
"Strikes absorbed"
|
|
552
|
+
].sum()
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
statistic["Opp sig att"] = statistic["Date"].apply(
|
|
556
|
+
lambda d: rounds.loc[
|
|
557
|
+
rounds["Date"].dt.date < d,
|
|
558
|
+
"Opp sig att"
|
|
559
|
+
].sum()
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
statistic["Str Def"] = 1 - (
|
|
563
|
+
statistic["Strikes absorbed"] /
|
|
564
|
+
statistic["Opp sig att"]
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
rounds["Td"] = (
|
|
568
|
+
rounds["Fighter 1 TD"]
|
|
569
|
+
.str.split()
|
|
570
|
+
.str[0]
|
|
571
|
+
.astype(int)
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
rounds["TD landed"] = (
|
|
575
|
+
rounds["Fighter 1 TD"]
|
|
576
|
+
.str.split()
|
|
577
|
+
.str[0]
|
|
578
|
+
.astype(int)
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
rounds["TD att"] = (
|
|
582
|
+
rounds["Fighter 1 TD"]
|
|
583
|
+
.str.split()
|
|
584
|
+
.str[2]
|
|
585
|
+
.astype(int)
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
rounds["TD absorbed"] = (
|
|
589
|
+
rounds["Fighter 2 TD"]
|
|
590
|
+
.str.split()
|
|
591
|
+
.str[0]
|
|
592
|
+
.astype(int)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
rounds["Opp TD att"] = (
|
|
596
|
+
rounds["Fighter 2 TD"]
|
|
597
|
+
.str.split()
|
|
598
|
+
.str[2]
|
|
599
|
+
.astype(int)
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
rounds["Sub att"] = rounds["Fighter 1 Sub Att"]
|
|
603
|
+
|
|
604
|
+
statistic["TD Avg"] = statistic["Date"].apply(
|
|
605
|
+
lambda d: rounds.loc[
|
|
606
|
+
rounds["Date"].dt.date < d,
|
|
607
|
+
"Td"
|
|
608
|
+
].sum()
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
statistic["TD Avg"] = (
|
|
612
|
+
statistic["TD Avg"] /
|
|
613
|
+
statistic["Fight Time"] * 15
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
statistic["TD landed"] = statistic["Date"].apply(
|
|
617
|
+
lambda d: rounds.loc[
|
|
618
|
+
rounds["Date"].dt.date < d,
|
|
619
|
+
"TD landed"
|
|
620
|
+
].sum()
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
statistic["TD att"] = statistic["Date"].apply(
|
|
624
|
+
lambda d: rounds.loc[
|
|
625
|
+
rounds["Date"].dt.date < d,
|
|
626
|
+
"TD att"
|
|
627
|
+
].sum()
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
statistic["TD absorbed"] = statistic["Date"].apply(
|
|
631
|
+
lambda d: rounds.loc[
|
|
632
|
+
rounds["Date"].dt.date < d,
|
|
633
|
+
"TD absorbed"
|
|
634
|
+
].sum()
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
statistic["Opp TD att"] = statistic["Date"].apply(
|
|
638
|
+
lambda d: rounds.loc[
|
|
639
|
+
rounds["Date"].dt.date < d,
|
|
640
|
+
"Opp TD att"
|
|
641
|
+
].sum()
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
statistic["TD Acc"] = np.where(
|
|
645
|
+
statistic["TD att"] > 0,
|
|
646
|
+
statistic["TD landed"] /
|
|
647
|
+
statistic["TD att"],
|
|
648
|
+
np.nan
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
statistic["TD Def"] = np.where(
|
|
652
|
+
statistic["Opp TD att"] > 0,
|
|
653
|
+
1 - (
|
|
654
|
+
statistic["TD absorbed"] /
|
|
655
|
+
statistic["Opp TD att"]
|
|
656
|
+
),
|
|
657
|
+
np.nan
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
statistic["Sub Avg"] = statistic["Date"].apply(
|
|
661
|
+
lambda d: rounds.loc[
|
|
662
|
+
rounds["Date"].dt.date < d,
|
|
663
|
+
"Sub att"
|
|
664
|
+
].sum()
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
statistic["Sub Avg"] = (
|
|
668
|
+
statistic["Sub Avg"] /
|
|
669
|
+
statistic["Fight Time"] * 15
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
statistic = statistic.drop(
|
|
673
|
+
columns=[
|
|
674
|
+
"Sig landed",
|
|
675
|
+
"Sig att",
|
|
676
|
+
"Strikes absorbed",
|
|
677
|
+
"Opp sig att",
|
|
678
|
+
"TD landed",
|
|
679
|
+
"TD att",
|
|
680
|
+
"TD absorbed",
|
|
681
|
+
"Opp TD att"
|
|
682
|
+
]
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
statistic.columns = statistic.columns.str.replace(
|
|
686
|
+
"Fighter 1",
|
|
687
|
+
"Fighter",
|
|
688
|
+
regex=False
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
statistic = statistic.reset_index(drop=True)
|
|
692
|
+
|
|
693
|
+
current_statistic = statistic.iloc[[0]].copy()
|
|
694
|
+
current_statistic["Date"] = np.nan
|
|
695
|
+
past_statistic = statistic.iloc[1:].reset_index(drop=True)
|
|
696
|
+
|
|
697
|
+
return (current_statistic, past_statistic)
|
ufcdata-0.2.0/README.md
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# UFCData
|
|
2
|
-
|
|
3
|
-
An open-source Python package for accessing, manipulating, and analyzing UFC data.
|
|
4
|
-
|
|
5
|
-
https://pypi.org/project/UFCData/
|
|
6
|
-
|
|
7
|
-
add helper functions like convert odds
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
- [Installation](#installation)
|
|
12
|
-
|
|
13
|
-
Accessing Data
|
|
14
|
-
- [Loading Data](#loading-data)
|
|
15
|
-
- [Search](#search)
|
|
16
|
-
|
|
17
|
-
Rating Functions
|
|
18
|
-
- [Elo](#elo)
|
|
19
|
-
- [Glicko-2](#glicko-2)
|
|
20
|
-
|
|
21
|
-
Fighter Functions
|
|
22
|
-
- [Fighter Record](#elo)
|
|
23
|
-
- [Fighter Statistic](#elo)
|
|
24
|
-
|
|
25
|
-
Model Evaluation
|
|
26
|
-
- [Odds Baseline Example](#elo)
|
|
27
|
-
|
|
28
|
-
Helper Functions
|
|
29
|
-
- [Odds Convert](#elo)
|
|
30
|
-
- [Weight Convert](#elo)
|
|
31
|
-
- [Gender Convert](#elo)
|
|
32
|
-
- [Mirror](#elo)
|
|
33
|
-
|
|
34
|
-
Data Sources
|
|
35
|
-
- [Online Sources](#online-sources)
|
|
36
|
-
- [Discrepancies in Data](#discrepancies-in-data)
|
|
37
|
-
- [Update Frequency](#update-frequency)
|
|
38
|
-
|
|
39
|
-
## Installation
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pip install ufcdata
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Loading Data
|
|
46
|
-
|
|
47
|
-
```python
|
|
48
|
-
import ufcdata as ufc
|
|
49
|
-
data = ufc.get_data()
|
|
50
|
-
|
|
51
|
-
## Obtain fighter_bio dataframe
|
|
52
|
-
fighter_bio = data["fighter_bio"].copy()
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Note that data in here and other functions below reference data that is available prior to the data.
|
|
57
|
-
|
|
58
|
-
## Search
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
search(data, column, query, matches=1)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Parameters
|
|
67
|
-
|
|
68
|
-
* `data` — The dataframe you are searching.
|
|
69
|
-
* `column` — The name of the column to search.
|
|
70
|
-
* `query` — The name or text you are searching for.
|
|
71
|
-
* `matches` — The number of closest matches to return. Defaults to `1`.
|
|
72
|
-
|
|
73
|
-
### Example
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
john_jones = ufc.search(fighter_bio, "Name", "Jon Jones", 3)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
This searches the `"Name"` column of the `fighter_bio` dataframe for the three closest matches to `"Jon Jones"`.
|
|
80
|
-
|
|
81
|
-
The results are returned as a dataframe, with the closest match appearing first.
|
|
82
|
-
|
|
83
|
-
Output:
|
|
84
|
-
|
|
85
|
-
```text
|
|
86
|
-
Name Nickname Height Weight Reach Stance Total W Total L Fighter Link
|
|
87
|
-
Jon Jones Bones 76.0 248.0 84.0 Orthodox 28 1 ufcstats.com/...
|
|
88
|
-
Roshaun Jones NaN 68.0 135.0 NaN NaN 2 6 ufcstats.com/...
|
|
89
|
-
Mason Jones The Dragon 70.0 155.0 74.0 Orthodox 18 2 ufcstats.com/...
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
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.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
## Elo
|
|
96
|
-
|
|
97
|
-
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**.
|
|
98
|
-
|
|
99
|
-
This allows Elo ratings to be used as features for predictive modeling without incorporating information from the fight being predicted.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
ufc.get_elo(fight_df, fighter_df, r=1500, k=30, s=400)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Parameters
|
|
107
|
-
|
|
108
|
-
* `fight_df` — The UFC fight dataframe, sorted from most recent to least recent.
|
|
109
|
-
* `fighter_df` — The fighter information dataframe containing a `"Fighter Link"` column.
|
|
110
|
-
* `r` — Initial Elo rating for each fighter. Defaults to `1500`.
|
|
111
|
-
* `k` — K-factor controlling how much ratings change after each fight. Defaults to `30`.
|
|
112
|
-
* `s` — Scaling factor used when calculating expected scores. Defaults to `400`.
|
|
113
|
-
|
|
114
|
-
### Returns
|
|
115
|
-
|
|
116
|
-
The function returns a tuple containing:
|
|
117
|
-
|
|
118
|
-
1. `fighter_elo` — A dictionary mapping each fighter's UFCStats link to their final Elo rating.
|
|
119
|
-
2. `elo` — A dataframe containing fight-level Elo ratings. `"Fighter 1 Elo"` and `"Fighter 2 Elo"` represent each fighter's rating immediately before the corresponding fight.
|
|
120
|
-
|
|
121
|
-
### Example
|
|
122
|
-
|
|
123
|
-
```python
|
|
124
|
-
import ufcdata as ufc
|
|
125
|
-
|
|
126
|
-
data = ufc.get_data()
|
|
127
|
-
|
|
128
|
-
fight_df = data["past_fights"].copy()
|
|
129
|
-
fighter_df = data["fighter_bio"].copy()
|
|
130
|
-
|
|
131
|
-
fighter_elo, fight_elo = ufc.get_elo(
|
|
132
|
-
fight_df,
|
|
133
|
-
fighter_df
|
|
134
|
-
)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The resulting `fight_elo` dataframe can then be used to examine or incorporate pre-fight Elo ratings into analysis and predictive models.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
### Important
|
|
141
|
-
|
|
142
|
-
`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.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## Glicko-2
|
|
146
|
-
Glicko-2 extends the Elo system by incorporating rating deviation (RD) and volatility. Unlike Elo, which represents a fighter's ability with a single rating, Glicko-2 also estimates the uncertainty and consistency of that rating.
|
|
147
|
-
|
|
148
|
-
fighter_glicko, fight_glicko = ufc.glicko_2(
|
|
149
|
-
fight_df,
|
|
150
|
-
fighter_df
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
## Online Sources
|
|
154
|
-
|
|
155
|
-
Odds and birthplace data was obtained from https://www.tapology.com
|
|
156
|
-
|
|
157
|
-
Venue and attendance data was obtained from https://en.wikipedia.org/wiki/List_of_UFC_events
|
|
158
|
-
|
|
159
|
-
All other data was obtained from http://ufcstats.com
|
|
160
|
-
|
|
161
|
-
## Discrepancies in Data
|
|
162
|
-
|
|
163
|
-
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.
|
|
164
|
-
|
|
165
|
-
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.
|
|
166
|
-
|
|
167
|
-
## Update Frequency
|
|
168
|
-
|
|
169
|
-
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.
|
|
170
|
-
|
|
171
|
-
A second update occurs 24 hours after the start of the broadcast. This update captures the finalized event, fight, and round data, as well as information on upcoming events and fights.
|
|
172
|
-
|
|
173
|
-
Changes occurring between these scheduled updates are not automatically captured. Users are responsible for manually updating the dataset if they require the most current data for analysis or prediction.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|