K-prototypów – grupowanie zmiennych kategorycznych i ciągłych

k-prototypes, k-modes, k-prototypów, k-średnich, data science, segmentacja, grupowanie

Ostatni, najbardziej zaawansowany z algorytmów iteracyjno-optymalizacyjnych, który opisuję na blogu. K-prototypów, bo o nim mowa pozwala na grupowanie mieszanego zbioru składającego się ze zmiennych kategorycznych i ciągłych. 

Z tego artykułu dowiesz się: 1. Jak działa algorytm k-prototypów? 2. Jakie ma wady, a jakie zalety? 3. Kiedy go stosować?

Na blogu opisywałem już algorytmy, które znakomicie radzą sobie ze zmiennymi ciągłymi (k-średnich, k-median, oraz zmiennymi dyskretnymi (k-modes). Problem pojawiał się ze zbiorem posiadającym co najmniej dwa typy zmiennych: dyskretne i ciągłe. W pewnych sytuacjach, np. jak to słusznie zauważył jeden z czytelników - Adam, w komentarzu pod wpisem o k-median - owego algorytmu można użyć dla "zmiennych nienumerycznych, ale w skali porządkowej (np. wykształcenie podstawowe-średnie-wyższe)". Co zrobić jednak w przypadku zmiennych dyskretnych? Z pomocą przychodzi algorytm k-prototypów. 🙂 Zaczynamy!

Opis algorytmu

K-prototypes - najważniejsze informacje:

  • Jest on zaliczany do metod iteracyjno-optymalizacyjnych ze względu na schemat działania.
  • W kolejnych iteracjach wykonywana jest optymalizacja wyniku działania algorytmu przedstawionego w postaci odległości wszystkich obserwacji danej grupy względem jej "prototypu".
  • Nazwa k-prototypes odnosi się do k prototypów będących reprezentantami tendencji centralnej danej grupy. Prototyp jest obiektem będącym reprezentantem danej grupy obserwacji. Jest odpowiednikiem centroidu z algorytmu k-średnich i mody z algorytmu k-modes.
  • Zgodnie z powyższym punktem, algorytm ten jest połączeniem k-średnich i k-modes.
  • Zamiast dystansu (jak w k-średnich) używa on miary odmienności, będącej zmodyfikowaną wersją miary odmienności opisywanej przeze mnie przy okazji [algorytmu k-modes].
  • Miara odmienności (więcej o niej w akapicie poniżej) jest połączeniem odległości z k-średnich i miary odmienności k-modes.
  • Algorytm dąży do minimalizacji wariancji wewnątrz grup i jej maksymalizacji pomiędzy grupami.
  • Dla zmiennych ciągłych algorytm bazuje na centroid.
  • Dla zmiennych kategorycznych algorytm bazuje na częstościach występowania kategorii.
  • Im mniejsza jej wartość, tym większe podobieństwo pomiędzy obserwacjami. Miara odmienności jest przedstawiona jako suma niedopasowań poszczególnych zmiennych kategorycznych pomiędzy obserwacjami.
  • Implementacja algorytmu w Python jest dostępna w bibliotece KModes.

    Opis algorytmu k-modes.

Wyznaczanie miary odmienności.

  • Jest ona sumą odmienności dwóch obserwacji (obserwacja nr 1 będąca prototypem i obserwacja nr 2 należąca do tego samego segmentu).
  • Miara odmienności jest sumą dwóch wartości:
    • dystans dla zmiennych ciągłych. Jest to odległość euklidesowa tak jak w k-średnich. $$s^r$$
    • miara odmienności dla zmiennych kategorycznych. Jest zdefiniowanych jako liczba niedopasowań kategorii między dwoma obiektami. Parametr y został dodany, by równoważyć wpływ obu miar (dla zmiennych ciągłych i kategorycznych) na końcowy wynik, czyli finalną miarę odmienności: $$y*s^c$$
  • Finalny wzór na miarę odmienności to: $$S = s^r + y*s^c$$.

Schemat działania k-prototypów.

  1. Losowanie k-prototypów (gdzie k, to liczba segmentów, a prototyp, to środek segmentu, czyli najbardziej typowa obserwacja, która początkowo jest "kiepsko" dobrana) startowych w przestrzeni.
  2. Przyporządkowanie wszystkich obserwacji do najbliższego prototypu z pomocą miary odmienności (ang. dissimilarity measure).
  3. Dla każdej z k-grup wyznaczamy nowy prototyp, będącym reprezentantem segmentu.
  4. Powtarzamy krok 2 i 3 (obserwacje migrują pomiędzy segmentami, optymalizując (zmniejszając miarę odmienności dla obserwacji wewnątrz segmentu) aż do osiągnięcia warunku stopu, którym może być:
    • Osiągnięcie zbieżności, ew. „znacznej” poprawy względem wybranej miary jakości grupowania.
    • Osiągnięcie momentu, w którym przydział obserwacji do grup się nie zmienia.
    • Osiągnięcie zakładanej liczby iteracji.

Wady i zalety algorytmu

Stosując algorytm k-median warto mieć również na uwadze zarówno wszystkie jego ograniczenia, jak i mocne strony.

Wady:

  • Wymaga ustalenia liczby grup – zanim uruchomimy algorytm, musimy a priori podać liczbę grup, które mają zostać wyznaczone. Bez uprzedniego wizualizowania zbioru lub wykonania dodatkowych analiz jest to dosyć trudne.
  • Wrażliwy na dobór punktów startowych – w pierwszej iteracji swojego działania algorytm losowo dobiera punkty startowe (ew. możesz je ręcznie zdefiniować). To jak dobre wyniki uzyska, zależy zatem w pewnym stopniu od czynnika losowego.

Zalety:

  • Dosyć szybki – wynika to bezpośrednio ze sposobu jego działania. Niższa złożoność obliczeniowa sprawia, że w porównaniu np. z grupowaniem aglomeracyjnym, algorytm k-modes działa błyskawicznie. Wielkość zbioru przestaje więc być tak dużym problemem.
  • Wspiera zmienne kategoryczne - bez konieczności jakichkolwiek transformacji.
  • Wspiera zmienne ciągłe – algorytm wspiera zmienne numeryczne. W k-modes bylibyśmy zmuszeni do przeprowadzenia kategoryzacji zmiennych ciągłych. Tu nie ma takiej konieczności. Zmienne ciągłe są wspierane "out of the box". 😉
  • Działa pomimo brakujących wartości - braki są uzupełniane automatycznie i tworzona jest z nich odrębna kategoria.

    Wprowadzenie teoretyczne do algorytmów iteracyjno-optymalizacyjnych.

Przykład użycia

By pokazać działanie algorytmu, posłużę się biblioteką KModes. Poniżej kilka istotnych informacji na jej temat:

  • Posiada ona ten sam styl budowania modeli, co scikit-learn. Jest zatem intuicyjna dla wszystkich użytkowników sklearna.
  • Brakujące dane są uzupełniane automatycznie i traktowane jako odrębna kategoria (braki powinny być ujęte jako np.NaN) - jest to ułatwienie, choć nawet sam autor zaleca, by w większości przypadków lepiej zdecydować się na ręczne uzupełnianie braków zgodne z np. wiedzą biznesową.
  • Zmienne kategoryczne powinny być stringami. W bibliotece kmodes algorytm korzysta ze zmiany kodowania LabelEncoding, która na wejściu powinna mieć tekst. W bibliotece Pandas sprowadzi się to do tego, że zmienna (po wykonaniu metody abt.dtypes) powinna być widoczna jako Object.
  • Jeśli w ABT umieścimy same zmienne ciągłe, to algorytm nie wykona się tak, jak byśmy chcieli. Zostanie wyświetlony komunikat sugerujący zmianę na k-średnich.
  • Jeśli w ABT umieścimy same zmienne nominalne/kategoryczne, to algorytm nie wykona się tak jak byśmy chcieli. Zostanie wyświetlony komunikat sugerujący zmianę na k-modes.
  • Algorytm wymaga ustalenia liczby grup – zanim uruchomimy algorytm, musimy a priori podać liczbę grup, które mają zostać wyznaczone. Bez uprzedniego wizualizowania zbioru lub wykonania dodatkowych analiz jest to dosyć trudne.
  • Algorytm jest wrażliwy na dobór punktów startowych – w pierwszej iteracji swojego działania algorytm losowo dobiera punkty startowe. To jak dobre wyniki uzyska, zależy zatem w pewnym stopniu od czynnika losowego.
    • Rozwiązaniem jest tu wykonywanie algorytmu kilka razy z różnymi punktami startowymi. Na końcu wybrany zostaje ten model, który dał najlepszy wynik (najmniejsza wartość zadanej statystyki). Nie trzeba tego robić w sposób ręczny - biblioteka kmodes daje taką możliwość z użyciem parametru przy wywoływaniu modelu (parametr n_init=liczba_wywolan przy inicjowaniu nowego obiektu klasy KPrototypes).
  • Algorytm jest wrażliwy na wpływ obserwacji odstających i szum. Do wyznaczenia przeciętnej obserwacji używana jest wartość średnia współrzędnych wszystkich obserwacji danej grupy.
  • K-prototypes jest kombinacją algorytmów k-średnich i k-modes. Należy zatem pamiętać o założeniach k-średnich. Dobrą praktyką jest wykonanie kilku operacji na zmiennych ciągłych:
    • Usunięcie ujemnych wartości (na potrzeby transformacji logarytmicznej).
    • Usunięcie skośności zmiennych (transformacja logarytmiczna).
    • Centrowanie i skalowanie zmiennych (przesunięcie się o 1 w zmiennej_1 waży tyle, co 1 w zmiennej_2).

Główne informacje o zbiorze użytym w przykładzie:

Opis zmiennych,których użyję:

  • X1: Suma_kredytow / Limit_na_karcie (danej osoby i najbliższej rodziny).
  • X2: Plec (1 = mężczyzna; 2 = kobieta).
  • X4: Stan_cywilny (1 = w_zwiazku; 2 = kawaler_panna; 3 = inny).
  • X5: Wiek (podany w latach).
1. Wczytuję kilka niezbędnych bibliotek.
In [1]:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from kmodes.kprototypes import KPrototypes
from sklearn.preprocessing import StandardScaler
2. Wczytuję zbiór.
In [2]:
ef = pd.ExcelFile('data/credit_card_default.xls')
df = ef.parse('Data', skiprows=1, names = ['id', 'lim_kredytu', 'plec', 'wyksztalcenie', 'stan_cywilny', 'wiek', 'opozn_plat_wrz', 'opozn_plat_sie', 'opozn_plat_lip', 'opozn_plat_cze', 'opozn_plat_maj', 'opozn_plat_kwi', 'kwota_wyciagu_wrz', 'kwota_wyciagu_sie', 'kwota_wyciagu_lip', 'kwota_wyciagu_cze', 'kwota_wyciagu_maj', 'kwota_wyciagu_kwi', 'platnosc_wrz', 'platnosc_sie', 'platnosc_lip', 'platnosc_cze', 'platnosc_maj', 'platnosc_kwi', 'y'])
df.drop('id', axis = 1, inplace = True)
3. Zamieniam wartości jakie przyjmują poszczególne zmienne.
In [3]:
df.plec.replace([1,2], ['kobieta', 'mezczyzna'], inplace = True)
df.stan_cywilny.replace([0, 1, 2, 3], ['nieznany', 'w_zwiazku', 'kawaler_panna', 'inny'], inplace = True)
4. Ograczam zbiór do czterech zmiennych, którymi się posłużę.
In [4]:
df = df[['lim_kredytu', 'plec', 'stan_cywilny', 'wiek']]
In [5]:
df.head()
Out[5]:
lim_kredytu plec stan_cywilny wiek
0 20000 mezczyzna w_zwiazku 24
1 120000 mezczyzna kawaler_panna 26
2 90000 mezczyzna kawaler_panna 34
3 50000 mezczyzna w_zwiazku 37
4 50000 kobieta w_zwiazku 57
5. Usuwam obserwacje odstające, standaryzuję zmienne numeryczne i usuwam skośność.

Sprawdzam, czy istnieją odstające obserwacje.

In [6]:
q1 = df.quantile(0.25)
q3 = df.quantile(0.75)
iqr = q3 - q1

low_boundary = (q1 - 1.5 * iqr)
upp_boundary = (q3 + 1.5 * iqr)
num_of_outliers_L = (df[iqr.index] < low_boundary).sum()
num_of_outliers_U = (df[iqr.index] > upp_boundary).sum()
outliers = pd.DataFrame({'lower_boundary':low_boundary, 'upper_boundary':upp_boundary,'num_of_outliers__lower_boundary':num_of_outliers_L, 'num_of_outliers__upper_boundary':num_of_outliers_U})
In [7]:
outliers
Out[7]:
lower_boundary upper_boundary num_of_outliers__lower_boundary num_of_outliers__upper_boundary
lim_kredytu -235000.0 525000.0 0 167
wiek 8.5 60.5 0 272

Usuwam odstające obserwacje.

In [8]:
for row in outliers.iterrows():
    df = df[(df[row[0]] >= row[1]['lower_boundary']) & (df[row[0]] <= row[1]['upper_boundary'])]

Usuwam skośność zmiennych.

In [9]:
df_modified = df.copy()
df_modified = df_modified.assign(wiek = np.log(df_modified.wiek))
df_modified = df_modified.assign(lim_kredytu = np.log(df_modified.lim_kredytu))

Wykonuje standaryzację zmiennych.

In [10]:
scaler = StandardScaler()
scaler.fit(df_modified.wiek.values.reshape(-1, 1))
df_modified = df_modified.assign(wiek = scaler.transform(df_modified.wiek.values.reshape(-1, 1)))
scaler.fit(df_modified.lim_kredytu.values.reshape(-1, 1))
df_modified = df_modified.assign(lim_kredytu = scaler.transform(df_modified.lim_kredytu.values.reshape(-1, 1)))

Podgląd zbioru.

In [11]:
df_modified.head()
Out[11]:
lim_kredytu plec stan_cywilny wiek
0 -1.869490 mezczyzna w_zwiazku -1.436359
1 0.046324 mezczyzna kawaler_panna -1.110258
2 -0.261277 mezczyzna kawaler_panna -0.017327
3 -0.889759 mezczyzna w_zwiazku 0.327168
4 -0.889759 kobieta w_zwiazku 2.087717
6. Sprawdzam na ile grup podzielić zbiór.
In [12]:
df_sample = df_modified.sample(frac=0.2)
In [57]:
res = []
for n in range(1, 21):
    kp = KPrototypes(n_clusters=n, init='Huang', n_init=3, n_jobs=4)
    kp.fit_predict(df_sample, categorical=[1, 2])
    res.append([n, kp.cost_])
In [58]:
res = pd.DataFrame(res, columns=[0, 'wspolcz_odm']).set_index(0)
In [59]:
plt.figure(figsize=(10,7))
sns.set(font_scale=1.4, style="whitegrid")
sns.lineplot(data = res, palette = ['#eb6c6a']).set(title = "Miara odmienności grup vs liczba grup")
plt.show()

Powyższy wykres wskazuje, że "łokieć" znajduje się dokładnie przy 4 grupach. To o czym należy pamiętać, to by nie zdawać się tylko i wyłącznie na powyższe wyniki. Analiza statystyk uzyskanych grup jest o wiele ważniejsza niż wiedza płynąca z wykresu łokcia, który jest jedynie punktem wyjścia dla poszukiwań. Przeprowadziłem "offline" analizę statystyczną dla segmentacji z użyciem 3, 4, 5, 6 i 7 segmentów. Metodą prób i błędów zdecydowałem się na 6 segmentów.

7. Przeprowadzam grupowanie.
In [17]:
kp = KPrototypes(n_clusters=6, init='Huang', n_init=5, n_jobs=4)
clusters = kp.fit_predict(df_modified, categorical=[1, 2])
In [18]:
kp.cluster_centroids_
Out[18]:
[array([[ 0.66308765,  0.35301211],
        [ 0.66486204,  1.41641721],
        [ 0.81477005, -0.56233847],
        [-1.12070411,  0.9689542 ],
        [-1.58595631, -1.14611821],
        [-0.35778674, -0.94958434]]), array([['mezczyzna', 'w_zwiazku'],
        ['mezczyzna', 'w_zwiazku'],
        ['mezczyzna', 'kawaler_panna'],
        ['kobieta', 'w_zwiazku'],
        ['kobieta', 'kawaler_panna'],
        ['mezczyzna', 'kawaler_panna']], dtype='<U13')]

W powyższym przykładzie centroidy, to prototypy, a więc "sztuczne" obserwacje reprezentujące tendencję centralna grup.

8. Przygotowuję dane do końcowej analizy.
In [19]:
df = df.assign(segment = clusters)
In [20]:
df.segment = df.segment.astype(str)
9. Analiza wyników grupowania.

Analiza całego zbioru.

Prześledzę rozkłady całego zbioru, tak by mieć punkt odniesienia dla poszczególnych grup.

In [21]:
for column in ['plec', 'stan_cywilny']:
    print((df[column].value_counts(normalize = True) * 100).round(2))
    print('')
mezczyzna    60.54
kobieta      39.46
Name: plec, dtype: float64

kawaler_panna    53.57
w_zwiazku        45.17
inny              1.08
nieznany          0.18
Name: stan_cywilny, dtype: float64

In [22]:
df.describe().transpose()
Out[22]:
count mean std min 25% 50% 75% max
lim_kredytu 29564.0 164671.887431 125353.622726 10000.0 50000.0 140000.0 240000.0 520000.0
wiek 29564.0 35.200041 8.827269 21.0 28.0 34.0 41.0 60.0

Segment 0.

In [23]:
segment_0 = df[df.segment == "0"]
In [24]:
for column in ['plec', 'stan_cywilny']:
    print((segment_0[column].value_counts(normalize = True) * 100).round(2))
    print('')
mezczyzna    64.57
kobieta      35.43
Name: plec, dtype: float64

w_zwiazku        77.28
kawaler_panna    21.89
inny              0.64
nieznany          0.19
Name: stan_cywilny, dtype: float64

In [25]:
segment_0.describe().transpose()
Out[25]:
count mean std min 25% 50% 75% max
lim_kredytu 6396.0 237238.536585 109027.554552 70000.0 150000.0 210000.0 300000.0 520000.0
wiek 6396.0 37.350219 2.906774 31.0 35.0 37.0 40.0 42.0

Charakterystyka segmentu:

  • Zmienna "plec" - zdecydowana przewaga mężczyzn.
  • Zmienna "stan_cywilny" - przewaga osób w stałych związkach.
  • Zmienna "lim_kredytu" - powyżej średniej i mediany dla całego zbioru.
  • Zmienna "wiek" - nieco poniżej średniej i mediany.

Segment 0 to głównie młodzi mężczyźni będący w stałych związkach i o ponad przeciętnym limicie kredytu.

Segment 1.

In [26]:
segment_1 = df[df.segment == "1"]
In [27]:
for column in ['plec', 'stan_cywilny']:
    print((segment_1[column].value_counts(normalize = True) * 100).round(2))
    print('')
mezczyzna    59.48
kobieta      40.52
Name: plec, dtype: float64

w_zwiazku        77.82
kawaler_panna    20.54
inny              1.34
nieznany          0.30
Name: stan_cywilny, dtype: float64

In [28]:
segment_1.describe().transpose()
Out[28]:
count mean std min 25% 50% 75% max
lim_kredytu 3954.0 241269.600405 117033.519024 70000.0 150000.0 210000.0 320000.0 520000.0
wiek 3954.0 48.533384 4.391656 43.0 45.0 48.0 52.0 60.0

Charakterystyka segmentu:

  • Zmienna "plec" - rozkład niemal identyczny z rozkładem dla całego zbioru. Nie można mówić zatem o zdecydowanej dyskryminacji grupy ze względu na którąkolwiek wartość.
  • Zmienna "stan_cywilny" - przewaga osób w stałych związkach.
  • Zmienna "lim_kredytu" - znacznie powyżej średniej i mediany dla całego zbioru.
  • Zmienna "wiek" - znacznie powyżej średniej i mediany (34 vs 48 lat).

Segment 1 to starsi klienci banku, będący w stałych związkach i o ponad przeciętnym limicie kredytu.

Segment 2.

In [29]:
segment_2 = df[df.segment == "2"]
In [30]:
for column in ['plec', 'stan_cywilny']:
    print((segment_2[column].value_counts(normalize = True) * 100).round(2))
    print('')
mezczyzna    65.42
kobieta      34.58
Name: plec, dtype: float64

kawaler_panna    86.24
w_zwiazku        13.42
inny              0.24
nieznany          0.10
Name: stan_cywilny, dtype: float64

In [31]:
segment_2.describe().transpose()
Out[31]:
count mean std min 25% 50% 75% max
lim_kredytu 6223.0 262138.839788 96015.300702 120000.0 190000.0 230000.0 320000.0 520000.0
wiek 6223.0 29.872730 2.786499 22.0 28.0 30.0 32.0 36.0

Charakterystyka segmentu:

  • Zmienna "plec" - przewaga mężczyzn.
  • Zmienna "stan_cywilny" - niemal sami kawalerowie/panny.
  • Zmienna "lim_kredytu" - powyżej średniej i mediany dla całego zbioru.
  • Zmienna "wiek" - znacznie poniżej średniej i mediany.

Segment 2 to młodzi klienci banku, niebędący w stałych związkach i o ponad przeciętnym limicie kredytu.

Segment 3.

In [32]:
segment_3 = df[df.segment == "3"]
In [33]:
for column in ['plec', 'stan_cywilny']:
    print((segment_3[column].value_counts(normalize = True) * 100).round(2))
    print('')
kobieta      53.69
mezczyzna    46.31
Name: plec, dtype: float64

w_zwiazku        65.01
kawaler_panna    31.09
inny              3.68
nieznany          0.22
Name: stan_cywilny, dtype: float64

In [34]:
segment_3.describe().transpose()
Out[34]:
count mean std min 25% 50% 75% max
lim_kredytu 4481.0 45731.309975 21220.886029 10000.0 30000.0 50000.0 50000.0 110000.0
wiek 4481.0 43.778175 6.455920 32.0 38.0 43.0 48.0 60.0

Charakterystyka segmentu:

  • Zmienna "plec" - mając na uwadze rozkład zmiennej dla całego zbioru, możemy mówić tu po raz pierwszy o znaczącej przewadze kobiet.
  • Zmienna "stan_cywilny" - przewaga osób w stałych związkach.
  • Zmienna "lim_kredytu" - znacznie poniżej średniej i mediany (140k vs 50k) dla całego zbioru.
  • Zmienna "wiek" - znacznie powyżej średniej i mediany.

Segment 3 to głównie starsze kobiety (mając na uwadze statystyki całego zbioru), będące w stałych związkach i o niskim limicie kredytu.

Segment 4.

In [35]:
segment_4 = df[df.segment == "4"]
In [36]:
for column in ['plec', 'stan_cywilny']:
    print((segment_4[column].value_counts(normalize = True) * 100).round(2))
    print('')
kobieta      59.67
mezczyzna    40.33
Name: plec, dtype: float64

kawaler_panna    82.82
w_zwiazku        16.55
inny              0.54
nieznany          0.10
Name: stan_cywilny, dtype: float64

In [37]:
segment_4.describe().transpose()
Out[37]:
count mean std min 25% 50% 75% max
lim_kredytu 3154.0 28979.074192 12998.238663 10000.0 20000.0 30000.0 30000.0 60000.0
wiek 3154.0 25.983830 3.401748 21.0 23.0 25.0 28.0 36.0

Charakterystyka segmentu:

  • Zmienna "plec" - jeszcze większa przewaga kobiet niż w przypadku segmentu 3.
  • Zmienna "stan_cywilny" - przewaga osób niebędących w stałych związkach.
  • Zmienna "lim_kredytu" - bardzo niski (mediana 140k vs 30k).
  • Zmienna "wiek" - bardzo niski (mediana 34 vs 25 lat).

Segment 4 to młode panny o bardzo niskim limicie kredytu.

Segment 5.

In [38]:
segment_5 = df[df.segment == "5"]
In [39]:
for column in ['plec', 'stan_cywilny']:
    print((segment_5[column].value_counts(normalize = True) * 100).round(2))
    print('')
mezczyzna    74.63
kobieta      25.37
Name: plec, dtype: float64

kawaler_panna    79.42
w_zwiazku        19.87
inny              0.50
nieznany          0.21
Name: stan_cywilny, dtype: float64

In [40]:
segment_5.describe().transpose()
Out[40]:
count mean std min 25% 50% 75% max
lim_kredytu 5356.0 87638.162808 31227.076401 40000.0 60000.0 80000.0 110000.0 180000.0
wiek 5356.0 27.229276 3.197526 21.0 25.0 27.0 29.0 37.0

Charakterystyka segmentu:

  • Zmienna "plec" - zdecydowana przewaga mężczyzn.
  • Zmienna "stan_cywilny" - przewaga osób niebędących w stałych związkach.
  • Zmienna "lim_kredytu" - poniżej średniej i mediany dla całego zbioru.
  • Zmienna "wiek" - znacznie poniżej średniej i mediany.

Segment 5 to młodzi kawalerowie o niskim limicie kredytu (męska wersja segmentu 4).

9. Pogrupowane wyniki segmentacji.

Zmienna "plec".

In [45]:
((df.groupby(['plec', 'segment'])['segment'].count().unstack().fillna(0)/df['segment'].value_counts())*100).round(2)
Out[45]:
0 1 2 3 4 5
plec
kobieta 35.43 40.52 34.58 53.69 59.67 25.37
mezczyzna 64.57 59.48 65.42 46.31 40.33 74.63

Widoczny rozkład pomiędzy kobiety i mężczyzn w poszczególnych segmentach.

Zmienna "stan_cywilny".

In [46]:
((df.groupby(['stan_cywilny', 'segment'])['segment'].count().unstack().fillna(0)/df['segment'].value_counts())*100).round(2)
Out[46]:
0 1 2 3 4 5
stan_cywilny
inny 0.64 1.34 0.24 3.68 0.54 0.50
kawaler_panna 21.89 20.54 86.24 31.09 82.82 79.42
nieznany 0.19 0.30 0.10 0.22 0.10 0.21
w_zwiazku 77.28 77.82 13.42 65.01 16.55 19.87

Podsumowanie

To już ostatni wpis opisujący algorytmy iteracyjno-optymalizacyjne. Mam nadzieję, że zawarte w nim informację okażą się dla Ciebie użyteczne. 🙂

Jeśli masz jakieś pytania, to proszę, podziel się nimi w komentarzu pod wpisem. Na wszystkie postaram się odpowiedzieć. Zapraszam do dyskusji. 🙂

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 🙂

Bądź pierwszy, który skomentuje ten wpis!

Dodaj komentarz

Twój adres email nie zostanie opublikowany.


*