Przedmiotem analizy są dane sprzedażowe pochodzące ze sklepu e-commerce specjalizującego się w sprzedaży odzieży, ze szczególnym naciskiem na spodnie. Zbiór danych obejmuje pełną historię transakcyjną z lat 2021–2025.
Specyfika asortymentu sklepu opiera się na dwóch kluczowych atrybutach produktowych:
Category):
Jest to główny wyróżnik asortymentu (np. Jeans, Cotton, Linen,
Shorts). W analizie zmienna ta determinuje materiał wykonania,
sezonowość i styl produktu.Color): Drugi kluczowy wymiar
decyzyjny klienta, pozwalający na identyfikację preferencji
wizualnych.Pozostałe kategorie (np. T-shirt, Other) stanowią asortyment uzupełniający.
Głównym celem projektu jest wyjście poza standardową analizę “co sprzedaje się najlepiej” i przejście do wielowymiarowej eksploracji struktury koszyka zakupowego w ujęciu długoterminowym.
Wstępna eksploracja danych ujawniła istotne wyzwanie analityczne: większość zamówień w sklepie składa się z tylko jednego produktu.
order_id) jako
identyfikator transakcji, w tym przypadku jest nieskuteczna. Algorytm
nie jest w stanie wygenerować reguł asocjacyjnych (\(A \to B\)), gdy większość transakcji
zawiera zbiór jednoelementowy \(\{A\}\).Aby rozwiązać problem rzadkości danych, w projekcie przyjęto podejście oparte na koncepcji Koszyka Cyklu Życia.
Zamiast analizować pojedyncze transakcje, dokonano agregacji historii
zakupowej na poziomie unikalnego klienta (client_id).
Dzięki temu badamy “super-transakcję”, która reprezentuje całą garderobę skompletowaną przez klienta w badanym sklepie. Pozwala to na wykrycie wzorców typu cross-selling, które mogą być odroczone w czasie.
Do ekstrakcji reguł wykorzystano algorytm Apriori, jeden z najpopularniejszych algorytmów reguł asocjacyjnych.
Jako jednostkę analityczną przyjęto parę atrybutów: \[Item\_ID = Kategoria (Materiał) + \text{"_"} + Kolor\] Przykładowo, produkt “Spodnie Jeans” w kolorze “Blue” jest traktowany jako unikalny byt “Jeans_Blue”. Taka granulacja pozwala algorytmowi Apriori wykryć precyzyjne zależności.
## Rows: 53,700
## Columns: 23
## $ order_id <dbl> 61842, 61888, 61896, 61899, 61901, 61903, 619…
## $ order_date <dttm> 2021-01-12 17:01:12, 2021-01-14 10:05:06, 20…
## $ client_id <chr> "ppakier+12@gmail.com", "skuziak1@gmail.com",…
## $ country_id <chr> "PL", "PL", "PL", "PL", "PL", "PL", "PL", "PL…
## $ currency <chr> "PLN", "PLN", "PLN", "PLN", "PLN", "PLN", "PL…
## $ order_origin <chr> "Nieznane", "Nieznane", "Nieznane", "Nieznane…
## $ coupon_code <chr> "wojciechpakker", NA, NA, "mirecki", NA, NA, …
## $ discount_amount <dbl> 278, 0, 0, 139, 0, 0, 0, 278, 0, 278, 278, 0,…
## $ total_amount_afterrefund <dbl> 12, 210, 289, 150, 210, 289, 211, 12, 290, 28…
## $ total_items <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, …
## $ sku <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ item_name <chr> "Pakker Linen - Aperol - L (175-185cm/~90kg)"…
## $ item_quantity <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ item_price <dbl> 278, 278, 278, 278, 278, 278, 278, 278, 278, …
## $ item_price_paid <dbl> 0, 199, 278, 139, 199, 278, 199, 0, 278, 139,…
## $ model <chr> "Linen I", "Linen I", "Linen I", "Cotton I", …
## $ category <chr> "Linen", "Linen", "Linen", "Cotton", "Linen",…
## $ color <chr> "Aperol", "Gold", "Black", "Black Sea", "Gold…
## $ prod_cost <dbl> 126, 126, 126, 117, 126, 117, 126, 117, 117, …
## $ size <chr> "L", "M", "XL", "L", "M", "L", "L", "L", "M",…
## $ line_revenue <dbl> 0, 199, 278, 139, 199, 278, 199, 0, 278, 139,…
## $ profit_amount <dbl> -126, 73, 152, 22, 73, 161, 73, -117, 161, 22…
## $ profit_margin <dbl> -1.00000, 36.68342, 54.67626, 15.82734, 36.68…
Surowe dane wymagają wstępnego przetworzenia, aby były czytelne dla
algorytmów asocjacyjnych. W tym kroku tworzymy zmienną
Item_ID (zgodnie z definicją we wstępie) oraz anonimizujemy
dane osobowe.
Category i Color w jeden ciąg znaków.# Tworzenie zmiennej Item_ID i anonimizacja Klienta
df_prepared <- df_raw %>%
mutate(
# Czyszczenie tekstu
Category_Clean = str_to_title(str_trim(category)),
Color_Clean = str_to_title(str_trim(color)),
# Zamieniamy maile klientów na numery
client_id = as.numeric(as.factor(client_id)),
# Tworzenie Item_ID
Item_ID = paste(Category_Clean, Color_Clean, sep = "_")
) %>%
# Wybieramy tylko kluczowe kolumny: Kto (ID) i Co (Produkt)
select(client_id, Item_ID) %>%
distinct() # Usuwamy duplikaty
# Podgląd
head(df_prepared) %>% kable(caption = "Dane po anonimizacji i transformacji")
| client_id | Item_ID |
|---|---|
| 15106 | Linen_Aperol |
| 16762 | Linen_Gold |
| 11737 | Linen_Black |
| 13853 | Cotton_Black Sea |
| 7938 | Linen_Gold |
| 18855 | Cotton_Black Sea |
W tym kroku konwertujemy przygotowaną ramkę danych
(data.frame) na obiekt transactions wymagany
przez bibliotekę arules.
Użyto parametru format = "single". W terminologii
pakietu arules odnosi się on do struktury pliku
wejściowego (dwie kolumny: ID transakcji i ID produktu), a nie
do zawartości koszyka. Jest to standardowy sposób wczytywania danych z
relacyjnych baz danych lub plików CSV w formacie długim (long
format).
## Transformacja do formatu transakcyjnego
# Zapisujemy do tymczasowego CSV
write.csv(df_prepared, "temp_trans_lifetime.csv", row.names = FALSE, quote = FALSE)
# Wczytujemy transakcje
trans_lifetime <- read.transactions(
"temp_trans_lifetime.csv",
format = "single",
sep = ",",
cols = c(1, 2),
rm.duplicates = TRUE,
skip = 1
)
# Sprzątamy
file.remove("temp_trans_lifetime.csv")
## [1] TRUE
# Podgląd transakcji
inspect(head(trans_lifetime, 5))
## items transactionID
## [1] {Corduroy_Beech} 1
## [2] {Linen_Aperol} 10
## [3] {Jeans_Black Jeans} 100
## [4] {Cotton_Gold,
## Cotton_Khaki} 1000
## [5] {Corduroy_Beech,
## Corduroy_Green Corduroy,
## Cotton_Deep Blue,
## Cotton_Gold,
## Cotton_Khaki,
## Linen_Aperol,
## Linen_Natural} 10000
Zanim przystąpimy do generowania reguł asocjacyjnych, kluczowe jest zrozumienie struktury transakcji oraz zachowań zakupowych klientów. Analiza ta pozwoli nam poprawnie dobrać parametry algorytmu Apriori (takie jak minimalny próg Support).
W pierwszej kolejności badamy podstawowe metryki zbioru: liczbę unikalnych klientów, liczbę produktów w asortymencie oraz statystyki dotyczące wielkości koszyka “Cyklu Życia Klienta” (ile unikalnych produktów średnio kupuje jeden klient w całym badanym okresie).
| Metryka | Wartość |
|---|---|
| Liczba Unikalnych Klientów | 19730.00 |
| Liczba UnikalnychProduktów | 62.00 |
| Minimum wielkości koszyka | 1.00 |
| Średnia wielkość koszyka | 2.39 |
| Mediana wielkości koszyka | 2.00 |
| Maksimum wielkości koszyka | 31.00 |
Analiza Rozkładu Wielkości Koszyka:
Powyższe histogramy ujawniają fundamentalną różnicę w strukturze danych, która uzasadnia przyjętą metodykę:
Wielkość koszyka Lifetime), rozkład
przesuwa się w prawo. Widoczna jest grupa lojalnych klientów
posiadających 2, 3, a nawet 10+ produktów.
Analiza Popularności Produktów:
W tej części projektu przechodzimy do właściwej analizy Data Mining. Proces ten składa się z trzech kluczowych etapów:
Zgodnie z przyjętą metodyką, z analizy wyłączono klientów, którzy w całym cyklu życia zakupili tylko jeden unikalny wariant produktu.
## Liczba klientów przed filtracją: 19730
## Liczba klientów po filtracji: 10289
## items transactionID
## [1] {Cotton_Gold,
## Cotton_Khaki} 1000
## [2] {Corduroy_Beech,
## Corduroy_Green Corduroy,
## Cotton_Deep Blue,
## Cotton_Gold,
## Cotton_Khaki,
## Linen_Aperol,
## Linen_Natural} 10000
## [3] {Linen_Black,
## Linen_Natural} 10002
Efekt: Zbiór zredukowano z 19 730 do 10 289 klientów (redukcja o ok. 48%). Pozostawiona grupa ponad 10 tysięcy klientów stanowi solidną bazę do wnioskowania o wzorcach cross-sellingu.
Algorytm uruchomiono z następującymi parametrami, dobranymi pod kątem specyfiki “długiego ogona”:
supp = 0.005) pozwala wykryć zależności nawet dla
produktów niszowych.conf = 0.2) eliminuje najsłabsze powiązania.# Generowanie reguł
rules_raw <- apriori(trans_filtered,
parameter = list(supp = 0.005,
conf = 0.2,
minlen = 2))
## Apriori
##
## Parameter specification:
## confidence minval smax arem aval originalSupport maxtime support minlen
## 0.2 0.1 1 none FALSE TRUE 5 0.005 2
## maxlen target ext
## 10 rules TRUE
##
## Algorithmic control:
## filter tree heap memopt load sort verbose
## 0.1 TRUE TRUE FALSE TRUE 2 TRUE
##
## Absolute minimum support count: 51
##
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[62 item(s), 10289 transaction(s)] done [0.00s].
## sorting and recoding items ... [57 item(s)] done [0.00s].
## creating transaction tree ... done [0.00s].
## checking subsets of size 1 2 3 4 5 done [0.01s].
## writing ... [1283 rule(s)] done [0.00s].
## creating S4 object ... done [0.00s].
cat("Liczba surowych reguł:", length(rules_raw), "\n")
## Liczba surowych reguł: 1283
Wynik: Wygenerowano 1283 surowe reguły. Zbiór ten zawiera jednak wiele duplikatów i reguł przypadkowych, które należy usunąć w kolejnym kroku.
Aby wyłonić najbardziej wartościowe asocjacje, zastosowano kaskadowy system filtrów:
is.maximal):
Pozostawienie tylko najszerszych zestawów produktów (np. zachowanie
{A, B} -> {C} i usunięcie {A} -> {C}
jako podzbioru).# Usuwanie nadmiarowych reguł
rules_sorted <- sort(rules_raw, by = "lift", decreasing = TRUE)
rules_clean <- rules_sorted[!is.redundant(rules_sorted)]
# Usuwanie reguł nieistotnych
rules_clean <- rules_clean[is.significant(rules_clean, trans_filtered)]
# Zostawiamy tylko reguły maksymalne
rules_clean <- rules_clean[is.maximal(rules_clean)]
cat("Liczba reguł po selekcji:", length(rules_clean), "\n")
## Liczba reguł po selekcji: 737
Diagnostyka Jakości i Dobór Progu Odcięcia confidence:
Przeprowadzono analizę wizualną relacji między trzema kluczowymi metrykami, aby zweryfikować jakość reguł i ustalić optymalny próg ufności:
Decyzja Metodologiczna: Na podstawie wizualnej inspekcji punktów na wykresach, przyjęto próg Confidence > 0.4. Pozwala to na skuteczną eliminację szumu informacyjnego (słabych powiązań), zachowując jednocześnie reguły o wysokim potencjale rekomendacyjnym, również te z obszaru niskiego wsparcia.
Poniżej przedstawiono zestawienie 10 reguł o najwyższym wskaźniku Lift.
## Liczba reguł po korekcie: 204
## lhs rhs support confidence coverage lift count
## [1] {Shorts_Green} => {Shorts_Blue Jeans} 0.006317426 0.4609929 0.013703956 7.111178 65
## [2] {Jeans_Black Jeans,
## Ripstop_Olive} => {Ripstop_Black} 0.005248323 0.4426230 0.011857323 4.296366 54
## [3] {Jeans Ultralight_Blue Jeans,
## Jeans_Black Jeans,
## Shorts_Blue Jeans} => {Jeans_Blue Jeans} 0.007483720 0.7403846 0.010107882 3.808909 77
## [4] {Cotton_Deep Blue,
## Jeans_Black Jeans,
## Shorts_Blue Jeans} => {Jeans_Blue Jeans} 0.005248323 0.6923077 0.007580912 3.561577 54
## [5] {Jeans Ultralight_Blue Jeans,
## Jeans_Black Jeans,
## Ripstop_Black} => {Jeans_Blue Jeans} 0.005345515 0.6790123 0.007872485 3.493179 55
## [6] {Jeans_Black Jeans,
## Jeans_Blue Jeans,
## Shorts_Blue Jeans} => {Jeans Ultralight_Blue Jeans} 0.007483720 0.6363636 0.011760132 3.455169 77
## [7] {Hemp_Natural,
## Jeans Ultralight_Blue Jeans,
## Linen_Aperol} => {Jeans_Blue Jeans} 0.005151132 0.6708861 0.007678103 3.451373 53
## [8] {Cotton_Aperol,
## Jeans Ultralight_Blue Jeans,
## Jeans_Black Jeans} => {Jeans_Blue Jeans} 0.005345515 0.6626506 0.008066868 3.409006 55
## [9] {Jeans Ultralight_Blue Jeans,
## Jeans_Black Jeans,
## Linen_Black} => {Jeans_Blue Jeans} 0.007094956 0.6517857 0.010885412 3.353112 73
## [10] {Jeans Ultralight_Blue Jeans,
## Jeans_Black Jeans,
## Linen_Aperol} => {Jeans_Blue Jeans} 0.006609000 0.6476190 0.010205073 3.331676 68
Szczegółowa Analiza Top 10 Reguł (Ranking wg Lift):
Tabela ukazuje dwa kluczowe zjawiska w zachowaniach klientów:
Jeans_Blue Jeans jako produkt wynikowy (RHS). Niezależnie
od tego, czy klient kupił wcześniej produkty lniane
(Linen), konopne (Hemp) czy techniczne
(Ripstop), jego ścieżka zakupowa niemal zawsze prowadzi do
klasycznych niebieskich jeansów.
{Shorts_Green} => {Shorts_Blue Jeans}) dotyczy
produktów z tej samej kategorii.
{Jeans Ultralight_Blue, Jeans_Black, Shorts_Blue} =>
{Jeans_Blue}. Osiąga ona Confidence na poziomie
74%. Oznacza to, że jeśli zidentyfikujemy klienta posiadającego
te trzy produkty, mamy niemal 3/4 szans, że dokupi on klasyczne Blue
Jeans.Aby umożliwić swobodną eksplorację wszystkich 204 reguł, udostępniono poniższe narzędzie interaktywne.
# Wyświetlamy
inspectDT(rules_clean)
Ostatnim etapem jest analiza struktury powiązań w formie grafu. Pozwala ona zidentyfikować, które produkty pełnią rolę “Hubów” (centrów), a które są jedynie dodatkami.
set.seed(123)
# Wybieramy top 100 reguł do grafu
top_viz <- head(rules_clean, 100)
plot(top_viz,
method = "graph",
engine = "htmlwidget")
Interpretacja Wizualna vs. Analityczna:
Powyższy graf pozwala na szybką ocenę ogólnej topologii sieci – wyraźnie widać centralne skupiska (“węzły”) oraz peryferyjne gałęzie. Jednak przy dużej liczbie reguł precyzyjne odczytanie kierunku powiązań (co jest przyczyną, a co skutkiem) staje się utrudnione.
Aby uzupełnić ten obraz i nadać mu wymiar ilościowy, w kolejnym kroku przeprowadzono dekompozycję reguł. Obliczono, jak często dany wariant produktu występuje w roli Inicjatora (LHS – Left Hand Side) oraz jak często jest Celem (RHS – Right Hand Side). Pozwoli to precyzyjnie określić funkcję każdego produktu w ekosystemie sprzedażowym.
Poniższa tabela przedstawia warianty produktów najczęściej pojawiające się w regułach. Należy jednak pamiętać, że w modelu transakcje zostały zagregowane, co uniemożliwia analizę wymiaru czasowego i faktycznej kolejności zamówień.
| Item_ID | LHS_Count | RHS_Count | Total_Count |
|---|---|---|---|
| Jeans_Blue Jeans | 72 | 69 | 141 |
| Jeans_Black Jeans | 68 | 52 | 120 |
| Jeans Ultralight_Blue Jeans | 47 | 37 | 84 |
| Cotton_Deep Blue | 30 | 16 | 46 |
| Cotton_Black Sea | 28 | 14 | 42 |
| Shorts_Blue Jeans | 30 | 1 | 31 |
| Linen_Aperol | 21 | 6 | 27 |
| Cotton_Khaki | 22 | 0 | 22 |
| Hemp_Natural | 20 | 0 | 20 |
| Cotton_Sand | 15 | 0 | 15 |
| Other_- | 14 | 0 | 14 |
| Cotton Light_Blue | 12 | 0 | 12 |
| Cotton_Gold | 5 | 7 | 12 |
| Linen_Black | 11 | 1 | 12 |
| Ripstop_Black | 10 | 1 | 11 |
| Cotton Light_Aperol | 10 | 0 | 10 |
| Cotton_Aperol | 10 | 0 | 10 |
| Shirt_Black | 8 | 0 | 8 |
| Cotton_Green | 7 | 0 | 7 |
| Cotton_Red | 6 | 0 | 6 |
Aby precyzyjnie określić, od czego klienci faktycznie zaczynają swoją przygodę z marką, konieczny jest powrót do surowych danych transakcyjnych. Poniższa analiza wyodrębnia “Produkty Wejściowe” – czyli te warianty, które znalazły się w pierwszym zamówieniu klientów powracających.
Zestawienie wyników z trzech przeprowadzonych analiz – topologii sieci (Graf), ról w regułach (LHS vs RHS) oraz sekwencji czasowej – pozwala na precyzyjną segmentację asortymentu według funkcji pełnionej w cyklu życia klienta.
Produkty takie jak Jeans_Blue Jeans, Jeans_Black Jeans, Cotton_Deep Blue oraz Jeans Ultralight_Blue Jeans dominują w statystykach, pojawiając się w największej ilości reguł.
Ciekawym zjawiskiem są produkty z wysoką liczbą wystąpień po lewej stronie (LHS), a zerową lub niską po prawej. Przykłady: Cotton_Khaki (22 LHS / 0 RHS), Linen_Aperol (21 LHS / 6 RHS) czy Shorts_Blue Jeans (30 LHS / 1 RHS).
Mniej popularne kategorie produktów jak Linen, Hemp lub Cotton Light są słabo ze sobą powiązane.
Analiza reguł ujawnia, że klienci rzadko zachowują się chaotycznie – ich decyzje podlegają ścisłym regułom stylistycznym:
Przeprowadzona analiza asocjacji zakupowych w ujęciu cyklu życia klienta pozwoliła na zidentyfikowanie ukrytych wzorców zachowań, które byłyby niewidoczne przy klasycznym podejściu transakcyjnym (pojedynczych zamówień).
Kluczowe obserwacje wynikające z badania:
Skuteczność Metodologii Koszyka Cyklu Życia Klienta: Agregacja historii zakupowej na poziomie klienta okazała się kluczowa dla rozwiązania problemu rzadkości danych. Pozwoliło to na wykrycie relacji odroczonych w czasie, budując pełniejszy obraz preferencji stylistycznych.
Struktura Sieci Asortymentowej (Huby vs. Inicjatory): Analiza ról produktów (LHS vs RHS) ujawniła wyraźną hierarchię w asortymencie sklepu:
Segmentacja Preferencji (Kolor i Funkcja): Zidentyfikowano silne klastry behawioralne:
Rola Produktów Niszowych: Kategorie uzupełniające (np Linen, Hemp) charakteryzują się słabymi powiązaniami wewnętrznymi. Produkty te są często uzupełnieniem garderoby, są to “eksperymenty” zakupowe.
Konkluzja: Badanie potwierdziło, że asortyment sklepu nie jest zbiorem niezależnych produktów, lecz tworzy skomplikowaną sieć powiązań. Zrozumienie tej dynamiki pozwala na precyzyjne określenie roli każdego produktu w budowaniu długookresowej wartości klienta.