Warenkorb Analyse mit R

Warenkorb Analyse mit R
Foto von Unsplash 

Die diversen Handelsgeschäfte, ob online oder lokal, besitzen meist eine Vielzahl von Daten, in denen sie das Kaufverhalten ihrer Kunden speichern. Insbesondere die Information über den Warenkorb eines Kunden ist besonders wertvoll. Durch die Daten können Kunden verglichen werden anhand der gekauften Artikeln. Das ist bereits Standard geworden und erlaubt den Geschäften ihren Kunden Empfehlungen zu anderen Produkten zu machen, die andere Kunden mit dem selben oder zumindest einem ähnlichen Kaufverhalten ebenso erworben haben.

Die hierfür notwendige Analyse der Daten wird daher auch als Warenkorb Analyse bezeichnet.

In diesem Blog werden wir nun eine solche Warenkorb Analyse an einem Beispiel Datensatz durchführen.

Disclaimer

Die Daten sind lizenziert unter CC-BY-NC-SA und wurden von Dr. Wölker unter der folgenden Seite veröffentlicht:

Logistics Case Studies: Der Lieferschein
Ein Blog für wahre Logistik (Diagnose, Analyse,Design, Lösung, Praxis) und wie man es studiert, damit man hinterher einen guten Job bekommt.

Was genau ist eine Warenkorb Analyse?

Unter eine Warenkorb Analyse wird eine Analyse, die das Kaufverhalten von Kunden betrachtet, verstanden. Mit ihr sollen Vorhersagen getroffen werden, welches Produkt der Kunde am wahrscheinlichsten zu seinem Warenkorb hinzufügt, ausgehend von den Produkten, die er bereits gewählt hat. Also welche Artikel er am wahrscheinlichsten zusätzlichen kaufen würde.

Auf diese Weise kann dem Kunden noch während dessen Kauf zielgerichtet Empfehlungen zu anderen Produkten gemacht werden.

Streng genommen handelt es sich hierbei um eine Korrelation Analyse, die die Wahrscheinlichkeit berechnet das Produkt A gekauft wird, wenn Produkt B bereits zum Kauf gewählt wurde. Also die Korrelation zwischen A und B.

Das Basket Shop Layout

Um in R eine Warenkorb Analyse durchführen zu können, müssen die Daten in die Form eines sogenannten „basket shop layout“ überführt werden. Das ist ein Datenformat, dass speziell für solch eine Warenkorb Analyse entworfen worden ist.

Im folgenden betrachten wir einen Ausschnitt der Daten, wie sie im basket shop layout aussehen.

Zu beachten ist, dass alle Materialnummern in einer Zeile zu einem einzelnen Kauf bzw. Warenkorb gehören und dass eben jene Materialnummern nicht in unterschiedlichen Spalten, sondern als Liste vereint hinterlegt sind.

Im allgemeinen sollten Daten immer zunächst bereinigt werden, bevor diese genutzt werden. Es ist nicht unübliches das eine Vielzahl an Zellen NULL Werte beinhalten oder andere Anomalien aufweisen. Unser zu Grunde liegenden Daten weisen glücklicherweise jedoch keine solchen Probleme auf, da diese als Lehrmittel erstellt worden sind.

Für die Verarbeitung der Daten gehen wir zunächst von der vollständigen Lieferschein Tabelle als Ausgangslage aus. Für unsere Warenkorb Analyse sind jedoch nur die Lieferscheine mit den jeweiligen Materialnummern von Interesse, weshalb wir diese Daten herausfiltern.

Hierzu überführen wir die Daten zunächst mit glimpse in eine atomare Tabelle der Form:

Lieferschein -> Materialnummer

#cast into a glimpse object
gTemp <- glimpse(lieferscheine_df %>% select(lieferschein,materialnummer))

Anschließend werden die beiden Spalten nach Lieferscheinen gruppiert und die Materialnummern zu den jeweiligen Lieferscheinen in einer Liste zusammengefasst. Ebenso entfernen wir danach die Lieferscheine selbst, da wir die Zuordnung nun zeilenbasiert haben. Prinzipiell liegt damit auch das basket shop layout bereits vor. Jedoch ist die Tabelle noch etwas unschön, weshalb wir sie zusätzlich noch zur besseren Lesbarkeit umbenennen.

#cast into a glimpse object
#groub by delivery bill and merge all materialnumbers in one list
transactionData <- ddply(gTemp,c("lieferschein"),
                       function(dataFrame)paste(dataFrame$materialnummer,
                       collapse = ","))

#rename column and remove unused columns
names(transactionData)[names(transactionData) == "V1"] <- "items"
transactionData <- transactionData %>% select(items) #called basket layout

Das resultierende Ergebnis sieht dann aus wie die oben gezeigte Tabelle.

Zusätzliche können wir diese noch als CSV-Datei exportieren, was uns im nächsten Schritt auch nützlich sein wird und wir auch im nachhinein die Daten auch außerhalb von R repräsentieren könnten.

#export the data as CSV file
write.csv(transactionData,"./market_basket_transactions.csv", quote = FALSE, row.names = FALSE))

Die Apriori Funktion

Nun, da wir die Daten in der passenden Form haben, schauen wir wie genau wir eine Warenkorb Analyse durchführen können mit Hilfe einer Korrelation Analyse. Hierzu wird die sogenannte Apriori Funktion genutzt.

Diese leitet uns Regeln zu unseren Daten ab, basierend auf vorliegende Korrelation und berechnet uns damit eine konkrete Wahrscheinlichkeit eines Kaufs von Produkt A, wenn auch B gekauft wird. Diese kann zum Beispiel wie folgt aussehen:

{263012040,276016000} => {276012000} 0.7542662%

Dies lässt sich verstehen als:

Wenn "263012040" und "276016000" gekauft wurde, dann wir zu einer Wahrscheinlichkeit von rund 75% auch "276012000" gekauft.

Die Apriori Funktion erfordert jedoch im speziellen ein Transaktion Objekt. Mit der Vorarbeit durch das basket shop layout ist der notwendige Typ-cast nun trivial und kann allein durch den Import der CSV-Datei erfolgen.

#export the data as CSV file
#import the data using a Transaction object builder
tr <- read.transactions("./market_basket_transactions.csv", format = 'basket', sep=','))

Die Apriori Funktion ist nun einfach anzuwenden:

# search for rules within the items using Apriori algorithm
rules <- apriori(tr, parameter = list(supp=0.001, conf=0.5,maxlen=10))

Ausschlaggebend für diese Analyse sind die Parameter supp (Support), conf (Confidence) und maxlen (maximal Länge). Alle drei dienen als Grenzwerte und beschränken den Suchraum der Apriori Funktion für Regeln.

  • Confidence: Beschreibt die Wahrscheinlichkeit, dass A gekauft wird, wenn auch B gekauft wird. Es werden nur Regeln gesucht, die mindestens so zuversichtlich sind wie in conf angegeben (Hier 50%).
  • Support: Beschreibt die Frequenz der Korrelation in dem Datensatz und wird auf ein Minimum von 0.001 festgelegt.
  • Maximal Länge: Beschreibt die maximale Größe der Menge an Materialien, die jeweils für A und B betrachtet werden, da auch Korrelationen in der Form

{ A1, A2, … A maxlen } -> { B1, B2, ... B maxlen }

möglich sind.

Interpretation der Ergebnisse

Wir können nun die Ergebnisse der Apriori Funktion einerseits textuell als auch visuell Beschreibung. Eine textuelle Beschreibung allein mit ...

rulesDF <- as(rules,"data.frame") %>% arrange(desc(confidence))
summary(rules)

ist jedoch nicht empfehlenswert, da sie schlicht durch die Fülle an Daten unübersichtlich ist. Stattdessen empfiehlt es sich die Daten in einem Scatter Plot darstellen zu lassen.


plot(rules)

Im Scatter Plot wird zusätzlich der lift angezeigt, welcher die Abhängigkeit von A zu B ausdrückt und damit die Wichtigkeit einer Regel.

Zu sehen ist, dass insbesondere eine große Menge an Regeln mit einer hohen Confidence auch einen hohen Lift haben. Dadurch können wir mit ziemlicher Sicherheit auf B schließen, aber ebenso ausschließen, dass B für ein andere Menge von Materialnummern in A gekauft wird. Damit sind uns zuverlässige Vorhersagen möglich. Zugleich fällt aber auch auf, dass der Support mit wenigen Ausnahmen gleichbleibend gering ist. Dies lässt uns darauf schließen, dass der Datensatz zwar starke Korrelationen besitzt, diese aber nur selten im Datensatz vorzufinden sind.

Für eine exemplarisches Beispiel von mehreren Regeln wählen wir eine beliebige Materialnummer und lassen uns die zehn Regeln mit der höchsten Confidence anzeigen. Hier für die Materialnummer:

276012000

Im folgenden finden sich die zehn Regel mit der höchsten Confidence als ein exemplarisches Beispiel. Dabei betrachten wir aber nur dass das Produkt auf dass wir schließen wollen die Materialnummer "276012000".

Der notwendige Code sieht aus wie folgt:

#defining some functions for modularisation
getRulesFor <- function(material) {
  temp <- apriori(tr, parameter = list(supp=0.001, conf=0.5),appearance = list(default="lhs",rhs=material))
  rulesSpecificDF <- as(temp,"data.frame") %>% arrange(desc(confidence))
  rulesSpecificDF$ruleNumber <- seq.int(nrow(rulesSpecificDF))
  rulesSpecificDF
}

plotMaterialRules <- function(material,amountOfRules) {
  rulesSpecific <- getRulesFor(material)
  plottingData <- rulesSpecific %>% select(confidence,support, rules)%>% head(amountOfRules)
  plottingData <- plottingData %>% arrange(confidence)
  plottingData$ruleNumber <- seq.int(nrow(plottingData))
  ggplot(plottingData, aes(x=ruleNumber, y=confidence,fill=confidence)) +
    geom_col(fill="steelblue",color="white")+
    scale_y_continuous(breaks = seq(from=0, to=1, by=0.1)) +
    scale_x_continuous(breaks = seq(from=0, to=amountOfRules, by=1)) +
    geom_text(aes(label=paste(rules,"\t",as.character(round(confidence*100,digits=2)),"%"), hjust=+1))+
    coord_flip()+
  theme_minimal()
}

plotMaterialRules("276012000",10)

Unsere Schlussfolgerung:

Wir können zwar Muster im Kaufverhalten der Kunden erkennen, erkennen aber ebenso am Support, dass ihr Kaufverhalten äußerst heterogen zueinander ist und es zwischen ihnen allgemein nur wenig Ähnlichkeiten gibt. Gründe hierfür können schlicht die Interessen der Kunden oder die Vielfalt an unterschiedlichen Materialien sein.