Machine Learning

Nutzen Sie Ihre Kenntnisse in SQL, um R zu lernen

R: Streng, funktional, dynamisch
Keine Kommentare

Sie sind neugierig, wie Sie Probleme in R lösen können, und haben möglicherweise schon Erfahrung mit SQL? Dann sind Sie bei diesem Artikel genau richtig! Wir beginnen mit den grundlegenden Elementen der Sprache – mit jeder Menge konkretem Beispielcode. Dann werfen wir einen Blick darauf, wie wir mit Daten umgehen können (hier helfen SQL-Grundkenntnisse – sind aber nicht erforderlich). Und zu guter Letzt widmen wir uns noch Anwendungsfällen, die typischerweise mit R gelöst werden können.

R ist als Programmiersprache streng aber dynamisch typisiert, funktional und wird interpretiert (also nicht kompiliert). Beliebt ist sie bei sogenannten Data Scientists unter anderem, weil es (kostenlose) Pakete gibt, mit denen man statistische Berechnungen (wie z. B. Matrixkalkulationen oder beschreibende Statistik) durchführen kann. Außerdem lässt sich Machine Learning (z. B. lineare Regression, Clustering, neuronale Netzwerke etc.) implementieren. Die Ergebnisse der Berechnungen können effizient und effektiv visualisiert werden.
R ist der Open-Source-Nachfolger der Programmiersprache S. Eine sehr aktive Community treibt die Entwicklung der Sprache und der verfügbaren Pakete voran. Zu Redaktionsschluss waren über 12 000 (in Worten: zwölftausend!) Pakete auf dem Comprehensive R Archive Network (CRAN) verfügbar. Das ist sowohl ein Segen (weil z. B. neue Machine-Learning-Modelle, die in der akademischen Welt oder von Unternehmen entwickelt werden, sehr schnell verfügbar gemacht werden), als auch ein Fluch (weil natürlich kein Mensch einen nur annähernd vollständigen Überblick über alle Pakete haben kann). Sie werden im Zuge dieses Artikels eine Handvoll Pakete kennenlernen.
Eins ist jedenfalls sicher: Wenn Sie mit Daten arbeiten, werden Sie R schätzen lernen. Sie haben damit viele Möglichkeiten, auf Daten zuzugreifen, sie zu transformieren und zu bereinigen, um sie letztendlich analysieren zu können. Und Microsoft hat beginnend mit SQL Server 2016 die Programmiersprache R in ihre Data Platform integriert.

Scripte erstellen und ausführen

Die Beispiele können Sie selber ausführen, nachdem Sie R und ein Integrated Development Environment (IDE), wie z. B. RStudio oder R Tools for Visual Studio , bei sich installiert haben. Abbildung 1 zeigt den grundlegenden Aufbau der IDE. Links oben (in der Abbildung rot umrahmt) kann man ein Script erstellen und ganz oder schrittweise ausführen. Links unten (orange) ist die Eingabekonsole. Hier kann man einzelne Anweisungen direkt eingeben, ausführen und die Ergebnisse sehen. Wenn man das Script ausführt, wird es automatisch Zeile für Zeile in die Konsole kopiert. Rechts oben (grün) kann man zwischen einer Übersicht über die vorhandenen Variablen, einer Ausführungshistorie und vorhandenen Verbindungen wechseln. Rechts unten (blau) erhält man u. a. Zugriff auf die Script-Dateien, graphische Ausgaben (Plots) oder die Hilfe.

Abb. 1: Aufbau von RStudio

Abb. 1: Aufbau von RStudio

„Hello World“

Genug theoretisiert – jetzt ist es Zeit für konkreten Code! Der wichtigste Codebestandteil sind natürlich die Kommentare, da diese erfahrungsgemäß die einzig zuverlässige Dokumentation von Code enthalten. Ab einem #-Zeichen wird der Rest einer Zeile als Kommentar ignoriert (Listing 1).

### KOMMENTARE
# Eine Kommentarzeile startet mit einem #-Zeichen
# Mehrzeilenkommentare sind nicht möglich (jede Zeile muss ...
# ... mit einem #-Zeichen beginnen)

Zeichenketten können wahlweise unter einfache oder doppelte Hochkommata gesetzt werden (Listing 2). Daran könnte man sich in anderen Sprachen auch schnell gewöhnen.

### ZEICHENKETTEN
"Hello World"
'Hello World'

Berechnungen (Listing 3) können einfach eingegeben werden – deren Ergebnis wird in der Konsole sofort angezeigt.

### BERECHNUNGEN
1 + 2
2 * 3
2^16

Wie oben schon angedeutet, ist Open Source sowohl Segen als auch Fluch. Im Fall von R fehlt manchmal eine klare Linie bzw. ein Standard. Um einer Variable einen Wert zuweisen zu können, gibt es ganze vier Möglichkeiten, die Sie allesamt in Listing 4 finden. Am häufigsten sieht man die Linkszuweisung (<-).

Zu beachten ist bei der Verwendung der Zuweisungsfunktion (assign), dass der Variablenname unter Hochkommata angegeben werden muss. Wird der Variablenname nicht unter Hochkommata angegeben, wird der Inhalt der Variable ausgewertet und der Wert auf eine Variable zugewiesen, die so heißt wie der Inhalt. In unserem Fall wird also dann eine neue Variable SQL2R x angelegt (weil die Variable a zu diesem Zeitpunkt diesen Inhalt hat) und der Wert SQ2R y zugewiesen (weil das der zweite Parameter der assign-Funktion ist). Listing 4 zeigt das alles.

### WERTZUWEISUNG
a = 'SQL2R'       # Zuweisung
a <- 'SQL2R'      # Links-Zuweisung
'SQL2R' -> a      # Rechts-Zuweisung
assign('a', 'SQL2R x')	# Zuweisungsfunktion auf Variable "a"
assign(a, 'SQL2R y')	# Zuweisungsfunktion auf Variable "SQL2R x" (!)

R ist, wie erwähnt, streng aber dynamisch typisiert. Das lässt sich mit dem Beispiel in Listing 5 gut illustrieren. Zuerst weisen wir den numerischen Wert 1 auf die Variable a zu. Dynamische Typisierung erlaubt uns, auf die idente Variable jederzeit einen Wert eines anderen Datentyps zuzuweisen. In diesem Beispiel ist das eine Zeichenkette (xyz).

R ist allerdings streng typisiert. Die Kalkulation a+b wird mit einem Fehler quittiert, wenn die Datentypen der beiden Variablen dafür nicht geeignet sind. Im Beispiel ist a numerisch und b eine Zeichenkette. Das ist nicht erlaubt. Sobald b einen kompatiblen Datentyp hat, klappt die Berechnung (Listing 5).

### DYNAMISCH TYPSIERT
a <- 1
a <- 'xyz'

### STRENG TYPSIERT
a <- 1
b <- 'xyz'
a + b	# Error in a + b : non-numeric argument to binary operator

### STRENG, DYNAMISCH TYPISIERT
a <- 1
b <- 2
a + b

Vorsicht! R ist case-sensitiv, unterscheidet also zwischen Groß- und Kleinschreibung. Damit habe ich persönlich weniger Freude (und kämpfe immer wieder mit Fehlermeldungen, weil ich bei Paket- oder Funktionsnamen die falsche Groß- bzw. Kleinschreibung verwende). Aber über Pro und Contra Case Sensitivity kann nächtelang diskutiert werden – und wird es bisweilen auch. Jedenfalls lässt sich das in R nicht abändern, wir müssen damit leben. Die beiden Variablen a und A in Listing 6 sind unterschiedliche Objekte und können daher auch unterschiedliche Werte besitzen.

a <- 'SQL2R'
A <- 'sql2r'
a
A

Datentypen

Neben den üblichen Datentypen, die wir auch in relationalen Datenbanken kennen, gibt es auch komplexe Zahlen (i) als Datentyp (Listing 7).

### DATENTYPEN
a <- 1    # numeric
mode(a)
a <- "1"  # character
mode(a)
a <- '1'  # character
mode(a)
a <- TRUE # logical
mode(a)
a <-  1i # complex
mode(a)

Unbekannte oder fehlende Werte werden in relationalen Datenbanken als NULL dargestellt. Das betrifft alle Datentypen, inklusive BOOLEAN (TRUE, FALSE, NULL). Fehler in Berechnungen können ebenfalls zu NULL führen. Die Programmiersprache R unterscheidet hier stattdessen zwischen nicht verfügbaren oder undefinierten Werten (NULL) und einem logischen Wert, der weder TRUE noch FALSE ist: NA (Not Available).

Berechnungsfehler werden signalisiert als: Inf (Infinity), -Inf (negative Infinity) oder NaN (Not a Number). Parallel zu Konstanten mit diesen Namen gibt es auch die Funktionen is.null, is.na, is.infinite sowie is.nan, um auf den entsprechenden Wert zu prüfen, weil ein direkter Vergleich mit == manchmal nicht zum erwarteten Ergebnis führt. Listing 8 zeigt ausführlich die Möglichkeiten.

### MEHR ALS BLOSS NULL
3/0               # Inf = Infinity
Inf               
is.infinite(4)    # FALSE
is.infinite(3/0)  # TRUE
is.infinite(Inf)  # TRUE
-3/0              # -Inf = negative Infinity
-Inf              # -Inf = negative Infinity
is.infinite(-3/0) # TRUE
is.infinite(-Inf) # TRUE
1 < Inf           # TRUE
1 < -Inf          # FALSE


0/0               # NaN = Not A Number
is.nan(4)         # FALSE
is.nan(0/0)       # TRUE
is.nan(NaN)       # TRUE
1/0 - 1/0         # NaN
Inf - Inf         # NaN


NA                # Not Available
is.na(4)          # FALSE
is.na(NA)         # TRUE

Inf == Inf        # TRUE

NaN == NaN        # NA
NA == NaN         # NA
NA == NA          # NA

is.finite(Inf)    # FALSE
is.finite(-Inf)   # FALSE
is.finite(NaN)    # FALSE
is.finite(NA)     # FALSE

NULL              # NULL
is.null(NULL)     # TRUE
is.null(NA)       # FALSE
NULL == NULL      # logical(0)
NULL == NaN       # logical(0)
NULL == NA        # logical(0)

Data-Frame

Der wichtigste Datentyp für jemanden, der mit der Welt der relationalen Datenbanken vertraut ist, ist der Data-Frame. Ein Data-Frame besteht aus Variablen (in der relationalen Welt würden wir Spalten sagen) und Beobachtungen (engl. „obs.“ oder „observations“; relational: Zeilen). Ich werde die relationalen Begriffe verwenden. Alle Zeilen einer Spalte haben den identen Datentyp. Die Spalten können benannt werden. Typischerweise werden Ergebnisse einer Datenbankabfrage oder der Inhalt von CSV-Dateien in einen Data-Frame geladen. Auch wenn dieses Objekt data.frame und nicht Tabelle heißt, so verhält es sich doch sehr ähnlich. Im Listing 9 erstellen wir einen Data-Frame mit dem einfallsreichen Namen df aus den drei Vektoren col1, col2 und col3. Die drei Vektoren wurden mithilfe der Combine-Funktion (c) erstellt. Ein Vektor ist also vergleichbar mit einer Spalte aus einer Tabelle (alle Elemente müssen den identen Datentyp haben). Das Ergebnis ist ein Data-Frame mit drei Spalten (Variablen) und vier Zeilen (Beobachtungen).

### DATA FRAMES

# 3 vectors
col1 <- c(11, 21, 31, 41)
col2 <- c(12, 22, 32, 42)
col3 <- c(13, 23, 33, 43)
df <- data.frame(col1, col2, col3)
df

Der Zugriff auf den Inhalt eines Data-Frames ähnelt wiederum dem Zugriff auf eine Matrix (die ebenfalls ein gültiger Datentyp in R ist – bei der aber der gesamte Inhalt, also nicht nur alle Zeilen einer Spalte, den identen Datentyp haben müssen). Listing 10 führt die verschiedenen Möglichkeiten aus: Um auf eine bestimmte Zeile einer bestimmten Spalte zuzugreifen, werden Zeilen- und Spaltennummer in eckigen Klammern angegeben. Lässt man die Spaltennummer leer, wird die gesamte Zeile zurückgeliefert. Lässt man die Zeilennummer leer, die ganze Spalte.

Auf eine Spalte kann auch mithilfe des Spaltennamens zugegriffen werden. Eine Möglichkeit ist, den Namen des Data-Frames und den Namen der Spalte getrennt durch das Dollarzeichen ($), anzugeben. Das ist vergleichbar mit dem Zugriff auf eine Spalte einer Tabelle in der relationalen Welt, wo wir als Trennzeichen den Punkt verwenden. Um weniger schreiben zu müssen, kann ein Data-Frame mithilfe von Funktionen auch angehängt (attach) und abgekoppelt (detach) werden. Die with-Funktion beschränkt dieses Anhängen auf den Ausdruck im zweiten Parameter.

# Eine Zelle auswählen
df[3, 2]

# Eine Zeile auswählen
df[3,]

# Eine Spalte auswählen
df[,2]
df$col2

attach(df)
col2
detach(df)

with(df, col2)

Ein bestehender Data-Frame kann über die Funktionen cbind (column bind – Spaltenanbindung) und rbind (row bind – Zeilenanbindung) erweitert werden, wie in Listing 11 dokumentiert ist.

# Eine Spalte hinzufügen
col4 <- c(99, 98, 97, 96)
df <- cbind(df, col4)
df

# Eine Zeile hinzufügen
df <- rbind(df, c(51, 52, 53, 54))
df

Daten importieren

Einen Data-Frame „händisch“ zu erstellen, wird eher selten vorkommen. Viel öfter werden Sie bereits vorhandene Daten in einen Data-Frame laden wollen. Ein gängiges Austauschformat sind CSV-Dateien. Praktikabler wird in vielen Fällen der direkte Zugriff auf die Datenquelle (z. B. SQL Server) sein. Dazu stehen uns die Standardfunktionen wie read.csv und Nützliches aus dem Paket ODBC zur Verfügung (Listing 12). Die aktuellste Version des Pakets ODBC installiert man mittels install.packages. Um die Funktionalitäten des Pakets nutzen zu können, muss es mit der Funktion library geladen werden. Mit odbcConnect können wir eine Verbindung zu einer Datenbank über eine (vorhandene) ODBC-Verbindung eröffnen. Die Funktion sqlQuery gibt uns das Ergebnis einer Abfrage praktischerweise als Data-Frame zurück. Mit close wird die Verbindung wieder geschlossen.

### DATEN AUS CSV LADEN
setwd("C:\\SQL2R\\Data") # Arbeitsverzeichnis festlegen
FactResellerSales <- read.csv("FactResellerSales.csv")

### ZUGRIFF AUF SQL SERVER VIA ODBC
install.packages("RODBC")	# Hochkomma notwendig!
library(RODBC)		# ohne Hochkomma!
MyConnection <- odbcConnect("AdventureWorksDW2014", uid="readonly", pwd="sTr4nGg3h31m")
DimProductCategory    <- sqlQuery(MyConnection, 'SELECT * FROM dbo.DimProductCategory')
close(MyConnection)

Daten abfragen

Die Daten in einem Data-Frame gespeichert zu haben, ist zwar nett und schön, aber kein Selbstzweck. In den folgenden Absätzen werden wir uns typische Abfragen ansehen. Das SQL-Schlüsselwort TOP ist in R als Funktion head verfügbar. Wird keine Zeilenanzahl angegeben, werden die ersten sechs Zeilen angezeigt. Analog wird mit tail auf die letzten Zeilen eines Data-Frames gefiltert (Listing 13).

### TOP
head(FactResellerSalesByDateSubCat)      # Zeigt die ersten 6 Zeilen eines Vektors, Matrix, Table, Data-Frame oder Funktion
head(FactResellerSalesByDateSubCat, 1)
tail(FactResellerSalesByDateSubCat, 1)

Die Spalten können wir namentlich filtern, indem wir einen Vektor mit den Namen der gewünschten Spalten erstellen und als zweiten Parameter in eckigen Klammern angeben (Listing 14). Alternativ bietet uns das Paket dplyr Funktionen an, die den Code vielleicht etwas lesbarer gestalten. Typisch für dplyr (und nicht für R) ist die Verwendung von %>%. Diese Funktion (auch wenn sie auf den ersten Blick nicht als solche erkennbar ist) leitet die Ausgabe von einem Ausdruck als Eingabe für den nächsten Ausdruck weiter. In unserem Beispiel wird also der gesamte Inhalt des Data-Frames FactResellerSalesByDateSubCat an die Funktion select geleitet. Letztere macht genau das, was wir auch in SQL erwarten würden: Sie filtert auf die angegebenen Spalten.

### PROJEKTION
FactResellerSalesByDateSubCat[,c("OrderDate", "EnglishProductSubcategoryName", "SalesAmount")]

install.packages("dplyr")
library(dplyr)
FactResellerSalesByDateSubCat %>% 
  select(OrderDate, EnglishProductSubcategoryName, SalesAmount)

Sortieren können wir die Ausgabeliste eines Data-Frames entweder mit der order-Funktion (beim Zeilenparameter der eckigen Klammern) oder mithilfe der arrange-Funktion aus dem dplyr-Paket (Listing 15).

### ORDER BY
FactResellerSalesByDateSubCat[order(FactResellerSalesByDateSubCat$SalesAmount),]

FactResellerSalesByDateSubCat %>% 
  select(OrderDate, EnglishProductSubcategoryName, SalesAmount) %>% 
  arrange((SalesAmount))

Einen Filter auf die angezeigten Zeilen machen wir in SQL mit WHERE. In R nutzen wir wieder die eckigen Klammern und schreiben im ersten Parameter die Bedingung. In Listing 16 ist das einmal ein Vergleich auf genau einen Wert mit doppeltem =-Zeichen und einmal über %in% und einen Vektor mit einer Liste von Produktkategorien. Das base-Paket von R (das automatisch installiert und geladen ist) bietet auch die Funktion subset, um einen Data-Frame zu filtern. Mit dplyr steht uns die filter-Funktion zur Verfügung. In SQL verhindern wir Duplikate mit DISTINCT, in R mit der Funktion unique (Listing 16).

### WHERE
FactResellerSalesByDateSubCat[FactResellerSalesByDateSubCat$EnglishProductSubcategoryName=="Road Bikes",]
FactResellerSalesByDateSubCat[FactResellerSalesByDateSubCat$EnglishProductSubcategoryName %in% c("Road Bikes", "Mountain Bikes"),]

subset(FactResellerSalesByDateSubCat, EnglishProductSubcategoryName=="Road Bikes")

FactResellerSalesByDateSubCat %>% 
  select(OrderDate, EnglishProductSubcategoryName, SalesAmount) %>% 
  arrange(SalesAmount)%>% 
  filter(EnglishProductSubcategoryName=="Road Bikes")

### DISTINCT
unique(FactResellerSalesByDateSubCat)

Für Berechnungen, sei es als Aggregation über mehrere Sätze, sei es als zusätzliche Spalte, greife ich gerne auf das nun schon bekannte dplyr-Paket zurück. Mit group_by wird eine Gruppierung definiert (identisch zu GROUP BY in SQL) und mit summarize die eigentliche Aggregatsfunktion (die man in SQL in der Projektion nach SELECT schreibt). Totalsummen (also über den gesamten Data-Frame) erhält man, wenn man nicht den Spaltennamen alleine, sondern mit vorangestelltem Data-Frame schreibt. Das ist beim Drüberlesen im Listing 17 möglicherweise nicht gleich augenfällig: sum(FactResellerSalesByDateSubCat$SalesAmount) berechnet den Umsatz (SalesAmount) über den gesamten Data-Frame, während sum(SalesAmount) nur den Umsatz der aktuellen Gruppierung (d. h. in diesem Fall der jeweiligen Produktkategorie (EnglishProductCategoryName)) enthält. In SQL müssten wir hier auf Sub-Selects oder Window-Funktionen (OVER()) zurückgreifen. Ganz zum Schluss in Listing 17 ergänze ich noch die Ausgabe eines Data-Frames mit einer zusätzlichen Spalte mithilfe der mutate-Funktion. Die Spalte Margin wird in dem Beispiel zwar ausgegeben, ist aber nicht im Data-Frame gespeichert.

### AGGREGATION
df <- data.frame(A = c(1, 1, 2, 3, 3), B = c(2, 3, 3, 5, 6))
df
df %>% group_by(A) %>% summarise(B = sum(B))

# Total & Prozent
FactResellerSalesByDateSubCat %>%
  group_by(EnglishProductCategoryName) %>%
  summarise(SalesAmountSum = sum(SalesAmount),
            SalesAmountTotal = sum(FactResellerSalesByDateSubCat$SalesAmount),
            SalesAmountPerc = sum(SalesAmount) / sum(FactResellerSalesByDateSubCat$SalesAmount) * 100
            )

### BERECHNETE SPALTE
# Total & Percentage
FactResellerSalesByDateSubCat %>%
  mutate(Margin = SalesAmount - TotalProductCost)

Zwei Data-Frames so miteinander zu verknüpfen, dass sich deren Spalten gegenseitig ergänzen, erledigen wir mit der merge-Funktion. In SQL würden wir einen JOIN machen. Das Beispiel zeigt zwei simple Data-Frames df1 und df2, die beide eine Spalte CustomerID beinhalten, die uns als JOIN-Kriterium dient. Je nachdem, ob wir den Parameter all, all.x oder all.y auf TRUE setzen, können wir einen full outer join, einen left outer join oder einen right outer join verwirklichen. Setzen wir keinen dieser Parameter und setzen stattdessen den Parameter by auf NULL, erhalten wir einen cross join (oder auch: Kartesisches Produkt), d. h. eine Ausgabe, bei der jeder Satz von df1 mit jedem Satz von df2 kombiniert ist (Listing 18).

### JOINS
df1 <- data.frame(CustomerID = c(1, 1, 2, 3, 3), 
                  SalesAmount = c(20, 30, 30, 50, 60))
df1
df2 <- data.frame(CustomerID = c(1, 2, 4), 
                  Name = c("x","y","z"))
df2

# Full outer join
merge(x = df1, y = df2, by = "CustomerID", all = TRUE)

# Left outer:
merge(x = df1, y = df2, by = "CustomerID", all.x = TRUE)

# Right outer:
merge(x = df1, y = df2, by = "CustomerID", all.y = TRUE)

# Cross join:
merge(x = df1, y = df2, by = NULL)

Vorhersagen

Die wahre Stärke von R (bzw. den verfügbaren Paketen) liegt sicherlich in Machine Learning. Diese reichen von simplen (linearen) Regressionen über Clustering bis zu neuronalen Netzwerken. In Listing 19 nehmen wir uns das Paket prophet vor. Der Name ist dabei Programm: Das Paket ermöglicht sogenannte time-series predictions, also Prognosen in die Zukunft, und wurde durch die Data-Science-Gruppe von Facebook entwickelt und zur Verfügung gestellt.

Nach der Installation und dem Laden des Pakets erstellen wir einen Data-Frame MyProphetDF aus dem existierenden Data-Frame FactResellerSales. Das hat einen zweifachen Zweck: Einerseits kann der Rest des Scripts identisch bleiben, auch wenn wir Vorhersagen für andere Daten machen möchten. Andererseits füttern wir prophet mit einem Data-Frame, der nur die notwendigen Dinge (Datum und Wert) enthält.

Im nächsten Schritt ändern wir die Spaltennamen auf ds und y, weil das für prophet so notwendig ist. Mit der as.POSIXct-Funktion stellen wir sicher, dass das Datum (das ursprünglich aus einer SQL-Server-Abfrage stammt) auch wirklich kompatibel mit prophet ist.

ML Conference 2019

Workshop: Machine Learning 101++ using Python

mit Dr. Pieter Buteneers (Chatlayer.ai)

Honey Bee Conservation using Deep Learning

mit Thiago da Silva Alves, Jean Metz (JArchitects)

Python Summit 2019

Daten analysieren und transformieren mit Python

mit Doniyor Jurabayev (Freelancer)

Advanced Flow Control

mit Oz Tiram (noris network AG)

Die nächsten drei Zeilen sind nur notwendig, weil die verwendeten Demodaten auf nur wenige Tage pro Monat unregelmäßig verteilt sind. Eine tagesgenaue Prognose ist damit nicht möglich. Daher ändern wir alle Werte der Datumsspalte auf den Ersten des Monats und aggregieren anschließend alle Werte auf einen Satz pro Monat.

Dann lassen wir prophet ein Modell (MyProhpetmodel) aufgrund unserer Werte aus der Vergangenheit erstellen. Mit make_future_Data-Frame erhalten wir eine leere Hülle für unsere Prognosewerte (in unserem Fall: für die nächsten sechs Monate), die dann mit predict befüllt wird. Wir verschmelzen anschließend mit merge die Vergangenheitswerte und die Prognosewerte in einen gemeinsamen Data-Frame MyProphetForecast, um Auswertungen leichter zu machen. Mit tail zeigen wir die letzten sieben Zeilen an, d. h. den letzten verfügbaren tatsächlichen Wert und die sechs von prohpet berechneten Prognosewerte.

### prophet
install.packages("prophet")
library(prophet);
MyProphetDF <- FactResellerSales[, c("OrderDate", "SalesAmount")]
colnames(MyProphetDF) <- c("ds", "y")
MyProphetDF$ds <- as.POSIXct(MyProphetDF$ds)
MyProphetDF$ds = as.Date(format(MyProphetDF$ds, "%Y-%m-01"))
library(dplyr)
MyProphetDF <- MyProphetDF %>%
  group_by(ds) %>%
  summarise(y = sum(y))

MyProphetModel <- prophet(df = MyProphetDF);
MyProphetFuture <- make_future_Data-Frame(MyProphetModel, periods = 6, freq = "m");
MyProphetForecast <- predict(MyProphetModel, MyProphetFuture);
MyProphetForecast <- merge(x = MyProphetForecast, y = MyProphetDF, by = "ds", all = TRUE)
MyProphetForecast <- merge(x = MyProphetForecast, y = MyProphetForecast[is.na(MyProphetForecast$y) == TRUE, c("ds", "yhat")], by = "ds", all = TRUE)
tail(MyProphetForecast, 7)

Visualisieren

Die tatsächlichen Umsatzwerte und die Prognose lassen sich gemeinsam in einem Chart darstellen (Abb. 2). Dazu verwenden wir das Paket ggplot2. Das Paket zeichnet sich vor allem durch seine Implementation der „Grammar of Graphics“ aus. Ein Chart wird dabei sehr flexibel Schicht für Schicht erstellt. Damit lassen sich z. B. ein Punkt- und ein Liniendiagramm wie in Abbildung 2 kombinieren. Das Paket scales hilft, das Zahlenformat wie gewünscht zu konfigurieren. In Listing 20 rufen wir die ggplot-Funktion auf und übergeben die folgenden Parameter: als erstes den Data-Frame mit den Daten (MyProphetForcast). Mit aes werden die aestetics, also aufzutragenden Achsenwerte (in unserem Fall das Datum ds und der Echtumsatz y, in der ersten vorgeschlagenen Farbe), festgelegt. Die Funktion geom_point() legt fest, dass wir diese Werte bloß als Punkte sehen wollen. Mit dem Aufruf von theme() und scale_y_continuous legen wir die Textgröße und den Wertebereich der Y-Achse fest. Anschließend ergänzen wir das erstellte Diagramm mit einer weiteren Linie (geom_line()) und Punkten (geom_point()) mit den Prognosewerten (yhat.y) in einer zweiten Farbe. Zu guter Letzt schalten wir die Legende aus und stellen eine vernünftige Achsenbeschriftung sicher.

### AKTUELLE UND PROGNOSTIZIERTE UMSATZWERTE ALS LINIENDIAGRAMM ANZEIGEN
install.packages("ggplot2")
library(ggplot2)
library(scales)
ggplot(MyProphetForecast,
  aes(x = ds,
    y = y,
      color = 1)) +
  geom_point() +
  theme(text = element_text(size = 18)) +
  scale_y_continuous(limits = c(0, 5000000), labels = comma) +
  geom_line(y = MyProphetForecast$yhat.y, color = 2) +
  geom_point(y = MyProphetForecast$yhat.y, color = 2) +
  theme(legend.position = "none") +
  labs(x = "Order Date",
    y = "Sales Amount")
Abb. 2: Aussagekräftige Visualisierungnen mit R

Abb. 2: Aussagekräftige Visualisierungnen mit R

Fazit

Ich hoffe, ich konnte Ihnen mit diesem Artikel zu einem Einstieg in das Erlernen der Programmiersprache R verhelfen. Die großen Datenbankhersteller haben jedenfalls das Potenzial von R erkannt. So hat etwa Microsoft einen R-Interpreter sowohl in die Datenbank-Engine integriert (man kann daher ohne Datentransfer ein R-Script auf Daten im SQL-Server und in einer Azure-SQL-Datenbank laufen lassen) als auch in Power BI Desktop, was speziell für Visualisierungen interessant ist.

Die Zahl der Anwender der Programmiersprache R (zu denen Sie ab jetzt vielleicht auch gehören) und der Anwendungsfälle hat in den letzten Jahren eine steile Wachstumskurve hingelegt. Eine aktive Community sorgt dafür, dass das auch künftig so bleiben wird.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -