Ce post n'est pas vraiment une question que je pose ici, mais le fruit d'élucubrations personnelles sur un exercice que je me suis récemment imposé pour pratiquer la création de plots en 3D (un peu comme un musicien qui s’impose des gammes pour faire le tour d'un sujet).
L'objectif est de construite un scatter plot en 3D, d'y ajuster une surface de régression, de mettre ce graphe en rotation et d’insérer le résultat dans un fichier GIF (par exemple pour le mettre dans une présentation PowerPoint - chose que je fais parfois). En cherchant pas mal sur le web, et dans les docs en tout genre, je n'ai trouvé que trois solutions, qui présentent des avantages et des inconvénients. Je n'ai rien trouvé de parfait. Je vous donne ici ces trois solutions, en pesant le pour et le contre. Si vous avez d'autres idées, elles sont les bienvenues..
Solution 1
Passer par persp, puis mettre le graphe en rotation et sauver le tout dans un fichier GIF avec le package animation. Un code possible est le suivant :
Code : Tout sélectionner
function()
{
# excercice de rotation d'un graphe 3D avec regression et GIF
# Option avec persp() et package animation
#
# Eric Wajnberg - Décembre 2021
# Charger d'abord library(animation)
#
# ensuite il faut appeler cette fonction avec
# saveGIF(nom_de_cette_fontion(),movie.name="test.gif")
#
# Ca produit une image GIF assez pixelisée mais sans fond
# ce qui peut être pratique pour inserer le GIF dans une présentation PowerPoint
# (par(bg=..) est pris en compte dans l'affichage à l'écran mais pas dans
# le fichier GIF généré)
# ani.options(ani.res=...) n'a pas d'effet
# Dans ce cas imagick (notamment ffmpeg.exe) a été posé dans la directory donnée ci-dessous
ani.record(reset = TRUE)
ani.options(interval = .1)
ani.options(ffmpeg = 'C:/Program Files/ImageMagick-7.1.0-Q16-HDRI/ffmpeg.exe')
# construction d'un jeu de données bidon
x=seq(-3,3,0.5)
y=x
my.data=expand.grid(x,y)
names(my.data)=c("abscisse","ordonnee")
my.data=as.data.frame(cbind(my.data$abscisse, my.data$ordonnee,
my.data$abscisse+my.data$ordonnee*my.data$ordonnee+rnorm(length(x)*length(y))))
names(my.data)=c("abscisse","ordonnee","cote")
# calcul des données attendu par regression locale et
# redistribution sous forme matricielle (format imposé par persp)
attendu=predict(loess(my.data$cote~my.data$abscisse+my.data$ordonnee))
z=matrix(0,length(x),length(x))
for (i in 1:(length(x)*length(y)))
{
z[which(x==my.data$abscisse[i]),which(y==my.data$ordonnee[i])]=attendu[i]
}
# couleur du fond et taille du texte
par(bg=rgb(.001,.01,.7,.01),cex=1.5)
# tracer du plot sous différents angles
for (i in seq(-180,180,5))
{
my.persp=persp(x,y,z, col="lightblue", ticktype="detailed", theta=i, phi=20,
cex.lab=1.5, family="serif", lwd=3, zlim=c(min(my.data$cote),max(my.data$cote)))
# ajout de points
my.points=trans3d(x=my.data$abscisse,y=my.data$ordonnee,z=my.data$cote,pmat=my.persp)
points(my.points,pch=16,col="red")
ani.record()
}
}
Pour cette solution 1, selon moi :
Avantages :
- Le fond est transparent (pratique pour insérer par exemple dans PowerPoint).
Désavantages :
- Nécessite de créer « à la main » la surface de régression.
- Suppose l’apprentissage du package « animation ».
- Devoir de jongler avec différentes représentations des données (vectoriels et matricielles)
- Le fichier résultat est assez pixélisé sans moyen de régler la résolution.
- L’image ne tourne pas autour de son véritable centre. Elle se « dandine ».
- Nécessité de contrôler « à la main » la rotation (ce qui est également un avantage)
Solution 2
Passer par scatter3d du package rgl, et utiliser les outils de rotation et de sauvegarde en GIF de ce package. Un code possible est le suivant :
Code : Tout sélectionner
function()
{
# Excercice de rotation d'un graphe 3D avec regression et sortie GIF
#
# Option avec scatter3d du package car (et rgl)
#
# Eric Wajnberg - Décembre 2021
# Charger d'abord library(car) et library(rgl)
#
# par(bg=..) n'est est pris en compte et le fond reste blanc ou
# noir (argument "bg.col"), ce qui n'est pas "pratique"
# par exemple pour inserer le GIF dans une présentation PowerPoint
# construction d'un jeu de données bidon
x=seq(-3,3,0.5)
y=x
my.data=expand.grid(x,y)
names(my.data)=c("abscisse","ordonnee")
my.data=as.data.frame(cbind(my.data$abscisse,my.data$ordonnee,
my.data$abscisse+my.data$ordonnee*my.data$ordonnee+rnorm(length(x)*length(y))))
names(my.data)=c("abscisse","ordonnee","cote")
# parametre font sur les axes
par3d(family="serif",cex=1.5, font=2)
# tracer du plot
scatter3d(x=my.data$abscisse,z=my.data$ordonnee,y=my.data$cote,fit="smooth",revolutions=0,
xlab="x",zlab="y",ylab="z", axis.ticks=TRUE,residuals=FALSE, speed=2.5, axis.col=rep("black",3))
# sauvegarde l'animation dans un fichier gif (un package supplementaire doit être chargé
# (par exemple magick)
movie3d(spin3d(axis = c(0, 1, 0), rpm = 15),duration=4,movie="test",dir = getwd(),convert=TRUE)
}
Pour cette solution 2, selon moi :
Avantages :
- Image « plus élégante ».
- Tourne bien sur elle-même sans se « dandiner »
Désavantages :
- Le fond est blanc ou noir (pas pratique pour insérer par exemple dans PowerPoint).
- Pas de « tick marks » sur les axes.
- Pas de contrôle de la surface de régression (pas paramétrable) (ou alors on peut s'en sortir en combinant persp3d avec plot3d)
- Pas moyen de contrôler le sens de la rotation.
Solution 3
Passer par scatter3D du package plot3D, et s'appuyer ensuite sur les outils du package rgl (via le package plot3Drgl). Un code possible est le suivant :
Code : Tout sélectionner
function()
{
# Excercice de rotation d'un graphe 3D avec regression et sortie GIF
#
# Option avec scatter3D du package plot3D (et rgl), et egalement plot3Drgl
#
# Eric Wajnberg - Décembre 2021
# Charger d'abord library(plot3D), library(rgl)
#
# par(bg=..) n'est est pris en compte et le fond reste blanc, ce qui n'est pas "pratique"
# par exemple pour inserer le GIF dans une présentation PowerPoint
# construction d'un jeu de données bidon
x=seq(-3,3,0.5)
y=x
my.data=expand.grid(x,y)
names(my.data)=c("abscisse","ordonnee")
my.data=as.data.frame(cbind(my.data$abscisse,my.data$ordonnee,
my.data$abscisse+my.data$ordonnee*my.data$ordonnee+rnorm(length(x)*length(y))))
names(my.data)=c("abscisse","ordonnee","cote")
# calcul des données attendu par regression local
# redistribution sous forme matricielle (format imposé)
attendu=predict(loess(my.data$cote~my.data$abscisse+my.data$ordonnee))
z=matrix(0,length(x),length(x))
for (i in 1:(length(x)*length(y)))
{
z[which(x==my.data$abscisse[i]),which(y==my.data$ordonnee[i])]=attendu[i]
}
# couleur du fond et taille du texte
par(bg=rgb(.001,.01,.7,.01),cex=1.5)
# tracer du plot
scatter3D(x=my.data$abscisse,y=my.data$ordonnee,z=my.data$cote,xlab="x",ylab="y",zlab="z",
colvar=NULL,col="red",bty="u",surf=list(x=x,y=y,z=z,col="blue",facets=NA),
col.panel=rgb(.001,.01,.7,.01), pch=16,lwd=3, cex.lab=1.5, family="serif",ticktype="detailed")
# on bascule ensuite le graphs dans rgl
# grace à la library plot3Drgl (necessite donc rgl installé)
library("plot3Drgl")
plotrgl()
# sauvegarde l'animation dans un fichier gif (un package supplementaire doit être chargé
# (par exemple magick)
movie3d(spin3d(axis = c(0, 0, 1), rpm = 15),duration=4,movie="test",dir = getwd(),convert=TRUE)
}
Pour cette solution 3, selon moi :
Avantages :
- Tourne bien sur elle-même sans se « dandiner »
Désavantages :
- Le fond est blanc (pas pratique pour insérer par exemple dans PowerPoint).
- Nécessite de créer « à la main » la surface de régression.
- Devoir de jongler avec différentes représentations des données (vectoriels et matricielles)
- Pas moyen de contrôler le sens de la rotation.
- Pas moyen de modifier les polices de caractère utilisées sur les axes
Cette solution 3 est en fait un compromis entre les deux premières solutions.
Vous le voyez donc, rien d'idéal ici, et je crois avoir fait le tour de toutes les solutions qu'offrent R. A moins que j'en ai encore loupées ? Toute réaction sur ce post, ou d'autres solutions encore sont les bienvenues.
En espérant également que ces explications seront utiles pour d'autres.
Amicalement, Eric.