R als Tool im Process Mining

Die Open Source Sprache R ermöglicht eine Vielzahl von Analysemöglichkeiten, die von einer einfachen beschreibenden Darstellung eines Prozesses bis zur umfassenden statistischen Analyse reicht. Dabei können Daten aus einem Manufacturing Execution System, kurz MES, als Basis der Prozessanalyse herangezogen werden. R ist ein Open Source Programm, welches sich für die Lösung von statischen Aufgaben im Bereich der Prozessoptimierung sehr gut eignet, erfordert jedoch auf Grund des Bedienungskonzepts als Scriptsprache, grundlegende Kenntnisse der Programmierung. Aber auch eine interaktive Bedienung lässt sich mit einer Einbindung der Statistikfunktionen in ein Dashboard erreichen. Damit können entsprechend den Anforderungen, automatisierte Analysen ohne Programmierkenntnisse realisiert werden.

Der Prozess als Spagetti Diagramm

Um einen Überblick zu erhalten, wird der Prozess in einem „process value flowchart“, ähnlich einem Spagetti‐ Diagramm dargestellt und je nach Anforderung mit Angaben zu den Key Performance Indicators ergänzt. Im konkreten Fall werden die absolute Anzahl und der relative Anteil der bearbeiteten Teile angegeben. Werden Teile wie nachfolgend dargestellt, aufgrund von festgestellten Mängel bei der Qualitätskontrolle automatisiert ausgeschleust, können darüber Kennzahlen für den Ausschuss ermittelt werden.

Der Prozess in Tabellen und Diagrammen

Im folgenden Chart sind grundlegende Angaben zu den ausgeführten Prozessschritten, sowie deren Varianten dargestellt. Die Statistikansicht bietet eine Übersicht zu den Fällen, den sogenannte „Cases“, sowie zur Dauer und Taktzeit der einzelnen Aktivitäten. Dabei handelt es sich um eine Fertigungsline mit hohem Automatisierungsgrad, bei der jeder Fertigungsschritt im MES dokumentiert wird. Die Tabelle enthält statistische Angaben zur Zykluszeit, sowie der Prozessdauer zu den einzelnen Aktivitäten. In diesem Fall waren keine Timestamps für das Ende der Aktivität vorhanden, somit konnte die Prozessdauer nicht berechnet werden.

Die Anwendung von Six Sigma Tools

R verfügt über eine umfangreiche Sammlung von Bibliotheken zur Datendarstellung, sowie der Prozessanalyse. Darin sind auch Tools aus Six Sigma enthalten, die für die weitere Analyse der Prozesse eingesetzt werden können. In den folgenden Darstellungen wird die Möglichkeit aufgezeigt, zwei Produktionszeiträume, welche über eine einfache Datumseingabe im Dashboard abgegrenzt werden, gegenüber zu stellen. Dabei handelt es sich um die Ausbringung der Fertigung in Stundenwerten, die für jeden Prozessschritt errechnet wird. Das xbar und r Chart findet im Bereich der Qualitätssicherung häufig Anwendung zur ersten Beurteilung des Prozessoutputs.

Zwei weitere Six Sigma typische Kennzahlen zur Beurteilung der Prozessfähigkeit sind der Cp und Cpk Wert und deren Ermittlung ein Bestandteil der R Bibliotheken ist. Bei der Berechnung wird von einer Normalverteilung der Daten ausgegangen, wobei das Ergebnis aus der Überprüfung dieser Annahme im Chart durch Zahlen, als auch grafisch dargestellt wird.

Von Interesse ist auch die Antwort auf die Frage, welchem Trend folgt der Prozess? Bereits aus der Darstellung der beiden Produktionszeiträume im Box‐Whiskers‐Plot könnte man anhand der Mediane auf einen Trend zu einer Verschlechterung der Ausbringung schließen, den der Interquartilsabstand nicht widerspiegelt. Eine weitere Absicherung einer Aussage über den Trend, kann über einen statistischen Vergleichs der Mittelwerte erfolgen.

Der Modellvergleich

Besteht die Anforderung einer direkten Gegenüberstellung des geplanten, mit dem vorgefundenen, sogenannten „Discovered Model“, ist aufgrund der Komplexität beim Modellvergleich, dieser in R mit hohem Programmieraufwand verbunden. Besser geeignet sind dafür spezielle Process Miningtools. Diese ermöglichen den direkten Vergleich und unterstützen bei der Analyse der Ursachen zu den dargestellten Abweichungen. Bei Produktionsprozessen handelt es sich meist um sogenannte „Milestone Events“, die bei jedem Fertigungsschritt durch das MES dokumentiert werden und eine einfache Modellierung des Target Process ermöglichen. Weiterführende Analysen der Prozessdaten in R sind durch einen direkten Zugriff über ein API realisierbar oder es wurde vollständig integriert. Damit eröffnen sich wiederum die umfangreichen Möglichkeiten bei der statistischen Prozessanalyse, sowie der Einsatz von Six Sigma Tools aus dem Qualitätsmanagement. Die Analyse kann durch eine, den Kundenanforderungen entsprechende Darstellung in einem Dashboard vereinfacht werden, ermöglicht somit eine zeitnahe, weitgehend automatisierte Prozessanalyse auf Basis der Produktionsdaten.

Resümee

Process Mining in R ermöglicht zeitnahe Ergebnisse, die bis zur automatisierten Analyse in Echtzeit reicht. Der Einsatz beschleunigt erheblich das Process Controlling und hilft den Ressourceneinsatz bei der Datenerhebung, sowie deren Analyse zu reduzieren. Es kann als stand‐alone Lösung zur Untersuchung des „Discovered Process“ oder als Erweiterung für nachfolgende statistische Analysen eingesetzt werden. Als stand‐alone Lösung eignet es sich für Prozesse mit geringer Komplexität, wie in der automatisierten Fertigung. Besteht eine hohe Diversifikation oder sollen standortübergreifende Prozessanalysen durchgeführt werden, übersteigt der Ressourcenaufwand rasch die Kosten für den Einsatz einer Enterprise Software, von denen mittlerweile einige angeboten werden.

 

Datenschutz, Sicherheit und Ethik beim Process Mining – Regel 4 von 4:

Dieser Artikel ist Teil 4 von 4 aus der Reihe Datenschutz, Sicherheit und Ethik beim Process Mining.

english-flagRead this article in English:
Privacy, Security and Ethics in Process Mining – Rule 4 of 4


Schaffung einer Kooperationskultur

Möglicherweise ist der wichtigste Bestandteil bei der Schaffung eines verantwortungsbewussten Process Mining-Umfeldes der Aufbau einer Kooperationskultur innerhalb Ihrer Organisation. Process Mining kann die Fehler Ihrer Prozesse viel eindeutiger aufzeigen, als das manchen Menschen lieb ist. Daher sollten Sie Change Management-Experten miteinbeziehen wie beispielsweise Lean-Coaches, die es verstehen, Menschen dazu zu bewegen, sich gegenseitig “die Wahrheit“ zu sagen (siehe auch: Erfolgskriterien beim Process Mining).

Darüber hinaus sollten Sie vorsichtig sein, wie Sie die Ziele Ihres Process Mining-Projektes vermitteln und relevante Stakeholder so einbeziehen, dass ihre Meinung gehört wird. Ziel ist es, eine Atmosphäre zu schaffen, in der die Menschen nicht für ihre Fehler verantwortlich gemacht werden (was nur dazu führt, dass sie verbergen, was sie tun und gegen Sie arbeiten), sondern ein Umfeld zu schaffen, in dem jeder mitgenommen wird und wo die Analyse und Prozessverbesserung ein gemeinsames Ziel darstellt, für das man sich engagiert.

Was man tun sollte:

  • Vergewissern Sie sich, dass Sie die Datenqualität überprüfen, bevor Sie mit der Datenanalyse beginnen, bestenfalls durch die Einbeziehung eines Fachexperten bereits in der Datenvalidierungsphase. Auf diese Weise können Sie das Vertrauen der Prozessmanager stärken, dass die Daten widerspiegeln, was tatsächlich passiert und sicherstellen, dass Sie verstanden haben, was die Daten darstellen.
  • Arbeiten Sie auf iterative Weise und präsentieren Sie Ihre Ergebnisse als Ausgangspunkt einer Diskussion bei jeder Iteration. Geben Sie allen Beteiligten die Möglichkeit zu erklären, warum bestimmte Dinge geschehen und seien Sie offen für zusätzliche Fragen (die in der nächsten Iteration aufgegriffen werden). Dies wird dazu beitragen, die Qualität und Relevanz Ihrer Analyse zu verbessern, als auch das Vertrauen der Prozessverantwortlichen in die endgültigen Projektergebnisse zu erhöhen.

Was man nicht tun sollte:

  • Voreilige Schlüsse ziehen. Sie können nie davon ausgehen, dass Sie alles über den Prozess wissen. Zum Beispiel können langsamere Teams die schwierigen Fälle behandeln, es kann gute Gründe geben, von dem Standardprozess abzuweichen und Sie sehen möglicherweise nicht alles in den Daten (beispielsweise Vorgänge, die außerhalb des Systems durchgeführt werden). Indem Sie konstant Ihre Beobachtungen als Ausgangspunkt für Diskussionen anbringen und den Menschen die Möglichkeit einräumen, Ihre Erfahrung und Interpretationen mitzugeben, beginnen Sie, Vertrauen und die Kooperationskultur aufzubauen, die Process Mining braucht.
  • Schlussfolgerungen erzwingen, die ihren Erwartungen entsprechen oder die sie haben möchten, indem Sie die Daten falsch darstellen (oder Dinge darstellen, die nicht wirklich durch die Daten unterstützt werden). Führen Sie stattdessen ganz genau Buch über die Schritte, die Sie bei der Datenaufbereitung und in Ihrer Process-Mining-Analyse ausgeführt haben. Wenn Zweifel an der Gültigkeit bestehen oder es Fragen zu Ihrer Analysebasis gibt, dann können Sie stets zurückkehren und beispielsweise zeigen, welche Filter bei den Daten angewendet wurden, um zu der bestimmten Prozesssicht zu gelangen, die Sie vorstellen.

Einstieg in das Maschinelle Lernen mit Python(x,y)

Python(x,y) ist eine Python-Distribution, die speziell für wissenschaftliche Arbeiten entwickelt wurde. Es umfasst neben der Programmiersprache auch die Entwicklungsumgebung Spyder und eine Reihe integrierter Python-Bibliotheken. Mithilfe von Python(x,y) kann eine Vielzahl von Interessensbereichen bearbeitet werden. Dazu zählen unter anderem Bildverarbeitung oder auch das maschinelle Lernen. Das All-in-One-Setup für Python(x,y) ist für alle gängigen Betriebssysteme online erhältlich. Read more

Datenschutz, Sicherheit und Ethik beim Process Mining – Regel 3 von 4:

Dieser Artikel ist Teil 3 von 4 aus der Reihe Datenschutz, Sicherheit und Ethik beim Process Mining.

english-flagRead this article in English:
Consider Anonymization – Process Mining Rule 3 of 4

 

Anonymisierung in Betracht ziehen

Falls Ihr Datensatz vertrauliche Informationen enthält, können Sie auch Anonymisierungsmethoden anwenden. Wenn Sie einen Wertesatz anonymisieren, werden die tatsächlichen Werte (z.B. die Mitarbeiternamen “Mary Jones”, “Fred Smith” usw.) durch einen anderen Wert ersetzt (z.B. ”Ressource 1”, ”Ressource 2″, etc.).

Falls der gleiche Originalwert mehrfach im Datensatz auftaucht, wird er stets durch den gleichen Wert ersetzt (”Mary Jones” wird immer durch “Ressource 1” ersetzt). Auf diese Weise ermöglicht Ihnen die Anonymisierung, die ursprünglichen Daten zu verschleiern und gleichzeitig wesentliche Muster des Datensatzes für Ihre Analyse zu bewahren. Sie können z.B. die Arbeitsauslastung alle Mitarbeiter analysieren, ohne die tatsächlichen Namen zu sehen.

Einige Process Mining-Tools (wie Disco oder ProM) haben Anonymisierungsfunktionalität bereits eingebaut. Dies bedeutet, dass Sie Ihre Daten in das Process-Mining-Tool importieren und dort auswählen können, welche Datenfelder anonymisiert werden sollen. Sie können beispielsweise die Case-IDs, den Ressourcennamen, die Attributwerte oder die Zeitstempel anonymisieren. Anschließend können Sie den anonymisierten Datensatz exportieren und an Ihr Team für die Analyse weitergeben.

Was man tun sollte:

  • Denken Sie daran, dass trotz einer Anonymisierung bestimmte Informationen immer noch identifizierbar sein können. Vielleicht gibt es beispielsweise nur einen Patienten mit einer sehr seltenen Krankheit oder das Geburtsdatum Ihres Kunden in Kombination mit dem Geburtsort kann die Anzahl der möglichen Personen, auf die dies zutrifft, so stark einschränken, dass die Daten nicht mehr anonym sind.

Was man nicht tun sollte:

  • Anonymisieren der Daten, bevor Sie Ihre Daten bereinigt haben, da nach der Anonymisierung eine Datenreinigung oft nicht mehr möglich ist. Stellen Sie sich beispielsweise vor, dass in verschiedenen Regionen Kundenkategorien unterschiedliche benannt werden, obwohl sie dasselbe bedeuten. Sie möchten diese unterschiedlichen Namen in einem Datenreinigungsschritt zusammenführen. Nachdem Sie jedoch die Namen als “Kategorie 1”, “Kategorie 2” usw. anonymisiert haben, kann die Datenreinigung nicht mehr durchgeführt werden.
  • Anonymisierung von Feldern, die nicht anonymisiert werden müssen. Während eine Anonymisierung dabei helfen kann, die Muster Ihrer Daten zu bewahren, können Sie leicht relevante Informationen verlieren. Wenn Sie beispielsweise die Case-ID in Ihrem Incident-Management-Prozess anonymisieren, können Sie die Ticketnummer des Vorgangs im Service Desk-System nicht mehr ausfindig machen. Durch die Schaffung einer Kooperationskultur rund um Ihre Process Mining-Initiative (siehe Leitfaden Nr. 4) und durch eine verantwortungsvolle, zielorientierte Arbeitsweise, können Sie oft offen mit den ursprünglichen Daten arbeiten.

R Data Frames meistern mit dplyr – Teil 2

Dieser Artikel ist Teil 2 von 2 aus der Artikelserie R Data Frames meistern mit dplyr.

Noch mehr Datenbank-Features

Im ersten Teil dieser Artikel-Serie habe ich die Parallelen zwischen Data Frames in R und Relationen in SQL herausgearbeitet und gezeigt, wie das Paket dplyr eine Reihe von SQL-analogen Operationen auf Data Frames standardisiert und optimiert. In diesem Teil möchte ich nun drei weitere Analogien aufzeigen. Es handelt sich um die

  • Window Functions in dplyr als Entsprechung zu analytischen Funktionen in SQL,
  • Joins zwischen Data Frames als Pendant zu Tabellen-Joins
  • Delegation von Data Frame-Operationen zu einer bestehenden SQL-Datenbank

Window Functions

Im letzten Teil habe ich gezeigt, wie durch die Kombination von group_by() und summarise() im Handumdrehen Aggregate entstehen. Das Verb group_by() schafft dabei, wie der Name schon sagt, eine Gruppierung der Zeilen des Data Frame anhand benannter Schlüssel, die oft ordinaler oder kategorialer Natur sind (z.B. Datum, Produkt oder Mitarbeiter).

Ersetzt man die Aggregation mit summarise() durch die Funktion mutate(), um neue Spalten zu bilden, so ist der Effekt des group_by() weiterhin nutzbar, erzeugt aber „Windows“, also Gruppen von Datensätzen des Data Frames mit gleichen Werten der Gruppierungskriterien. Auf diesen Gruppen können nun mittels mutate() beliebige R-Funktionen angewendet werden. Das Ergebnis ist im Gegensatz zu summarise() keine Verdichtung auf einen Datensatz pro Gruppe, sondern eine Erweiterung jeder einzelnen Zeile um neue Werte. Das soll folgendes Beispiel verdeutlichen:

library(dplyr)
set.seed(42)	

df <- data.frame(id = 1:20, 
                 a=sample(c("Hund","Katze","Maus","Tiger"),20,replace=T),
                 b=sample(1:10,20, replace = T))
df
   id     a  b
1   1  Maus  7
2   2  Hund  3
3   3 Katze  3
4   4  Maus  4
5   5 Tiger 10
6   6  Maus 10
7   7  Hund  8
8   8  Hund  8
9   9  Hund  6
10 10 Katze  1
11 11  Maus  7
12 12  Hund  9
13 13  Hund  8
14 14 Tiger  5
15 15 Tiger  6
16 16  Maus  6
17 17 Katze  1
18 18  Maus  4
19 19  Maus  7
20 20  Maus  9
df %>%
  group_by(a) %>%
  mutate(r = row_number(),        # aus dplyr 
         n_memb = n(),            # aus dplyr
         n_dist = n_distinct(b),  # aus dplyr
         ra=rank(desc(b)),        # aus base und dplyr
         last_b = lag(b),         # aus dplyr
         next_b = lead(b),        # aus dplyr
         mb = mean(b),            # aus base
         cs = cumsum(b)  )        # aus base
Source: local data frame [20 x 11]
Groups: a [4]

     id      a     b     r n_memb n_dist    ra last_b next_b       mb     cs
                    
1      1   Maus     7     1      8      5   4.0     NA      4 6.750000     7
2      2   Hund     3     1      6      4   6.0     NA      8 7.000000     3
3      3  Katze     3     1      3      2   1.0     NA      1 1.666667     3
4      4   Maus     4     2      8      5   7.5      7     10 6.750000    11
5      5  Tiger    10     1      3      3   1.0     NA      5 7.000000    10
6      6   Maus    10     3      8      5   1.0      4      7 6.750000    21
7      7   Hund     8     2      6      4   3.0      3      8 7.000000    11
8      8   Hund     8     3      6      4   3.0      8      6 7.000000    19
9      9   Hund     6     4      6      4   5.0      8      9 7.000000    25
10    10  Katze     1     2      3      2   2.5      3      1 1.666667     4
11    11   Maus     7     4      8      5   4.0     10      6 6.750000    28
12    12   Hund     9     5      6      4   1.0      6      8 7.000000    34
13    13   Hund     8     6      6      4   3.0      9     NA 7.000000    42
14    14  Tiger     5     2      3      3   3.0     10      6 7.000000    15
15    15  Tiger     6     3      3      3   2.0      5     NA 7.000000    21
16    16   Maus     6     5      8      5   6.0      7      4 6.750000    34
17    17  Katze     1     3      3      2   2.5      1     NA 1.666667     5
18    18   Maus     4     6      8      5   7.5      6      7 6.750000    38
19    19   Maus     7     7      8      5   4.0      4      9 6.750000    45
20    20   Maus     9     8      8      5   2.0      7     NA 6.750000    54

Das group_by() unterteilt den Data Frame nach den 4 gleichen Werten von a. Innerhalb dieser Gruppen berechnen die beispielsweise eingesetzten Funktionen

  • row_number(): Die laufende Nummer in dieser Gruppe
  • n(): Die Gesamtgröße dieser Gruppe
  • n_distinct(b): Die Anzahl verschiedener Werte von b innerhalb der Gruppe
  • rank(desc(b)): Den Rang innerhalb der selben Gruppe, absteigend nach b geordnet
  • lag(b): Den Wert von b der vorherigen Zeile innerhalb derselben Gruppe
  • lead(b): Analog den Wert von b der folgenden Zeile innerhalb derselben Gruppe
  • mean(b): Den Mittelwert von b innerhalb der Gruppe
  • cumsum(b): Die kumulierte Summe der b-Werte innerhalb der Gruppe.

Wichtig ist hierbei, dass die Anwendung dieser Funktionen nicht dazu führt, dass die ursprüngliche Reihenfolge der Datensätze im Data Frame geändert wird. Hier erweist sich ein wesentlicher Unterschied zwischen Data Frames und Datenbank-Relationen von Vorteil: Die Reihenfolge von Datensätzen in Data Frames ist stabil und definiert. Sie resultiert aus der Abfolge der Elemente auf den Vektoren, die die Data Frames bilden. Im Gegensatz dazu haben Tabellen und Views keine Reihenfolge, auf die man sich beim SELECT verlassen kann. Nur mit der ORDER BY-Klausel über eindeutige Schlüsselwerte erreicht man eine definierte, stabile Reihenfolge der resultierenden Datensätze.

Die Wirkungsweise von Window Functions wird noch besser verständlich, wenn in obiger Abfrage das group_by(a) entfernt wird. Dann wirken alle genannten Funktionen auf der einzigen Gruppe, die existiert, nämlich dem gesamten Data Frame:

df %>%
  mutate(r = row_number(),        # aus dplyr 
         n_memb = n(),            # aus dplyr
         n_dist = n_distinct(b),  # aus dplyr
         ra=rank(desc(b)),        # aus base und dplyr
         last_b = lag(b),         # aus dplyr
         next_b = lead(b),        # aus dplyr
         mb = mean(b),            # aus base
         cs = cumsum(b)  )        # aus base


   id     a  b  r n_memb n_dist   ra last_b next_b  mb  cs
1   1  Maus  7  1     20      9  9.0     NA      3 6.1   7
2   2  Hund  3  2     20      9 17.5      7      3 6.1  10
3   3 Katze  3  3     20      9 17.5      3      4 6.1  13
4   4  Maus  4  4     20      9 15.5      3     10 6.1  17
5   5 Tiger 10  5     20      9  1.5      4     10 6.1  27
6   6  Maus 10  6     20      9  1.5     10      8 6.1  37
7   7  Hund  8  7     20      9  6.0     10      8 6.1  45
8   8  Hund  8  8     20      9  6.0      8      6 6.1  53
9   9  Hund  6  9     20      9 12.0      8      1 6.1  59
10 10 Katze  1 10     20      9 19.5      6      7 6.1  60
11 11  Maus  7 11     20      9  9.0      1      9 6.1  67
12 12  Hund  9 12     20      9  3.5      7      8 6.1  76
13 13  Hund  8 13     20      9  6.0      9      5 6.1  84
14 14 Tiger  5 14     20      9 14.0      8      6 6.1  89
15 15 Tiger  6 15     20      9 12.0      5      6 6.1  95
16 16  Maus  6 16     20      9 12.0      6      1 6.1 101
17 17 Katze  1 17     20      9 19.5      6      4 6.1 102
18 18  Maus  4 18     20      9 15.5      1      7 6.1 106
19 19  Maus  7 19     20      9  9.0      4      9 6.1 113
20 20  Maus  9 20     20      9  3.5      7     NA 6.1 122

Anwendbar sind hierbei sämtliche Funktionen, die auf Vektoren wirken. Diese müssen also wie in unserem Beispiel nicht unbedingt aus dplyr stammen. Allerdings komplettiert das Package die Menge der sinnvoll anwendbaren Funktionen um einige wichtige Elemente wie cumany() oder n_distinct().

Data Frames Hand in Hand…

In relationalen Datenbanken wird häufig angestrebt, das Datenmodell zu normalisieren. Dadurch bekommt man die negativen Folgen von Datenredundanz, wie Inkonsistenzen bei Datenmanipulationen und unnötig große Datenvolumina, in den Griff. Dies geschieht unter anderem dadurch, dass tabellarische Datenbestände aufgetrennt werden Stammdaten- und Faktentabellen. Letztere beziehen sich über Fremdschlüsselspalten auf die Primärschlüssel der Stammdatentabellen. Durch Joins, also Abfragen über mehrere Tabellen und Ausnutzen der Fremdschlüsselbeziehungen, werden die normalisierten Tabellen wieder zu einem fachlich kompletten Resultat denormalisiert.

In den Data Frames von R trifft man dieses Modellierungsmuster aus verschiedenen Gründen weit seltener an als in RDBMS. Dennoch gibt es neben der Normalisierung/Denormalisierung andere Fragestellungen, die sich gut durch Joins beantworten lassen. Neben der Zusammenführung von Beobachtungen unterschiedlicher Quellen anhand charakteristischer Schlüssel sind dies bestimmte Mengenoperationen wie Schnitt- und Differenzmengenbildung.

Die traditionelle R-Funktion für den Join zweier Data Frames lautet merge(). dplyr erweitert den Funktionsumfang dieser Funktion und sorgt für sprechendere Funktionsnamen und Konsistenz mit den anderen Operationen.

Hier ein synthetisches Beispiel:

products <- data.frame(
  id = 1:5, 
  name = c("Desktop", "Laptop", "Maus", "Tablet", "Smartphone"),
  preis = c(500, 700, 10, 300, 500)  
)

set.seed(1)

(salesfacts <- data.frame(
  prod_id = sample(1:5,size = 8,replace = T),
  date = as.Date('2017-01-01') + sample(1:5,size = 8,replace = T)
)  )  

 prod_id       date
1      2 2017-01-05
2      2 2017-01-02
3      3 2017-01-03
4      5 2017-01-02
5      2 2017-01-05
6      5 2017-01-03
7      5 2017-01-05
8      4 2017-01-04

Nun gilt es, die Verkäufe aus dem Data Frame sales mit den Produkten in products zusammenzuführen und auf Basis von Produkten Bilanzen zu erstellen. Diese Denormalisierung geschieht durch das Verb inner_join() auf zweierlei Art und Weise:

salesfacts %>% 
  inner_join(products, by = c("prod_id" = "id"))

  prod_id       date       name preis
1       2 2017-01-05     Laptop   700
2       2 2017-01-02     Laptop   700
3       3 2017-01-03       Maus    10
4       5 2017-01-02 Smartphone   500
5       2 2017-01-05     Laptop   700
6       5 2017-01-03 Smartphone   500
7       5 2017-01-05 Smartphone   500
8       4 2017-01-04     Tablet   300

products %>% 
  inner_join(salesfacts, by = c("id" = "prod_id")) 

  id       name preis       date
1  2     Laptop   700 2017-01-05
2  2     Laptop   700 2017-01-02
3  2     Laptop   700 2017-01-05
4  3       Maus    10 2017-01-03
5  4     Tablet   300 2017-01-04
6  5 Smartphone   500 2017-01-02
7  5 Smartphone   500 2017-01-03
8  5 Smartphone   500 2017-01-05

Die Ergebnisse sind bis auf die Reihenfolge der Spalten und der Zeilen identisch. Außerdem ist im einen Fall der gemeinsame Schlüssel der Produkt-Id als prod_id, im anderen Fall als id enthalten. dplyr entfernt also die Spalten-Duplikate der Join-Bedingungen. Letzere wird bei Bedarf im by-Argument der Join-Funktion angegeben. R-Experten erkennen hier einen „Named Vector“, also einen Vektor, bei dem jedes Element einen Namen hat. Diese Syntax verwendet dplyr, um elegant die äquivalenten Spalten zu kennzeichnen. Wird das Argument by weggelassen, so verwendet dplyr im Sinne eines „Natural Join“ automatisch alle Spalten, deren Namen in beiden Data Frames vorkommen.

Natürlich können wir dieses Beispiel mit den anderen Verben erweitern, um z.B. eine Umsatzbilanz pro Produkt zu erreichen:

salesfacts %>% 
  inner_join(products, by = c("prod_id" = "id")) %>% 
  group_by(prod_id) %>% 
  summarise(n_verk = n(), sum_preis = sum(preis), letzt_dat = max(date))

# A tibble: 4 × 4
  prod_id n_verk sum_preis  letzt_dat
                
1       2      3      2100 2017-01-05
2       3      1        10 2017-01-03
3       4      1       300 2017-01-04
4       5      3      1500 2017-01-05

dplyr bringt insgesamt 6 verschiedene Join-Funktionen mit: Neben dem bereits verwendeten Inner Join gibt es die linksseitigen und rechtsseitigen Outer Joins und den Full Join. Diese entsprechen genau der Funktionalität von SQL-Datenbanken. Daneben gibt es die Funktion semi_join(), die in SQL etwa folgendermaßen ausgedrückt würde:

SELECT ...
FROM a
WHERE EXISTS (SELECT * FROM b WHERE b.a_id = a.id)

Das Gegenteil, also ein NOT EXISTS, realisiert die sechste Join-Funktion: anti_join(). Im folgenden Beispiel sollen alle Produkte ausgegeben werden, die noch nie verkauft wurden:

products %>% anti_join(salesfacts,c("id" = "prod_id"))

  id    name preis
1  1 Desktop   500

… und in der Datenbank

Wir schon mehrfach betont, hat dplyr eine Reihe von Analogien zu SQL-Operationen auf relationalen Datenbanken. R Data Frames entsprechen Tabellen und Views und die dplyr-Operationen den Bausteinen von SELECT-Statements. Daraus ergibt sich die Möglichkeit, dplyr-Funktionen ohne viel Zutun auf eine bestehende Datenbank und deren Relationen zu deligieren.

Mir fallen folgende Szenarien ein, wo dies sinnvoll erscheint:

  • Die zu verarbeitende Datenmenge ist zu groß für das Memory des Rechners, auf dem R läuft.
  • Die interessierenden Daten liegen bereits als Tabellen und Views auf einer Datenbank vor.
  • Die Datenbank hat Features, wie z.B. Parallelverarbeitung oder Bitmap Indexe, die R nicht hat.

In der aktuellen Version 0.5.0 kann dplyr nativ vier Datenbank-Backends ansprechen: SQLite, MySQL, PostgreSQL und Google BigQuery. Ich vermute, unter der Leserschaft des Data Science Blogs dürfte MySQL (oder der Fork MariaDB) die weiteste Verbreitung haben, weshalb ich die folgenden Beispiele darauf zeige. Allerdings muss man beachten, dass MySQL keine Window Funktionen kennt, was sich 1:1 auf die Funktionalität von dplyr auswirkt.

Im folgenden möchte ich zeigen, wie dplyr sich gegen eine bestehende MySQL-Datenbank verbindet und danach einen bestehenden R Data Frame in eine neue Datenbanktabelle wegspeichert:

mysql_db <- src_mysql(host = "localhost", user = "testuser",
                   password = "********", dbname = "test")

library(ggplot2)

str(diamonds)

Classes ‘tbl_df’, ‘tbl’ and 'data.frame':       53940 obs. of  10 variables:
 $ carat  : num  0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
 $ cut    : chr  "Ideal" "Premium" "Good" "Premium" ...
 $ color  : chr  "E" "E" "E" "I" ...
 $ clarity: chr  "SI2" "SI1" "VS1" "VS2" ...
 $ depth  : num  61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
 $ table  : num  55 61 65 58 58 57 57 55 61 61 ...
 $ price  : int  326 326 327 334 335 336 336 337 337 338 ...
 $ x      : num  3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
 $ y      : num  3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
 $ z      : num  2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...

diamonds %>% mutate(cut = as.character(cut), 
                    color = as.character(color),
                    clarity = as.character(clarity)) -> diamonds

diamonds_mysql <- copy_to(mysql_db, diamonds, name="diamonds",
                         temporary = FALSE, indexes = list(
                       c("cut", "color", "clarity"), "carat", "price"))

diamonds_mysql %>% summarise(count = n())

Source:   query [?? x 1]
Database: mysql 5.5.54-0ubuntu0.14.04.1 [testuser@localhost:/test]

  count
  <dbl>
1 53940

Die erste Anweisung verbindet R mit einer bestehenden MySQL-Datenbank. Danach lade ich den Data Frame diamonds aus dem Paket ggplot2. Mit str() wird deutlich, dass drei darin enthaltene Variablen vom Typ Factor sind. Damit dplyr damit arbeiten kann, werden sie mit mutate() in Character-Vektoren gewandelt. Dann erzeugt die Funktion copy_to() auf der MySQL-Datenbank eine leere Tabelle namens diamonds, in die die Datensätze kopiert werden. Danach erhält die Tabelle noch drei Indexe (von dem der erste aus drei Segmenten besteht), und zum Schluß führt dplyr noch ein ANALYSE der Tabelle durch, um die Werteverteilungen auf den Spalten für kostenbasierte Optimierung zu bestimmen.

Meistens aber wird bereits eine bestehende Datenbanktabelle die interessierenden Daten enthalten. In diesem Fall lautet die Funktion zum Erstellen des Delegats tbl():

diamonds_mysql2 <- tbl(mysql_db,"diamonds")

identical(diamonds_mysql,diamonds_mysql2)

[1] TRUE

Die Rückgabewerte von copy_to() und von tbl() sind natürlich keine reinrassigen Data Frames, sondern Objekte, auf die die Operationen von dplyr wirken können, indem sie auf die Datenbank deligiert werden. Im folgenden Beispiel sollen alle Diamanten, die ein Gewicht von mindestens 1 Karat haben, pro Cut, Color und Clarity nach Anzahl und mittlerem Preis bilanziert werden:

bilanz <- diamonds_mysql2 %>% 
  filter(carat >= 1) %>% 
  group_by(cut,color,clarity) %>% 
  summarise(count = n(), mean_price = mean(price))

bilanz

Source:   query [?? x 5]
Database: mysql 5.5.54-0ubuntu0.14.04.1 [testuser@localhost:/test]
Groups: cut, color

     cut color clarity count mean_price
   <chr> <chr>   <chr> <dbl>      <dbl>
1   Fair     D      I1     3   9013.667
2   Fair     D     SI1    26   6398.192
3   Fair     D     SI2    29   6138.552
4   Fair     D     VS1     1   7083.000
5   Fair     D     VS2     7   8553.429
6   Fair     D    VVS1     1  10752.000
7   Fair     D    VVS2     2   9639.000
8   Fair     E      I1     5   2469.800
9   Fair     E     SI1    28   6407.464
10  Fair     E     SI2    45   5627.489
# ... with more rows

explain(bilanz)

<SQL>
SELECT `cut`, `color`, `clarity`, count(*) AS `count`, AVG(`price`) AS `mean_price`
FROM (SELECT *
FROM `diamonds`
WHERE (`carat` >= 1.0)) `cttxnwlelz`
GROUP BY `cut`, `color`, `clarity`


<PLAN>
  id select_type      table type  possible_keys  key key_len  ref  rows
1  1     PRIMARY <derived2>  ALL           <NA> <NA>    <NA> <NA> 19060
2  2     DERIVED   diamonds  ALL diamonds_carat <NA>    <NA> <NA> 50681
                            Extra
1 Using temporary; Using filesort
2                     Using where

Die Definition der Variablen bilanz geschieht dabei komplett ohne Interaktion mit der Datenbank. Erst beim Anzeigen von Daten wird das notwendige SQL ermittelt und auf der DB ausgeführt. Die ersten 10 resultierenden Datensätze werden angezeigt. Mittels der mächtigen Funktion explain() erhalten wir das erzeugte SQL-Kommando und sogar den Ausführungsplan auf der Datenbank. SQL-Kundige werden erkennen, dass die verketteten dplyr-Operationen in verschachtelte SELECT-Statements umgesetzt werden.

Zu guter Letzt sollen aber meistens die Ergebnisse der dplyr-Operationen irgendwie gesichert werden. Hier hat der Benutzer die Wahl, ob die Daten auf der Datenbank in einer neuen Tabelle gespeichert werden sollen oder ob sie komplett nach R transferiert werden sollen. Dies erfolgt mit den Funktionen compute() bzw. collect():

compute(bilanz, name = "t_bilanz", temporary = F)

df <- collect(bilanz)

str(df)

Classes ‘grouped_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 265 obs. of  5 variables:
 $ cut       : chr  "Fair" "Fair" "Fair" "Fair" ...
 $ color     : chr  "D" "D" "D" "D" ...
 $ clarity   : chr  "I1" "SI1" "SI2" "VS1" ...
 $ count     : num  3 26 29 1 7 1 2 5 28 45 ...
 $ mean_price: num  9014 6398 6139 7083 8553 ...
...

Durch diese beiden Operationen wurde eine neue Datenbanktabelle „t_bilanz“ erzeugt und danach der Inhalt der Bilanz als Data Frame zurück in den R-Interpreter geholt. Damit schließt sich der Kreis.

Fazit

Mit dem Paket dplyr von Hadley Wickham wird die Arbeit mit R Data Frames auf eine neue Ebene gehoben. Die Operationen sind konsistent, vollständig und performant. Durch den Verkettungs-Operator %>% erhalten sie auch bei hoher Komplexität eine intuitive Syntax. Viele Aspekte der Funktionalität lehnen sich an Relationale Datenbanken an, sodass Analysten mit SQL-Kenntnissen rasch viele Operationen auf R Data Frames übertragen können.

Zurück zu R Data Frames meistern mit dplyr – Teil 1.

 

Datenschutz, Sicherheit und Ethik beim Process Mining – Artikelserie

Als ich vor zwölf Jahren in die Niederlande zog und anfing, bei lokalen Supermarktketten wie Albert Heijn einzukaufen, habe ich mich zunächst gegen die Bonuskarte (Treuekarte für Rabatte) gewehrt, da ich nicht wollte, dass das Unternehmen meine Einkäufe nachverfolgen konnte. Ich verstand, dass die Verwendung dieser Informationen ihnen helfen könnte, mich zu manipulieren, indem sie Produkte anwerben oder so arrangieren würden, dass ich mehr kaufen würde, als mir lieb war. Es fühlte sich einfach falsch an.

english-flagRead this article in English:
Privacy, Security and Ethics in Process Mining – Article Series

Fakt ist aber, dass keine Datenanalyse-Technik intrinsisch gut oder schlecht ist. Es liegt allein in den Händen der Menschen, ob sie die Technologie so einsetzen, dass dabei etwas Produktives und Konstruktives entsteht. Während Supermärkte die Informationen ihrer Kunden aufgrund der Treue-Karten benutzen könnten, um sicherzustellen, dass sie den längsten Weg im Geschäft haben, wenn sie ihre gewöhnlichen Produkte einkaufen (und dadurch an soviel anderen Produkten wie möglich vorbeikommen), können sie auf der anderen Seite die Informationen verwenden, um den Einkauf angenehmer zu gestalten und mehr Produkte anzubieten, die wir mögen.

Die meisten Unternehmen haben mit der Anwendung von Datenanalysetechniken begonnen, mit welchen sie ihre Daten auf die eine oder andere Weise analysieren. Diese Datenanalysen können Unternehmen und ihren Kunden gewaltige Chancen einräumen, doch mit der zunehmenden Nutzung der Data-Science-Techniken drängt sich auch die Frage der Ethik und die einer verantwortungsvollen Anwendung in den Vordergrund. Initiativen, wie die Seminarreihe ‘Responsible Data Science [1]’, beschäftigen sich mit dem Thema insofern, als ein Bewusstsein geschaffen wird und die Forscher ermutigt werden, Algorithmen zu entwickeln, die sich auf Konzepte wie Fairness, Genauigkeit, Vertraulichkeit und Transparenz stützen [2].

Process Mining kann Ihnen erstaunlichen Einblicke in Ihre Prozesse verschaffen und Ihre Verbesserungsinitiativen mit Inspiration und Enthusiasmus bereichern, wenn Sie es richtig anwenden. Aber wie können Sie sicherstellen, dass Sie Process Mining verantwortungsvoll anwenden? Was sollten Sie beachten, wenn Sie Process Mining in Ihre eigene Organisation integrieren?

In dieser Artikelserie stellen wir Ihnen vier Richtlinien vor, die Sie befolgen können, um Ihre Process Minining-Analyse verantwortungsvoll vorzubereiten:

Teil 1 von 4: Klarstellung des Analyseziels

Teil 2 von 4: Verantwortungsvoller Umgang mit Daten

Teil 3 von 4: Anonymisierung in Betracht ziehen

Teil 4 von 4: Schaffung einer Kooperationskultur

Danksagung

Wir danken Frank van Geffen und Léonard Studer, der die ersten Diskussionen in der Arbeitsgruppe rund um das verantwortungsvolle Process Mining im Jahr 2015 initiiert haben. Wir danken ausserdem Moe Wynn, Felix Mannhardt und Wil van der Aalst für ihr Feedback zu früheren Versionen dieses Artikels.

 

Data Leader Mindset

Wie werden Führungskräfte zum Data Leader?

Als eine Keynote am Data Leader Day 2016 (www.dataleaderday.com) erläuterte ich den Weg einer gewöhnlichen Führungskräft hin zum Data Leader, gemäß meiner Erfahrung. Ein Data Leader ist eine Führungskraft mit datengetriebener, problemlösungsorientierter Denkweise.

Die Präsentation findet sich nachfolgend eingebettet und zeigt die Route von der konventionellen Führungskraft zum innovativen Data Leader:

Read more

ABC-XYZ-Analyse

Die ABC-XYZ-Analyse ist eine aussagekräftige Analyse für die Strategiefindung in der Warenwirtschaft und Logistik bzw. im Supply Chain Management. Die Analyse basiert auf der Vorstellung einer Pareto-Verteilung, die darauf hindeutet, dass oftmals eine kleine Menge eines großen Ganzen einen unverhältnismäßig großen Einfluss auf eben dieses große Ganze hat.

Die ABC-XYZ-Analyse beinhaltet im ersten Schritt eine ABC- und im zweiten Schritt eine XYZ-Analyse. Im dritten Schritt werden die Ergebnisse in einer Matrix zusammengeführt. In diesem Artikel erläutere ich nicht, wofür eine ABC-XYZ-Analyse dient und wie die Ergebnisse zu interpretieren sind, hier kann ich jedoch auf einen älteren Artikel “ABC-XYZ-Analyse” – www.der-wirtschaftsingenieur.de vom 3. Mai 2011 von mir verweisen, der vorher lesenswert ist, wenn kein Vorwissen zur ABC-XYZ-Analyse vorhanden ist.

Die Vorarbeit

Für die ABC- und XYZ-Analyse benötigen wir folgende Python-Bibliotheken:

import pandas as pd
import numpy as np
import random as random
import matplotlib.pyplot as pyplot

Wir laden die EKPO-Tabelle in ein DataFrame (Datenstruktur der Pandas-Bibliothek):

EKPO = pd.read_csv("[PFAD]EKPO.csv", delimiter=';', thousands='.', decimal=',')

Die Datei stammt aus einem SAP-Testsystem und steht hier zum Download bereit:

csv-icon

SAP.EKPO

Wir benötigen daraus nur folgende Zeilen:

EKPO_X = EKPO[['MATNR', 'MATKL', 'MENGE', 'PEINH', 'NETPR', 'NETWR']].copy()

Jetzt kommt der erste Kniff: Das Feld “MENGE” im SAP beschreibt die Menge in der jeweiligen Mengeneinheit (z. B. Stück, Meter oder Liter). Da wir hier jedoch nicht den genauen Verbrauch vorliegen haben, sondern nur die Einkaufsmenge (indirekt gemessener Verbrauch), sollten wir die Menge pro Preiseinheit “PEINH” berücksichtigen, denn nach dieser Preiseinheitsmenge erfolgt der Einkauf.

EKPO_X['Preiseinheitsmenge'] = EKPO_X['MENGE'] / EKPO_X['PEINH']

Für die Preiseinheitsmenge ein Beispiel:
Sie kaufen sicherlich pro Einkauf keine 3 Rollen Toilettenpapier, sondern eine oder mehrere Packungen Toilettenpapier. Wenn Sie zwei Packung Toilettenpapier für jeweils 2 Euro kaufen, die jeweils 10 Rollen beinhalten, ist die Preiseinheit = 10 und die Preiseinheitsmenge => 20 gekaufte Toilettenrollen / 10 Rollen pro Packung = 2 Packungen Toilettenpapier.

