Tillykke du er i anden uge af din ansættelse i den norske virksomhed, som operere med data science. Din leder var meget tilfreds med analysen fra sidste uge. Vedkommende mener at vi skal begynde at kigge lidt bredere på spillere.
Vores kunder er interesseret i at undersøge hvordan forholdet mellem spillernes placeringer på banen er i forhold til deres fysiske egenskaber. I og med det gik så godt i sidste uge har vi fået ansvaret for at lave en analyse der viser dette.
In [ ]:
#PURE PYTHON!!!!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from sklearn import cluster
In [ ]:
# Run the datacleaning notebook to get all the variables
%run 'Teknisk Tirsdag - Data Cleaning.ipynb'
Diskutere med andre, i 5 min, om hvorfor det kan være en god idé, at bruge clustering til at vise spillerpositioner. Hvilken viden kan vi få ud af det? Kan der være problemer med denne analyse og hvorfor? Kom gerne med eksempler på om vi kan overføre denne tankegang til det virkelige verden.
Vi tager en kort gennemgang af denne opgave, inden vi forsætter med resten.
Som data scientist er det uundgåeligt, ikke at skulle få beskidte fingre og arbejde med noget kode. I denne øvelse vil vi bruge nogle af Python's funktioner til at finde hhv. det indeks hvor de fysiske attributter starter og slutter. Bare rolig: Hvis alt andet fejler, kan vi også tælle os frem til løsningen :-)
Lidt om Pandas dataframes
Et pandas dataframe har en attribut som indeholder en liste af kolonner. Man tilgår attributten som på mange andre kodesprog ved at bruge punktum efter objektes navn fx. x.attribut.
Først skal vi indeksere elementerne i den udtrukket liste. Der er mange veje til målet, dette er nok den nemmeste
Vores næste opgave er at finde de fysiske egenskaber; heldigvis ligger alle de fysiske egenskaber i rækkefølge, så vi har kun brug for den første og den sidste, da vi derefter kan udtrække dem alle.
Nu skal resultatet fra den første og anden opgave kombineres. Udtræk indekset for hhv. start og slut kolonnen.
Tip: Som i mange Objekt Orienteret Sporg findes der mange veje til målet. Dine bedste redskaber til at komme til målet er: Din sunde fornuft og Google...
Hvert element i vores liste tager formen: (indeks, kolonne_med_første_fysisk_egenskab), hvis vi kalder et element i listen for x, kan indeks f.eks. tilgås ved x[0]. Et godt sted at starte er at bruge filter filter metoden.
In [ ]:
# Vi bruger alle spillere i FIFAs katelog.
attribute_df = df.copy()
position_df = attribute_df[['Name','Preferred Positions', 'Age', 'Nationality']]
In [ ]:
attribute_df = attribute_df.set_index(['Name']);
position_df = position_df.set_index('Name');
In [ ]:
attribute_df.head()
In [ ]:
position_df.head()
In [ ]:
enumareted_cols = None ### INDSÆT KODE HER ###
# For vist liste, fjern havelågen:
# enumareted_cols
In [ ]:
attribute_start_col = None ### INDSÆT FØRSTE FYSISKE EGENSKAB ###
print(attribute_start_col)
In [ ]:
attribute_end_col = None ### INDSÆT FØRSTE FYSISKE EGENSKAB ###
print(attribute_end_col)
In [ ]:
# Vi ordner resten for dig.
attribute_col = list(range(attribute_start_col, attribute_end_col+1, 1))
In [ ]:
attribute_df = attribute_df.iloc[:, attribute_col]
In [ ]:
print(list(attribute_df.columns))
In [ ]:
attribute_df.head()
In [ ]:
attribute_df.describe()
In [ ]:
position_df.loc[:,'position_list'] = np.array(position_df['Preferred Positions'].str.split(' ').tolist())
In [ ]:
position_df.head()
Da alt databearbejdningen fandt sted i sidste uge, er der blot tilbage, at kører den egentlig analyse. Til dette har vi KMeans. Denne algoritme bruger et afstandsmål som reference til at skabe grupperinger. Ud fra afstandsmålne kan vi skabe nogle cluster centre, centriods.
Inden vi kommer så vidt, er vi nødt til at omforme positionerne således de kan sammenlignes med det resultat vi får fra vores Kmeans-algoritme.
In [ ]:
from sklearn.preprocessing import MultiLabelBinarizer
from scipy.spatial.distance import cdist
mlb = MultiLabelBinarizer()
labels = mlb.fit(position_df['position_list'])
In [ ]:
real_position_list = [i for i in labels.classes_ if i != '']
position_df[labels.classes_] = pd.DataFrame(labels.transform(position_df['position_list']), index=position_df.index, columns=labels.classes_)
del position_df['']
del position_df['position_list']
position_df.head()
Kør selve KMeans
In [ ]:
def run_kmeans(attributes, lables, k = 20):
kmeans = cluster.KMeans(n_clusters= k, random_state= True)
model = kmeans.fit(attributes)
lables['prediction'] = model.predict(attributes)
# Vi indsætter centerpunkterne i en midlertidig dataframe Z
Z = pd.DataFrame(model.cluster_centers_[lables['prediction']], columns=attributes.columns)
#Beregner afstand
lables['distance'] = np.linalg.norm(attributes.as_matrix()-Z.as_matrix(), axis=1)
return lables
n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
position_df = run_kmeans(attribute_df, position_df, n_clusters)
In [ ]:
position_df.head()
In [ ]:
def print_cluster_til_position(df, columns, **kwargs):
size = kwargs.get('figsize',(20, 10))
grouped_by_position_df = df.groupby('prediction', as_index=True)[columns].sum()
f, ax = plt.subplots(1,1,figsize=size)
sb.set(style="ticks")
sb.heatmap(grouped_by_position_df, annot=True, fmt="d", linewidths=1., ax=ax)
ax.set_xlabel('Spillerpositioner', fontsize= 20)
ax.set_ylabel('Cluster', fontsize= 12, rotation='horizontal')
plt.show()
In [ ]:
print_cluster_til_position(position_df,real_position_list)
In [ ]:
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
sb.violinplot(x='prediction', y='distance', data=position_df, orient='v', ax=ax)
ax.set_xlabel('Cluster', fontsize= 18)
ax.set_ylabel('Afstand', rotation='horizontal', fontsize= 20)
plt.show()
Den øverste figur viser fordelingen mellem hvad KMeans grupperer spillerne som (predictions opad y-aksen), i forhold til deres fortrukne spillerpositioner (ud af x-aksen). Den nederste figur viser fordelingen af afstande for hvert cluster. Der hvor hver "violin" buler ud viser hvor den største koncentration af punkter ligger.
Her er et billedet over positionerne med deres respektive forkortelser. Dette skulle gerne hjælpe i tabellen ovenfor.
Diskutere hvad tallene i tabellen betyder. Giver dette resultat mening?
Første forsøg med at køre KMeans var ikke så sucessfuldt som håbet. Nogle af de fejlkilder som er i eksemplet er at flere spillere har mange fortrukne positioner; mange af disse positioner er tæt på hinanden f.eks. LWB og LB.
Generelt ses det, at vi har variationer i positioner med 3-bogstavskombinationer, dette kunne vi rette op på. En anden fejlkilde er at vi tager rådata ind. Dette kan ødelægge vores analyse, ved f.eks. at indfører gigantiske afstande i visse clustre. Normalt fortages der en standardisering af ens data.
In [ ]:
from sklearn.preprocessing import scale
In [ ]:
simplied_position_df = position_df.copy()
del simplied_position_df['prediction']
# Samler CAM og CDM til CM
simplied_position_df['CM'] = (simplied_position_df['CAM'] &# INDSÆT BINÆR OPERATOR
simplied_position_df['CM'] &# INDSÆT BINÆR OPERATOR
simplied_position_df['CDM']
)
del simplied_position_df['CAM']
del simplied_position_df['CDM']
simplied_position_df['RB'] = (simplied_position_df['RB'] &# INDSÆT BINÆR OPERATOR
simplied_position_df['RWB'])
simplied_position_df['LB'] = (simplied_position_df['LB'] &# INDSÆT BINÆR OPERATOR
simplied_position_df['LWB'])
del simplied_position_df['RWB']
del simplied_position_df['LWB']
simplied_position_df.head()
Det ses nu at vi har kun 2-bogstavskoder, og derved har vi simplificeret vores problem meget.
In [ ]:
attribute_df.head()
In [ ]:
scaled_attributes_df = pd.DataFrame(
scale(attribute_df),
columns=attribute_df.columns,
index=attribute_df.index
)
scaled_attributes_df.head()
For at teste om gennemsnittet er 0 og standardafvigelsen er 1.0 kan vi igen bruge describe()
In [ ]:
scaled_attributes_df.describe()
Det ser jo fornuftigt ud
Den forrige analyse var ikke særlig tilfredsstillende - Vi havde ingen idé hvilket antal clusters vi skulle bruge.
Som data scientist har vi nogle værktøjer, som vi kan bruge til at træffe en beslutning på et mere oplysende grundlag.
Den mest intuitive metode er at undersøge hvor god Kmeans er til at cluster data. KMeans outputter, foruden grupperingerne, også et mål for hvor god modellen er. Kmeans basere dette mål på kvadreret afstand fra alle datapunkter til deres respektive cluster-centrum.
Dvs. detso lavere en værdi, desto tættere ligger datapunkterne på centrum, og derved er modellen mere forklarende.
Ved at køre flere KMeans, med forskellig antal clusters kan man få et billedet af hvor godt modellen udvikler sig. Det man som data scientist gør, er at kigge efter et knæk i grafen, eller den såkaldte albue, hvor en øgelse af antal cluster ikke påvirker målet i så høj grad.
Undersøg nedenstående graf, og diskutere hvilket antal af cluster I ville bruge i den nye analyse. Giver det mere mening nu?
In [ ]:
iterations = 60
r = range(5, iterations+5, 5)
km = [cluster.KMeans(n_clusters=i) for i in r]
score = [i.fit(scaled_attributes_df).score(scaled_attributes_df) for i in km]
In [ ]:
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
ax.grid(True, axis='x')
plt.plot(np.array(r), -1*np.array(score), 'ro')
plt.xticks(range(5, iterations+5, 5))
plt.xlabel('Antal clustre', fontsize= 20)
plt.ylabel('z', rotation= 'horizontal', fontsize= 20)
plt.title('Værdien af hvor godt hver Kmeans klarer sig')
plt.show()
Det er tydeligt at se, at Elbow-metoden bare vil gå efter en lavere z-score. Dette er også menningen, men håbet var at vi i denne analyse ville se nogle plateau'er, gerne i den lave ende af antal clustre.
På trods af analysen mangler, skal der vælges et antal clustre.
In [ ]:
n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
simplied_position_df = run_kmeans(scaled_attributes_df, simplied_position_df, n_clusters)
simplied_position_df.head()
In [ ]:
positions_list = "CB CF CM GK LB LM LW RB RM RW ST".split(' ')
print_cluster_til_position(simplied_position_df, positions_list)
Giver dit nye resultat mere mening? Hvordan fordeler de sig?
Som en lidt mere advanceret metode til at validere om ens model gør det godt, kan vi bruge entropi.
Entropi stammer fra fysik og beskriver mængden af uorden i Universet. Når entropien er 0 vil Universet være perfekt ordnet, omvendt når vi har total kaos vil entropien være meget høj. Entropi bruges også i andre områder, heriblandt Machine Leraning.
Vi kan bruge dette begreb i vores cluster analyse. Antag at hvert cluster er et 'univers', og det perfekte univers indeholder kun elementer som er ens, dvs. én spillertype pr. cluster.
Mere realistisk, men stadig ideelt, vil vores cluster bestå af en hoved spillertype, med nogle få outliers, i form af andre spillertype. Det vi ønsker er at finde det antal cluster som giver den laveste entropi.
Kør nedenstående celler. Giver analysen med entropi det samme som med elbow-metoden, diskutere?
In [ ]:
def get_best_model(attributes, lables, max_k = 60):
positions_list = "CB CF CM GK LB LM LW RB RM RW ST".split(' ')
all_players = lables[positions_list].sum().sum()
resultater = []
def entropi(pdf):
n = pdf.sum(axis=1) # rækkevis summering
return pdf.apply(lambda x: -x/n*np.log(x/n)).sum(axis=1, skipna=True)
for i in range(5, max_k, 5):
km = cluster.KMeans(n_clusters=i, random_state= True)
lables['prediction'] = km.fit_predict(attributes)
grouped_df = lables.groupby(
'prediction', as_index=True)[positions_list].sum()
weighted_sum_of_squared_entropi = (grouped_df.sum(axis=1)*entropi(grouped_df)).sum()/all_players
print('Antal cluster: {}, entropi: {}'.format(i, weighted_sum_of_squared_entropi))
resultater.append((i,weighted_sum_of_squared_entropi))
return resultater
In [ ]:
entropi = get_best_model(scaled_attributes_df,simplied_position_df)
In [ ]:
fig = plt.figure(figsize=(20,10))
ax = fig.gca()
ax.grid(True, axis='x')
plt.plot(list(map(lambda x: x[0], entropi)), list(map(lambda x: x[1], entropi)),'ro')
plt.xticks(range(5, iterations+5, 5))
plt.xlabel('Antal clustre', fontsize= 20)
plt.ylabel('H', fontsize= 20, rotation= 'horizontal')
ax.set_xticklabels(ax.get_xticks(), fontsize= 15)
ax.set_yticklabels(ax.get_yticks(), fontsize= 15)
plt.title('Entropien som funktion af antal clustre')
plt.show()
In [ ]:
n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
entropi_position_df = run_kmeans(scaled_attributes_df, simplied_position_df, n_clusters)
print_cluster_til_position(entropi_position_df, positions_list, figsize=(20,15))
Som en sidste analyse kan vi udtage et eller to clustre og se hvordan afstanden, fra datapunkt til centrum, forholder sig i clusteret.
Udvælg et cluster, som har en høj entropi, dvs. der er mange forskellige spillertyper i clusteret, og undersøg hvordan afstanden forholder sig. Brug nedenstående celle til at visualiser afstanden
In [ ]:
n_cluster = None ###INDSÆT ET CLUSTERTAL HER ###
entropi_position_df[entropi_position_df['prediction'] == n_cluster]['distance']
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
sb.distplot(entropi_position_df[entropi_position_df['prediction'] == n_cluster]['distance'], kde=False, ax=ax)
ax.set_xlabel('Afstand', fontsize= 20)
ax.set_xticklabels(ax.get_xticks(), fontsize= 15)
ax.set_yticklabels(ax.get_yticks(), fontsize= 15)
ax.set_ylabel('Antal', rotation='horizontal', fontsize= 20)
plt.show()
Udtræk den eller de spillere som I mener, baseret på histogrammet, skulle være en outlier. Hvem er det? Kan vi se hvad det er som gør dem til outliers?
In [ ]:
distance_boundary = None ### INDSÆT ET AFSTANDSMÅL HER ###
outliers = entropi_position_df[(entropi_position_df['prediction'] == n_cluster) &
(entropi_position_df['distance'] > distance_boundary)]
display(attribute_df[attribute_df.index.isin(outliers.index)])
display(outliers)
In [ ]: