data > opinion

Tom Alby

Das Datum als Datentyp: Vom Umgang mit schwierigen Fällen, Teil 1

2022-02-28


library(tidyverse)

Ein Datum in einem Datensatz kann zu viel Kopfzerbrechen führen, darum widme ich dem Datum gleich eine ganze Artikelreihe. Wir kennen alle das Problem bereits von der Verwirrung, ob 03-04-2021 nun der 3. April oder der 4. März ist, da die Amerikaner den Monat zuerst schreiben, wir aber den Tag zuerst. In meiner schriftlichen Kommunikation nutze ich daher meist sowas wie 10-Oct-2021, so dass es keine Missverständnisse geben kann.

Zur Einführung: Alles, was mit Zeit zu tun hat, wird in R als Zahl gespeichert, und zwar als Anzahl Sekunden oder Tagen seit dem 1.1.1970 0 Uhr. Warum alles in der Welt macht man das? Ganz einfach: Es ist viel einfacher damit zu rechnen :) Festgelegt wurde dies als Posix-Standard, durch den die Interoperabilität zwischen Betriebssystemen gewährleistet werden soll. Und ganz ehrlich, wenn ich die Anzahl Tage zwischen zwei Daten, zum Beispiel zwischen Weihnachten 2021 (24.12) und Ostern 2022 (17. April) berechnen will, dann ist

weihnachten <- as.numeric(as.POSIXct("2021-12-24 00:00:00 EST"))
ostern <- as.numeric(as.POSIXct("2022-04-17 00:00:00 EST"))
(ostern - weihnachten) / 60 / 60/ 24
## [1] 113.9583

etwas einfacher, auch wenn es auf den ersten Blick nicht so aussieht. Mit dem Epoch-Converter kann man Daten einfach umrechnen. Übrigens existiert ein Jahr-2038-Problem, da irgendwann der 32-bit Integer das Datum nicht mehr halten kann. Aber das nur am Rande. Vom Jahr-2000-Problem hat man kaum etwas mitbekommen, was aber auch daran liegen könnte, dass vorher genug getan wurde oder die Fälle, wo es Probleme gegeben hat, verschwiegen wurden.

Nun bieten uns Datensätze nicht immer das Format so an, wie wir es gerne hätten. Eine Studierende hatte sich einen Datensatz über getötete Journalisten ausgesucht, und dieser bringt ein paar Widrigkeiten mit. Schauen wir uns die Daten einmal an:

cpj <- read_csv("../cpj.csv")
## Rows: 1782 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (18): Type, Date, Name, Sex, Country_killed, Organization, Nationality, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(cpj$Date, 20)
##  [1] "October 22, 2016"         "October 21, 2016"        
##  [3] "October  2, 2016"         "August 14, 2016"         
##  [5] "August  8, 2016"          "August  8, 2016"         
##  [7] "August  5, 2016"          "August  1, 2016"         
##  [9] "July 24, 2016"            "July 21, 2016"           
## [11] "July 20, 2016"            "October 2015 - June 2016"
## [13] "October 2015 - June 2016" "October 2015 - June 2016"
## [15] "October 2015 - June 2016" "July 14, 2016"           
## [17] "July 11, 2016"            "June 25, 2016"           
## [19] "June 24, 2016"            "June 19, 2016"

Das sieht zunächst gut aus, aber wir haben dann Fälle wie “October 2015 - June 2016” oder “January 30 to February 24, 2016” oder “May 13, 14, 15, 16, 17, or 18, 2015”. Kein Wunder, dass R die Spalte als character importiert hat. Angenommen wir wollten ein Diagramm erstellen, um zu sehen, ob die Anzahl der getöteten Journalisten abgenommen hat - Dies ist mit den Daten so nicht möglich. Natürlich könnte man ganz einfach die letzte Jahreszahl nehmen in der Spalte und diese als Datum nutzen, aber dann würden wir allen Fällen Unrecht tun, bei denen nicht klar ist, in welchem Jahr sie genau gestorben sind. Sollte man sich für diese Option entscheiden, so muss unbedingt diese Ungenauigkeit genannt werden und auch der Mechanismus, der dann für eine Zahl entscheidet. In R könnte das so aussehen:

cpj %>%
  mutate(year = str_extract(Date, "[0-9]*$")) %>%
  select(Date, year) %>%
  head(., 20)
## # A tibble: 20 × 2
##    Date                     year 
##    <chr>                    <chr>
##  1 October 22, 2016         2016 
##  2 October 21, 2016         2016 
##  3 October  2, 2016         2016 
##  4 August 14, 2016          2016 
##  5 August  8, 2016          2016 
##  6 August  8, 2016          2016 
##  7 August  5, 2016          2016 
##  8 August  1, 2016          2016 
##  9 July 24, 2016            2016 
## 10 July 21, 2016            2016 
## 11 July 20, 2016            2016 
## 12 October 2015 - June 2016 2016 
## 13 October 2015 - June 2016 2016 
## 14 October 2015 - June 2016 2016 
## 15 October 2015 - June 2016 2016 
## 16 July 14, 2016            2016 
## 17 July 11, 2016            2016 
## 18 June 25, 2016            2016 
## 19 June 24, 2016            2016 
## 20 June 19, 2016            2016

Das scheint schon einmal zu funktionieren, wunderbar. Wir haben ein paar Zeilen, wo kein Date vorhanden ist, in diesem Fall ist der Todeszeitpunkt unbekannt. Auch dies muss dann in dem Text zur Visualisierung erwähnt werden. Noch ist die Spalte year ein Character-String, auch das müssen wir noch beheben:

cpj %>%
  mutate(year = as.integer(str_extract(Date, "[0-9]*$"))) %>%
  filter(!is.na(year)) %>%
  ggplot(., aes(x = year)) +
  geom_histogram(bins = 25) +
  theme_minimal()

Die Visualisierung zeigt, dass es anscheinend leider einen besorgniserregenden Trend gibt. Hier könnte man noch mehr tun, um die Visualisierung aufzupeppen, aber in diesem Artikel geht es um die Probleme mit dem Datum.

Eine andere Möglichkeit, mit dem Datum umzugehen, wäre ein Plot, in dem das Datum entweder als Punkt oder als Range angegeben wird. Das wäre genau, aber es würde die Visualisierung unnötig verkomplizieren. Wir müssen uns also entscheiden zwischen Genauigkeit und der vereinfachten Kommunikation. Dieser Trade-Off muss aber, wie gesagt, erläutert werden.

Tags: