GRC: Segmentation d'une clientèle bancaire en

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.

Introduction

Objectif

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.

Présentation des données

Les variables

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

Épisode 1 Data Munging </font>

Lecture et prétraitement des données

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.

Lecture des données


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)

Premiers nettoyages

Suppression d'observations non pertinentes:interdits bancaires et comptes professionnels.


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]

Desciption univariée et bivariée


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()

Épisode 2 ACP</font>

Analyse en composantes principales

Cette phase de l'analyse permet de mieux comprendre la structure de corrélation des variables afin de d'appréhender ce que seront les grandes classes de comportement des clients.

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)

Choix de dimension


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()

Représentation des individus

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()

Variables et cercle des corrélations

Q Que représent le graphe ci-dessous? Donner en une ligne une interprétation de l'axe 1 puis de l'axe2.


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()

Classification des variables

Q Les classes de variables sont-elles cohérentes avec l'interprétataion des axes?


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.

Épisode 4 AFCM</font>

Analyse multiple des correspondances

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

  • Le nombre de classes doit être relativement restreint pour limiter les dimensions et faciliter les interprétations,
  • il est préférable de choisir, pour des variables distribuées "normalement", des classes d'effectifs égaux: les bornes sont des quantiles.
  • Dans le cas des données bancaires, les variables présentant une distribution très asymétrique se résument souvent en une variable dichotomique: présence ou absence d'un produit financier donné. Dans ce dernier cas, il est inutile de conserver à la fois une variable "nombre" et une variable "montant" du même produit financier.
  • Enfin, pour des raisons techniques de lisibilité des graphiques en SAS, les codes des modalités associés à une même variable commencent par la même lettre. Les programmes ci-dessous devraient être adaptés afin d'utiliser cette convention pour améliorer la lisibilité des graphiques.

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()

Épisode 5 Classification non supervisée</font>

Segmentation ou clustering des clients

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.

Sur les seules variables quantitatives

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()

Clustering sur composantes de l'AFCM

Q Préciser la démarche permettant d'obtenir une typologie des clients décrits par des variables à la fois quantitatives et qualitatives.


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!