Teile diesen Beitrag:

Liebe Brainies,

hier kommt ein Grundlagen-Artikel, es geht um Datentypen in R. Für Anfänger besonders geeignet, aber die eine oder andere Variante, mit den Variablen umzugehen, findet auch der fortgeschrittene R-Programmierer. Teil 2 beschäftigt sich dann mit zusammengesetzten Datentypen wie Vektoren, Listen, Tabellen usw.

Was sind überhaupt Datentypen?

Das ist tatsächlich gar nicht so einfach zu erklären, ohne in Fachsprache abzugleiten. Im Prinzip kategorisiert es die Art einer Variablen. Also zum Beispiel ob diese Variable eine ganze Zahl, eine Kommazahl, eine Zeichenkette oder auch ein komplexerer Typ wie Matrix oder Liste ist. Wenn man also einen Wert, einen Text, eine Tabelle oder andere Strukturen später wieder verwenden will, legt man diese Struktur im Arbeitsspeicher ab. Das passiert, indem man einen Stück vom Speicher reserviert, diesem Stück einen Namen zuweist und dann die Daten dort ablegt. Dieses Speicherstück heißt dann Variable. Die Größe des Speicherstücks hängt vom Datentyp ab. Ein Text braucht mehr Speicher als eine Wahr/Falsch-Variable.

Zum Glück ist das in R ganz einfach und intuitiv. Man muss in R häufig sogar nicht den Datentyp angeben, sondern durch eine Zuweisung wie x = 3 erkennt R automatisch, dass es sich um eine Zahl handelt. Das hat aber manchmal auch seine Tücken, denn so wird hier die 3 als Datentyp Kommazahl und nicht als ganze Zahl definiert. Bei vielen Programmiersprachen muss man daher den Typ immer mit angeben. In R fährt man aber ziemlich gut mit der automatischen Erkennung, nur in einigen Fällen muss man aufpassen.

Die meisten Programmiersprachen bieten einige Standard-Datentypen an, R ist da natürlich keine Ausnahme.

Der Zuweisungsoperator <- oder =

Um einen Wert in einer Variablen zu speichern, benutzt man in R entweder den Pfeil nach links <- oder das Gleichheitszeichen =. Es funktioniert sogar der Pfeil nach recht ->. Was man verwendet, macht in den allermeisten Situationen keinen Unterschied. Im R tidyverse styleguide wird allerdings der Linkspfeil dem Gleichheitszeichen vorgezogen. Es ist aber so, dass fast alle Programmiersprachen nur das Gleichheitszeichen kennen und somit R-Code für andere schwerer lesbar ist.

Tatsächlich waren früher nur die Pfeile möglich, das Gleichheitszeichen kam erst 2001 dazu. Die Pfeil-Zuordnung hat in erster Linie historische Gründe, denn R stammt von S ab und S wiederum ist inspiriert von APL. APL wurde in den 1960ern für IBM-Rechner entwickelt und die hatten diese Pfeile als Tasten auf der Tastatur.

Aus mathematischer Sicht schöner ist der Pfeil, denn er macht die Richtung der Zuordnungsrichtung klar. Shortcut für den Linkspfeil in R-Studio ist übrigens Alt + – (siehe auch Shortcuts in RStudio).

In dem Post Why do we use arrow as an assignment operator? von Colin Fay findet ihr auch Beispiele, wann nur der Pfeil funktioniert.

Ein Beispiel (zugegebenermaßen kein sauberer Programmierstil) ist die Zuordnung einer Variable innerhalb einer Funktion.

mean(y = 1:5)
#Fehler in mean.default(y = 1:5) : Argument "x" fehlt (ohne Standardwert)
 
mean(y <- 1:5)
#[1] 3

Die Lesbarkeit erhöht sich aber durch Verwendung des Pfeils, wenn ein Vergleich gemacht wird.

a = 5
b = 5.1
gleich = a == b
gleich <- a == b

 

Einfache Datentypen

Ganze Zahl – Der Datentyp INTEGER

Die Überschrift sagt eigentlich schon alles. Es geht um positive und negative ganze Zahlen, in den meisten Programmiersprache integer oder int genannt. Wie oben geschrieben ist es in R etwas tückisch, denn mit x = 3 erzeugt man keinen integer, sondern eine Gleitkommazahl (numeric). Um explizit einen integer zu generieren, muss man noch ein L hinter der Zahl ergänzen. Mit is.integer prüft man, ob eine Variable vom Typ integer ist, sie gibt TRUE oder FALSE zurück. Solche Funktionen is.XXX gibt es für jeden Datentyp, also zum Beispiel auch is.numeric. Die Funktion class gibt mir den Datentyp zurück.

x <- 3
is.integer(x)
is.numeric(x)
class(x)
 
x <- 3L
is.integer(x)
# Besonderheit: Jeder integer ist gleichzeitig auch numeric
is.numeric(x)
class(x)

Achtung: Jede Zahl ist in R gleichzeitig auch ein Vektor der Länge 1. D.h. is.integer prüft nicht, ob es sich um eine einzelne Zahl handelt, sondern gibt auch TRUE zurück, wenn es sich um eine Folge von ganzen Zahlen handelt.

x <- 3L
is.vector(x)
# Vektor (1, 2, 3) erzeugen
x = c(1L, 2L, 3L)
is.integer(x)
is.numeric(x)
class(x)

Mit as.integer lassen sich andere Datentypen in integer verwandeln

as.integer(3)
as.integer(3.14)
as.integer(3)
as.integer(3.14)

Das L steht übrigens für long, was in der Programmiersprache C einem 32-bit integer entspricht (int ist dort 16-bit integer). Wer sich weiter verwirren möchte, in Java bezeichnet long einen 64-bit integer.

Das heißt also, dass für jede Zahl 32 Bit im Speicher reserviert sind, also 32 Nullen und Einsen. Da wir sowohl positive als auch negative Zahlen darstellen können, brauchen wir schon mal ein Bit für das Vorzeichen. Man spricht von einem signed int. Mit den restlichen 31 Bits können wir 2^31-1 Zahlen darstellen, d.h. der Wertebereich des integers in R reicht von -2147483647 bis +2147483647. In der System-Variablen .Machine$integer.max ist der maximale Wert gespeichert. Wollen wir dazu noch 1 addieren, dann erzeugen wir einen Überlauf und bekommen NA (not available) zurück.

2^31-1
.Machine$integer.max
as.integer(-.Machine$integer.max)
as.integer(.Machine$integer.max) + 1L

Der Datentyp numeric benutzt 64 Bit und kann daher größere Zahlen darstellen.

.Machine$integer.max + 1
.Machine$double.xmax

Aber auch hier ist etwas Vorsicht geboten. numeric repräsentiert eine Zahl nämlich intern als Gleitkommazahl, welche aufgrund der Darstellung mit Mantisse und Exponent manchmal komische Resultate liefert. Mehr dazu im nächsten Abschnitt.

Echte 64-Bit Ganzzahlen gibt es standardmäßig in R noch nicht (siehe auch R in a 64-Bit World auf R-Blogger). Es gibt aber alternativen. Für echte 64 Bit Ganzzahlen kann man das Package bit64 verwenden. Damit erweitert man den Wertebereich auf -9.223.372.036.854.775.807 bis +9.223.372.036.854.775.807 (=2^63 – 1). Man stößt tatsächlich schneller an die integer-Beschränkung als man so denkt. Zum Beispiel bei Datenbank-Tabellen, die die Zeilen durchnummerieren. Die Tabellen müssen noch nicht einmal besonders „groß“ sein, sondern sind vielleicht im Längsformat, bei der jedes Attribut eine eigene Zeile bekommt. In meinem Artikel über das Längs- und Querformat und wie man diese in R ineinander überführt könnt ihr darüber mehr erfahren.

Kommazahlen – Der Datentyp NUMERIC

Wie wir schon im vorherigen Abschnitt gesehen haben, ist Numeric der Standard-Datentyp in R für Zahlen aller Art. Dieser entspricht dem C-Datentyp double, ist also eine Gleitkommazahl mit 64 Bit.

So eine Gleitkommazahl setzt sich aus Vorzeichen s, Mantisse m, Basis b und Exponent e zusammen:

x = (-1)^s * m * b^e

Intern wird als Basis 2 gewählt, auf dem Binärsystem basieren schließlich unsere Computer. Wenn man sich aber so eine Zahl anzeigen lässt, dann wird die Basis 10 gewählt.

print(23476132999423)
#[1] 2.347613e+13

Diese Darstellung ist eine Kurzform für 2,347613 * 10^13 = 23476130000000. Aber das ist nur die Darstellung, nicht die interne Genauigkeit. Mittels format oder sprintf kann man die Anzahl Stellen setzen, mit der eine Zahl ausgedruckt wird.

format(x, digits=14)
#[1] "23476132999423"
sprintf("%.0f",x)
#[1] "23476132999423"
sprintf("%.1f",x)
#[1] "23476132999423.0"
sprintf("%.2f",x)
#[1] "23476132999423.00"
sprintf("%.6f",x)
#[1] "23476132999423.000000"

Alternativ kann man in R eine „Bestrafung“ für diese Exponentialschreibweise einstellen:

options(scipen=999)
print(x)
#[1] 23476132999423

Wie gesagt, damit ändert man aber nur die Darstellung bei der Anzeige in der Konsole. Die tatsächliche Genauigkeit ist durch die 64 Bit gegeben. Diese 64 Bit werden so für eine Gleitkommazahl genutzt, wie es der Standard IEEE 754 festlegt: 1 Bit für das Vorzeichen, 52 Bit für die Mantisse und 11 Bit für den Exponenten. Um noch ein bisschen mehr aus den Bits herauszuholen, soll die Mantisse größer oder gleich 1 und kleiner als 2 sein, also 1,XXX. Das kann man durch Normalisierung erreichen. Da jetzt also die Mantisse nun immer mit einer 1 beginnt, kann diese 1 einfach weggelassen werden.

Der genaue Aufbau kann uns als R-Nutzer aber recht egal sein. Man sollte sich nur bewusst sein, dass diese Darstellung Grenzen hat. Das liegt aber nicht an R, sondern an der Definition der Gleitkommazahlen. So lässt sich 0,1 zum Beispiel nicht exakt als Gleitkommazahl darstellen, so dass das folgende passiert:

0.1 * 0.1
#[1] 0.01
0.1 * 0.1 == 0.01
#[1] FALSE

Bei sehr großen Zahlen rundet R automatisch, ohne das eine Warnung ausgegeben wird

x <- 999999999999456781
format(x, digits=14)
#[1] "999999999999456768"
 
x <- 999999999999456500
format(x, digits=14)
#[1] "999999999999456512"

Wahr oder Falsch – Der Datentyp LOGICAL

1 oder 0, wahr oder falsch, darauf basiert alles digitale. Genau diese zwei Werte, nämlich TRUE oder FALSE kann eine Variable vom Typ logical annehmen. Andere Programmiersprachen nennen diesen Datentyp auch bool oder eine Variable von dem Typ boolsche Variable.

In R erzeugt man eine boolsche Variable, indem man den Wert TRUE oder FALSE zuweist.

b <- TRUE
class(b)
#[1] "logical"
is.integer(b)
#[1] FALSE
is.numeric(b)
#[1] FALSE
as.integer(b)
#[1] 1
as.numeric(FALSE)
#[1] 0
as.character(b)
#[1] "TRUE"

Auch wenn der Test auf integer oder numeric falsch ergibt, kann man doch ganz normal damit rechnen. So steht TRUE für 1, FALSE für 0.

3 * TRUE - 3 * FALSE
#[1] 3

Wörter / Zeichenketten – Der Datentyp CHARACTER

Nun brauchen wir noch einen Datentyp, in dem wir Text speichern können. Statt Text spricht man von Zeichenketten oder englisch strings, einzelne Zeichen heißen character oder char. Der R-Datentyp für Strings heißt character.

Die Definition einer character-Variable in R ist einfach. Man stellt dafür den Text in Anführungsstriche (einfache oder doppelte). Mit nchar bekommt man die Anzahl Zeichen zurück (Achtung: length macht etwas anderes, siehe Vektoren)

a = "Hallo"
class(a)
#[1] "character"
is.character(a)
#[1] TRUE
nchar(a)
#[1] 5

Mit Strings kann oder muss man viele Dinge machen. Die wichtigsten sind:

  • Strings verbinden mittels paste und paste0
  • Teile von Strings extrahieren oder ändern mittels substring bzw. substr
  • Filtern mittels regulärer Ausdrücke. Reguläre Ausdrücke sind mächtig und bei allem, was mit Text zu tun hat, kaum wegzudenken. Es ist allerdings kein ganz leichtes Thema. Daher habe ich für Euch einen ganzen Blogpost über reguläre Ausdrücke in R geschrieben
substr(a,1,2)
#[1] "Ha"
substring(a,2) <- "ey"
toupper(a)
#[1] "HEYLO"
tolower(a)
#[1] "heylo"
paste(a, "Du da")
#[1] "Heylo Du da"
paste(a, " Du da",sep=",")
#[1] "Heylo, Du da"
grepl("e.l",a)
#[1] TRUE

Neben den Standard-Funktionen in R helfen vor allem die Packages stringi und stringr weiter, wobei stringr auf stringi aufbaut.

Datum und Zeitstempel – Die Datentypen DATE und POSIXCT

Um die Basis-Datentypen zu vervollständigen, dürfen Datum und Uhrzeit nicht fehlen. Tatsächlich ist der Umgang mit diesen beiden Typen ein wenig komplizierter. Am besten schaut ihr mal in mein R-Tutorial zu Datum und Uhrzeit.

# aktuelles Datum
d <- Sys.Date()
class(d)
format(d,"%Y")
as.Date("2020-03-11")
as.Date("11.03.2020", format = "%d.%m.%Y")
 
# aktueller Zeitstempel
ts <- Sys.time()
class(ts)
format(ts,"%Y")

So, damit haben wir die Basis-Datentypen INTEGER, NUMERIC, LOGICAL, CHARACTER, DATE und POSIXCT durch. Aber damit fangen wir gerade erst an, denn nun lassen sich diese Datentypen zu Vektoren, Matrizen, Tabellen und Listen zusammensetzen. Dazu findet ihr hier in Teil 2 Erklärungen und Code-Beispiele.

Happy coding,

Euer Holger

Teile diesen Beitrag: