Explorer et visualiser des données Big Data ou mégadonnées
en langage R
L'essentiel de cette page
Visualiser de grands jeux de données (ce qu'on appelle du Big-Data), sans perdre de l'information, est impossible.
Il existe toutefois des représentations pour visualiser de gros jeux de données, de taille intermédiaire, sans perdre d'information. On pourrait parler de la théorie des graphes (les représentations en réseau) développé dans une autre page. On s'intéressera ici au représentation pour visualiser l'ensemble d'un tableau de données, d'une dataframe d'un seul coup d’œil.
1- Visualiser les courbes de densité de toutes les variables d'un seul coup avec ridge_plot()
La fonction maison ridge_plot() permet d'afficher une courbe de densité pour chaque variable. Elle est efficace pour visualiser ainsi toutes les données d'une dataframe (d'un tableau) de 10 à 15 variables.
Elle permet aussi de subdiviser les coubes de densité par classes. On peut aussi donner plusieurs variables de classes pour croiser le tout.
Exemple avec les données wine de FactoMineR
# Les données
install.packages("FactoMineR") ; library("FactoMineR") ; data("wine")
# L'affichage
ridge_plot(wine[,1:12],by=wine[,1])
# Variables 1 à 12 en découpant les classes en fonction de la variable discrète 1
Chaque bâtonnet représente 1 individu.
Code de la fonction ridge_plot() à copier-coller (cliquer pour dérouler)
install.packages("RColorBrewer") ; install.packages("berryFunctions")
##############################
# ridge_plot() - version 02 - 08/06/2020
##############################
ridge_plot = function(data,by=c(),col_pch="#AAAAAA99",cmap="Set1") {
# by : liste de classes ou dataframe de plusieurs classes à croiser.
data <- data.frame(data)
indices = c()
for (i in c(1:ncol(data))) {
if (is(data[,i])[1]=="numeric") {
indices <- c(indices,i)
}
}
if (length(indices) < ncol(data)) {cat("Attention, les variables non-numériques ne sont pas affichées !\n")}
data <- data.frame(data[,indices])
nvar = ncol(data)
#layout(matrix(1:nvar,nvar,1))
# Si la variable de catégorie n'est pas précisée
if (length(by) == 0) {
#plot.new()
#par(mar=c(marge basse, marge gauche, marge haute, marge droite))
for (i in c(1:nvar)) {
density <- density(data[,i]) # créer une liste des densités
if (i==1) {plot.new()}
fig_temp <- c(0,1,(nvar-i)*(1/nvar),(nvar+1-i)*(1/nvar))
par(fig=fig_temp, mar = c(0.2,0.2,0.5,0.2), new=TRUE)
plot(x=data[,i],y=rep(max(density$y)/2,length(data[,i])),pch="|",col=col_pch,axes=F,main=colnames(data)[i],xlab="",ylab="",ylim=c(0,max(density$y)),cex.main=0.6)
lines(density, col = "red",lwd=1) # Superposer la ligne
polygon(density, col = "#AA111177") # Superposer le polygone
}
# Si la/les variables de catégorie est précisée
} else {
# Agglomérer les variables de catégories
by = data.frame(by)
#print(by)
cat_all <- by[,1]
if (ncol(by) > 1) {
for (i in c(2:ncol(by))) {
cat_all <- paste(cat_all,by[,i])
}
}
cat_unique = unique(cat_all)
require(RColorBrewer) ; require(berryFunctions)
col = addAlpha(brewer.pal(n = length(cat_unique), name = cmap),0.5)
for (i in c(1:nvar)) {
#cat("Variable :",i,"\n")
# Détection de la zone de densité maximale pour pondération de l'affichage
maxx = 0
for (j in c(1:length(cat_unique))) {
x_temp = data[,i][cat_all==cat_unique[j]]
maxx <- max(maxx,max(density(x_temp)$y))
}
if (i==1) {plot.new()}
fig_temp <- c(0,1,(nvar-i)*(1/nvar),(nvar+1-i)*(1/nvar))
par(fig=fig_temp, mar = c(0.2,0.2,0.5,0.2), new=TRUE)
plot(x=data[,i],y=rep(max(density(data[,i])$y)/2,length(data[,i])),pch="|",col="white",axes=F,xlab="",main=colnames(data)[i],ylab="",ylim=c(0,maxx),cex.main=0.6)
for (j in c(1:length(cat_unique))) {
x_temp = data[,i][cat_all==cat_unique[j]]
#cat(x_temp,"\n")
densit <- density(x_temp) # créer une liste des densités
if (i == 1) {
#cat("stop1\n")
points(x=x_temp,y=rep(max/2,length(x_temp)),pch="|",col=col[j],cex=2)
}else{
#cat("stop2")
points(x=x_temp,y=rep(maxx/2,length(x_temp)),pch="|",col=col[j],cex=2)}
#cat("stop\n");#browser()
lines(densit, col = col[j],lwd=1) # Superposer la ligne
#cat("stop polygon\n");# browser()
polygon(densit, col = col[j]) # Superposer le polygone
}
}
}
}
En projet d'amélioration de cette fonction de kernel density 1D :
Ajouter les min, max, quartiles et moyennes de chaque variable (en option)
Segmenter en fonction de cat ou cat à n colonnnes : déjà fait mais il faut que la fonction gère lorsqu'il n'y a pas assez de point pour une sous-cat pour faire une courbe de densité (ne rien afficher, passer à la suite)
Calibrer les couleurs automatiquement mais afficher une légende dans une fenêtre
Mettre les points sous les courbes
Mettre une option quart = 1 pour trier en fonction de la variable 1 les autres variables pour les quartiles 1 et 4 et -3 groupés
Afficher la légende dans une boîte X11
Ne pas afficher les individus en Big Data
S'inspirer de la comparaison de densités (install.packages("sm") ; library("sm") ; sm.density.compare()) pour permettre de comparer les densités en tenant compte du nombre d'individus ou pas (scale = F/T)
Autoriser le contournement de données manquantes
2- Heatmap, visualiser un tableau de données de façon interactive
matrixplot() permet de visualiser les données d'un tableau, les données manquantes en rouge et de trier toutes les données en fonction d'une variable en cliquant dessus.
# install.packages("VIM")
# Charger le jeu de données sleep (exemple)
data(sleep)
library("VIM")
# Sous RStudio (optionnel sous R)
x11() # Ouvrir fenêtre interactive
# Afficher la matrice
matrixplot(sleep,cex.axis=0.6)
# Cliquer dessus pour trier
# Faire échap à la fin
3- Coordonnées parallèles pour visualiser la relation entre une variables et plusieurs autres
Les coordonnées parallèles permettent de visualiser les données d'une dataframe en fonction d'une variable en particulier pour voir si cette variable est reliée à d'autres.
Dans cet exemple : la largeur des sépales d'iris est reliée à 3 autres variables. On voit ainsi que les sépales peu larges correspondent à des pétales peu larges aussi.
Le code pour réaliser ce graphique de parallele coordinate plot avec R
data(iris) ; colnames(iris)
library(plotly)
# Mettre titre d'une colonne en variable
j= "Sepal.Width"
iris[[j]]
# parallele coordinate plot sur variable
mon_titre = paste("Variable :",j)
fig <- iris %>% plot_ly(type = 'parcoords',
line = list(color = ~get(j),
colorscale = 'Jet',
showscale = TRUE,
reversescale = TRUE,
cmin = min( iris[[j]]),
cmax = max( iris[[j]])),
dimensions = list(
list(range = range(iris$Sepal.Length),
label = 'Longueur Sépale', values = ~Sepal.Length),
list(range = range(iris$Petal.Length),
label = 'Longueur Pétale', values = ~Petal.Length),
list(range = range(iris$Petal.Width),
label = 'Largeur Pétale', values = ~Petal.Width)
)) %>% layout(title = mon_titre)
#
print(fig)
Un exemple où l'on colore selon une catégorie (variable discrète)).
Ici on colore les données de chaque espèce d'iris.
Coloration de catégories en parcoords avec plotly (code à copier-coller)
# parallele coordinate plot en fonction d'une catégorie
# Conversion des catégories en valeurs comprises entre 0 et 1
categories <- as.character(iris$Species) ; j <- 0
for (i in unique(categories)) {
categories[categories==i] <- j
j <- j+0.5
}
categories <- as.numeric(categories)
# Graphique
mon_titre = paste("Espèces d'Iris")
fig <- iris %>% plot_ly(type = 'parcoords',
line = list(color = categories,
colorscale = list(c(0,'red'),c(0.5,'green'),c(1,'blue'))),
dimensions = list(
list(range = range(iris$Sepal.Length),
label = 'Longueur Sépale', values = ~Sepal.Length),
list(range = range(iris$Sepal.Width),
label = "Largeur Sépale", values = ~Sepal.Width),
list(range = range(iris$Petal.Length),
label = 'Longueur Pétale', values = ~Petal.Length),
list(range = range(iris$Petal.Width),
label = 'Largeur Pétale', values = ~Petal.Width)
)) %>% layout(title = mon_titre)
print(fig)
Fonction moins performante, mais idéal pour voir les données manquantes et comprendre comment elles sont reliées d'une variable à l'autre :
Coordonnées parallèles et données manquantes :
library("VIM")
data(sleep) ; colnames(sleep)
parcoordMiss(sleep[,1:7])
4-Treemap pour explorer des données qualitatives
Simulons d'abord un jeu de données qualitatives.
data(iris)
colnames(iris)
iris2 <- data.frame(
"A"=cut(iris[,1],pretty(range(iris[,1]),2)),
"B"=cut(iris[,2],pretty(range(iris[,2]),2)),
"C"=cut(iris[,3],pretty(range(iris[,3]),2)),
"D"=cut(iris[,4],pretty(range(iris[,4]),2)),
"Species"=iris$Species)
levels(iris2$A) <- c("SepLen petit","SepLen moyen","SepLen grand") ; levels(iris2$B) <- c("SepWid petit","SepWid moyen","SepWid grand") ; levels(iris2$C) <- c("PetLen petit","PetLen moyen","PetLen grand") ; levels(iris2$D) <- c("PetWit petit","PetWit moyen","PetWit grand")
table(iris2) -> table2 ; ftable(table2) -> table3 ; table3 <- data.frame(table3)
table3 <- table3[table3$Freq!=0,]
head(table3)
On a ainsi 5 variables qualitatives, il serait bien d'explorer combien j'ai d'iris de chaque espèce. Combien ont des grands pétales...
A B C D Species Freq
1 SepLen petit SepWid petit PetLen petit PetWit petit setosa 8
4 SepLen petit SepWid moyen PetLen petit PetWit petit setosa 39
7 SepLen petit SepWid grand PetLen petit PetWit petit setosa 3
82 SepLen petit SepWid petit PetLen petit PetWit petit versicolor 6
109 SepLen petit SepWid petit PetLen petit PetWit moyen versicolor 20
110 SepLen moyen SepWid petit PetLen petit PetWit moyen versicolor 14
On va ainsi créer un tableau de comptage ci-après qui compte les individus pour chaque catégorie et sous-catégorie :
ids labels parents Freq
1 setosa setosa 50
2 versicolor versicolor 50
3 virginica virginica 50
4 setosa-SepLen petit SepLen petit setosa 50
5 versicolor-SepLen petit SepLen petit versicolor 30
6 virginica-SepLen petit SepLen petit virginica 9
Comptabilisons les individus pour chaque catégorie et sous catégories
# Ordre de la prise en compte des variables
mydata <- iris2
ordre <- c(5,1:4)
ids <- c()
labels <- c()
Freq <- c()
parents <- c()
for (i in 1:length(ordre)) {
if (i==1) {
temp <- table(mydata[,ordre[i]])
ids <- c(ids,names(temp))
labels <- as.character(c(labels,names(temp)))
Freq <- c(Freq,table(mydata[,ordre[i]]))
parents <- as.character(c(parents,rep("",length(names(temp)))))
} else {
temp <- data.frame(ftable(table(iris2[,ordre[1:i]])))
temp <- temp[temp$Freq!=0,]
ids= c(ids,apply(temp[,1:(ncol(temp)-1)],1,function(x){paste0(x,collapse="-")}))
labels = c(labels,as.character(temp[,i]))
if (i==2) {
print("a")
parents <- c(parents,as.character(temp[,1]))
} else {
parents <- c(parents,apply(temp[,1:(i-1)],1,function(x){paste0(x,collapse="-")}))
}
Freq=c(Freq,temp[,ncol(temp)])
}
}
table4 <- data.frame(ids,labels,parents,Freq)
head(table4)
Affichage
library(plotly)
plot_ly(data = table4, branchvalues = "total",
type = "treemap", labels = ~labels,
parents = ~parents, values = ~Freq, ids = ~ids)
5-Les kernels density 2D lorsqu'il y a trop de points pour faire nuages de points classiques
Lorsqu'il y a trop de points à affiche, l'on va comptabiliser la densité des points.
Ainsi on passera de l'exemple ci-dessous au graphique à droite : idéal pour voir qu'il y en réalité 3 types de points !
Suivre ce lien pour construire son propre kernel density 2D (pages sur les autres graphiques y = f(x).
# Reproduire cet exemple
library(MASS) # va générer le kernel (penser à l'installer) (kernel density 2D)
x <- c(rnorm(600,10,3),rnorm(1000,16,4),rnorm(400,20,3))
y <- c(rnorm(600,16,3),rnorm(1000,8,4),rnorm(400,20,3))
plot(x,y,pch=3)
my_kernel <- kde2d(x, y,n=300)
image(my_kernel)
library(raster)
my_raster <- raster(my_kernel)
plot(my_raster)
# n permet de lisser l'image. n grand, image lisse - n petit, image moche