Chapitre 10 Aller plus loin avec les objets et la programmation fonctionnelle

Ce qui a été présenté dans ce module repose sur les fonctions du package tidyverse. Cette approche tend à se généraliser depuis quelques années, mais quand on cherche la réponse à un problème sur Internet, on trouve d’autres façons de programmer en R, qui font appel aux fonctions du package base et non du tidyverse \(\Rightarrow\) Cette partie donne quelques clés de compréhension.

10.1 Les objets dans R, plus de détails

Rappel : en informatique, un objet est défini par : ses attributs et ses méthodes (fonctions). Dans l’exemple du jeu d’échec, chaque pièce peut être vue comme un objet :

  • sa position sur le plateau constitue ses attributs
  • sa façon de se déplacer peut être vue comme une fonction qui ne s’applique qu’à ce type de pièce, donc une méthode

R est un langage orienté objet ; ces objets permettent de structurer les données selon leurs caractéristiques \(\Rightarrow\) on retrouve les données dans les attributs. Les méthodes sont en général transparentes pour l’utilisateur (cf. utilisation des fonctions summary, plot…). Les objets les plus courants sont les suivants :

  • Vecteurs : suite unidimensionnelle de valeurs ayant le même type.
  • Facteurs : vecteur qui prend un nombre limité de modalités (exemple : sexe). Il est défini par les niveaux (levels) et les libellés associés (labels).
  • Matrice et arrays : suites multidimensionnelles de valeurs (matrices=dimension 2 ; array=dimension n). A la différence d’une dataframe, les valeurs d’une matrice sont toutes du même type. Les arrays peuvent être très puissants pour gérer des millésimes.
  • Liste : ensemble d’objets différents. On peut stocker un vecteur alphanumérique + une matrice numérique dans une liste.
  • Tableaux (data.frame) : Objet qui ressemble le plus aux tables Excel, SAS ou SPSS… : description d’individus statistiques (observations, en ligne) par des caractéristiques (variables, en colonnes).
  • Fonctions : Objets particuliers qui donnent un résultat à partir de paramètres en entrée.
  • Autres objets : Il existe un très grand nombre d’objets ad hoc dans R. Par exemple
    • ts (time serie) pour les séries temporelles,
    • lm (linear model) qui contient tous les résultats d’une régression linéraire…
    • des graphiques
    • On peut même en définir de nouveaux soi-même !

10.2 Créer une nouvelle fonction en R

La fonction est un objet comme les autres, qu’on crée avec l’opérateur d’affectation. Elle est définie par des paramètres et elle se termine par la fonction return(). On reprend l’exemple du calcul de l’IMC

calcul_IMC <- function (poids, taille)
{
  ## La taille est exprimée en mètres
  imc <- poids / taille ^ 2
  return (imc)
}
calcul_IMC (poids = 80, taille = 1.89)
## [1] 22.39579
calcul_IMC (poids = 60, taille = 1.55)
## [1] 24.97399

10.3 Les boucles conditionnelles

Les commandes if et else sont bien entendues utilisables. Le “then” n’existe pas : il est implicite après les accolades.

diag_IMC <- function(poids,taille)
{
  imc <- poids / taille ^ 2
  if (imc < 18.5) {diag <- "maigre"}
  else if (imc < 25) {diag <- "normal"}
       else {diag <- "surpoids"}
  return (diag)
}
diag_IMC (poids=60,taille=1.89)
## [1] "maigre"
diag_IMC (poids=80,taille=1.89)
## [1] "normal"
diag_IMC (poids=80,taille=1.55)
## [1] "surpoids"

10.4 Les boucles

On peut utiliser les boucles classiques : repeat, while, for :

for (pp in seq(from = 50, to = 100, by = 5))
{
  print(paste ("Taille = 1,70m, poids =", pp, "Diagnotic :",
               diag_IMC (poids = pp, taille = 1.70)))
}
## [1] "Taille = 1,70m, poids = 50 Diagnotic : maigre"
## [1] "Taille = 1,70m, poids = 55 Diagnotic : normal"
## [1] "Taille = 1,70m, poids = 60 Diagnotic : normal"
## [1] "Taille = 1,70m, poids = 65 Diagnotic : normal"
## [1] "Taille = 1,70m, poids = 70 Diagnotic : normal"
## [1] "Taille = 1,70m, poids = 75 Diagnotic : surpoids"
## [1] "Taille = 1,70m, poids = 80 Diagnotic : surpoids"
## [1] "Taille = 1,70m, poids = 85 Diagnotic : surpoids"
## [1] "Taille = 1,70m, poids = 90 Diagnotic : surpoids"
## [1] "Taille = 1,70m, poids = 95 Diagnotic : surpoids"
## [1] "Taille = 1,70m, poids = 100 Diagnotic : surpoids"

10.5 Pour aller plus loin

10.5.1 matrices et arrays

Les matrices et les arrays permettent des calculs rapides et efficaces, et peuvent être très pratiques et optimisent le stockage des données. Ils demandent cependant plus de réflexion en amont quant à leur utilisation. On accède aux éléments avec les [].

Un hypercube de trois dimensions peut être représenté comme suit : On peut par exemple créer une matrice à 10 lignes et 10 colonnes remplie avec un tirage aléatoire selon une loi normale centrée réduit. De la même façon on peut créer un hypercube avec la fonction avec 10 lignes, 5 colonnes et de profondeur 3, toujours avec un tirage aléatoire selon une loi normale

mat <- matrix(rnorm(50), ncol = 5, nrow = 10)
arr <- array(rnorm(150),dim = c(10,5,3))
mat
##             [,1]       [,2]        [,3]        [,4]        [,5]
##  [1,]  1.8298970  1.9155237 -0.89170744 -1.38393491  0.33090038
##  [2,] -0.5859457  0.2704362 -1.39684058  0.33790250  0.06794822
##  [3,] -0.9014928  0.4969728 -1.30396452  0.55950906 -0.06483164
##  [4,]  0.4704234  0.5712386 -0.69828470 -0.31486558 -0.07120763
##  [5,] -0.9696539 -1.3090243  0.82188639  0.41390202  0.09632524
##  [6,]  0.7142647 -0.6392071 -1.10677379  0.56210125  0.08949779
##  [7,]  0.2615644 -1.5083266  0.06009281  0.78766302  0.79693897
##  [8,]  0.3533574  1.1150566 -0.61658806  2.43104459 -0.22886904
##  [9,]  0.1744039 -1.0749350  0.74073849  0.06538894  1.39909538
## [10,]  0.7072237 -0.3094418 -0.28265838  0.32017082 -0.84729113
arr
## , , 1
## 
##             [,1]       [,2]        [,3]       [,4]        [,5]
##  [1,]  0.6289631  0.8099894 -0.11116094 -0.2195063 -0.25187189
##  [2,] -0.1508188  2.7805517  1.29514658  0.3235999 -0.28332410
##  [3,] -0.4489119  2.4587440 -1.30199048 -0.7219475 -0.04622427
##  [4,] -0.7229349 -1.4175480 -0.07993031 -0.2183211  1.51428455
##  [5,]  1.2091629 -0.2882699  0.80760717 -0.9319585  1.46395446
##  [6,] -1.0210656  0.2181453 -1.07549735  1.1639663 -0.15427057
##  [7,]  1.3410832  0.1973808 -0.55963879  1.3949682 -2.15301827
##  [8,] -1.1671814  2.4020150 -0.14959200  2.2635160 -0.69074344
##  [9,] -0.3898478 -0.7015050 -1.21863993 -1.3082619  0.11558265
## [10,]  1.3900988 -0.7536617 -1.33658104 -0.2403796 -0.77709969
## 
## , , 2
## 
##             [,1]       [,2]       [,3]       [,4]       [,5]
##  [1,]  0.9500794 -0.8813329  0.3669647  0.4321616  0.2859256
##  [2,] -1.2320569  0.2199210 -1.9463721  0.6570434 -0.1430404
##  [3,] -0.2555368 -1.4525104 -1.5720584 -0.5714278 -1.3429734
##  [4,]  0.3659933  0.8261212  0.8552008  0.1107770  0.2433908
##  [5,] -1.0255099  0.1516270 -1.6463431 -0.8525100  0.0468298
##  [6,] -1.0205132 -1.6316776  0.8828802 -1.1884887  0.3727641
##  [7,]  0.8356102  1.4554968  0.6551278  1.1386203  1.4159557
##  [8,] -0.2545264  0.1411197 -0.6674148 -0.1536324  0.5049107
##  [9,]  0.1310065 -1.0324879 -0.1780315  0.9334523  0.5742045
## [10,] -0.1392483 -1.1100389 -1.4535866 -0.2223382 -0.2249076
## 
## , , 3
## 
##             [,1]        [,2]       [,3]       [,4]       [,5]
##  [1,] -1.3167017  2.11030214 -1.1528532 -0.5298752  0.8166477
##  [2,] -0.9571713 -0.87435542 -0.5562974 -1.4299889 -1.6167667
##  [3,] -0.6079834 -0.48620405  2.2058089 -0.3564476  1.6470953
##  [4,] -0.1405643 -0.36072761  0.3855486 -0.7180859 -1.3371532
##  [5,] -1.2490883 -0.35843645  0.2616756  2.6912969 -1.1075974
##  [6,] -0.7770963  1.77955963  1.2171825  0.4254494  0.4728384
##  [7,] -0.3569483  0.47348166 -0.7015739 -0.5697856  0.3873735
##  [8,]  0.7865385  0.03720543  1.4801365 -0.9580305  0.8058474
##  [9,]  0.3899946  1.48809787  0.9552694 -2.8341642 -0.8581658
## [10,]  0.3279757 -0.29919687 -0.9285252  0.3193689 -0.6322436

Pourquoi s’embêter avec ça ? Parce qu’on peut appliquer des fonctions facilement sur les lignes, colonnes et autres dimensions grâce à la fonction apply(). Exemple : résultats de validations croisées par bloc, simulations de loi selon différents paramètres. Et on calcule facilement des statistiques “marginales”.

Par, exemple, sur une matrice, on peut calculer des statistiques par lignes :

apply(mat, MARGIN = 1, FUN=mean)
##  [1]  0.360135735 -0.261299878 -0.242761410 -0.008539187 -0.189312923
##  [6] -0.076023427  0.079586511  0.610800297  0.260938352 -0.082399366

Ou par colonnes :

apply(mat, MARGIN = 2, FUN=mean)
## [1]  0.2054042 -0.0471707 -0.4674100  0.3778882  0.1568507

Sur notre hypercube de type array, on peut aussi calculer des stats sur ses différentes dimensions :

apply (arr, MARGIN = 3, FUN=mean)
## [1]  0.05774114 -0.15290760 -0.05214668
apply (arr, MARGIN = c(2,3), FUN = mean)
##             [,1]        [,2]       [,3]
## [1,]  0.06685476 -0.16447021 -0.3901045
## [2,]  0.57058416 -0.33137620  0.3509726
## [3,] -0.37302771 -0.47036331  0.3166372
## [4,]  0.15056756  0.02836574 -0.3960263
## [5,] -0.12627306  0.17330598 -0.1422124

10.5.2 Inspection d’un objet : la régression

La régression linéaire consiste à exprimer une variable Y en fonction d’une variable X dans une fonction linéaire. C’est à dire qu’on cherche a et b tels que : \[ Y = a \cdot X + b + \epsilon\]\(\epsilon\) est le résidu de la régression. On utilise dans cet exemple la table des iris de Fisher, existant dans R base qu’il suffit d’appeler avec data(iris) (il existe d’autres dataframe inclus dans les packages et qui sont utilisés en exemple dans l’aide).

data ("iris")
str (iris)
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Faire la régression de la Sepal.Length sur Petal.length à l’aide de la fonction lm()

lm (data = iris, formula = Sepal.Length ~ Petal.Length)
## 
## Call:
## lm(formula = Sepal.Length ~ Petal.Length, data = iris)
## 
## Coefficients:
##  (Intercept)  Petal.Length  
##       4.3066        0.4089

On a les paramètres a et b mais on aimerait en savoir plus… Au moins la qualité d’ajustement (le \(R^2\) par exemple), et un graphique des résidus pour détecter une éventuelle structure. Pour cela, stocker le résultat dans un nouvel objet, et explorez-le avec les fonctions str(), summary() et plot()

