Classifying Fighter Types with K-Means Clustering
In the UFC we often see different types of fighters, where some are mainly strikers, some are mainly grapplers, and some are a hybrid of…
Classifying Fighter Types with K-Means Clustering
In the UFC we often see different types of fighters, where some are mainly strikers, some are mainly grapplers, and some are a hybrid of both. Using Python and fight data, can we group fighters into these classes? The answer is yes…kind of. The biggest problem with our goal is that these classifications are subjective — there is no metric that must be achieved, after which someone is a striker, and fans would likely argue about how to classify certain people. What metrics are important to consider? How high must they be to classify a fighter as one thing over another?
This is only a preliminary run at trying to classify fighters (to try to get some data practice), seeing if some different techniques work and if there is any way at all to separate them. However, I’m going to try to make sure my classifications aren’t completely separate from reality.
Let’s get started by importing a dataset from Kaggle (where else?):
import kagglehub
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import plotly.express as px
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# Read CSV file to a pandas df
data_csv = pd.read_csv('/content/raw_fighter_details.csv')
# Download the dataset from kaggle
path = kagglehub.dataset_download("rajeevw/ufcdata")
# Use $ to keep python variable in the terminal command
!ls $path # make sure there are files in the path as we expect
!cp -r $path/* /content/ # move to /content folder
# Read CSV file to a pandas df
data_csv = pd.read_csv('/content/raw_fighter_details.csv')
From here, I want to get rid of columns that don’t have anything to do with fighting style (like Height or Weight). I also want to rename the columns so that the information is clearer:
# Take away some columns and then rename the ones we have so the info is clearer
fighter_df = data_csv[['fighter_name', 'SLpM', 'Str_Acc', 'SApM', 'Str_Def', 'TD_Avg', 'TD_Acc', 'TD_Def', 'Sub_Avg']]
fighter_df = fighter_df.rename(columns={'SLpM': 'Strikes Landed per Minute','Str_Acc': 'Strike Accuracy',
'SApM': 'Strikes Attempted per Minute', 'Str_Def': 'Strike Defense',
'TD_Avg': 'Takedown Average', 'TD_Acc': 'Takedown Accuracy',
'TD_Def': 'Takedown Defense', 'Sub_Avg': 'Submission Average'})
Next, I want to do data cleaning to make sure that all of the columns are numerical, as well as make sure that no rows are only 0 and make sure there are no duplicate names:
# Change the data types so everything is numerical
str_cols = ['Strike Accuracy', 'Strike Defense', 'Takedown Accuracy', 'Takedown Defense']
for col in str_cols:
fighter_df[col] = fighter_df[col].replace('%', '', regex=True).astype(float) / 100
# fighter_df.dtypes
# Get rid of any rows with all 0 and check for duplicate names
fighter_df = fighter_df[fighter_df.iloc[:, 1:].sum(axis=1) != 0]
if fighter_df['fighter_name'].duplicated().sum() == 0:
pass
else:
print('We have duplicate names! \n')
fighter_df.head(5)

Table 1
From here, I thought it would be helpful to create a few new metrics so that we could have ratios between takedowns and strikes. This way, we have comparisons between the two groups of numbers:
# Try to make some new metrics that can be used to better show fighting style
fighter_df.loc[:, 'Strikes per Takedown'] = fighter_df.apply(lambda x: x['Strikes Attempted per Minute'] / (x['Takedown Average'] + 1), axis=1)
fighter_df.loc[:, 'Takedown Attempts'] = fighter_df.apply(lambda x: x['Takedown Average'] / x['Takedown Accuracy'] if x['Takedown Accuracy'] > 0 else 0, axis=1)
fighter_df.loc[:, 'Submission Success Rate'] = fighter_df.apply(lambda x: x['Takedown Average'] / (x['Submission Average'] + 1), axis=1)
Finally, we can move on to our clustering. Since the data is not all on the same scale, we first scale it. Then, we can use PCA to find the most important components of the data (and to make it so that we can graph and visualize everything). After this, let’s use K-Means clustering with 3 different groups (“Mostly Striker”, “Mostly Grappler”, “Hybrid”):
# Scaling the data
scaler = StandardScaler()
features = list(fighter_df.columns[1:])
scaled_features = scaler.fit_transform(fighter_df[features])
# Applying PCA
pca = PCA(n_components=3, random_state=401)
pca_features = pca.fit_transform(scaled_features)
# K-Means Clustering
kmeans = KMeans(n_clusters=3, random_state=42)
fighter_df['Cluster'] = kmeans.fit_predict(pca_features)
# Graph Visualize PCA + Clusters
plt.figure(figsize=(10,6))
sns.scatterplot(x=pca_features[:, 0], y=pca_features[:, 1], hue=fighter_df['Cluster'], palette='Set1')
plt.title('PCA-Reduced Clustering of UFC Fighters')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.show()

Figure 1
Since we have 3 principal components, it would be helpful to use a 3D graph, so let’s implement that for better visualization:
# Use Plotly to make a 3D graph
pca_df = pd.DataFrame({
'PC1': pca_features[:, 0],
'PC2': pca_features[:, 1],
'PC3': pca_features[:, 2],
'Cluster': fighter_df['Cluster']
})
# Plotly 3D scatter plot
fig = px.scatter_3d(
pca_df,
x='PC1', y='PC2', z='PC3',
color='Cluster',
title='3D PCA-Reduced Clustering of UFC Fighters',
symbol='Cluster'
)
fig.show()

Figure 2
Since our clusters are only labeled 0, 1, or 2, we can look at some fighters whose style we know so that we can get our labeling:
display(fighter_df[fighter_df['fighter_name'].str.contains('Max H')])
display(fighter_df[fighter_df['fighter_name'].str.contains('Khabib N')])

Table 2
We can see that Max Holloway is labeled as a “1” while Khabib is a “0,” which tells us that strikers should be 1, grapplers should be 0, and hybrid would then be 2. Now we can put that into our table, and focus our sights on names and styles:
fighter_df['Cluster'] = fighter_df['Cluster'].map(lambda x: 'Mostly Grappler' if x == 0 else ('Mostly Striker' if x == 1 else 'Hybrid'))
classification_df = fighter_df[['fighter_name', 'Cluster']]
classification_df

Table 3
Let’s do testing on the labels from our clusters by looking at a few famous fighters:
classification_df[classification_df['fighter_name'].str.contains('Dustin Po|Israel Ad|Volkanov|Merab|Jon Jones|Islam|Georges S|Charles Ol')]

Table 4
Based on the few fighters chosen, we can see that a few of our labels are spot on. However, a few are also off (Charles Oliveira). Overall though, the classifications seem to be ok, especially given that this analysis is not as in-depth as it could be. It seems to be a good starting point for future projects!
메타데이터
- post_id
- bc5d52cb1180
- slug
- classifying-fighter-types-with-k-means-clustering-bc5d52cb1180
- url
- https://medium.com/@dmanis13/classifying-fighter-types-with-k-means-clustering-bc5d52cb1180
- canonical_url
- https://medium.com/@dmanis13/classifying-fighter-types-with-k-means-clustering-bc5d52cb1180
- author_url
- https://medium.com/@dmanis13
- status
- ok
- fetched_at
- 2026-06-26 06:47:43