[1] 2
12 Arithmetik und Variablen
12.1 Arithmetik
Öffnet man ein R Terminal, so kann man dieses zunächst einmal als Taschenrechner nutzen, wie folgendes Beispiele zeigt
Im Terminal erscheint dabei einerseits das Ergebnis \(2\), andererseits mit [1]
ein Hinweis darauf, dass das Ergebnis der erste Eintrag in dem von R erzeugten Ergebnisvektor ist. Vektoren werden wir in Kapitel 14 im Detail behandeln. Weitere Beispiele für den Gebrauch eines R Terminals als Taschenrechner sind folgende.
Tabelle 12.1 gibt einen ersten Überblick über die in Base R verfügbaren arithmetischen Operatoren. Neben den vertrauten Operationen der Addition, Subtraktion, Multiplikation, Division und Potenz hält Base R zum Beispiel mit der Matrixmultiplikation, der ganzzahligen Teilung (5 %\% 2 = 2
) und der Modulooperation, die den ganzzahligen Rest bei ganzzahliger Teilung ausgibt (5 %% 2 = 2
), auch Operatoren für spezielle arithmetische Operationen bereit.
Operation | Operator |
---|---|
Addition | + |
Subtraktion | - |
Multiplikation | * |
Division | \ |
Potenz | ^ |
Matrimultiplikation | %*% |
Ganzzahlige Teilung | %/% |
Modulo | %% |
Neben den arithmetischen Operatoren zur Verknüpfung zweier Zahlen bietet Base R natürlich auch die Möglichkeit mathematische Standardfunktionen auszuwerten, einen ersten Überblick gibt Tabelle 12.2.
Funktion | Aufruf |
---|---|
Exponentialfunktion | exp(x) |
Logarithmusfunktion | log(x) |
Betrag | abs(x) |
Wurzel | sqrt(x) |
Aufrunden | ceiling(x) |
Abrunden | floor(x) |
Mathematisches Runden | round(x) |
Wenn auch die in Tabelle 12.1 aufgeführten Operatoren und die in Tabelle 12.2 aufgeführten Funktionen auf den ersten Blick einen etwas anderen Charakter haben, so unterscheidet Base R formal nicht zwischen Operatoren und Funktionen. Insbesondere können Operatoren mithilfe der sogenannten Infixnotation auch also Funktionen mehrer Argumente genutzt und verstanden werden. Untenstehender R Code zeigt, wie die arithmetische Verknüpfung \(2 + 3\) als Ausführung einer Funktion der Form \(+ : \mathbb{R}^2 \to \mathbb{R}\) mit dem Namen “+” verstanden werden kann
Schließlich bietet Base R wie jede Programmiersprache auch die Möglichkeit Ausdrücke auf ihren logischen Gehalt hin auszuwerten. Dabei ist Logik hier im Sinne der aus Kapitel 1 Aussagenlogik zu verstehen, in der Aussagen einen von zwei Werten annehmen können, wahr (TRUE) oder falsch (FALSE). Die in Tabelle 12.3 aufgeführten Relationsoperatoren <
, <=
,>
, und >=
werden zumeist auf numerische Werte angewendet, die Gleichheitsoperatoren ==
und !=
können auf beliebige Datenstrukturen angewendet werden und die Operatoren zur Verknüpfung logischer Werte &
und |
entsprechen den aus Kapitel 1 bekannten Begiffen der logischen Konjunktion (“und”) und Disjunktion (“nicht-exklusive oder”)
Logische Verknüpfung | Operator |
---|---|
Gleich | == |
Ungleich | != |
Konjunktion | & |
Disjunktion | | |
Kleiner | < |
Kleiner-gleich | <= |
Die in Tabelle 12.1, Tabelle 12.2 und Tabelle 12.3 aufgelisteten Operatoren znd Funktionen stellen nur einen kleinen Auszug der von Base R bereitgestellten Operatoren und Funktionen dar. Einen vollständigen Überblick bietet folgender R Aufruf, der die Namen aller von Base R bereitgestellten Funktionen auflistet. Wir werden viele dieser Funktionen im weiteren Verlauf kennenlernen.
[1] "$" "$<-" "["
[4] "[<-" "[[" "[[<-"
[7] "%*%" "crossprod" "tcrossprod"
[10] "xtfrm" "c" "all"
[13] "any" "sum" "prod"
[16] "max" "min" "range"
[19] "cummax" "rep" "^"
[22] "cos" "levels<-" "cumsum"
[25] "asin" "anyNA" "<="
[28] "Conj" "exp" "is.matrix"
[31] "log10" "as.environment" "ceiling"
[34] "asinh" "abs" "as.raw"
[37] "is.infinite" "is.array" "floor"
[40] "==" "sign" "cummin"
[43] "|" "round" "Re"
[46] "!" "is.numeric" "acosh"
[49] "!=" "names<-" "&"
[52] "atanh" "sinh" ">="
[55] "sin" "*" "atan"
[58] "+" "length" "length<-"
[61] "sinpi" "-" "is.nan"
[64] "/" "sqrt" "%/%"
[67] "as.numeric" "seq.int" "trunc"
[70] "digamma" "acos" "<"
[73] "as.logical" "cosh" "Mod"
[76] "tanpi" "dimnames<-" "log2"
[79] ">" "tanh" "is.na"
[82] "dim" "signif" "gamma"
[85] "is.finite" "as.integer" "dim<-"
[88] "as.double" "lgamma" "Arg"
[91] "log1p" "trigamma" "%%"
[94] "tan" "cumprod" "Im"
[97] "expm1" "as.call" "log"
[100] "as.complex" "cospi" "as.character"
[103] "dimnames" "names" "("
[106] ":" "=" "@"
[109] "{" "~" "&&"
[112] ".C" "baseenv" "quote"
[115] "::" "<-" "is.name"
[118] "if" "||" "attr<-"
[121] "untracemem" ".cache_class" "substitute"
[124] "interactive" "is.call" "switch"
[127] "function" "is.single" "is.null"
[130] "is.language" "is.pairlist" ".External.graphics"
[133] "declare" "globalenv" "class<-"
[136] ".Primitive" "is.logical" "enc2utf8"
[139] "UseMethod" ".subset" "proc.time"
[142] "enc2native" "repeat" ":::"
[145] "<<-" "@<-" "missing"
[148] "nargs" "isS4" ".isMethodsDispatchOn"
[151] "forceAndCall" "Exec" ".primTrace"
[154] "storage.mode<-" ".Call" "unclass"
[157] "gc.time" ".subset2" "environment<-"
[160] "emptyenv" "seq_len" ".External2"
[163] "is.symbol" "class" "on.exit"
[166] "is.raw" "for" "is.complex"
[169] "list" "invisible" "is.character"
[172] "oldClass<-" "is.environment" "attributes"
[175] "break" "return" "attr"
[178] "tracemem" "next" ".Call.graphics"
[181] "standardGeneric" "is.atomic" "retracemem"
[184] "expression" "is.expression" "call"
[187] "is.object" "pos.to.env" "attributes<-"
[190] ".primUntrace" "...length" "Tailcall"
[193] ".External" "oldClass" ".Internal"
[196] ".Fortran" "browser" "is.double"
[199] ".class2" "while" "nzchar"
[202] "is.list" "lazyLoadDBfetch" "unCfillPOSIXlt"
[205] "...elt" "...names" "is.integer"
[208] "is.function" "is.recursive" "seq_along"
[211] "unlist" "as.vector" "lengths"
12.2 Präzedenz
Eine wichtige Eigenschaft bei der Benutzung von Operatoren in der Programmierung ist ihre durch die jeweilige Programmiersprache festgelegte Präzedenz. Dabei handelt es sich um von den Entwicklern der Programmiersprache festgelegten Regeln, in welcher Rangfolge Operatoren in Ausdrücken, in denen mehrere von ihnen vorkommen, ausgeführt werden. Aus der Mathematik ist man insbesondere die Präzedenzregel “Punktrechnung geht vor Strichrechnung” gewöhnt. Diese besagt, dass in Ausdrücken, in denen sowohl Produkte oder Divisionen als auch Summen oder Differenzen vorkommen, die Produkte oder Divisionen zunächst ausgeführt werden und ihre Ergebnisse dann in die Summen- oder Differenzbildung eingehen. So ergibt sich zum Beispiel \[ 2 \cdot 5 - 3 = 10 - 3 = 7 \tag{12.1}\] und nicht etwa \[ 2 \cdot 5 - 3 \neq 2 \cdot 2 = 4. \tag{12.2}\] Wir setzen als bekannt voraus, dass die Präzedenzregeln durch Klammerbildung betont bzw. überschrieben werden können. So ist beispielsweise der Ausdruck in Gleichung 12.1 äquivalent zu \[ (2 \cdot 5) - 3 = 10 - 3 = 7 \tag{12.3}\] und die in Gleichung 12.2 beabsichtigte Rechnung kann durch Klammersetzung als \[ 2 \cdot (5 - 3) = 2 \cdot 2 = 4 \tag{12.4}\] richtig gestellt werden. Diese vertrauten Rechenregeln und ihre Überschreibung durch Klammern finden sich in R entsprechend implementiert:
Eine weitere als bekannt voraussgesetzte Präzendenzregel ist, dass Potenzen eine höhere Präzedenz als Produkte oder Divisionen und Summen oder Differenzen haben. Es ergibt sich also beispielsweise \[\begin{equation} 2^3 + 3 = (2\cdot 2 \cdot 2) + 3 = 8 + 3 = 11 \end{equation}\] und nicht etwa \[\begin{equation} 2^3 + 3 \neq 2^{3 + 3} = 2^6 = 64 \end{equation}\] Entsprechend gelten in R
Eine wichtige Besonderheit hinsichtlich der Präzedenzregeln in R ist, das auch unitäre Vorzeichen, also Vorzeichen, die eine Zahl als eine negative Zahl identifizieren in den Bereich der Operatorpräzedenz fallen. Wenn man geneigt sein sollte, einen Ausdruck der Form \(-1^2\) als \((-1)^2 = 1\) zu interpretieren, so folgt R der Regel, dass in \(-1^2\) zunächst die Potenz und dann das Vorzeichen ausgewertet werden, dass also \(-1^2 = -(1^2) = -1\) gilt, wie folgender R Code demonstriert.
Weiterhin werden in R Potenzen von rechts nach links abgearbeitet, Produkte, Divisionen, Summen und Differenzen dagegen von links nach rechts. So gelten beispielsweise
und
[1] 5.75
[1] 2.15
Generell gilt, dass man in der Programmierung Präzedenzregeln nicht raten sollte oder versuchen sollte, sie logisch herzuleiten, und dann darauf vertrauen sollte, dass die Entwickler der Programmiersprache die Präzedenzregeln schon gemäß des eigenen Verständnisses implementiert haben werden. Stattdessen sollte man Berechnungen immer kritisch prüfen und zur Sicherheit die beabsichtigte Rechnung lieber einmal zuviel mit Klammern betonen als zu wenig.
Neben den hier betrachteten Präzedenzregeln für die Grundrechenarten gibt es in R eine ganze Reihe weiterer Regeln zum Umgang mit vielen weiteren Operatoren, von denen wir bisher nur sehr wenige kennengelernt haben. Wenn man sich beim Erlernen einer Programmiersprache mit einem neuen Operator vertraut macht, sollte man also für sich auch unbedingt die Präzedenz dieses Operators klären. Der in Abbildung 12.1 gezeigte und mit dem Befehl ?Syntax
aufzurufende Text der R Dokumentation diskutiert die in R geltenden Präzedenzregeln vollumfänglich und sollte bei der Arbeit mit R häufig konsultiert werden.

12.3 Variablen
In der Programmierung sind Variablen abstrakte Behälter für Daten, deren Inhalt im Verlauf einer Programmausführung geändert werden kann. Variablen werden im Programmcode mit Namen bezeichnet und besitzen bei Programmausführung eine Adresse im Arbeitsspeicher. Variablen können verschiedenen Typs sein, d.h. unterschiedliche Arten von Daten, und nur diese, repräsentieren. In traditionellen Programmiersprachen wird der Typ einer Variable explizit deklariert, d.h. es wird zu Beginn der Programmausführung festgelegt, welche Art von Daten eine bestimmte Variable repräsentiert. Die Deklaration einer Variable A
, die ganze Zahlen (und nur diese) repräsentiert hat etwa die Form
In der Folge kann der Variable A
dann ein Wert vom Typ ganze Zahl zugewiesen werden, beispielsweise in der Form
In 4GL Sprachen wir R wird der Variablentyp überlicherweise direkt durch eine Initialisierungsanweisung festgelegt. So deklariert folgender R Code die Variable a
gleichzeitig als vom Typ double
(Dezimalzahl) mit dem Wert 1
,
In den allermeisten Programmiersprachen ist der Zuweisungsbefehl =
. R ist dahingehend besonders, als es mit ->
und <-
Zuweisungsbefehle für Zuweisungen von Werten von links nach rechts oder von links nach rechts erlaubt, wobei letzteres auch durch =
erreicht wird. Offiziell empfohlener Zuweisungsbefehl für R ist <-
, da dies aber sehr idiosynkratisch ist, benutzen wir =
wie in jeder anderen Programmiersprache. Ein erstes Beispiel soll den Umgang mit Variablen vedeutlichen.
Beispiel
Wir betrachten folgende Textaufgabe:
“Lina geht ins Schreibwarengeschäft und kauft vier Hefte und zwei Stifte. Ein Heft koste 1 Euro und ein Stift koste 3 Euro. Wieviele Dinge kauft Lina insgesamt und wieviel Euro muss Lina insgesamt bezahlen?”
Um die Aufgabe zu lösen, definieren wir zunächst die Variablen anzahl_hefte
und anzahl_stifte
und weisen ihnen dabei ihre jeweiligen Wert aus der Aufgabe zu.
Nach Zuweisung existieren die Variablen mit den ihnen zugewiesenen Werte nun im Arbeitsspeicher, dem sogenannten Workspace. Der Befehl ls()
zeigt die existierenden benutzbaren Variablen im Arbeitsspeicher an.
Entscheidend ist, dass die Variablennamen jetzt wie Zahlen in Berechnungen genutzt werden können print()
gibt dabei Variablenwerte in einem R Terminal aus.
Um den Gesamtpreis zu berechnen, definieren wir als nächsted die Variablen preis_heft
und preis_stift
anhand der Aufgabenspezifikation.
Der Gesamtpreis berechnet sich dann wie folgt.
Variablen im Workspace könnne auch wieder gelöscht werden. rm()
(remove) erlaubt das Löschen einzelner Variablen
[1] "anzahl_dinge" "anzahl_hefte" "anzahl_stifte" "preis_heft"
[5] "preis_stift"
rm(list=ls())
löscht alle Variablen
Variablennamen
Prinzipiell ist man in der Wahl der Namen für Variablen mit kleineren Einschränkungen frei. Zulässige Variablennamen in R
- bestehen aus Buchstaben, Zahlen, Punkten (.) und Unterstrichen (_),
- beginnen mit einem Buchstaben oder einem Punkt, dann allerdings nicht gefolgt von einer Zahl und
- dürfen keine in R schon belegten Ausdrücke wie
for
,if
,NaN
, … sein
Hilfestellung zu den Einschränkungen von Variablennamen geben ?reserved
. Eine Funktion, um aus beliebigen Vorschlägen einen syntaktisch zulässigen Variablennamen zu generieren, ist make.names()
. Ihre Hilfe beschreibt auch die hier skizzierten Regeln zur Variablenbenennung in weiteren Details. Generell sind sinnvolle Variablen kurz, um im Code Platz zu sparen und aussagekräftig, um das menschliche Verständnis des Codes zu erhöhen. Meist bestehen Variablen nur aus Kleinbuchstaben und Unterstrichen.
Variablenrepräsentation
Wir wollen uns noch etwas genauer mit dem Verhältnis von Variablennamen (Bezeichnern) und Variableninhalten (Objekten im Arbeitsspeicher) befassen. Die folgenden Begriffe und Zusammenhänge sind insbesondere dann wichtig, wenn Berechnungen an die Grenzen des verfügbaren Arbeitsspeichers gelangen und optimiert werden müssen.
Wir beginnen mit dem Begriff des Binding. Dazu betrachten wir zunächst die folgende Variableninitialisierung:
Intuitiv wird durch dieses Statement eine Variable genannt x
mit dem Wert 1
erzeugt. Auf technischer Ebene geschehen dabei de-facto geschehen zwei Dinge (vgl. Abbildung 12.2):
- R erzeugt ein Objekt (Vektor mit Wert 1) mit Arbeitsspeicheradresse `lobstr::obj_addr(x).
- R verbindet dieses Objekt mit dem Namen
x
(binding), der das Objekt im Arbeitsspeicher referenziert.

Betrachten wir nun folgendes Statement :
Intuitiv wird dabei eine Variable, genannt y
, mit Wert gleich dem Wert von x
erzeugt. Auf technischer Ebene wird dabei neuer Name y
erzeugt, der dasselbe Objekt im Arbeitsspeicher referenziert wie x
(vgl .@fig-binding-2).

Man kann sich mithilfe von lobstr::obj_addr(x)
und lobstr::obj_addr(y)
davon überzeugen, dass die Addressen des Objekts im Arbeitsspeicher identisch sind
Das betreffende Objekt wird also bei Allokation zu y
nicht kopiert und dadurch Arbeitsspeicher eingespart.
Weiterhin nutzt R das sogenannte Copy-on-modify Prinzip. Dieses Prinzip besagt, dass ein Objekt im Arbeitsspeicher kopiert wird und eine neue Addresse erhält, sobald es modifiziert wird. Das ursprüngliche Object im Arbeitsspeicher bleibt dabei unverändert. Abbildung 12.4 und folgender R Code verdeutlichen zunächst das Copy-on-Modify Prinzip
x = 1 # Objekt (z.B. 0x74b) erzeugt, x referenziert Speicheradresse des Objektes
y = x # y referenziert dieselbe Speicheradresse wie x (0x74b)
y = 3 # y modifiziert, eine modifizierte Kopie (z.B. 0xcd2) wird gespeichert
y # y referenziert jetzt (0xcd2)
[1] 3

Die Tatsache, dass Objekte bei Veränderung im Arbeitsspeicher kopiert werden, die ursprünglichen Objekte aber im Prinzip unverändert erhalten bleiben, wird auch als die Immutability von Objekten in R bezeichnet. Allerdings greift die Immutability von Objekten in R nicht besonders weit, wie folgendes Beispiel zeigt. Dabei wird zunächst ein Objekt einer 1
im Arbeitsspeicher erzeugt, das mit dem Variablennamen x
referenziert werden kann. Per Indexing (vgl. ?sec-Datenstrukturen-und-Datenmanagement) wird das Objekt dann bei einer beabsichtigten Modifikation im Arbeitsspeicher neu addressiert.
x = 1 # Objekt erzeugt, x referenziert Speicheradresse des Objektes
print(lobstr::obj_addr(x)) # Speicheraddresse des Objekts
[1] "0x1f59c031150"
[1] "0x1f59c47f120"
Das Copy-on-modify-Prinzip gilt auch in umgekehrter Reihenfolge, d.h. wird ein Objekt, auf das ein weiterer Variablenname zeigt, verändert, so ändert sich das ursprünglich referenzierte Objekt nicht. Folgender R Code mag dies verdeutlichen.
x = 1 # Objekt (z.B. 0x74b) erzeugt, x referenziert Speicheradresse des Objektes
y = x # y referenziert dieselbe Speicheradresse wie x (0x74b)
x = 3 # Ein neues Objekt (z.B. 0x2a08) wird erzeugt, x referenziert nun 0x2a08
y # y referenziert weiterhin das ursprüngliche Objekt (0x74b)
[1] 1
Schließlich ist auch ein Unbinding von Objekten im Arbeitsspeicher möglich, z.B. als Folge folgenden R Codes (vgl. Abbildung 12.5):

Das Löschen von nicht-referenzierten im Objekten im Arbeitsspeicher wird als Garbage collection bezeichnet. R löscht nicht referenzierte Objekte im Arbeitsspeicher automatisch, allerdings meist erst dann, wenn der Speicher benötigt wird, es also wirklich nötig ist. Generell funktioniert dies aber gut und es ist nicht nötig, aktiv die von R bereitgestellte Garbage Collection Funktion gc()
zu benutzen.
Repräsentation fehlender Werte
In der datenanalytischen Programmierung hat man es desöfteren mit fehlenden Werten zu tun, z.B. wenn eine experimentelle Messung bei einer Versuchsperson abgebrochen wurde. Zur Repräsentation fehlender Werte nutzt R NA
(not applicable). Versucht man mit NA
zu rechnen, so ergibt dies meist wieder NA
:
Auf NA
testet man mit is.na()
:
[1] NA NA NA NA
Neben NA
besitzt R mit NaN
auch eine Möglichkeit zur Darstellung mathematisch nicht definierter Werte. Im Gegensatz zu NA
ist NaN
immer vom numerischen Typ. Die Ergebnisse beim Rechnen mit NaN
ähnlen denen mit NA
.
Auf NaN
testet man mit is.nan()