Chapitre 7 Programmation orientée objet à l’aide de classe R6

7.1 Principes et buts

Un objet est une sorte de liste mieux structurée.

Principe :

  • créer de nouvelles classes avec des attributs/champs (variables) définis et des méthodes (fonctions propres)
  • un objet est alors instancié à partir d’une classe,
  • créer des classes générales et des classes plus spécifiques qui héritent des clases générales.

Intérêt :

  • structurer le code,
  • éviter de dupliquer le code,
  • plus facile à débugger, plus robuste,
  • plus lisible pour la diffusion,
  • coder à plusieurs.

Structure d’une classe R6 (Chang 2017)

library(R6)
maclasse=R6class("maclasse",
                 public=list(
                  attr1=NULL,
                  attr2=NULL,
                  attr3=NULL,
                  initialize=function(attr1=NA,attr2=NA)
                  {
                    self$attr1=attr1
                    self$attr2=attr2
                    private$check()
                    private$attr4=attr1+attr2
                  },
                  somme=function()
                  {
                    return(private$attr4)
                  }
                  ),
                  private=list(
                  attr4=NULL,
                  check=function()
                  {
                    if (self$attr1<0) 
                      stop("attr1 must be positive")
                  }
                 )
                 )
  • self$... permet d’appeler (dans la définition de la classe) les champs et les méthodes propres à la classe déclarés en public.
  • private$ permet d’appeler (dans la définition de la classe) les champs et les méthodes propres à la classe déclarés en private.
  • on crée/instancie un objet de la classe en tapant obj=maclasse$new(a1,a2).
  • on appelle les méthodes de la classe en tapant maclasse$somme().
  • on peut créer des sous-classes qui hériteront d’une classe supérieure.

7.2 Exemple de classes pour le modèle linéaire (généralisé)

On commence par créer des classes modèles ajustées qui contiennent les paramètres qui seront ajustés par le modèle.

library(R6)
modlin_ajuste=R6Class("modlin_ajuste",
        public = list(
          X=NULL,
          beta=NULL,
          initialize=function(X,beta)
          {
             self$X=X
             self$beta=beta
          }
        )
)

On a créé une classe générale et on crée 2 sousclasses qui en hériteront : gauss_modlin_ajuste et poiss_modlin_ajuste. Dans la définition des sous-clases, la commande inherit permet de faire hériter de la classe précédemment définie. Pour le modèle gaussien, on peut compléter l’initialisation pour ajouter \(\sigma^2\) en utilisant super$initialize(X,beta) dans la fonction intialize de cette classe.

poiss_modlin_ajuste=R6Class("poiss_modlin_ajuste",
          inherit = modlin_ajuste,
          public = list(
             pred=function(Xnew=NULL)
             {
               if (is.null(Xnew))
                 {
                   return(exp(self$X%*%self$beta))
               }
               else
                 return(exp(Xnew%*%self$beta))
             },
             sim=function(Xnew=NULL)
             {
               if (is.null(Xnew))
               {
                 return(rpois(nrow(self$X),self$pred()))
               }
               else 
                 return(rpois(nrow(Xnew),self$pred(Xnew)))
             }
          )
)
gauss_modlin_ajuste=R6Class("gauss_modlin_ajuste",
          inherit = modlin_ajuste,
          public = list(
            sigma2=NULL,
            initialize=function(X,beta,sig2)
            {
               super$initialize(X,beta)
               self$sigma2=sig2
               private$check()
            },
            pred=function(Xnew=NULL)
            {
              if (is.null(Xnew))
              {
                 return(self$X%*%self$beta)  
              }
              else 
                  return(Xnew%*%self$beta)
            },
            sim=function(Xnew=NULL)
            { 
              if (is.null(Xnew))
              {
                return(rnorm(nrow(self$X),self$pred(),sqrt(self$sigma2)))
              }
              else 
                 return(rnorm(nrow(Xnew),self$pred(Xnew),sqrt(self$sigma2)))
            }
          ),
          private = list(
            check=function()
            {
              if (self$sigma2<0) stop("sigma^2 must be positive")
            }
          )
)

On essaie :

