Résumé Ce scénario décrit le traitement classique, la fouille, de données pour la gestion de la relation client (GRC); exploration de données bancaires par des méthodes uni, bi et multidimensionnelles: ACP, AFCM puis segmentation de clientèle par classification non-supervisée k-means, CAH et représentation, interprétation des classes.
Ce scénatio propose de décrire un jeu de données bancaires en utilisant les principales méthodes de statistique exploratoire multidimensionnelle: analyse en composantes principales, analyse des correspondances simple et multiple, classification non supervisée. L'objectif principal, classqiue en marketing, est de produire une segmentation de clientèle, c'est-à-dire une répartition en classes homogènes des clients en fonction de leur comportement bancaire. C'est aussi la mise en oeuvre d'une démarche classique permettant d'affiner sa compréhension des données dans l'idée de construire un score d'appétence pour la carte visa premier. Ce deuxième objectif est intégré à la saison 3 (Apprentissage Statistique). Il s'agit du score d'appétence de la carte Visa Premier mais ce pourrait être un score d'attrition ({\it churn}) d'un opérateur téléphonique ou encore un score de défaillance d'un emprunteur ou de faillite d'une entreprise; les outils de modélisation sont les mêmes et sont très largement utilisés dans tout le secteur tertiaire pour l'aide à la décision.
Une autre version du même scénario propose d'utiliser le logiciel SAS alors que ce calepin propose une solution en Python utilisant la librairie Scikit-learn.
La liste des variables est issue d'une base de données retraçant l'historique mensuel bancaire et les caractéristiques de tous les clients. Un sondage a été réalisé afin d'alléger les traitements ainsi qu'une première sélection de variables. Les variables contenues dans le fichier initial sont décrites dans le tableau ci-dessous. Elles sont observées sur 1425 clients.
Tableau: Liste des variables et de leur libellé Attention, ils sont finalement écrits en majuscules dans les programmes.
Identifiant | Libellé |
---|---|
matric |
Matricule (identifiant client) |
depts |
Département de résidence |
pvs |
Point de vente |
sexeq |
Sexe (qualitatif) |
ager |
Age en années |
famiq |
Situation familiale: Fmar Fcel Fdiv Fuli Fsep Fveu |
relat |
Ancienneté de relation en mois |
pcspq |
Catégorie socio-professionnelle (code num) |
quals |
Code "qualité" client évalué par la banque |
GxxGxxS |
plusieurs variables caractérisant les interdits bancaires |
impnbs |
Nombre d'impayés en cours |
rejets |
Montant total des rejets en francs |
opgnb |
Nombre d'opérations par guichet dans le mois |
moyrv |
Moyenne des mouvements nets créditeurs des 3 mois en Kf |
tavep |
Total des avoirs épargne monétaire en francs |
endet |
Taux d'endettement |
gaget |
Total des engagements en francs |
gagec |
Total des engagements court terme en francs |
gagem |
Total des engagements moyen terme en francs |
kvunb |
Nombre de comptes à vue |
qsmoy |
Moyenne des soldes moyens sur 3 mois |
qcred |
Moyenne des mouvements créditeurs en Kf |
dmvtp |
Age du dernier mouvement (en jours)\hline |
boppn |
Nombre d'opérations à M-1 |
facan |
Montant facturé dans l'année en francs |
lgagt |
Engagement long terme |
vienb |
Nombre de produits contrats vie |
viemt |
Montant des produits contrats vie en francs |
uemnb |
Nombre de produits épargne monétaire |
uemmts |
Montant des produits d'épargne monétaire en francs |
xlgnb |
Nombre de produits d'épargne logement |
xlgmt |
Montant des produits d'épargne logement en francs |
ylvnb |
Nombre de comptes sur livret |
ylvmt |
Montant des comptes sur livret en francs |
nbelts |
Nombre de produits d'épargne long terme |
mtelts |
Montant des produits d'épargne long terme en francs |
nbcats |
Nombre de produits épargne à terme |
mtcats |
Montant des produits épargne à terme |
nbbecs |
Nombre de produits bons et certificats |
mtbecs |
Montant des produits bons et certificats en francs |
rocnb |
Nombre de paiements par carte bancaire à M-1 |
ntcas |
Nombre total de cartes |
nptag |
Nombre de cartes point argent |
segv2s |
Segmentation version 2 |
itavc |
Total des avoirs sur tous les comptes |
havef |
Total des avoirs épargne financière en francs |
jnbjd1s |
Nombre de jours à débit à M |
jnbjd2s |
Nombre de jours à débit à M-1 |
jnbjd3s |
Nombre de jours à débit à M-2 |
carvp |
Possession de la carte VISA Premier} |
Réponde aux questions en s'aidant des résultats des exécutions
Les données sont disponibles dans le répertoire de ce calepin et chargées en même temps.
Même si les données sont déjà extraites par échantillonnage d'une très grande base, elles nécessitent un travail prémiminaire (data munging) pour détecter, corriger les erreurs et incohérences, éliminer des redondances, traiter les données manquantes, transformer certaines variables. Ce travail préliminaire, souvent long et fastidieux, nécessite d'y consacer beaucoup de temps et de rigueur afin de s'assurer de la qualité finale des résultats.
Il fait appel à des outils classiques de statistique descriptive.
Les données sont anonymisées et datent du siècle dernier, elles n'ont plus d'intérêt "commercial". De façon générale, plutôt que de conserver tous les fichiers de données intermédiaires à une étude, ce qui peut nécessiter beaucoup d'espace disque, il est important, voire crucial, d'archiver tous les programmes intermédiaires de saisie, sélection, transformation des données. En effet, en cas de problème ou même simplement d'un mauvais choix méthodologique, il faut pouvoir rapidement repartir d'une étape précédente.
La production d'un calepin aide à cette mémoire lors de l'analyse préliminaire mais ne constitue pas un code opérationnel.
In [ ]:
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
pd.options.mode.chained_assignment = None # default='warn'
In [ ]:
#Lecture
listeVar=['MATRIC','DEPTS','PVS','SEXEQ','AGER','FAMIQ',
'RELAT','PCSPQ','QUALS','G03G04S','G25G26S',
'G29G30S','G35G36S','G37G38S','G45G46S','G47G48S',
'IMPNBS','REJETS','OPGNB','MOYRV','TAVEP','ENDET','GAGET',
'GAGEC','GAGEM','KVUNB','QSMOY','QCRED','DMVTP','BOPPN',
'FACAN','LGAGT','VIENB','VIEMT','UEMNB','UEMMTS','XLGNB',
'XLGMT','YLVNB','YLVMT','NBELTS','MTELTS','NBCATS','MTCATS',
'NBBECS','MTBECS','ZOCNB','NTCAS','NPTAG','SEGV2S','ITAVC',
'HAVEF','JNBJD1S','JNBJD2S','JNBJD3S','CARVP']
path=""
visaraw = pd.read_table(path+'visa_raw.dat',
sep=';',header=None, index_col=0, names=listeVar)
Une fonction permet de définir le bon type "categorical" des variables qualitatives en utilisant un dictionnaire des modalités pour le recodage.
In [ ]:
def create_categorical_data(df,column_name, cat_name_dic):
cat_columns = pd.Categorical(df[column_name],ordered=False)
new_categorie = [cat_name_dic[old_name] for old_name in cat_columns.categories]
return cat_columns.rename_categories(new_categorie)
In [ ]:
#FAMIQ
cat_name_dic = {'C':'Fcel','D':'Fdiv','M':'Fmar','S':'Fsep','U':'Fuli','V':'Fveu','inc':'Finc'}
visaraw["FAMIQ"]=create_categorical_data(visaraw,"FAMIQ", cat_name_dic)
#SEXEQ
cat_name_dic = {'1':'Shom','2':'Sfem','inc':'Sinc'}
visaraw["SEXEQ"]=create_categorical_data(visaraw,"SEXEQ", cat_name_dic)
#CARVP
cat_name_dic = {'non':'Cnon','oui':'Coui'}
visaraw["CARVP"]=create_categorical_data(visaraw,"CARVP", cat_name_dic)
#PCSPQ
visaraw["PCSPQ"] = visaraw["PCSPQ"].str.get(0).replace("0","i")
cat_name_dic = {'1':'Pagr', '2':'Part', '3':'Pcad', '4':'Pint', '5':'Pemp','6':'Pouv',
'7':'Pret', '8':'Psan', 'i':'Pinc'}
visaraw["PCSPQ"]=create_categorical_data(visaraw,"PCSPQ", cat_name_dic)
In [ ]:
visaraw = visaraw[visaraw.NBCATS!= "1"]
visaraw = visaraw[visaraw.NBBECS!= "1"]
visaraw= visaraw[np.logical_not(visaraw.G29G30S.isin(['B','X']))]
visaraw= visaraw[np.logical_not(visaraw.G03G04S.isin(['B','X']))]
visaraw= visaraw[np.logical_not(visaraw.G45G46S.isin(['A','B','X']))]
visaraw= visaraw[np.logical_not(visaraw.G37G38S.isin(['A']))]
visaraw= visaraw[np.logical_not(visaraw.G25G26S.isin(['A','B','X']))]
visaraw= visaraw[np.logical_not(visaraw.G47G48S.isin(['B']))]
Suppression d'observations avec données manquantes de la variable AGER
In [ ]:
visaraw = visaraw[visaraw.AGER!='.']
Seulement les clients de 18 à 65 ans
In [ ]:
visaraw["AGER"]=visaraw["AGER"].astype(int)
visaraw=visaraw[np.logical_and(visaraw["AGER"]<66,visaraw["AGER"]>17)]
Recodage des données manquantes
In [ ]:
visaraw["ZOCNB"]=visaraw["ZOCNB"].replace(".",np.nan)
visaraw["DMVTP"]=visaraw["DMVTP"].replace(".",np.nan)
Correction des types des variables
In [ ]:
listeVarFl=['RELAT','IMPNBS','REJETS','OPGNB','MOYRV',
'TAVEP','ENDET','GAGET','GAGEC','GAGEM','KVUNB','QSMOY','QCRED',
'DMVTP','BOPPN','FACAN','LGAGT','VIENB','VIEMT','UEMNB','UEMMTS','XLGNB',
'XLGMT','YLVNB','YLVMT','NBELTS','MTELTS','NBCATS','MTCATS',
'NBBECS','MTBECS','ZOCNB','NTCAS','NPTAG','ITAVC',
'HAVEF','JNBJD1S','JNBJD2S','JNBJD3S']
visaraw[listeVarFl]=visaraw[listeVarFl].astype(float)
Un seule variable nombre de jours de débit cumulé
In [ ]:
visaraw["JNBJD"]=visaraw["JNBJD1S"]+visaraw["JNBJD2S"]+visaraw["JNBJD3S"]
Suppression des variables devenues inutiles
In [ ]:
listeVarSup=["DEPTS","QUALS","G03G04S","G25G26S","G29G30S","G35G36S",
"G37G38S","G45G46S","G47G48S","SEGV2S","PVS","JNBJD1S","JNBJD2S","JNBJD3S"]
visaraw.drop(listeVarSup,inplace=True,axis=1)
Mise à jour de la liste des variables
In [ ]:
listeVar = [x for x in listeVar if x not in listeVarSup]
In [ ]:
# Moyennes
visaprem=visaraw
visaprem.mean()
Q Quel problème révèle la distribution ci-dessous de la variable « ancienneté dans la banque », exprimée en mois?
In [ ]:
visaprem["RELAT"].hist(bins=20)
plt.show()
Q Que dire de la distribution de la variable ci-dessous? Quelle transformaiton proposer?
In [ ]:
visaprem["TAVEP"].hist(bins=20)
plt.show()
In [ ]:
visaprem["QSMOY"].hist(bins=20)
plt.show()
In [ ]:
import scipy.stats as stats
stats.probplot(visaprem["QSMOY"], dist="norm", plot=plt)
Q Quel problème pose les deux variables ci-dessous? Quelle correction proposer?
In [ ]:
visaprem["PCSPQ"].value_counts()
In [ ]:
visaprem["FAMIQ"].value_counts()
Q Quel est le graphe ci-dessous? Les variables vous semblent-elles liées?
In [ ]:
visaprem.boxplot('QSMOY','CARVP')
plt.show()
La moyenne des soldes moyens sur trois mois QSMOY
pose un problème mais elle semble peu discriminante, elle est conservée en l'état. Sinon une transformation par la fonction "argument sinus hyperbolique" peut être utilisée avec ce type de distribution mais ce n'est pas indispensable.
Q Que représente le graphique ci-dessous? Interprétation en quelques lignes. Ces variables semblent-elles liées ?
In [ ]:
from statsmodels.graphics.mosaicplot import mosaic
visaprem["SEXEQ"]=visaprem["SEXEQ"].cat.remove_unused_categories()
table=pd.crosstab(visaprem["SEXEQ"],visaprem["CARVP"])
print(table)
mosaic(table.stack())
plt.show()
Q Quel problème révèle le tableau ci-dessous? Comment le régler?
In [ ]:
table=pd.crosstab(visaprem["NTCAS"],visaprem["CARVP"])
print(table)
Q Ce graphique représente la variable « ancienneté dans la banque », exprimée en mois, en fonction de l’âge du client en années. Quelle incohérence montre ce graphique ? Quelles en est la raison?.
In [ ]:
visaprem.plot('AGER','RELAT',kind='scatter')
plt.show()
Q Quel problème révèle le tableau ci-dessous? Comment le résoudre?
In [ ]:
table=pd.crosstab(visaprem["NTCAS"],visaprem["ZOCNB"].isnull())
print(table)
In [ ]:
#Transformations des variables
## regroupements de modalités
visatrans=visaprem
FAMIQ_DIC = {"Fmar":'Fcou',"Fuli":'Fcou',"Fdiv":'Fseu','Fveu':'Fseu',
"Fsep":'Fseu',"Fcel":'Fseu'}
PCSPQ_DIC = {"Pagr":"Pint",'Part':"Pint","Pret":"Psan","Pinc":"Psan"}
# attention de bien gétrer le type des variables
visatrans["PCSPQ"]=pd.Categorical(visatrans["PCSPQ"].astype(str).replace(PCSPQ_DIC),ordered=False)
visatrans["FAMIQ"]=visatrans["FAMIQ"].astype(str).replace(FAMIQ_DIC)
In [ ]:
# complétion de valeurs
visatrans["ZOCNB"].fillna(0, inplace=True)
visatrans["ZOCNB"]
visatrans = visatrans[visatrans.DMVTP.notnull()]
# Correction ancienneté relation
visatrans["RELAT"]= [x-720 if x>600 else x for x in visatrans["RELAT"]]
In [ ]:
# log des variables de distribution dissymétrique
def log1(x):
return np.log(1+x)
listeVarLog=['OPGNB','MOYRV','TAVEP','ENDET','GAGET','GAGEC',
'GAGEM','QCRED','DMVTP','BOPPN','FACAN','LGAGT',
'VIEMT','XLGMT','YLVMT','ITAVC','HAVEF','JNBJD']
visatrans[listeVarLog]=log1(visatrans[listeVarLog])
visatrans["ZOCNB"]=np.sqrt(visatrans["ZOCNB"])
Vérifier les distributions des variables transformées.
In [ ]:
visatrans2 = visatrans[["SEXEQ","AGER","FAMIQ","RELAT","PCSPQ","OPGNB", "MOYRV","TAVEP", "ENDET", "GAGET","GAGEC", "GAGEM", "KVUNB","QSMOY", "QCRED", "DMVTP","BOPPN", "FACAN", "LGAGT","VIENB", "VIEMT", "UEMNB","XLGNB", "XLGMT", "YLVNB","YLVMT", "ZOCNB", "NPTAG","ITAVC", "HAVEF","JNBJD", "CARVP"]]
visatrans2.head()
Q Comment interpréter le graphe ci-dessous?
In [ ]:
from pandas.tools.plotting import scatter_matrix
scatter_matrix(visatrans[["AGER", "RELAT","QSMOY", "OPGNB", "MOYRV","TAVEP", "ENDET", "GAGET","GAGEC","GAGEM", "QCRED", "DMVTP","BOPPN","FACAN","LGAGT","VIENB","VIEMT","UEMNB","XLGNB","XLGMT","YLVNB","YLVMT","ZOCNB"]], alpha=0.2, figsize=(15, 15), diagonal='kde')
plt.show()
Q L'ACP calculée ci-dessous est-elle réduite ? Pourquoi?
In [ ]:
from sklearn.decomposition import PCA
from sklearn.preprocessing import scale
# définition de la commande
pca = PCA()
# Sélection des variables quantitatives
dat=visatrans[["AGER","RELAT","KVUNB", "OPGNB", "MOYRV","TAVEP","ENDET","GAGET","GAGEC","GAGEM","QCRED","DMVTP","BOPPN","FACAN","LGAGT","VIEMT","XLGMT","YLVMT","ITAVC","HAVEF","JNBJD","ZOCNB"]]
# réduction
data = pd.DataFrame(scale(dat),columns=dat.columns)
# composantes principales
C = pca.fit(data).transform(data)
In [ ]:
# Eboulis des valeurs propres
plt.figure()
plt.plot(pca.explained_variance_ratio_)
plt.show()
Q Que représente le graphe ci-dessous? Quelle information en tirer?
In [ ]:
# Diagrammes boîte des composantes principales
plt.figure()
plt.boxplot(C[:,0:20])
plt.show()
Q Quel est le graphe ci-dessous? Quelle information en retenir?
In [ ]:
plt.figure(figsize=(10,8))
for i, j, nom in zip(C[:,0], C[:,1], visatrans['CARVP']):
color = "red" if nom == "Coui" else "blue"
plt.text(i, j, nom, color=color)
plt.axis((-6,7.5,-5,6))
plt.show()
In [ ]:
# coordonnées des variables
coord1=pca.components_[0]*np.sqrt(pca.explained_variance_[0])
coord2=pca.components_[1]*np.sqrt(pca.explained_variance_[1])
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(1, 1, 1)
for i, j, nom in zip(coord1,coord2, data.columns):
plt.text(i, j, nom)
plt.arrow(0,0,i,j,color='r')
plt.axis((-1.2,1.2,-1.2,1.2))
# cercle
c=plt.Circle((0,0), radius=1, color='b', fill=False)
ax.add_patch(c)
plt.show()
In [ ]:
# Stratégie utilisée pour aider à l'interprétation du plan principal
# lorsque le nombre de variables est important
from scipy.cluster.hierarchy import dendrogram, linkage
Z = linkage(data.T, 'ward')
plt.figure(figsize=(15, 8))
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Variable')
plt.ylabel('Distance')
dendrogram(Z,leaf_font_size=8.,labels=data.columns)
plt.show()
Pas d'épisode 3 car l'AFD n'est pas adaptée aux données.
Afin d'intégrer toutes les variables et pas seulement celles quantitatives, un recodage de ces dernières variables, permet de toutes les intégrer dans une analyse multiple des correspondances.
Remarques
Q Commenter succinctement chacune des étapes nécessaires à la transformation des variables pour réaliser une analyse multiple des correspondances.
In [ ]:
#Recodage des modalités
import random
visapremv=visatrans2
In [ ]:
visapremv["FAMIQ"].value_counts()
In [ ]:
# Recodages aléatoire des Finc
def rep_fami(x):
if random.random()<0.45:
x="Fseu"
else:
x="Fcou"
return x
visapremv["FAMIQ"]=[x if x!="Finc" else rep_fami(x) for x in visapremv["FAMIQ"]]
In [ ]:
visapremv["FAMIQ"].value_counts()
In [ ]:
# attention au type
visapremv["FAMIQ"]=visapremv["FAMIQ"].astype('category')
In [ ]:
kvunbq = ["K0" if x in [0,1] else "K1" for x in visapremv["KVUNB"] ]
visapremv["KVUNBQ"]=pd.Categorical(kvunbq,ordered=False)
vienbq = ["V0" if x==0 else "V1" for x in visapremv["VIENB"]]
visapremv["VIENBQ"]=pd.Categorical(vienbq,ordered=False)
uemnbq = ["U0" if x==0 else "U1" if x==1 else "U2" for x in visapremv["UEMNB"] ]
visapremv["UEMNBQ"]=pd.Categorical(uemnbq,ordered=False)
xlgnbq = ["X0" if x==0 else "X1" if x==1 else "X2" for x in visapremv["XLGNB"] ]
visapremv["XLGNBQ"]=pd.Categorical(xlgnbq,ordered=False)
ylvnbq = ["Y0" if x==0 else "Y1" if x==1 else "Y2" for x in visapremv["YLVNB"] ]
visapremv["YLVNBQ"]=pd.Categorical(ylvnbq,ordered=False)
zocnbq = ["Z0" if x ==0 else "Z1" for x in visapremv["ZOCNB"] ]
visapremv["ZOCNBQ"]=pd.Categorical(zocnbq,ordered=False)
nptagq = ["N0" if x ==0 else "N1" for x in visapremv["NPTAG"] ]
visapremv["NPTAGQ"]=pd.Categorical(nptagq,ordered=False)
jnbjdq = ["J0" if x==0 else "J1" for x in visapremv["JNBJD"] ]
visapremv["JNBJDQ"]=pd.Categorical(jnbjdq,ordered=False)
In [ ]:
def rec(df,col_name,a,b):
colq = [b if x>0 else a for x in df[col_name]]
return colq
visapremv["ENDETQ"]=pd.Categorical(rec(visapremv,"ENDET","E0","E1"),ordered=False)
visapremv["GAGETQ"]=pd.Categorical(rec(visapremv,"GAGET","G0","G1"),ordered=False)
visapremv["FACANQ"]=pd.Categorical(rec(visapremv,"FACAN","F0","F1"),ordered=False)
visapremv["LGAGTQ"]=pd.Categorical(rec(visapremv,"LGAGT","L0","L1"),ordered=False)
visapremv["HAVEFQ"]=pd.Categorical(rec(visapremv,"HAVEF","H0","H1"),ordered=False)
In [ ]:
visapremw=visapremv[["AGER","RELAT","QSMOY","OPGNB","MOYRV","TAVEP","DMVTP","BOPPN","ITAVC"]]
In [ ]:
visapremw["AGEQ"]=pd.qcut(visapremw.AGER,3,labels=["A0","A1","A2"])
visapremw["RELATQ"]=pd.qcut(visapremw.RELAT,3,labels=["R0","R1","R2"])
visapremw["QSMOYQ"]=pd.qcut(visapremw.QSMOY,3,labels=["Q0","Q1","Q2"])
visapremw["MOYRVQ"]=pd.qcut(visapremw.MOYRV,3,labels=["M0","M1","M2"])
visapremw["TAVEPQ"]=pd.qcut(visapremw.TAVEP,3,labels=["T0","T1","T2"])
visapremw["DMVTPQ"]=pd.qcut(visapremw.DMVTP,3,labels=["D0","D1","D2"])
visapremw["BOPPNQ"]=pd.qcut(visapremw.BOPPN,3,labels=["B0","B1","B2"])
visapremw["ITAVCQ"]=pd.qcut(visapremw.ITAVC,3,labels=["I0","I1","I2"])
visapremw["OPGNBQ"]=pd.qcut(visapremw.OPGNB,2,labels=["O0","O1"])
# suppression des variables devenues inutiles
listeVarSup=["AGER","RELAT","QSMOY","MOYRV","TAVEP","DMVTP","BOPPN","ITAVC","OPGNB"]
visapremw.drop(listeVarSup,inplace=True,axis=1)
In [ ]:
fram=[visapremv,visapremw]
visapremvw=pd.concat(fram,1)
In [ ]:
visapremvw.head()
Il est classique de rechercher une première AFCM n'utilisant que les variables résumant les caractéristiques sociales (signalétique) des clients en variables principales. afin de détecter d'éventuelles incohérences. Les variables bancaires sont projetées en tant que variables supplémentaires.
In [ ]:
import mca
# variables qualitatives
visapremQ=visapremvw.select_dtypes(include=[pd.Categorical,"object"])
visapremQ.columns
In [ ]:
visapremQ["RELATQ"].value_counts()
In [ ]:
visapremQ[["FAMIQ","SEXEQ","PCSPQ","AGEQ","RELATQ"]].describe()
In [ ]:
# La commande ci-dessous ne marche pas, il faut créer soi même le tableau disjonctif
# mct=mca.MCA(visapremQ, cols=["FAMIQ","SEXEQ","PCSPQ","AGEQ","RELATQ"],benzecri=False)
In [ ]:
# Tableau disjonctif complet
D=pd.DataFrame(pd.get_dummies(visapremQ[["FAMIQ","SEXEQ","PCSPQ","AGEQ","RELATQ"]]))
In [ ]:
# AFC
mct=mca.MCA(D,benzecri=False)
In [ ]:
# Graphique rudimenttaire
col=[1,1,2,2,3,3,3,3,3,4,4,4,4,4,4]
plt.scatter(mct.fs_c()[:, 0],mct.fs_c()[:, 1],c=col)
for i, j, nom in zip(mct.fs_c()[:, 0],mct.fs_c()[:, 1], D.columns):
plt.text(i, j, nom)
plt.show()
Q Commenter le graphique ci-dessous. Présente-t-il des incohérences particulières ?
L'objectif de cette exploration est de construire la représentation la plus explicite pour bien appréhender les structures des données. Les fonctions de python sont bien moins élaborées que celles des librairies de R. Il manque dans le graphe ci-dessous une utilisation habile des couleurs pour distinguer les modalités d'une même variable (même première lettre).
Q Remarquant que la plupart des modalités "0" se trouvent à gauche du graphique suivant, quelle interprétation donner du premier axe?
Rappel: le premier caractère de chaque libellé de modalité identifie la variable.
Exo améliorer la lisibilité du graphique ci-dessous
Q Identifier quelques variables et modalités caractéristiques et donnez une interprétation du 2ème axe.
Exo: Représenter les plans (1,3) et (2,3). Une interprétation de l'axe 3 est-elle possible?
Q Qu’est-ce qui caractérise principalement les porteurs de la carte visa premier (variable CARVP) ?
In [ ]:
# Tableau disjonctif complet
D=pd.DataFrame(pd.get_dummies(visapremQ))
# AFCM de toutes les variables qualitatives
mca_comp=mca.MCA(D,benzecri=False)
In [ ]:
fig = plt.figure(1, figsize=(15, 13))
plt.scatter(mca_comp.fs_c()[:, 0],mca_comp.fs_c()[:, 1])
for i, j, nom in zip(mca_comp.fs_c()[:, 0],mca_comp.fs_c()[:, 1], D.columns):
plt.text(i, j, nom)
plt.show()
Q Le graphe ci-dessous représente également les clients dans le plan (1,2). L’AFC de quelle matrice a été calculée pour aboutir à ce résultat ?
In [ ]:
fig = plt.figure(1, figsize=(15, 13))
# graphe des individus et des modalités
for i, j, nom in zip(mca_comp.fs_c()[:, 0],-mca_comp.fs_c()[:, 1],D.columns):
#print(nom,i,j)
plt.text(i, j, nom,color='g')
plt.scatter(mca_comp.fs_c()[:, 0],-mca_comp.fs_c()[:, 1],marker='1',c='g')
plt.scatter(mca_comp.fs_r()[:, 0],-mca_comp.fs_r()[:, 1],marker='+',c='b')
plt.show()
L'objectif principal, lors de cette première étude des données, est de fournir une typologie ou segmentation des clients. C'est-à-dire de définir des classes les plus homogènes au regard des comportements bancaires. Les algorithmes de classification étudiés sont adaptés à des variables {\em quantitatives} ou des matrices de distances. L'intégration d'informations qualitatives peut se faire par un recodage préalable (scoring) à l'aide d'une analyse des correspondances multiples. Ce scénatio se propose donc de comparer deux approches: classification à partir des seules variables quantitatives ou classification à partir des scores issues d'une afcm. D'autres approches sont envisageables sur des variables qualitatives qui nécessitent la définition d'une distance ou dissimilarité entre individus adaptée aux variables qualitatives. Mais, nécessitant la construction de la matrice n x n des distances des individus deux à deux, elles ne sont pas adaptées aux très grands tableaux.
A titre d'exemple, voici comment obtenir une typologie à partir des seules variables quantitatives. La stratégie est adaptée à des gros fichiers.
Q Quel problème peut poser la CAH (classification ascendante hiérérchique)) pour de très gros fichiers?
Q Résumer la stratégie développée ci-dessous pour pouvoir appliquer la CAH à un gros fichier. Quel est l'avantage, que cette stratégie utilise, de la CAH sur les algortihmes par réallocation dynamique de type $k-means$?
In [ ]:
# Sélection des variables quantitatives
visaprems=visapremvw.select_dtypes(include=["int32","float","int64"])
# Centrage et réduction
visaprems = scale(visaprems)
In [ ]:
# Classification d'un grand tableau par k-means puis CAH
## k-means avec 100 classes
from sklearn.cluster import *
T=k_means(visaprems,100)
poids=np.zeros((100,))
for i in T[1]:
poids[i]=1./np.size(T[1])
np.sum(poids)
In [ ]:
## CAH des barycentres des classes
## Calcul de la matrices des distances pondérées
def weightedL2(a,b,wa,wb):
q = wa*a-wb*b
return np.sqrt((q*q).sum())
dist=np.zeros((100,100))
for i in range(dist.shape[0]):
for j in range(dist.shape[1]):
dist[i,j]=weightedL2(T[0][i],T[0][j],poids[i],poids[j])
from scipy.cluster.hierarchy import *
Q= linkage(dist, 'ward')
In [ ]:
## Dendrogramme des barycentres des classes
plt.figure(figsize=(20, 10))
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('sample index')
plt.ylabel('distance')
dendrogram(Q,leaf_font_size=8.)
plt.show()
Q Quel est le graphique ci-dessus? Quel est le graphique ci-dessous? Quelle conclusion en tirer?
In [ ]:
plt.figure(figsize=(20, 10))
plt.plot(maxdists(Q)[::-1][0:10])
plt.show()
In [ ]:
pd.DataFrame(Z[1],columns=["Classe"])
In [ ]:
## k-means avec le nombre choisi de classes
Z=k_means(visaprems,5)
pca=PCA()
C = pca.fit(visaprems).transform(visaprems)
In [ ]:
## représentation des classes dans l'ACP
plt.figure(figsize=(20, 10))
plt.scatter(C[:,0], C[:,1],marker='1',c=Z[1],cmap=plt.cm.Set1)
plt.show()
In [ ]:
mca_comp=mca.MCA(D,benzecri=False)
In [ ]:
mca_comp.fs_r().shape
Q Quelles sont les options disponibles de la méthode mise en oeuvre ci-dessous?
In [ ]:
# Avec n grand, il faudrait suivre la même stratégie que pour
# l'acp: k-means puis CAH sur barycentres puis re-k-means.
# On fait directement la CAH
# data = (dat - dat.mean()) / dat.std()
ZZ = linkage(mca_comp.fs_r(), 'ward')
plt.figure(figsize=(15, 8))
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('sample index')
plt.ylabel('distance')
dendrogram(ZZ,leaf_font_size=8.)
plt.show()
Q Justifier le choix du nombre de classes.
In [ ]:
plt.figure(figsize=(20, 10))
plt.plot(maxdists(ZZ)[::-1][0:10])
plt.show()
In [ ]:
# k-means des composantes de l'AFCM avec 4 classes
KL=k_means(mca_comp.fs_r(),4)
fig = plt.figure(1, figsize=(15, 13))
# Représentation des classes dans les composantes de l'AFCM
for i, j, nom in zip(mca_comp.fs_c()[:, 0],-mca_comp.fs_c()[:, 1],D.columns):
plt.text(i, j, nom,color='g')
plt.scatter(mca_comp.fs_c()[:, 0],-mca_comp.fs_c()[:, 1],marker='1',c='g')
plt.scatter(mca_comp.fs_r()[:, 0],-mca_comp.fs_r()[:, 1],marker='+',c=KL[1])
plt.show()
In [ ]:
# Ajout de la variable Classe dans la table
visapremvw["Klasse"]=KL[1]
visapremvw["Klasse"]=pd.Categorical(visapremvw["Klasse"],ordered=False)
visapremvw["Klasse"]=visapremvw["Klasse"].cat.rename_categories(['KL2','KL1','KL0','KL3'])
visapremvw["Klasse"].value_counts()
Q Les classes obtenues sont représentées dans une AFCM comme les modalités KL0, KL1, KL2, KL3 d’une variable qualitative. A partir du graphe ci-dessous, résumer quelques caractéristiques des classes de client kL1, KL2, KL3.
Exo Représenter le plan (1,3). Interprétation de KL0.
In [ ]:
# AFCM avec la variable Classe
D=pd.get_dummies(visapremvw.select_dtypes(include=[pd.Categorical,"object"]))
mca_class=mca.MCA(D,benzecri=False)
fig = plt.figure(1, figsize=(15, 13))
# Graphe des modalités
for i, j, nom in zip(mca_class.fs_c()[:, 0],-mca_class.fs_c()[:, 1],D.columns):
if nom=='Klasse_KL0' or nom=='Klasse_KL1' or nom=='Klasse_KL2' or nom=='Klasse_KL3':
plt.text(i, j, nom,color='r')
else:
plt.text(i, j, nom,color='g')
plt.scatter(mca_class.fs_c()[:, 0],-mca_class.fs_c()[:, 1],marker='1',c='g')
plt.show()
Exo Améliorer la lisibilité de ces graphiques!