Nun haben wir also unsere für den Einkauf relevante Mengeneinheit. Jetzt sortieren wir diese Materialeinkäufe primär nach dem Umsatzvolumen “NETWR” absteigend (und sekundär nach der Preiseinheitsmenge aufsteigend, allerdings spielt das keine große Rolle):

EKPO_X = EKPO_X.sort_values(by = ['NETWR', 'Preiseinheitsmenge'], ascending=[False, True]) # Sortierung nach Umsatzvolumen pro Bestellung absteigend

Einige Störfaktoren müssen noch bereinigt werden. Erstens sollen Einträge mit Preisen oder Umsätzen in Höhe von 0,00 Euro nicht mehr auftauchen:

EKPO_X = EKPO_X[(EKPO_X.NETPR != 0) & (EKPO_X.NETWR != 0)]

Zweitens gibt es Einkäufe, die ein Material ohne Materialnummer und/oder ohne Materialklasse haben. Bei einer Zusammenfassung (Aggregation) über die Materialnummer oder die Materialklasse würden sich diese “leeren” Einträge als NULL-Eintrag bündeln. Das wollen wir vermeiden, indem wir alle NULL-Einträge mit jeweils unterschiedlichen Zufallszahlen auffüllen.

EKPO_X.MATNR[EKPO_X.MATNR.isnull() == True] = EKPO_X.MATNR[EKPO_X.MATNR.isnull() == True].apply(lambda x: random.random()) # Manche MATNR fehlen (NULL), diese füllen wir mit zufälligen Werten auf. Dabei ist es natürlich wichtig, dass die Zufallszahl für jede Zeile neu generiert wird! EKPO_X.MATNR.fillna(random.random()) funktioniert nicht, denn hier würde ein gleicher Wert alle NaN-Werte ersetzen

ABC – Analyse:

Nun geht es an die eigentliche ABC-Analyse, dafür müssen wir die Gruppierung der Materialien vornehmen. Gleich vorweg: Dies sollte man eigentlich über die einzelnen Materialnummern machen, da dies jedoch in der Visualisierung (auf Grund der hohen Anzahl und Vielfältigkeit) etwas aufwändiger ist, machen wir es über die Materialklassen. Wir gehen dabei einfach davon aus, dass die Materialklassen relativ homogene Materialien zusammenfassen und somit auch das Verbrauchs-/Einkaufverhalten innerhalb einer Gruppe nicht sonderlich viel Abweichung aufweist.

# Aggregation über die Materialklasse, Aufsummierung der Umsätze, Mengen und Volumen 
MATKL_MENGEN = (EKPO_X.MENGE.groupby(EKPO_X.MATKL).sum()).to_frame()
MATKL_PREISEINHEIT_MENGE = (EKPO_X.Preiseinheitsmenge.groupby(EKPO_X.MATKL).sum()).to_frame()
MATKL_VOLUMEN = (EKPO_X.NETWR.groupby(EKPO_X.MATKL).sum()).to_frame()

# Aggregation über die Materialklasse, Berechnung des Durchschnittpreises (ist bei einer Materialklasse, allerdings wenig sinnvoll!)
MATKL_Preise = (EKPO_X.NETPR.groupby(EKPO_X.MATKL).mean()).to_frame()EKPO_G = MATKL_MENGEN.join(MATKL_PREISEINHEIT_MENGE, how='left')

# Zusammenfügen der Ergebnisse (Left-Join)
EKPO_G = EKPO_G.join(MATKL_Preise, how='left')
EKPO_G = EKPO_G.join(MATKL_VOLUMEN, how='left')
EKPO_G = EKPO_G.sort_values(['NETWR'], ascending=False)

# Berechnung der kumulierten Umsätze und Mengen (Beachte: Vorher muss nach Umsätzen absteigend sortiert worden sein! (siehe oben)
EKPO_G['Volumen_kumuliert'] = EKPO_G.NETWR.cumsum()
EKPO_G['Menge_kumuliert'] = EKPO_G.MENGE.cumsum()

Nun können wir uns ganz im Sinne der ABC-Analyse die typische Pareto-Verteilung der kumulierten Umsätze (Umsatzgrößen absteigend sortiert) ansehen:

EKPO_G[['Menge_kumuliert','Volumen_kumuliert']].plot([EKPO_G.Menge_kumuliert, EKPO_G.Volumen_kumuliert], color=['red','pink'], figsize=[20,10], fontsize=8, title='Kumulierte Werte - Sortierung nach Materialklassen-Volumen')

abc_analyse_sap_netwr_menge_kumulierte_kurve_pareto

Die X-Achse zeigt die Materialklassen von links nach rechts in der Sortierung nach dem Umsatzvolumen (größester Umsatz links, kleinster Umsatz rechts). Die Y-Achse zeigt den Betrag der Umsatzhöhe (Euro) bzw. der Menge (Preiseinheitsmenge). Die Kurve der Menge ist mit Vorsicht zu bewerten, da primär nach dem Umsatz und nicht nach der Menge sortiert wurde.

Klassifikation:

Nun kommen wir zur Klassifikation. Hier machen wir es uns sehr einfach: Wir gehen einfach davon aus, dass 80% des Wertbeitrages aller Umsätze von etwa 20% der Materialien (hier: Materialklassen) umfassen und klassifizieren daher über feste relative Größen:

EKPO_G['ABC_Gruppe'] = "C" # Erstmal sind alle Materialien der C-Gruppe zugeordnet
EKPO_G['ABC_Gruppe'][EKPO_G.Volumen_kumuliert <= EKPO_G.NETWR.sum() / 100 * 95] = 'B' # Materialien, deren kumuliertes Volumen maximal 95% des Gesamtvolumens umfassen, sind Gruppe B
EKPO_G['ABC_Gruppe'][EKPO_G.Volumen_kumuliert <= EKPO_G.NETWR.sum() / 100 * 80] = 'A' # Materialien, deren kumuliertes Volumen maximal 80% des Gesamtvolumens umfassen, sind Gruppe A

Hinweis:
Intelligenter wird so eine Klassifikation, wenn wir den steilsten Anstieg innerhalb der kumulierten Volumen (die zuvor gezeigte Kurve) ermitteln und danach die Grenzen für die A-, B-, C-Klassen festlegen.

Optional: Farben für die Klassen festlegen (für die nachfolgende Visualisierung)

EKPO_G['Color'] = 'red'
EKPO_G['Color'][EKPO_G['ABC_Gruppe'] == 'B'] = 'orange'
EKPO_G['Color'][EKPO_G['ABC_Gruppe'] == 'C'] = 'green'

Jetzt Aggregieren wir über die ABC-Gruppe:

GruppenWerte = EKPO_G.groupby(['ABC_Gruppe'])
GruppenVolumen = (GruppenWerte.NETWR.sum()).to_frame()
GruppenMengen = (GruppenWerte.Preiseinheitsmenge.sum()).to_frame()

# Wieder zusammenfügen
GruppenVolumenMengen = GruppenVolumen.join(GruppenMengen)

Das Ergebnis:

GruppenVolumenMengen

Out:
NETWR Preiseinheitsmenge
ABC_Gruppe
A 6190725.01 175748.29
B 1231070.86 199599.24
C 408128.45 99745.63

Schauen wir uns nun die Verteilung der Werte und Mengen zwischen den Klassen A, B und C an:

GruppenVolumenMengen.plot(kind='bar', width=0.90, xlim=[0,1000], figsize=[10,5], yticks=GruppenVolumenMengen.NETWR)

 

abc_analyse_gruppen_vergleich

Es ist recht gut erkennbar, dass die Gruppe A deutlich mehr Umsatzvolumen (also Wertbeitrag) als die Gruppen B und C hat. Allerdings hat sie auch eine höhere Bestellmenge, wie jedoch nicht proportional von C über B zu A ansteigt wie das Umsatzvolumen.

Nachfolgend sehen wir die Klassifikation nochmal nicht kumuliert über die Umsatzvolumen der Materialien (Materialklassen):

EKPO_G[['NETWR']].plot(kind='bar', figsize=[20,10], legend = True, color=EKPO_G.Color, alpha=0.65, title='ABC - Analyse')

abc_analyse_sap_netwr

XYZ – Analyse

Für die XYZ-Analyse berechnen wir den arithmetischen Mittelwert, die Standardabweichung und die Summe aller Mengen pro Materialklasse [‘MATKL’] (oder alternativ, der einzelnen Materialnummern [‘MATNR’]) über eine Aggregation: 

Material_Menge = EKPO_X.Preiseinheitsmenge.groupby(EKPO_X.MATKL).agg({'mean', 'std', 'sum'})
#Oder mit dem Material: Material_Menge = EKPO_X.Preiseinheitsmenge.groupby(EKPO_X.MATNR) .agg({'mean', 'std', 'sum'})

#Leider ergeben sich einige NaNs bei der Standardabweichung, da ein Material oder eine Materialklasse nur eine einzige Buchung haben kann, diese müssen wir bereinigen (hier: mit Nullen auffüllen):
Material_Menge = Material_Menge.fillna(0)