reg <- lm(data = iris, formula = Sepal.Length ~ Petal.Length)
str (reg)
## List of 12
##  $ coefficients : Named num [1:2] 4.307 0.409
##   ..- attr(*, "names")= chr [1:2] "(Intercept)" "Petal.Length"
##  $ residuals    : Named num [1:150] 0.2209 0.0209 -0.1382 -0.32 0.1209 ...
##   ..- attr(*, "names")= chr [1:150] "1" "2" "3" "4" ...
##  $ effects      : Named num [1:150] -71.566 8.812 -0.155 -0.337 0.104 ...
##   ..- attr(*, "names")= chr [1:150] "(Intercept)" "Petal.Length" "" "" ...
##  $ rank         : int 2
##  $ fitted.values: Named num [1:150] 4.88 4.88 4.84 4.92 4.88 ...
##   ..- attr(*, "names")= chr [1:150] "1" "2" "3" "4" ...
##  $ assign       : int [1:2] 0 1
##  $ qr           :List of 5
##   ..$ qr   : num [1:150, 1:2] -12.2474 0.0816 0.0816 0.0816 0.0816 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:150] "1" "2" "3" "4" ...
##   .. .. ..$ : chr [1:2] "(Intercept)" "Petal.Length"
##   .. ..- attr(*, "assign")= int [1:2] 0 1
##   ..$ qraux: num [1:2] 1.08 1.1
##   ..$ pivot: int [1:2] 1 2
##   ..$ tol  : num 1e-07
##   ..$ rank : int 2
##   ..- attr(*, "class")= chr "qr"
##  $ df.residual  : int 148
##  $ xlevels      : Named list()
##  $ call         : language lm(formula = Sepal.Length ~ Petal.Length, data = iris)
##  $ terms        :Classes 'terms', 'formula'  language Sepal.Length ~ Petal.Length
##   .. ..- attr(*, "variables")= language list(Sepal.Length, Petal.Length)
##   .. ..- attr(*, "factors")= int [1:2, 1] 0 1
##   .. .. ..- attr(*, "dimnames")=List of 2
##   .. .. .. ..$ : chr [1:2] "Sepal.Length" "Petal.Length"
##   .. .. .. ..$ : chr "Petal.Length"
##   .. ..- attr(*, "term.labels")= chr "Petal.Length"
##   .. ..- attr(*, "order")= int 1
##   .. ..- attr(*, "intercept")= int 1
##   .. ..- attr(*, "response")= int 1
##   .. ..- attr(*, ".Environment")=<environment: R_GlobalEnv> 
##   .. ..- attr(*, "predvars")= language list(Sepal.Length, Petal.Length)
##   .. ..- attr(*, "dataClasses")= Named chr [1:2] "numeric" "numeric"
##   .. .. ..- attr(*, "names")= chr [1:2] "Sepal.Length" "Petal.Length"
##  $ model        :'data.frame':   150 obs. of  2 variables:
##   ..$ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##   ..$ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##   ..- attr(*, "terms")=Classes 'terms', 'formula'  language Sepal.Length ~ Petal.Length
##   .. .. ..- attr(*, "variables")= language list(Sepal.Length, Petal.Length)
##   .. .. ..- attr(*, "factors")= int [1:2, 1] 0 1
##   .. .. .. ..- attr(*, "dimnames")=List of 2
##   .. .. .. .. ..$ : chr [1:2] "Sepal.Length" "Petal.Length"
##   .. .. .. .. ..$ : chr "Petal.Length"
##   .. .. ..- attr(*, "term.labels")= chr "Petal.Length"
##   .. .. ..- attr(*, "order")= int 1
##   .. .. ..- attr(*, "intercept")= int 1
##   .. .. ..- attr(*, "response")= int 1
##   .. .. ..- attr(*, ".Environment")=<environment: R_GlobalEnv> 
##   .. .. ..- attr(*, "predvars")= language list(Sepal.Length, Petal.Length)
##   .. .. ..- attr(*, "dataClasses")= Named chr [1:2] "numeric" "numeric"
##   .. .. .. ..- attr(*, "names")= chr [1:2] "Sepal.Length" "Petal.Length"
##  - attr(*, "class")= chr "lm"
summary (reg)
## 
## Call:
## lm(formula = Sepal.Length ~ Petal.Length, data = iris)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.24675 -0.29657 -0.01515  0.27676  1.00269 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   4.30660    0.07839   54.94   <2e-16 ***
## Petal.Length  0.40892    0.01889   21.65   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.4071 on 148 degrees of freedom
## Multiple R-squared:   0.76,  Adjusted R-squared:  0.7583 
## F-statistic: 468.6 on 1 and 148 DF,  p-value: < 2.2e-16
plot (reg)

Les méthodes summary, print et plot sont implémentées pour tous les objets en R, et on peut les utiliser pour avoir un premier aperçu de ce que l’on obtient avec la fonction.