Przejdź do treści

Analiza koszykowa – przepisy kulinarne

Dzisiaj zajmiemy się zagadnieniem zwanym analizą koszykową, która jest jednym z podstawowych narzędzi do badania preferencji zakupowych klientów. Na podstawie wyników można odpowiednio ułożyć produkty w sklepie albo przygotować promocje. Można również przygotować algorytm rekomendacji (“klienci, którzy kupili X kupili też Y”).

Zobaczymy też, że polska kuchnia cebulą stoi. I pieprzem.

Do analizy koszykowej w R wykorzystamy pakiet arules.

Teoria

Przed przystąpieniem do analizy potrzebujemy trochę teorii. Wcześniejsze wpisy mówiły o zagadnieniach dość intuicyjnych, ale analiza koszyków wymaga wprowadzenia kilku pojęć.

Ale jeśli nie interesuje Cię teoria, jakieś wskaźniki i całe to programowanie to możesz od razu przeskoczyć do analizy przepisów klikając tutaj.

Koszyk zakupów

Wyobraźmy sobie sklep, w którym można kupić tylko cztery produkty: chleb, mleko, masło i jabłka.

Do tego sklepu przychodzi kolejno 10 osób i każdy kupuje co mu potrzeba, kolejno:

  • chleb
  • chleb, mleko, masło
  • mleko, jabłka
  • jabłka
  • masło, jabłka
  • chleb, masło
  • chleb, mleko
  • chleb, jabłka
  • mleko
  • chleb, mleko

Każdy klient to transakcja albo koszyk zakupów.

Zapiszmy to w formie listy w R:

Możemy taką listę przerobić na obiekt typu transactions, na którym operuje pakiet arules. Zobaczmy od razu co zawiera taki obiekt w podsumowaniu:

Mamy:

  • liczbę transakcji (10) i liczbę produktów (4)
  • od razu dostajemy listę najpopularniejszych produktów razem z liczbą koszyków, w których się znalazły
  • oraz liczbę produktów w koszykach i jej rozkład (mieliśmy 3 zakupy po 1 produkcie, 6 zakupów po 2 produkty i jeden zakup złożony z trzech produktów)

Teraz możemy sobie na przykład narysować jak wyglądała macierz koszyk-produkt:

Dość to przekombinowane (ale ładne), w dodatku trzeba znać nazwy poszczególnych produktów i wiedzieć w jakiej kolejności są ułożone (alfabetycznie). To samo można zrobić prościej – potraktować macierz jako obrazek w dwóch kolorach:

W tym układzie osie są odwrotnie – numery transakcji rosą w prawo.

Oczywiście takie rysowanie ma sens przy małej ilości koszyków i produktów. W pewnym momencie staje się nieczytelne (zobaczycie później, pod koniec wpisu).

Nie daje też właściwie żadnej informacji. Ale zapamiętajcie na chwilę ten obrazek, bo przyda się przy objaśnianiu trzech współczynników (będzie łatwiej liczyć poszczególne kombinacje jako szare pola na obrazku).

Interesuje nas przede wszystkim popularność produktów. W pakiecie arules mamy na to gotową funkcję:

Wszystko zgadza się z tym co dostaliśmy w wyniku działania summary(trans):

  • chleb kupiono 6 razy (6/10 = 0.6)
  • jabłka 4 razy
  • masło 3 razy
  • i mleko 5 razy

Można też policzyć szare pola na obrazku (ich liczbę w kolumnie dla każdego z produktów).

Wskaźniki

Liczba (i częstość – znając liczbę koszyków zawierających chleb i liczbę wszystkich koszyków wiemy, że chleb był kupowany w 60% przypadków) to podstawowa miara, ale nie daje wiele informacji, szczególnie o produktach współkupowanych. Częściej kupuje się chleb i masło czy chleb i mleko? A jak ktoś kupił chleb to bardziej prawdopodobne, że dokupi masło czy jednak mleko?

Przy 10 koszykach można to policzyć, ale dla wszystkich transakcji w miesiącu w jakimś hipermarkecie?

Są trzy wskaźniki, które pomogą nam ustalić reguły rządzące koszykami. Skorzystam z angielskich nazw (polskie są jakieś koślawe, a i to co widać w wyniku działania omawianych tutaj funkcji też opisane jest angielskimi nazwami):

Support

    \[ {\mathrm {supp}}(A) = \frac {|\{t\in T;A\subseteq t\}|}{|T|} \]

