Skip to content

Commit 9bee415

Browse files
author
Jean-Matthieu Schertzer
committed
add clustering notebook
1 parent 1975176 commit 9bee415

File tree

2 files changed

+242
-2
lines changed

2 files changed

+242
-2
lines changed

notebooks/demo_clustering.ipynb

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Skope-Rules Demo: application to cluster description"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"This notebook shows a demo of skope-rules applied to identify segments after a clustering analysis. The dataset is available here: https://www.kaggle.com/thec03u5/fifa-18-demo-player-dataset/data. It describes football players and their performance attributes.\n",
15+
"\n",
16+
"This notebook performs a hierarchical clustering with 4 segments. Skope-rules is used to interpret each segments by a 1(segment)-vs-all approach.\n",
17+
"\n",
18+
"The notebook is structured into 4 parts:\n",
19+
"1. Imports\n",
20+
"2. Data preparation\n",
21+
"3. Clustering of data\n",
22+
"4. Interpretation of clusters"
23+
]
24+
},
25+
{
26+
"cell_type": "markdown",
27+
"metadata": {},
28+
"source": [
29+
"## 1. Imports"
30+
]
31+
},
32+
{
33+
"cell_type": "code",
34+
"execution_count": 1,
35+
"metadata": {},
36+
"outputs": [
37+
{
38+
"name": "stderr",
39+
"output_type": "stream",
40+
"text": [
41+
"/Users/jms/SquareNetFishing/envclean/lib/python3.6/site-packages/IPython/core/interactiveshell.py:2728: DtypeWarning: Columns (23,35) have mixed types. Specify dtype option on import or set low_memory=False.\n",
42+
" interactivity=interactivity, compiler=compiler, result=result)\n"
43+
]
44+
}
45+
],
46+
"source": [
47+
"# Import skope-rules\n",
48+
"from skrules import SkopeRules\n",
49+
"\n",
50+
"# Import librairies\n",
51+
"import pandas as pd\n",
52+
"from sklearn.cluster import AgglomerativeClustering\n",
53+
"import warnings\n",
54+
"\n",
55+
"# Import Titanic data\n",
56+
"data = pd.read_csv('../data/CompleteDataset.csv')"
57+
]
58+
},
59+
{
60+
"cell_type": "markdown",
61+
"metadata": {},
62+
"source": [
63+
"## 2. Data preparation"
64+
]
65+
},
66+
{
67+
"cell_type": "code",
68+
"execution_count": 2,
69+
"metadata": {},
70+
"outputs": [],
71+
"source": [
72+
"data = data.query(\"Overall>=85\") # Select players with an overall attribute larger than 85/100.\n",
73+
"\n",
74+
"column_to_keep = ['Name', 'Acceleration', 'Aggression', 'Agility', 'Balance', 'Ball control',\n",
75+
" 'Composure', 'Crossing', 'Curve', 'Dribbling', 'Finishing',\n",
76+
" 'Free kick accuracy', 'GK diving', 'GK handling', 'GK kicking',\n",
77+
" 'GK positioning', 'GK reflexes', 'Heading accuracy', 'Preferred Positions']\n",
78+
"data = data[column_to_keep] # Keep only performance attributes and names.\n",
79+
"\n",
80+
"data.columns = [x.replace(' ', '_') for x in data.columns] # Replace white spaces in the column names\n",
81+
"\n",
82+
"feature_names = data.drop(['Name', 'Preferred_Positions'], axis=1).columns.tolist()"
83+
]
84+
},
85+
{
86+
"cell_type": "markdown",
87+
"metadata": {},
88+
"source": [
89+
"## 3. Clustering of data"
90+
]
91+
},
92+
{
93+
"cell_type": "code",
94+
"execution_count": 3,
95+
"metadata": {},
96+
"outputs": [],
97+
"source": [
98+
"clust = AgglomerativeClustering(n_clusters=4) #with euclidian distance and ward linkage\n",
99+
"\n",
100+
"data['cluster'] = clust.fit_predict(data.drop(['Name', 'Preferred_Positions'], axis=1))"
101+
]
102+
},
103+
{
104+
"cell_type": "markdown",
105+
"metadata": {},
106+
"source": [
107+
"## 4. Interpretation of clusters"
108+
]
109+
},
110+
{
111+
"cell_type": "markdown",
112+
"metadata": {},
113+
"source": [
114+
"The 4 clusters obtained have to be interpreted. It can be done with a graphical analysis (parallel coordinates, mean comparison, reasoning with examples of players). This notebook presents an approach which consists in looking for a way to separate a cluster from the rest of population (and to repeat the process for each cluster). \n",
115+
"\n",
116+
"With this 1-vs-all approach, it becomes a supervized binary classification task. Skope-rules is very useful because a good interpretation of a cluster is based on a simple expression of the frontier which isolates the cluster. And that is skope-rules's scope!"
117+
]
118+
},
119+
{
120+
"cell_type": "code",
121+
"execution_count": 4,
122+
"metadata": {},
123+
"outputs": [
124+
{
125+
"name": "stdout",
126+
"output_type": "stream",
127+
"text": [
128+
"Cluster 0:\n",
129+
"[('Heading_accuracy > 58.5 and Free_kick_accuracy > 56.0 and Agility <= 81.5', (0.93548387096774188, 0.8529411764705882, 10))]\n",
130+
"Cluster 1:\n",
131+
"[('Agility > 81.5 and Aggression <= 76.5 and Heading_accuracy <= 84.0', (1.0, 0.77419354838709675, 2))]\n",
132+
"Cluster 2:\n",
133+
"[('Heading_accuracy > 82.5 and Curve <= 61.5', (1.0, 0.7857142857142857, 8)), ('Heading_accuracy > 82.5 and Free_kick_accuracy <= 56.5', (1.0, 0.7857142857142857, 2))]\n",
134+
"Cluster 3:\n",
135+
"[('GK_reflexes > 59.0', (1.0, 1.0, 2))]\n"
136+
]
137+
}
138+
],
139+
"source": [
140+
"warnings.filterwarnings('ignore') #To deals with warning raised by max_samples=1 (see below).\n",
141+
"#With max_samples=1, there is no Out-Of-Bag sample to evaluate performance (it is evaluated on all samples. \n",
142+
"#As there are less than 100 samples and this is a clustering-oriented task, the risk of overfitting is not \n",
143+
"#dramatic here.\n",
144+
"\n",
145+
"i_cluster = 0\n",
146+
"for i_cluster in range(4):\n",
147+
" X_train = data.drop(['Name', 'Preferred_Positions', 'cluster'], axis=1)\n",
148+
" y_train = (data['cluster']==i_cluster)*1\n",
149+
" skope_rules_clf = SkopeRules(feature_names=feature_names, random_state=42, n_estimators=5,\n",
150+
" recall_min=0.5, precision_min=0.5,\n",
151+
" max_samples=1., max_depth=3)\n",
152+
" skope_rules_clf.fit(X_train, y_train)\n",
153+
" print('Cluster '+str(i_cluster)+':')\n",
154+
" #print(data.query('cluster=='+str(i_cluster))[['Name', 'Preferred_Positions']])\n",
155+
" print(skope_rules_clf.rules_)"
156+
]
157+
},
158+
{
159+
"cell_type": "markdown",
160+
"metadata": {},
161+
"source": [
162+
"In cluster 0, we find players with **good heading and free kick accuracy**, but which are **not the best agile players** (<= 81/100). This rule is a good description of cluster 0: it captures 85% of cluster 1, with a precision of 93% (7% of players described by the rule are not in cluster 0. The third term of the performance term (10) is the number of time that this rule was extracted from the trees built during skope-rules' fitting.\n",
163+
"\n",
164+
"In cluster 1, we find **very agile players** which are **not** the **most aggressive** and **accurate with their heads**. This rule is very precise but misses 23% of this cluster.\n",
165+
"\n",
166+
"In cluster 2, we find players **accurate with their heads** but with **less skills for dribbling** (Curve). Note that an alternative rule is proposed here, with similar performances.\n",
167+
"\n",
168+
"In cluster 3, we find players who have **good goal-keeper reflexes**. This rule perfectly defines the cluster (100% precision, 100% recall). This is the goal-keeper cluster."
169+
]
170+
},
171+
{
172+
"cell_type": "code",
173+
"execution_count": 5,
174+
"metadata": {},
175+
"outputs": [
176+
{
177+
"name": "stdout",
178+
"output_type": "stream",
179+
"text": [
180+
"5 players from cluster 0:\n",
181+
"['M. Hamšík', 'Alex Sandro', 'Casemiro', 'K. Benzema', 'Z. Ibrahimović']\n",
182+
"\n",
183+
"5 players from cluster 1:\n",
184+
"['H. Mkhitaryan', 'David Silva', 'F. Ribéry', 'J. Rodríguez', 'P. Dybala']\n",
185+
"\n",
186+
"5 players from cluster 2:\n",
187+
"['Pepe', 'K. Glik', 'G. Chiellini', 'V. Kompany', 'Piqué']\n",
188+
"\n",
189+
"5 players from cluster 3:\n",
190+
"['M. ter Stegen', 'D. Subašić', 'M. Neuer', 'K. Navas', 'H. Lloris']\n",
191+
"\n"
192+
]
193+
}
194+
],
195+
"source": [
196+
"for i_cluster in range(4):\n",
197+
" print('5 players from cluster '+str(i_cluster)+':')\n",
198+
" print(data.query(\"cluster==\"+str(i_cluster))['Name'].sample(5, random_state=42).tolist()) # Get 5 random players per cluster\n",
199+
" print()"
200+
]
201+
},
202+
{
203+
"cell_type": "markdown",
204+
"metadata": {},
205+
"source": [
206+
"In brief, **cluster 0** tends to concentrate strikers and midfielders talented with their heads. **Cluster 1** tends to group other midfielders. **Cluster 2** focuses on defenders while goal-keepers are gathered in **cluster 3**!\n",
207+
"\n",
208+
"For a visual analysis (kind of parallel coordinates) of these clusters, you can check this Kaggle kernel: https://www.kaggle.com/malimo1024/clustering-top-players"
209+
]
210+
},
211+
{
212+
"cell_type": "code",
213+
"execution_count": null,
214+
"metadata": {},
215+
"outputs": [],
216+
"source": []
217+
}
218+
],
219+
"metadata": {
220+
"kernelspec": {
221+
"display_name": "Python 3",
222+
"language": "python",
223+
"name": "python3"
224+
},
225+
"language_info": {
226+
"codemirror_mode": {
227+
"name": "ipython",
228+
"version": 3
229+
},
230+
"file_extension": ".py",
231+
"mimetype": "text/x-python",
232+
"name": "python",
233+
"nbconvert_exporter": "python",
234+
"pygments_lexer": "ipython3",
235+
"version": "3.6.2"
236+
}
237+
},
238+
"nbformat": 4,
239+
"nbformat_minor": 2
240+
}

0 commit comments

Comments
 (0)