Tag Punkt Monat Punkt Jahr – Ein Datum ist doch eigentlich ziemlich einfach, oder?

In diesem Artikel geht es darum, wie ihr in R mit Datums- und Zeitangaben umgeht. Insbesondere beim Importieren von Daten aus csv- oder Excel-Dateien (siehe meinen Blogpost zum Excel-Import und -Export mit R) gibt es immer wieder komische Datums-Formate, die konvertiert werden müssen.

Dafür hat R drei Datentypen vorgesehen:

  • Date für Datum
  • POSIXct für Zeitstempel, also Datum plus Zeitangabe und fast identisch
  • POSIXlt für Zeitstempel, also Datum plus Zeitangabe (Den Unterschied zwischen POSIXct und POSIXlt kläre ich weiter unten auf)

Reine Zeitangaben, also nur die Uhrzeit ohne Datum, sind eher selten und nicht sehr praktikabel. Gibt es in Euren Daten ein Feld für das Datum und ein Feld für die Uhrzeit, dann solltet ihr diese zu einem Zeitstempel zusammenfassen.

Ich zeige euch, wie ihr Strings oder Zahlen in einen der Datentypen überführt und damit rechnen könnt. Für ein besseres Handling ist das Package lubridate aus dem tidyverse eine große Erleichterung und jedem ans Herz gelegt.

Dazu aber weiter unten mehr, wir fangen mit Standard-R an.

Der Datumstyp in R

Schauen wir uns mal Datum- und Zeitstempel-Variablen an. Mit Sys.Date() bekommt man das aktuelle Datum, mit Sys.time() den aktuellen Zeitstempel des Rechners.
Mit dem R-Befehl typeof wird der Typ zurückgegeben.

heute <- Sys.Date()print(heute)## [1] "2019-02-23"jetzt <- Sys.time()print(jetzt)## [1] "2019-02-23 08:34:00 CET"typeof(heute)## [1] "double"typeof(jetzt)## [1] "double"str(heute)## Date[1:1],format: "2019-02-23"str(jetzt)## POSIXct[1:1],format: "2019-02-23 08:34:00"

Wir sehen interessanterweise, dass sowohl Datum als auch Zeitstempel intern als Zahl gespeichert werden, nämlich als Anzahl Tage (Date) oder Sekunden (POSIX) seit einem festgelegten Startdatum, nämlich seit dem 1. Januar 1970 00:00 UTC. Als Zusatz wird aber doch noch abgespeichert, dass es sich um ein Datum oder Zeitstempel handelt. Das sehen wir an der Struktur (mit Hilfe des Befehls str). Sonst würde die print-Funktion ja auch nur eine Zahl ausgeben und nicht automatisch in einen String umwandeln.

print(as.numeric(heute))## [1] 17950print(as.numeric(jetzt))## [1] 1550907241# ein Datum kann als Anzahl Tage seit einemUpsprungsdatum angegeben werdend <- as.Date(1,origin="1970-01-01")print(as.numeric(d))## [1] 1

Datum aus String

Wollen wir ein Datum aus einem String erzeugen, geht das mit der Funktion as.Date(str, format). Dabei können wir ohne Angabe des Parameters format die Standardformate verwenden, die in R YYYY-MM-DD und YYYY/MM/DD sind. D.h. der String muss zuerst die vierstellige Jahreszahl, dann ein Trennzeichen, dann den zweistelligen Monat, wieder das Trennzeichen und dann den zweistelligen Tag beinhalten, also “2021-03-25” oder “2021/03/25”. R versteht Monat und Tag auch ohne die führender Null bei Zahlen unter 10, also “2021-3-5” würde auch gehen.

Diese Form haben wir aber natürlich nicht immer. Dann müssen wir mit dem Parameter format angeben, welches Format wir übergeben. Dabei gibt es eine ganze Menge Kürzel, die alle mit % anfangen. Daraus können wir uns alle möglichen und unmöglichen Datumsformate zusammenbasteln. In der Liste unten findet ihr sie.

Für die ganz Eiligen unter Euch hier die Umwandlung des deutschen Datumformats:

as.Date(str, "%d.%m.%Y")

String aus Datum in R

Um aus einem Datum wieder einen String zu machen, könnt ihr einfach die Funktion format(datum, format) benutzen. Dabei funktioniert der Parameter format genauso wie bei as.Date(), nur das eben der R-interne Datumtyp in einen String mit entsprechender Formatierung umgewandelt wird.

So bekommen wir mit as.Date(Sys.Date(), "%B %Y") den aktuellen Monatsnamen und Jahr, also z.B. “April 2021”. Hier einige weitere Beispiele:

d1 <- as.Date("2018-02-22")d2 <- as.Date("20.02.2018",format="%d.%m.%Y")d3 <- as.Date("02/18/18",format="%m/%d/%y")format(d1,"%d.%m.%Y")## [1] "22.02.2018"format(d2,"%d.%m.%Y")## [1] "20.02.2018"

Liste der Datumsformate in R

Und nun zu der versprochenen Liste mit den Format-Kürzeln.