Miara, która mówi o tym jak często dany koszyk t zawierający produkt A (może to być lista produktów) pojawia się we wszystkich transakcjach T. Czyli ile procent klientów kupuje dokładnie te produkty w czasie jednej sesji zakupowej.

Policzyliśmy już każdy produkt oddzielnie, ale ile procent kupuje chleb i mleko, a ile chleb i masło? Kogo jest więcej? Z obrazka już widać, że chleb i mleko pojawia się w koszyku 3 razy (tak więc support = 0.3), a chleb i masło – 2 razy (support = 0.2). Przy okazji jeden z tych razów jest wspólny: koszyk zawiera wszystkie trzy produkty (i dla niego support to 0.1). Oczywiście specjalnie, na potrzeby przykładu.

Confidence

    \[ {\mathrm {conf}}(A\Rightarrow B) = \frac{{\mathrm {supp}}(A\cup B)}{\mathrm {supp}(A)} \]

Ta miara mówi o tym jak często towar A jest kupowany z towarem B (czyli w koszyku są co najmniej A i B). Mleko kupowane razem z chlebem i masłem kupione było dokładnie jeden raz wśród 10 koszyków, albo inaczej na to patrząc – jeden z dwóch koszyków zawierających chleb i masło zawierał też mleko (confidence = 0.5).

Lift

To miara która mówi o swego rodzaju “wzmocnieniu” zakupu produktu B przez produkt A. Definiowana jest jako:

    \[ {\mathrm {lift}}(A\Rightarrow B) = \frac{{\mathrm {supp}}(A\cup B)}{{\mathrm {supp}}(A)\times {\mathrm {supp}}(B)} \]

Jeśli wartość lift jest równa jeden dla danej reguły to prawdopodobieństwo wystąpienia poprzednika (produkt A) i konsekwencji (produkt B) są niezależne od siebie. Nie można wówczas wyciągnąć żadnego wniosku o współkupowalności produktów A i B.

Dla lift > 1, lift określa stopień w jakim zdarzenia są od siebie zależne, co daje nam miernik współkupowalności produktów.

Spójrzmy na nasze 10 koszyków i wartości poszczególnych wskaźników.

Najpierw musimy przygotować odpowiednie reguły – czyli całą analizę. “Całą analizę” oznacza na szczęście wywołanie jednej funkcji:

Widzimy, że powstało 19 reguł. Parametry supp oraz conf mówią o minimalnych wartościach dla Support i Confidence – tutaj dobrane są tak, aby załapały się wszystkie koszyki. Ale przy ogromnych wolumenach danych trzeba je dobrać rozsądnie.

Spójrzmy na wszystkie reguły jakie zostały wyliczone:

Jeśli kolumna lhs jest pusta oznacza to, że w koszyku był tylko jeden produkt. I to się zgadza z tym co policzyliśmy wcześniej (teraz przyda się przypomnienie sobie obrazka) – 3/10 razy masło, 4/10 razy jabłka itd. Można ograniczyć tworzenie reguł do określonej minimalnej liczby produktów – służy do tego parametr minlen.

Ale już jeśli ktoś kupił masło to mógł mieć w koszyku też jabłka (reguła numer 5). Support = 0.1, czyli 1 z 10 transakcji tak właśnie wyglądała. Rzut oka na obrazek i widzimy, że to transakcja numer 5. Oczywiście działa to tak samo w drugą stronę – mając jabłka kupiliśmy masło (reguła 6). Nie znamy kolejności wkładania produktów do koszyka, zatem reguły są symetryczne.

Zobaczmy na Confidence. Wiersz 10 – Chleb => Masło. 2/10 (wartość support) takich transakcji, zgadza się. W koszyku chleb pojawił się 6 razy, z czego razem z masłem 2 razy (transakcje 2 oraz 6). Czyli w 1/3 = 0.333 przypadków. Można więc powiedzieć, że kupienie chleba w 33% oznacza również kupno masła (“33% klientów którzy kupili chleb, kupili też masło”).

Dla sytuacji odwrotnej (Masło => Chleb) mamy confidence na poziomie 66% (3 koszyki z masłem, w tym 2 też z chlebem).

Ostatni współczynnik to Lift.

Interesują nas reguły z lift > 1. Największą wartość lift ma reguła 17 (Masło, Mleko => Chleb). Widzimy, że jeśli ktoś kupił jednocześnie masło i mleko kupił też chleb. Spójrzmy na regułę 19 (Chleb, Mleko => Masło). Chleb i mleko mamy w trzech koszykach (2, 7 i 10), z czego dodatkowo masło jest w koszyku 2 (stąd confidence dla reguły 19 równe 1/3). Lift jest większy od jedynki, co oznacza że prawdopodobne jest kupienie masła jeśli już w koszyku mamy chleb i mleko. Im większy lift tym większe to prawdopodobieństwo.

Pakiet arules pozwala na policzenie jeszcze kilkunastu innych miar – polecam dokumentację dla funkcji interestMeasure().

Uzbrojeni w taką wiedzę możemy przejść do realnego świata. Nie mamy co prawda danych o zakupach (och, jakże bym chciał mieć taki wyciąg z kasy fiskalnej jakiegoś sklepu! Kto podeśle?), ale jest coś analogicznego. Bo transakcjami może być wszystko. W pakiecie arules mamy zaszyte dane Income (transakcje) oraz IncomeESL (to samo w formie data frame). Są to dane demograficzne z 8993 ankiet zawierające 14 cech.

My jednak zajmiemy się czymś innym.

Praktyka – przepisy kulinarne

Każdy przepis składa się z produktów. Zatem jest swego rodzaju koszykiem z zakupami, prawda? Bingo! To znajdźmy sobie dużo przepisów, tak żeby zobaczyć jakie produkty są potrzebne…

Pobranie danych

Skąd wziąć dane? Jest sobie serwis Ugotuj.to (który kiedyś, w jakiejś wcześniejszej wersji projektowałem jako UX desinger) zawierający masę przepisów z jedną bardzo fajną charakterystyczną cechą. W większości przepisów wśród składników znajdziemy linki do strony ze składnikiem podstawowym (za moich czasów w Ugotuju tego nie było). Dzięki temu pozbędziemy się problemu odmiany słów (jedna cebula, dwie cebule, pół cebuli, dwa ziemniaki, jeden ziemniak, pół kilo ziemniaków) – wykorzystamy link do strony składników podstawowych.

Jeśli nie interesują Cię technikalia pobierania danych i całe to programowanie – możesz przeskoczyć do analizy przepisów klikając tutaj

Lista wszystkich przepisów

Najpierw potrzebujemy listy wszystkich przepisów. Tak się znakomicie składa, że Ugotuj.to posiada taką listę! Na kolejnych stronach są kolejne linki do przepisów. Trzeba tylko przejechać przez wszystkie strony i wyciągnąć URLe do pełnych przepisów.

Żeby oszczędzić serwery Ugotuj.to oraz nie narazić się na szybkie zbanowanie wykorzystamy sztuczkę, którą znalazłem na blogu Maëlle (w ślad za rud.is). Cały poniższy kod można zapisać w jednym skrypcie i uruchomić tylko raz.

Wynik to lista adresów URL do przepisów zapisana w pliku ugotuj_urls_all.rds

Treści przepisów

Skorzystamy teraz z tego pliku i pobierzemy kolejne przepisy. Znowu – skrypt uruchamiamy raz, a w wyniku mamy pliki z danymi

Na początek musimy wyczyścić sobie nieco dane (można było to zrobić na etapie ich zbierania). Mamy zapisane pliki, możemy więc zacząć od zera:

Teraz możemy zacząć zabawę!

Analiza przepisów

Zaczniemy standardowo, bez wykorzystania narzędzi do analizy koszyków.

Najpopularniejsze składniki w przepisach

Jakie są najpopularniejsze składniki przepisów? Zobaczmy top 30:

Pieprz jak najbardziej. Soli nie ma, bo nie jest wyróżniona w przepisach jako składnik podstawowy. Szkoda, bo nie dowiemy się tym sposobem czy jest bardziej popularna niż pieprz…

Wysoka pozycja masła nie powinna dziwić: używa się go do smażenia (stąd też wysoko olej) ale też do pieczenia. Podobniej jest z cebulą i czosnkiem: występują właściwie w każdej potrawie obiadowej (ale jak widać tylko w około 3.5% przepisów).

Co się z czym łączy?

Jakie kombinacje składników (dwóch) są najpopularniejsze? Pieprz i sól? Wiemy, że o soli się nie dowiemy…

Najpierw przygotujemy odpowiednią tabelę (układ danych wymaga nieco gimnastyki):

Dla czytelności wykresu weźmy tylko najpopularniejszy 1% połączeń:

Jak widać pieprz łączy się właściwie ze wszystkim.

Najpopularniejsze połączenia to pieprz z cebulą lub czosnkiem, nieco mniej z masłem lub olejem. Dominują kombinacje pieprz, cebula, czosnek, masło, olej. Do tego dochodzą pomidory, które lubią pieprz, cebulę i czosnek.

Inna kategoria ciekawych i popularnych połączeń to kombinacje jajka, mąki, masła i cukru. Kto wie co to? Myślimy, myślimy i wiemy.

Trzydzieści najpopularniejszych połączeń to:

prod_a prod_b n
cebula pieprz 6650
czosnek pieprz 6234
masło pieprz 4862
cukier masło 4444
jajko masło 4112
cebula czosnek 3942
masło mąka 3622
cukier jajko 3360
olej pieprz 3302
jajko pieprz 3254
jajko mąka 3056
pieprz pomidory 2750
cukier mąka 2716
cebula masło 2592
masło mleko 2456
cukier mleko 2362
pieprz śmietana 2328
masło śmietana 2232
mąka pieprz 2210
cebula olej 2206
cukier pieprz 2200
papryka pieprz 2178
czosnek pomidory 2168
cebula pomidory 2050
jajko mleko 2046
czosnek masło 1960
cukier woda 1886
czosnek olej 1886
cebula jajko 1810
pieprz ziemniaki 1806

Kiedy popatrzymy na wszystkie połączenia:

Widzimy, że są pewne produkty, które łączą się z prawie wszystkim, ale są też białe plamy.

Mając tak przygotowane dane możemy zobaczyć

z czym najczęściej występuje dany produkt

Weźmy jakiś zakręcony, na przykład ciecierzycę:

Na początku oczywiście pieprz, czosnek i cebula – te występują prawie zawsze. Ale jak widać ciecierzyca doprawiana jest kolendrą albo cynamonem.

Powyższy fragment kodu jest na tyle uniwersalny, że bardzo łatwo możecie znaleźć najdziwniejsze produkty, wystarczy odpowiednio ustawić wartość zmiennej produkt_a.

Co można ugotować mając…

Zobaczmy co możemy ugotować mając jakieś produkty w lodówce. Odwieczny problem studenta: co zrobić z parówek, żółtego sera i jednego jajka?

Dla całej listy posiadanych składników przejdziemy po kolei listę przepisów zawężając ją do przepisów zawierających dany składnik. Później tę zawężoną listę przechodzimy ponownie i zawężamy dla drugiego składnika. I tak składnik po składniku.

Dla przykładu poszukajmy przepisów, w których występuje limonka, makaron ryżowy oraz sos rybny:

Sprawdźmy co nam zostało?

link n_skladnikow
Schab w sezamie po wietnamsku 6
Spring rolls z bratkiem 7
Pad thai 8
Smażony makaron z wieprzowiną 11
Makaron z cury i mleczkiem kokosowym 11
Azjatycka sałatka makaronowa z krewetkami 11
Rożki z mango i krewetkami 12
Makaron ryżowy z polędwicą wieprzową 12
Makaron ryżowy z kurczakiem i brokułami 12
Makaron z wieprzowiną i warzywami 12
Rożki z mango i krewetkami 12
Makaron z krewetkami 12
Spring rolls z mango i krewetkami 12
Zupa wietnamska z wieprzowiną 12
Zupa Pho 13
Błyskawiczna zupa tajska w 7 minut 13
Rosół wołowy po wietnamsku 14
Sajgonki 15
Zupa wiosenna po wietnamsku 15
Zupa wiosenna 15
Pad Thai 15
Pad thai 16
Zupa kokosowa z makaronem ryżowym 17
Wiosenna zupa z makaronem 18

Graf

Widzieliśmy, że wśród par składników pojawiają się produkty związane ze smażeniem większości mięs (olej, masło, pieprz, cebula i czosnek) oraz coś co może sugerować wypieki (masło, jajka, mąka, cukier). Spróbujmy używając grafów skategoryzować produkty na grupy.

Widać, że jeden kolor to produkty okołoowocowe (lewa strona grafu), inny – mięso (prawa strona), w środku mamy przyprawy, olej, masło i mąkę.

Analiza koszykowa przepisów kulinarnych

Wróćmy do tematu przewodniego dzisiejszego postu – czy podobne informacje uda się uzyskać za pomocą analizy koszykowej?

Na początek potrzebujemy listy transakcji (koszyków). Za koszyk uznamy składniki pojedynczego przepisu.

Mamy ponad 17 tysięcy przepisów (zgadza się – tyle było URLi do przepisów), a najpopularniejsze produkty to pieprz, masło, cukier, cebula i czosnek. Widać to też na wykresie (porównajcie go z tym wcześniejszym):

Kolejność produktów jest taka sama jak na wykresie stworzonym wcześniej.

Zobaczmy jeszcze to co robiliśmy na początku – heatmapę reguł i produków:

Mówiłem na początku, że to nieczytelne przy większych liczbach produktów i koszyków? No to teraz to widać. Widać też jedną wyróżniającą się linię poziomą, która biegnie przez prawie wszystkie koszyki. To oczywiście najpopularniejszy pieprz.

Przygotujmy zestaw reguł:

Mamy 378 reguł, zobaczmy po 20 z największymi kolejnymi wskaźnikami:

Największy support

Widzimy najpopularniejsze połączenia, które znamy już z wcześniejszej analizy.

Największy confidence

W 84% przepisów z mąką i żółtkiem potrzebne jest też masło. W 81% przypadków gdzie jest jajko, masło i proszek do pieczenia potrzebny jest cukier. To ciasta i desery. Swoją drogą co to za przepisy te pozostałe 9%?

Największy lift

Widzimy, że jeśli w przepisie jest cukier, jajko i masło to bardzo prawdopodobne jest, że potrzebny będzie też proszek do pieczenia. Ma to sens.

Wśród reguł możemy sobie czegoś poszukać, na przykład:

tam gdzie potrzebne jest jajko potrzebne są też…

do których składników potrzebny też cukier?

Te dwa przykłady można przełożyć na normalne zakupy. Z czym kupowane są chipsy? A jak ktoś kupuje pieluchy to co kupuje jeszcze? W powyższych przykładach dane posortowane zostały po kolumnie support, ale na przykład planując promocje można oprzeć się na lift. O co chodzi?

Wiedząc, że zakup piwa bardzo często powiązany jest z zakupem chipsów (powinien być wysoki lift) możemy zrobić kilka rzeczy w samym sklepie:

  • postawić obok siebie te produkty – bo jak już wziąłem piwo z półki to wezmę chipsy – tym bardziej, że są pod ręką
  • przygotować ofertę kupując 6 piw chipsy gratis – bo często produkty te są kupowane razem, więc podnosząc ceną piwa możemy zrekompensować “stratę” na gratisowych chipsach. Oczywiście jak nie skorzystasz z promocji to owe 6 piw kosztuje nieco więcej

Dodatkowo mając pełne listy koszyków na przykład z całego tygodnia, godzina po godzinie, możemy przeanalizować schematy zakupów. Czy wieczorem kupowane jest coś innego? I czy w innych kombinacjach? Dodając dane o pogodzie wiemy jeszcze więcej. A dodając informacje o kliencie (na podstawie numeru karty kredytowej czy karty lojalnościowego) możemy wręcz prognozować co i kiedy kupi. I odpowiednio kierować do niego promocje i reklamy.

6 komentarzy do “Analiza koszykowa – przepisy kulinarne”

  1. Pingback: Analiza tekstów z wiadomości | Łukasz Prokulski

  2. Mały probelm przy generowaniu połączeń

    skladniki_polaczenia %
    select(n, skladniki) %>%
    # podziel po numerze przepisu
    slice_rows(„n”) %>%
    # dla kazdej grupy macierz kazdy-kazdy
    by_slice(~ dcast(., formula = skladniki ~ skladniki,
    fun.aggregate = length, value.var = „skladniki”) %>%
    # pivot macierzy
    melt(id.vars = „skladniki”, value.name = „value”) %>%
    # bez przeciątnej macierzy (bez sam ze sobą)
    filter(skladniki != variable) %>%
    mutate(value = 1),
    .collate = „list”) %>%
    # wynikowe mikrotabelki złącz w jedną dużą
    bind_rows(.$.out) %>%
    select(skladniki, variable, value) %>%
    filter(!is.na(value))

    # posumuj pary składników
    skladniki_polaczenia %
    mutate(prod_a = ifelse(skladniki < variable, skladniki, variable),
    prod_b = ifelse(skladniki %
    select(prod_a, prod_b, n = value) %>%
    group_by(prod_a, prod_b) %>%
    summarise(n = sum(n)) %>%
    ungroup() %>%
    distinct()

    Error in skladniki_polaczenia %>% mutate(prod_a = ifelse(skladniki %”

  3. np.

    Error in trans@data %>% t() %>% data.matrix() %>% as.data.frame() %>% :
    could not find function „%>%”

Skomentuj Natalia Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *