POO en langue R (partie 1): classes S3

R est un langage orienté objet. Dans ce document, absolument tout est un objet, à partir des fonctions et se terminant par des tableaux.


À son tour, chaque objet dans R appartient à une classe. En fait, dans le monde qui nous entoure, la situation est à peu près la même. Nous sommes entourés d'objets, et chaque objet peut être attribué à une classe. Une classe détermine l'ensemble des propriétés et actions pouvant être effectuées avec cet objet.


image


Par exemple, dans toute cuisine, il y a une table et une cuisinière. Et la table de cuisine et le poêle peuvent être appelés équipements de cuisine. En règle générale, les propriétés de la table sont limitées par ses dimensions, sa couleur et le matériau à partir duquel elle est fabriquée. Le poêle a une plus large gamme de propriétés, au moins la puissance, le nombre de brûleurs et le type de poêle (électrique ou gaz) seront obligatoires.


Les actions pouvant être effectuées sur des objets sont appelées leurs méthodes. Pour la table et l'assiette, respectivement, l'ensemble des méthodes sera également différent. Vous pouvez dîner à table, vous pouvez cuisiner dessus, mais il est impossible de traiter thermiquement les aliments, pour lesquels un poêle est généralement utilisé.
image


Table des matières



Propriétés de classe


Dans R, chaque objet appartient également à une classe. Selon la classe, il possède un certain ensemble de propriétés et de méthodes. En termes de programmation orientée objet (POO), la possibilité de combiner des éléments similaires dans un ensemble de propriétés et de méthodes d'objets en groupes (classes) est appelée encapsulation .


Un vecteur est la classe d'objets la plus simple de R; il a la propriété de longueur. Pour un exemple, nous prendrons les lettres vectorielles intégrées.


length(letters) 

 [1] 26 

En utilisant la fonction de length , nous avons obtenu la longueur du vecteur de lettres . Essayons maintenant d'appliquer la même fonction au cadre de date intégré de l' iris .


 length(iris) 

 [1] 5 

La fonction de length , applicable aux tables, renvoie le nombre de colonnes.


Les tables ont également une autre propriété, dimension.


 dim(iris) 

 [1] 150 5 

La fonction dim dans l'exemple ci-dessus affiche des informations indiquant qu'il y a 150 lignes et 5 colonnes dans la table d' iris .


À son tour, le vecteur n'a pas de dimension.


 dim(letters) 

 NULL 

Ainsi, nous nous sommes assurés que les objets de différentes classes ont un ensemble de propriétés différent.


Fonctions généralisées


R a de nombreuses fonctions génériques: print , plot , summary , etc. Ces fonctions fonctionnent différemment avec des objets de différentes classes.


Prenons, par exemple, la fonction de plot . Exécutons-le en passant la table iris comme argument principal.


plot(iris)


Résultat:


Le résultat de la fonction de tracé


Essayons maintenant de passer à la fonction de plot un vecteur de 100 nombres aléatoires qui ont une distribution normale.


plot(rnorm(100, 50, 30))


Résultat:


Le résultat de la fonction de tracé


Nous avons obtenu différents graphiques, dans le premier cas, la matrice de corrélation, dans le second, le nuage de points, sur lequel l'indice d'observation est affiché le long de l'axe x, et sa valeur le long de l'axe y.


Ainsi, la fonction de plot peut s'adapter pour fonctionner avec différentes classes. Si nous revenons à la terminologie OOP, alors la capacité de déterminer la classe d'un objet entrant et d'effectuer diverses actions avec des objets de différentes classes s'appelle le polymorphisme . Cela est dû au fait que cette fonction n'est qu'un wrapper pour une variété de méthodes écrites pour travailler avec différentes classes. Vous pouvez le vérifier avec la commande suivante:


 body(plot) 

 UseMethod("plot") 

La commande body imprime le corps de la fonction sur la console R. Comme vous pouvez le voir, le corps de la fonction body se compose d'une seule commande UseMethod("plot") .


C'est-à-dire la fonction de plot , démarre simplement l'une des nombreuses méthodes qui y sont écrites, en fonction de la classe de l'objet qui lui est transmise. Affichez une liste de toutes ses méthodes comme suit.


 methods(plot) 

  [1] plot.acf* plot.data.frame* plot.decomposed.ts* [4] plot.default plot.dendrogram* plot.density* [7] plot.ecdf plot.factor* plot.formula* [10] plot.function plot.hclust* plot.histogram* [13] plot.HoltWinters* plot.isoreg* plot.lm* [16] plot.medpolish* plot.mlm* plot.ppr* [19] plot.prcomp* plot.princomp* plot.profile.nls* [22] plot.raster* plot.spec* plot.stepfun [25] plot.stl* plot.table* plot.ts [28] plot.tskernel* plot.TukeyHSD* 

Le résultat indique que la fonction de tracé a 29 méthodes, parmi lesquelles il y a plot.default , qui fonctionne par défaut si la fonction reçoit un objet d'une classe inconnue en entrée.


En utilisant la fonction methods , vous pouvez également obtenir un ensemble de toutes les fonctions généralisées qui ont une méthode écrite pour n'importe quelle classe.


 methods(, "data.frame") 

  [1] $<- [ [[ [[<- [5] [<- aggregate anyDuplicated as.data.frame [9] as.list as.matrix by cbind [13] coerce dim dimnames dimnames<- [17] droplevels duplicated edit format [21] formula head initialize is.na [25] Math merge na.exclude na.omit [29] Ops plot print prompt [33] rbind row.names row.names<- rowsum [37] show slotsFromS3 split split<- [41] stack str subset summary [45] Summary t tail transform [49] type.convert unique unstack within 

Qu'est-ce qu'une classe S3 et comment créer votre propre classe


Il existe un certain nombre de classes dans R que vous pouvez créer vous-même. L'un des plus populaires est S3.


Cette classe est une liste dans laquelle sont stockées diverses propriétés de la classe que vous avez créée. Pour créer votre propre classe, créez simplement une liste et donnez-lui un nom de classe.


Le livre "L'art de la programmation en R" donne un exemple de la classe des employés , qui stocke des informations sur l'employé. Comme exemple de cet article, j'ai également décidé de prendre un objet pour stocker des informations sur les employés. Mais il l'a rendu plus complexe et fonctionnel.


 #    employee1 <- list(name = "Oleg", surname = "Petrov", salary = 1500, salary_datetime = Sys.Date(), previous_sallary = NULL, update = Sys.time()) #    class(employee1) <- "emp" 

Ainsi, nous avons créé notre propre classe, qui stocke les données suivantes dans sa structure:


  • Nom de l'employé
  • Nom de famille de l'employé
  • Salaire
  • Le moment où le salaire a été établi
  • Salaire précédent
  • Date et heure de la dernière mise à jour des informations

Après cela, avec la commande class(employee1) <- "emp" nous affectons la classe emp à l'objet.


Pour la commodité de créer des objets de classe emp, vous pouvez écrire une fonction.


Code de fonction pour créer des objets de classe emp
 #     create_employee <- function(name, surname, salary, salary_datetime = Sys.Date(), update = Sys.time()) { out <- list(name = name, surname = surname, salary = salary, salary_datetime = salary_datetime, previous_sallary = NULL, update = update) class(out) <- "emp" return(out) } #    emp    create_employee employee1 <- create_employee("Oleg", "Petrov", 1500) #     class(employee1) 

 [1] "emp" 

Fonctions d'affectation aux classes S3 personnalisées


Nous avons donc créé notre propre emp classe, mais jusqu'à présent, cela ne nous a rien donné. Voyons pourquoi nous avons créé notre propre classe et ce que nous pouvons en faire.


Tout d'abord, vous pouvez écrire des fonctions d'affectation pour la classe créée.


Fonction d'assignation pour [
 "[<-.emp" <- function(x, i, value) { if ( i == "salary" || i == 3 ) { cat(x$name, x$surname, "has changed salary from", x$salary, "to", value) x$previous_sallary <- x$salary x$salary <- value x$salary_datetime <- Sys.Date() x$update <- Sys.time() } else { cat( "You can`t change anything except salary" ) } return(x) } 

Fonction d'assignation pour [[
 "[[<-.emp" <- function(x, i, value) { if ( i == "salary" || i == 3 ) { cat(x$name, x$surname, "has changed salary from", x$salary, "to", value) x$previous_sallary <- x$salary x$salary <- value x$salary_datetime <- Sys.Date() x$update <- Sys.time() } else { cat( "You can`t change anything except salary" ) } return(x) } 

Lors de la création, les fonctions d'affectation sont toujours entre guillemets et ressemblent à ceci: "[<-. " / "[[<-. " . Et ils ont 3 arguments requis.


  • x - L'objet auquel la valeur sera affectée;
  • i - Le nom / index de l'élément objet (nom, prénom, salaire, salaire_datetime, salaire_précédent, mise à jour);
  • value - La valeur assignée.

Plus loin dans le corps de la fonction, vous écrivez comment les éléments de votre classe doivent changer. Dans mon cas, je souhaite que l'utilisateur ne puisse modifier que le salaire (élément de salaire , dont l'indice est 3) . Par conséquent, à l'intérieur de la fonction, j'écris un chèque if ( i == "salary" || i == 3 ) . Si l'utilisateur essaie de modifier d'autres propriétés, il reçoit le message "You can't change anything except salary" .


Lorsque l'élément de salaire est modifié, un message s'affiche contenant le nom et le prénom de l'employé, son niveau de salaire actuel et nouveau. Le salaire actuel est transmis à la propriété previous_sallary , et le salaire reçoit une nouvelle valeur. Les valeurs des propriétés salaire_datetime et mise à jour sont également mises à jour.


Vous pouvez maintenant essayer de modifier le salaire.


 employee1["salary"] <- 1750 

 Oleg Petrov has changed salary from 1500 to 1750 

Développement de méthodes personnalisées pour des fonctions génériques


Plus tôt, vous avez déjà appris que dans R, il existe des fonctions généralisées qui modifient leur comportement en fonction de la classe reçue à l'entrée de l'objet.


Vous pouvez ajouter vos méthodes aux fonctions généralisées existantes et même créer vos propres fonctions généralisées.


L' print est l'une des fonctions génériques les plus utilisées. Cette fonction est déclenchée chaque fois que vous appelez un objet par son nom. Maintenant, la sortie d'impression de l'objet de classe emp que nous avons créé ressemble à ceci:


 $name [1] "Oleg" $surname [1] "Petrov" $salary [1] 1750 $salary_datetime [1] "2019-05-29" $previous_sallary [1] 1500 $update [1] "2019-05-29 11:13:25 EEST" 

Écrivons notre méthode pour la fonction d'impression.


 print.emp <- function(x) { cat("Name:", x$name, x$surname, "\n", "Current salary:", x$salary, "\n", "Days from last udpate:", Sys.Date() - x$salary_datetime, "\n", "Previous salary:", x$previous_sallary) } 

Maintenant, la fonction d'impression peut imprimer des objets de notre classe emp . Entrez simplement le nom de l'objet dans la console et obtenez la sortie suivante.


 employee1 

 Name: Oleg Petrov Current salary: 1750 Days from last udpate: 0 Previous salary: 1500 

Création de fonctions et de méthodes génériques


La plupart des fonctions génériques à l'intérieur se ressemblent et utilisent simplement la fonction UseMethod .


 #   get_salary <- function(x, ...) { UseMethod("get_salary") } 

Nous allons maintenant écrire deux méthodes pour cela, l'une pour travailler avec des objets de la classe emp , la deuxième méthode sera lancée par défaut pour les objets de toutes les autres classes, pour travailler avec laquelle notre fonction généralisée n'a pas de méthode écrite séparément.


 #      emp get_salary.emp <- function(x) x$salary #      get_salary.default <- function(x) cat("Work only with emp class objects") 

Le nom de la méthode se compose du nom de la fonction et de la classe d'objets que cette méthode traitera. La méthode par défaut sera exécutée à chaque fois si vous passez un objet de classe dans lequel la méthode n'est pas écrite.


 get_salary(employee1) 

 [1] 1750 

 get_salary(iris) 

 Work only with emp class objects 

Héritage


Un autre terme que vous rencontrerez lors de l'apprentissage de la programmation orientée objet.


image


Tout ce qui est montré sur l'image peut être classé comme classe de transport . En effet, tous ces objets ont une méthode commune - le mouvement, et des propriétés communes, par exemple, la vitesse. Néanmoins, les 6 objets peuvent être divisés en trois sous-classes: la terre, l'eau et l'air. Dans ce cas, la sous-classe héritera des propriétés de la classe parente, mais aura également des propriétés et des méthodes supplémentaires. Une propriété similaire dans le cadre de la programmation orientée objet est appelée héritage .


Dans notre exemple, nous pouvons allouer des travailleurs distants à une sous-classe distincte de remote_emp . Ces employés auront une propriété supplémentaire: la ville de résidence.


 #    employee2 <- list(name = "Ivan", surname = "Ivanov", salary = 500, salary_datetime = Sys.Date(), previous_sallary = NULL, update = Sys.time(), city = "Moscow") #    remote_emp class(employee2) <- c("remote_emp", "emp") #    class(employee2) 

 [1] "remote_emp" "emp" 

Lors de l'affectation d'une classe, de la création d'une sous-classe, nous utilisons un vecteur dans lequel le premier élément est le nom de la sous-classe, suivi du nom de la classe parente.


Dans le cas de l' héritage, toutes les fonctions et méthodes généralisées écrites pour fonctionner avec la classe parente fonctionneront correctement avec ses sous-classes.


 #    remote_emp   employee2 

 Name: Ivan Ivanov Current salary: 500 Days from last udpate: 0 Previous salary: 

 #   salary   remote_emp get_salary(employee2) 

 [1] 500 

Mais vous pouvez développer des méthodes séparément pour chaque sous-classe.


 #     salary   remote_emp get_salary.remote_emp <- function(x) { cat(x$surname, "remote from", x$city, "\n") return(x$salary) } 

 #   salary   remote_emp get_salary(employee2) 

 Ivanov remote from Moscow [1] 500 

Cela fonctionne comme suit. Tout d'abord, la fonction généralisée recherche une méthode écrite pour la sous-classe remote_emp , si elle ne la trouve pas, elle va plus loin et recherche une méthode écrite pour la classe parent emp .


Quand vous pouvez utiliser vos propres cours


Il est peu probable que la fonctionnalité de création de vos propres classes S3 soit utile à ceux qui commencent tout juste leur voyage dans la maîtrise du langage R.


Personnellement, ils ont été utiles pour développer le package rfacebookstat . Le fait est que dans l'API Facebook, le paramètre action_breakdowns existe pour charger les événements et répondre aux publications publicitaires dans différents groupes.


Lorsque vous utilisez de tels regroupements, vous obtenez une réponse sous la forme d'une structure JSON au format suivant:


 { "action_name": "like", "action_type": "post_reaction", "value": 6 } { "action_type": "comment", "value": 4 } 

Le nombre et le nom des éléments pour les différentes actions_breakdowns sont différents, donc pour chacun, vous devez écrire votre propre analyseur. Pour résoudre ce problème, j'ai utilisé la fonctionnalité de création de classes S3 personnalisées et une fonction généralisée avec un ensemble de méthodes.


Lors de la demande de statistiques sur les événements avec des regroupements, en fonction des valeurs des arguments, une classe a été définie qui a été affectée à la réponse reçue de l'API. La réponse a été transmise à une fonction générique et, en fonction de la classe spécifiée précédemment, une méthode a été déterminée pour analyser le résultat. Toute personne intéressée à approfondir les détails de l'implémentation peut trouver le code pour créer une fonction et des méthodes généralisées, et voici leur utilisation.


Dans mon cas, j'ai utilisé des classes et des méthodes pour les traiter exclusivement à l'intérieur du package. Si vous devez généralement fournir à l'utilisateur du package une interface pour travailler avec les classes que vous avez créées, toutes les méthodes doivent être incluses en tant que directive S3method dans le fichier S3method , comme suit.


 S3method(_,) S3method("[<-",emp) S3method("[[<-",emp) S3method("print",emp) 

Conclusion


Comme le montre clairement le titre de l'article, ce n'est que la première partie, car en R, en plus des classes S3 , il y en a d'autres: S4 , R5 ( RC ), R6 . À l'avenir, j'essaierai d'écrire sur chacune de ces implémentations de POO. Néanmoins, toute personne ayant un niveau d'anglais leur permet de lire des livres librement, alors Headley Wickham est plutôt concis, et avec des exemples, il a couvert ce sujet dans son livre "Advanced R" .


Si tout à coup, dans un article, j'ai manqué des informations importantes sur les cours S3, je vous serais reconnaissant de bien vouloir en parler dans les commentaires.

Source: https://habr.com/ru/post/fr453964/


All Articles