Segmentacja wcale nie musi być skomplikowana. Najważniejszy jest jej efekt i korzyści jakie przynosi dla firmy. W tym projekcie chciałbym pokazać jak szybko, łatwo i przyjemnie wykonać segmentacje behawioralną z użyciem relatywnie prostych metod. Zapraszam! 🙂
- Wstęp
- Cel projektu
- Założenia dotyczące projektu
- Opis zbioru danych
- Wczytanie danych
- Przygotowanie danych do grupowania
- Grupowanie danych
- Budowa modelu
- Opis uzyskanych wyników
- Podsumowanie
1. Wstęp¶
Niniejszy projekt koncentruje się na segmentacji behawioralnel. Użyta zostanie metoda RFM, z pomocą której klienci zostaną podzieleni na grupy.
Prócz aspektów technicznych pokażę, w jaki sposób można przekuć wyniki analizy na akcje marketingowe mające na celu zwiększenie sprzedaży.
2. Cel projektu¶
- Głównym celem projektu jest przeprowadzenie grupowania klientów sklepu online.
- Celem szczegółowym jest wykonanie analizy zbioru, grupowania (w rozumieniu SQL) transakcji i wyciąganie na ich podstawie wniosków.
3. Założenia dotyczące projektu¶
- Postawię się w roli osoby przeprowadzającej projekt grupowania klientów dla klienta (dużego sklepu online sprzedającego unikalne prezenty, którego klientami są głównie hurtownicy).
- Zakładam przeprowadzenie grupowania klientów z użyciem metody RFM (requency, frequency, monetary value).
- Będę oceniać klientów kryteriami, jakie zakłada metoda RFM. Najbardziej wartościowi kliencie, to ci, którzy:
- Requency - w ostatnim czasie byli zainteresowani produktami, które oferuje nasz sklep,
- Frequency - często wykonują zakupy w naszym sklepie.
- Monetary value - wydają na zakupy w naszym sklepie relatywnie duże pieniądze.
Każdorazowo punktem odniesienie dla każdego klienta będą inni klienci naszego sklepu. Zakładam ocenę danego czynnika względem pozostałych klientów, dzieląc klientów z użyciem metody kwantyli.
- Nie oceniam skuteczności grupowania RFM. Jedynie przedstawiam sposób jej przeprowadzenia dla przykładowego zbioru danych i wybranego przypadku.
- Jako że mowa o grupowaniu, to ciężko jest ocenić jej wpływ na sprzedaż bez przeprowadzenia przykładowej kampanii marketingowej na wybranym segmencie z RFM i grupie kontrolnej, a następnie wykonaniu np. testów A/B dla różnicy w średniej, mając na uwadze zysk z różnych grup.
- W skrócie: przeprowadzam proces grupowania klientów sklepu online, z użyciem prostej i intuicyjnej metody RFM, bez oceny i badania jej jakości.
4. Opis zbioru danych¶
Główne informacje o zbiorze:
- Link do zbioru
- Autor: Dr Daqing Chen.
- Dodano w 2015.11.06.
- 8 zmiennych, 541 909 obserwacji (przed agregacją).
Zbiór zawiera dane dotyczące transakcji w jednej z Brytyjskich firm sprzedających drobne pomysły na prezent i upominki. Działa ona tylko i wyłącznie online, nie posiadając sklepu stacjonarnego (co traktuję jako plus, gdyż wszystkie transakcje pochodzą z tego samego kanału sprzedaży). Wiele klientów firmy to hurtownicy.
Wszystkie transakcje znajdujące się w pliku zostały wykonane pomiędzy 01/12/2010 i 09/12/2011.
5. Wczytanie danych i niezbędnych bibliotek¶
Cały projekt wykonuję w Pythonie, korzystając z Jupyter-a. Pierwszym krokiem jest więc zatem wczytanie niezbędnych bibliotek.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.stats
import statsmodels.formula.api as sm
from sklearn.cluster import KMeans
from mpl_toolkits.mplot3d import Axes3D
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import auc, roc_curve
Ustawiam formatowanie zmiennych ciągłych w Pandas z precyzją do trzech miejsc po przecinku.
pd.set_option('float_format', '{:.3f}'.format)
Wczytuję teraz zbiór z pliku csv. To, co robię dodatkowo przy wczytywaniu, to parametrami wskazuję Pandas-owi:
- brak nagłówka w zbiorze,
- nazwy poszczególnych zmiennych,
- typy niektórych zmiennych,
- znak za pomocą którego oznaczone są braki w danych.
ef = pd.ExcelFile('data/Online Retail.xlsx')
print(ef.sheet_names)
df = ef.parse('Online Retail')
Mając wczytany zbiór, sprawdzam jego rozmiar i podglądam, jak wyglądają poszczególne obserwacje, by sprawdzić, czy wszystko wczytało się prawidłowo.
print(str(df.shape[0]) + ' wierszy.')
print(str(df.shape[1]) + ' kolumn.')
df.head()
Wszystko wygląda dobrze. Przechodzę zatem dalej.Sprawdzam typy poszczególnych zmiennych zmiennych.
df.dtypes
Z opisu danych, który jest zawarty na stronie UCI wynika, że wszystkie faktury o identyfikatorze zaczynającym się od litery "c" zostały anulowane - "InvoiceNo" nie mógł zatem zostać potraktowany przez Pandas jako liczba całkowita - typ zmiennej "object" jest tu jak najbardziej na miejscu. Docelowo interesować nas będą jedynie faktury, które nie zostały anulowane. Czemu? Otóż w założeniach metody RFM widnieje monetary value, a ciężko mówić o wartości w przypadku anulowanych faktur (btw. ciekaw jestem, jak dobry klasyfikator udałoby się zbudować do przewidywania czy faktura zostanie anulowana). 🙂
Co więcej, mając na uwadze cel, jaki przyświeca większości firm - czysto zarobkowy - bardziej wartościowi są klienci zamawiający i płacący, niż zamawiający i anulujący. 😉 W rozszerzonej wersji grupowania, można by się pokusić o wychwycenie grup klientów najczęściej anulujących, a rzadko płacących.
6. Przygotowanie danych do grupowania¶
Buduję prosty data frame z podstawowymi informacjami o zbiorze.
summary = pd.DataFrame(df.dtypes, columns=['Dtype'])
summary['Nulls'] = pd.DataFrame(df.isnull().any())
summary['Sum_of_nulls'] = pd.DataFrame(df.isnull().sum())
summary['Per_of_nulls'] = round((df.apply(pd.isnull).mean()*100),2)
summary.Dtype = summary.Dtype.astype(str)
print(summary)
Sprawdzam braki danych.
print(str(round(df.isnull().any(axis=1).sum()/df.shape[0]*100,2))+'% obserwacji zawiera braki w danych.')
Pierwsze wnioski wyglądają następująco:
- Istnieją klienci bez identyfikatora.
- Typ zmiennej "CustomerID" budzi pewne wątpliwości, ale prawdopodobnie stoją za tym brakujące wartości (int nie przyjmuje pustych wartości i wtedy Pandas automatycznie traktuje je jako float).
- A więc jedynie dwie zmienne zawierają braki. Jedna to już wspominana "CustomerID", a druga to "Description". Z biznesowego punktu widzenia braki w zmiennej "Description" są uzasadnione - nie wszystkie produkty muszą mieć opis.
- Aż 135080 spośród 541909 obserwacji nie ma podanej wartości zmiennej "CustomerID". To niemal 25%. Sporo.
Z opisu zmiennej na UCI można przeczytać, że: "Customer number. Nominal, a 5-digit integral number uniquely assigned to each customer." Skąd zatem wynikają braki? Czy byli to po prostu klienci detaliczni, którym nie był nadawany identyfikator? Nie znalazłem na to pytanie odpowiedzi. Mam zatem dwie możliwości:
- Potraktować klientów bez identyfikatora jako jednego klienta "detalicznego".
- Usunąć wszystkie transakcje bez identyfikatora klienta (pozostanie nieco ponad 400 tys. transakcji).
Nie muszę Cię chyba przekonywać, że mając na uwadze problem, z którym się mierzymy w tym przypadku i potencjalne oczekiwania biznesowe, zdecydowanie rozsądniejsza jest opcja druga. Opcja pierwsza zachwiałaby i nieco wypaczyła proces grupowania. Powstałby jeden klient odpowiadający za 25% wszystkich transakcji. Pozostali nawet najwięksi klienci wyglądaliby przy nim jak szum.
Poza tym grupowanie ma na celu podział zbioru na grupy będące wsadem do dalszych działań, np. marketingowych. Jakie działania marketingowe można przeprowadzić dla klientów, których nie jesteśmy w stanie zidentyfikować? 🙂
Usuwam brakujące wartości w zmiennej "CustomerID".
df = df[~df.CustomerID.isnull()]
print('Pozostało ' + str(df.shape[0]) + ' obserwacji.')
Usunięcie anulowanych faktur.
df = df[df.InvoiceNo.astype('str').str.isdigit()]
print('Pozostało ' + str(df.shape[0]) + ' obserwacji.')
df.head()
df['CustomerID'] = df.CustomerID.astype(int)
df['InvoiceNo'] = df.InvoiceNo.astype(int)
6.1. Usunięcie zbędnych zmiennych¶
Usuwam zbędne zmienne, z których już nie będę korzystać.
df.drop(['Description', 'StockCode', 'Country'], axis = 1, inplace = True)
6.2. Usunięcie starych operacji¶
W grupowaniu metodą RFM zazwyczaj przyjmuje się, że brane pod uwagę są jedynie operacje z ostatnich 12 miesięcy. Przyczyna takiego zabiegu jest prosta: starsze operacje mogą niewiele mówić o obecnej sytuacji sprzedającego, kupującego i o samym produkcie. Oczywiście mowa o przypadku ogólnym. Są branże, w których relacja z klientem i produkt ma zdecydowanie dłuższy cykl życia. Okres analizy należy zatem dobrać stosownie do wymagań i charakterystyki rozpatrywanego przypadku.
W analizowanym przykładzie "przeterminowanych" operacji nie ma wiele. By je wyznaczyć, muszę przyjąć umowną datę przeprowadzania grupowania. Dane, na których operuję, są dosyć wiekowe, dlatego za "dziś" przyjmę dzień do najnowszej operacji w zbiorze.
df.shape
today = df.InvoiceDate.max() + pd.to_timedelta(1, "D")
today
df = df[df.InvoiceDate >= today - pd.to_timedelta(12, "M")]
7. Grupowanie danych¶
Ponownie sprawdzam rozmiar zbioru.
print(df.shape)
7.1. Agregacja danych¶
Dodaję nową zmienną: "MonetaryValue".
df['MonetaryValue'] = df.Quantity * df.UnitPrice
df.MonetaryValue.describe()
W tym miejscu jestem ciągle na poziomie pojedynczego produktu. By wykonać grupowanie RFM muszę przejść najpierw do agregacji na poziomie faktury, a później do poziomu klienta. Całość mógłbym wykonać w jednym kroku, lecz konieczne jest dodanie jednej dodatkowej zmiennej do zbioru: liczby dni od "dziś" (zmienna "today", która jest niezbędna do policzenia "Recency").
Kolejna zmienna - "Recency", czyli informacja o tym, jak dawno klient robił zakupy w sklepie.
df['Recency'] = (today - df.InvoiceDate)/np.timedelta64(1,'D')
Ok, resztę ("F" jak Frequency, oraz agregowanaie danych do poziomu klienta) wykonam za pomocą metody groupby. 🙂
abt = df.groupby(['CustomerID']).agg({'Recency':'min', 'MonetaryValue':'sum', 'InvoiceNo':'count'})
Jeszcze szybka zamiana nazw zmiennych...
abt.rename(columns = {'InvoiceNo':'Frequency'}, inplace = True)
abt = abt[['Recency', 'Frequency', 'MonetaryValue']]
abt.head()
7.2. Transformacja zmiennych¶
Kolejnym krokiem jest wykonanie transformacji wszystkich trzech zmiennych. Metoda RFM wymaga na tym etapie zmiennych porządkowych. Można to wykonać na kilka sposobów, m.in. na podstawie:
- percentyli rozkładu danej zmiennej,
- wiedzy ekspercką.
Ja decyduję się na pierwszą opcję. 🙂
Etykiety będą mieć wartości z 1-4. W tym momencie należy zwrócić uwagę na biznesowe znaczenie poszczególnych zmiennych. Nadając etykiety wartościom zmiennych, trzeba pamiętać, że wyższa etykieta powinna oznaczać lepszą sytuację danej osoby/firmy z punktu widzenia sprzedającego. W związku z tym w przypadku "Recency" - im mniejsza wartość zmiennej, tym wyższa etykieta (preferujemy klientów, którzy są aktywni). Odwrotnie wygląda sytuacja w przypadku "Frequency" - im większa wartość zmiennej, tym wyższa etykieta (preferujemy klientów, którzy kupują częściej).
r = pd.qcut(abt.Recency, 4, labels = list(range(0,4)))
f = pd.qcut(abt.Frequency, 4, labels = list(range(0,4)))
m = pd.qcut(abt.MonetaryValue, 4, labels = list(range(0,4)))
abt_cutted = pd.DataFrame({'Recency' : r, 'Frequency' : f, 'MonetaryValue' : m})
abt_raw = abt_cutted.values
8. Budowa modelu.¶
Przygotowanie funkcji do podsumowania grupowania (by wielokrotnie nie pisać tego samego kodu podczas testów).
def summarize_data():
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
for x in abt.groups.unique():
xs = abt[abt.groups == x]['Recency']
zs = abt[abt.groups == x]['Frequency']
ys = abt[abt.groups == x]['MonetaryValue']
ax.scatter(xs, ys, zs, s=50, alpha=0.6, edgecolors='w', label = x)
ax.set_xlabel('Recency')
ax.set_zlabel('Frequency')
ax.set_ylabel('MonetaryValue')
plt.title('Wizualizacja utworzonych grup')
plt.legend()
plt.show()
8.1. Wybór odpowiedniej liczby grup.¶
res = []
for n in range(2, 20):
kmeans = KMeans(n_clusters=n)
kmeans.fit(abt_raw)
res.append([n, kmeans.inertia_])
res = pd.DataFrame(res, columns = ['liczba_grup', 'inercja'])
res
plt.figure(figsize=(10,7))
sns.set(font_scale=1.4, style="whitegrid")
sns.lineplot(data = res, x = 'liczba_grup', y = 'inercja').set(title = "Miara odmienności grup vs liczba grup")
plt.axvline(x = 4, linestyle = '--')
plt.axvline(x = 8, linestyle = '--')
plt.show()
Mam dwa typy: 4 i 8 grup. Zweryfikuję oba, ponieważ jak pisałem w jednym z ostatnich wpisów, to analiza wybranych statystyk jest rzeczą najwazniejszą przy ocenie jakości grupowania.
8.2. Pierwszy model - 8 grup.¶
model_1 = KMeans(n_clusters=8)
groups = model_1.fit_predict(abt_raw)
abt_cutted['groups'] = groups
abt['groups'] = groups
summarize_data()
Rozkład liczności poszczególnych grup:
print((abt.groups.value_counts(normalize = True, sort = True) * 100).to_string())
Statystyki dla całego zbioru:
abt.agg(['mean'])
Statystyki dla poszczególnych grup:
abt.groupby('groups').agg(['mean'])
Statystyki wyglądają nieźle. Grupy są różnorodne, ładnie różnicują grupy.
8.3. Drugi model - 4 grupy.¶
model_1 = KMeans(n_clusters=4)
groups = model_1.fit_predict(abt_raw)
abt['groups'] = groups
summarize_data()
Rozkład liczności poszczególnych grup:
print((abt.groups.value_counts(normalize = True, sort = True) * 100).to_string())
Statystyki dla całego zbioru:
abt.agg(['mean'])
Statystyki dla poszczególnych grup:
abt.groupby('groups').agg(['mean'])
Segmentacja dla czterech grup w mojej ocenie wygląda lepiej, dlatego decyduję się na nią.
9. Opis uzyskanych wyników.¶
9.1. Interpretacja powstałych grup.¶
- Grupa 0 - klienci, którzy:
- robili niedawno zakupy,
- kupują często,
- robią zakupy za duże kwoty.
- Grupa 1 - klienci, którzy:
- od dłuższego czasu nic nie kupowali,
- kupują rzadko,
- robią zakupy za relatywnie niewielkie kwoty.
- Grupa 2 - klienci, którzy:
- robili dawno zakupy,
- kupują umiarkowanie często,
- robią zakupy za wysokie kwoty.
- Grupa 3 - klienci, którzy:
- robili niedawno zakupy,
- kupują rzadko,
- robią zakupy za relatywnie niewielkie kwoty.
9.2. Nadanie nazw grupom.¶
- Grupa 0 - Klienci VIP.
- Grupa 1 - Przypadkowi klienci detaliczni - starzy.
- Grupa 2 - Byli klienci VIP.
- Grupa 3 - Przypadkowi klienci detaliczni - nowi.
9.3. Przykładowe akcje i wnioski.¶
Sedno analizy segmentacyjnej, czyli akcje mające się przełożyć na zysk dla firmy. W omawianym przykładzie zdecydowałem się na 4 grupy, ponieważ chciałem, by analiza wyników była dla Ciebie przejrzysta. Dla czterech grup liczba akcji będzie oczywiście znacznie mniejsza niż dla ośmiu grup. Warto o tym pamiętać rozważając liczby już na etapie budowania modelu.
Szalenie istotne jest, by w całym procesie segmentacji behawioralnej zaangażowany był biznes. Mowa tu o dziale sprzedaży, który powinien mieć oko na wyniki analizy. Nieoceniona jest też rola marketingu w całym procesie.
Poniżej przedstawiam przykładowe akcje dla poszczególnych grup uzyskanych w procesie segmentacji:
- Grupa 0 - Klienci VIP.
- Dla najlepszych klientów warto pamiętać o "pielęgnacji" relacji. Jeśli prowadzimy biznes oparty o sprzedaż poprzez internet, przykładowymi akcją moga być:
- specjalne oferty, z rabatami przy odpowiednio dużych zakupach,
- upominek przy najbliższych zamówieniu,
- karty klienta VIP uprawniające do benefitów (zbieranie punktów wymienianych na nagrody, etc.).
- Jeśli prowadzimy biznes oparty o sprzedaż bezpośrednią, przykładowymi akcją mogą być:
- częstsze wizyty przedstawicieli handlowych u klienta,
- złożenie specjalnej oferty skierowanej do klientów VIP,
- zostawienie upominku przy ważnych okazjach.
- Dla najlepszych klientów warto pamiętać o "pielęgnacji" relacji. Jeśli prowadzimy biznes oparty o sprzedaż poprzez internet, przykładowymi akcją moga być:
- Grupa 1 - Przypadkowi klienci detaliczni - starzy.
- Zakładając, że zależy nam na zwiększaniu sprzedaży i zysku netto, grupa ta nie wydaje się specjalnie rokująca. Sugerowałbym "obsłużyć" ich w ostatniej kolejności (o ile w ogóle powinni oni zostać obsłużeni - dział marketingu powinien zdecydować, mając na uwadze spodziewaną stopę zwrotu z inwestycji).
- Grupa 2 - Byli klienci VIP.
- W tej grupie znajdują się duzi klienci, którzy z jakiegoś powodu przestali kupować nasze produkty (zamknięcie działalności, przejście do konkurencji). Niezależnie od powodu warto spróbować odnowić relacje z nimi poprzez sprawnie przeprowadzona akcję marketingową.
- Grupa 3 - Przypadkowi klienci detaliczni - nowi.
- Klienci, którzy wcześniej nie kupowali w naszym sklepie. W ich przypadku warto powalczyć o przekształcenie ich w klientów VIP. Przykładowe akcje:
- e-mail marketing z ofertami "2+1", przedstawiającymi zalety naszych produktów,
- upsell, cross-sell, połączony z case study naszych zadowolonych klientów (uproduktawianie klientów połączone ze "społecznym dowodem słuszności" i atrakcyjności produktu).
- Klienci, którzy wcześniej nie kupowali w naszym sklepie. W ich przypadku warto powalczyć o przekształcenie ich w klientów VIP. Przykładowe akcje:
10. Podsumowanie¶
Dziękuję Ci za dobrnięcie do końca. Mam nadzieję, że ten projekt przypadł Ci do gustu. Jeśli masz jakiekolwiek uwagi, pytania, lub spostrzeżenia, to zapraszam do dyskusji w komentarzach pod wpisem. 🙂
photo: pixabay.com (MetsikGarden)
PODOBAŁ CI SIĘ TEN ARTYKUŁ?
Jeśli tak, to zarejestruj się, by otrzymywać informacje o nowych wpisach.
Dodatkowo w prezencie wyślę Ci bezpłatny poradnik :-)
Witam,
bardzo ciekawy artykuł. Mam pytanie dotyczące przypisywanie etykiet (0-4)
„Etykiety będą mieć wartości z 1-4. W tym momencie należy zwrócić uwagę na biznesowe znaczenie poszczególnych zmiennych. Nadając etykiety wartościom zmiennych, trzeba pamiętać, że wyższa etykieta powinna oznaczać lepszą sytuację danej osoby/firmy z punktu widzenia sprzedającego. W związku z tym w przypadku „Recency” – im mniejsza wartość zmiennej, tym wyższa etykieta (preferujemy klientów, którzy są aktywni). Odwrotnie wygląda sytuacja w przypadku „Frequency” – im większa wartość zmiennej, tym wyższa etykieta (preferujemy klientów, którzy kupują częściej).”
W którym miejscu w kodzie określamy, że etykiety dla Recency i Frequency mają być przypisywane wg innych zasad?
Cześć Rafał! Dziękuję za komentarz i celną uwagę. 🙂 Bardzo fajnie, że to zaznaczyłeś.
Nie zrobiłem tego (dziś już nie pamiętam, czy było to celowe działanie, czy też najzwyczajniej zapomniałem o tym). W klasycznym RFM łączymy wartości kolejnych elementów (konkatenacja, ew. suma) i wtedy ma to kolosalne znaczenie. Gdy używamy RFM w połączeniu z grupowaniem ten zabieg nie jest konieczny. Rozważamy grupy obserwacji podobnych do siebie. Nie musimy wskazywać, że ktoś jest lepszy lub gorszy. Po prostu grupujemy klientów, sprawdzamy statystyki na surowych danych, a następnie do różnych grup mapujemy indywidualne działania marketingowe.
dziękuję za odpowiedź
Proszę Rafał 🙂 Ja dziękuję za zwrócenie uwagi na istotną rzecz pominiętą przeze mnie, a która mogłaby wprowadzić kogoś w błąd. Dzięki! 😉