R ist eine objektorientierte Sprache. Darin ist absolut alles ein Objekt, beginnend mit Funktionen und endend mit Tabellen.
Jedes Objekt in R gehört wiederum zu einer Klasse. In der Welt um uns herum ist die Situation ungefähr gleich. Wir sind von Objekten umgeben und jedes Objekt kann einer Klasse zugeordnet werden. Eine Klasse bestimmt die Eigenschaften und Aktionen, die mit diesem Objekt ausgeführt werden können.

Zum Beispiel gibt es in jeder Küche einen Tisch und einen Herd. Und der Küchentisch und der Herd können als Küchengeräte bezeichnet werden. Die Eigenschaften des Tisches sind in der Regel durch Abmessungen, Farbe und Material begrenzt, aus dem er besteht. Der Ofen hat ein breiteres Spektrum an Eigenschaften, zumindest die Leistung, die Anzahl der Brenner und die Art des Ofens (elektrisch oder gasförmig) sind obligatorisch.
Aktionen, die für Objekte ausgeführt werden können, werden als ihre Methoden bezeichnet. Für die Tabelle bzw. die Platte sind die Methoden ebenfalls unterschiedlich. Sie können am Tisch zu Abend essen, Sie können darauf kochen, aber es ist unmöglich, Lebensmittel, für die normalerweise ein Herd verwendet wird, wärmebehandelt zu werden.

Inhalt
Klasseneigenschaften
In R gehört jedes Objekt auch zu einer Klasse. Abhängig von der Klasse verfügt es über bestimmte Eigenschaften und Methoden. In Bezug auf die objektorientierte Programmierung (OOP) wird die Möglichkeit, ähnliche Eigenschaften und Methoden von Objekten in Gruppen (Klassen) zu kombinieren, als Kapselung bezeichnet .
Ein Vektor ist die einfachste Klasse von Objekten in R und hat die Eigenschaft der Länge. Als Beispiel nehmen wir die eingebauten Vektorbuchstaben .
length(letters)
[1] 26
Mit der length
haben wir die Länge des Buchstabenvektors erhalten . Versuchen wir nun, dieselbe Funktion auf den in die Iris integrierten Datumsrahmen anzuwenden.
length(iris)
[1] 5
Die für Tabellen geltende Längenfunktion gibt die Anzahl der Spalten zurück.
Tabellen haben auch eine andere Eigenschaft, Dimension.
dim(iris)
[1] 150 5
Die dim
Funktion im obigen Beispiel zeigt Informationen an, dass die Iris- Tabelle 150 Zeilen und 5 Spalten enthält.
Der Vektor hat wiederum keine Dimension.
dim(letters)
NULL
Daher haben wir sichergestellt, dass Objekte verschiedener Klassen unterschiedliche Eigenschaften haben.
Verallgemeinerte Funktionen
R hat viele allgemeine Funktionen: print
, plot
, summary
usw. Diese Funktionen funktionieren unterschiedlich mit Objekten verschiedener Klassen.
Nehmen Sie zum Beispiel die plot
. Lassen Sie es uns ausführen, indem Sie die Iris- Tabelle als Hauptargument übergeben.
plot(iris)
Ergebnis:

Versuchen wir nun, einen Vektor von 100 Zufallszahlen mit normaler Verteilung an die Plotfunktion zu übergeben.
plot(rnorm(100, 50, 30))
Ergebnis:

