Exploration de données cliniques en : diagnostic coronarien

Résumé

Exploration des données de suivi clinique. Analyses uni, bi et multidimensionnelles: ACP AFC, AFCM, classification non-supervisée k-means, CAH} des individus après AFCM ou factorisation non négative des matrices (NMF) puis interprétation des classes. L'objectif est de préparer l'étape suivante d'aide au diagnostic par prévision de la pathologie.

Introduction

Des données publiques disponibles sur le site UCI repository décrivent des facteurs de risque et résultats cliniques: 13 parmi 75 de l’étude originale de Detrano et al. (1989), liés à une maladie coronarienne (athérosclérose). Celle-ci est jugée présente lorsque tous les vaisseaux coronariens sont obstrués à plus de 50% par des athéromes. Les variables étudiées sont observées sur un échantillon de 270 patients admis dans une clinique de Cleveland (Ohio) à la suite de douleurs thoraciques pouvant être dues à une angine de poitrine. Elles sont décrites dans le tableau ci-dessous:

Num Code Libellé Valeurs
1 Age
2 Sexe sxF, sxM
3 Douleur Thoracique dlA (angine typique), dlb(atypique) dlc(différent) dlD(asymptom.)
4 Tension Systolique mmHg à l’admission et au repos
5 Cholest Taux mg/dl (préférable<200, limite entre 200 et 240, risqué au-delà)
6 Sucre Taux à jeun scN (<120mg/dl), scO (>120mg/dl)
7 Cardio ECG au repos cdA (Normal) cdB (ST/T anormal) cdC (hypertrophie ventr. gauche)
8 FreqM Fréquence cardiaque maximum lors du test d’effort
9 AngInd Angine induite par l’effort : tmA (oui), tmB (non)
10 PicInd Dépression ST Induite par effort / repos
11 PentInd Segment ST Induit à l’effort piA(ascendante), piB(plate), piC(descendante)
12 Nvais Nombre de vaisseaux fl0, fl1, fl2, fl3 majeurs colorés par fluoroscopie
13 Thal Scintigraphie thN(normal) thF(défaut fixé) thR(défaut révers.) avec effort
14 Coro Coronaropathie CoA(Absence), CoP(Présence)

Certaines sont associées à des risques potentiels et d’autres sont résultats d’examens cliniques au repos ou à la suite d’un test d’effort. Les variables 1, 4, 5, 8, 10 sont quantitatives, les autres sont qualitatives dont certaines binaires : 2, 6, 9, 14. Le diagnostic (variable Coro) a été établi par une angiographie permettant de mesurer l’obstruction des artères coronariennes.

En principe, l’objectif sur ces données est de construire un modèle de prévision de la variable Coro à partir de l’observation des autres, pas ou peu invasives, car l’angiographie est un examen invasif comportant des risques. La variable Coro étant qualitative à 2 classes, c’est une régression logistique qui permettrait d’atteindre l’objectif mais une étude exploratoire préalable, objet du présent travail, est utile et nécessaire à la bonne compréhension des données.

Répondre aux questions en s'aidant des résultats des exécutions.

1 Description uni et bidimensionnelle

Q Quelle est la population étudiée ? Quel est l’échantillon?

Lecture des données et recodage en clair des modalités pour construire le dataFrame. Les données osnt disponibles dans le répertoire de ce calepin ou dans celui désignié par le chemin en commentaire.


In [ ]:
# path="http://www.math.univ-toulouse.fr/~besse/Wikistat/data/"
path=""
heart=read.table(paste(path,"heart.dat",sep=""))

# recodage des classes et nom des variables
heart=data.frame(
    Age=heart[,1],
    Sexe=factor(as.factor(heart[,2]),labels=c("sxF","sxM")),
    Douleur=factor(as.factor(heart[,3]),labels=c("dlA","dlB","dlC","dlD")),
    Tension=heart[,4],
    Cholest=heart[,5],
    Sucre=factor(as.factor(heart[,6]),labels=c("scN","scO")),
    Cardio=factor(as.factor(heart[,7]),labels=c("cdA","cdB","cdC")),
    FreqM=heart[,8],
    AngInd=factor(as.factor(heart[,9]),labels=c("tmA","tmB")),
    PicInd=heart[,10],
    PenteInd=factor(as.factor(heart[,11]),labels=c("piA","piB","piC")),
    Nvais=factor(as.factor(heart[,12]),labels=c("fl0","fl1","fl2","fl3")),
    Thal=factor(as.factor(heart[,13]),labels=c("thN","thF","thR")),
    Coro=factor(as.factor(heart[,14]),labels=c("CoA","CoP")))
summary(heart)

In [ ]:
# Pour adapter la taille des graphiques à la fenêtre
# car ils sont sinon nien trop grands
library(repr)
options(repr.plot.width=3, repr.plot.height=3)

Q Que représentent ces graphiques? Que dire de ces variables? Décrire les autres variables.


In [ ]:
hist(heart[,"Age"],xlim=c(20,80), probability=T, main="",xlab="Age")
boxplot(heart[,"Age"], horizontal=TRUE,boxwex=.01,  outline=TRUE,  
        ylim=c(20,80), frame=F, col = "lightgrey", add = TRUE,at=0.01)

In [ ]:
hist(heart[,"Tension"],xlim=c(90,200), probability=T, main="",xlab="Tension systolique")
boxplot(heart[,"Tension"], horizontal=TRUE,boxwex=.01,  outline=TRUE,  
        ylim=c(90,200), frame=F, col = "lightgrey", add = TRUE,at=0.005)

In [ ]:
hist(heart[,"FreqM"],xlim=c(70,200), probability=T, main="",xlab="Fréquence cardiaque maximale")
boxplot(heart[,"FreqM"], horizontal=TRUE,boxwex=.01,  outline=TRUE,  
        ylim=c(70,200), frame=F, col = "lightgrey", add = TRUE,at=0.005)

Q Que représente ce graphe? Commentaires?


In [ ]:
options(repr.plot.width=5, repr.plot.height=5)
pairs(heart[,c(1,4,5,8,10)],pch=16,cex=.5)

Q Que représentent ces graphes? Commentaires? Quels sont les tests utilisés (hypothèse H0)? Quelles conclusions? Croisez d'autres variables.


In [ ]:
par(mar=c(2.1, 2.1, .1, .1))
options(repr.plot.width=2, repr.plot.height=2)
boxplot(Age~Coro,data=heart,ylab="Age")
var.test(Age~Coro,data=heart)
t.test(Age~Coro,var.equal=T,data=heart)

In [ ]:
par(mar=c(2.1, 2.1, .1, .1))
mosaicplot(table(heart[,"Sexe"],heart[,"Coro"]),main="")
chisq.test(table(heart[,"Sexe"],heart[,"Coro"]))

In [ ]:
par(mar=c(2.1, 2.1, .1, .1))
mosaicplot(table(heart[,"Sucre"],heart[,"Coro"]),main="")
chisq.test(table(heart[,"Sucre"],heart[,"Coro"]))

2 Approche multidimensionnelle

2.1 Analyse en composantes principales

Q Quelle matrice est diagonalisée? Que signifient les valeurs propres? Que dire des pourcentages associés aux axes?


In [ ]:
summary(heart)

In [ ]:
pca=prcomp(heart[,c(1,4,5,8,10)],scale=TRUE)
pca

In [ ]:
pt=15+as.integer(heart$Sexe)
pt=16
col=as.integer(heart$Coro)
par(mar=c(2.1, 2.1, -1, -1)+2)
options(repr.plot.width=5, repr.plot.height=5)
plot(pca$x,type="p",pch=pt,col=col)
legend("topright",c("CoA","CoP"),col=c(1,2),pch=16,title ="Coro")
text(5*pca$rotation,colnames(heart[,c(1,4,5,8,10)]), col="blue")

Q Quel est ce graphique ? Que représentent les libellés bleus ? Les points rouges ? Les points noirs ? Comment interpréter la position respective de Choles et PicInd ?

Q Comment interpréter l’axe 1? Que montre-t-il sur les individus? L’axe 2?

Q Comment interpréter la proximité entre Choles et l’un des points noirs les plus proches ? L’un des points noirs les plus éloignés ? Qu’est-ce qui justifie cette interprétation ?

2.2 Préparation des données

Des traitements préalables sont nécessaires avant une AFCM; les justifier.

Q A quoi sert la commande (expliciter les options) :

AgeQ=cut(heart$Age,breaks=quantile(heart$Age,c(0,.33,.67,1)),labels=c("AgeA","AgeB","AgeC"))

La même commande est appliquée aux variables Tension, Cholest, FreqM, PicInd et produit les variables TensQ, CholQ, FreQ, PicQ avec les mêmes formes de libellés.


In [ ]:
options(repr.plot.width=2, repr.plot.height=2)
par(mar=c(2.1, 2.1, .1, .1))
hist(heart$Age, main="")
AgeQ=cut(heart$Age,breaks=quantile(heart$Age,c(0,.33,.66,1)),labels=c("AgeA","AgeB","AgeC"),include.lowest = TRUE)

In [ ]:
par(mar=c(2.1, 2.1, .1, .1))
pie(table(AgeQ))

In [ ]:
# De façon analogue:
TensQ=cut(heart$Tension,breaks=quantile(heart$Tension,c(0,.33,.66,1)),labels=c("TensA","TensB","TensC"),include.lowest = TRUE)
CholQ=cut(heart$Cholest,breaks=quantile(heart$Cholest,c(0,.33,.66,1)),labels=c("CholA","CholB","CholC"),include.lowest = TRUE)
FreQ=cut(heart$FreqM,breaks=quantile(heart$FreqM,c(0,.33,.66,1)),labels=c("FreqA","FreqB","FreqC"),include.lowest = TRUE)
PicQ=cut(heart$PicInd,breaks=quantile(heart$PicInd,c(0,.33,.66,1)),labels=c("PicA","PicB","PIcC"),include.lowest = TRUE)

In [ ]:
heart[heart$Cardio=="cdB","Cardio"]="cdA"
heart$Cardio=factor(heart$Cardio,exclude=NULL)
summary(heart)

In [ ]:
heartT=data.frame(heart,AgeQ,TensQ,CholQ,FreQ,PicQ)
summary(heartT)

2.3 Analyse factorielle multiple des correspondances

Alors que celles-ci sont importantes, les variables qualitatives ne sont pas prises en compte dans l’ACP. D’où l’intérêt de l’analyse suivante. Les variables quantitatives sont retirées, il reste alors 14 variables toutes qualitatives. Pour savoir si, en « aveugle », les autres variables permettent d’expliquer la cible (variable Coro) qui sera l’objet de la modélisation, cette dernière est en supplémentaire. Seules 13 variables actives participent aux analyses.


In [ ]:
heartQ=heartT[,-c(1,4,5,8,10)]
summary(heartQ)

Q L’AFCM considère deux matrices à partir de toutes ces variables. Quelles sont-elles? Quelles en sont les dimensions ?

Q Quelle AFC conduit au résultat ci-dessous. Par quelle ACP équivalente est-il obtenu?

Q Comparer avec le graphe de l'ACP. Que pouvez-vous dire sur la qualité de séparation des deux classes ? Commentaire.


In [ ]:
library(FactoMineR)
afc=MCA(heartQ,quali.sup=9,graph=F)
par(mar=c(2.1, 2.1, .1, .1))
options(repr.plot.width=5, repr.plot.height=5)
palette("default")
plot(afc$ind$coord,type="p",pch=".",cex=8,col=as.integer(heartQ[,9]))
legend("topright",c("CoA","CoP"),col=c(1,2),pch=16,title ="Coro")

Q Quelles ACP équivalentes avec quelles métriques (comment sont-elles définies) fournissent le résultat ci-dessous. Que signifient les valeurs dans les légendes des axes.

Q Quel axe est à prendre en compte pour s’intéresser au risque d’artériosclérose ? Quelles sont les indicateurs de ce risque ?


In [ ]:
afc=MCA(heartQ,quali.sup=9,graph=F)
par(mar=c(2.1, 2.1, .1, .1))
plot.MCA(afc,invisible=c("ind"),habillage="quali",palette=palette(rainbow(14)),title="")

Alors que (cf. analyse bivariée) le risque semble augmenter avec l’âge, ce n’est pas clair sur le graphique de l’AFCM. L’effet de l’interaction Age x Sexe vient compliquer la situation. Pour vérifier, une variable Sexe:Age à 6 modalités est obtenu par croisement pour mieux mettre en évidence la liaison avec la pathologie.


In [ ]:
SexAge=heartQ$Sexe:heartQ$AgeQ
table(SexAge,heart[,"Coro"])
par(mar=c(2.1, 2.1, .1, .1))
options(repr.plot.width=3, repr.plot.height=3)
mosaicplot(table(SexAge,heart[,"Coro"]),main="")

In [ ]:
heartQ=data.frame(heartQ,"SexAge"=SexAge)
summary(heartQ)

Les variables Sexe et Age deviennent supplémentaires et l’AFCM produit le graphique cidessous.


In [ ]:
options(repr.plot.width=5, repr.plot.height=5)
par(mar=c(2.1, 2.1, .1, .1))
afc=MCA(heartQ,quali.sup=c(1,9,10),graph = F)
plot.MCA(afc,invisible=c("ind"),habillage="quali",palette=palette(rainbow(15)),title="")

Q Pourquoi ce graphique suggère-t-il de simplifier en considérant 3 modalités de Sexe:Age : Femmes ou hommes jeunes (sHFageA), Hommes plus âgés (sHageBC), Femmes plus âgées (sFageBC)?


In [ ]:
# regrouper les agés m d'un coté, f de l'autre, les jeunes ensemble
SxAg=as.factor(rep(c("sHFageA","sFageBC","sHageBC"),90))
SxAg[heartQ$AgeQ=="AgeA"]="sHFageA"
SxAg[heartQ$SexAge=="sxF:AgeB" | heartQ$SexAge=="sxF:AgeC"]="sFageBC"
SxAg[heartQ$SexAge=="sxM:AgeB" | heartQ$SexAge=="sxM:AgeC"]="sHageBC"
summary(SxAg)
table(heartQ[,"Sexe"],heartQ[,"AgeQ"])

In [ ]:
heartQ2=data.frame(heartQ[,-15],SxAg)
summary(heartQ2)

Q Que deviennent alors les principaux éléments de diagnostic en association avec la pathologie ?


In [ ]:
afc=MCA(heartQ2,quali.sup=c(1,9,10),graph=F)
par(mar=c(2.1, 2.1, .1, .1))
plot.MCA(afc,invisible=c("ind"),cex=0.8,habillage="quali",palette=palette(rainbow(14)),title="")

3 Classification non supervisée

L’objectif est de construire des classes de patients homogènes à mettre en relation avec les résultats des examens. Cela correspond à un objectif de profilage des patients selon leur irsque. Comme précédemment, la variable à expliquer (Coro) n’est pas incluse pour éviter de biaiser les résultats et de bien évaluer le caractère prédictif des variables de diagnostic.

Q Que représentent les graphiques ci-dessous? Quelle méthode à été employée ? Quelles options et choix sont possibles ?


In [ ]:
res.hcpc=HCPC(afc,method="ward",order=FALSE,nb.clust=3,graph=FALSE)
par(mar=c(2.1, 2.1, .1, .1))
plot(res.hcpc,choice="tree")

Q Pourquoi choisir trois classes ? Quelles autres méthodes de classification non supervisée pourraient être employées ? Avec quelle différence majeure ? Commenter les résultats ci-dessous.


In [ ]:
options(repr.plot.width=5, repr.plot.height=5)
color=as.integer(res.hcpc$data.clust$clust)
par(mar=c(2.1, 2.1, .1, .1))
plot(afc, c(1,2),choix="ind",cex=0.8,label="var",habillage="quali",col.ind=color,title="")

In [ ]:
options(repr.plot.width=2, repr.plot.height=2)
par(mar=c(2.1, 2.1, .1, .1))
mosaicplot(table(color,heartQ2[,"Coro"]),main="")
table(color,heartQ2[,"Coro"] )

Q Que produisent les commandes suivantes?


In [ ]:
dist.mod=dist(afc$var$coord, method="euclidean")
hclusmod=hclust(dist.mod,method="ward.D")

Q Décrire la strétégie mise en oeuvre ci-dessous.


In [ ]:
options(repr.plot.width=2, repr.plot.height=2)
par(mar=c(2.1, 2.1, .1, .1))
plot(sort(hclusmod$height,decreasing=T)[1:10],ylab="")

Q Que représente le graphe ci-dessus? Décision?


In [ ]:
hclasmod = cutree(hclusmod,k=4)
clas.mod=kmeans(afc$var$coord, 4)
kclasmod=clas.mod$cluster
# comparaison des classes entre CAH et k-means
table(hclasmod,kclasmod)

In [ ]:
# classif des individus
dist.ind=dist(afc$ind$coord, method="euclidean")
hclusind=hclust(dist.ind,method="ward.D")
par(mar=c(2.1, 2.1, .1, .1))
plot(sort(hclusind$height,decreasing=T)[1:10],ylab="")

In [ ]:
hclasind = cutree(hclusind,k=3)
clas.ind=kmeans(afc$ind$coord, 3)
kclasind=clas.ind$cluster
# comparaison des classes entre CAH et k-means
table(hclasind,kclasind)

In [ ]:
table(kclasind,heartQ2[,"Coro"])

4 Factorisation de matrices non négatives (NMF)


In [ ]:
library(NMF)
options(repr.plot.width=6, repr.plot.height=6)
aheatmap(t(tab.disjonctif(heartQ2[,-c(1,9,10)])),Rowv=hclusmod,Colv=hclusind,legend=F,annCol=heartQ2[,9])

Q Que représente le graphe ci-dessus? Quelle interprétation en faire?

Q Quelles sont les caractéristiques d’un tableau disjonctif complet ? Quelle autre méthode qu’une SVD permet donc sa décomposition ? Quelles sont les différences majeures avec la SVD ?