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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UFCData
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Obtain UFC data and functions to manipulate it
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE.md
@@ -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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UFCData
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Obtain UFC data and functions to manipulate it
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE.md
@@ -8,6 +8,7 @@ UFCData.egg-info/requires.txt
8
8
  UFCData.egg-info/top_level.txt
9
9
  ufcdata/__init__.py
10
10
  ufcdata/data.py
11
+ ufcdata/fighter.py
11
12
  ufcdata/ratings.py
12
13
  ufcdata/search.py
13
14
  ufcdata.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "UFCData"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "Obtain UFC data and functions to manipulate it"
5
5
  requires-python = ">=3.10"
6
6
 
@@ -0,0 +1,4 @@
1
+ from .data import get_data
2
+ from .search import search
3
+ from .ratings import get_elo
4
+ from .fighter import get_fighter_history
@@ -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.
@@ -1,3 +0,0 @@
1
- from .data import get_data
2
- from .search import search
3
- from .ratings import get_elo
File without changes
File without changes
File without changes
File without changes
File without changes