Wir haben verschiedene Graphen erhalten, im ersten Fall die Korrelationsmatrix, im zweiten das Streudiagramm, auf dem der Beobachtungsindex entlang der x-Achse angezeigt wird, und seinen Wert entlang der y-Achse.
Somit kann sich die Plotfunktion an die Arbeit mit verschiedenen Klassen anpassen. Wenn wir zur OOP-Terminologie zurückkehren, wird die Fähigkeit, die Klasse eines eingehenden Objekts zu bestimmen und verschiedene Aktionen mit Objekten verschiedener Klassen auszuführen, als Polymorphismus bezeichnet . Dies liegt an der Tatsache, dass diese Funktion nur ein Wrapper für eine Vielzahl von Methoden ist, die für die Arbeit mit verschiedenen Klassen geschrieben wurden. Sie können dies mit dem folgenden Befehl überprüfen:
body(plot)
UseMethod("plot")
Der Befehl body
druckt den Funktionskörper auf die R-Konsole. Wie Sie sehen können, besteht der Hauptteil der Körperfunktion aus nur einem UseMethod("plot")
.
Das heißt, Die plot
startet nur eine der vielen Methoden, die in sie geschrieben wurden, abhängig von der Klasse des Objekts, das an sie übergeben wurde. Zeigen Sie eine Liste aller Methoden wie folgt an.
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*
Das Ergebnis zeigt an, dass die Plotfunktion über 29 Methoden verfügt, darunter die Datei plot.default , die standardmäßig funktioniert, wenn die Funktion ein Objekt einer unbekannten Klasse für die Eingabe empfängt.
Mit der methods
können Sie auch eine Reihe aller verallgemeinerten Funktionen abrufen, für die eine Methode für eine beliebige Klasse geschrieben wurde.
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
Was ist eine S3-Klasse und wie erstelle ich eine eigene Klasse?
Es gibt eine Reihe von Klassen in R, die Sie selbst erstellen können. Eines der beliebtesten ist S3.
Diese Klasse ist eine Liste, in der verschiedene Eigenschaften der von Ihnen erstellten Klasse gespeichert sind. Um eine eigene Klasse zu erstellen, erstellen Sie einfach eine Liste und geben Sie ihr einen Klassennamen.
Das Buch "Die Kunst des Programmierens in R" gibt ein Beispiel für die Mitarbeiterklasse , in der Informationen über den Mitarbeiter gespeichert sind. Als Beispiel für diesen Artikel habe ich mich auch für ein Objekt zum Speichern von Informationen über Mitarbeiter entschieden. Aber machte es komplexer und funktionaler.
# employee1 <- list(name = "Oleg", surname = "Petrov", salary = 1500, salary_datetime = Sys.Date(), previous_sallary = NULL, update = Sys.time()) # class(employee1) <- "emp"
Daher haben wir unsere eigene Klasse erstellt, die die folgenden Daten in ihrer Struktur speichert:
- Name des Mitarbeiters
- Familienname des Mitarbeiters
- Gehalt
- Der Zeitpunkt, zu dem das Gehalt festgelegt wurde
- Vorheriges Gehalt
- Datum und Uhrzeit der letzten Aktualisierung der Informationen
Danach weisen wir mit dem Befehl class(employee1) <- "emp"
dem Objekt die Klasse emp zu.
Zum bequemen Erstellen von Objekten der Klasse emp können Sie eine Funktion schreiben.
Funktionscode zum Erstellen von emp-Klassenobjekten # 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"
Zuweisungsfunktionen für benutzerdefinierte S3-Klassen
Also haben wir unser eigenes Klassen- Emp geschaffen , aber bisher hat uns das nichts gegeben. Mal sehen, warum wir unsere eigene Klasse erstellt haben und was wir damit machen können.
Zunächst können Sie Zuweisungsfunktionen für die erstellte Klasse schreiben.
Zuweisungsfunktion für [ "[<-.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) }
Zuweisungsfunktion für [[ "[[<-.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) }
Zuweisungsfunktionen bei der Erstellung werden immer in Anführungszeichen gesetzt und sehen folgendermaßen aus: "[<-. " / "[[<-. "
. Und sie haben 3 erforderliche Argumente.
- x - Das Objekt, dem der Wert zugewiesen wird;
- i - Name / Index des Elements des Objekts (Name, Nachname, Gehalt, Gehaltsdatenzeit, vorherige_Zahlung, Aktualisierung);
- value - Der zugewiesene Wert.
Weiter im Hauptteil der Funktion schreiben Sie, wie sich die Elemente Ihrer Klasse ändern sollen. In meinem Fall möchte ich, dass der Benutzer nur das Gehalt ändern kann ( Gehaltselement , dessen Index 3 ist) . Daher schreibe ich innerhalb der Funktion einen if ( i == "salary" || i == 3 )
Check if ( i == "salary" || i == 3 )
. Wenn der Benutzer versucht, andere Eigenschaften zu bearbeiten, erhält er die Meldung "You can't change anything except salary"
.
Wenn das Gehaltselement geändert wird, wird eine Meldung angezeigt, die den Vor- und Nachnamen des Mitarbeiters sowie seine aktuelle und neue Gehaltsstufe enthält. Das aktuelle Gehalt wird an die Eigenschaft previous_sallary übergeben , und dem Gehalt wird ein neuer Wert zugewiesen. Die Werte der Eigenschaften Salary_Datetime und Update werden ebenfalls aktualisiert.
Jetzt können Sie versuchen, das Gehalt zu ändern.
employee1["salary"] <- 1750
Oleg Petrov has changed salary from 1500 to 1750
Entwicklung benutzerdefinierter Methoden für generische Funktionen
Sie haben bereits zuvor erfahren, dass es in R verallgemeinerte Funktionen gibt, die ihr Verhalten abhängig von der Klasse ändern, die an der Eingabe des Objekts empfangen wird.
Sie können Ihre Methoden zu vorhandenen verallgemeinerten Funktionen hinzufügen und sogar Ihre eigenen verallgemeinerten Funktionen erstellen.
Eine der am häufigsten verwendeten generischen Funktionen ist das print
. Diese Funktion wird jedes Mal ausgelöst, wenn Sie ein Objekt beim Namen nennen. Die Druckausgabe des von uns erstellten emp- Klassenobjekts sieht nun folgendermaßen aus:
$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"
Schreiben wir unsere Methode für die Druckfunktion.
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) }
Jetzt kann die Druckfunktion Objekte unserer emp- Klasse drucken . Geben Sie einfach den Namen des Objekts in die Konsole ein und erhalten Sie die folgende Ausgabe.
employee1
Name: Oleg Petrov Current salary: 1750 Days from last udpate: 0 Previous salary: 1500
Generische Funktionen und Methoden erstellen
Die meisten der darin enthaltenen generischen Funktionen sehen gleich aus und verwenden nur die UseMethod
Funktion.
# get_salary <- function(x, ...) { UseMethod("get_salary") }
Jetzt werden wir zwei Methoden dafür schreiben, eine für die Arbeit mit Objekten der emp- Klasse, die zweite Methode wird standardmäßig für Objekte aller anderen Klassen gestartet, für die unsere verallgemeinerte Funktion keine separat geschriebene Methode hat.
# emp get_salary.emp <- function(x) x$salary # get_salary.default <- function(x) cat("Work only with emp class objects")
Der Name der Methode besteht aus dem Namen der Funktion und der Objektklasse, die diese Methode verarbeitet. Die Standardmethode wird jedes Mal ausgeführt, wenn Sie ein Klassenobjekt übergeben, in das die Methode nicht geschrieben wurde.
get_salary(employee1)
[1] 1750
get_salary(iris)
Work only with emp class objects
Vererbung
Ein weiterer Begriff, auf den Sie beim Erlernen der objektorientierten Programmierung stoßen werden.

Alles, was auf dem Bild gezeigt wird, kann als Transportklasse klassifiziert werden. In der Tat haben alle diese Objekte eine gemeinsame Methode - Bewegung und gemeinsame Eigenschaften, zum Beispiel Geschwindigkeit. Trotzdem können alle 6 Objekte in drei Unterklassen unterteilt werden: Land, Wasser und Luft. In diesem Fall erbt die Unterklasse die Eigenschaften der übergeordneten Klasse, verfügt jedoch auch über zusätzliche Eigenschaften und Methoden. Eine ähnliche Eigenschaft im Rahmen der objektorientierten Programmierung heißt Vererbung .
In unserem Beispiel können wir Remote-Mitarbeiter einer separaten Unterklasse von remote_emp zuordnen . Diese Mitarbeiter haben ein zusätzliches Eigentum: Wohnort.
# 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"
Beim Zuweisen einer Klasse und Erstellen einer Unterklasse verwenden wir einen Vektor, in dem das erste Element der Name der Unterklasse ist, gefolgt vom Namen der übergeordneten Klasse.
Im Falle der Vererbung funktionieren alle verallgemeinerten Funktionen und Methoden, die für die Arbeit mit der übergeordneten Klasse geschrieben wurden, ordnungsgemäß mit ihren Unterklassen.
# remote_emp employee2
Name: Ivan Ivanov Current salary: 500 Days from last udpate: 0 Previous salary:
# salary remote_emp get_salary(employee2)
[1] 500
Sie können Methoden jedoch für jede Unterklasse separat entwickeln.
# 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
Es funktioniert wie folgt. Zunächst sucht die verallgemeinerte Funktion nach einer Methode, die für die Unterklasse remote_emp geschrieben wurde. Wenn sie diese nicht findet, geht sie weiter und sucht nach einer Methode, die für die übergeordnete Klasse emp geschrieben wurde .
Wenn Sie Ihre eigenen Klassen verwenden können
Es ist unwahrscheinlich, dass die Funktionalität zum Erstellen eigener S3-Klassen für diejenigen nützlich ist, die gerade erst ihre Reise in die Beherrschung der R-Sprache beginnen.
Persönlich haben sie sich bei der Entwicklung des rfacebookstat- Pakets als nützlich erwiesen . Tatsache ist, dass in der Facebook-API der Parameter action_breakdowns vorhanden ist, um Ereignisse zu laden und auf Werbeveröffentlichungen in verschiedenen Gruppen zu reagieren.
Wenn Sie solche Gruppierungen verwenden, erhalten Sie eine Antwort in Form einer JSON-Struktur im folgenden Format:
{ "action_name": "like", "action_type": "post_reaction", "value": 6 } { "action_type": "comment", "value": 4 }
Die Anzahl und der Name der Elemente für verschiedene action_breakdowns sind unterschiedlich, daher müssen Sie für jedes Element einen eigenen Parser schreiben. Um dieses Problem zu lösen, habe ich die Funktionalität zum Erstellen benutzerdefinierter S3-Klassen und einer verallgemeinerten Funktion mit einer Reihe von Methoden verwendet.
Beim Anfordern von Statistiken zu Ereignissen mit Gruppierungen wurde abhängig von den Werten der Argumente eine Klasse definiert, die der von der API empfangenen Antwort zugewiesen wurde. Die Antwort wurde an eine generische Funktion übergeben, und abhängig von der zuvor angegebenen Klasse wurde eine Methode ermittelt, die das Ergebnis analysierte. Wer möchte sich mit den Implementierungsdetails befassen? Hier finden Sie den Code zum Erstellen einer verallgemeinerten Funktion und Methoden. Hier finden Sie deren Verwendung.
In meinem Fall habe ich Klassen und Methoden verwendet, um sie ausschließlich innerhalb des Pakets zu verarbeiten. Wenn Sie dem Benutzer des Pakets im Allgemeinen eine Schnittstelle für die Arbeit mit den von Ihnen erstellten Klassen zur Verfügung stellen müssen, müssen alle Methoden wie folgt als S3method
Direktive in die S3method
Datei aufgenommen werden.
S3method(_,) S3method("[<-",emp) S3method("[[<-",emp) S3method("print",emp)
Fazit
Wie aus dem Titel des Artikels hervorgeht, ist dies nur der erste Teil, weil In R gibt es neben den S3- Klassen noch andere: S4 , R5 ( RC ), R6 . In Zukunft werde ich versuchen, über jede dieser OOP-Implementierungen zu schreiben. Trotzdem erlaubt jeder mit Englischkenntnissen, Bücher frei zu lesen, dann ist Headley Wickham ziemlich prägnant und mit Beispielen behandelte er dieses Thema in seinem Buch "Advanced R" .
Wenn ich in einem Artikel plötzlich einige wichtige Informationen zu S3-Klassen verpasst habe, wäre ich Ihnen dankbar, wenn Sie dies in den Kommentaren schreiben.