modgauss=gauss_modlin_ajuste$new(matrix(runif(100,1,7),50,2),c(-2,6),3)
# on accede aux champs ainsi
modgauss$beta
## [1] -2  6
# on peut utiliser les fonctions pour simuler aux points du meme design
modgauss$sim()
##  [1]  3.7987847 24.3013605 29.5980269 13.5334639  7.7853039 33.4422171
##  [7] -4.2696812  3.9788951 11.9717539  1.4167324 14.7194657 28.3688206
## [13] 26.4696512  4.4812454 -2.8421331 31.7879151 10.9969143 32.0781787
## [19] 15.4464883 29.1907547 -0.7384483 27.1382962 18.3899368  8.3923479
## [25]  3.1030153  5.4330596 27.5677067 26.0662218 11.3457485 -0.6470589
## [31] 26.6901825 12.1130800 -3.3846881 12.5507711 -1.3021859 17.0468251
## [37]  2.7562345 10.4962191 14.2075305  9.5410273 34.9897079 17.2015327
## [43] 11.0175978 -1.7854537 17.9720691 26.6476333 26.6555067  1.6562477
## [49] 27.5496485  2.0639072
# on peut utiliser la meme fonction pour simuler aux points d'un autre design
modgauss$sim(matrix(c(0,2),1,2))
## [1] 14.49275

On crée à présent les classes permettant de recevoir les données. Une fonction estime au sein de ces classes aura pour sortie un modèle ajusté.

don=R6Class("don",
    public=list(
      Y=NULL,
      X=NULL,
      initialize=function(X,Y)
      {
        self$X=X
        self$Y=Y
        private$check()
      }
      ),
     private=list(
       check=function()
       {
         if (nrow(self$X)!=length(self$Y))
           stop("êtes vous ivre ?")
       }
     )
    )

On distingue une classe pour le modèle Poisson et pour le modèle gaussien.

poisdon=R6Class("poisdon",
                 inherit = don,
                 public = list(
                   estime=function()
                   {
                     res=glm(self$Y~self$X-1,family = "poisson")
                     obj=poiss_modlin_ajuste$new(self$X,unname(res$coefficients))
                     return(obj)
                   }
                 )
                 )

On essaie

#on définit un modèle
modpois=poiss_modlin_ajuste$new(matrix(runif(100,-1,2),50,2),c(1,2))
#on simule selon ce modele
Y=modpois$sim()
X=modpois$X

# on cree la structure donnees
ex=poisdon$new(X,Y)
#on estime
est=ex$estime()
est$beta
## [1] 1.012569 1.984351
#on peut prédire selon le modèle
est$pred(matrix(c(1,2),1,2))
##          [,1]
## [1,] 145.6593

On fait de même pour les données censées suivre une loi gaussienne en ajoutant la fonction à la suite de la définition de la classe. Ceci est utile lorsque les fonctions sont trop volumineuses.

gaussdon=R6Class("gaussdon",
                 inherit = don)

gaussdon$set("public","estime",
       function()
       {
            beta   <- solve(t(self$X)%*% self$X) %*% t(self$X) %*% self$Y
            sigma2 <- sum((self$Y - self$X %*% beta)^2)/(nrow(self$X) - ncol(self$X)) 
            obj=gauss_modlin_ajuste$new(self$X,c(beta),sigma2)
            return(obj)       
        }
)
modgauss=gauss_modlin_ajuste$new(matrix(runif(100,1,7),50,2),c(-2,6),3)
X=modgauss$X
Y=modgauss$sim()
exx=gaussdon$new(X,Y)
estt=exx$estime()
estt$beta
## [1] -2.083857  6.028376
estt$sigma2
## [1] 3.870877

Si on s’était trompé sur les dimensions.

modgauss=gauss_modlin_ajuste$new(matrix(runif(100,1,7),50,2),c(-2,6),3)
X=modgauss$X
Y=modgauss$sim()
Y=Y[-1]
a=try(gaussdon$new(X,Y))
print(a)
## [1] "Error in private$check() : êtes vous ivre ?\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in private$check(): êtes vous ivre ?>

7.3 Exercice

    1. Coder la sous-classe correspondante au modèle linéaire généralisée pour une loi bernoulli (lien logit ou probit)
    1. Coder en classe votre modèle préféré

7.4 Quelques remarques supplémentaires

Pour diffuser un code, certains (dont JC) pensent qu’il vaut mieux refaire une surcouche qui s’appelle de manière classique avec une fonction et des options.

Exemple

fitmod=function(X,Y,loi)
{
  switch(loi, 
         poisson={
           obj=poisdon$new(X,Y)
           res=obj$estime()
           return(res$beta)
         },
         gaussien={
           obj=gaussdon$new(X,Y)
           res=obj$estime()
           return(list(res$beta,res$sigma2))
         }  
  )
}

Lorsque l’on veut copier un objet (par exemple exx de la classe gaussdon), il faut faire :

a=exx$clone()

Sinon, si on fait b=exx, on modifiera exx en modifiant b.

Quelques liens utiles :

References

Chang, Winston. 2017. R6: Classes with Reference Semantics. https://CRAN.R-project.org/package=R6.