KürzelBeschreibungBeispiel
%mMonat (01-12)05
%b or %h3-stelliger MonatsnameFeb
%BMonatsnameFebruar
%dTag des Monats (01-31)27
%jTag vom Jahr (001-366)148
%y2-stelliges Jahr (00-99)18
%Y4-stelliges Jahr2018
%CJahrhundert20
%xDatum (System)
%DDatum MM/TT/JJJJ05/27/84
%UKalenderwoche, Sonntag=erster Tag der Woche
(01-53)
22
%WKalenderwoche, Montag=erster Tag der Woche
(00-53)
22
%uWochentag, Montag=1 (1-7)7
%wWochentag, Sonntag=0 (0-6)0
%a3-stelliger WochentagSo
%AWochentagDonnerstag

 

Und so sieht das dann im Code aus:

d <- as.Date("2018-02-22")print(paste("Monat:",format(d,"%m")))## [1] "Monat: 02"print(paste("3-stelliger Monatsname:",format(d,"%b")))## [1] "3-stelliger Monatsname: Feb"print(paste("Monatsname:",format(d,"%B")))## [1] "Monatsname: Februar"print(paste("Tag:",format(d,"%d")))## [1] "Tag: 22"print(paste("Tag vom Jahr:",format(d,"%j")))## [1] "Tag vom Jahr: 053"print(paste("2-stelliges Jahr:",format(d,"%y")))## [1] "2-stelliges Jahr: 18"print(paste("4-stelliges Jahr:",format(d,"%Y")))## [1] "4-stelliges Jahr: 2018"print(paste("Jahrhundert:",format(d,"%C")))## [1] "Jahrhundert: 20"print(paste("Kalenderwoche (US):",format(d,"%U")))## [1] "Kalenderwoche (US): 07"print(paste("Kalenderwoche (DE):",format(d,"%W")))## [1] "Kalenderwoche (DE): 08"print(paste("Datum (Systemeinstellung):",format(d,"%x")))## [1] "Datum (Systemeinstellung): 22.02.2018"print(paste("Datum MM/TT/JJJJ:",format(d,"%D")))## [1] "Datum MM/TT/JJJJ: 02/22/18"print(paste("Wochentag (Montag=1):",format(d,"%u")))## [1] "Wochentag (Montag=1): 4"print(paste("Wochentag (Sonntag=0):",format(d,"%w")))## [1] "Wochentag (Sonntag=0): 4"print(paste("3-stelliger Tagesname:",format(d,"%a")))## [1] "3-stelliger Tagesname: Do"print(paste("Tagesname:",format(d,"%A")))## [1] "Tagesname: Donnerstag"

Rechnen mit Daten

d1 <- as.Date("2018-01-23")d2 <- as.Date("2018-02-22")delta <- d2 - d1delta## Time difference of 30 daysstr(delta)## 'difftime' num30## - attr(*,"units")= chr "days"attributes(delta)## $class## [1] "difftime"## ## $units## [1] "days"difftime(d2,d1,units="weeks")## Time difference of 4.285714 weeks#erster Tag des MonatsTage <- as.numeric(format(d1,"%d")) - 1as.Date(-Tage,d1)## [1] "2018-01-01"ersterTagDesMonats <- function(Datum) {    return(as.Date(-as.numeric(format(Datum,"%d")) + 1,Datum))}ersterTagDesMonats(d2)## [1] "2018-02-01"

Datum mit dem Package lubridate

Viel einfacher geht das alles mit dem Package lubridate. Wie bei jedem Packages muss es einmal installiert werden install.packages(“lubridate”). Danach kann es mit library(lubridate) oder require(lubridate) im Skript geladen werden.

Lubridate hat einige Funktionen, um schnell ein Datum zu erzeugen, an die einzelnen Teile heranzukommen und auch diese abzuändern

library(lubridate)## ## Attaching package: 'lubridate'## The following object is masked from 'package:base':## ##      date# Ein Datum erstellend <- ymd(20180514)d2 <- mdy("4/1/17")# Abruf der einzelnen Teileyear(d)## [1] 2018month(d)## [1] 5day(d)## [1] 14wday(d)## [1] 2wday(d,label=TRUE)## [1] Mo## Levels: So < Mo < Di < Mi < Do < Fr < Sawday(d,label=TRUE,abbr=FALSE)## [1] Montag## 7 Levels: Sonntag < Montag < Dienstag < Mittwoch < ... < Samstagwday(d,label=TRUE,abbr=FALSE,week_start=1)## [1] Montag## 7 Levels: Montag < Dienstag < Mittwoch < Donnerstag < ... < Sonntagyday(d)## [1] 134# Modifikationmonth(d) <- 2day(d) <- 5d## [1] "2018-02-05"

Als nützlich haben sich auch die Rundungsfunktionen in lubridate erwiesen. Mit diesen kann zum Beispiel der Monatsanfang leicht bestimmt werden.

