Library

Overfitting im Machine Learning – Was ist das und wie vermeidet man es?

Overfitting ist eine der größten Stolpersteine im Machinellen Lernen. Jeder, der mit Daten zu tun hat, ist in diese Falle schon mal gelaufen. Hoffentlich wurde es bemerkt, denn ansonsten waren die gefunden Zusammenhänge oder der Prognose-Algorithmus für die Tonne. Muss man so hart sagen.
In diesem Artikel erfährst Du alles über Overfitting, aber auch Underfitting, und warum es so wichtig ist, das Problem in den Griff zu bekommen.
Was ist Overfitting?
Overfitting bedeutet übersetzt Überanpassung und bezeichnet eben die Überanpassung von Algorithmen bzw. deren Parameter an die beobachteten Daten. Im Machine Learning Kontext bedeutet das, der Algorithmus lernt im Prinzip den Datensatz „auswendig“, erkennt aber nicht das zugrundeliegende Muster oder System. Damit sind Prognosen, die der Algorithmus aus noch unbekannten Daten liefern soll, nicht sonderlich gut.
Auch in der Datenanalyse kommt es häufig zu Overfitting. In diesem Kontext werden Muster und Zusammenhänge erkannt, die aber eigentlich gar nicht vorhanden sind.
Nehmen wir als Beispiel eine Kurve durch vier Punkte. Statt einer Geraden durch die Punkte zu legen (als Python-Nutzer kannst Dir mein Praxis-Tutorial zur linearen Regression anschauen) könnten wir auch ein Polynom dritten Grades durch die Punkte legen. Das passt ja viel besser, denn die Kurve führt genau durch die Punkte, dementsprechend ist der Fehler 0.

Ok, also was ist eigentlich das Problem hinter der Sache? Im Prinzip verwendet man ein zu komplexes Modell, das zwar genau auf den vorhandenen Datensatz passt, aber keine Verallgemeinerungskraft besitzt.
y = a*x³ + b*x² + c*x + d
Aber Moment, wenn wir a und b gleich Null setzen, haben wir ja die einfache Geradengleichung. Also theoretisch sollte auch das komplexere Modell den richtigen Zusammenhang finden. Das eigentliche Problem liegt also nicht direkt in der Modellwahl, sondern in der Anzahl verfügbarer Parameter. Dabei geht es nicht um die absolute Anzahl Parameter, sondern um das Verhältnis zwischen der Anzahl Parameter und der Anzahl Beobachtungen. Gibt es genug Beobachtungen, sollte der Algorithmus erkennen, dass die Koeffizienten a und b Null oder nahe bei Null sein sollten.
Nehmen wir an, wir haben 100 Beobachten. Dann scheint es zu funktionieren. Das Verhältnis zwischen 100 Beobachtungen und 4 Parametern ist gut genug.

 

a

b

c

d

tatsächlicher Wert

0

0

3

0

Regression mit Polynom 3.Grades

0,007271

0,004726    

2,987975    

0,078643    

Neuronale Netze, die aktuell modernste Form der Machine Learning Algorithmen, sind strukturell zwar komplizierter als so eine lineare Regression, aber das Prinzip ist das Gleiche. Wir haben Parameter, welche anhand eines Datensatzes bestimmt werden. Jetzt ist es aber so, dass die Anzahl Parameter mit der Größe bzw. Tiefe des neuronalen Netzes explodiert. Die große Version von OpenAI GPT-3, der aktuell wahrscheinlich mächtigsten künstlichen Texterzeugung, hat stolze 175 Milliarden Parameter. Dementsprechend wurde GPT-3 mit den größten Textdatensätzen gefüttert, die zur Verfügung stehen, insgesamt waren das auch mehrere hundert Milliarden Wörtern.
Das ist vielleicht ein Extrembeispiel, aber viele sich im Einsatz befindenden neuronalen Netze haben mehrere Millionen Parameter. Und damit wird dann auch klar, warum man so große Datenmengen benötigt, um diese Machine Learning Algorithmen vernünftig zu trainieren. Irgendwie müssen diese vielen Parameter eingestellt werden. Der Mensch hingegen ist ziemlich gut darin, aus wenigen Beispielen zu lernen. Daher wird auch daran geforscht, wie das mit Machine Learning gehen könnte (siehe meinen kurzen Artikel über One-Shot-Learning)
Aber auch im Kleinen ist Overfitting ein Problem. Habt ihr auch schon so häufig gehört: „Ich habe 10 Leute befragt und möchte nun diese 5 Variablen statistisch untersuchen“? Also nochmal:
Das Verhältnis zwischen der Anzahl Parameter und der Datensatzgröße ist entscheidend!
 
Ist Underfitting eigentlich auch ein Problem?
Underfitting bedeutet interessanterweise nicht das direkte Gegenteil: „Zu viele Daten und zu wenig Parameter“. Zu viele Daten kann man nie haben 😉. Mit Underfitting ist gemeint, dass das Modell zu den beobachteten Daten passt bzw. zu einfach ist, also irgendwie doch wieder zu wenig Parameter.
Stellt Euch vor, es liegt in der Realität ein quadratischer Zusammenhang vor (z.B. Bremsweg zu Geschwindigkeit), wir legen aber nur eine Gerade an.

Wie vermeidet man Overfitting?
Ok, nun wissen wir also, was Overfitting im Machine Learning bedeutet. Aber wie können wir es vermeiden?
Einen ersten Ansatz kennt Ihr bereits: mehr Daten. Das hilft auf jeden Fall schon mal weiter, ist aber nicht immer realisierbar oder mit hohen Kosten (z.B. Umfragen) verbunden.
Der zweite Ansatz ist auch aus dem vorherigen Abschnitt klar: weniger Parameter. Erst kürzlich hatte ich bei einer Analyse einige Variablen, die jeweils für eine bestimmte Vorerkrankung mit Werten ja oder nein standen. Anstatt alle diese Variablen in das statistische Modell zu kippen, habe ich diese Variablen zu einer Variablen zusammengefasst, die nur zählt, wie viele Vorerkrankungen der Patient hat. Solche Ansätze werden übrigens auch als Feature Engineering bezeichnet.
 
Aufteilung in Trainings- und Testdatensatz
Wie überprüft man, ob ein Modell gut ist bzw. das zugrundeliegende Muster abbildet? Man testet es an frischen Daten und analysiert das Ergebnis!
Wenn ich jetzt aber den gesamten Datensatz für das Training (also die Parameterbestimmung/-optimierung) verbraten habe, habe ich ein Problem. Ich kann nämlich nicht den gleichen Datensatz zur Bestimmung der Qualität verwenden, denn diese Daten lagen dem Algorithmus schon vor. Da beißt sich die Katze in den Schwanz. Ich müsste also irgendwoher neue Daten sammeln.
Deshalb unterteilt man den vorhandenen Datensatz in einen Trainings- und einen Testdatensatz auf. Nur mit dem Trainingsdatensatz wird der Machine Learning Algorithmus gefüttert. Der Testdatensatz kommt erst zum Einsatz, wenn der Algorithmus ausgelernt hat. Der Testdatensatz ist ausschließlich zur Bestimmung der Güte da, für nichts anderes.
Meist wird im Verhältnis 70/30, 80/20 oder bei großen Datensätzen auch mal 90/10 aufgeteilt, also 70%, 80% oder 90% für das Training und der Rest entsprechend für den Test.
 
Und was ist der Validierungsdatensatz?
Im Zusammenhang mit ML-Algorithmen wie neuronalen Netzen gibt es noch einen weiteren Typ, nämlich den Validierungsdatensatz. Dieser wird verwendet, um die sogenannten Hyperparameter des ML-Algorithmus zu optimieren. Bei neuronalen Netzen versteht man darunter zum Beispiel die Topologie des neuronalen Netzes, also wie viele Ebenen, Knoten, Art der Ebenen usw. es besitzt.
Denn auch diese Parameter müssen festgelegt werden. Hyperparameter deshalb, weil sie noch vor dem eigentlichen Training gesetzt werden.
Das Vorgehen ist dann so, dass für die Hyperparameter mehrere mögliche Werte definiert werden. D ann wird für jede Kombination anhand des Trainingsdatensatzes gelernt. Anschließend wird mit Hilfe des Validierungsdatensatz die Qualität bestimmt und dadurch die beste Wahl der Hyperparameter herausgefunden. Der Testdatensatz kommt erst wieder ganz zum Schluss zum Einsatz, wenn alle Parameter festgelegt sind und die finale Qualität beurteilt werden soll.
 
 
Also, haltet euch fit, aber niemals overfit 😉
So long,
Euer Holger

Glossar: Maschinelles Lernen Hyperparameter

Shiny Tutorial: Erstelle ein Dashboard in R

Ist es nicht cool, die Daten, die man mühsam aufbereitet hat, schön zu visualisieren anstatt nur langweilige Tabellen zu erstellen? Und das bitteschön interaktiv, dann kann man tiefer in die Daten einsteigen. Genau das zeige ich Euch in diesem R Shiny Tutorial. Wir erstellen zusammen eine Shiny App in R, und das ist ganz easy.

Inhalt

Was ist R Shiny?Gibt es Alternativen zu R Shiny?Die ersten Schritte zum Erstellen einer R Shiny AppZweigeteilter Aufbau einer R Shiny AppDie Server-Funktion der Shiny AppDas erste eigene interaktives Dashboard in R erstellenMehrseitiges Layout einer Shiny App mittels navbarPageDie Server-Seite unserer Shiny AppAusblick: Möglichkeiten von ShinyDeployment
Was ist R Shiny?
Shiny ist eigentlich nur ein Package für R, aber es ermöglicht uns, interaktive Dashboards in R zu erstellen, die dann als Webseite im Browser angezeigt werden. Wenn man Webseite hört, denkt man an HTML- und CSS-Programmierung. Und ja, ein paar Grundlagen zu wissen ist nicht verkehrt, aber das benötigte Wissen hält sich wirklich in Grenzen.
Um mal einen Eindruck zu bekommen, was Shiny Apps so alles können, solltet ihr die Shiny Gallery besuchen, die einige wirklich tolle Showcases beinhaltet.
Da Shiny ein R-Package ist, programmiert Ihr also Euer Dashboard einfach in R. Dazu teilt man das Skript in einen Anzeige- und einen Interaktionsteil auf, aber dazu mehr im nächsten Abschnitt. Will man dann das Programm starten, drückt man einfach auf „Run App“ und schon sieht man es im Browser.
Shiny wurde von RStudio entwickelt und ist dementsprechend gut in die RStudio IDE integriert. Ich empfehle also auf jeden Fall, RStudio zu benutzen, wenn Ihr eine Shiny App programmieren wollt. Natürlich geht es aber auch in anderen Entwicklungsumgebungen wie VS Code.
Das Deployment, also das Veröffentlichen in der Cloud, so dass andere Benutzer darauf zugreifen können, kann durchaus etwas komplexer sein, das hängt aber vom Anbieter ab. Für den Anfang gibt es aber eine einfache, kostenlose Möglichkeit. Mehr dazu am Ende von diesem R Shiny Tutorial, also dranbleiben.
Gibt es Alternativen zu R Shiny?
In R nicht wirklich, da ist Shiny einfach die beste Möglichkeit, um Dashboard-Apps zu erstellen. Die Python-Community schielt sogar ein wenig neidisch auf dieses tolle R-Package. Ähnliche Funktionalitäten gibt es in Plotly Dash, das in Python recht weit verbreitet ist, aber auch in R und Julia läuft. In meinem Einsteiger-Tutorial zu Plotly Dash in Python erfährst Du mehr darüber.
Natürlich gibt es auch noch Anbieter, bei denen man sich mehr oder weniger per Point & Click seine Dashboards zusammenstellt. Die grafischen Oberflächen zum Erstellen der Dashboards sind Fluch und Segen. Es ist wirklich einfach und schnell, sich damit ein Dashboard zu bauen. Aber will man eine spezielle Interaktivität oder Verhaltensweise haben, muss man sich durch viele Optionen wühlen und häufig irgendwelche Tricks einsetzen, damit es klappt. Auch die Wartbarkeit ist eher bescheiden: Wo war nochmal die Option? Von Automatisierung, Modularisierung und Versionskontrolle ganz zu schweigen.
Mittels Shiny oder Plotly Dash ist das Erstellen der grafischen Elemente zwar etwas mühselig, aber dafür hat man es schön übersichtlich im Skript, kann Dinge in Funktionen auslagern, hat Git als Versionskontrolle usw.
Hier trotzdem eine Liste der wichtigsten Dashboard-Anbieter:

Microsoft PowerBI: Eher im Business Intelligence angesiedelt, daher ein bisschen behäbig und nicht so wirklich schön damit zu arbeiten. Ich habe aber auch schon tolle Dashboards in PowerBI gesehen. Die Desktop-Version ist kostenlos und da PowerBI Bestandteil des Office-Pakets ist, wird es in vielen Firmen eingesetzt.
QlikSense: Eine interessante Kombination von mächtigem Lade-Skript und interaktiver Oberfläche. D.h. in QlikSense kann man die Datentransformation und -bearbeitung gut in einem Skript, welches beim Aktualisieren ausgeführt wird, erledigen. Sind die Daten dann in der gewünschten Form, kommt Dashboard und die Interaktivitäten darauf. Man muss sich ein wenig daran gewöhnen, aber dann ist diese Kombination wirklich cool.
Tableau: Ich kenne es zu wenig, als dass ich wirklich eine Meinung dazu habe. Tableau galt lange Zeit als der Platzhirsch und ist insbesondere in den USA weit verbreitet. Die Visualisierungen sollen sehr gelungen sein.
Grafana: Eine tolle kostenlose Software und bei der IT sehr beliebt. Tatsächlich wird Grafana viel im Monitoring-Bereich eingesetzt, denn Echtzeit-Zeitreihen sind der Schwerpunkt.

 
Die ersten Schritte zum Erstellen einer R Shiny App
Zuerst müsst Ihr Shiny wie jedes extra R-Package installieren. Das geht über install.packages(“shiny”).
Als nächstes erstellen wir ein Shiny Projekt. Also File -> New Project -> New Directory -> Shiny Web Application ausgewählt und den neuen Verzeichnisnamen und dessen Stammverzeichnis eingegeben.

In dem Verzeichnis wurde nun eine Datei app.R angelegt, welche als Ausgangspunkt für unsere eigene App dient und schon ein paar Demoelemente enthält. Schaut Euch erstmal an, was die Shiny App macht, klickt dafür auf „Run App“ rechts über dem Editorfenster.

In der Konsole seht ihr, dass dieser Button einfach den Befehl runApp() ausführt.

Es wird ein lokaler Webserver gestartet. Also ein Dienst, der auf Eurem Rechner läuft und eben die Shiny Webapp bereitstellt.
Dann öffnet sich ein RStudio-Browserfenster mit der Shiny Applikation. Ihr könnt aber auch die Adresse http://127.0.0.1:3906 im Browser Eurer Wahl eingeben. 127.0.0.1 ist die Adresse von Eurem Rechner und 3906 der Port, auf dem die App erreichbar ist.

So oder so ähnlich sollte das dann aussehen. Nach der Überschrift ist links ein Schieberegler, der die Anzahl Unterteilungen für das Histogramm auf der rechten Seite steuert. Schauen wir uns den zugehörigen Code an.
Zweigeteilter Aufbau einer R Shiny App
Jede Shiny App besteht aus drei Teilen, der Benutzeroberfläche (UI = User Interface), dem Server, der für die Interaktionen zuständig ist und schließlich der Aufruf der shinyApp. Statt einer Datei app.R kann man übrigens auch die beiden ersten Teile trennen und eine Datei ui.R und eine Datei server.R anlegen.
So sieht der UI-Teil aus:
# Define UI for application that draws a histogram
ui <- fluidPage(

# Application title
titlePanel("Old Faithful Geyser Data"),

# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),

# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)

Also, das UI besteht aus einer sogenannten fluidPage, welche aus Zeilen und diese wiederum aus Spalten besteht. In diesem Fall ist die erste Zeile der Titel mittels titlePanel. In der zweiten Zeile definieren wir dann ein sidebarLayout, welches aus einem sidebarPanel und einem mainPanel besteht. Im sidebarPanel ist der Slider und im mainPanel das Histogramm.

In dieser Art lassen sich alle möglichen Strukturen realisieren: mehrere Seiten, Tabs, verschachtelte Zeilen/Spalten usw.
Die Server-Funktion der Shiny App
Im Server-Abschnitt müssen wir den Slider mit dem Plot verknüpfen bzw. den Plot überhaupt erstmal definieren. Denn im UI-Abschnitt steht ja nur plotOutput("distPlot"). Dabei ist distPlot der Name/die Identifikation des Plots.
# Define server logic required to draw a histogram
server <- function(input, output) {

output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)

# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}

Wir definieren also, dass distPlot die Ausgabe von renderPlot ist. Innerhalb der renderPlot-Funktion nutzen wir input$bins als Parameter für das Histogramm. Wir können darauf zugreifen, weil wir den Slider im UI als „bins“ benannt haben (sliderInput("bins",…)).
Das erste eigene interaktives Dashboard in R erstellen
Wir wollen ein kleines Aktiendashboard bauen. Dazu gibt es links eine Sidebar mit Auswahl des Zeitraums und der anzuzeigenden Aktie. Rechts soll dann das zugehörige Liniendiagramm oder eine Tabelle angezeigt werden. Um zu zeigen, dass auch mehrere Seiten möglich sind, bauen wir auch noch eine zweite Seite ein, die lassen wir aber erstmal leer.
So soll das Ganze aussehen:

Zuerst erzeugen wir ein neues, leeres Projekt. In dem Ordner erstellen wir die Dateien ui.R und server.R. Wir wählen also die Variante mit zwei Dateien.
Außerdem brauchen wir noch ein paar Packages, und zwar quantmod, gglot2, dplyr, DT und shinythemes. Mit Quantmod können wir Aktiendaten von Yahoo Finance laden, ggplot2 ist für Grafik, dplyr für Datentransformation (falls Du dplyr noch nicht kennst, schau mal in mein dplyr Tutorial rein), DT für interaktive Tabellen und schließlich shinythemes, um dem Dashboard ein anderes Farbschema überzustülpen. Prinzipiell funktioniert hier übrigens jedes Bootstrap-Theme, wir bleiben aber erstmal bei den in dem Package vorhandenen.
 

Mehrseitiges Layout einer Shiny App mittels navbarPage
Fangen wir mit der Datei ui.R an. Hier definieren wir uns die zwei Seiten als tabPanels und fügen diese am Ende mittels navbarPage zusammen. Seite 2 ist, wie gesagt, nur ein Dummy, den wir nicht weiter beachten. Seite 1 hat das Sidebar-Layout, bestehend aus sidebarPanel und mainPanel, das wir schon aus dem Standard-Beispiel kennen.
Im sidebarPanel definieren wir ein Datums-Picker, um den Zeitraum festzulegen. Dafür wollen wir eigentlich dateRangeInput verwenden, dieses Input-Element legen wir aber erst in der server.R-Datei fest. Daher gibt es hier nur den Platzhalfter uiOutput. Der Grund dafür ist, dass wir Variablen aus der server-Datei benutzen wollen und diese sind erstmal nicht in der ui.R verfügbar. UiOutput ist übrigens gut dafür geeignet, dynamische Inhalte zu erstellen, doch dazu später mehr.
uiOutput("ui_datumswahl")
Das zweite Element der Sidebar ist eine Gruppe von Checkboxen, mit denen man die Aktien-Symbole an- und abwählen kann. Das geht über ein checkboxGroupInput, aber wir müssen es wieder in die server.R schieben, da wir die Variable symbols benötigen.
uiOutput("ui_aktienwahl")
Und so sieht dann unsere gesamte ui.R aus:
library(shinythemes)
library(DT)

seite1
 

Die Server-Seite unserer Shiny App
Zuerst laden wir die benötigten Packages und definieren uns drei Parameter. Symbols ist ein Vektor mit den Aktien-Symbolen, die von Yahoo Finance geholt werden soll. Zudem gibt es noch das Start- und Enddatum.
library(quantmod)
library(ggplot2)
library(dplyr)

symbols <- c("AAPL","NFLX","AMZN","MSFT")
start_datum = as.Date("2019-01-01")
end_datum = Sys.Date()
Im nächsten Schritt laden wir die benötigten Daten herunter und transformieren diese in einen data.frame, der für Tabelle und Chart taugt. Ich habe das ziemlich ineffizient mit einer for-Schleife programmiert, aber so ist es leicht nachzuvollziehen. Als erstes holen wir uns die Symbole, diese werden von Quantmod als Zeitreihe unter dem Symbolnamen abgelegt. Wir deklarieren uns einen leeren data.frame, den wir dann in jedem Schleifendurchgang mit den Daten eines Symbols ergänzen.
## Daten von Yahoo Finance holen ###################################
getSymbols(symbols, from = start_datum, to = end_datum)
df <- data.frame(Symbol=character(),
Datum=as.Date(character()),
Open=numeric(),
High=numeric(),
Low=numeric(),
Close=numeric(),
Volume=numeric(),
Adjusted=numeric())
for (s in symbols) {
temp <- data.frame(Symbol=s, Datum=index(get(s)), get(s))
colnames(temp) <- c("Symbol","Datum","Open","High","Low","Close","Volume","Adjusted")
df <- rbind(df,temp)
}
Code, der nicht in der server-Funktion steht, wird einmalig beim Start der R Shiny App ausgeführt.
Und schließlich folgt die eigentliche Server-Funktion. Wir haben vier Elemente: den Date-Picker, die Aktien-Checkboxen, das Liniendiagramm und die Tabelle.
Der Date-Picker ist ein dateRangeInput mit ein paar Parametern, die relativ selbsterklärend sind. Eigentlich wollen wir ihn ja in der UI haben, also kapseln wir ihn über renderUI.
output$ui_datumswahl <- renderUI(
dateRangeInput("datumswahl",
label = "Zeitauswahl:",
start = start_datum,
end = end_datum,
min = start_datum,
max = end_datum,
format = "dd.mm.yyyy",
language = "de",
separator = "bis")
)
Das Gleiche machen wir für die Aktien-Checkboxen
output$ui_aktienwahl <- renderUI(
checkboxGroupInput("aktienwahl",
label="Aktienauswahl:",
symbols,
selected=symbols[1])
)
Das Liniendiagramm wird über renderPlot und einen ggplot-Chart realisiert. Dazu nehmen wir den Datensatz, filtern ihn auf die ausgewählten Symbole (input$aktienwahl) und ausgewählten Zeitraum. Damit keine Fehlermeldung bei nicht gewähltem Zeitraum auftritt, habe ich noch per ifelse das minimale Anfangs- bzw. maximale Enddatum ergänzt.
# Das Liniendiagramm mit Filter abhängig von den Inputs
output$chart <- renderPlot({
df %>%
filter(Symbol %in% input$aktienwahl,
Datum >= ifelse(length(input$datumswahl[1])==0,start_datum,input$datumswahl[1]),
Datum <= ifelse(length(input$datumswahl[2])==0,end_datum,input$datumswahl[2])) %>%
ggplot(aes(x = Datum, y = High, color = Symbol)) +
geom_line()
})
Das vierte und letzte Element ist die Tabelle, ebenfalls mit dem identischen Filter wie der Chart versehen. Wichtig hierbei ist der Parameter style = „bootstrap“, denn ansonsten wird die Tabelle nicht an das gewählte dunkle shinytheme angepasst und man kann die Überschriften nicht lesen.
# Datentablle als DT
output$table <- renderDT({
df %>%
filter(Symbol %in% input$aktienwahl,
Datum >= ifelse(length(input$datumswahl[1])==0,start_datum,input$datumswahl[1]),
Datum <= ifelse(length(input$datumswahl[2])==0,end_datum,input$datumswahl[2])) %>%
datatable(style = "bootstrap", options = list(pageLength = 20))
})
Damit haben wir alles soweit fertig. Hier sind die Dateien server.R und ui.R auch nochmal zum Herunterladen. Das ist doch schon mal ziemlich cool, oder?

Ausblick: Möglichkeiten von Shiny
Dieses Tutorial gibt natürlich nur einen kleinen Einblick in die glänzende Shiny-Welt. Es gibt tausende Möglichkeiten und wahrscheinlich habt Ihr auch schon Ideen, wie man unsere kleine App erweitern könnte. Also mir fällt jedenfalls noch eine Menge ein, zum Beispiel:

Abspeichern der Daten in einer kleinen Datenbank (z.B. SQLite) bzw. noch besser eine Trennung in Datenholen (dann täglich automatisiert) und dem Dashboard.
Verschiedene Charttypen (z.B. Candlesticks)
Zusammenfassende Statistiken zu den ausgewählten Aktien
Tabelle im Responsive Design

Und dann gibt es ja auch noch Performance-Überlegungen in Shiny. Welche Elemente müssen reactive sein, welche nicht, …
Deployment
Und schließlich das Deployment einer Shiny App, also das Hochladen in die Cloud, damit man es von jedem Rechner aus ansehen kann. Mit shinyapp.io könnt ihr kostenlos die ersten Schritte machen, muss dann aber recht schnell monatlich etwas zahlen. Shinyapp.io ist der bequemste Weg. Aber wenn ihr Zugang zu einem Linux-Server habt, könnt ihr darauf auch selber einen Shiny Server installieren. Wollt Ihr Eure Applikation bei anderen Anbietern hosten, dann ist es am besten, die App in einen Docker-Container zu packen. Das ist eigentlich ganz einfach, aber dazu ein anderes Mal mehr.
 
So, und nun ran an die Dashboards und viel Spaß beim Coden,
Euer Holger

Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist

Interaktive Dashboards in Python – Plotly Dash Tutorial

Interaktive Dashboards sind ideal, um vielfältige Daten übersichtlich darzustellen. In diesem Tutorial zu Plotly Dash zeige ich Euch, wie man diese in Python programmiert.
Die Aufgaben eines Data Scientist umfassen nämlich nicht nur Analysen, sondern häufig auch die Darstellung der Ergebnisse. Und wie cool ist es, wenn man nicht nur ein statisches PDF erzeugt oder sich manuell Powerpoint-Folien bastelt, sondern ein interaktives Dashboard baut. Mit diesem kann dann der Nutzer interagieren und sich so die Kennzahlen, gefiltert nach seinen Wünschen, anzeigen lassen. Zudem lassen sich Dashboards leicht aktualisieren, wenn neue Daten verfügbar sind. Wer monatlich oder wöchentlich Excel- oder Powerpoint-Reports bastelt, weiß, wovon ich rede.

Inhalt

Software, um Dashboards zu erstellenDashboards mit Python programmierenWas ist Plotly Dash?Installation von Plotly DashDas erste Dashboard mit Plotly DashSteuerungselemente wie Dropdowns in Plotly DashErste Interaktivität in unserem Plotly Dash DashboardDeployment von Plotly Dash in der Cloud
Software, um Dashboards zu erstellen
Es gibt eine Reihe kommerzieller, aber auch kostenlose Angebote, mit denen man Dashboards erstellen kann.

Microsoft Power BI: Recht schwerfällig, eher für Business Intelligence geeignet als für polierte Dashboards mit viel Interaktivität. Die Desktop-Version ist kostenlos. Um jedoch Reports vernünftig zu teilen (also nicht nur Dateien hin- und herzuschieben), werden Lizenzen für die Cloud-Lösung fällig. Auf der anderen Seite haben viele Firmen eh ein Office-Paket, bei dem PowerBI enthalten ist.
Qlik bzw. QlikSense: Finde ich persönlich ziemlich gut, allerdings sind die Lizenzen relativ teuer. Fluch und Segen zugleich sind die umfangreichen Skripting-Möglichkeiten. Damit kann man richtiges Datenmanagement machen, aber die Mischung zwischen Programmieren und Zusammenklicken gestaltet die Übersicht schwierig.
Tableau: Hat in der Anfangszeit viel Lob bekommen. Die Stärken liegen vor allem in der Datenvisualisierung. Der Einsatz ist aber auch nicht ganz preiswert.
Grafana: Eine tolle kostenlose Software, die vor allem für Zeitreihen (Monitoring von Servers etc.) eingesetzt wird

Dashboards mit Python programmieren
Der Vorteil und gleichzeitig Nachteil von all diesen Klick-basierten Lösungen ist eben das visuelle Arbeiten. Das macht es für den Nicht-Programmierer zwar einfacher bzw. überhaupt erst möglich, Dashboards zu erstellen. Aber die Wartbarkeit ist unübersichtlich bis nicht vorhanden. Gerade bei komplexeren Projekten möchte man verschiedene Dateien für verschiedene Module und Definitionen usw.
Hier kommen die komplett programmierbaren Dashboards ins Spiel. Vorteil solcher Frameworks ist die Kontrollierbarkeit. Nachteil ist, dass man schon ein paar Programmier-Fähigkeiten besitzen muss und auch ein paar Grundlagen in HTML/CSS haben sollte.
Wer in R programmiert, für den ist Shiny das Framework, an dem man nicht vorbeikommt. Für Python gibt es zwei größere Frameworks, Plotly Dash und Bokeh. Die Beliebtheit von Dash steigt stetig und anscheinend ist es etwas einfacher zu lernen. Bokeh benötigt für einige Interaktionen auch Javascript-Kenntnisse. Deshalb geht dieses Einsteiger-Tutorial um Plotly Dash.
Was ist Plotly Dash?
Dash ist ein Framework der Firma Plotly, um Webapps zur Datenanalyse-/visualisierung in Python, R oder Julia zu programmieren. Dash basiert auf React, einem bekannten Javascript-Web-Framework und Flask, einem der bekanntesten Webserver in Python.
Was alles mit Dash möglich ist, kann man sich in der Dash App Galerie anschauen.
Dash ist komplett kostenlos und open-source. Plotly bietet aber kommerzielle Lösungen zum Hosting der Webapps an. Eines muss Euch aber klar sein: Damit jemand anderes Euer Dashboard im Browser sehen kann, braucht ihr einen Server, der Python unterstützt. Für erste Versuche gibt es ein paar kostenlose Schnupperangebote im Netz (pythonanywhere, Heroku). Wollt ihr mehrere Apps erstellen, dann solltet Ihr Euch eine virtuelle Maschine im Netz zulegen. Das kostet zum Glück nicht viel. Ich empfehle Euch dafür Linode*, bei denen man für 5$ pro Monat bereits eine kleine Maschine bekommt.
 
Installation von Plotly Dash
Die Installation ist ganz einfach, da man sie über pip bzw. conda machen kann.
pip install dashbzw.conda install dash

Das erste Dashboard mit Plotly Dash
Nach dem Import von Dash und zugehörigen HTML-Funktionen wird die App erzeugt. Anschließend definieren wir das Layout der Seite, welches erstmal nur aus der H1-Überschrift „Willkommen zu Dash!“ besteht. Und schließlich wird in der Main-Routine der Server gestartet.
import dash
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.H1(children="Willkommen zu Dash!")

if __name__ == "__main__":
app.run_server(debug=True)

Lässt Du nun dieses Programm laufen, sollte die Konsole folgendes ausgeben. Damit hast Du einen flask-Server auf Deinem Rechner gestartet, der unter http://127.0.0.1:8050 über den Webbrowser erreichbar ist (nur von Deinem Rechner aus, 127.0.0.1 ist die Adresse des localhosts, also des Rechners selbst)

Und so sollte das dann im Browser aussehen:

Ist noch ziemlich unspektakulär, aber immerhin haben wir schon mal eine Webseite erzeugt. Ändert Ihr etwas im Code und speichert die Datei ab, dann wird die Webseite automatisch neu geladen. Manchmal hängt es aber ein bisschen, dann einfach im Webbrowser nochmal laden.

Grundsätzlich wird das Layout über app.layout definiert, d.h. dort können wir alle möglichen HTML-Abschnitte & -Strukturen, Charts oder Gadgets einbauen. Das wollen wir jetzt im zweiten Beispiel ausbauen. Allerdings bleibt alles erstmal ohne Funktion, denn die bauen wir erst im nächsten Abschnitt ein.
Steuerungselemente wie Dropdowns in Plotly Dash
Wir ergänzen einige Elemente aus der HTML-Library dash_html_components und einige aus der Dash Core Library dash_core_components.

Zuerst müssen wir diese also importieren. Außerdem wollen wir noch eine Grafik mittels plotly.express und Zufallszahlen mit numpy erzeugen.

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
import numpy as np

 
Anschließend schreiben wir eine Funktion hist(verteilung, n), die eine Stichprobe aus einer Verteilung zieht und daraus ein Histogramm macht. Als Parameter wird der Name einer Verteilung (normal, binomial oder chisquared ) und die Stichprobengröße n übergeben. Per np.random.xxx wird dann die Zufallsstichprobe gezogen und mit der plotly-express-Funktion px.histogram das Histogramm erzeugt.

def hist(verteilung, n):
data = None
if verteilung == "normal":
data = np.random.normal(loc=0, scale=1, size=n)
elif verteilung == "binomial":
data = np.random.binomial(n=10, p=0.5, size=n)
elif verteilung == "chisquared":
data = np.random.chisquare(df=5, size=n)
else:
print("Diese Verteilung wird noch nicht unterstützt!")
return
return px.histogram(data)

 
Nun kommt der Dash-Block, also

app = dash.Dash(__name__)

app.layout = …

if __name__ == "__main__":
app.run_server(debug=True)

Und jetzt definieren wir das Layout, welches sich wie gesagt aus dash_html_components und dash_core_components zusammensetzt. Im Prinzip ist das Layout einfach eine Liste von solchen Objekten. Wer ein bisschen HTML kennt, erkennt diese Komponenten: html.H1 bzw. html.H2 für die Überschriften, html.Div für die Abschnitte (divisions). Komplexere Element sind dcc.Dropdown (ein Dropdown), dcc.Input (Inputfeld) und dcc.Graph für die Grafik.

Die Komponenten kann mit mittels des Parameters style per CSS formatieren. Dabei wird ein dictionary mit den CSS-Einstellungen übergeben. Der Syntax ist minimal verändert, da Bindestriche hier nicht funktionieren würden. Statt CSS „background-color“ benutzt man also „backgroundColor“.

Achtung, wir haben erstmal nur das Layout definiert. Verteilungs-Dropdown und Eingabefeld für die Stichprobengröße werden zwar angezeigt, sind aber noch nicht „verdrahtet“, machen also erstmal gar nichts.

So sieht die Layout-Definition dann aus:

app.layout = html.Div(
children=[
html.H1(children="Verteilungen"),
html.Div(
children=[
html.H2("Inputs"),
html.Div(
children=[
html.P("Verteilung"),
dcc.Dropdown(
id="verteilung-dropdown",
options=[
{"label": "Normal", "value": "normal"},
{"label": "Binomial", "value": "binomial"},
{"label": "Chi²", "value": "chisquared"},
],
value="normal",
),
],
),
html.Div(
children=[
html.P("Stichprobengröße"),
dcc.Input(
id="n-input",
placeholder="Stichprobengröße",
type="number",
value=100,
),
],
),
],
# mit style kann man CSS-Formatierungen verwenden
style={
"backgroundColor": "#DDDDDD",
"maxWidth": "800px",
"padding": "10px 20px",
},
),
html.Div(
children=[
html.H2("Histogramm"),
dcc.Graph(id="histogramm", figure=hist("normal", 100)),
],
style={
"backgroundColor": "#DDDDDD",
"maxWidth": "800px",
"marginTop": "10px",
"padding": "10px 20px",
},
),
]
)

 

Und so wird unser Dashboard im Browser dargestellt:

Erste Interaktivität in unserem Plotly Dash Dashboard
So, nun wird es langsam interessant. Denn nun geht es an die Interaktivität. Diese wird über Decorators bei den entsprechenden Funktionen erreicht.

Dazu definiert man zum einen den Output, also welches Element des Layouts von der Funktion zurückgegeben wird. In unserem Fall ist es der Parameter figure der Grafik dcc.Graph. Damit wir diese identifizieren können, benutzen wir die id-Parameter aus dem Layout.

Dann definieren wir die Inputs, also bei Änderung welcher Elemente die Funtion aufgerufen werden soll. Da das mehrere sein können, wird das als Liste realisiert. In unserem Fall ist es einmal das Verteilungsdropdown mit ID „verteilung-dropdown“ und das Input-Feld mit ID „n-input“.

Unsere Funktion hist bekommt also einfach den Decorator aufgesetzt:

@app.callback(
Output("histogramm", "figure"),
[Input("verteilung-dropdown", "value"), Input("n-input", "value")],
)
def hist(verteilung, n):
data = None
if verteilung == "normal":
data = np.random.normal(loc=0, scale=1, size=n)
elif verteilung == "binomial":
data = np.random.binomial(n=10, p=0.5, size=n)
elif verteilung == "chisquared":
data = np.random.chisquare(df=5, size=n)
return px.histogram(data)

Eine kleine Änderung machen wir noch in unserem Layout. In der dcc.Graph-Funktion definieren wir nur die id, lassen also den figure-Parameter weg. Durch unseren Decorator wird die Funktion hist sowieso beim Start aufgerufen und so vermeiden wir den zweifachen Aufruf.
dcc.Graph(id="histogramm")

Und nun können wir im Browser Dropdown und das Input-Feld nutzen. Die Grafik wird direkt aktualisiert.

Deployment von Plotly Dash in der Cloud
Nun wollen wir unser Dashboard ja nicht nur auf unserem Rechner laufen lassen. Und dass jemand anderem erstmal Python und die benötigten Libraries installiert, ist auch ziemlich umständlich. Also wollen wir das Plotly Dashboard in der Cloud hosten, damit man bequem per Browser darauf zugreifen kann. Theoretisch kann man auch den eigenen Rechner oder einen Raspberry Pi als Server missbrauchen, aber der müsste dann ja immer laufen. Da ist eine Cloud-Lösung die bessere Wahl.
Leider ist das „Ins-Internet-stellen“ (deployment genannt) nicht ganz so einfach, denn man braucht eine vernünftige Umgebung mit den benötigten Packages und einen Webserver. Der Webserver, der bei dash bzw. flask dabei ist, ist für die produktive Umgebung nicht so geeignet.
Eine schöne, kostenlose Möglichkeit für die ersten Schritte war pythonanywhere. Das funktioniert aber aktuell nicht mehr vernünftig, denn das Package numpy zickt mit dem dortigen Webserver uWSGI herum. Aber als Data Scientist auf numpy und pandas zu verzichten, geht nicht wirklich.
Eine zweite kostenlose Möglichkeit ist heroku. Dafür muss man sich aber die Heroku-Kommandozeile herunterladen und ein bisschen was von git verstehen (hier mein Blogpost über Git). Das folgt dann mal in einem separaten Tutorial.
Als dritte Möglichkeit und gute Alternative zu AWS empfehle ich Euch Linode*, wo Ihr eine virtuelle Linux-Maschine für wenig Geld bekommt.
 
So long, so good. Viel Spaß beim Entdecken der unzähligen Möglichkeiten von Plotly Dash.
Euer Holger

Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist

Wie funktioniert Reinforcement Learning? Bestärkendes Lernen erklärt

Maschinellen Lernen kann man in drei Klassen unterteilen: überwachtes Lernen (supervised learning), unüberwachtes Lernen (unsupervised learning) und bestärkendes Lernen (reinforcement learning). Diese drei Lernarten decken unterschiedliche Fragestellungen ab, auch wenn es natürlich Überschneidungen gibt. In diesem Blogartikel geht es um das reinforcement learning, aber klären wir zuerst die anderen beiden Arten.
Supervised Learning: Die populärste ML-Klasse
Supervised Learning, auf Deutsch überwachtes Lernen, fasst die Algorithmen zu einer Klasse zusammen, welche anhand eines gelabelten Datensatzes lernen. D.h. es steht ein Datensatz inklusive Ergebnisse zum Lernen zur Verfügung.

Will man zum Beispiel der Computer beibringen, zu erkennen, ob ein Hund auf einem Bild ist. Dann füttert man den Algorithmus mit vielen Bildern, auf denen mal ein Hund und mal kein Hund abgebildet ist. Zusätzlich bekommt der Algorithmus noch zu jedem Bild die Information, ob eben dort ein Hund abgebildet ist oder nicht.

Die meisten Algorithmen des maschinellen Lernens gehören zur Klasse des überwachten Lernens. Aber das Problem ist der benötigte Datensatz. Denn ML-Algorithmen, allen voran neuronale Netze, sind datenhungrig. Da sie so viele Parameter haben, die eingestellt werden müssen, brauchen sie eine enorm große Menge an Trainingsdaten. Nun muss man also einen großen Datensatz bereitstellen, der eben auch die gewünschten Ergebnisse enthält. Und das auch noch möglichst ohne Fehler, denn der Algorithmus ist immer nur so gut wie der zugrundeliegende Trainingsdatensatz.
Bei Hundebildern kann man vielleicht noch mit einigem Aufwand einen Datensatz mit 1 Millionen Bildern bereitstellen. Bei Röntgenaufnahmen inklusive korrekter Diagnose ist das deutlich aufwändiger. Solch einen Datensatz zur Verfügung zu haben, ist viel wert und meist entscheidend, ob ein ML-Algorithmus erfolgreich eingesetzt werden kann. Das wird in Zukunft ein interessanter Markt sein, in dem viel Geld steckt. Mittlerweile gibt es für viele Themen schon einige große, gelabelte Datensätze. Viele sogar kostenlos, da sie aus Forschungsprojekten entstanden sind.
Anhand des Trainingsdatensatzes dreht der Algorithmus an seinen Parametern, um eine Kostenfunktion zu minimieren. Solch eine Kostenfunktion kann in unserem Hundebild-Beispiel einfach die Anzahl falsch klassifizierter Bilder sein.
Aber gibt es nicht auch Algorithmen, die die Labels nicht benötigen? Kommen wir zur Klasse des unüberwachten Lernens.
Unsupervised Learning: Datenstrukturen erkennen
Viele Unternehmen sammeln schon seit einiger Zeit jede Menge Daten, sei es über Produktionsabläufe oder Kundenverhalten. Wäre es nicht cool, diese einfach fürs maschinelle Lernen zu verwenden? Und zwar ohne umfangreiches händisches Labeln? Hier schlägt die Stunde der unsupervised learning Algorithmen. Denn diese benötigen zwar einen Datensatz, erkennen aber selbstständig Muster und Strukturen in den Daten.
Ein typisches Beispiel ist die Ausreißer-Erkennung (anomaly detection), die unter anderem bei der Betrugserkennung in Banken eingesetzt wird. Hierbei braucht der Algorithmus nicht unbedingt die zusätzliche Information, ob es sich um einen Betrug handelt oder nicht. In den Daten steckt, ob eine Transaktion aus dem Rahmen fällt und dementsprechend als auffällig markiert werden sollte.

Weitere wichtige Anwendungsgebiete von unüberwachtem Lernen sind die Gruppierung von Kunden oder Objekten (Clustering) sowie das Feature Engineering.

Reinforcement Learning: Aktion und Belohnung
Reinforcement Learning, auf Deutsch bestärkendes Lernen, funktioniert etwas anders als die beiden anderen Maschine Learning Algorithmen-Klassen. Denn es wird nicht aus einem Datensatz gelernt, sondern durch Aktionen in einer Umgebung und darauffolgende Belohnungen oder Bestrafungen.

Bestärkendes Lernen erscheint als eine sehr natürliche Art des Lernens. Denn Kinder lernen auf diese Weise: sie machen etwas und erfahren daraufhin ein Feedback, zum Beispiel dass ihr Lachen beim Gegenüber ebenfalls ein Lachen hervorzaubert.
Reinforcement Learning kann also immer dann zum Einsatz kommen, wenn ein Algorithmus/Agent in einer Umgebung (engl. Environment) handeln kann und diese Handlungen direkt oder auch später bewertet werden können. Je später das Feedback kommt, desto schwieriger ist es natürlich für den Agenten.
Machen wir ein einfaches Beispiel und stellen uns eine selbstlernende Schiebetür vor. Diese soll sich öffnen, wenn Menschen sich nähern und schließen, wenn keine Menschen in der Nähe sind. Die Tür hat vier Zustände: geschlossen, geöffnet, wird geschlossen, wird geöffnet. Zudem ist sie mit einem Sensor ausgestattet, der jede Sekunde überprüft, ob sich Menschen in einem 2m-Bereich vor oder hinter der Tür befinden. Daraus ergeben sich also insgesamt 8 Zustände:

Im Endeffekt ist hier ein ML-Algorithmus natürlich völlig übertrieben, denn die optimale Handlung kann man mit einfachen Wenn-Dann-Regeln programmieren, aber es geht ja ums Veranschaulichen. Das Belohnungssystem ist dementsprechend auch so ausgelegt, dass die richtige Handlung 100 Punkte bringt und die falschen Handlungen 0 Punkte. Der Algorithmus kann in diesem Fall einfach alle Handlungsmöglichkeiten durchprobieren und hat so nach einigen Versuchen die gewünschte Handlung gelernt.

AlphaGo Zero: Das Paradebeispiel für bestärkendes Lernen
In der Realität kommt Reinforcement Learning für deutlich komplexere Problemstellungen als die obige Schiebetür zum Einsatz. Gerade bei Brett- oder Computerspielen, welche klare Regeln haben, funktioniert Reinforcement Learning besonders gut und konnte beachtliche Erfolge erzielen.
Paradebeispiel ist Googles/DeepMinds AlphaGo Zero, ein Go-Programm, welches der Nachfolger von AlphaGo ist und diesen vernichtend schlagen konnte. AlphaGo war das erste Computerprogramm, welches 2016 den Go-Großmeister Lee Sedol, der als einer der stärksten menschliche Spieler galt, 4-1 besiegen konnte. Das war eine Sensation, denn Go hat durch das 19×19 große Spielfeld viel mehr (Faktor 10100) mögliche Spielzüge. Dadurch fällt das Durchprobieren aller möglichen Züge (Brute-Force) aus. Zudem gibt es keine gute Heuristik, um eine Stellung zu bewerten.
Während AlphaGo zwar auch schon Neuronale Netze einsetzte, ist die Besonderheit bei AlphaGo Zero, dass letzteres nur die Spielregeln einprogrammiert bekommen hat und Go nur durch das Spielen gegen sich selbst zur Meisterschaft gebracht hat. Nach 3 Tagen Lernen konnte AlphaGo Zero dann die Version von AlphaGo schlagen, die Lee besiegt hatte. Nach 40 Tagen schlug AlphaGo Zero auch die zu der Zeit beste Version von AlphaGo namens AlphaGo Master. Wobei Tage hier eigentlich nichts aussagen, denn es geht ja um die Rechenleistung, die verbraten wurde. Und davon hat Google ja fast unbegrenzt zur Verfügung.

Lernen im Simulator
Reinforcement Learning funktioniert durch Agieren in einer Umgebung. Das geht bei Spielen wie Go, aber nicht in der Wirklichkeit. Man kann nicht einfach ein Auto mit initialen zufälligen Aktionen in den Straßenverkehr stellen und tausende Handlungen abwarten, dass es sich mal etwas vernünftiger verhält und nicht gegen das nächste Auto fährt. Oder einen teuren Roboter in eine Fertigungsstraße einbauen und sehr lange nur Ausschuss produzieren.
Daher wird zuerst in einer Simulation gelernt, in der nichts kaputt gehen kann. Das hat neben den geringeren Kosten und Risiken auch den entscheidenden Vorteil, dass das Lernen in Zeitraffer ablaufen kann und die Geschwindigkeit eigentlich nur von der Rechenpower beschränkt ist. Denn wenn wir neuronale Netze einsetzen, müssen tausende oder Millionen Parameter optimiert werden und das braucht normalerweise einen riesigen Datensatz bzw. im bestärkenden Lernen sehr viele Durchgänge.
Ist in der Simulation das Lernen ausgereizt, kann dieses Wissen genommen werden und in einen realen Roboter übertragen werden und dann in der Realität weiter verbessert werden. Dieses Vorgehen nennt man Transfer Learning, wobei der Begriff Transfer Learning weiter gefasst ist.
Die bekannteste Trainingshalle für künstliche Intelligenz mit anschaulichen Videoclips findet man bei OpenAI, die OpenAI Gym. Dafür gibt es auch zahlreiche Erweiterungen für andere Bereiche wie Recommendation Engines, Aktien-Trading, Kartenspiele und viele weitere.
Ein mathematisches Modell hinter Reinforcement Learning
Ein Basismodell hinter Reinforcement Learning ist das sogenannte Markov Entscheidungsproblem bzw. der Markov Entscheidungsprozess (engl. Markov decision process = MDP). Dieser besteht aus folgenden Zutaten:

Einer Menge von Zuständen
Einer Menge von Aktionen
Ein Aktionsmodell, welches eine Funktion ist. Diese ordnet jeder Kombination aus vorherigem Zustand, Aktion und Folgezustand eine Wahrscheinlichkeit zu.
Eine Belohnungsfunktion , welche jeder Kombination aus letztem Zustand, Aktion und aktuellem Zustand einen Wert (reelle Zahl) zuweist.
Eine Startverteilung, wiederum als Funktion . Die Funktion bestimmt also die Wahrscheinlichkeit, in einem Zustand zu starten.

Eine Strategie (engl. policy) ist dann eine Funktion , die jedem Zustand eine Aktion zuweist. Man kann das auch nicht-deterministisch formulieren, d.h. der Agent entscheidet sich in einem Zustand für eine Aktion mit einer gewissen Wahrscheinlichkeit. Dann ist pi eine Funktion . Als Lösung bzw. optimale Strategie wird eine Strategie bezeichnet, die die Summe der Belohnungen (engl. reward) maximiert. In der nicht-deterministischen Variante ist es der Erwartungswert der Summe der Belohnungen. Die Summe der Belohnungen kann man noch mit einem Diskontierungsfaktor (wie beim Zinseszins) versehen, um eine Gewichtung zwischen sofortiger und späterer Belohnung zu erreichen. Das ist zum Beispiel notwendig, wenn keine zeitliche Begrenzung vorliegt (denn dann könnte die Summe unendlich groß werden).
Dieses Modell kann man aber nicht für alle Problemstellungen verwenden, denn es hat die sogenannte Markov-Eigenschaft, dass es sich nicht erinnert. Der Zeitpunkt ist also egal, das Aktionsmodell und die Belohnungsfunktion hängen nur vom direkt vorherigen Zustand ab, nicht von dem vor zwei Zeiteinheiten oder dem bisherigen Verlauf.
Reinforcement Learning umgesetzt: Q-Learning
Der bekannteste Algorithmus des bestärkenden Lernens nennt sich Q-Learning. Man kann beweisen, dass Q-Learning für jeden endlichen Markov Entscheidungsprozess (also mit endlich vielen Zuständen und endlich vielen Handlungen) eine optimale Policy finden kann, sofern er unendlich viel Zeit dafür hätte. Aber auch in der Praxis funktioniert der Algorithmus meist ziemlich gut.
Es geht ja darum, eine Funktion zu finden, die jede Zustand-Aktion-Kombination bewertet. Haben wir diese, dann kann sich der Algorithmus in einem Zustand einfach für die Aktion entscheiden, die die höchste Bewertung hat.
Das Ganze funktioniert iterativ, d.h. zuerst wird Q zufällig oder mittels Vorwissen initialisiert und anschließend in jedem Schritt optimiert. In dem Optimierungsschritt i wird ein neuer Wert gebildet, der sich aus dem alten Wert und geschätzten Gesamtbelohnung zusammensetzt. Diese zwei werden mit der Lernrate gewichtet, also . Die Lernrate bestimmt, wie wichtig neue Information ist. Bei lernt der Agent nichts, bei werden nur die neuesten Informationen berücksichtigt. ist sinnvoll, wenn die Umgebung deterministisch ist, also kein Zufall bezüglich des nächsten Zustands im Spiel ist. In der Praxis wird also ein Wert zwischen 0 und 1 gewählt, der sich ggf. auch über die Zeit verändert.
Die Belohnung wiederum setzt sich aus der sofortigen Belohnung und der maximalen Belohnung in Zustand , noch mit einem Diskontierungsfaktor gamma versehen, zusammen. Der Diskontierungsfaktor bestimmt, wie gierig der Agent ist. Ist die jetzige Belohnung (kleiner Faktor nahe 0) oder zukünftige Belohnungen (Faktor nahe 1) wichtiger? Häufig wird auch mit einem kleinen Diskontierungsfaktor angefangen, der dann im Laufe der Zeit erhöht wird.
Die komplette Formel lautet dann:
   

 

Q-Learning und neuronale Netze
AlphaGo und all die anderen erfolgreichen Reinforcement Learning Programme basieren aber auf neuronalen Netzen. Wie kommen diese jetzt ins Spiel? Die Idee ist einfach, die Funktion Q durch ein neuronales Netz lernen zu lassen. Wir füttern es also mit dem aktuellen Zustand und ggf. weiteren Informationen der Umgebung und es spuckt die Aktion bzw. für jede Aktion eine Bewertung aus.
Das Problem mit dem ursprünglichen Algorithmus ist nämlich, dass er einfach zu viel Rechen- und Speicherkapazität benötigt, wenn die Zustandsmenge S und die Aktionsmenge A größer sind. Das neuronale Netz approximiert die Funktion Q.
Der erste bekannte Algorithmus dieser Art heißt DQN für Deep-Q-Network und wurde von DeepMind entwickelt, um dem Computer das Computer-Spielen (genauer gesagt Atari 2600). Das Original-Paper Playing Atari with Deep Reinforcement Learning von 2013 ist auf arXiv zu finden.
Mittlerweile gilt A3C (Asynchronous Advantage Actor Critic), ebenfalls aus der Schmiede von DeepMind, als überlegener Algorithmus, da er schneller und robuster ist. Dabei heißt asynchronous hier, dass mehrere Agenten parallel losgeschickt werden und so von den Erfahrungen der anderen profitieren können. Advantage meint, dass nicht die diskontierte Belohnung betrachtet wird, um Aktionen zu bewerten, sondern wieviel besser die Belohnung gegenüber der erwarteten Belohnung ist. Und schließlich bedeutet Actor Critic, dass sowohl die Policy als auch die Value Function, welche einen Zustand bewertet, benutzt werden. Auch darüber könnt ihr auf arXiv im Artikel Asynchronous Methods for Deep Reinforcement Learning von 2016 nachlesen.
Dieser Blogartikel kratzt natürlich nur an der Oberfläche der faszinierenden Machine Learning-Klasse Reinforcement Learning. Willst Du mehr darüber wissen, dann hinterlasse doch einen Kommentar oder schreib mir auf Twitter.
Seid bestärkt Freunde,
Euer Holger

Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz

Eigene Funktionen in R programmieren

R-Funktionen sind für die R-Experten ein alter Hut, aber für die Newbies unter Euch ein wichtiges Werkzeug. Ja, Funktionen sind eigentlich elementar in der R-Programmierung, schließlich handelt es sich bei R um eine funktionale Programmiersprache.
Warum soll man überhaupt eigene R-Funktionen programmieren?
Es gibt direkt mehrere Vorteile, warum Funktionen so nützlich beim Programmieren in R sind. Funktionen werden eigentlich immer dann eingesetzt, wenn man gleichen oder ähnlichen Programmcode an mehreren Stellen im Skript benutzt. Statt viele Zeilen identischen Codes zu haben, wird dieser Code in eine Funktion ausgelagert, so dass dann nur noch die Funktion aufgerufen werden muss.
Damit verringern wir nicht nur die Zeilen Code und machen das Ganze übersichtlicher, sondern es sinkt auch die Fehlerwahrscheinlichkeit, denn es gibt nur noch eine Stelle, an der Änderungen oder Bugfixes gemacht werden müssen.
Funktionen erhöhen die Übersichtlichkeit des R-Codes, insbesondere, wenn sie verständlich benannt sind. Statt sich durch x Zeilen Code zu kämpfen, steht dann im Hauptteil einer Datenanalyse vielleicht nur noch folgende paar Zeilen.
datum_von <- "01.01.2020"
datum_bis <- "30.09.2020"
daten <- daten_einlesen(datum_von, datum_bis)
ergebnisse <- daten_analysieren(daten)
daten_visualieren(ergebnisse, speichern = TRUE)

Damit ist für jeden klar, was in diesem Skript gemacht wird. Natürlich ist der vermutlich komplexe Code in den Funktionen versteckt und muss bei Fehlern genauso analysiert werden, aber die Vorteile sind klar. Und da haben wir noch gar nicht von Tools wie automatischen Unit Tests gesprochen.
Und durch die Übergabe von Parametern sind Funktionen total flexibel. Besonders komfortabel wird es, wenn ihr Standard-Werte für die Parameter definiert, so dass die Parameter nicht unbedingt angegeben werden müssen.
Ihr könnt euch auch eure nützlichsten Funktionen in ein R-Skript packen oder sogar ein eigenes R-Package bauen. Das wird dann am Anfang eines Skripts eingelesen (siehe xyz) bzw. eingebunden und schon stehen sie euch zur Verfügung.
 
Wie definiere ich eine Funktion in R?
Eine eigene Funktion zu definieren ist ganz einfach
meine_funktion <- function() {
#hier kommt der Code rein
print("meine_funktion wurde aufgerufen")
}

Der Aufruf erfolgt dann mittels meine_funktion(). Das ist natürlich eine ziemlich langweilige Funktion, denn sie hat weder Parameter noch wird etwas zurückgegeben. Von dem auszuführenden Code ganz zu schweigen 😉
Die Rückgabe von einer Variable v erfolgt mittels return(v). Dabei kann die Variable ein beliebiges R-Objekt sein, also nicht nur ein Wert, sondern auch Listen, data.frames oder sogar Funktionen. Code nach einem return-Befehl wird nicht mehr ausgeführt, sondern es direkt wird zum Ende gesprungen. Das ermöglicht es zum Beispiel, am Anfang Fehler in den Parametern abzufangen.
Nehmen wir als Beispiel die Überprüfung, ob eine Zahl eine ganze Zahl ist. Das geht leider nicht mit is.integer, denn diese Funktion überprüft nicht den Wert, sondern nur, ob es sich schon um den Typ integer handelt. Daher machen wir es so, dass wir von der gegebenen Zahl die Nachkommastellen runden (mittels round) und uns dann die Differenz zu der ursprünglichen Zahl anschauen. Ist diese 0 bzw. sehr klein, dann ist durch das Runden nichts passiert. Der Ausdruck stimmt dann.
Für den Toleranzparameter tol geben wir einen Default-Wert an, so dass dieser nicht beim Aufruf übergeben werden muss. Warum brauchen wir so einen Toleranzparameter überhaupt? Das hängt mit Rundungsungenauigkeiten zusammen. Ist x als 2/49*49 definiert, dann stimmt der direkte Vergleich zum Beispiel nicht mehr.
is.integer(3)
is.integer(3L)

x = 2/49*49
round(x)==x

istGanzeZahl <- function(zahl, tol = 0.000000001) {
return(round(zahl)-zahl < tol)
}

istGanzeZahl(3)
istGanzeZahl(3.5)

Wie oben geschrieben, beendet R beim Schlüsselwort return direkt die Funktion. Das ist ganz praktisch, um Abfragen bezüglich fehlerhafter Parameter am Anfang abzufragen. Hier ein Beispiel dazu, dass die obige Funktion istGanzeZahl nutzt.
wiederhole <- function(wort, anzahl = 1) {
if (!is.character(wort)) {
print("Bitte gib als ersten Parameter eine Zeichenkette ein")
return()
}
if (anzahl<1) {
print("Bitte gib eine Anzahl größer oder gleich 1 ein")
return()
}
if (!istGanzeZahl(anzahl)) {
print(paste("Die Anzahl",anzahl,"ist keine ganze Zahl"))
return()
}
return(rep(wort, anzahl))
}

wiederhole("Hallo")
wiederhole("Hallo",3)
wiederhole("Hallo",0)
wiederhole("Hallo",-3)
wiederhole("Hallo",3.5)

Zwei Parameter in einer R-Funktion zurückgeben
Eine Funktion in R kann immer nur ein Objekt zurückgeben. Um mehrere Variablen zurückzugeben, müssen wir diese also zu einem Objekt verbinden. Und das geht am besten mit einer Liste.
Wenn ihr zum Beispiel schon mal eine lineare Regression in R gerechnet habt, kennt ihr das schon.
n <- 100
x <- rnorm(n, 3, 4)
y <- 3 * x + rnorm(n, 0, 0.5)
l <- lm(y ~ x)

Die Ausgabe von l (also der print-Befehl) sieht zwar nicht unbedingt nach einer Liste aus, davon sollte man sich aber nicht täuschen lassen.
> print(l)

Call:
lm(formula = y ~ x)

Coefficients:
(Intercept)            x
-0.05468      3.00677

Wenn wir uns das Objekt l aber im Environment-Fenster von RStudio ansehen, dann steht dort Liste mit 12 Einträgen.

Das können wir auch leicht mit is.list(l) überprüfen. Mit names(l) bekommen wir die Namen der Elemente, auf die wir dann mit der eckigen Doppelklammer oder dem Dollarzeichen zugreifen können.
is.list(l)
names(l)
l[["coefficients"]]
l$coefficients

 
Warum sieht die Ausgabe mittels print dann so gar nicht nach Liste aus? Das liegt aber daran, dass die Programmierer von lm der Ausgabe die Klasse „lm“ gegeben hat, welche eine eigene print-Funktion besitzt und die Standard-Print-Funktion der Liste überschreibt. Abfragen kann man den Typ eines Objekts übrigens mittels class.
Wie gibt man mehrere Parameter in einer Funktion zurück?
Dazu bauen wir uns einfach in der Funktion eine Liste und geben diese mittels return zurück. Die folgende Funktion erzeugt einen data.frame und berechnet dann dazu eine lineare Regression. Zurückgegeben wird sowohl der data.frame als auch das lm-Objekt, also eine Liste bestehend aus data.frame und wiederum einer Liste (bzw. ein Objekt der Klasse lm).
erzeuge_lm <- function(n = 100, seed = NA) {
if (!is.na(seed)) set.seed(seed)
df <- data.frame(x = rnorm(n, 3, 4))
df$y <- 3 * x + rnorm(n, 0, 0.5)
l <- lm(y ~ x, df)
liste <- list(daten = df, lm = l)
return(liste)
}

a <- erzeuge_lm()
a$daten
a$lm

Der Phantasie sind hierbei kaum Grenzen gesetzt. Wir könnten jetzt noch sämtliche verwendeten Parameter als Parameter übergeben. Oder per Parameter steuern, aus welcher Verteilung die Daten stammen. Das führt uns zum nächsten Kapitel, denn Verteilungen haben unterschiedlich viele Parameter. Die t-Verteilung hat nur einen Freiheitsgrad, die Normalverteilung hat zwei.
Beliebige Anzahl Parameter in R-Funktionen mittels …
Um die Parameter bei der Funktionsdefinition nicht explizit anzugeben, kann man auch … verwenden. Das ist praktisch, wenn man in der Funktion wieder eine Funktion aufruft und ihr diese Parameter übergeben will.
Greifen wir das letzte Beispiel nochmal auf und definieren uns eine Funktion, die als Parameter die Stichprobengröße und den Namen der Verteilung bekommt und anschließend folgen die Parameter für die Verteilung. Also bei der Normalverteilung Mittelwert und Standardabweichung, bei der t- oder χ²-Verteilung die Freiheitsgrade.
erzeuge_daten <- function(n = 100, verteilung = "normal", ...) {
if (verteilung == "normal") x = rnorm(n, ...)
else if (verteilung == "t") x = rt(n, ...)
else if (verteilung == "chi2") x = rchisq(n, ...)
else {
print("Aktuell werden nur Normal-, t- und Chi²-Verteilung unterstützt")
return()
}
return(x)
}

erzeuge_daten(n = 10, verteilung = "normal", 3, 1)
erzeuge_daten(n = 10, verteilung = "normal", 3)
erzeuge_daten(n = 10, verteilung = "t")

Wollen wir ein bisschen mehr Kontrolle haben, wann welcher Parameter eingesetzt wird, können wir das mit zwei Punkten und der Position angeben, also ..1 für den ersten Parameter, ..2 für den zweiten Parameter usw.
erzeuge_daten <- function(n = 100, verteilung = "normal", ...) {
if (verteilung == "normal") x = rnorm(n, ..1, ..2)
else if (verteilung == "t") x = rt(n, ..1)
else if (verteilung == "chi2") x = rchisq(n, ..1)
else {
print("Aktuell werden nur Normal-, t- und Chi²-Verteilung unterstützt")
return()
}
return(x)
}
erzeuge_daten(n = 10, verteilung = "normal", 3, 1)
erzeuge_daten(n = 10, verteilung = "normal", 3)
erzeuge_daten(n = 10, verteilung = "t")

Machen wir das so, sehen wir beim Aufruf, dass die Default-Werte kaputt gegangen sind, d.h. für die Normalverteilung muss man nun zwei Parameter angeben.
Achtung: Der Einsatz von … in R-Funktionen sollte mit Bedacht gewählt sein. Denn nicht oder falsch benannte Parameter erzeugen keine Fehlermeldung.
Wir können natürlich fordern, dass die zusätzlichen Parameter benannt sind. Man muss sich allerdings selber darum kümmern und das artet dann in einige Zeilen Code mit unschönen if-Bedingungen aus. Die Länge bzw. Namen der Parameter bekommt man , indem man … in eine Liste umwandelt, wie das im folgenden Beispiel in der ersten Zeile der Funktion passiert.
erzeuge_daten <- function(n = 100, verteilung = "normal", ...) {
args = list(...)
if (verteilung == "normal") {
if (length(args)==0 |
(length(args) <= 2 & ("mean" %in% names(args) | "sd" %in% names(args)))) {
x = rnorm(n, ...)
}
else {
print("Für die Normalverteilung gibt es die optionalen Parameter mean und sd")
return()
}
}
else if (verteilung == "t") {
if (length(args) == 1 & "df" %in% names(args)) {
x = rt(n, ...)
}
else {
print("Für die t-Verteilung muss der Parameter df angegeben werden")
return()
}
}
else if (verteilung == "chi2") {
if (length(args) == 1 & "df" %in% names(args)) {
x = rchisq(n, ...)
}
else {
print("Für die Chi²-Verteilung muss der Parameter df angegeben werden")
return()
}
}
else {
print("Aktuell werden nur Normal-, t- und Chi²-Verteilung unterstützt")
return()
}
return(x)
}
erzeuge_daten(n = 10, verteilung = "normal", 3, 1)
erzeuge_daten(n = 10, verteilung = "normal", mean = 3)
erzeuge_daten(n = 10, verteilung = "t", df = 4)

Noch da? Bei dem ganzen Aufwand ist schon die Frage, ob man nicht einfach die Parameter explizit angibt. Dabei Default-Werte für die Parameter vergeben, dann müssen nur die benötigten angegeben werden.
erzeuge_daten <- function(n = 100, verteilung = "normal", mean = 0, sd = 1, df = 3) {
if (verteilung == "normal") x = rnorm(n, mean, sd)
else if (verteilung == "t") x = rt(n, df)
else if (verteilung == "chi2") x = rchisq(n, df)
else {
print("Aktuell werden nur Normal-, t- und Chi²-Verteilung unterstützt")
return()
}
return(x)
}

Oder ihr macht es ganz anders und gestaltet die Funktion ganz allgemein, indem man ihr als ersten Parameter die Zufalls-Erzeugungsfunktion (ist das ein Wort?) übergibt. Dann ist es nämlich nur ein Einzeiler:
erzeuge_daten <- function(zufallsfunktion, ...) {
return(do.call(zufallsfunktion, list(...)))
}
erzeuge_daten(rnorm, n=10, mean = 3)

Rekursive Funktionen in R
Als rekursiv bezeichnen wir eine Funktion, wenn sie sich selbst wieder aufruft. Dabei sollte natürlich irgendein Parameter gesetzt werden, so dass wir nicht in einer Endlos-Schleife gefangen sind.
Dieses Konstrukt ist ganz elegant (obwohl nicht immer mit der besten Performance), wenn man mathematische Folgen nachbauen möchte, die selber schon rekursiv sind.
Ein Beispiel ist die Fakultätsfunktion n!, die alle natürlichen Zahlen bis zu n miteinander multipliziert. Würde man (n-1)! kennen, müsste man es nur noch mit n multiplizieren und hätte das Ergebnis
n! = 1 * 2 * … * n = (n-1)! * n
Zusätzlich ist 0! = 1 definiert. Die Umsetzung in R ist kein großes Problem.
fac <- function(n) {
if(n == 0 | n ==1) return(1)
else return(fac(n-1))
}

Ein weiteres beliebtes Beispiel sind die Fibonacci-Zahlen. Die n-te Fibonacci-Zahl ist definiert als die Summe der beiden Vorgänger. Zudem sind die ersten beiden Fibonacci-Zahlen als 1 definiert.
fibo <- function(n) {
if(n == 1 | n == 2) return(1)
else return(fibo(n-1) + fibo(n-2))
}

Hier sehen wir schon, dass das zwar elegant, aber nicht sonderlich effizient ist, denn die beiden Vorgänger werden einzeln berechnet. In der Praxis sind rekursive Funktionen auch eher die Ausnahme.
Operatoren in R definieren
Ok, jetzt habt ihr es bis hierhin geschafft. Dann gibt es als Belohnung noch eine Möglichkeit, Funktionen zu definieren. Und zwar kennt ihr vermutlich Operatoren, die zwischen zwei Objekten stehen. Also zum Beispiel 5 %in% 1:5, wobei geprüft wird, ob der erste Wert in dem zweiten Vektor enthalten ist. Solche Operatoren können wir auch selber definieren. Vielleicht nervt euch ja auch das Verbinden von Zeichenketten mittels paste. Wir könnten ja einen Operator definieren, der das für uns erledigt. Das geht ganz einfach, wenn man weiß wie. Dafür muss nämlich der Operator zum einen mit % anfangen und enden, zum anderen muss er bei der Definition in schräge Hochkommata eingepackt werden. Unser String-Verkettungs-Operator sieht dann folgendermaßen aus:
`%+%` <- function(str1, str2) {
return(paste0(str1, str2))
}
"Hans" %+% "wurst" %+% " ist da!"

Aber Achtung: Viele eigene Operatoren können das Nachvollziehen für jemand anderen erschweren, da man sich quasi eine ganz eigene Syntax bastelt.
Ein R-Skript mit nützlichen Funktionen einbinden
Habt ihr eine Sammlung eigener Funktionen angelegt, von denen ihr die eine oder andere in fast jedem Skript braucht. Dann habt ihr zwei Möglichkeiten, diese einzulesen. Am einfachsten geht es mit dem source-Befehl. Dazu einfach am Anfang eines Skripts source(„Meine_Funktionen.R“) schreiben und fertig!
Ihr könnt natürlich auch direkt ein Package daraus machen. Das ist auch nicht so schwer, wie es sich anhört, aber das heben wir uns für einen anderen Artikel auf.
Happy functioning, Euer Holger

Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean

Python-Tutorial für ein Kommandozeilen-Tool mit argparse

Möchtest Du ein Kommandozeilen-Tool (CLI = command line interface) in Python bauen? Mir ging es vor kurzem so. Ich hatte einige nützliche Funktionen geschrieben und die IT von einem Kunden wollte diese Funktionen direkt von der Kommandozeile aus nutzen. Die Funktionen haben natürlich Parameter, die übergeben werden müssen.

Inhalt

Was ist ein „command line interface“ (CLI)?Arten von ParameternWie erstellt man ein command line interface (CLI) in Python?Der manuelle WegWürfelsimulation mit dem Python-Package argparseVerschiedene Parametertypen in argparseBenannte Parameter in CLIFlags mit argparseKomplexere Kommandozeilen mit mehreren Befehlen Und was gibt es sonst noch
Was ist ein „command line interface“ (CLI)?

Jeder, der schon mal mit der Eingabeaufforderung (Command Prompt oder Shell) gearbeitet hat, sei es unter Windows oder in einer Linux-Shell, kennt es. Nach dem Programm folgen noch ein oder mehrere Parameter, z.B. ls –l, cd .. oder conda install pandas. Für Linux-User, die sowieso viel in einer Shell unterwegs sind, braucht man den Nutzen gar nicht zu erklären. Für viele Windows-User ist es ungewohnt, sind sie doch eher mit der Maus unterwegs. Als Python-Programmierer kommt man aber sowieso nicht drum herum, schließlich benötigen wir Environments und müssen Packages installieren (entweder über virtualenv und pip oder mittels conda).

Prinzipiell sind der Komplexität von CLIs keine Grenzen gesetzt. Auch verschachtelte Menüs sind denkbar, durch die der Nutzer navigieren kann. Dabei stellt sich allerdings die Frage, ob eine grafische Benutzeroberfläche nicht besser geeignet ist. Auf der anderen Seite ist eine textbasierte Benutzeroberfläche nötig, wenn es sich um einen Cloud-Rechner handelt, auf den man nur per Terminal zugreift. So gibt es CLIs für Azure und AWS.

Als besonders praktisch haben sich aber solche Kommandozeilen-Tools herausgestellt, die direkt alle Optionen in ihrem Aufruf abdecken. Die meisten Betriebssystem-Befehle funktionieren so, auch viele Programmier-Tools wie git, pip oder conda. Der Vorteil gegenüber einem Benutzermenü ist, dass solche Programme in Skripten (Batch- bzw. Bash), aus anderen Programmiersprachen oder von einer GUI aufgerufen werden können.

Arten von Parametern
Man kann drei Arten von Parametern unterscheiden:

positional: hier ist die Position entscheidend, also an welcher Stelle der Parameter steht.
optional: Die häufigste Art, der Parameter wird über ein Schlüsselwort (mit – oder — davor) sowie den Wert angegeben. Dadurch ist die Reihenfolge unwichtig. Meist wird — mit dem vollständigen Parameternamen und alternativ – mit der Abkürzung verwendet. Zum Beispiel der Namensparameter beim Erzeugen eines Environments in conda: conda create –name myenv conda create -n myenv. Interessant bei conda ist, dass das erste Argument create positional ist. Das liegt daran, dass conda im Prinzip viele Befehle in ein einziges CLI packt. Der erste Parameter wählt also wie in einem Menü aus, welcher Befehl dran ist, erst danach folgen die Parameter zu dem Befehl. Wie man diese Art von CLIs programmiert, erkläre ich im Kapitel Komplexere Kommandozeilen-Tools.
flag: Das sind Parameter, die nur ja/nein bzw. true/false als Ausprägungen haben. Insofern ist es meist so realisiert, dass die Abwesenheit des Flags FALSE bedeutet und die Anwesenheit TRUE. In conda zum Beispiel die Parameter –all und –y in conda update –all -y, um alle Packages upzudaten bzw. die Nachfrage im Skript zu überspringen.

Wer coole Tricks zu Linux-CLIs sucht, schaut mal auf Twitter bei @climagic vorbei.

Wie erstellt man ein command line interface (CLI) in Python?
Ein Kommandozeilen-Interface in Python zu bauen ist überhaupt nicht schwer. Ein bisschen hängt es natürlich davon ab, welche Komplexität man benötigt. Aber für (fast) jeden Anwendungsfall gibt es ein Package, welches einem die Arbeit erleichtert.
Wir wollen uns den Programmaufruf mit Parametern ansehen. Wie oben geschrieben ist das ein ganz typischer Fall, denn er erlaubt uns, unsere Python-Programme flexibel aufzurufen oder in Batch-Skripten zu nutzen.
Fangen wir einfach an. Wir wollen einen Würfel simulieren (Im Blog gibt es übrigens ein Würfelsimulations-Tutorial) und übergeben als Parameter die Anzahl Würfe.
Der manuelle Weg
Die Parameter beim Aufruf des Programms heißen übrigens arguments und sind in fast allen Programmiersprachen über eine Variable, die mit arg beginnt, abzugreifen. In Python ist es eine Liste mit Namen argv aus der Standardbibliothek sys.
Mit den folgenden zwei Zeilen Python-Code geben wir also einfach die Parameter aus:
import sysprint(sys.argv)
Wenn wir dieses Mini-Programm unter miniargs.py abspeichern und aufrufen, wird eine Liste mit den Parametern ausgegeben. Beachte, dass der erste Eintrag (Index 0) das Programm selber ist.
Nun geht es an die Würfelsimulation. Dazu definieren wir die Funktion wuerfeln mittels dem Zufallsgenerator von Numpy. Dann folgt die Abfrage, ob es einen Parameter gibt (die Länge muss also zwei sein, da als erster Eintrag das Python-Skript selber übergeben wird) und ob dieser Parameter eine ganze Zahl ist. Sind die Bedingungen erfüllt, wird die wuerfel-Funktion aufgerufen und ausgegeben, andernfalls kommt die Fehlermeldung.
import numpy as npimport sysdef wuerfeln(wuerfe=1, seiten=6): return np.random.choice(np.arange(1, seiten + 1), size=wuerfe)if len(sys.argv) == 2 and sys.argv[1].isdigit():    wuerfe = int(sys.argv[1])    print(wuerfeln(wuerfe))else:    print("Bitte gib die Anzahl Würfe als Parameter an, z.B. python wuerfel 3")
Das war ja schon ganz nett, aber damit wir gerade bei komplexeren Abfragen nicht alles manuell machen müssen, gibt es das tolle Package argparse.
Würfelsimulation mit dem Python-Package argparse
Das Python-Package argparse ist wirklich sehr bequem, um Kommandozeilen-Tools zu bauen. Die Installation erfolgt wie gewohnt über pip oder conda, je nachdem was ihr nutzt, also
pip install argparse bzw. conda install argparse
Und dann können wir mit dem Würfelbau loslegen!
Natürlich brauchen wir wieder unsere wuerfel-Funktion. Dann definieren wir den Parser und fügen ein int-Parameter, nämlich die Anzahl Würfe hinzu. In den letzten zwei Zeilen passiert dann das eigentliche Aufdröseln der Parameter (ok, hier gibt es nicht viel zu tun) und der Aufruf der wuerfel-Funktion.
Praktischerweise wird auch direkt eine Hilfe-Funktion, allerdings auf Englisch, mitgeliefert.
import argparseimport numpy as npdef wuerfeln(wuerfe=1, seiten=6):    return np.random.choice(np.arange(1, seiten + 1), size=wuerfe)parser = argparse.ArgumentParser(description="Würfelsimulator")parser.add_argument("wuerfe", type=int, help="Anzahl der Würfe")args = parser.parse_args()print(wuerfeln(args.wuerfe))
Auf einen Parameter, den ihr mit add_argument(„parametername“, …) hinzugefügt habt, könnt ihr mit args.parametername zugreifen.

Verschiedene Parametertypen in argparse
Es gibt ja mehrere Arten von Parametern, was ich oben in Was ist ein CLI? beschrieben habe. Wir haben bisher nur positionale Parameter gebaut. Flexibler ist man mit Optionen, da dann die Reihenfolge keine Rolle mehr spielt. Dafür brauchen sie einen Namen. Das zeige ich euch jetzt. Im Anschluss kommen noch Flags in Spiel, also ein Schalter.
Benannte Parameter in CLI
Gestalten wir das vorherige Beispiel um, so dass wir zwei Parameter übergeben, die Anzahl Würfe und die Seitenanzahl des Würfels. Und das setzen wir mit Optionen um. Der Aufruf soll also z.B. so aussehen:
python wuerfeln.py --wuerfe 3 --seiten 6
Meist gibt es auch Abkürzungen, die dann nur mit einem Minus geschrieben werden:
python wuerfeln.py -w 3 -s 6
Das ist ganz einfach, wir müssen nur die eine add_argument-Zeile anpassen und eine zweite hinzufügen. Dabei fügen wir direkt noch jeweils einen Default-Wert hinzu, so dass der Parameter nicht angegeben werden muss.

parser.add_argument("--wuerfe", "-w", type=int, default=1, help="Anzahl der Würfe")
parser.add_argument("--seiten", "-s", type=int, default=6, help="Seitenanzahl des Würfels")

Statt einer beliebigen Seitenzahl wollen wir vielleicht nur bestimmte Würfel zulassen. Dazu gibt es den Parameter choices, den wir einfach einfügen können und damit die zugelassene Würfel auf W6, W12 und W20 einschränken:

parser.add_argument(    "--seiten", "-s", type=int, choices=[6, 12, 20], default=6, help="Seitenanzahl des Würfels")
Schon gibt es eine Fehlermeldung wenn wir eine andere Zahl benutzen: error: argument –seiten/-s: invalid choice: 3 (choose from 6, 12, 20)
Flags mit argparse
Flags sind super einfach zu implementieren. Man muss nur einen Parameter mit add_argument hinzufügen, der mit action=store_true oder store_false initialisiert wird.
Also ans Werk: Wir bauen ein Flag –schummeln ein, welches die Wahrscheinlichkeit für die höchste Punktzahl verdoppelt. Also bei einem 6-seitigen Würfel würde die 6 die Wahrscheinlichkeit 1/3 statt 1/6 bekommen und die verbleibenden 5 Seiten entsprechend weniger, d.h. jede Seite (1- 1/3) * 1/5 2/15. Das Komplizierteste an diesem Beispiel ist, das für beliebige Seitenanzahlen umzusetzen.
Wir schreiben also zuerst die wuerfel-Funktion um und ergänzen dort den Schummel-Parameter. Die choose-Funktion von numpy erlaubt die Angabe eines Vektors mit den Wahrscheinlichkeiten, den müssen wir also zuerst konstruieren.

def wuerfeln(wuerfe=1, seiten=6, schummeln=False):
    if schummeln:
        w_max = 2 / seiten
        w_normal = (1 - w_max) / (1 - seiten)
        p = ([w_normal] * (seiten - 1)).append(w_max)
        return np.random.choice(np.arange(1, seiten + 1), size=wuerfe, p=p)
    else:
        return np.random.choice(np.arange(1, seiten + 1), size=wuerfe)
Ist das geschafft, ergänzen wir den Parameter in der Parser-Definiton, unter den Parameters für Anzahl Würfe und Seiten

parser.add_argument("--schummeln", "-sch", action="store_true", help="Schummel-Flag")
Komplexere Kommandozeilen mit mehreren Befehlen
Stellt euch vor, wir haben mehrere Befehle, die wir aber mit nur einer CLI aufrufen wollen. Dazu geben wir im ersten Argument den Befehl an, die Optionen zum entsprechenden Befehl folgend dann danach. So ist das z.B. bei conda, da gibt es conda activate, conda install packagename, conda create –name envname usw.

Solch eine komplexe Struktur ist mit argparse ziemlich leicht und bequem umzusetzen. Wir wollen unser Beispiel von oben erweitern, indem wir neben Würfeln auch noch Münzwurf und Lotto simulieren. Beim Lotto soll man seine Zahlen angeben müssen.
Wir wollen also, dass das CLI namens glueck.py folgendes kann:

python glueck.py wuerfeln -w 3 -s 6 -schummeln (wobei die Parameter wuerfe und seiten optional sind, da sie default-Werte haben und schummeln ein flag ist)
python glueck.py muenze -w 10 (wobei der Parameter wuerfe einen default-Wert hat)
python glueck.py lotto --zahlen 10 13 19 20 33 49 –superzahl 3

Zuerst definieren wir uns die eigentlichen Funktionen, die aufgerufen werden sollen:
import argparseimport numpy as npdef wuerfeln(wuerfe=1, seiten=6, schummeln=False):    if schummeln:        w_max = 2 / seiten        w_normal = (1 - w_max) / (1 - seiten)        p = ([w_normal] * (seiten - 1)).append(w_max)        return np.random.choice(np.arange(1, seiten + 1), size=wuerfe, p=p)    else:        return np.random.choice(np.arange(1, seiten + 1), size=wuerfe)def muenze(wuerfe=2):    return np.random.choice(["W", "Z"], size=wuerfe)def lotto(zahlen, superzahl):    gezogen = np.random.choice(np.arange(1, 50), size=6)    gezogen_sz = np.random.choice(np.arange(0, 10), size=1)    anzahl_richtige = np.sum(zahlen == gezogen)    nicht_sz = "" if superzahl == gezogen_sz else "nicht "    ziehung_text = "Ziehung: " + str(gezogen) + ", Superzahl: " + str(gezogen_sz)    print(ziehung_text)    return "Anzahl Richtige: " + str(anzahl_richtige) + ", Superzahl stimmt " + nicht_sz + "überein"
Nun bauen wir die CLI zusammen. Dazu definieren wir uns für jeden Befehl (also den ersten Parameter) eine Funktion, der die Argumente args übergeben werden. Prinzipiell hätten wir auch in diesen Funktionen die eigentlichen Funktionen reinschreiben können, aber durch die Entkoppelung ist es zum einen übersichtlicher, zum anderen können die Funktionen oben auch innerhalb von Python genutzt werden.
def wuerfeln_fct(args):    print(wuerfeln(args.wuerfe, args.seiten, args.schummeln))def muenze_fct(args):    print(muenze(args.wuerfe))def lotto_fct(args):    print(lotto(args.zahlen, args.superzahl))
Jetzt folgt noch die eigentliche Definition des ArgumentParsers. Dazu fügen wir Subparser hinzu, für jeden Befehl einen. Diesen Subparsern geben wir die jeweiligen Argumente mit.  Beim lotto-Parser haben wir noch eine Besonderheit, nämlich nargs, das angibt, ob ein Parameter mehrfach vorhanden sein darf/muss, in unserem Fall sollen es genau 6 Zahlen sein. Neben Zahlen funktionieren auch “*” für beliebig viele Argumente, “+” für mindestens ein Argument und noch ein paar andere Möglichkeiten. Schaut doch mal in die Dokumentation von argparse, was es alles für Parameter von add_argument gibt.
parser = argparse.ArgumentParser(description="Glücksspiel")subparsers = parser.add_subparsers()# wuerfeln-Befehleparser_wuerfeln = subparsers.add_parser("wuerfeln", help="Simulation eines Würfels")parser_wuerfeln.add_argument("--wuerfe", "-w", type=int, default=1, help="Anzahl der Würfe")parser_wuerfeln.add_argument(    "--seiten", "-s", type=int, choices=[6, 12, 20], default=6, help="Seitenanzahl des Würfels")parser_wuerfeln.add_argument("--schummeln", "-sch", action="store_true", help="Schummel-Flag")parser_wuerfeln.set_defaults(func=wuerfeln_fct)# muenze-Befehlparser_muenze = subparsers.add_parser("muenze", help="Simulation von Münzwürfen")parser_muenze.add_argument("--wuerfe", "-w", type=int, default=1, help="Anzahl der Würfe")parser_muenze.set_defaults(func=muenze_fct)# lotto-Befehlparser_lotto = subparsers.add_parser("lotto", help="Simulation von Lotto 6 aus 49")parser_lotto.add_argument(    "--zahlen", "-z", type=int, nargs=6, choices=np.arange(1, 50), required=True, help="Deine 6 Zahlen")parser_lotto.add_argument(    "--superzahl", "-sz", type=int, choices=np.arange(0, 10), required=True, help="Superzahl")parser_lotto.set_defaults(func=lotto_fct)args = parser.parse_args()try:    args.func(args)except AttributeError:    parser.print_help()    parser.exit()
Noch eine kleine Besonderheit in den letzten Zeilen. Statt einfach nur args.func(args) zu verwenden, habe ich es in einen try-except-Block geschoben. Hintergrund ist, dass die Funktion einen Fehler ausgibt, wenn gar kein Argument übergeben wird, also einfach nur python glueck.py aufgerufen würde. Dann ist es aber für den Nutzer schöner, wenn die Hilfe angezeigt wird.
 Und was gibt es sonst noch
Habt Ihr immer noch nicht genug und wollt CLI-Experten werden. Ok, dann habe ich Euch noch ein paar Links zum Weiterlesen zusammengesucht. Es gibt auch einige Alternativen zu argparse, die Euch vielleicht besser gefallen bzw. andere Möglichkeiten haben:

Das Manual zu argparse
Ein Artikel über CLI Best Practices von Cody A. Ray auf Medium (auch wenn ich Medium sonst nicht mag).
Click ist eine Alternative zu argparse mit einer etwas anderen Herangehensweise. Der Parser wird nämlich über decorators definiert.
Wollt ihr interaktive Text-Menüs? Dann schaut Euch mal den PyInquirer an, der dieses coole Feature ermöglicht.
Clint ist ein Package, welches Farben, Einrückungen etc. unterstützt.

So, das war’s jetzt aber. Ich hoffe, Ihr konntet die Beispiele gut nachvollziehen und brennt nun darauf, selber ein Kommandozeilen-Tool in Python zu bauen.
Happy coding,Euer Holger
 
Photo by Dan LeFebvre on Unsplash

Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean

Datentypen in R – einfach erklärt (Teil 2)

So, liebe R-Braineos, hier kommt der zweite Teil zu Datentypen in R. Nachdem wir in Teil 1 die Basis-Datentypen von R erledigt haben, geht es nun datum, diese zu kombinieren.

Inhalt

Zusammengesetzte Datentypen in RMehrere Elemente eines Datentyps – Der Datentyp VECTOR in RMehrere Elemente verschiedener Datentypen – Der Datentyp LIST in RTabellen – Der Datentyp DATA.FRAME in RSpezielle Datentypen in RFolge von kategoriellen Variablen – Der Datentyp FACTORMatrizenrechnung – Der Datentyp MATRIX
Zusammengesetzte Datentypen in R
Die Power von R liegt nämlich nicht in den Basisdatentypen, sondern darin, dass fast alles auf Vektoren aufbaut. Vector und data.frame (Tabellen) sind die Objekte, die ich als Data Scientist ständig gebrauche. Aber auch list ist nicht zu verachten.
Mehrere Elemente eines Datentyps – Der Datentyp VECTOR in R
Vektoren sind eine durchnummerierte Kette von Objekten eines Datentyps. Wichtig: Es geht nur ein Datentyp pro Vektor, man kann nicht bunt mischen (dafür benötigt man dann Listen). Und in R ist jeder der Basisdatentypen automatisch ein Vektor, das erkennt man an der 1 in eckigen Klammern vor dem Output. Eckige Klammern sind das Stichwort, denn so können wir die einzelnen Elemente ansprechen. Die Funktion zum Verbinden von Elementen zu einem Vektor heißt einfach c (für combine). Mit length bekommt man die Länge des Vektors zurück.
# Einen Vektor x mit drei Elementen erzeugen
x = c(3,4,5)
# Die Klasse eines Vektors ist der zugrundeliegende Datentyp, hier numeric
class(x)
# length gibt die Anzahl Elemente des Vektors zurücl
length(x)
# die Elemente spricht man über Indizes in den eckigen Klammern an
x[1]
# das erste und dritte Element
x[c(1,3)]

Operationen und Vergleiche werden für jedes Element durchgeführt. Und hier kommt die Power von R ins Spiel, die manchmal leider auch schwierig sein kann. Es geht um die vektorielle Auswertung. Anstatt zum Beispiel eine Schleife zu programmieren, mit der jedes Element von x um 1 erhöht wird, schreibt man einfach x+1. Solche Vektoroperationen haben übrigens auch einen enormen Geschwindigkeitsvorteil und sollten (fast) immer einer Schleife vorgezogen werden.
# Operationen wie Addition wird auf jedes Element angewandt
y <- x + 1
y
# auch Vergleiche sind Operationen und werden auf jedes Element angewandt
z = y > 4
class(z)
# Auswahl über einen boolschen Vektor der gleichen Länge
y[z]
# Das geht natürlich auch direkt, ohne eine andere Variable zu erzeugen
y[y > 4]

Vektoren lassen sich von jedem Grunddatentyp erzeugen. Im nächsten Beispiel ist der Vektor a vom Typ character. Negative Indizes entfernen die entsprechende Stelle. a[-1] entspricht also a[2:length(a)]. Mit dem Doppelpunkt erzeugt man übrigens einen integer-Vektor mit alles ganzen Zahlen zwischen den beiden angegeben Zahlen, also 1:3 entspricht also c(1L, 2L, 3L).
a = c("H","a","l","l","o")
class(a)
a[1]
# Der Doppelpunkt erzeugt einen Vektor von ganzen Zahlen
1:3
a[1:3]
a[c(1,2,3)]
# Ein negativer Wert entfernt das Element aus dem Vektor
a[-1]
a[-3:-1]
length(a)
 
# nchar wird auf jedes Element angewandt
b = c("Ha","llo")
nchar(b)
# paste verbindet mehrere Strings
paste(a, collapse ="")

# implizite Umwandlung von numeric in character, damit der Vektor nur einen Datentyp hat
x = c("Hallo",1L, 3.14)
# Kombination von zwei Vektoren zu einem
z = c(x,y)

Mehrere Elemente verschiedener Datentypen – Der Datentyp LIST in R
Vektoren sind super, haben aber die Einschränkung, dass es sich immer um einen Datentyp handeln muss. Wollen wir verschiedene Datentypen in einem Objekt, dann benötigen wir eine Liste.
Auch Listen sind in R ganz einfach und schnell erstellt. Sie sind aber bei weitem nicht so performant wie Vektoren. Wenn es also möglich ist, sollten Vektoren verwendet werden. Dafür sind Listen aber soooo flexibel. Listen können Vektoren, wiederum Listen und eben jedes Objekt in R enthalten.
Die Elemente einer Liste lassen sich wie bei Vektoren über den Index ansprechen, wir brauchen hier aber doppelte eckige Klammern. Die Anzahl Elemente bekommt man wie bei Vektoren über die Funktion length.
l = list(1L, 1.34, "Hallo", 1:3)
# l ist vom Typ list
class(l)
is.list(l)
length(l)
l[[1]]
class(l[[1]])
l[[4]][1]

Eine super nützliche Sache ist das Benennen der Elemente einer Liste mit der Funktion names. Dazu einfach einen Character-Vektor der gleichen Länge übergeben. Dann lassen sich die Elemente per Namen, besonders praktisch in der $-Notation, ansprechen. Der Name eines Elements lässt sich auch einfach ändern, denn die Namen sind nur ein Vektor.
names(l) <- c("Tick","Tack","Toe","Foo")
l[["Tick"]]
l$Tick
# Benennung der Elemente direkt beim Erzeugen
l2 <- list(pi=3.14, euler=2.718, Toe="Hallo", Foo=1:3)
l2$Foo
names(l)[3] <- "Tock"
names(l)

Listen lassen sich wie Vektoren mit c einfach zusammenfügen. Beim Löschen gibt es zwei Varianten. Willst Du wirklich ein Element in der Liste löschen, also die Liste verändern, dann setzt man das Element auf NULL. Willst Du die Liste ohne ein Element übergeben, also die Liste selber nicht verändern, dann geht es wie bei Vektoren mit einem negativen Index.
l3 <- c(l, l2)
# Löschen eines Elements, Liste wird dadurch verändert
l3[[1]] <- NULL
# Element wird nicht mit übergeben, l3 aber nicht verändert
l4 <- l3[-1]

Tabellen – Der Datentyp DATA.FRAME in R
Data.frames sind ein Muss für alle Datenanalyen. Im Endeffekt sind data.frames Tabellen, bei der jede Spalte von einem Datentyp ist. Stellt euch die Erfassung einer Befragung vor. Jede Zeile entspricht einem Befragten, die Spalten sind die einzelnen Fragen.
Einen data.frame erzeugt man, indem man die einzelnen Spalten der Funktion data.frame übergibt. Die Anzahl Spalten bzw. Zeilen bekommt man mit ncol bzw. nrow oder direkt beide mit dim.
df = data.frame(x = rnorm(10),
y = sample(c("Rot","Gelb","Grün"), 10, replace = TRUE),
z = 1L:10L)
class(df)
ncol(df)
nrow(df)
dim(df)

Der Zugriff auf die einzelnen Elemente oder Spalten bzw. Zeilen ist eine Mischung aus Vektor und Liste. Ein Element des data.frames bekommt man über Zeilen- und Spaltenangabe in eckigen Klammern. Lässt man einen der beiden Indizes weg (das Komma aber nicht), dann bekommt man die ganze Zeile bzw. Spalte. Die Spalten lassen sich wie bei Listen praktisch über den Spaltennamen ansprechen.
df[1,3]
df[1,]
df[,1]
df$x
df[,"x"]
df[1,1:2]
class(df$x)

# Filterung der Zeilen anhand einer Bedingung
df[df$y=="Gelb",]
# Mittelwert der Spalte x, wenn auf Gelb gefiltert wurde
mean(df$x[df$y=="Gelb"])

Die Spaltennamen lassen sich über colnames ansprechen. names wie bei Listen geht auch, aber colnames ist eindeutiger, es gibt auch rownames. Eine neue Spalte kann man einfach anfügen, indem man diese direkt benennt.
colnames(df)[1] <- "a"
colnames(df)
df$b <- rbinom(nrow(df),20,0.4)
df$b <- NULL

Spezielle Datentypen in R
Folge von kategoriellen Variablen – Der Datentyp FACTOR
R wurde für Datenauswertungen konzipiert. Und wer ein wenig Statistikerfahrung hat, weiß, dass Merkmale unterschiedliche Skalenniveaus haben können, also nominal, ordinal oder kardinal. Haben Daten ein nominales Skalenniveau, können also nur benannt werden wie z.B. Lieblingsfarbe, dann gibt es dafür in R den Datentyp factor.
f <- factor(c("Rot","Gelb","Rot","Blau","Blau","Blau"))
f
class(f)
is.factor(f)
length(f)
levels(f)
levels(f) <- c("Rot","Gelb","Blau","Grün")
f

Für ordinale Skalenniveaus, deren Ausprägungen geordnet werden können, kann das auch mit angegeben werden. Wir erhalten dann ein Objekt, was zwei Klassen angehört, nämlich factor und ordered.
auspraegungen <- c("nie","selten","oft","sehr oft")
of <- factor(c(1,1,2,2,4,4,3),labels = auspraegungen, ordered = TRUE)
of
class(of)
table(of)

# Mittelwert gibt es bei Faktoren nicht
mean(of)
# Median ist eigentlich bei ordinalen Skalen erlaubt
# in R funktioniert median nicht, man kann aber das 50%-Quantil verwenden
median(of)
quantile(of, .5, type=1)

Matrizenrechnung – Der Datentyp MATRIX
In R gibt es einen extra Datentyp Matrix. Im Prinzip wie ein data.frame, nur dass alle Elemente von einem Datentyp sein müssen. Besser ist daher der Vergleich mit einem Vektor, der zusätzlich Angaben zu den Dimensionen hat (so ist es auch in R realisiert).
Eine Matrix initialisiert man also durch Angabe eines Vektors und einer Spalten- oder Zeilenanzahl. Die Elemente des Vektors werden dann spaltenweise verwendet. Alternativ setzt man byrow auf TRUE, dann wird der Vektor zeilenweise verwendet.
Der Zugriff auf einzelne Elemente, Spalten und Zeilen erfolgt über einen Doppelindex in eckigen Klammern.
M = matrix(c(1,2,3,4,5,6),nrow=2)
class(M)
ncol(M)
nrow(M)
dim(M)
M
M[1,2]
M[1,]
M[,2]
M = matrix(c(1,2,3,4,5,6),nrow=2, byrow = TRUE)
M

Quadratische Matrizen können miteinander multizipliert werden. Achtung, dabei muss man zwischen der elementweisen Multiplikation * und der echten mathematische Matrixmultiplikation %*% unterscheiden. Die elementweise Multiplikation multipliziert jeden Eintrag der einen Matrix mit dem entsprechenden Eintrag der zweiten Matrix. M*M entspricht also dem quadrieren jedes Eintrags.
M = matrix(1:9,nrow=3, byrow = TRUE)
M
M * M
M %*% M
diag(M)

So, damit ist auch der zweite Teil geschafft und ihr seid nun R-Datentypen-Experten. Wenn ihr mit den Erklärungen und Beispielen etwas anfangen konntet, dann helft mir auch, indem ihr den Beitrag teilt. Danke!
Happy Coding,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median

Datentypen in R – einfach erklärt

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.

Inhalt

Was sind überhaupt Datentypen?Der Zuweisungsoperator <- oder =Einfache DatentypenGanze Zahl – Der Datentyp INTEGERKommazahlen – Der Datentyp NUMERICWahr oder Falsch – Der Datentyp LOGICALWörter / Zeichenketten – Der Datentyp CHARACTERDatum und Zeitstempel – Die Datentypen DATE und POSIXCT
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
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median

Lineare Regression in Python

Hallo liebe Brainies,
ich erkläre euch hier, was lineare Regression ist und wie ihr lineare Regression in Python umsetzen könnt. Natürlich liefere ich den Python-Code direkt mit, so dass ihr diesen direkt übernehmen könnt.
Lineare Regression ist den meisten vermutlich schon einmal begegnet. Grundsätzlich geht es darum, eine Variable Y durch eine oder mehrere andere Variablen X1, X2, …, Xn zu bestimmen bzw. vorherzusagen. Die zu bestimmende Variable Y nennt man auch Regressant, Kriterium oder einfach abhängige Variable (weil sie von den X abhängt), die für die Vorhersage verwendeten Variablen nennt man Prädiktoren oder Regressoren.
Die Abhängigkeit zwischen dem Regressant Y und den Prädiktoren X wird als linear angenommen. Y sollte metrisch sein, also so etwas wie Alter, Gewicht, Größe, Preis usw. Für Kategoriale Variablen wie Geschlecht, Lieblingsfarbe oder ja/nein-Fragen eignet sich die lineare Regression nicht, da kommt dann zum Beispiel die logistische Regression ins Spiel.
Es gibt noch viele Varianten der Regression, zum Beispiel in denen Polynome höheren Grads statt Geraden (das sind übrigens Polynome 1.Grads) verwendet werden.

Inhalt

Was ist lineare Regression?Das Bestimmtheitsmaß R²Multiple lineare RegressionWas sind die Voraussetzungen für lineare Regression?Lineare Beziehung zwischen den VariablenWenig MulitkolinearitätHomoskedastizität der ResiduenNormalverteilung der ResiduenRegression mit dem Package scikit-learnBeispielcode für scikit-learn Linear RegressionCodebeispiel Lineare Regression mit mehreren Variablen
Was ist lineare Regression?
Fangen wir einfach an. Nehmen wir erstmal nur eine Prädiktor-Variable X. Der Zusammenhang zwischen X und Y wird als linear angenommen. Haben wir nun einen Datensatz mit Beobachtungen, dann wollen wir eine Gerade durch die Punkte (xi, yi) legen. Und zwar so, dass die Gerade möglichst nahe an den Punkten liegt. D.h. die Summe der Abweichungen zwischen Gerade und den Punkten soll minimiert werden. Nun benutzt man nicht die einfache Abweichung, sondern die quadrierte, weil eine solche Minimierung statistisch schönere Eigenschaften hat. Man spricht daher von einem Kleinste-Quadrate-Schätzer (engl. OLS = ordinary-least-square).
Man kennt vielleicht noch die Geradengleichung aus der Schule: g(x) = m*x + c, wobei m die Steigung und c der y-Achsen-Abschnitt ist.
Bei der linearen Regression sieht es ganz ähnlich aus:
Y ~ b1*X + b0 + ε
Hier schreibt man kein Gleichheitszeichen hin, weil X und Y Zufallsvariablen sind. Man sagt dann, dass Y verteilt ist wie b1*X + b0 + ε. Die bs heißen Regressionskoeffizienten. Im Vergleich zur Geradengleichung kommt noch der Term ε (= epsilon) dazu. Damit wird der Störterm oder Fehler bezeichnet, weil die Punkte ja nicht direkt auf der Geraden liegen.
Haben wir nun eine Stichprobe aus Paaren (xi,yi), man spricht auch von Realisierung der Zufallsvariablen, dann gilt yi = b1*xi + b0 + εi.
Nun versucht man, b1 und b0 so zu bestimmen, dass die Summe der quadrierten Fehler minimal wird. Zum Glück müssen wir das nicht per Hand machen, sondern lassen den Computer rechnen.
Das Bestimmtheitsmaß R²
Für die Güte der linearen Regression benutzt man das Bestimmtheitsmaß R². Das liegt zwischen 0 (= überhaupt kein linearer Zusammenhang) und 1 (= perfekter linearer Zusammenhang). Hast Du einen negativen Wert oder etwas über 1 raus, dann ist es garantiert falsch.
R² kann aber auch trügerisch sein, wie Du in den Beispielen sehen wirst. Da bekommen wir ein hohes R², aber trotzdem ist das Modell eigentlich falsch, weil der Zusammenhang nicht linear ist.

Im ersten Bild ist ein linearer Zusammenhang zu sehen. Das Bestimmtheitsmaß R² ist mit 0.91 sehr hoch und zeigt, dass x und y stark zusammenhängen. Im zweiten Bild besteht ein quadratischer Zusammenhang von x und y, d.h. die Punkte (x,y) liegen auf einer Parabeln. Die lineare Regression malt trotzdem einfach eine Gerade durch die Punkte. Damit unterschätzen wir die y-Werte für kleine und große x, für mittlere x wird y überschätzt. Trotzdem ist das Bestimmtheitsmaß mit 0.94 sogar noch höher.
Multiple lineare Regression
Meistens hat man nicht nur einen Prädiktor X, sondern eine ganze Reihe davon. Das Prinzip bleibt aber gleich. Hat man zwei Prädiktoren X1 und X2, dann legt man eine Ebene durch die Punkte (x1i, x2i, yi) und bestimmt die Regressionskoeffizienten b0, b1, b2 mit der Gleichung yi = b0 + b1*x1i + b2 * x2i + εi.
Allgemein schreibt man Y ~ B*X + ε.
Was sind die Voraussetzungen für lineare Regression?
Damit die lineare Regression vernünftige Ergebnisse liefert, müssen einige Voraussetzungen erfüllt sein. Natürlich kann man immer eine Gerade so gut wie möglich durch eine Punktwolke legen, aber man möchte ja auch einige Statistiken wie das Bestimmtheitsmaß haben oder vielleicht einen Hypothesentest machen, der prüft, ob die Regressionskoeffizienten ungleich 0 sind.
Lineare Beziehung zwischen den Variablen
Das haben wir schon weiter oben gesehen. Y sollte linear von X abhängen, sonst macht das ganze Modell keinen Sinn.
Wenig Mulitkolinearität
Haben wir mehr als einen Prädiktor, dann sollten die Prädiktoren nicht zu stark miteinander zusammenhängen. Das Problem ist nämlich sonst, das die Regressionskoeffizienten nicht eindeutig sind.
Homoskedastizität der Residuen
Na, auf Anhieb richtig ausgesprochen? Kann ich mir kaum vorstellen. Der Zungenbrecher Homoskedastizität der Residuen bedeutet einfach nur, dass die Varianz des Fehlers ε überall gleich ist. Es sollte nicht sein, dass kleine Werte von y auch eine kleinere Fehlerstreuung bedeutet und große Werte von y dann mit deutlich größerer Fehlerstreuung einhergeht, oder umgekehrt. Im Bild sieht man es, wenn die Breite der Punktwolke (xi,yi) überall ungefähr gleich ist.
Normalverteilung der Residuen
Die Fehler εi sollten in etwa normalverteilt sein. Sind sie es, dann ist übrigens auch die Homoskedastizität erfüllt, da die Fehler alle aus der gleichen N(0, σ²)-Verteilung stammen. Auch große Ausreißer, auf die die Regression sensibel reagiert, sollten dann nur sehr selten auftreten.
Regression mit dem Package scikit-learn
Es gibt natürlich verschiedene Möglichkeiten, die lineare Regression in Python umzusetzen. Eine Möglichkeit ist mit dem Package scikit-learn gegeben. Das Tolle an scikit-learn ist, dass in dem Package auch noch jede Menge weiterer Algorithmen implementiert sind, die alle genauso funktionieren.
Scikit-learn setzt auf numpy auf, d.h. Daten sollten als Arrays vorliegen und auch Ausgabewerte sind im numpy-Format, wie wir nachher sehen werden
Mit der Funktion LinearRegression wird zuerst ein Modell erzeugt, das dann mit fit an die Daten angepasst wird. Dabei muss der Prädiktor x als zweidimensionales Array vorliegen (auch wenn x in dem Beispiel eigentlich eindimensional ist), daher müssen wir x noch mit reshape in die benötigte Form bringen. In dem meisten Fällen ist x ja sowieso mehrdimensional, weil man eine multiple Regression macht.

import numpy as np
from sklearn.linear_model import LinearRegression

x = np.array([1, 2, 3, 4])
x = x.reshape(-1, 1)

y = np.array([2, 5, 7, 9])

model = LinearRegression()
model.fit(x, y)

Nun, bisher sieht man noch nichts. Die Regressions-Koeffizienten wurden aber durch fit berechnet, sie sind im Modell gespeichert und zwar in den Variablen intercept_ und coef_.
print("y-Achsen-Abschnitt:", model.intercept_)
print("Steigung:", model.coef_)
print("R²:", model.score(x, y))

Achtung, coef_ ist hier ein Array mit einer Zahl drin, während intercept_ nur eine Zahl ist. Auch hier sieht man wieder, dass der Algorithmus auf mehrere Prädiktoren X ausgelegt ist.
print(type(model.intercept_))
# <class 'numpy.float64'>
print(type(model.coef_))
# <class 'numpy.ndarray'>

Beispielcode für scikit-learn Linear Regression
Im folgenden Codebeispiel habe ich die Erzeugung von x und y angepasst. Zudem gibt es am Ende eine kleine Grafik mit den Punkten und der Regressionsgerade. Den gesamten Code könnt ihr euch auch hier herunterladen: linreg.py
Zuerst ziehen wir x aus einer Normalverteilung mit Mittelwert mu1 und Standardabweichung sigma1. Anschließend erzeugen wir y genau so, wie es laut Modell von x abhängt:
yi = b0 + b1 * xi + εi, wobei εi aus einer Normalverteilung mit Mittelwert 0 und Standardabweichung sigmaError gezogen wird.
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt

# Stichprobengröße
n = 100

# ziehe x aus Normalverteilung
mu1 = 10
sigma1 = 3
x = np.random.normal(loc=mu1, scale=sigma1, size=n)

# erzeuge y
b1 = 2
b0 = 5
sigmaError = 2
y = b1 * x + b0 + np.random.normal(loc=0.0, scale=sigmaError, size=n)
formelText = (
"y = "
+ str(b1)
+ "*x+"
+ str(b0)
+ "+ ε mit ε~N(0,"
+ str(sigmaError * sigmaError)
+ ")"
)

# für die lineare Regression aus sklearn muss x eine Spalte sein
x = x.reshape((-1, 1))

# berechne lineare Regression
model = LinearRegression()
model.fit(x, y)

r_sq = model.score(x, y)
intercept = model.intercept_
slope = model.coef_
print("intercept:", intercept)
print("slope:", slope)
print("coefficient of determination:", r_sq)

# plot
plt.scatter(x, y, alpha=0.5)
plt.title(formelText)
plt.xlabel("x")
plt.ylabel("y")
t = (min(x), max(x))
plt.plot(t, model.predict(t), "-r")
ax = plt.gca()
plt.text(
0.95,
0.05,
"R² = " + str(round(r_sq, 2)),
horizontalalignment="right",
verticalalignment="center",
transform=ax.transAxes,
)
plt.show()

Die Grafik habe ich hier über matplotlib erzeugt. Eine Punktwolke entspricht dem Scatterplot plt.scatter. Ergänzt habe ich Titel und Achsenbeschriftungen. Dann folgt die Regressionsgerade, für die ich zuerst das Minimum und Maximum von x nehme und für diese beiden Werte die y-Werte der Regressionsgerade berechne (mit model.predict). Als letztes folgt noch ein Text mit dem Bestimmtheitsmaß R². Damit dieser unten rechts in der Grafik erscheint, musste ich mit ax die Achsen auf Prozent konvertieren.
Statt des linearen Zusammenhangs können wir y auch anders erzeugen, zum Beispiel als quadratischer Zusammenhang. So können wir schauen, wie die lineare Regression auf Verletzung der Grundvoraussetzung „linearer Zusammenhang“ reagiert.
# alternativ quadratischer Zusammenhang zwischen x und y
x = np.random.uniform(-1, 3, n)
sigmaError = 1
y = 3 * x * x + 1 + np.random.normal(loc=0.0, scale=sigmaError, size=n)
formelText = "y = 3*x² + 1" + "+ ε mit ε~N(0," + str(sigmaError * sigmaError) + ")"

Eurer Phantasie sind hier keine Grenzen gesetzt. Hier noch ein Beispiel für einen Wurzel-Zusammenhang.
# alternativ Wurzel-Zusammenhang zwischen x und y
x = np.random.uniform(-1, 200, n)
sigmaError = 0.5
y = np.sqrt(3 * x + 5) + np.random.normal(loc=0.0, scale=sigmaError, size=n)
formelText = "y = sqrt(3*x+5) " + "+ ε mit ε~N(0," + str(sigmaError * sigmaError) + ")"

Codebeispiel Lineare Regression mit mehreren Variablen
Kommen wir zu einem realistischeren Datensatz. Hier habe ich den Fish Market Datensatz von Kaggle heruntergeladen. Dieser kleine Datensatz mit 159 Datenpunkten besteht aus Gewicht, mehreren Größe-Messungen und Art. Einlesen geht ganz einfach mit pandas read_csv.
Machen wir zuerst nochmal eine einfach Regression, also mit einer Variablen (Python-Code: linreg2.py). Interessant ist das Bild, bei dem man schön sehen kann, dass der Zusammenhang nicht wirklich linear ist.

import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt

fish = pd.read_csv("fish.csv")
print(fish.shape)
print(fish.columns)

x = pd.DataFrame(fish["Length1"])
y = pd.DataFrame(fish["Weight"])

model = LinearRegression()
model.fit(x, y)

intercept = model.intercept_[0]
slope = model.coef_[0, 0]
r_sq = model.score(x, y)
print("intercept:", intercept)
print("slope:", slope)
print("coefficient of determination:", r_sq)

# Plot
plt.scatter(fish["Length1"], fish["Weight"], alpha=0.7)
plt.title("Fisch Markt")
plt.xlabel("Länge1")
plt.ylabel("Gewicht")
# Regressionsgerade hinzufügen
t = np.array([min(x.loc[:, "Length1"]), max(x.loc[:, "Length1"])])
t = t.reshape(-1, 1)
plt.plot(t, model.predict(t), "-r")
plt.show()

Wollen wir nun das Gewicht anhand der Größen vorhersagen, dann müssen wir einfach nur das x anpassen und ein Array aus mehreren Attributen basteln. Hier der Code linreg3.py als Download
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.transforms as mtransforms

fish = pd.read_csv("fish.csv")
print(fish.shape)
print(fish.columns)

x = fish[["Length1", "Length2", "Length3", "Height"]]
y = pd.DataFrame(fish["Weight"])

model = LinearRegression()
model.fit(x, y)

intercept = model.intercept_[0]
slope = model.coef_[0, 0]
r_sq = model.score(x, y)
print("intercept:", intercept)
print("slope:", slope)
print("coefficient of determination:", r_sq)

yhat = model.predict(x)
yhat[yhat < 0] = 0

# Plot
plt.scatter(fish["Weight"], yhat, alpha=0.7)
plt.title("Fisch Markt")
plt.xlabel("Gewicht")
plt.ylabel("Prognose")
plt.xlim(0, 1700)
plt.ylim(0, 1700)
ax = plt.gca()
line = mlines.Line2D([0, 1], [0, 1], color="red")
transform = ax.transAxes
line.set_transform(transform)
ax.add_line(line)
plt.show()

Bei mehr als 3 Dimensionen können wir nicht mehr als Grafik darstellen. Was man aber machen kann, ist ein Plot mit den tatsächlichen Y-Werten auf der einen Achse und den vorhergesagten Y-Werten auf der anderen Achse. Bei einem linearen Zusammenhang streuen die Punkte gleichmäßig um die Winkelhalbierende.

Hier sehen wir gut, dass die Form der Punktwolke eher an eine Wurzelfunktion erinnert. Das heißt, kleinere Gewichte werden eher überschätzt, da die Punkte über der Winkelhalbierenden sind. Größere Gewichte hingegen werden eher unterschätzt, weil die Punkte sich unter der Winkelhalbierenden befinden.
So, ich hoffe, ihr habt ein bisschen was über einen der Brot- und Butter-Algorithmen jedes Data Scientisten gelernt.
Happy coding,
Euer Holger
 
Titelfoto by Anders Jildén on Unsplash
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median

Geld verdienen als Data Scientist-Freelancer – Die Freelancer-Börse Malt.de im Interview

Hej meine Braineos,
heute mal etwas ganz anderes als meine Data Science-Tutorials. Es geht um’s Geld verdienen. Der eine oder andere von Euch hat sicherlich schon mal daran gedacht, seine Fähigkeiten als Freelancer anzubieten. Aber wie kommt man an interessante Projekte? Zum einen natürlich über das eigene Netzwerk. Aber wenn man frisch in die Selbstständigkeit startet, dann hat man das noch nicht. Dann sind Freelance-Börsen genau das richtige. Die Freelance-Plattform Malt.de ist schon in Frankreich und Spanien erfolgreich und ist nun 2019 in Deutschland gestartet.
Ich konnte David von Malt.de ein paar Fragen stellen, die Euch hoffentlich genauso interessieren wie mich.
 
Was ist Malt.de und warum lohnt es sich, mich dort anzumelden?
Malt bringt Freelancer und Unternehmen jeder Größe zusammen und ermöglicht ihnen eine direkte Zusammenarbeit über die Plattform – ohne Agentur oder Personalvermittlung.
Alles was Unternehmen und Freelancer für ein konkretes Angebot brauchen ist ein Malt-Profil, das innerhalb von Minuten erstellt ist. Die Anmeldung und Nutzung der Plattform ist für beide Seiten komplett kostenlos. Erst wenn eine Zusammenarbeit zustande kommt, erhält Malt eine Provision. Man kann sich also komplett risikofrei anmelden. Ein aussagekräftiges Profil ist dabei das A und O. Dafür fällt eine aufwändige Kundenakquise weg. Auf Malt kontaktieren die Unternehmen die Freelancer. Über die intelligente Suchfunktion können Unternehmen nach spezifischen Fähigkeiten filtern und geeignete Freelancer direkt kontaktieren, um ein Projekt vorzuschlagen. Der Freelancer schreibt daraufhin ein Angebot, das das Unternehmen annehmen oder ablehnen kann. Nach Abschluss jedes Projektes erhalten die Freelancer eine Referenz des Auftraggebers, sodass sie sukzessive ihre Reputation steigern können. Die Bezahlung des Freelancers erfolgt dann innerhalb von 48 Stunden nach Projektabschluss.
 
Wie unterscheidet sich Malt.de von anderen Freelancer-Börsen?
Auf Malt werden keine Projekte von Unternehmen veröffentlicht. Daher sehen wir uns auch nicht als Börse, sondern eher als Marktplatz. Der Freelancer präsentiert sich auf Malt mit einem aussagekräftigen Profil und die Unternehmen suchen anhand der Profile nach dem für sie passenden Experten. Diesen Suchprozess halten wir für beide Seiten für zielgerichteter.
Zusätzlich gehen wir noch einen Schritt weiter und ermöglichen Freelancern und Unternehmen eine Zusammenarbeit über unsere Plattform. Hier beginnt unser eigentlicher Service. Unser Marktplatz unterstützt beide Seiten bei den administrativen Aufgaben, die eine Zusammenarbeit mit sich bringt. Akquise, Angebot, Vertragsabschluss sowie Zahlungsabwicklung können komplett digitalisiert über die Tools von Malt abgewickelt werden. Unternehmen können damit ihre kompletten Freelancer-Aktivitäten über Malt steuern und verwalten.  Zusätzlich sind die Freelancer auf Malt automatisch über eine Berufshaftpflicht versichert.
Der wichtigste Punkt ist für uns aber der direkte Ansatz. Unternehmen und Freelancer gehen ein direktes Vertragsverhältnis ein. Viele Probleme von klassischen Personalvermittlungen wie Scheinselbständigkeit gegenüber dem Provider und illegale Arbeitnehmerüberlassung werden dadurch weitgehend vermieden.  Als Plattform stellen wir mit unseren Tools nur sicher, dass auf beiden Seiten so wenig Bürokratie wie möglich entsteht.
 
Welche Voraussetzungen muss ich erfüllen, um mich als Freelancer anzumelden? Kann ich das neben meinem Beruf machen?
Wenn es der aktuelle Arbeitsvertrag erlaubt – klar 😉 Von unserer Seite ist für die Anmeldung als Freelancer nur eine Steuernummer notwendig. Die kann beim Finanzamt relativ einfach beantragt werden.
 
Wieviel Provision nimmt Malt.de?
Für Freelancer berechnet Malt eine Provision von 10%, die automatisch in das Angebot des Freelancers einbezogen wird und bei Rechnungsstellung vom Auftragshonorar abgezogen wird. Für die laufende Zusammenarbeit mit einem Kunden wird nach dem dritten Monat nur noch 5% Provision berechnet. Die gestaffelte Provision wird dafür eingesetzt, die Freelancer bei der Akquise von attraktiven Kunden zu unterstützen und die Geschäftsbeziehung zu den Unternehmen abzusichern. Die Provision können Freelancer dann im Regelfall als Werbungskosten von der Steuer absetzen.
Für Unternehmen bewegen sich die Kosten für die Plattform zwischen 5 – 12% vom Projektvolumen, je nachdem welcher Leistungsumfang gewünscht ist. Die Standardfunktionen sind bereits für 5% verfügbar, für 7 – 12% gibt es dann weitere Service-Dienstleistungen wie z.B. einen dedizierten Account-Manager, Zugriff auf Malt Insights, ein umfangreiches Freelancer Management System, Unterstützung bei der Erstellung von eigenen Verträgen  oder die Möglichkeit zu eigenen Zahlungsfristen. Alle Details dazu findet ihr hier: https://www.malt.de/offers
 
Welche Fähigkeiten im Bereich Data Science / Data Analytics sind bei den Kunden besonders beliebt?
Von den Suchanfragen der Kunden auf Malt ausgehend, sind Python und C++ an der Spitze, wobei Python relevanter für den Bereich Data Analytics ist. Weit oben bei den Tools rankt Excel, R, Apache Spark, d3 und Tableau für Datenvisualisierungen, MATLAB, Hadoop und SAS. Außerdem Tensorflow und Pandas als Software-Bibliotheke für Python Datenmanipulationen und -analysen.
 
Wird eher nach Tagessatz oder Projekt abgerechnet? Welchen Tagessatz kann man als Data Scientist nehmen? 
Im Malt-Profil wird standardmäßig ein unverbindlicher Tagessatz hinterlegt. Allerdings kann auch eine Abrechnung nach Projekt individuell mit dem Kunden vereinbart werden. Die Tagessätze für Data Scientists bewegen sich meistens zwischen 700 und 1.200€ je nach Erfahrung und Umfang der Kenntnisse.
 
Sind die Aufträge meistens beim Kunden vor Ort oder remote?
Wir verfolgen einen lokalen Ansatz, bei dem Kunden die Möglichkeiten haben sollen Freelancer in Ihrer Nähe zu finden und persönlich zu treffen. Denn je nach Projektinhalt ist oft eine Arbeit vor Ort erwünscht, andere Projekte erlauben es wiederum remote zu arbeiten. Es ist also definitiv beides möglich. Hier wollen wir beiden Parteien möglichst viele Freiheiten lassen, um die Zusammenarbeit so zu gestalten, wie es für beide passt. Freelancer können dazu in ihrem Profil angeben, welche Form der Zusammenarbeit bevorzugt wird und ob sie remote oder vor Ort (und wo) zur Verfügung stehen. Unternehmen können ihre entsprechende Anforderung dann bei der Suche filtern.
Vielen Dank für das Interview, David.
 
So liebe Braineos, also worauf wartet Ihr? Meldet Euch direkt mal an und teilt das Interview mit anderen potentiellen Freiberuflern.
Happy freelancing,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow

Datenimport in Python: Excel-Dateien einlesen und schreiben

Hej Leute,
wir wollen Daten analysen, oder? Sonst wärt ihr ja nicht hier! Dazu müssen aber erstmal die Daten in Python importiert werden. Neben Datenbanken sind Dateien das Mittel zum Zweck. Und im Data Science Umfeld sind die Dateien zum großen Teil im Format csv (comma separated value), aber erstaunlich oft eben auch im Excel-Format (xlsx), denn in fast allen Abteilungen einer Firma ist Excel immer noch die Software, mit der Tabellen bearbeitet werden.
Ich zeige euch, wie ihr Excel in Python einlesen könnt. Das ist mit dem richtigen Package überhaupt nicht schwer.

Inhalt

Python: Excel einlesenxlsx-Dateien mit pandas importierenMehrere Excel-Arbeitsblätter gleichzeitig importierenExcel-Datei importieren mit dem Package xlrdPython-Package openpyxl: Excel-Dateien einlesenPython: Excel abspeichernEinen DataFrame mit pandas in eine xlsx-Datei schreibenMehrere Arbeitsblätter in eine Excel-Datei schreibenEine Excel-Datei mit xlwt schreibenEine Excel-Datei mit openpyxl schreibenFazit zu Excel-Dateien mit Python
Python: Excel einlesen
Ihr habt die Qual der Wahl. Es gibt nämlich einige Möglichkeiten, xlsx-Dateien nach Python zu bringen. Wollt ihr nur schnell Daten aus einer Excel-Datei holen, dann ist pandas wohl am besten geeignet. Braucht ihr noch mehr Kontrolle oder wollt Formeln auslesen, dann schaut euch xlrd oder openpyxl an. Tatsächlich nutzt pandas xlrd im Hintergrund, um auf die Excel-Daten zuzugreifen.
Eine Jupyter Notebook-Datei mit dem Code für alle drei Möglichkeiten findest Du hier.
xlsx-Dateien mit pandas importieren
Eine populäre Methode, Excel-Dateien in Python einzulesen, ist mit der read_excel-Funktion von pandas. Wenn man als Data Scientist mit Python arbeitet, ist pandas sowieso unverzichtbar. Die read_excel-Funktion kann sowohl xls- (altes Format) als auch xlsx-Dateien verarbeiten und schreibt den Inhalt in ein DataFrame.
import pandas as pd
df = pd.read_excel(“Dateiname“)

dazu gibt es eine ganze Reihe von Parametern, die wichtigsten findet ihr hier:

sheet_name = 0
Hier kann man eine Zahl (beginnend bei 0) oder den Namen des Arbeitsblatts. Standardmäßig wird also das erste Arbeitsblatt eingelesen. Gibt man hier eine Liste an, dann wird ein Dictionary von DataFrames zurückgegeben (siehe unten)
skiprows = None
Will man die ersten Zeilen überspringen, was ja relativ typisch für Excel-Tabellen ist, denn oben sollte hoffentlich Titel und Beschreibung stehen, geht das mit diesem Parameter, der einfach sagt, wie viele Zeilen man überspringen will. Wie in Python üblich, fängt das Zählen bei 0 an, d.h. wenn ich ab der 4. Zeile einlesen will, benutze ich skiprows=3.
header = 0
Die Zeile, in der der Header, also die Spaltenüberschriften steht. Bei 0 wird also die erste Zeile verwendet. Gibt es keine Spaltenüberschriften, gibt man None an.
names = None
Diesem Parameter kann man eine Liste mit den Spaltennamen übergeben
usecols = None
Hier gibt man an, dass man nur ausgewählte Spalten einlesen möchte. Dafür übergibt man am besten eine Liste mit Spaltennummern oder Spaltennamen.
dtype = None
Möchte man andere Datentypen als die automatisch erkannten verwenden, kann man ein dictionary mit den Datentypen der Spalten angeben.
nrows = None
Möchte man nicht alle Zeilen einlesen, kann man das mit dem Parameter nrows begrenzen.

 
Das sollte die meisten Anwendungsfälle abdecken. Braucht ihr noch weitere Spezialisierungen, dann schaut in die API Referenz von read_excel.
Mehrere Excel-Arbeitsblätter gleichzeitig importieren
Wie bei der Erklärung zum Parameter sheet_name geschrieben, kann man dort auch eine Liste mit den Arbeitsblatt-Nummern oder –Namen angeben. Dann wird kein DataFrame zurückgegeben, sondern ein Dictionary von DataFrames.
Mappe = pd.read_excel("Beispiel-Excel.xlsx", sheet_name=['Tabelle1','Tabelle2','Tabelle3'], skiprows=3)
print(type(Mappe))
print(Mappe.keys())
print(Mappe['Tabelle3'].head())

Excel-Datei importieren mit dem Package xlrd
Mit dem Package xlrd habt ihr deutlich mehr Kontrolle über die Excel-Arbeitsmappe, aber es ist natürlich auch ein bisschen aufwändiger. Tatsächlich wird aber xlrd aktuell nicht mehr weiterentwickelt. Auf der GitHub-Seite steht, dass man doch besser openpyxl verwenden soll.
Neben xlrd zum Lesen gibt es auch xlwt zum Schreiben von xls-Dateien (Excel 97 – 2003) und xlutils, wenn man Excel-Dateien modifizieren will.
Leicht ist erstmal das Öffnen der Datei
wb = xlrd.open_workbook("Beispiel-Excel.xlsx")
Mit sheet_names bekommt man die Namen der Arbeitsblätter
sheet_names = wb.sheet_names()
Ein Arbeitsblatt kann man dann über den Namen ansprechen, also z.B.
Tab1 = wb.sheet_by_name('Tabelle1')
Die xlrd API ist sehr logisch aufgebaut und sollte für Leute, die schon mal APIs gelesen haben, kein Problem darstellen.

Das Modul book beinhaltet alles für den Umgang mit der gesamten Arbeitsmappe. Die Klasse xlrd.book.Book enthält den Inhalt der Arbeitsmappe.
Das Modul sheet beinhaltet alles für den Umgang mit einem Arbeitsblatt. Die zugehörige Klasse xlrd.sheet.Sheet enthält den Inhalt eines Arbeitsblatts
formatting ist noch erwähnenswert, wenn man die Formatierungen auslesen will
formula befasst sich (Achtung) nicht mit den Formeln einer Zelle, sondern mit dem Namensmanager. Will man die Formeln einzelner Zellen auslesen, geht das mit openpyxl oder
xldate hilft beim Umgang mit Datum und Zeit

Python-Package openpyxl: Excel-Dateien einlesen
Openpyxl scheint aktuell das Package zu sein, welches man nutzen sollte, will man mehr machen als Daten einlesen. Im letzteren Fall ist meiner Meinung nach pandas zu bevorzugen.
Eine Arbeitsmappe einzulesen ist auch hier super einfach.
from openpyxl import load_workbook
wb2 = load_workbook("Beispiel-Excel.xlsx")

Mit dem Flag data_only kann man einstellen, ob Formeln oder die berechneten Werte eingelesen werden sollen. Standardmäßig ist data_only = False, es werden also Formeln eingelesen.
Leider werden Bilder und Charts nicht mit eingelesen, so dass Modifikationen einer Vorlage oder das Einfüllen von Daten in bestehende Excel-Dashboards mit openpyxl nicht vernünftig funktionieren. Dafür scheint xlwings das richtige Tool zu sein, dazu demnächst mehr.
Aber zurück zu openpyxl. Die Arbeitsmappe enthält die Arbeitblätter, auf die dann einfach per Namen zurückgegriffen werden kann
print(wb2.sheetnames)
ws = wb2['Tabelle1']
for i in ws.values:
print(i)

Um die Daten in ein pandas DataFrame umzuwandeln, könnte man es folgendermaßen machen. In dem Beispiel fängt die Tabelle in Zeile 4 an.
import pandas as pd
df = pd.DataFrame(ws.values)
df.columns = df.iloc[[3]].values.tolist()[0]
df = df[4:]
df.head()

Auch hier wieder der Verweis auf die Dokumentation von openpyxl. Interessant ist auch das Kapitel 12 im kostenlosen Buch Automate the boring stuff, dort geht es tatsächlich um Anwendung vom Package openpyxl.
 
Python: Excel abspeichern
Logisch, neben dem Einlesen von Excel-Dateien wollen wir natürlich auch Excel-Dateien abspeichern können. Eine Jupyter Notebook-Datei mit dem Code für alle drei Möglichkeiten findest Du hier.
Einen DataFrame mit pandas in eine xlsx-Datei schreiben
Mit pandas ist es wieder sehr leicht, einen DataFrame in eine Excel-Datei zu schreiben. Das geht über die Funktion to_excel, also z.B.
diamonds.to_excel("Diamonds.xlsx", sheet_name='Diamonds')
Dabei gibt es folgende Parameter zur Individualisierung. Es gibt noch ein paar mehr, aber gebraucht habe ich die bisher nicht.

na_rep = ''
Wie sollen fehlende Werte dargestellt werden. Standard ist hier leer.
float_format=None
Hier kann das Format für Kommazahlen angegeben werden
columns = None
Wenn nicht alle Spalten geschrieben werden sollen, dann kann man diese hier spezifizieren
header = True
Sollen Spaltennamen geschrieben werden. Es geht auch eine Liste mit den Spaltennamen.
index = True
Flag, ob ein Zeilenindex geschrieben werden soll.
index_label = None
Der Spaltenname für die Index-Spalte
startrow = 0
Die Zeile in der Excel-Datei, ab der die Tabelle geschrieben werden soll
startcol = 0
Die Spalte in der Excel-Datei, ab der die Tabelle geschrieben werden soll

Mehrere Arbeitsblätter in eine Excel-Datei schreiben
Auch das ist mit pandas kein Problem. Allerdings muss man dazu einen ExcelWriter verwenden. Das geht folgendermaßen:
with pd.ExcelWriter('test.xlsx') as writer:
iris.to_excel(writer, sheet_name='iris')
tips.to_excel(writer, sheet_name='tips')

 
Eine Excel-Datei mit xlwt schreiben
Das Schreiben einer xls-Datei (Achtung, xlwt unterstützt nicht das neue xlsx-Format, welches seit Excel 2007 verwendet wird)
import xlwt
from datetime import datetime

wb = xlwt.Workbook()
ws = wb.add_sheet('Tabelle1')

datumStyle = xlwt.easyxf(num_format_str='DD.MM.YYYY')

ws.write(0, 0, 1)
ws.write(0, 1, 1)
ws.write(0, 2, xlwt.Formula("A1+B1"))

ws.write(1, 0, datetime.now(),datumStyle)
ws.write(1, 2, 3.1415)

wb.save('Beispiel-xlwt.xls')

Eine Excel-Datei mit openpyxl schreiben
Das Schreiben der Datei ist wieder überhaupt kein Problem:
from openpyxl import Workbook
wb = Workbook()
# … Arbeitsmappe befüllen …
wb.save("Beispiel-openpyxl.xlsx")

 
Als Zwischenteil können wir zum Beispiel folgendes machen:
# Das erste Arbeitsblatt aktivieren
ws = wb.active
# Man kann direkt in Zellen schreiben
ws['C1'] = 3.1415
# Auch ganze Zeilen können angehangen werden
ws.append([1, 2, 3])
# Datumsfelder funktionieren auch
import datetime
ws['E2'] = datetime.datetime.now()

 
Fazit zu Excel-Dateien mit Python
Nochmal kurz zusammengefasst, lässt sich sagen:
 
Wollt ihr nur Tabellen einlesen bzw in eine Excel-Datei schreiben, dann benutz am besten pandas. Die Import- bzw. Export-Funktion ist einfach anzuwenden und pandas Datentyp DataFrame ist sowieso ideal, um die Daten weiter zu verarbeiten.
 
Braucht ihr mehr Kontrolle und wollt einzelne Zellen, Formate etc. auslesen oder bearbeiten, benutzt openpyxl. Falls ihr aus unerfindlichen Gründen noch das xls-Format verwenden müsst (ja, das gibt es in manchen Firmen noch), dann verwendet xlrt/xlwt/xlutils.
 
Ich hoffe, ihr habt was Nützliches mitgenommen. Schreibt mir einen Kommentar, welches Package ihr am liebsten verwendet. Und wenn ihr helfen wollt, Data Science Wissen zu verbreiten und mich damit unterstützt, dann teilt den Beitrag auf Twitter oder Facebook.
 
Happy Exceling,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow

Jupyter Lab Shortcuts: Steigere Deine Produktivität – auch für Jupyter Notebook

Du hast gerade angefangen, Jupyter Notebook oder Jupyter Lab zu benutzen? Oder Du möchtest einfach schneller werden und nicht immer die Maus benutzen? Dann sind die folgenden Shortcuts genau das richtige für Dich. Eilige springen direkt zu den Shortcuts, vorher aber ein bisschen nützliches Hintergrundwissen.
Was ist Jupyter Notebook?
Jupyter Notebook ist einerseits ein Datei-Format mit der Endung ipynb, um formatierten Text, Code und den Codeoutput wie Grafiken, Tabellen etc. in einem Dokument darzustellen. Im Prinzip ist es nur eine JSON-Datei. Ursprünglich für Julia, Python und R (daher der Name Jupyter) mit Schwerpunkt Python entwickelt, können die Dokumente jede beliebige Programmiersprache enthalten. Sinnvoll sind aber nur die, die auch von dem Programm Jupyter Notebook bzw. Jupyter Lab unterstützt werden. Es gibt aktuell ca. 50 Programmiersprachen, die einen sogenannten Kernel für Jupyter haben.
Jupyter Notebooks sind in der Data Science Welt recht verbreitet. Zum Beispiel findet man viele Data Science-Notebooks sowohl für Python als auch R auf Kaggle. Github unterstützt übrigens auch das Dateiformat, so dass die Dateien auf Github korrekt angezeigt werden. So gibt es das Buch Python Data Science Handbook komplett als Notebooks auf Github.
Die Software Jupyter Notebook ist eine webbasierte Oberfläche, um eben ipynb-Dateien zu erstellen. Sie als vollwertige Entwicklungsumgebung zu bezeichnen, wäre stark übertrieben. Aber gerade für die explorative Datenanalyse oder für eine Adhoc-Analyse mit Visualisierungen oder auch zum Lernen ist Jupyter Notebook bestens geeignet. Die fertigen Notebooks können dann auch als HTML oder PDF exportiert werden.
Jupyter Notebook ist in der Anaconda-Distribution enthalten, kann aber auch über pip oder conda installiert werden. Wenn Du mehr über Anaconda wissen willst, findest Du in meinem Blogartikel Die Python-Distribution Anaconda.
Nachfolger mit mehr und besseren Features ist Jupyter Lab.
Jupyter Notebook oder Jupyter Lab? Was ist besser?
Klare Sache, benutzt Jupyer Lab! Das ist der offizielle Nachfolger von Jupyter Notebook und wird diesen also bald komplett ersetzen. Jupyter Lab bietet zum Beispiel mehrere Tabs in einem Browserfenster. Auch kann eine Konsole zusätzlich angezeigt werden und nach und nach kann man auch auf verschiedene Erweiterungen zugreifen. Das Dateiformat hat sich nicht geändert, man arbeitet also immer noch mit ipynb-Dateien, nur der Editor ist sozusagen komfortabler geworden.
Aufbau eines Notebooks
Ein Notebook besteht aus Zellen, welche man mit Abschnitten oder Kapiteln übersetzen kann. Eine Zelle enthält entweder formatierten Text in Form von Markdown oder Code. Markdown-Zellen können also Überschriften, Links, Grafiken, mathematische Formeln (Latex) oder auch Tabellen enthalten. Hat man eine Codezelle ausgeführt, wird darunter der Output angezeigt, egal ob das nun Text aus der Konsole oder eine schicke Grafik ist.

Die zwei Modi: Navigation und Editieren
Entweder will man etwas in eine Zelle schreiben. Das ist der Editiermodus. Aus dem Navigationsmodus wechselt man in ihn durch Drücken der ENTER-Taste.
Oder man will zwischen den Zellen hin- und herspringen, neue Zellen einfügen, Zellen löschen, etc. Das ist der Navigationsmodus bzw. Befehlsmodus. In diesen wechselt man durch Drücken der ESC-Taste.
 
Die wichtigsten Shortcuts für Jupyter Lab
So, hier kommen nun die wichtigsten Shortcuts. Es sind gar nicht so viele, aber für das Programmieren sind sie extrem nützlich. Insbesondere die Navigation zwischen den Zellen, ohne Maus oder Touchpad zu benutzen, beschleunigt Deine Arbeit erheblich.

Shortcut
Modus
Beschreibung

ENTER
Navi
wechselt in den Editier-Modus

ESC
Edit
wechselt in den Navigations-Modus

B
Navi
eine neue Zelle unterhalb einfügen (B für below)

A
Navi
eine neue Zelle oberhalb einfügen (A für above)

D, D
Navi
Löschen einer Zelle (zweimaliges Drücken von D)

STRG + ENTER
beide
führt den Code aus und bleibt in der Zelle

SHIFT + ENTER
beide
führt den Code aus und springt in die nächste Zelle bzw. legt eine neue Zelle an

y
Navi
Wechselt den Zelltyp zu Code

m
Navi
Wechselt den Zelltyp zu Markdown

STRG + SHIFT + C
beide
Öffnet die Command Console (Achtung: Hat sich zu Jupyter Notebook geändert, dort war es STRG + SHIFT + P)

TAB
Edit
Autocomplete (angefangener Befehl wird ergänzt)

X
Navi
Zelle ausschneiden

C
Navi
Zelle kopieren

V
Navi
Zelle einfügen

SHIFT + TAB
Edit
Hilfe zu dem Befehl aus DocString

STRG + SHIFT + –
Edit
Teilt die Zelle an der Cursorposition

SHIFT + M
Navi
Fügt ausgewählte Zellen zusammen zu einer Zelle

STRG + B
beide
Blendet die Sidebar links aus/ein. So hat man mehr Platz für den Code

STRG + POS1
Edit
Springt an den Anfang der aktiven Zelle

STRG + ENDE
Edit
Springt an das Ende der aktiven Zelle

 
Natürlich kann man auch eigene Shortcuts definieren. Das geht im Menü unter Settings -> Advanced Settings Editor -> Keyboard Shortcuts. Ganz so bequem ist es allerdings nicht, man muss etwas JSON kopieren und anpassen. Links sind die Voreinstellungen vom System, rechts kann man eigene Definitionen benutzen. Dazu muss man wissen, wie das command heißt, das kann man aber leicht über die Command Console (STRG + SHIFT + C) herausfinden. Zudem muss man den selector kennen. Also am besten schaut man sich die System-Shortcuts an oder sucht im Internet.
Hier ein Beispiel, wie sowas aussehen kann:
{
"shortcuts": [
{
"command": "application:activate-next-tab",
"keys": ["Ctrl Shift ]"],
"selector": "body"
},
{
"command": "application:activate-previous-tab",
"keys": ["Ctrl Shift ["],
"selector": "body"
}
]
}
 
Die Dokumentation von Jupyter Lab ist ansonsten immer ein guter Ausgangspunkt, wenn man etwas verstehen möchte.
Happy Jupytering,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

SQL 1×1

Als Data Scientist haben wir mit Daten zu tun (wer hätte das gedacht) und in ziemlich vielen Fällen liegen diese Daten in einer relationalen Datenbank. Und die Abfragesprache dafür ist SQL. Also muss ein Data Scientist mindestens die Grundzüge von SQL beherrschen.
Aber zum Glück ist das SQL 1×1 gar nicht schwer, denn die am meisten verwendeten Befehle sind nicht viele und logisch aufgebaut. Natürlich steckt hinter relationalen Datenbanken und SQL wesentlich mehr, wenn es um Datenbank-Design oder komplizierte Berechnungen geht. Da gibt es ganze Regalmeter voll Fachwissen. Aber das soll Dich vorerst gar nicht kümmern. Mit dieser SQL Anleitung für Anfänger bekommst Du eine praktische Einführung und kannst direkt mit der Beispiel-Datenbank loslegen.

Inhalt

Was ist SQL?Zugriff auf eine SQL DatenbankDie SQL IDE DBeaverZugriff per ODBCAufbau einer SQL-QueryDer SELECT-BefehlFiltern mittels WHERE-BedingungSortieren mit ORDER BYEindeutige Ergebnisse mit dem Schlüsselwort DISTINCTBedingungen mittels CASE WHENAggregation mit GROUP BYEine Abfrage über mehrere Tabellen mittels JOINVerschachtelte AbfragenAusblick
Was ist SQL?
SQL steht für „Structured Query Language“, wurde in den 1970ern entwickelt und ist laut Wikipedia eine Datenbanksprache zur Definition von Datenstrukturen in relationalen Datenbanken sowie zum Bearbeiten (Einfügen, Verändern, Löschen) und Abfragen von darauf basierenden Datenbeständen. Es gibt also drei Aufgabengebiete:

Definition von Datenstrukturen, sprich das Design und Anlegen einer Datenbank mit ihren Tabellen, Sichten (Views), Indizes, Trigger usw.
Das Bearbeiten von Daten
Die Abfrage von Daten (query), also Anzeige/Export einer Tabelle

Auf Punkt 3 gehen wir in diesem Tutorial ein, denn als Data Scientist wollen wir ja Daten in unsere R- oder Python-Skripte importieren, um dann darauf Analysen zu machen.
Es gibt zwar gewisse SQL-Standards (ANSI), jedoch gibt es viele Dialekte, je nachdem welches Datenbank-System (MSSQL, PostgreSQL, DB, MySQL, …) im Einsatz ist. Insofern muss man bei der Suche nach Anleitungen im Internet immer schauen, für welchen Dialekt das Beispiel geschrieben ist.
Zugriff auf eine SQL Datenbank
Auf einem Server läuft ein Datenbankmanagement-System (DBMS), z.B. PostgreSQL, MS-SQL oder MySQL. Das sind ziemlich komplexe Softwaresysteme, die die Datenbanken verwalten. Zum Beispiel habe ich eine MySQL-Datenbank bei meinem Webhost, die alle Daten der databraineo-Website enthält. Auf dieses DBMS kann ich nun per SQL zugreifen.
Man kann sich auch lokal auf dem PC ein Datenbankmanagement-System wie PostgreSQL installieren, allerdings ist dieses dann in der Regel auch nur vom lokalen PC zugreifbar.
Wir machen es uns einfach und benutzen SQLite. Das ist eine relationale Datenbank in einer einzigen Datei. Es gibt zwar ein paar Einschränkungen, dafür braucht man aber keine weitere Server-Software und kann sofort loslegen. Auch auf Mobiltelefonen oder in Webbrowsern kommt zum Beispiel SQLite zum Einsatz.
Die SQL IDE DBeaver
Ein sehr nützliches Tool, um SQL-Datenbanken zu verwalten, ist DBeaver. In der Community Edtion ist es kostenlos und unterstützt viele SQL-DBMS, also probiert es aus. In DBeaver können wir z.B. Verbindungen zu verschiedenen Datenbanken aufbauen, sehen, welche Tabellen es gibt, SQL-Code schreiben, Daten importieren oder exportieren.

Zugriff per ODBC
ODBC (open database connectivity) ist ein Standard für eine Datenbankschnittstelle. Man installiert den Treiber für das anzubindende DBMS. Fast jede Programmiersprache hat eine Bibliothek, mit dem man dann die Verbindung herstellen und SQL-Abfragen machen kann.
Für R gibt es beispielsweise die Packages odbc oder Rodbc. Für Python gibt es ebenfalls viele Möglichkeiten, z.B. pyodbc oder turbodbc. Im Python-Wiki gibt es eine Seite mit den ODBC-Libraries.
Aufbau einer SQL-Query
Wenn ihr DBeaver installiert habt, dann könnt ihr direkt loslegen und eigene Queries ausprobieren. In den folgenden Beispielen benutzen wir die Chinook-Datenbank, welche als Beispiel-Datenbank bei DBeaver dabei ist. Ansonsten kann man sich die sqlite-Datei aus dem Github-Repository herunterladen.

Nun wollen wir unsere erste Abfrage machen. Dazu wählst Du die Datenbank „DBeaver Sample Database“ aus, klickst rechts und wählst SQL Editor (Shortcut F3). Dadurch öffnet sich ein Textfenster, in das wir direkt die SQL-Befehle tippen können.

View the code on Gist.
 
Mit Strg + Eingabe bzw. dem Play-Button links (siehe Screenshot) wird die Zeile bzw. der markierte Bereich ausgeführt und unten kommt das Ergebnis.

Mit diesem Befehl wird die gesamte Tabelle Artist zurückgegeben.

Im Datenbanknavigator auf der linken Seite sehen wir übrigens, welche Tabelle es gibt und welche Spalten diese haben. Artist hat zwei Spalten, nämlich ArtistId und Name.
Der SELECT-Befehl
Jede Abfrage, welche Daten aus der Datenbank holt, fängt mit SELECT an. Dann folgen Spaltennamen oder Ausdrücke, gefolgt von FROM Tabellenname und einem Semikolon.
Statt dem * im obigen Beispiel könnten wir auch die Spaltennamen aufzählen, also
View the code on Gist.
Um den Spaltennamen in der Ausgabe zu ändern, benutzen wir das Schlüsselwort AS
View the code on Gist.
Filtern mittels WHERE-Bedingung
Meist sind Tabellen ja ziemlich groß, so dass wir die Daten filtern. Das passiert mit einer WHERE-Bedingung, die nach dem FROM kommt.
View the code on Gist.
substr steht für substring, wir pulen damit das erste Zeichen aus dem Namen raus. D.h. die Query gibt alle Künstler zurück, die mit A anfangen.
Eine sehr praktische Alternative zum Vergleichsoperator = ist LIKE. Auch LIKE vergleicht einfach nur zwei Strings. Das tolle daran sind  aber die zwei Wildcards % und _. Dabei steht % für beliebig viele Zeichen und _ für ein beliebiges Zeichen.
View the code on Gist.
Mehrere Bedingungen können mit AND und OR kombiniert werden und eine Bedingung kann mit NOT negiert werden.
View the code on Gist.
In dieser Query haben wir noch eine zweite Bedingung ergänzt: Der zweite Buchstabe darf kein c sein.
Sortieren mit ORDER BY
Um die Ausgabe-Tabelle zu sortieren, benutzen wir am Ende den Zusatz ORDER BY. Soll nach mehreren Spalten sortiert werden, trennt man diese mit Komma. Mit dem Keyword DESC wird die Reihenfolge umgedreht.
View the code on Gist.
In dem Beispiel wird also zuerst absteigend nach Composer sortiert, im Anschluss aufsteigend nach Name.

Eindeutige Ergebnisse mit dem Schlüsselwort DISTINCT
Häufig gibt es mehrere Einträge mit dem gleichen Wert in einer Spalte. Interessiert uns nur eindeutige Werte, ist das Schlüsselwort DISTINCT nützlich, welches direkt nach SELECT verwendet wird.
View the code on Gist.
 
Bedingungen mittels CASE WHEN
Wollen wir Bedingungen einbauen, dann benutzen wir in SQL eine CASE-WHEN-Struktur. In vielen anderen Sprachen entspricht das einer IF-ELSE-Bedingung.
View the code on Gist.
 
Schauen wir uns die CASE WHEN Bedingung an. Nach dem CASE WHEN kommt eine (oder mehrere Bedingungen mit AND, OR, NOT). Trifft diese zu, wird der Teil nach dem THEN zurückgegeben. Ein ELSE-Teil ist optional. Falls dieser nicht vorhanden ist, wird NULL zurückgegeben, ansonsten eben der Wert aus dem ELSE-Teil. Abgeschlossen wird CASE WHEN mit END.
 
Noch eine Sache: Wir haben Index in eckige Klammern gepackt. Das liegt daran, dass Index ein SQL-Schlüsselwort ist. Mit den eckigen Klammern sagen wir, dass es hier nur als Spaltenname verwendet werden soll. Das ist zwar kein so guter Stil, aber manchmal nötig, wenn man gewisse Spaltennamen im Export benötigt. Häufiger Kandidat ist auch Alter, welches auch ein SQL-Schlüsselwort ist. Auch Leerzeichen sind mit den eckigen Klammern in den Spaltennamen möglich.
 
Mehrere Bedingungen lassen sich einfach untereinander schreiben. Das END kommt erst am Ende von allen Bedingungen.
View the code on Gist.
 
Aggregation mit GROUP BY
Bisher haben wir nur Teile einer Tabelle angezeigt. Nun kommen wir zu einem der wichtigsten Hilfsmittel für Data Scientisten, nämlich die Aggregation von Zeilen. Wir fassen mehrere Zeilen zusammen, z.B. als Summe, Durchschnitt, Minimum oder Anzahl.
View the code on Gist.
In diesem Beispiel wird also der Mittelwert über die Länge aller Tracks in Minuten berechnet. Jetzt wollen wir aber nicht alle Zeilen zusammenfassen, sondern mehrere Kennzahlen für jeden Composer berechnen. Das geht mit dem Zusatz GROUP BY.
View the code on Gist.
Wollen wir bezüglich der Aggregate filtern, geht das nicht mit WHERE, denn die Filterung passiert vor der Gruppierung. Für die Filterung nach der Gruppierung benutzt man HAVING.
View the code on Gist.
Die Query oben filtert die Tabelle Track zuerst auf die Tracks, deren Namen mit einem Vokal anfangen. Dann berechnet sie für jeden Vokal separat die Anzahl und durchschnittliche Länge der Tracks. Danach wird nochmal gefiltert, nämlich auf mehr als 50 Tracks (dadurch fällt der Vokal u heraus).
 
Eine Abfrage über mehrere Tabellen mittels JOIN
In den seltensten Fällen genügt die Abfrage auf eine Tabelle, denn eine Datenbank ist relational aufgebaut, hat also Beziehungen zwischen den Tabellen. Wir sehen das gut in unsere Beispiel-Datenbank. In der Tabelle Track ist zwar eine AlbumId vorhanden, aber kein Albumname. Dazu müssen wir in der Tabelle Album nachschauen. Und das geht folgendermaßen:
View the code on Gist.
Es gibt vier Arten von JOIN:

INNER JOIN: Behält nur die Zeilen, die in beiden Tabellen vorhanden sind
LEFT JOIN: Behält alle Zeilen aus der ersten Tabelle
RIGHT JOIN: Behält alle Zeilen aus der zweiten Tabelle
FULL OUTER JOIN: Behält alle Zeilen aus beiden Tabellen

Ich habe für euch die vier JOIN-Typen auch als PDF-Cheatsheet dargestellt.

Man kann auch mehrere Tabellen miteinander verbinden. Hier habe ich zudem Aliases a, b, c, verwendet, um etwas Schreibarbeit zu sparen.
View the code on Gist.
Verschachtelte Abfragen
Man kann Abfragen ineinander verschachteln, also statt einer Tabelle nach FROM oder JOIN eine ganze SELECT-Query. Wir nehmen die vorherige Abfrage und bilden nun den Mittelwert der Bestellungen
View the code on Gist.
Ausblick
So, jetzt kennt ihr die wichtigsten Befehle für Datenbankabfragen. Natürlich braucht man manchmal noch ein bisschen mehr, z.B. wenn man eine Datums- oder Zeitstempel-Spalte umwandeln muss. Zudem kommt irgendwann die Performance-Optimierung ins Spiel, wenn die Queries zu lange dauern.
Aber mit den obigen SQL-Statements kommt ihr schon ziemlich weit.
Ein großes Thema ist das Design von Datenbanken, also welche Tabellen mit welchen Spalten angelegt werden. Das fängt dann mit Normalformen an und geht mit Triggern und Indizes weiter. Das ist eine ganze Welt für sich. Einige Grundkenntnisse hier helfen natürlich weiter, aber es hängt auch davon ab, ob man diese Sachen als Data Scientist selber machen muss oder IT-Support bekommt.
Daten einfügen und aktualisieren hingegen ist nicht sonderlich schwer, das machen wir in einem anderen Blogpost. Und dann wollen wir das alles ja nicht mit einem extra SQL-Skript, sondern SQL direkt aus R oder Python ausführen. Aber auch das ist mit den richtigen Packages ziemlich einfach.
Happy SQLing,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Der t-Test – Vergleich von Mittelwerten

Hej Leute,
heute geht es um Statistik und zwar um den t-Test! Jetzt ist es raus, kreisch, schrei, Hilfe, renn weg. Ruhig Blut, so schwierig ist der Test gar nicht. Ich erkläre euch ganz in Ruhe, wie er funktioniert und worauf es ankommt.
Der t-Test ist einer der bekanntesten und häufigsten verwendeten Hypothesentest. Er dient dazu, statistisch zu prüfen, ob der Mittelwert einer Stichprobe einem vorgegebenen Wert entspricht. Ich könnte zum Beispiel prüfen, ob das mittlere Alter der Personen, die ich befragt habe, dem deutschen Altersdurchschnitt von 44,3 (im Jahr 2010) entspricht.
Das kann man auch auf zwei Stichproben erweitern. D.h. man prüft, ob die Mittelwerte zweier Stichproben übereinstimmen. So könnte man testen, ob in einer Stichprobe das mittlere Alter der befragten Frauen dem mittleren Alter der befragten Männern entspricht.

Inhalt

Die Geschichte des t-TestsWie funktioniert ein HypothesentestDer Z-Test – wenn es doch so einfach wäreDie Teststatistik ist t-verteiltVoraussetzungen zur Anwendung vom t-testEinseitiger vs. zweiseitiger t-TestEinstichproben- vs. Zweistichproben-t-TestUnabhängige Stichproben mit gleicher VarianzVerbundene/Abhängige StichprobenUnabhängige Stichproben mit ungleicher VarianzAnwendungen vom t-TestPrüfverfahrenBefragungenA/B-TestRegression
Die Geschichte des t-Tests
Der t-Test wurde ursprünglich von William Sealy Gosset, besser bekannt als Student, im Jahr 1908 entwickelt. Gosset arbeitete bei der Guiness-Brauerei, befasste sich dort mit Gerstenqualität und entdeckte dabei die t-Verteilung. Er veröffentlichte seine Erkenntnisse unter dem Pseudonym Student, da sein Arbeitgeber nicht erlaubte, wissenschaftliche Arbeiten zu veröffentlichen. Daher der Name Studentsche t-Verteilung. Ronald Aylmer Fisher erkannte die Bedeutung von Gossets Arbeit und entwickelte die heute gebräuchliche t-Prüfgröße.
Gosset hat herausgefunden, dass der Mittelwert einer normalverteilten Stichprobe nicht mehr normalverteilt ist, wenn man die Varianz schätzen muss. Aber nochmal ganz langsam.
Wie funktioniert ein Hypothesentest
Ich versuche in diesem Abschnitt ganz grob zu erklären, wie ein Hypothesentest funktioniert. Bald sollte ein eigener, ausführlicheren Artikel dazu folgen, den verlinke ich dann natürlich hier.
Zuerst brauchen wir eine Nullhypothese, also z.B. die Annahme, dass der Mittelwert des Alters der Grundgesamtheit, aus der wir die Stichprobe gezogen haben, 44,3 Jahre beträgt. Im Endeffekt will man das Gegenteil zeigen. Man sagt, dass man die Nullhypothese ablehnt. Man lehnt ab, wenn es sehr unwahrscheinlich ist, die Daten – gegeben die Nullhypothese – zu beobachten. Wir gehen aber erstmal von der Nullhypothese aus. Nun wird eine Prüfgröße, auch Test-Statistik genannt, berechnet. Unter der Annahme, dass die Nullhypothese stimmt, sollte die Prüfgröße einer bekannten Verteilung gehorchen, z.B. normal- oder t-verteilt sein.
Was heißt das nun wieder? Die Prüfgröße ist doch nur eine Zahl. Das ist so gemeint, dass wenn wir das Experiment (also die Auswahl einer Stichprobe und Berechnung der Prüfgröße) vielfach wiederholen, die Prüfgrößen der ganzen Experimente sich einer bekannten Verteilung nach verteilen.

Nun können wir berechnen, ob es (unter der angenommenen Nullhypothese) sehr unwahrscheinlich ist, die Stichprobe zu beobachten. Hier kommt das Signifikanzniveau ins Spiel. Wählen wir (ein typischer Wert), dann prüfen wir, ob wir diese Teststatistik in weniger als 5% der Fälle bekommen, wenn wir immer wieder eine Stichprobe ziehen würden. Ist das so, dann lehnen wir die Nullhypothese ab.
Mathematisch wird das über die Quantilsfunktion der bekannten Verteilung gemacht, weil die Quantilsfunktion die Umkehrfunktion zur Verteilungsfunktion ist. Es wird berechnet, ob die Prüfgröße Z am Rande der Verteilung liegt. Also bei einem einseitigem Test mit , ob Z im 5%- bzw. 95%-Quantil liegt (je nach Richtung).
Der Z-Test – wenn es doch so einfach wäre
So, dieser Teil hier ist tatsächlich mathematisch, kommen doch ein paar Formeln vor. Danach wird es aber wieder besser.
Der Mittelwert einer Stichprobe wird über das arithmetische Mittel berechnet, also die Summe aller Werte, geteilt durch die Anzahl
   
Stammt die Stichprobe aus einer Normalverteilung mit Mittelwert mu und Varianz sigma², dann ist das arithmetische Mittel wieder normalverteilt mit Mittelwert mu, aber mit Varianz sigma²/n.
Würden wir die Varianz kennen, dann könnten wir statt des t-Tests einen Gauß-Test, auch Z-Test genannt, machen, um zu prüfen, ob der Mittelwert einem vorgegebenen Wert mu_0 entspricht. Unter der Hypothese, dass der wahre Mittelwert mu_0 ist, können wir folgende Transformation machen, damit das Ergebnis standardnormalverteilt ist. Also Mittelwert = 0 und Varianz = 1
   
Nun können wir berechnen, ob es (unter der Nullhypothese) sehr unwahrscheinlich ist, die Prüfgröße Z zu beobachten.
 
Die Teststatistik ist t-verteilt
Da aber in der Praxis die Varianz nicht bekannt ist, müssen wir diese schätzen
   
Nun macht uns das aber unsere schöne Normalverteilung von Z kaputt, denn statt durch Konstante sigma teilen wir nun durch S. ist -verteilt. Und so kann man die t-Verteilung definieren, als Standardnormalverteilung durch Wurzel aus -Verteilung. Die -Verteilung hat einen Freiheitsgrad, also
   
mit n = Stichprobengröße
Jetzt kennen wir die Verteilung von T und können die übliche Transformation mit Quantilsfunktion machen.
Wir bekommen nur eine t-Verteilung, wenn die ursprüngliche Verteilung eine Normalverteilung ist. Ansonsten kommt der zentrale Grenzwertsatz ins Spiel, der besagt, dass X_quer einer Normalverteilung annähert. Typischerweise stellt man die Bedingung, dass die Stichprobengröße n>30 ist.
Voraussetzungen zur Anwendung vom t-test
Der vorherige Abschnitt gibt uns schon die Antwort

X ist normalverteilt oder
Der zentrale Grenzwertsatz wird erfüllt und die Stichprobengröße > 30

Einseitiger vs. zweiseitiger t-Test
Bei einem zweiseitigen Test lautet die Nullhypothese und die Alternative . Haben wir vorher schon eine Idee zur Richtung, dann können wir auch einen einseitigen Test machen. Da haben wir dann die Nullhypothese und als Alternative . Das ganze geht natürlich auch umgekehrt
Einstichproben- vs. Zweistichproben-t-Test
Beim Einstichproben-t-Test haben wir eine Stichprobe, deren Mittelwert wir mit einem vorgegebenen Wert vergleichen wollen.
Beim Zweistichproben-t-Test haben wir zwei Stichproben, deren Mittelwerte wir miteinander vergleichen wollen. Das macht die Sache leider etwas komplizierter. Wir unterscheiden drei Fälle:

zwei unabhängige Stichproben mit gleicher Varianz
zwei verbundene/abhängige Stichproben
zwei unabhängige Stichproben mit ungleicher Varianz

Unabhängige Stichproben mit gleicher Varianz
Wir betrachten die Differenz der beiden Mittelwerte und schauen, ob diese gleich ist. Für die Teststatistik schätzen wir noch die gemeinsame Varianz der beiden Stichproben.
   
mit
   
Voraussetzungen: Stichproben normalverteilt oder beide so groß, dass Mittelwert durch Normalverteilung approximiert wird
Verbundene/Abhängige Stichproben
Unter diesem Fall versteht man zwei Beobachtungen der gleichen Gruppe. Also zum Beispiel vor und nach einer Behandlung oder auch unter verschiedenen Bedingungen.
Man will nun wissen, ob sich die zwei Mittelwerte voneinander unterscheiden. Anders ausgedrückt, ob die Differenz gleich null ist. Und genauso macht man es dann auch, d.h. einen Einstichproben-t-Test, der auf die Differenz angewandt wird.
Hier muss natürlich wieder geschaut werden, dass die Differenz die Voraussetzungen erfüllt. Im Wesentlichen heißt das, dass die Stichprobe groß genug ist.
Unabhängige Stichproben mit ungleicher Varianz
Problem ist die Voraussetzung, dass Varianz beider Stichproben gleich ist. Das ist in der Realität meist nicht gegeben, daher verwendet man den Welch-Test, der diese Bedingung nicht stellt. Streng genommen ist die Teststatistik nicht t-verteilt, aber sie nähert sich einer t-Verteilung an.
   
Man kann auch die Freiheitsgrade mit folgender Formel bestimmen:
   
Anwendungen vom t-Test
Prüfverfahren
Wie oben geschrieben ist war die Untersuchung von Gerstenqualität tatsächlich der Ursprung der t-Verteilung. Das lässt sich natürlich auf alle möglichen Situationen verallgemeinern, ob Agrarwissenschaft, Biologie, Pharma oder Maschinenbau. Immer dann, wenn die Mittelwerte zweier Gruppen verglichen werden sollen, kommt der t-Test ins Spiel.
Befragungen
Alle Arten von Befragungen, bei denen ein Mittelwert sinnvoll ist. Aber Achtung: Verwendung von Mittelwerten bei Likert-Skalen sind umstritten. Der Modus ist hier die bessere Wahl.
A/B-Test
A/B-Tests sind im Online-Marketing in aller Munde. Dazu werden zwei Varianten gebildet, z.B. Buttonplatzierung oder –farbe oder zwei verschiedene Facebook-Werbeanzeigen. Diese werden nun den Nutzern gezeigt und das Verhalten gemessen. Dann können die beiden Varianten miteinander verglichen werden, um dann die bessere, d.h. meistens profitablere, Variante zu verwenden.
So könnte man zum Beispiel die Conversion-Rate zweier Anzeigen untersuchen, indem man mittels t-Test prüft, ob sie gleich sind oder eine doch signifikant höher ist.
Regression
In den Statistiken einer linearen Regression wird normalerweise für jeden Koeffizienten mittels t-Test geprüft, ob dieser sich signifikant von 0 unterscheidet.
So, habt ihr euch wirklich den ganzen Artikel durchgelesen? Jetzt seid ihr hoffentlich mit dem Grundlagenwissen ausgestattet, um t-Tests anwenden zu können. Schreibt einen Kommentar, in welchem Zusammenhang ihr schon einen t-Test gemacht habt.
Happy testing,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Strings in Python

Hej Leute,
Zu Beginn ein kleiner Hinweis: Dieser Beitrag ist ein Auszug aus dem neuen Python Komplettkurs von Edley. Falls ihr innerhalb von 30 Tagen Python lernen wollt und ein übersichtliches Nachschlagewerk mit allen wichtigen Themen braucht, ist das eine empfehlenswerte Ressource.
Heute lernt ihr, was Strings sind und wie man mit Ihnen arbeitet:
Strings
Im Beitrag über Datentypen wurden die String-Variablen ja schon kurz vorgestellt. Diese dienen dazu, Zeichenketten aufzunehmen und wiederzugeben. Strings werden in Anführungszeichen (” oder ‘) gestellt. Um String-Variablen zu bearbeiten bzw. zu verändern, kommen 2 Operatoren besonders häufig zum Einsatz:
Das Stern-Symbol (*). Dieses kommt bei Zahlen für die Multiplikation zum Einsatz. Ihr könnt jedoch auch eine Zeichenkette mit einer Zahl “multiplizieren”, in diesem Fall erhaltet ihr bei der Ausgabe eine Wiederholung der entsprechenden Zeichen in der vorgegebenen Anzahl.
Außerdem kennt ihr aus dem Beitrag über Operatoren bereits das Pluszeichen (+). Dieses hat, neben der mathematischen Addition von Zahlen, zwei Zeichenketten miteinander verbunden.
In den folgenden Abschnitten lernt ihr weitere Befehle kennen, mit denen es möglich ist, String-Variablen zu bearbeiten.
Wichtig ist es zunächst, auf einen einzelnen Teil der Zeichenkette zugreifen zu können. Das ist möglich, indem man hinter den Variablennamen in eine eckige Klammer ([] bei Windows mit Alt Gr + 8 und Alt Gr + 9, bei macOS mit alt + 5 und alt + 6) die Position des entsprechenden Buchstabens schreibt. Dabei müsst ihr lediglich berücksichtigen, dass der erste Buchstabe den Index 0 hat. Daher muss die entsprechende Zahl immer um 1 kleiner sein, als die eigentliche Position.
Will man beispielsweise auf den Buchstaben auf Position 4 zugreifen, muss man diesen Buchstaben über den Index 3 ansteuern.

Häufig ist es nicht nur notwendig, einen einzelnen Buchstaben aus der Zeichenkette herauszulösen, sondern einen größeren Bereich. Auch das ist möglich. Hierfür müsst ihr ebenfalls eine eckige Klammer hinter den Variablennamen schreiben. Darin stehen der Start- und der Endpunkt mit einem Doppelpunkt  voneinander getrennt. Dabei müsst ihr berücksichtigen, dass das Zeichen, das ihr als Endpunkt angebt, nicht mehr ausgegeben wird, sondern nur alle Zeichen, die vor dieser Position stehen. Wenn ihr am Anfang der Zeichenkette beginnen wollt, dann könnt ihr dafür den Index 0 eingeben. Alternativ dazu ist es möglich, die Zahl vor dem Doppelpunkt wegzulassen. Dann beginnt das Programm automatisch am Anfang der Zeichenkette. Wenn man die Zahl nach dem Doppelpunkt weglässt, beginnt das Programm am angegebenen Startpunkt und gibt alle Zeichen bis zum Ende der Zeichenkette wieder.
Das folgende Beispielprogramm zeigt, wie man Teile aus einer String-Variablen herauslösen kann:
variable = "Herzlich Willkommen!"

print (variable[0])
print (variable[5])
print (variable[3:7])
print (variable[:8])
print (variable[9:])
print (variable[-1])

Wenn ihr dieses Programm ausführt, seht ihr genau, wie der Zugriff auf einzelne Teile der Zeichenkette erfolgt.
Interessant ist dabei der letzte Befehl. Man könnte meinen, dass es keine negativen Positionen geben kann. Das Minuszeichen steht jedoch dafür, dass das Programm beim Zählen der Position von hinten beginnt. Die Position -1 bezeichnet daher das letzte Zeichen, die Position -2 das vorletzte und so weiter.

Anmerkung: Eigentlich wäre es logisch, für das letzte Zeichen die Position -0 zu verwenden. Das ist mathematisch jedoch gleichbedeutend mit 0, sodass der Interpreter das erste Zeichen ausgibt. Daher muss man immer mit -1 beginnen, wenn man beim letzten Zeichen beginnen will.
 

String-Methoden
Neben diesen einfachen Operationen mit Zeichenketten gibt es noch viele weitere, “schlauere” Möglichkeiten der Bearbeitung: Es handelt sich um Funktionen, die in Python integriert sind und die sich durch einen ganz bestimmten Schlüsselbegriff abrufen lassen. Diese Funktionen werden als String-Methoden bezeichnet. Die Vielfalt ist dabei so groß, dass es in diesem Beitrag nicht sinnvoll wäre, jede einzelne Funktion zu beschreiben. Die wichtigsten sollen jedoch kurz vorgestellt werden.
Um diese Methoden anzuwenden, ist es in der Regel notwendig, die Variable, die die Zeichenkette enthält, dem entsprechenden Schlüsselbegriff durch einen Punkt getrennt voranzustellen. Danach steht eine Klammer, die jedoch in vielen Fällen leer bleiben kann. Manchmal, je nach Methode, ist es allerdings notwendig, hier einen bestimmten Parameter einzufügen, damit die Methode richtig funktioniert. Um sie auszuprobieren, soll ein kleines Programm mit verschiedenen Methoden als Beispiel dienen:
text = "herzlich willkommen!"

print (text.capitalize())
print (text.count("i"))
print (text.endswith("en!"))
print (text.find("e"))
print (text.isdigit())
print (text.upper())
print (text.replace("e","0"))
 
Führt das Programm durch einen Klick auf Run aus und ihr erhaltet folgendes Ergebnis in der Konsole:

Die Methode capitalize() sorgt dafür, dass der erste Buchstabe der Zeichenkette groß geschrieben wird. Wenn es sich dabei bereits um einen Großbuchstaben handelt, hat sie keine Auswirkungen.
Die Methode count() benötigt als Parameter eine weitere Zeichenkette innerhalb der Klammer. Sie gibt an, wie häufig die in der Klammer angegebene Zeichenkette in der ursprünglichen Variable vertreten ist. Damit lässt sich beispielsweise die Häufigkeit eines Buchstabens oder eines Wortteils bestimmen. In längeren Texten könnt ihr damit auch bestimmte Wörter zählen.
Die Methode endswith() benötigt ebenfalls eine Zeichenkette in der Klammer und gibt eine boolesche Variable zurück. Diese ist True, wenn die ursprüngliche Variable genau mit den angegebenen Zeichen endet. Tut sie das nicht, ist der Wert False.
Die Methode find() sucht nach einzelnen Zeichen oder Zeichenketten innerhalb der Variable und gibt deren Position zurück. Sollten sie mehrfach auftreten, erhält man den Wert, an dem sie das erste Mal erscheint. Sollte das Zeichen überhaupt nicht vorkommen, erhält man den Wert -1 zurück.
Die Methode isdigit() überprüft, ob eine Zeichenkette ausschließlich aus Ziffern besteht. Je nach Ergebnis der Überprüfung gibt sie True oder False zurück.
Die Methode upper() verwandelt alle Kleinbuchstaben in der Zeichenkette in Großbuchstaben. Bei Ziffern, Satzzeichen oder bei Großbuchstaben hat sie keinen Effekt.
Mit der replace()-Methode könnt ihr einen Buchstaben oder eine bestimmte Abfolge durch einen anderen Ausdruck ersetzen. Diese Methode benötigt zwei Parameter. Der erste von ihnen enthält die Buchstabenfolge, die man ersetzen möchte. Der zweite gibt den neuen Wert an.
Hierbei handelt es sich nur um einen kleinen Ausschnitt aus den vielfältigen Methoden, die ihr auf Zeichenketten anwenden könnt. Eine vollständige Übersicht der String-Methoden findet man in der offiziellen Python-Referenz:
https://docs.python.org/3/library/stdtypes.html#string-methods
Probiert an dieser Stelle selbst ein paar Methoden mit eigenem Beispieltext aus, ihr könnt dabei nichts kaputt machen!
(Auszug aus dem Python Komplettkurs von Edley)
 
Happy coding,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Operatoren in Python

Hej Leute,
heute geht es um ein wichtiges Thema in Python: Operatoren. Ohne diese geht beim Programmieren gar nichts. Es ist also wichtig, dass Ihr die Funktionsweise von Operatoren verstanden habt, bevor es mit fortgeschrittenen Themen weitergeht. Aber keine Panik, im Endeffekt ist das Konzept ganz simpel, wie ihr gleich sehen werdet.
(Dieser Beitrag ist ein Auszug aus dem Python Komplettkurs* von Edley)
Eine Variable bleibt im Verlauf eines Programms nicht immer gleich. Stattdessen ist es häufig notwendig, ihren Wert zu verändern. Hierzu kommen sogenannte Operatoren zum Einsatz. Für euch sind für den Anfang erstmal 2 Arten von Operatoren wichtig:
Zuweisungsoperator
Der Zuweisungsoperator besteht aus dem Gleichheitszeichen (=).  Er ermöglicht es, einer Variablen einen Wert zuzuweisen. Das ist nicht nur zu Beginn des Programms möglich. Ihr könnt einer Variablen auch im weiteren Verlauf immer wieder einen neuen Wert zuweisen:
name = "Michael"
print ("Hallo, "+name)
name = "Susanne"
print ("Hallo, "+name)

Dabei wird der alte Wert überschrieben. Bei der zweiten Ausgabe gibt der print-Befehl daher die Begrüßung mit den neuen Namen (hier: Susanne) aus.
Mathematische Operatoren
Hinzu kommen mathematische Operatoren. Dabei handelt es sich um die vier Grundrechenarten, die ihr hoffentlich noch aus der Schule kennt 😉
Die Verwendung von Addition mit Plus (+) und Subtraktion mit Minus (-) funktioniert wie gewohnt. Für Multiplikationen kommt das Sternsymbol (*) und für Divisionen der Schrägstrich (/) zum Einsatz. Außerdem gibt es den Modulo-Operator. Dieser besteht aus dem Prozentzeichen (%) und gibt den Rest der ganzzahligen Division an. Beispiele:
x = 4
y = x + 2
x = y / 3
x = 5 % 2
x = x * 5

 
Auf diese Weise sind viele verschiedene Rechenoperationen zwischen Zahlen und Variablen möglich. Dabei kann eine Variable auch Bezug auf sich selbst nehmen. Das zeigt das letzte Beispiel. Dieser Befehl weist der Variablen x das Fünffache ihres bisherigen Werts zu. Da solche Ausdrücke in Python recht häufig vorkommen, gibt es dafür auch eine Kurzform: x *= 5
Analog dazu bestehen auch für die anderen Grundrechenarten entsprechende Ausdrücke:
x += y                 entspricht               x = x + y
x -= y                 entspricht               x = x – y
x *= y                 entspricht               x = x * y
x /= y                 entspricht               x = x / y
Wichtig: Mathematischen Operatoren sind teilweise auch auf Zeichenketten anwendbar. Allerdings haben sie dabei einen anderen Effekt:

Das Pluszeichen setzt zwei Zeichenketten zusammen.
Das Sternsymbol wiederholt die Zeichenkette mit der entsprechenden Anzahl.

Es ist wichtig, dass ihr dieses Prinzip versteht, also nichts wie ran an die Tasten!
Neben dem Zuweisungsoperator und den mathematischen Operatoren, die ihr in diesem Beitrag kennengelernt habt,  werden später die Vergleichsoperatoren eine sehr wichtige Rolle spielen.
Wie genau diese funktionieren, wird z. B. im oben erwähnten Python Komplettkurs* gut erklärt.
Happy coding,
Euer Holger
 
P.S.: Die mit Sternchen (*) versehene Links sind sogenannte Affiliate-Links. Du unterstützt mich, wenn Du auf einen solchen Link klickst und das Produkt kaufst, d.h. ich bekomme vom Anbieter eine Provision. Für dich ändert sich der Preis nicht.
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Data Scientist oder Data Engineer – Was ist der Unterschied?

Hej Leute,
ich werde immer mal wieder gefragt, was denn der Unterschied zwischen einem Data Scientist und einem Data Engineer oder zwischen einem Data Analyst und einem Data Scientist sei. In Jobanzeigen sieht man mal den einen, mal den anderen Begriff, aber auch dort scheint es nicht immer klar abgegrenzt zu sein.
Klar ist, dass es viele Überschneidungen zwischen den drei Tätigkeiten Data Engineering, Data Science und Data Analysis gibt. Und natürlich sind die Begriffe nicht scharf getrennt, so dass es in einem Jobprofil der Data Scientist eigentlich eher ein Data Analyst wäre und umgekehrt. Allerdings sind die Fähigkeiten, die benötigt werden, ziemlich unterschiedlich. Und es fördert Frust, wenn man einen Data Scientist als Data Engineer einsetzt und auch umgekehrt.
Zudem gibt es noch einen Haufen anderer Berufsbezeichnungen wie DevOps, BI-Specialist oder Business Analyst, die mehr oder weniger eng mit den drei genannten verwandt sind. In Jobportalen tauchen gerne auch alle möglichen Kombinationen auf.
Fangen wir aber erstmal damit an, was überhaupt die Aufgabenbereiche sind.
 

Inhalt

Die Aufgaben eines Data ScientistsDie Aufgaben eines Data EngineersDie Aufgaben eines Data AnalystsWeitere BerufsbezeichnungenDevOp – Development und OperationsSystemadministrator – Manager von Servern/ComputernBusiness Analyst – der GeschäftsmodellversteherBusiness Intelligence Developer – der BI-System-SpezialistStatistiker – der StatistikexperteFazit
Die Aufgaben eines Data Scientists
Im Englischen kann man die Aufgabe ganz gut mit “Advances Analytics” beschreiben, also komplexe Analysen. Dabei gibt es zwei Anwendungsbereiche:

zum einen geht es um die Gewinnung von Insights mittels Daten, also die Datenanalyse mit dem Ziel, andere Abteilungen zu unterstützen. Typische Beispiele sind die Verteilung von Marketing-Budget, Betrugserkennung im Finanzwesen (fraud detection) oder auch Absatz-/Bestellprognosen.
Der andere Anwendungsbereich ist die (Weiter-) Entwicklung von Produkten, also mit dem Kunden als Anwender, z.B. eine recommendation engine oder welche Beiträge im Feed eines sozialen Netzwerkes landen.

Beide Anwendungsbereiche können je nach Job unterschiedlich stark gewichtet sind bzw. kann ein Job auch nur einen verlangen. Dabei reichen die Ausprägungen von recht einfachen Adhoc-Analysen bis zur Entwicklung von künstlicher Intelligenz. Auch die Datenmenge ist sehr unterschiedlich und eignet sich nicht als Unterscheidungskriterium zwischen Data Analyst und Data Scientist. Manche sagen zwar: Data Analyst=small data, Data Scientist=big data, aber das greift viel zu kurz. Ab welcher Größe spricht man denn von Big Data? Und zudem kann man auch mit Small Data “Advanced Analytics” entwickeln.
 
Im Endeffekt benötigt ein guter Data Scientist die folgenden vier Fähigkeiten:

Mathematische und statistische Methoden
IT-Skills, insbesondere Programmierung in einer Skriptsprache (Python oder R)
Kommunikation inkl. Datenvisualisierung
Domain Knowledge

Die ersten drei kann man lernen, Domain Knowledge – modern für Branchenwissen – bekommt man mit der Erfahrung, die man in einer bestimmten Branche arbeitet, z.B. Gesundheitswesen, Digital Marketing oder Retail).
 
Die Aufgaben eines Data Engineers
Grob gesprochen ist der Data Engineer für die Infrastruktur der Daten zuständig. Insbesondere geht es um die saubere und verlässlichen Aufbau der Datenströme. Man spricht dabei von sogenannten data pipelines.
Das ist vor allem bei Big Data alles andere als trivial. Daher ist die Berufsbezeichnung Data Engineer eng mit Big Data verknüpft. Hat man “nur” normale Datenbanken, würde man vermutlich eher vom BI-Developer oder Data Warehouse-Specialist sprechen.
Der Data Engineer sollte sich also mit den modernen Tools für Big Data auskennen, sprich Hadoop, Hive, Spark, NoSQL-Datenbanken etc. Er sorgt im Endeffekt dafür, dass eine Big Data-Infrastruktur vorhanden ist, auf der ein Data Scientist dann die Analytics, z.B. maschinelles Lernen, durchführen kann.
 
Die Aufgaben eines Data Analysts
Der Data Analyst befindet sich zwischen Data Scientist und Business Analyst und ist häufig nur schwer vom Data Scientist abzugrenzen. Der Data Analyst hat seinen Schwerpunkt im Bereich Reporting mit den Tools Excel und SQL bzw. der BI-Anwendung. Vom Data Analyst wird in der Regel nicht erwartet, dass er komplexe statistische Modelle benutzt und programmiert, was typischerweise der Data Scientist macht.
Der Data Analyst arbeitet häufiger in einer Abteilung und hat dementsprechend großes Fachwissen, z.B. Controlling. Der Data Scientist agiert eher projektbasiert und abteilungsübergreifend.
 

 
Weitere Berufsbezeichnungen
Um Euch die Orientierung zu erleichtern, habe ich noch ein paar weitere Berufsbezeichnungen skizziert, die man im Zusammenhang mit Daten häufiger liest.
DevOp – Development und Operations
DevOps ist ein neuer Begriff und vielen ist gar nicht klar, was dieser denn nun bedeutet. Das liegt daran, das DevOps keine Berufsbezeichnung im eigentlichen Sinn ist, sondern eher eine Beschreibung einer IT-Kultur.
Vom Wort ist es erstmal eine Kombination von Development, also Softwarentwicklung, und Operations, also dem Betreiben der Software. Und das beschreibt es schon ganz gut. In der alten IT-Welt gibt es Softwareentwickler, die sich an Pflichten- und Lastenheften halten, und es gibt die Administratoren, die sich um den laufenden Betrieb kümmern. Eine Softwareentwicklung ist irgendwann abgeschlossen und Änderungen (Change Requests) müssen genau definiert, beurteilt, budgetiert, programmiert und dann produktiv gesetzt werden. Mit anderen Worten ein langwieriger Prozess. In der heutigen Zeit (Stichwort Agilität) geht es aber darum, in kurzer Zeit Änderungen am Softwareprodukt/-dienst vorzunehmen, um auf sich verändernde Anforderungen einzustellen. Denkt zum Beispiel mal an Google oder Facebook mit ihren tausend Tests, was denn nun besser funktioniert. DevOps kann darauf als Antwort gesehen werden, denn dabei geht es um die Reduktion der Zeit zwischen einer Änderungsanfrage und der tatsächlichen Änderung im produktiven System.
 
Systemadministrator – Manager von Servern/Computern
Der Systemadministrator lässt sich gut vom Data Engineer abgrenzen. Der Systemadministrator ist zuständig für Verfügbarkeit (uptime), Performance, Sicherheit und Ressourcen von Servern und Computern.
Dabei kommen im SysAdmin-Alltag meist einige der folgenden Tätigkeiten vor:

Installation und Konfiguration von neuer Hardware und Software
Installation von Updates und Patches des Betriebssystems und vorhandener Software
Management von User Accounts (Anlegen, Löschen, Passwörter zurücksetzen)
System Logs analysieren, um Probleme zu entdecken
Management der Netzwerk-Infrastruktur
Analyse und Behebung von berichteten Problemen
Anfragen von Usern beantworten
Training von Usern

 
Business Analyst – der Geschäftsmodellversteher
Der Business Analyst ist am nächsten am Data Analyst dran, aber die Ausrichtung ist noch mehr in Richtung BWL. Es geht also vor allem um Geschäftsprozesse und Geschäftsentwickung. Dabei sind die IT-Skills meistens begrenzter als die des Data Analysts, sprich es geht vor allem um Excel, Powerpoint und die Anwendung von BI-Software (SAP, Microstrategy, …). Dafür sind Kommunikationsfähigkeiten, Projektmanagement und Domain Knowledge ausgeprägter. Klassischerweise stellt der Business Analyst die Schnittstelle zwischen den Abteilungen und der IT dar und arbeitet projektbasiert als interner Berater. Je nach Größe des Unternehmens macht die Schnittstellenarbeit aber auch ein Data Analyst.
 
Business Intelligence Developer – der BI-System-Spezialist
Der BI Developer ist wie der Business Analyst auch eine Jobbeschreibung, die es schon einige Jahre gibt. Im Wesentlichen ist der BI-Developer für alles zuständig, was mit Reportings aus dem DataWarehouse und BI-System zu tun hat. Je nachdem, wie technisch der Job ausgelegt ist, gehört dazu neben der Arbeit im BI-System auch die Datenbankadministration. Je nach dieser Auslegung ist das Aufgabenprofil recht nacht nah am Data Analyst oder am Data Engineer. In Jobportalen sieht man dementsprechend auch häufiger die Kombination mit einem der beiden Begriffe. Insbesondere wenn es um moderne BI-Tools wie Tableau oder Qlik geht, ist die Nähe zum Data Analysten gegeben.

Datenmodelle, Reports und Dashboards im BI-System enwickeln
Programmierung und Optimierung von Datenbankabfragen (SQL)
Entwicklung und Design des Data Warehouses
Zusammenführung von verschiedenen Datenquellen im Data Warehouse
Administration von Datenbanken

 
Statistiker – der Statistikexperte
Reine Statistikstellen sind selten, daher beschreibe ich hier eher die Abgrenzung vom Data Scientist zum Statistiker. Am häufigsten sind die Statistiker (noch) in Versicherungen oder der Pharma-Branche, aber auch dort wandelt sich das Bild. In der Pharma-Branche geht es um Studiendesign, Stichprobengröße etc., also wie man die Wirksamkeit eines Medikaments nachweisen kann.
Der Statistiker ist weniger Programmierer als der Data Scientist. Das heißt nicht, dass der Statistiker nicht programmieren kann, denn das muss er durchaus. Im Normalfall sind die Programme aber nicht für den produktiven Einsatz bestimmt, sondern einmalige Analysen.
Man kann auch noch anführen, dass der Data Scientist breiteres Mathematikwissen benötigt (Analysis, Lineare Algebra, Optimierungsmethoden), insbesondere für das Verständnis von Neuronalen Netzen.
Dafür kennt sich der Statistiker natürlich am Besten mit den verschiedensten Verfahren der Statistik aus und hier kann ihm keiner das Wasser reichen. Das ist nicht zu unterschätzen, denn das korrekte Anwenden der Statistik-Methoden erfordert Wissen. Insbesondere der Bereich “Small Samplesize” (kleine Stichproben) ist das Wissen um die entsprechenden Verfahren sehr wichtig.
 

Fazit
Ihr habt gemerkt, dass Jobbeschreibungen keine exakte Wissenschaft sind. Neben großen Überschneidungen und zum Teil unscharfen Definitionen spielt auch noch Trends und wie modisch ein Unternehmen erscheinen will, eine Rolle.
Nichtsdestotrotz macht es Sinn, sich mit Anforderungsprofilen auseinanderzusetzen. Denn auf der einen Seite werden Data Scientisten händeringend gesucht. Auf der anderen Seite erfordern nicht alle Stellen, die für einen Data Scientist ausgeschrieben sind, wirklich einen Data Scientisten.
Was sind Eure Erfahrungen und Jobprofile? Schreibt mir einen Kommentar oder besucht die Data Science Deutschland – Facebookgruppe
Euer Holger (Data Scientist & Blogger)
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Datensatzformat – Quer vs. Längs – Konvertierung in Excel und R

Hej Leute,
in diesem Artikel geht es um die Struktur von Datentabellen, also wie eine Tabelle aufgebaut ist. Was gibt es denn darüber zu erzählen, denkt ihr euch vielleicht. Eine Tabelle hat Zeilen und Spalten. Das stimmt natürlich, aber dennoch gibt es zwei Varianten, die beide ihre Vor- und Nachteile haben, so dass auch beide ihre Daseins-Berechtigung haben.
Im ersten Teil des Artikels erfahrt ihr also, was ein Quer-Datensatz und was ein Längs-Datensatz ist. Dann gehen wir auf die Vor- und Nachteile der beiden Strukturen ein, welche auch erklären, wann man besser Längs oder Quer verwendet. Nun kommt der Praxisteil: Wie konvertiert man denn nun zwischen den beiden Formaten? Das erkläre ich Euch in einer bebilderten Schritt-für-Schritt-Anleitung in Excel. Für die R-Nutzer unter euch gehe ich auf die Funktionen aus den Packages tidyr, reshape2 und data.table ein, natürlich mit Code-Beispielen, damit ihr sofort loslegen könnt.

Der Quer-Datensatz – viele Spalten
Der Längs-Datensatz – viele Zeilen
Die Vor- und Nachteile der beiden Strukturen

Die Vorteile der Quer-Datenstruktur
Die Nachteile der Quer-Datenstruktur
Die Vorteile der Längs-Datenstrukur
Die Nachteile der Längs-Datenstruktur
Wann benutze ich welche Struktur?

Umwandeln in Excel

Von Längs zu Quer in Excel mittels Pivot
Von Längs zu Quer in Excel mittels Formeln
Von Quer zu Längs in Excel mittels Formeln

Umwandeln in R

spread und gather aus dem Package tidyr
melt und dcast aus dem Package data.table bzw. reshape2
Performance-Unterschiede tidyr vs data.table

 

Inhalt

Der Quer-Datensatz – viele SpaltenDer Längs-Datensatz – viele ZeilenDie Vor- und Nachteile der beiden StrukturenDie Vorteile der Quer-DatenstrukturDie Nachteile der Quer-DatenstrukturDie Vorteile der Längs-DatenstrukurDie Nachteile der Längs-DatenstrukturWann benutze ich welche Struktur?Umwandeln in ExcelVon Längs zu Quer in Excel mittels PivotVon Längs zu Quer in Excel mittels FormelnVon Quer zu Längs in Excel mittels FormelnUmwandeln in Rspread und gather aus dem Package tidyrmelt und dcast aus dem Package data.tablePerformance-Unterschiede tidyr vs data.tableFazit
Der Quer-Datensatz – viele Spalten
Der Quer-Datensatz (im Englischen Wide oder seltener un-stacked) ist die normale Tabelle, wie wir sie kennen. Jede Zeile steht für einen Eintrag, also zum Beispiel einen Kunden oder ein Produkt. Die Spalten entsprechen den Attributen. Bei Kunden könnte das Name, Adresse, Alter, Geschlecht sein, beim Produkt vielleicht Länge, Breite, Höhe, Gewicht, Farbe, Warenkategorie.

Auch bei einer Befragung ist der Quer-Datensatz typisch. Dort entspricht jede Zeile einem ausgefüllten Fragebogen, die Antworten auf die Fragen stehen in den Spalten

Der Längs-Datensatz – viele Zeilen
Der Längs-Datensatz oder auch Kontenmodell (im Englischen Long, Tall oder seltener stacked) hat nur wenige Spalten, dafür aber viele Zeilen. Denn jedes Attribut bekommt eine eigene Zeile. Im Extremfall benötigt man nur drei Spalten:

Die ID-Spalte(n), welche einen Identifikator (z.B. für einen Fragebogen, ein Produkt, einen Kunden …) enthält
Die Variable-Spalte, welche die Bezeichnung des Attributs / der Variablen enthält
Die Value-Spalte, welche die tatsächlichen Werte enthält

Die Vor- und Nachteile der beiden Strukturen
Die Vorteile der Quer-Datenstruktur
Der Quer-Datensatz hat den Vorteil, dass er die leichter lesbare und damit auch verbreitetste Struktur ist. Jede Spalte hat ihr Format (integer, float, string, Datum). Diese lassen sich gut auswerten, denn es ist ja alles schön nach Spalten getrennt.
Die Nachteile der Quer-Datenstruktur
Der Quer-Datensatz hat aber zwei Nachteile. Zum einen ist es nicht sehr speicherschonend, denn für jede ID wird für jedes Attribut ein Platz reserviert. Wenn nun die Zeilen sehr unterschiedlich sind, dann ist es nicht effizient, die Daten so zu speichern. Enthält zum Beispiel eine Tabelle zwei Fragebögen, wobei der eine 5 Fragen und der andere 100 Fragen enthält, dann muss die Tabelle trotzdem mindestens 101 (ID und 100 Variablen) Spalten haben. In den Zeilen mit dem kurzen Fragebögen sind halt nur 6 Spalten befüllt.
Der zweite Nachteil ist die Unflexibilität. Wenn man ein weiteres Attribut benötigt, dann benötigt man also eine neue Spalte. Erstmal kein Problem, wenn man nur für sich arbeitet. Aber wenn dahinter eine große Tabelle auf einer Datenbank liegt, muss diese auch angepasst werden, was Zeit kostet. Wenn nun die Daten über mehrere Systeme fließen, muss alles an die neue Struktur angepasst werden.
Die Vorteile der Längs-Datenstrukur
So kommt die Längs-Datenstruktur ins Spiel, denn diese zwei Nachteile hat sie nicht. Zum einen gibt es nur eine Zeile, wenn das Attribut auch da ist. In dem Fragebogenbeispiel kommen für einen kurzen Fragebogen maximal 5 Zeilen hinzu, für einen langen maximal 100 Zeilen. Maximal, denn werden Fragen nicht beantwortet, tauchen sie im Datensatz auch nicht unbedingt auf.

Der zweite Vorteil ist die Flexibilität, denn kommt noch ein Attribut hinzu, dann kommt eine Zeile hinzu. Fällt eine Variable weg (z.B. neue Version eines Fragebogens), dann ändert sich nichts. Die Struktur der Tabelle bleibt identisch.
Die Nachteile der Längs-Datenstruktur
OK, aber natürlich hat so ein Längs-Datensatz auch ein paar Nachteile. Zum einen gestalten sich die Auswertungen als nicht ganz so intuitiv, insbesondere, wenn man mit fehlenden Werten zu tun hat.
Zum anderen machen verschiedene Datentypen ein Problem. Denn hat man ein numerisches Attribut und ein Text-Attribut, muss man die Value-Spalte als String definieren. Das verlangsamt wiederum die Auswertungen, denn der String muss ja wieder in eine Zahl umgewandelt werden, wenn man zum Beispiel einen Mittelwert berechnen will.
Dieses Problem kann man aber umgehen, indem man für jeden Datentyp eine eigene Value-Spalte definiert, also eine Spalte für numerische Werte, eine für Text und eine für Datumsangaben. Aber auch das Vorgehen hat Nachteile, denn nun sind ja wieder viele Felder leer.

Wann benutze ich welche Struktur?
Eigentlich ergeben sich die Anwendungen automatisch aus den Vor- und Nachteilen. Die erste Regel ist, dass bei der Weitergabe an jemanden nicht IT-affinen die Quer-Struktur (viele Spalten) verwendet werden sollte. Generell ist meistens die Quer-Struktur vorzuziehen, denn diese ist intuitiver und die meisten Programme sind darauf ausgelegt. Ist aber der Inhalt der Daten variabel bzw. unterliegt häufiger mal Änderungen, die Struktur aber nicht ohne weiteres zu ändern (z.B. SQL-Tabellen), dann sollte man die Längs-Struktur verwenden. Auch bei späterer interaktiver Pivotierung, z.B. in Excel, kann man einfacher mit der Längs-Struktur arbeiten.
Zum Glück kann man relativ problemlos, je nach Größe des Datensatzes, einen Quer-Datensatz in einen Längs-Datensatz und umgekehrt konvertieren.
Umwandeln in Excel
Von Längs zu Quer in Excel mittels Pivot
Einen Längs-Datensatz in einen Quer-Datensatz umzuwandeln, ist in Excel ganz einfach. Aber nur, wenn nur numerische Werte (oder Datumswerte) vorhanden sind. Dafür erzeugt man eine Pivot-Tabelle.

Zuerst wählt ihr den Datensatz aus und erzeugt einen PivotTable
Die ID-Variable (hier BogenNr) gehört in die Zeilenbeschreibung, die Variable-Spalte in die Spaltenbeschreibung und die Wert-Spalte in Werte
Nun noch die Aggregation umstellen von Anzahl auf irgendetwas anderes (Summe, Mittelwert, Min, Max). Es sollte eh nur einen Wert pro Zelle geben, deshalb ist es egal. Nur eben nicht die Standard-Einstellung Anzahl.
Jetzt noch das Datumsfeld korrekt formatieren (Shortcut Strg+1)

Von Längs zu Quer in Excel mittels Formeln
Wir sehen, dass die Wörter (Strings) im Pivot nicht korrekt übertragen werden. Es gibt ein paar Varianten, wie man das erreichen kann. Ich zeige euch die einfachste, die aber mit etwas Handarbeit verbunden ist.

Zuerst fügt man eine Spalte an den Längs-Datensatz an, nämlich einen Schlüssel. Das ist einfach die Kombination der zwei Spalten BogenNr und Variable, getrennt durch das Zeichen #. So kann jede Zeile eindeutig identifiziert werden.
Nun bauen wir das Gerüst der Tabelle. Ihr könntet die Ausprägungen der BogenNr und die Ausprägungen der Variable abtippen. Weniger Arbeit ist es allerdings, die Werte zu kopieren und dann die Duplikate zu entfernen. Bei den Spalten kopiert ihr das Ergebnis nochmal, fügt es aber transponiert ein.
Nun bauen wir uns die Formel per INDEX-VERGLEICH. Die Kombination der beiden Befehle funktioniert wie SVERWEIS, ist allerdings etwas flexibler. Beim SVERWEIS hätten wir den Schlüssel in die erste Spalte schreiben müssen.
Wir ergänzen noch eine kleine Abfrage mit WENNFEHLER, damit fehlende Werte leere Felder sind. Dieser Schritt ist nicht unbedingt nötig und eigentlich ist es auch korrekter, die fehlenden Werte mit #NV zu bezeichnen. Aber die Tabelle sieht etwas netter aus.
Letzter Schritt: Noch das Datumsfeld wieder vernünftig formatieren, also auf Datumsformat umstellen.

 
Man kann nun noch den Prozess, die Quer-Tabelle aufzubauen, auch in Formeln packen. Meiner Meinung nach ist es den Aufwand aber nicht wert. Denn wenn ich etwas automatisieren möchte, dann benutze ich direkt R oder Python.
Von Quer zu Längs in Excel mittels Formeln
Die umgekehrte Richtung, also von Querformat zu Längsformat können wir auch mit ein bisschen Formelarbeit in Excel realisieren. Allerdings ist es nicht die platzsparende Version, in der nicht ausgefüllte Felder gar nicht erst auftauchen. Aber das kann man im Nachhinein durch Sortierung und dann Löschen schnell erreichen.

Zuerst benötigen wir eine Durchnummerierung der Zeilen. Der (vollständige) Längsdatensatz hat genau (Anzahl Zeilen) * (Anzahl Spalten-1) des Querdatensatzes an Zeilen.
In der zweiten Spalte kommt der Identifikator (hier BogenNr). Hier werden alle BogenNr einfach wiederholt, bis die Tabelle voll ist. Ich habe das durch Modulo-Rechnung (Excel-Befehl REST) gelöst.
Nun kommt die Spalte mit der Variablenbezeichnung. Dazu zählen wir, wie häufig die BogenNr schon vorgekommen ist (Excel-Befehl ZAEHLENWENN) und picken dementsprechend die entsprechenden Spaltennamen aus dem Querdatensatz raus.
Als letzter Schritt kommt noch der tatsächliche Zelleneintrag in die Werte-Spalte. Das lässt sich einfach per INDEX-VERGLEICH lösen. Wir brauchen eben zwei Vergleiche, einen für die Zeile, einen für die Spalte.

 
Wir sehen, auch diese Umwandlung ist nicht perfekt, denn das Datum wird nun wieder in der internen Darstellung (als ganze Zahl der Tage seit dem Ursprungsdatum, 01.01.1900 entspricht der 1) angezeigt.
 
Umwandeln in R
In R gibt es mehrere Funktionen, um aus einem Längs- ein Querdatensatz und umgekehrt zu machen, da mehrere Packages entsprechende Funktionen bereitstellen.
Also soweit man das nachvollziehen kann, war reshape2 von Hadley Wickam das erste Package, welches die Konvertierung längs-quer und quer-längs (Befehle melt und dcast) implementiert hat. Wickam hat aber im Package tidyr andere Funktionen (spread und gather) entwickelt, welche mehr zur Syntax des tidyverse passen. Insofern dürfte reshape2 nicht weiterentwickelt werden. Die Konvertierungsfunktionen in data.table (auch melt und dcast) haben die gleiche Syntax wie in reshape2 und rufen die Funktionen aus reshape2 auf, sofern die Daten nur als data.frame und nicht als data.table vorliegen. Sind die Daten allerdings ein data.table, dann werden eigene, für große Datenmengen optimierte, Funktionen aufgerufen.
spread und gather aus dem Package tidyr
Im tidyverse, genauer im R-Package tidyr, gibt es die beiden Funktionen spread und gather.  spread breitet die Daten aus, also von längs zu quer. gather sammelt die Daten ein, also von quer zu längs.
Bei spread gibt man als Parameter neben dem Datensatz noch key und value an. Key ist die Spalte im Längsdatensatz, welche den Variablen-/Attributnamen enthält. Value ist die Spalte, welche die tatsächlichen Werte enthält.
Bei gather gibt man auch key und value an, hier sind es aber die Namen, welche der konvertierte Datensatz haben soll. Dann übergibt man noch die Spalten, welche zusammengefasst werden sollen. In unserem Beispiel sind das Frage 1 bis Frage 5. Alternativ und deutlich kürzer schließt man mit Minus die Spalten aus, die erhalten bleiben sollen, in unserem Fall BogenNr.
library(tidyr)

Laengs <- data.frame(stringsAsFactors=FALSE,
BogenNr = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5),
Variable = c("Frage 1", "Frage 2", "Frage 3", "Frage 4", "Frage 5",
"Frage 1", "Frage 2", "Frage 1", "Frage 4", "Frage 5",
"Frage 1", "Frage 2", "Frage 3", "Frage 4", "Frage 1"),
Wert = c("20", "gut", "22.05.2019", "ja", "immer", "25", "schlecht",
"12", "nein", "selten", "18", "teils/teils", "06.12.2018",
"ja", "27")
)

# Umwandeln in Querdatensatz mittels der Funktion spread
# Parameter sind key: Spalte mit Attribut-/Variablenname und value: die Spalte mit den Werten
Quer <- spread(Laengs, key = Variable, value = Wert)
# Alternativ mit Pipe-Schreibweise
Quer <- Laengs %>% spread(key = Variable, value = Wert)

# Umwandeln von Quer nach Längs
gather(Quer, key = Variable, value = Wert, -BogenNr)
# Alternativ mit Pipe-Schreibweise
Quer %>% gather(key = Variable, value = Wert, -BogenNr)

Mehr Informationen zum Package tidyr findet ihr unter https://tidyr.tidyverse.org.
melt und dcast aus dem Package data.table
Im Package data.table gibt es die Funktionen dcast und melt. dcast konvertiert einen data.frame oder data.table von längs zu quer. melt ist die Umkehrung, konvertiert als von quer zu längs.
Sind die Daten in einem data.frame, dann ruft auch data.table die entsprechenden Funktionen aus reshape2 auf. Sind die Daten aber ein data.table (mit setDT einfach umzuwandeln), dann werden die performanteren dcast- und melt-Funktionen aus data.table verwendet. Zudem gibt es bei diesen die Möglichkeit, die Variablen-Spalte nicht als Faktor zurückzubekommen.
Bei melt werden als Parameter id.vars die ID-Variable(n) übergeben. Alle anderen Spalten werden zusammen konvertiert. Zudem können die Spaltennamen der Variablenspalte und der Werte-Spalte übergeben werden. Wird der Parameter variable.factor auf FALSE gesetzt, ist die Variablenspalte vom Typ chr (String), andernfalls vom Typ factor. Achtung: Der Parameter variable.factor funktioniert nur, wenn tatsächlich ein data.table vorliegt. Bei einem data.frame geht das nicht.
Bei dcast wird eine Formel übergeben, die die Konvertierung definiert. Auf der linken Seite ist die ID-Variable, rechts von der Tilde steht die Spalte mit den Variablen-/Attributnamen. Zusätzlich kann man mit dem Parameter value.var bestimmen, welche Spalte(n) die Werte enthalten. Ist dieser Parameter nicht angegeben, dann werden alle verbleibenden Spalten verwendet.
# melt und dcast vom Package data.table
library(data.table)

# Umwandeln von Quer zu Längs mittels melt
Laengs <- melt(Quer, id.vars = "BogenNr", variable.name = "Variable",
value.name = "Wert")

# damit die performante melt-Funktion zum Einsatz kommt, muss ein data.table vorliegen
QuerDT <- setDT(Quer)
LaengsDT <- melt(QuerDT, id.vars = "BogenNr",variable.name = "Variable",
value.name = "Wert", variable.factor = FALSE)
# explizite Angabe der umzuwandelnden Spalten (measure.vars)
LaengsDT <- melt(QuerDT, id.vars = "BogenNr",
measure.vars = c("Frage 1","Frage 2", "Frage 3", "Frage 4", "Frage 5"),
variable.name = "Variable", value.name = "Wert")

# Umwandeln von Längs zu Quer mittels dcast
QuerDT <- dcast(LaengsDT, BogenNr~Variable, value.var = "Wert")

Mehr Informationen zum Package data.table, welches viele Funktionen und Strukturen bereitstellt, um super schnell mit großen Datenmengen umzugehen, findet ihr unter https://github.com/Rdatatable/data.table/wiki/Getting-started.
Speziell zur Umwandlung der beiden Datenformate gibt es auch noch genaueres unter https://cran.r-project.org/web/packages/data.table/vignettes/datatable-reshape.html.
Performance-Unterschiede tidyr vs data.table
In unserem kleinen Beispiel haben beide bei jeweils einer Aufgabe die Nase vorn: spread war schneller als dcast und melt war schneller als gather. Der Fairness halber muss man aber sagen, dass dcast mehr Möglichkeiten bietet, so kann gleichzeitig eine Aggregation/Gruppierung stattfinden.
library(rbenchmark)
benchmark(
spread = spread(Laengs, key = Variable, value = Wert),
dcast = dcast(LaengsDT, BogenNr~Variable, value.var = "Wert"),
replications = 1000
)
benchmark(
gather = gather(Quer, key = Variable, value = Wert, -BogenNr),
melt = melt(QuerDT, id.vars = "BogenNr", variable.name = "Variable",
value.name = "Wert", variable.factor = FALSE),
replications=1000
)

Auch in einem größeren Beispiel zeigen sich die gleichen Unterschiede. Allerdings wird dcast immer besser, je größer der Datensatz ist. Bei 100.000 Zeilen ist dcast schneller. Interessanterweise sind die ursprünglichen melt- und dcast-Funktionen aus reshape2 deutlich schneller als die aus dem Package data.table. Das wundert mich doch sehr. Seht ihr, warum das so ist?
library(rbenchmark)
library(data.table)
library(tibble)
library(tidyr)
n = 1000000
set.seed(93)
BspQuer <- data.frame(ID=1:n,x = rnorm(n), y = runif(n), z = rbinom(n,1,0.3))
BspQuerDT <- setDT(BspQuer)
BspQuerTibble <- as_tibble(BspQuer)
BspLaengsDT <- melt(BspQuer, id.vars = "ID",variable.name = "Variable",
value.name = "Wert", variable.factor = FALSE)
BspLaengsTibble <- gather(BspQuer, key = Variable, value = Wert, -ID)

benchmark(
spread = spread(BspLaengsTibble, key = Variable, value = Wert),
dcastDT = dcast(BspLaengsDT, ID~Variable, value.var = "Wert"),
dcastReshape2 = dcast(BspLaengs, ID~Variable, value.var = "Wert"),
replications = 100
)

benchmark(
gather = gather(BspQuerTibble, key = Variable, value = Wert, -ID),
meltReshape2 = melt(BspQuer, id.vars = "ID",variable.name = "Variable",
value.name = "Wert"),
meltDT = melt(BspQuerDT, id.vars = "ID",variable.name = "Variable",
value.name = "Wert", variable.factor = FALSE),
replications = 500
)

 
Fazit
Habt ihr bis zum Ende durchgehalten? Perfekt! Ich hoffe, die Unterschiede in der Struktur zwischen einem Datensatz im Quer- oder Längs-Format sind nun klar. Ihr versteht, wann ihr welche Struktur einsetzt, weil ihr die Vor- und Nachteile kennt. Und die Schritt-für-Schritt-Anleitung in Excel und die R-Befehle waren hilfreich, so dass ihr einsatzbereit seid.
Happy Coding,
Euer Holger
 
 
 
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda

Das R-Package dplyr: Eine ausführliche Anleitung (mit vielen Beispielen)

Hej Leute,
heute stelle ich Euch ein super nützliches R-Package namens dplyr vor. Dieses dient der sogenannten Datenmanipulation. Damit ist aber nicht die negative Bedeutung von Manipulation, also Fälschung gemeint, sondern einfach häufige Aufgaben wie neue Spalten zu einer Tabelle hinzufügen, eine Tabelle nach bestimmten Werten zu filtern (wie der Filter in Excel) oder auch nach Klassen zu gruppieren.
Keine Angst, wir gehen Schritt für Schritt vor. Ich erkläre, wie ihr das Package installiert und dann schauen wir uns die wichtigsten R-Befehle von dplyr an, natürlich alle mit Beispielen versehen. Und am Ende kommen wir dann zu JOINs, also dem Verbinden von zwei data.frames. Das ist ein ganz wichtiges Konzept beim Arbeiten mit Datenbanken. Dazu gibt es noch ein praktisches Cheat Sheet, also eine Übersichtsseite zum Nachschlagen. Die könnt ihr kostenlos herunterladen und ausdrucken.
Der Artikel ist doch ziemlich lang geworden, ich will euch ja nichts vorenthalten. Wer es eilig hat und einfach nur einen der dplyr-Funktionen anwenden will, springt einfach zum entsprechenden Abschnitt:

Was ist das R-Package dplyr

dplyr vs. data.table

Installation von dplyr
Was bedeutet der R-Code „%>%“? Die Pipe aus magrittr
Die wichtigsten Funktionen von dplyr – R

Die Funktion dplyr::mutate und dplyr::transmute – Spalten ergänzen
Die Funktionen dplyr::select und dplyr::rename – Spalten auswählen und umbenennen
Die Funktion dplyr::filter – Zeilen auswählen
Die Funktionen dplyr::summarise und dplyr::group_by – Aggregation
Die Funktion dplyr:: arrange – Sortieren

Zwei Datensätze miteinander verbinden – joins

Für die vier join-Varianten von dplyr habe ich für euch eine Übersichtsseite zusammenstellt, die hoffentlich nützlich ist. Der Link ist im letzten Abschnitt Zwei Datensätze miteinander verbinden.

Inhalt

Was ist das R-Package dplyr?dplyr vs. data.tableInstallation von dplyrWas bedeutet der R-Code „%>%“? Die Pipe aus magrittrDie wichtigsten Funktionen von dplyr – RDie Funktionen dplyr::mutate und transmuteDie Funktion dplyr::select und renameDie Sache mit den Anführungszeichen – Variablen mit SpaltennamenNützliche Funktionen für select, aber auch andere dplyr-BefehleDie Funktion dplyr:: filterDie Funktion dplyr:: summarise/summarize und dplyr::group_byDie Funktion dplyr:: arrangeZwei data.frames miteinander verbinden – joinsFazit
Was ist das R-Package dplyr?
Dplyr wurde 2014 von Hadley Wickham entwickelt (https://blog.rstudio.com/2014/01/17/introducing-dplyr/) und hat sich seitdem rasant verbreitet. Wie oben schon geschrieben erleichtert das Package die Aufbereitung von Datensätzen, indem es einfach zu nutzende Funktionen für die üblichen Aufgaben bereitstellt wie z.B. für die Auswahl von Spalten (select), nach gewissen Kriterien die Zeilen filtern (filter) oder Werte zu aggregieren (summarise). Der zu bearbeitende Datensatz muss als data.frame oder tibble (die data.frame-Variante im tidyverse) vorliegen, also einer Tabelle mit mehreren Spalten und vielen Zeilen.
Im Prinzip sind viele diese Aufgaben vergleichbar mit dem SQL-Befehl select. Ist ja auch logisch, in SQL geht es schließlich auch um die Verarbeitung von Tabellen. Man könnte also für viele Befehle auch das Package sqldf nehmen, welches es erlaubt, SQL-Befehle auf data.frames loszulassen. Macht natürlich nur Sinn, wenn man sich ein wenig mit SQL auskennt. Ein ausführlicher Artikel ist in Planung, aktuell müsst ihr euch noch mit einem R-Bite, also nur einem Mini-Happen, zu SQL-Befehlen in R mit sqldf begnügen.
Die Stärke von dplyr liegt im klar strukturierten Aufbau: Die Befehle sind als Verb benannt. Das erste Argument ist immer die Datentabelle (data.frame), die weiteren Argumente beschreiben, was genau zu tun ist und als Rückgabe gibt es wieder einen data.frame.
dplyr vs. data.table
Als Alternative möchte ich noch das Package data.table nennen. Mittlerweile ist ein regelrechter Kampf entstanden, welches Package denn besser geeignet sei. Die Syntax ist jedenfalls grundlegend verschieden. Tendenziell wird dplyr als etwas einfacher in der Anwendung beschrieben (was Anwender von data.table verneinen), dafür ist data.table insbesondere bei großen Datensätzen schneller. Es muss aber jeder selber entscheiden, welches Package er bevorzugt. Ich nutze einfach beide abhängig von der Anwendung.
Installation von dplyr
dplyr ist ein ganz normales Package in R, d.h. ihr müsst es einmalig mit install.packages(“dplyr”)  herunterladen und installieren. Im Anschluss genügt es dann, das Package mittels library(dplyr) einzubinden.
Da dplyr ein Teil des tidyverse ist, funktioniert alternativ die Installation auch mit install.packages(“tidyverse”), womit ihr alle Packages, die im tidyverse enthalten sind, installiert.
Wenn dplyr einmal heruntergeladen und installiert ist, wird es einfach mit library(dplyr) oder require(dplyr) eingebunden. Dabei spuckt R folgende Warnung heraus:

Was bedeuten diese dplyr-Warnungen? R macht nur darauf aufmerksam, dass einige Funktionen aus dem Package dplyr genauso heißen wie welche aus den Package stats und base, welche standardmäßig in R geladen werden. D.h. wenn wir nun filter, lag, intersect, setdiff, setequal oder union verwenden, wird die Funktion dieses Namens von dplyr aufgerufen und nicht mehr die „Standard“-Funktion. Wenn man den Package-Namen gefolgt von zwei Doppelpunkten voranstellt, kann man aber dennoch darauf zugreifen. Also stats::filter, stats::lag, base::intersect, base::setdiff, base::setequal und base::union, falls man das möchte.
 
Was bedeutet der R-Code „%>%“? Die Pipe aus magrittr
Dieses komische Zeichen „Prozent – größer – Prozent“ nennt sich Forward-Pipe und ist ein Konzept, welches die Lesbarkeit von R-Code erhöhen soll. Prinzipiell braucht man sie nicht, die Pipe hat sich aber als so praktisch erwiesen, dass sie heute vielfach verwendet wird. Insbesondere im tidyverse, also dplyr, ggplot2 etc., ist sie nicht mehr wegzudenken.
Aber was macht sie denn nun. Ganz einfach eigentlich: Statt f(x, …) schreibt man nun x %>% f(…). Warum ist das so nützlich? Weil damit der verschachtelte Aufruf von Funktionen viel lesbarer wird. Stellt euch vor, wir wollen auf einen Datensatz df zuerst die Funktion f anwenden und auf das Ergebnis die Funktion g. Dann wäre der normale Code g(f(df)). Mit der magrittr-Pipe wäre dass df  %>% f %>% g und entspricht der Leserichtung von links nach rechts. Kommen nun noch Parameter in Spiel, z.B. p1 und p2 für f sowie p3 für g, dann wäre der konventionelle R-Code g(f(df,p1,p2),p3). Mit der Pipe df %>% f(p1,p2) %>% g(p3).
Diese Pipe wurde 2014 von Stefan Milton Bache mit dem Package magrittr in R (benannt nach dem Bild einer Pfeife von René Magritte) umgesetzt und von Hadley Wickham in das tidyverse integriert. Wenn ihr also ein Paket aus dem tidyverse ladet, bekommt ihr automatisch die Pipe-Funktionalität dazu. Wer sich für die Entstehung interessiert: Ursprünglich kommt die Pipe Forward wohl aus der Programmiersprache F#, dort allerdings mit dem Syntax „|>“. Diese Funktionalität fand Bache so praktisch, dass er sie in R umsetze. Schaut mal auf diesen Blogeintrag von R-Statistics.com.
Übrigens, der Shortcut in R-Studio für die Pipe ist Strg + Shift + M. In meinem Blogartikel über die nützlichsten RStudio Shortcuts findet ihr noch 14 weitere.
 
Die wichtigsten Funktionen von dplyr – R
Die Funktionen dplyr::mutate und transmute
Der R-Befehl mutate ergänzt einen Datensatz um eine oder mehrere Spalten. Der häufigste Fall ist die Berechnung aus anderen Spalten. Aber auch Ergänzung von Konstanten oder das Löschen einer Spalte kann man mit mutate erreichen. Als Rückgabewert wird der „mutierte“ Datensatz zurückgegeben.
Eine Variante ist die dplyr-Funktion transmute. Das funktioniert ganz genauso, nur der Rückgabewert ist nicht der komplette Datensatz, sondern nur die „mutierten“ Spalten.
###############################################################################
#
# Beispiele für dplyr::mutate
#
###############################################################################

# Im Package mlbench sind viele Standard-Datensätze enthalten
# In diesem Beispiel verwenden wir BostonHousing
install.packages("mlbench")
# AUflistung der enthaltenen Datensätze
library(help = "mlbench")

library(mlbench)
library(dplyr)
data(BostonHousing)

## Eine Spalte mit dem quadrierten Alter hinzufügen
# in Pipe-Schreibweise
BostonHousing <- BostonHousing %>% mutate(age2 = age*age)
# klassische Schreibweise
BostonHousing <- mutate(BostonHousing, age2 = age*age)
# Varianten ohne dplyr (Base-R)
BostonHousing$age2 <- BostonHousing$age * BostonHousing$age
BostonHousing$age2 <- with(BostonHousing, age2 <- age*age)
BostonHousing$age2 <- BostonHousing %>% with(age2 <- age*age)

## eine Spalte löschen
# in pipe-Schreibweise
BostonHousing <- BostonHousing %>% mutate(age2 = NULL)
# klassische Schreibweise
BostonHousing <- mutate(BostonHousing, age2 = NULL)
# ohne dplyr (Base-R)
BostonHousing$age2 <- NULL
BostonHousing <- BostonHousing[,colnames(BostonHousing)!="age2"]

## transmute gibt einen data.frame nur mit den neu berechneten Spalten zurück
BostonHousing$ID <- 1:nrow(BostonHousing)
# in Pipe-Schreibweise
mittlererAbstand <- BostonHousing %>% transmute(ID, avgDis = dis/mean(dis))
# klassische Schreibweise
mittlererAbstand <- transmute(BostonHousing, ID, avgDis = dis/mean(dis))

## Sind die zu verrechnende Variablen als Strings vorhanden,
# wird die Syntax etwas kompliziert
a = "crim"
b = "nox"
BostonHousing <- BostonHousing %>% mutate(Summe = .data[[a]] + .data[[b]])
# Varianten ohne dply
BostonHousing$Summe <- BostonHousing[[a]] + BostonHousing[[b]]
BostonHousing$Summe <- BostonHousing$a + BostonHousing$b

## Der Vorteil von dplyr zeigt sich bei der Bearbeitung von mehreren Spalten
BostonHousing <- BostonHousing %>%
mutate(
age2 = age * age,
rad = NULL,
v1 = dis / mean(dis),
v2 = tax / dis,
v3 = 100
)

Die Funktion dplyr::select und rename
Der R-Befehl select wählt eine oder mehrere Spalten aus. Nicht mehr und nicht weniger. Klingt eigentlich überflüssig, denn dafür gibt es schon mehrere Varianten im Standard-R (Base-R). Macht aber doch Sinn, denn select hat mehrere entscheidende Vorteile:

es erzeugt sehr gut lesbaren Code
man kann auf die Anführungszeichen verzichten
es funktioniert mit der Pipe, also dem Hintereinander Ausführen mehrerer Schritte
die Auswahl bzw. Abwahl von Spalten ist flexibel und hat einige nützliche Kniffe (siehe Code-Beispiel)

Während select nur einen data.frame mit den ausgewählten Spalten zurückgibt, gibt die dplyr-Funktion rename den ganzen data.frame zurück. Diese Funktion ist, wie der Name schon sagt, zum Umbenennen von Spalten da.
Wenn Du statt Spalten auszuwählen Zeilen auswählen willst, dann ist der Befehl filter der richtige.
###############################################################################
#
# Beispiele für dplyr::select
#
###############################################################################

library(dplyr)
library(mlbench)

data(BostonHousing)

# Auswahl von zwei Spalten
Boston2 <- BostonHousing %>% select(age, nox)
# Auswahl von allen Spalten zwischen "nox" und "age"
Boston2 <- BostonHousing %>% select(nox:age)
# Auswahl der ersten 5 Spalten
Boston2 <- BostonHousing %>% select(1:5)
# Auswahl aller Spalten außer der ersten
Boston2 <- BostonHousing %>% select(-1)
# Auswahl aller Spalten außer der ersten fünf
Boston2 <- BostonHousing %>% select(-1:-5)
# Auswahl aller Spalten außer "tax"
Boston2 <- BostonHousing %>% select(-"tax")
# Reihenfolge umdrehen
Boston2 <- BostonHousing %>% select(ncol(BostonHousing):1)
# Auswahl aller Spalten, die auf s enden
Boston2 <- BostonHousing %>% select(ends_with("s"))

# Mit rename werden Spalten umbenannt
BostonHousing <- BostonHousing %>% rename(CrimeRate = crim)
BostonHousing <- BostonHousing %>%
rename(CrimeRate = crim,
Distance = dis,
Steuer = tax)

# im Gegensatz zu den anderen dplyr-Funktionen unterstützen select auch
# Strings und Vektoren
Spaltennamen <- c(Stickoxid="nox",Autobahn="rad",Schule="ptratio")
Boston2 <- BostonHousing %>% select(Spaltennamen)
# bei rename funktioniert das nur, wenn alle Spalten benannte werden
# Fehler: Boston2 <- BostonHousing %>% rename(Spaltennamen)
# Um das zu umgehen, benutzt man die "richtige" Schreibweise mit doppeltem
# Ausrufezeichen
Boston2 <- BostonHousing %>% rename(!!Spaltennamen)

Spaltennamen <- c("nox","rad")
Boston2 <- BostonHousing %>% select(one_of(Spaltennamen))

Die Sache mit den Anführungszeichen – Variablen mit Spaltennamen
Das ist tatsächlich – man glaubt es nicht – ein kompliziertes Thema. Was ist, wenn man einem dplyr-Befehl statt direkt die Spaltennamen (ohne Anführungszeichen) eine Variable mit dem Spaltennamen oder einen Vektor mit Spaltennamen (mit Anführungszeichen) übergeben will?
Im Gegensatz zu den meisten anderen Funktionen verstehen select direkt Vektoren mit den Spaltennamen. Stellt euch vor, ihr wollt am Anfang vom Skript die zu untersuchenden Variablen definieren (übrigens sehr zu empfehlen, sauberer Programmierstil). Oder das Programm entscheidet dynamisch, welche Spalten ausgewählt werden sollen. Dann stehen die Spaltennamen in String-Variablen oder –Vektoren. Die meisten Funktionen aus dplyr können damit nicht ohne weiteres umgehen (das hat durchaus Sinn, denn dadurch werden Doppeldeutigkeiten verhindert), bei select funktioniert es aber problemlos.
Bei rename benötigen wir den !!-Operator (siehe Code-Beispiel)
Wer mehr über non-standard-evaluation und quote/unquote erfahren will, findet in diesem Artikel auf der tidyverse-Seite eine gute Erklärung.
Nützliche Funktionen für select, aber auch andere dplyr-Befehle
dplyr liefert einige hilfreiche Funktionen mit, die die Auswahl mehrerer Spalten mit rename oder select erleichtern. Diese Funktionen können aber auch in den anderen Befehlen wie filter angewendet werden. Wer es genau nimmt: Die Befehle sind im Package tidyselect, welches aber von dplyr geladen wird.

starts_with – Wie der Name schon sagt, liefert starts_with die Spalten zurück, die mit der im Parameter angegebenen Zeichenkette anfangen
ends_with – Wie starts_with, nur eben für die Endung
contains – auch diese Funktion analog zu starts_with und ends_with. Hier muss einfach nur der String im Spaltennamen enthalten sein
matches – Auswahl der Spalten über einen regulären Ausdruck. Mehr über reguläre Ausdrücke und die Base-R-Befehle grepl, gsub, … erfahrt ihr übrigens in diesem R-HowTo zu RegEx
num_range – Auswahl von Spaltennamen, welche durchnummeriert sind. Also zum Beispiel V01, V02, V03, …
one_of – Auswahl der Spalten, deren Namen per String-Vektor angegeben werden. Bei select funktioniert das impliziert (siehe Abschnitt über Anführungszeichen)
everything – eben alle Spalten
last_col – eben die letzte Spalte

Die Funktion dplyr:: filter
Mit dem dplyr-Befehl filter lassen sich Zeilen aus einem data.frame filtern. D.h. der Datensatz wird nach bestimmten Bedingungen reduziert, z.B. Alter > 50. Also ganz simpel, aber mit eine der häufigsten Anwendungen, denn meist wollen wir nur gewisse Daten analysieren oder zum Beispiel zwei Gruppen miteinander vergleichen.
###############################################################################
#
# Beispiele für dplyr::filter
#
###############################################################################

library(mlbench)
library(dplyr)
data(BostonHousing)

# nur Zeilen mit Spalte rad größer als 8
B2 <- BostonHousing %>;% filter(rad<8)
# alternativ ohne Pipe
B2 <- filter(BostonHousing,filter(rad<8))
# in Base-R würde das so aussehen
B2 <- BostonHousing[BostonHousing$rad<8,]

# nur Zeilen mit Kriminalität unterdurchschnittlich und
# Schüler-Lehrer-Ratio größer als 75%-Quantil
B2 <- BostonHousing %>% filter(crim < mean(crim) & ptratio>quantile(ptratio,0.75))

# Vergleich den Mittelwert des Lehrer-Schüler-Ratio zwischen unterdurchschnittlicher
# und überdurchschnittlicher Kriminalität
B1 <- BostonHousing %>% filter(crim > mean(crim))
B2 <- BostonHousing %>% filter(crim > mean(crim))
t.test(B1$ptratio,B2$ptratio)

# alternativ mittels einer gruppierenden Variablen crimHoch
B2 <- BostonHousing %>% mutate(crimHoch=crim < mean(crim))
t.test(ptratio~crimHoch,B2)

Die Funktion dplyr:: summarise/summarize und dplyr::group_by
Das Package dplyr beinhaltet auch die R-Funktion summarize oder summarise (je nachdem ob man britisches oder amerikanisches Englisch lieber mag). Mit dieser lassen sich Daten aggregieren, also z.B. eine Summe oder ein Mittelwert bilden.
Insbesondere in Kombination mit der dplyr-Funktion group_by entfaltet summarize seine Wirkung. group_by funktioniert wie der SQL-Zusatz group by zum select-Befehl. Man unterteilt damit den Datensatz in verschiedene Gruppen. Kombiniert man nun summarize und group_by, lassen sich bequem Aggregate in diesen Gruppen berechnen. Im Endeffekt ähnlich zur aggregate-Funktion von Base-R, nur komfortabler.
###############################################################################
#
# Beispiele für dplyr::summarize bzw. dplyr::summarise
#
###############################################################################

library(mlbench)
library(dplyr)
data(BostonHousing)

BostonHousing %>% summarise(MW = mean(crim), SD = sd(crim))
BostonHousing %>% group_by(rad) %>% summarise(MW = mean(crim), SD = sd(crim))

Die Funktion dplyr:: arrange
Der R-Befehl arrange ordnet einen Datensatz entsprechend der Variablen, die als Parameter übergeben werden. Viel mehr gibt es darüber gar nicht zu sagen, schaut euch das Code-Beispiel an. Um eine umgekehrte Reihenfolge zu erhalten, gibt es die Funktion desc. Bei numerischen Spalten geht natürlich auch einfach das Minus-Zeichen, aber für Strings und Faktoren braucht man desc.
###############################################################################
#
# Beispiele für dplyr::arrange
#
###############################################################################

library(mlbench)
library(dplyr)
data(BostonHousing)

# sortiert nach Spalte crim
BostonHousing <- BostonHousing %>% arrange(crim)
# klassisch
BostonHousing <- BostonHousing[order(BostonHousing$crim),]

# umgekehrte Reihenfolge
BostonHousing <- BostonHousing %>% arrange(desc(crim))
BostonHousing <- BostonHousing %>% arrange(-crim)
# bei Faktoren oder Strings geht - nicht, da braucht man desc
BostonHousing <- BostonHousing %>% arrange(desc(chas))

# klassisch
BostonHousing <- BostonHousing[order(-BostonHousing$crim),]

# sortiert nach Spalte chas und dann crim
BostonHousing <- BostonHousing %>% arrange(chas,crim)

Zwei data.frames miteinander verbinden – joins
Kommen wir nun zu einem zentralen Bestandteil vom Datenmanagement, dem Verbinden von zwei Tabellen mittels merge oder join. Der R-Befehl merge wird in Base-R bzw. data.table verwendet, join kommt aus SQL und es gibt genau vier Varianten davon.
Wie funktioniert das grundsätzlich? Zum einen können Tabellen ganz einfach aneinander gehangen werden (untereinander oder nebeneinander), zum anderen können Tabellen anhand eines eindeutigen Identifikators (z.B. Kundennummer, Seriennummer, Name) verbunden werden. D.h. die Zeile aus Tabelle 2 wird neben die Zeile aus Tabelle 1, die den gleichen Identifikator hat, gepackt.
Wie oben erwähnt, gibt es vier Varianten mit den Identifikator-Werten umzugehen, welche nicht übereinstimmen

inner join: Es bleiben nur die Zeilen übrig, deren Identifikator in beiden Tabellen vorkommen.
left join: Die zuerst genannte Tabelle (also die linke) bleibt vollständig erhalten. Gibt es kein Pendant in der zweiten Tabelle, wird mit NAs aufgefüllt.
right join: Genau umgekehr zu left join. Die zweite Tabelle bleibt komplett erhalten. Werte, die keine Entsprechung in der ersten Tabelle haben, werden mit NAs aufgefüllt.
full (outer) join: Sowohl die erste als auch die zweite Tabelle bleiben vollständig erhalten.

Das dplyr join-Cheatsheet als pdf zum Nachschlagen könnt ihr gerne kostenlos herunterladen.
Gibt man nichts explizit an, werden alle gleichnamigen Spalten als Identifikatoren verwendet. Man kann aber auch über den Parameter by die Identifikator-Spalten definieren.
###############################################################################
#
# Beispiele für dplyr::inner_join, left_join, right_join, full_join
#
###############################################################################

library(dplyr)

table1 <- data.frame(ID=1:10,x=rnorm(10))
table2 <- data.frame(ID=5:14,y=rnorm(10))

a = inner_join(table1,table2)
b = left_join(table1,table2)
c = right_join(table1,table2)
d = full_join(table1,table2)

# zwei Spalten als Identifikatoren
table1 <- data.frame(ID1=1:10,ID2=c("rot","grün"),x=rnorm(10),stringsAsFactors = FALSE)
table2 <- data.frame(ID1=5:14,ID2=c("rot","schwarz"),y=rnorm(10),stringsAsFactors = FALSE)

a2 = inner_join(table1,table2)
b2 = left_join(table1,table2)
c2 = right_join(table1,table2)
d2 = full_join(table1,table2)

# Identifikatorwerte nicht eindeutig
table1 <- data.frame(ID1=1:10,x=paste("A",1:10),stringsAsFactors = FALSE)
table2 <- data.frame(ID1=rep(1:3,2),y=paste("B",1:6),stringsAsFactors = FALSE)

a3 = inner_join(table1,table2)
b3 = left_join(table1,table2)
c3 = right_join(table1,table2)
d3 = full_join(table1,table2)

Fazit
Habt ihr euch wirklich alles bis zum Ende durchgelesen? Dann seid ihr nun bestens gerüstet, dplyr einzusetzen. Daneben habt ihr das Konzept der Pipe kennengelernt, welches viele Anwendungen hat, z.B. auch für das Grafik-Package ggplot2. Und ihr versteht nun die verschiedenen join-Arten.
Fun Fact: Pliers heißt Zange auf englisch. Daher kommt die Zange im Logo von dplyr.
Ich bin auf Euren Input angewiesen. Twittert diese Erklärung zu dplyr oder hinterlasst einen Kommentar und schreibt mir, wie ihr dplyr einsetzt.

Happy coding,
Euer Holger
 
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda t.test merge

Python Datentypen Übersicht

Dieser Blogartikel gibt eine Übersicht über die verschiedenen Basis-Datentypen in Python und richtet sich damit an die Python-Einsteiger. Es gibt gar nicht so viele verschiedene Python-Datentypen und jeder, der in Python programmiert, sollte diese Typen kennen. Je nach genutztem Package gibt es natürlich noch andere. Wer Data Science in Python lernen möchte, kommt an NumPy und dessen schnelles Array nicht herum.
Also los geht’s:
Als erstes brauchen wir natürlich die Möglichkeit, Zahlen und Wörter (Strings, Zeichenketten) in entsprechenden Variablen zu speichern. Dann erweitern wir diese zu Kombinationen, also Listen, Tupel und Mengen von Variablen sowie sogenannten Dictionaries (Lexika), welche man sich tatsächlich wie ein Lexikon oder Index vorstellen kann.
Die wichtigsten Datentypen in Python

Zahlen / numerische Datentypen
Strings / Zeichenketten
Listen
Tupel
Mengen
Dictionary

Und wenn ihr dieses HowTo nützlich findet, dann teilt es bitte bei Twitter. So finden es noch mehr Leute:

 

Inhalt

Benennung von VariablenVariablendeklaration in PythonNumerische Datentypen in PythonStrings / ZeichenkettenListen, Tupel, MengenListen in PythonTupel in PythonMengen in PythonPython DictionariesDen Datentyp einer Variable abfragenPython Datentypen umwandeln
Benennung von Variablen
Ok, man kann seine Python-Variablen ziemlich beliebig benennen, es gibt aber ein paar Regeln. Die wichtigste Regel ist natürlich, eure Variablen sinnvoll zu benennen, so dass ihr euch noch zurecht findet, wenn ihr den Code in einem Jahr nochmal anschaut.
Ansonsten gelten folgende Einschränkungen:

Es sind nur Buchstaben (a-z, A-Z), Zahlen (0-9) und der Unterstrich _ erlaubt
Variablen dürfen nicht mit einer Zahl anfangen, also 9a ist verboten
Python beachtet Groß- und Kleinschreibung, d.h. Abc und abc sind zwei unterschiedliche Variablen
Es gibt ein paar reservierte Schlüsselwörter, die nicht verwendet werden dürfen, da sie schon für andere Zwecke reserviert sind:

and
continue
except
global
lambda
pass
while

as
def
False
if
None
raise
with

assert
del
finally
import
nonlocal
return
yield

break
elif
for
in
not
True

class
else
from
is
or
try

 
Variablendeklaration in Python
Man muss Variablen in Python nicht deklarieren, also den Datentyp mit angeben, wie das in anderen Programmiersprachen wie C der Fall ist.
In Python schreibt man also einfach a=3. Python erkennt automatisch, dass es sich um eine ganze Zahl handelt und weist a den Variablentyp int zu. In C müsste man den Typ mit angeben also int a = 3;
Die automatische Typendeklaration macht es für den Programmierer einfacher, weil er sich nicht darum kümmern muss. Es hat aber auch Nachteile, denn die explizite Angabe gibt uns mehr Kontrolle. So können in Python zum Beispiel keine Konstanten (also nicht änderbare Variablen) definiert werden. Der Programmierer muss selber darauf achten, den Wert nicht zu verändern.
 
Numerische Datentypen in Python
Es gibt drei Arten von Zahlen in Python

int ist für ganze Zahlen, also z.B. 2, 3, 10, 100, 10000000, -5, -10000
float ist für Dezimalzahlen (Kommazahlen), also z.B. 3.14, 1.9999, -0.96424, 10.23. Beachte, dass als Dezimaltrennzeichen der Punkt statt des Kommas verwendet wird (englische Konvention)
complex ist für komplexe Zahlen, welche aus Real- und Imaginärteil bestehen. Wenn Du nicht weißt, was das ist, macht das nichts. Dann benötigst Du diesen Datentyp vermutlich auch nicht.

Die zulässige Größe ist im Gegensatz zu vielen anderen Programmiersprachen nicht beschränkt, sondern hängt nur vom Speicherplatz ab.
Wie im vorherigen Abschnitt geschrieben, müsst ihr euch in Python nicht explizit um den Typ kümmern, d.h. wenn ihr a = 3 eingebt, dann wird a eine Variable vom Typ int. Wenn ihr a = 3.0 eingebt, dann wird a eine Variable vom Typ float.

############################################
# Zahlen
############################################

# Integer-Variablen (ganze Zahlen) definieren
a = 3
b = -10
c = 100000000000000000000000000000000000000000000000000
d = a
a = -2

# Float-Variablen (Kommazahlen) definieren
fa = 3.14
fb = -0.9999999999999
# Exponentialschreibweise -5e-5 = -5 * 10^3 = -5 * 1000 = -5000
fc = -5e3

# complex-Variablen (komplexe Zahlen) definieren
w = 3 + 2j
z = 1j

# gleichzeitige Zuweisung mehrerer Variablen
i, j, k = 10, 20, 3.14

Strings / Zeichenketten
Jede Programmiersprache kann Zeichenketten in Variablen speichern, so auch Python. Und das ist genau simpel wie bei Zahlen, nämlich einfach die Zeichenkette, die ihr speichern wollt in Anführungsstriche, egal ob einfache oder doppelte, verpacken. Also s = „Hallo“ bzw.  s= ‚Hallo‘.
Für die Länge eines Strings s gibt es die Funktion len(s). Einzelne Zeichen eines Strings könnt ihr mit eckigen Klammern ansprechen, so gibt z.B. s[0] das erste Zeichen zurück oder s[0:2] die ersten zwei Zeichen. Achtung: In Python beginnt die Zählweise mit 0 und bei einer Folge wie 0:2 ist das rechte Element nicht mehr dabei, d.h. 0:2 entspricht den Werten 0 und 1, also das 1. und das 2. Zeichen des Strings
############################################
# Strings
############################################

s1 = "Hello"
s2 = "databraineo!"
s2 = s1
s1 = "Ola"
s3 = 'auch ein String'

s2[0]
s2[1:]
len(s2)

#Sonderzeichen
# \ wird als Start für Escape-Sequenz erkannt, \t als Tab
print('C:\temp')
# für den Backslash benötigen wir also \\
print('C:\\temp')
#raw string
s4 = r'C:\\temp'
#
#mehrzeiliger String
s5 = """abc
def
ghi"""
print(s5)

# einen String als Unicode definieren
s6 = u"Das hier ist Unicode"

Listen, Tupel, Mengen
Ihr wollt mehrere Elemente abspeichern, dann braucht ihr Listen, Tupel oder Mengen. Zusammengefasst sind Listen eben eine (geordnete) Liste von Elementen. Die einzelnen Elemente lassen sich über einen Index ansprechen. Tupel sind fast das gleiche wie eine Liste, nur lassen sich die einzelnen Elemente nicht mehr verändern. Mengen sind nicht geordnet und haben keine doppelten Elemente.
Listen in Python
Dabei sind Listen sicherlich die wichtigste Struktur. Eine Liste wird mit eckigen Klammern definiert und besteht aus einer Folge von Elementen, die sogar von unterschiedlichen Typen sein können. Also zum Beispiel l1 = [1, 2, 3] oder l2 = [„grün“, 3.14, 2].
Die Elemente der Liste lassen sich über die Indizes ansprechen. Im Prinzip genauso wie die einzelnen Zeichen eines Strings. Und wieder gilt, dass wir bei 0 mit dem Zählen anfangen. Also l1[0] gibt das 1. Element zurück, in dem Fall 1. l2[0] würde „grün“ zurückgeben.
Listen können beliebige Datentypen enthalten, auch wieder Listen. l3 = [[1,2], „grün“, [3.0,3.1,3.2]]
Vorsicht beim Kopieren von Listen: Wenn man mit l2 = l1 eine Liste „kopiert“, wird keine echte Kopie gemacht, sondern nur der sogenannte Zeiger auf die Liste kopiert. Verändere ich die ursprüngliche Liste l1, dann verändert sich damit auch l2. Um eine echte Kopie zu machen, verwendet man l2 = l1.copy().
Tupel in Python
Tupel sind im Gegensatz zu Listen nicht mehr veränderbar, d.h. im Nachhinein können keine Elemente eines Tupels geändert werden. Ansonsten verhalten sie sich genauso wie Listen. Tupel werden mit runden Klammern initialisiert, also z.B. t1 = (1, 2, 3).
Mit t1[1] greift ihr auf das 2. Element zurück, hier also 2. Nicht erlaubt ist t1[1] = 9, da wie eben geschrieben, Tupel nicht veränderbar sind.
Mengen in Python
Vermutlich eher ein selten genutztes Konstrukt. Mengen enthalten eindeutige Elemente, also keine doppelten Einträge, und sind nicht geordnet. D.h. im Gegensatz zu Listen oder Tupel können wir die Elemente nicht mit einem Index abrufen. Mengen werden in Python mit geschweiften Klammern gebildet. also z.B. m1 = { „grün“, „rot“, „blau“}
############################################
# Listen, Tupel, Mengen
############################################
alist = [1,2,3,4]
alist
atuple = (1,2,3,3)
atuple
aset = {'grün','gelb','rot','rot'}
aset

# Elemente einer Liste können geändert werden
alist[2] = 9.34
alist[3] = 'grün'
alist[0] = ['gelb',-2e-2,5]
len(alist)

# Elemente eines Tuples können nicht geändert werden
atuple[0] = 9
btuple = atuple + ('grün',2.1,[-3,-2,0])
btuple[6]
btuple[6][2] = 9
len(btuple)

# Mengen sind nicht geordnet
aset[0]
'grün' in aset
aset.add('lila')
aset.remove('grün')

# Listen werden nicht kopiert, sondern zeigen auf das gleiche Objekt
blist = [1,2,3]
clist = blist
clist[0]
blist[0] = 9
clist[0]
# echte Kopien erstellen
dlist = blist.copy()
dlist = list(blist)

 
Python Dictionaries
Ein Dictionary kann man sich, wie der Name sagt, als Lexikon vorstellen, d.h. es gibt einen Schlüsselbegriff (key), dem etwas zugeordnet wird. Beim normalen Lexikon wäre es eine Erklärung des Schlüsselbegriffs, beim dictionary können es aber beliebige Datentypen sein. Wie schon bei Listen, Tupeln oder Mengen können auch dictionaries verschachtelt sein.
Ein Dictionary wird wie eine Menge per geschweiften Klammern definiert, allerdings bestehen die Einträge aus dem Schlüssel, gefolgt von Doppelpunkt und dann dem Wert.
Die einzelnen Einträge werden dann anstatt über einen Index über den Schlüssel angesprochen. So können die Einträge auch geändert werden. Wenn man alle Werte des Dictionaries dic als Liste bekommen will, benutzt man die Funktion values, also list(dic.values()). Beachtet, dass wir noch list davor schreiben müssen, um auch wirklich eine Liste zu bekommen. Genauso funktioniert es mit den Schlüsseln, also list(dic.keys()).
Sehr praktisch sind dictionaries, wenn mehrere Eigenschaften eines Objekts gespeichert werden sollen. Zum Beispiel soll für einen Kunden Name, Vorname und Alter gespeichert werden.
############################################
# Dictionaries
############################################
dic = { 'E1':'Sofa','E2':'Stuhl','E3':'Tisch' }
print(dic)
dic['E1']
dic[0]
dic.values()
dic.keys()

dic = { 1:'Sofa',2:'Stuhl',3:'Tisch' }
dic[1]

Kunde = {'Vorname':'Kevin', 'Name': 'Chalupke', 'Alter': 32}
print(Kunde['Name'])
print(Kunde['Alter'])
Kunde['Alter'] = 33

 
Den Datentyp einer Variable abfragen
Um zu prüfen, welchen Datentyp eine Variable x hat, gibt es den Befehl type(x). Mit isinstance(x,typ) wird geprüft, ob x vom angegebenen Typ ist. Es wird also True oder False zurückgegeben
Python Datentypen umwandeln
Das Umwandeln von Datentypen in Python ist ziemlich intuitiv. Man benutzt einfach den Datentyp als Funktion und als Parameter kommt die umzuwandelnde Variable.

int zu float: float(3)
float zu int: int(3.14)
int zu string: str(3)
string zu int: int("3")
float zu string: str(3.14)
string zu float: float("3.14")

Auch die drei zusammengesetzten Typen List, Tuple und Set lassen sich so ineinander überführen

list zu tuple: tuple(l)
tuple zu list: list(t)
list zu set: set(l)
set zu list: list(s)
tuple zu set: set(t)
set zu tuple: tuple(s)

Nur das Dictionary fällt aus der Reihe, denn Python kann ja nicht wissen, wie es z.B. eine Liste in ein Dictionary umwandeln soll, denn es fehlen die Schlüssel. Andersherum funktioniert es: aus einem Dictionary kann man eine Liste/Tupel/Menge machen, allerdings werden nur die Schlüssel verwendet.
############################################
# Datentypen umwandeln
############################################

# int schneidet die Dezimalstellen ab
x = 3.14
int(x)
int(3.6)
int(-0.9)

float(3)
float("3.14")

int("3")
# ein String einer Dezimalzahl kann nicht in einen int umgewandelt werden
int("3.14")
int(float("3.14"))

str(10)
str(10.34)

# Listen, Tupel und Mengen lassen sich ineinander überführen
tuple(alist)
list(atuple)
set(alist)
set(atuple)
list(aset)
tuple(aset)

# Wenn man ein Dictionary in eine Liste/Tupel/Menge umwandelt, wird
# der Schlüssel (Key) in das entsprechende Objekt umgewandelt
list(dic)
tuple(dic)
set(dic)
list(Kunde)

 
So, das war es mit den wichtigsten Datentypen in Python. Eigentlich nicht so schwierig, oder?
Hat Euch der Beitrag gefallen? Dann schreibt mir einen Kommentar oder teilt den Artikel bei Twitter, damit andere auch etwas davon haben.

Happy coding,
Euer Holger
Glossar: Maschinelles Lernen Hyperparameter R (Programmiersprache) RStudio min hist überwachtes Lernen reinforcement learning Neuronales Netz paste rnorm set.seed mean median Tensorflow Anaconda t.test merge

Wird geladen

Hol Dir mein neues Buch

Gratis für Dich

0
    0
    Warenkorb
    Warenkorb ist leerzum Shop