Die XYZ-Analyse soll aufzeigen, welche Materialien (hier: Materialklassen) in stabilen Mengen verbraucht (hier: eingekauft) werden und welche größere Schwankungen hinsichtlich der Verbrauchsmenge (hier: Einkaufsmenge) aufweisen. Dazu berechnen wir den Variationskoeffizienten:

Variationskoeffizient = frac{Standardabweichung}{Mittelwert}

Wir berechnen diesen Variationskoeffizienten und sortieren das DataFrame nach diesem aufsteigend:

Material_Menge['Variationskoeffizient'] = Material_Menge['std'] / Material_Menge['mean']
Material_Menge = Material_Menge.sort_values(['Variationskoeffizient'], ascending = True)

Klassifikation:

Nun klassifizieren wir die Materialien (Materialklassen) über den Variationskoeffizienten in XYZ-Klassen. Dabei gehen wir davon aus, dass Materialien/Materialklassen, die einen Variationskoeffizienten von bis zu 70% des Maximalwertes aufweisen, in die Y-Klasse fallen. Solche, die nur maximal 20% des Maximalwertes aufweisen, fallen in die X-Klasse:

Material_Menge['XYZ_Gruppe'] = 'Z'
Material_Menge['XYZ_Gruppe'][Material_Menge.Variationskoeffizient <= Material_Menge.Variationskoeffizient.max() / 100 * 70] = 'Y'
Material_Menge['XYZ_Gruppe'][Material_Menge.Variationskoeffizient <= Material_Menge.Variationskoeffizient.max() / 100 * 20] = 'X'

Auch hier gilt analog zur ABC-Analyse: Intelligente Klassifikation erfolgt über die Analyse der Kurve der kumulierten Variationskoeffizienten. Die Grenzen der Klassen sollten idealerweise zwischen den steilsten Anstiegen (bzw. die größten Wertedifferenzen) zwischen den Werten der kumulierten Variationskoeffizienten-Liste gezogen werden.

Optional: Farben fürs Plotten setzen.

Material_Menge['Color'] = 'red'
Material_Menge['Color'][Material_Menge.XYZ_Gruppe == 'Y'] = 'orange'
Material_Menge['Color'][Material_Menge.XYZ_Gruppe == 'X'] = 'green'

Jetzt schauen wir uns mal die Verteilung der Materialien hinsichtlich des Variationskoeffizienten an:

Material_Menge.Variationskoeffizient.plot(kind='bar', width=0.90, xlim=[0,1000], figsize=[20,5], rot=90, color=Material_Menge.Color, title='XYZ - Analyse')

xyz_analyse_sap_matkl_menge

Die meisten Materialklassen haben einen recht niedrigen Variationskoeffizienten, sind im Einkauf (und daher vermutlich auch im Verbrauch) recht stabil. Die Materialklasse 0004 hingegen ist einigen Mengenschwankungen unterworfen. In der ABC-Analyse ist diese Materialklasse 0004 als B-Gruppe klassifiziert.

ABC-XYZ-Analyse

Nun möchten wir also die zuvor erstellte ABC-Klassifikation mit der XYZ-Klassifikation zusammen bringen.

Dafür fügen wir die beiden Pandas.DataFrame über den Index (hier die Materialklasse ‘MATKL’, im anderen Fall das Material ‘MATNR’) zusammen:

XYZ_ABC = pd.merge(EKPO_G, Material_Menge, left_index = True, right_index = True, how='left')

Die Zusammenfassung als Kreuztabelle:

pd.crosstab(XYZ_ABC.ABC_Gruppe, XYZ_ABC.XYZ_Gruppe, margins=True)

Out:

  X Y Z All

A 17 1 0 18

B 19 1 1 21

C 69 2 0 71

All 105 4 1 110

Für die Interpretation dieser Ergebnisse verweise ich erneut auf den Artikel bei der-wirtschaftsingenieur.de.

Einführung in WEKA

Waikato Environment for Knowledge Analysis, kurz WEKA, ist ein quelloffenes, umfangreiches, plattformunabhängiges Data Mining Softwarepaket. WEKA ist in Java geschrieben und wurde an der WAIKATO Iniversität entwickelt. In WEKA sind viele wichtige Data Mining/Machine Learning Algorithmen implementiert und es gibt extra Pakete, wie z. B. LibSVM für Support Vector Machines, welches nicht in WEKA direkt implementiert wurde. Alle Einzelheiten zum Installieren und entsprechende Download-Links findet man unter auf der Webseite der Waikato Universität. Zusammen mit der Software wird ein Manual und ein Ordner mit Beispiel-Datensätzen ausgeliefert. WEKA arbeitet mit Datensätzen im sogenannten attribute-relation file format, abgekürzt arff. Das CSV-Format wird aber ebenfalls unterstützt. Eine Datei im arff-Format ist eine ASCI-Textdatei, welche aus einem Header- und einem Datateil besteht. Im Header muss der Name der Relation und der Attribute zusammen mit dem Typ stehen, der Datenteil beginnt mit einem @data-Schlüsselwort. Als Beispiel sei hier ein Datensatz mit zwei Attributen und nur zwei Instanzen gegeben.

@relation my_relation
@attribute first_attribute numeric
@attribute second_attribute numeric
@attribute class {-1,1}
@data
2.5 3.8 1
1.2 1.5 -1

WEKA unterstützt auch direktes Einlesen von Daten aus einer Datenbank (mit JDBC) oder URL. Sobald das Tool installiert und gestartet ist, landet man im Hauptmenü von WEKA – WEKA GUI Chooser 1.

Abbildung 1: WEKA GUI Chooser

Abbildung 1: WEKA GUI Chooser

Der GUI Chooser bietet den Einstieg in WEKA Interfaces Explorer, Experimenter, KnowledgeFlow und simple CLI an. Der Explorer ist ein graphisches Interface zum Bearbeiten von Datensätzen, Ausführen von Algorithmen und Visualisieren von den Resultaten. Es ist ratsam, dieses Interface als Erstes zu betrachten, wenn man in WEKA einsteigen möchte. Beispielhaft führen wir jetzt ein paar Algorithmen im Explorer durch.

Der Explorer bietet mehrere Tabs an: Preprocess, Classify, Cluster, Associate, Select attributes und Visualize. Im Preprocess Tab hat man die Möglichkeit Datensätze vorzubereiten. Hier sind zahlreiche Filter zum Präprozessieren von Datensätzen enthalten. Alle Filter sind in supervised und unsupervised unterteilt, je nachdem, ob das Klassenattribut mitbetrachtet werden soll oder nicht. Außerdem kann man entweder Attribute oder Instanzen betrachten, mit Attributen lässt man Filter spaltenweise arbeiten und bei Instanzen reihenweise. Die Auswahl der Filter ist groß, man kann den ausgewählten Datensatz diskretisieren, normalisieren, Rauschen hinzufügen etc. Unter Visualize können z. B. die geladenen Datensätze visualisert werden. Mit Select attributes kann man mithilfe von Attribut Evaluator und Search Method ein genaueres Ergebnis erzielen. Wenn man im Preprocess den Datensatz lädt, erhält man einen Überblick über den Datensatz und dessen Visualisierung. Als Beispiel wird hier der Datensatz diabetes.arff genommen, welcher mit WEKA zusammen ausgeliefert wird. Dieser Datensatz enthält 768 Instanzen mit je 9 Attributen, wobei ein Attribut das Klassenattribut ist. Die Attribute enthalten z. B. Informationen über die Anzahl der Schwangerschaften, diastolischer Blutdruck, BMI usw. Alle Attribute, außer dem Klassenattribut, sind numerisch. Es gibt zwei Klassen tested negativ und tested positiv, welche das Resultat des Testens auf diabetes mellitus darstellen. über Preprocess -> Open File lädt man den Datensatz in WEKA und sieht alle relevanten Informationen wie z. B. Anzahl und Name der Attribute. Nach dem Laden kann der Datensatz klassifiziert werden.

Abbildung 2: Diabetes.arff Datensatz geladen in WEKA

Abbildung 2: Diabetes.arff Datensatz geladen in WEKA

Hierzu einfach auf Classify klicken und unter Choose den gewünschten Algorithmus auswählen. Für diesen Datensatz wählen wir jetzt den Algorithmus kNN (k-Nearest Neighbour). Der Algorithmus klassifiziert das Testobjekt anhand der Klassenzugehörigkeit von den k Nachbarobjekten, die am nähsten zu dem Testobjekt liegen. Die Distanz zwischen den Objekten und dem Testobjekt wird mit einer Ähnlichkeitsmetrik bestimmt, meistens als euklidische oder Manhattan-Distanz. In WEKA ist der Algorithmus unter lazy iBk zu finden. Wenn man auf das Feld neben dem Algorithmusnamen in WEKA mit rechter Maustaste klickt, kann man unter show properties die Werte für den ausgewählten Algorithmus ändern, bei iBk kann man u.A. den Wert für k ändern. Für den ausgewählten Datensatz diabetes.arff stellen wir beispielsweise k = 3 ein und führen die 10-fache Kreuzvalidierung durch, indem wir unter Test Options die Cross Validation auswählen. Nach der Klassifikation werden die Ergebnisse in einer Warhheitsmatrix präsentiert. In unserem Fall sieht diese wie folgt aus:

a b <-- classified as
410 90 | a = tested_negative
120 148 | b = tested_positive

Die Anzahl der richtig klassifizierten Instanzen beträgt 72.6563 %. Wenn man in der Result list auf den entsprechenden Algorithmus einen Rechtsklick macht, kann man z. B. noch den Fehler der Klassifizierung visualisieren. Entsprechend lassen sich im Explorer unter Cluster Clustering-Algorithmen und unter Associate Assoziationsalgorithmen auf einen ausgewählten Datensatz anwenden. Die restlichen Interfaces von WEKA bieten z. T. die gleiche Funktionalität oder erweitern die Möglichkeiten des Experimentierens, fordern aber mehr Erfahrung und Wissen von dem User. Das Experimenter Interface dient dazu, mehrere Datensätze mit mehreren Algorithmen zu analysieren. Mit diesem Interface kann man groß-skalierte Experimente durchführen. Simple CLI bietet dem User eine Kommandozeile, statt einem graphischen Interface, an.

Data Driven Thinking

Daten gelten als vierter Produktionsfaktor – diese Erkenntnis hat sich mittlerweile in den meisten Führungsetagen durchgesetzt. Während das Buzzword Big Data gerade wieder in der Senke verschwindet, wird nun vor allem von der Data Driven Company gesprochen, oder – im Kontext von I4.0 – von der Smart Factory.
Entsprechend haben die meisten Konzerne in den Aufbau einer Big-Data-Infrastruktur investiert und auch die größeren Mittelständler beginnen allmählich damit, einen Anfang zu setzen. Für den Anfang bedarf es jedoch gar nicht erst eine neue IT-Infrastruktur oder gar eine eigene Data Science Abteilung, ein richtiger Start zum datengetriebenen Unternehmen beginnt mit dem richtigen Mindset – ein Bewusst sein für Datenpotenziale.

Data Driven Thinking

Auch wenn es spezielle Lösungsanbieter anders verkaufen, ist nicht etwa eine bestimmte Datenbank oder eine bestimmte Analysemethodik für die Bewerkstelligung der Digitalisierung notwendig, sondern die datengetriebene Denkweise. In den Datenbeständen der Unternehmen und jenen aus weiteren bisher unerschlossenen Datenquellen stecken große Potenziale, die erkannt werden wollen. Es ist jedoch nicht notwendig, gleich als ersten Schritt jegliche Potenziale in Daten erkennen zu müssen, denn es ist viel hilfreicher, für aktuelle Problemstellungen die richtigen Daten zu suchen, in denen die Antworten für die Lösungen stecken könnten.

Data Driven Thinking oder auch kurz Data Thinking, wie angeblich von einem der ersten Chief Data Officer als solches bezeichnet und auch von meinem Chief Data Scientist Kollegen Klaas Bollhoefer beworben, ist die korrekte Bezeichnung für das richtige Mindset, mit dem sowohl aktuelle Probleme als auch deren Lösungen aus Daten heraus besser identifiziert werden können. Hierfür braucht man auch kein Data Scientist zu sein, es reicht bereits ein in den Grundzügen ausgeprägtes Bewusstsein für die Möglichkeiten der Datenauswertung – Ein Skill, der zeitnah für alle Führungskräfte zum Must-Have werden wird!

Data Scientists als Design Thinker

Was gerade in Europa vordergründig kritisiert wird: Es treffen traditionelle Denkmuster auf ganz neue Produkte und Dienste, mit immer schnelleren Entwicklungsprozessen und tendenziell kürzeren Lebenszyklen – eine zum Scheitern verurteilte Kombination und sicherlich auch einer der Gründe, warum us-amerikanische und auch chinesische Internetunternehmen hier die Nase vorn haben.

Ein zeitgemäßer Ansatz, der im Produktmanagement bereits etabliert ist und genau dort das letzte Quäntchen Innovationskraft freisetzt, ist Design Thinking. Dabei handelt es sich um einen iterativen Ideenfindungs und -validierungsprozess, bei dem die Wünsche und Bedürfnisse der Anwender durchgängig im Fokus stehen, im Hintergrund jedoch steht ein interdisziplinäres Team, dass ein Geschäftsmodell oder einen Geschäftsprozess unter Berücksichtigung des Kundenfeedbacks designed. Nutzer und Entwickler müssen dabei stets im engen Austausch stehen. Erste Ideen und Vorschläge werden bereits möglichst früh vorgestellt, damit bereits lange vor der Fertigstellung das Feedback der Anwender in die weitere Realisierung einfließen kann. Somit orientiert sich die gesamte Entwicklungsphase am Markt – Zu spät erkannte Fehlentwicklungen und Flops lassen sich weitgehend vermeiden. Design Thinker stellen dem Nutzer gezielte Fragen und analysieren dessen Abläufe (und nichts anderes tut ein Data Scientist, er beobachtet seine Welt jedoch viel umfassender, nämlich über jegliche zur Verfügung stehende Daten).

Der Design Thinking Prozess führt crossfunktionale Arbeitsgruppen durch  sechs  Phasen:

In der ersten Phase, dem Verstehen, definiert die Arbeitsgruppe den Problemraum. In der darauffolgenden Phase des Beobachtens ist es entscheidend, die Aktivitäten im Kontext, also vor Ort, durchzuführen und Anwender in ihrem jeweiligen Umfeld zu befragen. In der dritten Phase werden die gewonnenen Erkenntnisse zusammengetragen. In der nachfolgenden Phase der Ideenfindung entwickelt das Team zunächst eine  Vielzahl von Lösungsoptionen. Abschließend werden beim Prototyping, in der fünften Phase, konkrete Lösungen entwickelt, die in der letzten Phase an den Zielgruppen auf ihren Erfolg getestet werden.

Beim Design Thinking mag es zwar eine grundsätzliche Vorgabe für den Ablauf der Ideenfindung und -erprobung geben – der eigentliche Mehrwert steckt jedoch in der dafür nötigen Denkweise und der Einstellung gegenüber dem Experimentieren sowie die Arbeit in einem interdisziplinären Team.

Data Driven Business Cycle

Data Driven Thinking überträgt diesen Ansatz auf die Mehrwert-Generierung unter Einsatz von Datenanalytik und leistet einen Transfer dieser systematischen Herangehensweise an komplexe Problemstellungen im Hinblick auf die Realisierung dafür angesetzter Big Data Projekte. Design Thinking unter Nutzung von Big Data ist überaus mächtig, wenn es darum geht, kundenorientierte Produkte und Prozesse zu entwickeln. Im Data Driven Business Cycle werden für immer neue Ideen und Fragestellungen:

  1. Daten generiert und gesammelt
  2. Daten gesichert, verwaltet und aufbereitet
  3. Daten analysiert
  4. daraus Erkenntnisse gezogen

Aus diesen sich iterativ kreisenden Prozessen der Datennutzung entsteht ein Data Pool (oftmals auch als Data Lake bezeichnet), der immer wieder zum für die Beantwortung von Fragen genutzt werden kann.

Prinzipien des maschinellen Lernen verstehen lernen

Data Driven Thinking entsteht mit dem Bewusstsein für die Potenziale, die in Daten liegen. Noch wirkungsvoller wird diese Denkweise, wenn auch ein Bewusstsein für die Möglichkeiten der Datenauswertung vorhanden ist.

„Kinder, die heute nicht programmieren können, sind die Analphabeten der Zukunft.“ schimpfte Vorzeige-Unternehmer Frank Thelen kürzlich in einer Politik-Talkrunde und bekräftigte damit meine noch davor verkündete Meinung “Karriere ohne Programmier-Erfahrung wird nahezu undenkbar”, denn “Systeme der künstlichen Intelligenz werden in der Zukunft unseren Einkauf und die Warenlieferung übernehmen, unsere Autos fahren, unsere Buchhaltung erledigen, unser Geld optimal auf den Finanzmärkten anlegen und unsere Krankheiten frühzeitig diagnostizieren und die bestmögliche medizinische Behandlung vorgeben.”

Jetzt muss niemand zum Experten für die Entwicklung künstlicher Systeme werden, um hier schritthalten zu können. Ein grundsätzliches Verständnis von den unterschiedlichen Prinzipien des maschinellen Lernen kann jedoch dabei helfen, solche Systeme und die dazugehörigen Chancen und Risiken besser einschätzen zu können, denn diese werden uns in Alltag und Beruf vermehrt begegnen, dabei einen entscheidenden Einfluss auf den Erfolg des Data Driven Business ausüben.