# springt zum Anfang des Monatsfloor_date(d,unit="month")## [1] "2018-02-01"# springt zum Anfang des nächsten Monatsceiling_date(d,unit="month")## [1] "2018-03-01"# rundet auf Monate, also bis zum 14. zum Monatsanfang, ab dem 15. zum nächsten Monatsanfanground_date(d,unit="month")## [1] "2018-02-01"round_date(d+1,unit="month")## [1] "2018-02-01"

Zeitstempel in R

Ein Zeitstempel setzt sich aus Datum und Uhrzeit zusammen. Für eine richtige Uhrzeit ist noch die Zeitzone angegeben, damit es eindeutig wird. Der entsprechende Datentyp in R ist POSIXct bzw. POSIXlt. Diese beiden unterscheiden sich in der internen Darstellung. POSIXct speichert die Anzahl Sekunden seit dem Origin (UNIX Epoch, nämlich der 01.01.1970 00:00:00), POSIXlt speichert eine Liste mit Tag, Monat, Jahr, Stunde, Minute, etc.

Damit kann man mittels Attributen auf Teile eines POSIXlt zugreifen, beim POSIXct muss man über format den entsprechenden Wert extrahieren und dann gegebenenfalls noch in eine Zahl umwandeln. Mit dem Package lubridate läßt sich das vereinfachen, in dem per Funktion direkt auf die einzelnen Teile zugreifen kann, dazu weiter unten mehr.

# die aktuelle Systemzeit wird als POSIXct zurückgegebenjetzt <- Sys.time()str(jetzt)##  POSIXct[1:1], format: "2019-02-23 08:34:01"attributes(jetzt)## $class## [1] "POSIXct" "POSIXt"#gibt die Minuten ausas.numeric(format(jetzt,"%M"))## [1] 34# mit as.POSIXlt kann sie in ein POSIXlt umgewandelt werdenjetzt_lt <- as.POSIXlt(jetzt)str(jetzt_lt)##  POSIXlt[1:1],format: "2019-02-23 08:34:01"attributes(jetzt_lt)## $names## [1] "sec"   "min"   "hour"  "mday"  "mon"  "year"  "wday"## [8] "yday"  "isdst" "zone"  "gmtoff"## ## $class## [1] "POSIXlt" "POSIXt"## ## $tzone## [1] ""   "CET"   "CEST"jetzt_lt$min## [1] 34# origin und tz (timezone) sind optional, also nur nötig, wenn nicht der Standardwert genommen werden sollts <- as.POSIXct("20190222105519", format="%Y%m%d%H%M%S", origin="1970-01-01", tz="CET")ts## [1] "2019-02-22 10:55:19 CET"ts2 <- as.POSIXct("22.02.2019 10:55:19", format="%d.%m.%Y %H:%M:%S")ts2## [1] "2019-02-22 10:55:19 CET"

Neben den Datumskürzelns aus der obigen Tabelle können wir mit den folgenden Kürzeln noch die Zeit definieren. Das funktioniert sowohl bei der Erzeugung aus einem String als auch der umgekehrte Weg mittels format.

KürzelBeschreibungBeispiel
%cZeitstempel im System-FormatFr Feb 22 10:55:19 2019
%HStunden (00-24)23
%IStunden (00-12)11
%MMinuten55
%pAM/PM (sofern verwendet)AM
%SDecimal second19
%XZeit (Systemeinstellungen)HH:MM:SS
%zAbweichung von GMT+0100
%ZZeitzoneCET

format(ts,"%c")## [1] "Fr Feb 22 10:55:19 2019"format(ts,"%H")## [1] "10"format(ts,"%I")## [1] "10"format(ts,"%M")## [1] "55"format(ts,"%p")## [1] ""format(ts,"%S")## [1] "19"format(ts,"%X")## [1] "10:55:19"format(ts,"%z")## [1] "+0100"format(ts,"%Z")## [1] "CET"

Um die Zeitspanne zwischen zwei Zeitstempeln zu bestimmen, können wir sie einfach voneinander abziehen. Das Problem ist aber, dass wir dann die Einheit (Tage, Stunden, …) nicht selber wählen können. Daher ist es besser, dass über difftime zu machen und den
Parameter units explizit zu setzen.

delta <- Sys.time() - tsstr(delta)##  'difftime' num21.6450349113676##  - attr(*,"units")= chr "hours"delta <- difftime(Sys.time() , ts, units="days")

Zeitstempel mit lubridate

Zeittempel mit lubridate funktionieren ganz genauso wie wir das oben schon beim Datum gesehen haben. Es gibt Erzeuger-Funktionen wie ymd_hms und
Funktionen, die die einzelnen Teile des Zeitstempels extrahieren. Ordnet man
diesen Extraktionsfunktionen einen Wert zu, wird der Zeitstempel geändert.

library(lubridate)ts <- ymd_hms("2011-06-04 12:00:00")year(ts)## [1] 2011hour(ts)## [1] 12second(ts)## [1] 0second(ts) <- 25ts## [1] "2011-06-04 12:00:25 UTC"

So, ich hoffe, jetzt seid ihr zum Master of Date and Time geworden.

Happy Coding,
Euer Holger