Gérer les données discrètes et mixtes

Que faire en langage R si l'on désire faire différents calculs avec des données discrètes ou mixtes ?

L'essentiel de cette page !

Voici la présentation d'une fonction qui permet de convertir toutes les données discrètes (de type facteur ou texte) en données numériques. Elle rejoint dans un sens ce que fait AFDM de FactomineR pour faire des ACP (PCA) sur données mixtes.

Mais elle a l'ambition d'aller plus loin...

Le principe est de convertir chaque variable discrètes en des colonnes numériques selon la logique suivante :

  • Si j'ai une colonne sexe (avec F et M), on aura à la place deux colonnes, l'une mettant des 1 aux femmes et 0 aux hommes, l'autre l'inverse.

Il devient alors possible de faire des regroupements (k-means, CAH...), des réductions dimensionnelles (ACP, tSNE...), des régressions...

Pour trouver la fonction : copier-coller l'ensemble du code de l'encadré 3 en bas de page.

Ce graphique est le résultat d'une analyse en composantes principales réalisées sur des données mixtes. On y fait figurer l'IMC d'individus et leurs résultats de réponses à un questionnaire : sexe (F ou M), font-ils du sport (oui ou non), boivent-ils régulièrement (oui ou non).

On y voit apparaître des regroupements, celui en particulier des garçons qui boivent sans faire de sport, à l'IMC très élevé.

1- Des exemples d'applications

1- Chargeons les données mixtes wine de FactoMineR

install.packages("FactoMineR") ; library("FactoMineR") ; data("wine") ; head(wine)

2- Je vais pouvoir convertir l'ensemble en données numériques

temp = discretor(wine)

On remarquera que la fonction revoie le message : Warning : pas assez d'individus !

C'est normal : puisqu'il est toujours déconseillé d'avoir plus de variables que d'individus...

3- Je peux maintenant faire une ACP - voir page d'aide sur les ACP

PCA = prcomp(temp) ; biplot(PCA)

4- De la même façon sur les données IRIS, je peux utiliser les noms d'espèces comme une 5ème variable pour faire, par exemple, un kmeans

data(iris) ; temp = discretor(iris)

cluster_mixte = kmeans(temp,3) ; cluster_numeric = kmeans(iris[,1:4],3)

layout(matrix(1:3,1,3))

plot(iris[,1],iris[,2],col=iris[,5], cex=2, pch=16, main="Véritables résultats")

plot(iris[,1],iris[,2],col= cluster_numeric$cluster, cex=2, pch=16, main="K-means sur variables numériques")

plot(iris[,1],iris[,2],col=cluster_mixte$cluster, cex=2, pch=16, main="K-means sur données mixtes")

Les résultats du k-means sur données mixtes est forcément plus fiable et se rapproche des véritables résultats.

2- Comment configurer la fonction discretor() ?

Par défaut, discretor() va appliquer les actions suivantes avec le paramétrage ci-dessous :

  • ex = TRUE - Pour chaque variable discrète, contenant n modalités ou n classes, la fonction ne va générer que n-1 colonnes. En effet, si j'ai 3 classes A, B, C, on va pouvoir faire une colonne avec des 1 pour les A et 0 pour B & C ainsi qu'une colonne avec 1 pour B et 0 pour A & C. Pas besoin de faire une colonne avec des 1 pour C. Cela se déduit automatiquement des deux premières colonnes. Si on veut autant de colonnes que de classes, faire ex = FALSE.

  • NA, par défaut, les NA sont conservés.

  • center = TRUE - Permet de centrer toutes les variables, les anciennes, comme les nouvelles.

  • scale = TRUE - Permet de réduire toutes les variables, les anciennes, comme les nouvelles.

  • pond = TRUE - permet de diviser les valeurs des colonnes par le nombre de classes qui a généré ces colonnes. Par exemple. Si on a fait 2 colonnes M et F à partir de la colonne Sexe, les valeurs de toutes ces colonnes seront divisées par 2 (après qu'elles aient été centrées et réduites si nécessaire) afin de ne pas leur donner un poids proportionnel au nombre de classes.

3- Code de la fonction discretor() à copier-coller

#########################

# discretor() - version 01 - 29/11/2019 - à copier-coller

#########################

discretor = function(data,center=T,scale=T, pond=T, ex=T) {

# center : centrer les données

# scale : réduire les données

# pond : diviser le poids de chaque colonne en fonction du nombre de classes initiales

# ex : supprimer une classe sur n car 1 ddl.

df = data.frame(seq(1,nrow(data),by=1))

for (i in 1:ncol(data)) {

if (is(data[,i])[1] == "numeric") {

temp = data[,i]

if (center==T) {temp = temp-mean(temp,na.rm=T)}

if (scale==T) {temp = temp/sd(temp,na.rm=T)}

df = cbind(df,temp)

colnames(df)[ncol(df)] <- colnames(data)[i]

} else if ((is(data[,i])[1] == "character")|(is(data[,i])[1] == "factor")) {

classes <- unique(data[,i])

if (ex == T) {

classes = classes[!is.na(classes)]

for (j in classes[1:(length(classes)-1)]) {

temp <- rep(0,nrow(data))

temp[data[,i]==j] <- 1

ind_NA <- which(is.na(data[,i]))

temp[ind_NA] <- NA

if (center==T) {temp = temp-mean(temp,na.rm=T)}

if (scale==T) {temp = temp/sd(temp,na.rm=T)}

if (pond==T) {temp = temp/(length(classes[!is.na(classes)])-1)}

df = cbind(df,temp)

titre = paste0(colnames(data)[i],"+",j)

colnames(df)[ncol(df)] <- titre

}

} else {

for (j in classes) {

if (!is.na(j)==TRUE) {

temp <- rep(0,nrow(data))

temp[data[,i]==j] <- 1

ind_NA <- which(is.na(data[,i]))

temp[ind_NA] <- NA

if (center==T) {temp = temp-mean(temp,na.rm=T)}

if (scale==T) {temp = temp/sd(temp,na.rm=T)}

if (pond==T) {temp = temp/length(classes[!is.na(classes)])}

df = cbind(df,temp)

titre = paste0(colnames(data)[i],"+",j)

colnames(df)[ncol(df)] <- titre

}

}

}

}

}

df <- df[,-1]

if (ncol(df) >= nrow(df)) {cat("Warning : pas assez d'individus !\n")}

return(df)

}