Artificial Intelligence w Marketingu Bankowym.
Jak podkreślają autorzy raportu, banki wciąż eksperymentują z technologią opartą na sztucznej inteligencji. Jednak już teraz poważnie analizowane są możliwości wykorzystania AI w kilku obszarach.
1. Sztuczna inteligencja pomoże w walce z oszustami
AI jest testowane przede wszystkim pod kątem identyfikacji klienta w czasie rzeczywistym i zapobiegania oszustwom w bankowości internetowej. Oszustwa związane z kartami kredytowymi stały się w ostatnich latach jedną z najbardziej rozpowszechnionych form cyberprzestępczości, co spowodowane jest dużym wzrostem liczby płatności internetowych i mobilnych.
Aby zidentyfikować nielegalną działalność, algorytmy sztucznej inteligencji sprawdzają wiarygodność transakcji kart kredytowych klientów w czasie rzeczywistym i porównują nowe transakcje z poprzednimi kwotami oraz lokalizacjami, z których były wykonywane. System blokuje transakcje, jeśli tylko widzi potencjalne ryzyko.2. AI sprawdzi naszą tożsamość
Sztuczna inteligencja jest również testowana w procesach KYC (Know Your Customer), w celu weryfikacji tożsamości klientów. Algorytmy sztucznej inteligencji skanują dokumenty i oceniają wiarygodność dostarczonych przez klientów informacji, porównując je z danymi dostępnymi w Internecie. Jeśli algorytmy sztucznej inteligencji identyfikują niespójności, podnoszą czerwoną flagę i przeprowadzane jest bardziej szczegółowa analiza – z manualną ingerencją pracowników danego banku.
3. Boty zastąpią konsultantów?
Innym obszarem, w którym banki eksperymentują z technologiami sztucznej inteligencji, są chatboty, czyli asystenci cyfrowi, którzy kontaktują się z klientami za pomocą wiadomości tekstowych lub głosowo i starają się odpowiedzieć na ich prośby bez udziału pracownika banku.
Dobrym przykładem podawanym przez autorów raportu są też robo-doradcy, którzy umożliwiają pełną automatyzację niektórych usług zarządzania aktywami i narzędzia planowania finansowego online. Analizując szereg danych historycznych są w stanie dokonywać lepszych predykcji dotyczących zachowań portfeli inwestycyjnych. Równocześnie na podstawie analizy zachowań pomagają klientom podejmować bardziej świadome decyzje dotyczące wydatków i oszczędzania.
4. Sztuczna inteligencja sprawdzi za nas poprawność dokumentów
Banki badają również sztuczną inteligencję, aby wizualizować informacje z dokumentów prawnych lub raportów rocznych, a także wyodrębnić ważne klauzule. Narzędzia te tworzą modele autonomicznie, będące wynikiem analizy danych i testów wstecznych, aby wyciągnąć wnioski z ewentualnych wcześniejszych błędów. Dzięki czemu są w stanie same sprawdzać poprawność dokumentów.
5. Sztuczna inteligencja będzie przewidywać wydarzenia geopolityczne
Sztuczna inteligencja i narzędzia do samouczenia się maszyn (machine learning) umożliwiają również określanie ryzyka geopolitycznego i przewidzenie jego wpływu na rynki finansowe. Przykładem takiego rozwiązania jest platforma Alpha-Dig, wykorzystywana przez Deutsche Bank. Alpha-Dig analizując w czasie rzeczywistym dane pochodzące z mediów informacyjnych, społecznościowych i innych źródeł, tworzy obraz profilu ryzyka politycznego dla danego kraju.
Narzędzie wykorzystuje przetwarzanie języka naturalnego i techniki systemów uczących się, aby wywnioskować kontekst np. w danym artykule prasowym, wyciągając pozytywne i negatywne wskaźniki. W drugim kroku Alpha-Dig wykorzystuje wpisy z Wikipedii, gdzie teksty są łatwe do odczytania dla maszyn. Platforma analizuje też te tematy, które są popularne i istotne w danym momencie. Badana jest średnia liczba codziennych wiadomości geopolitycznych dla danego tematu w niedawnej przeszłości.
Oczywiście żaden system nie jest w stanie przewidzieć konsekwencji wydarzeń geopolitycznych ze stuprocentową pewnością. Ale dzięki narzędziom takim jak Alpha-Dig które opierają swoją analizę na ogromnej ilości danych statystycznych, można stworzyć obiektywne mierniki, które mogą pomóc inwestorom podejmować decyzje, ograniczając ryzyko w trudniejszych obszarach.
źródło: https://prnews.pl/5-rzeczy-ktore-sztuczna-inteligencja-moze-zmienic-w-bankach-444624
opis danych
Dane klienta banku
1 – age (numerycznie)
2 – job: rodzaj pracy (kategorycznie: „admin.”, „Pracownik fizyczny”, „przedsiębiorca”, „pokojówka”, „zarządzanie”, „emerytowany”, „samozatrudniony”, „ usługi ”,„ student ”,„ technik ”,„ bezrobotny ”,„ nieznany ”)
3 – marital: stan cywilny (kategorycznie:„ rozwiedziony ”,„ żonaty ”,„ samotny ”,„ nieznany ”; uwaga:„ rozwiedziony ”oznacza rozwiedziony lub owdowiały)
4 – education (kategoryczne: „podstawowy. 4 lata”, „podstawowy. 6 lat”, „podstawowy. 9 lat”, „szkoła średnia”, „analfabeta”, „kurs zawodowy”, „uniwersytet. stopień”, „nieznane”)
5 – default: czy kredyt jest niespłacony? (kategorycznie: „nie”, „tak”, „nieznany”)
6 – housing: ma kredyt mieszkaniowy? (kategorycznie: „nie”, „tak”, „nieznany”)
7 – loan: czy pożyczka osobista? (kategorycznie: „nie”, „tak”, „nieznany”)
Powiązane z ostatnim kontaktem bieżącej kampanii¶
8 – contact: typ komunikacji kontaktowej (kategorycznie: „komórkowy”, „telefon”)
9 – month: ostatni miesiąc kontaktowy w roku (kategorycznie: „jan”, „lut”, „mar”, …, „lis”, „dec”)
10 – day_of_week: ostatni dzień tygodnia w tygodniu (kategorycznie: „pon”, „wt”, „środ”, „czw”, „pt”)
11 – duration: czas trwania ostatniego kontaktu, w sekundach (numerycznie) . Ważna uwaga: ten atrybut ma duży wpływ na docelowy
wynik (np. Jeśli czas trwania = 0, to y = „nie”). Jednak czas trwania nie jest znany przed wykonaniem połączenia. Ponadto po zakończeniu połączenia y jest oczywiście znane. W związku z tym dane te należy uwzględnić wyłącznie do celów porównawczych i należy je odrzucić, jeżeli intencją jest stworzenie realistycznego modelu predykcyjnego.
Inne atrybuty
12 – campaign: liczba kontaktów wykonanych podczas tej kampanii i dla tego klienta (numerycznie, obejmuje ostatni kontakt)
13 – pdays: liczba dni, które upłynęły od ostatniego kontaktu klienta z poprzedniej kampanii (numerycznie; 999 oznacza, że
klient nie był wcześniej skontaktowano się)
14 – previous: liczba kontaktów wykonanych przed tą kampanią i dla tego klienta (numerycznie)
15 – poutcome: wynik poprzedniej kampanii marketingowej (kategorycznie: „porażka”, „nieistniejąca”, „sukces”)
Atrybuty kontekstu społecznego i gospodarczego
16 – emp.var.rate: wskaźnik zmienności zatrudnienia – wskaźnik kwartalny (liczbowy)
17 – Cons.price.idx: wskaźnik cen konsumpcyjnych – wskaźnik miesięczny (liczbowy)
18 – cons.conf.idx: wskaźnik zaufania konsumentów – wskaźnik miesięczny (liczbowy )
19 – euribor3 mln: stawka 3-miesięczna euribor – wskaźnik dzienny (liczbowy)
20 – nr_employed: liczba zatrudnionych: liczba pracowników – wskaźnik kwartalny (liczbowy)
Cel zadania
- zaproponują grupy klientów (segmenty), dla których Dep. Marketingu opracuje odrębne skrypty rozmów i argumenty sprzedażowe
- przygotują model predykcyjny, na podstawie którego do kolejnej akcji sprzedażowej zostaną wyselekcjonowani klienci cechujący się najwyższym prawdopodobieństwem kupna produktu.
1. Pobranie danych i bibliotek
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.preprocessing import LabelEncoder
import matplotlib.pylab as plt
from pylab import plot, show, subplot, specgram, imshow, savefig
from sklearn import preprocessing
from sklearn.preprocessing import Normalizer
from sklearn.preprocessing import Imputer
import matplotlib.pyplot as plote
df = pd.read_csv('c:/1/bank.csv')
df.head()
2. Sprawdzenie kompletność i formatu danych
df.isnull().sum()
del df['Unnamed: 0']
del df['Unnamed: 0.1']
Sprawdzam typ danych
df.dtypes
Wielkość bazy
df.shape
WYNIK 2:
- Dane są kompletne
- Format prawidłowy
- Ilość 41188 wierszy, 21 kolumn
3. Sprawdzanie struktury danych
3.1 Tworze grupy kolumn danych ciągłych i danych dyskretnych
categorical_vars = df.describe(include=["object"]).columns
continuous_vars = df.describe().columns
continuous_vars
categorical_vars
Dane w połowie dyskretne w połowie ciągłe
3.2 Wizualizacja struktury
sns.set(style="white")
_ = df.hist(column=continuous_vars, figsize = (16,16))
DANE DYSKRETNE – WYŚWIETLAM ILOŚĆ DANYCH UNIKALNYCH W KAŻDEJ KATEGORII
fig, axes = plt.subplots(4, 3, figsize=(16, 16))
# robie przestrzeń między wykresami
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0.8, hspace=0.3)
# pętla: mamy 9 zmiennych dyskretnych
for i, ax in enumerate(axes.ravel()):
if i > 9:
ax.set_visible(False)
continue
sns.countplot(y = categorical_vars[i], data=df, ax=ax)
Pozycje df[‘education’] -illiterate daje do unknown
df['education'] = df['education'].str.replace('illiterate','unknown')
4. Analiza korelacji zmiennych ciągłych
CORREL = df.corr().sort_values('y')
CORREL['y'].to_frame().sort_values('y')
plt.figure(figsize=(10,6))
CORREL['y'].plot(kind='barh', color='red')
plt.title('Korelacja ze zmienną wynikową', fontsize=20)
plt.xlabel('Poziom korelacji')
plt.ylabel('Zmienne nezależne ciągłe')
4.2 Wykres macierzowy korelacji zmiennych ciągłych
plt.figure(figsize=(10,6))
CORREL =df.corr()
sns.heatmap(CORREL, annot=True, cbar=False, cmap="coolwarm")
plt.title('Macierz korelacji ze zmienną wynikową y', fontsize=20)
plt.figure(figsize=(10,6))
CORREL =df.corr().sort_values('y')
sns.heatmap(CORREL, annot=True, cbar=False, cmap="coolwarm")
plt.title('Macierz korelacji posortowana według korelacji ze zmienną wynikową y', fontsize=20)
ZMIENNE CIĄGŁE – PRZYSTOSOWANIE DO REGRESJI LOGISTYCZNEJ
continuous_vars
Age – grupa nie jest ciekawa ponieważ jest bardzo niski poziom korelacji ze zmienną wynikową
Niski poziom korelacji ze zmienną y = 0.03
df['AA5_Grup_wiekowych'] = pd.qcut(df['age'],5)
df['AA5_Grup_wiekowych'].value_counts()
df['AA5_Grup_wiekowych'].value_counts(normalize=True)
df['AA5_Grup_wiekowych'] = df['AA5_Grup_wiekowych'].astype(object)
Analiza poziomu korelacji grup wiekowych pod kontem ewentualnego skasowania grupy lub zmiany grup.
Grupa wiekowa powyżej 60 lat stanowi 3% populacji lecz ma wysoki wskaźnik zakupów.
plt.style.use('seaborn')
table=pd.crosstab(df.AA5_Grup_wiekowych,df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=14)
plt.title('Grupy_wiekowe vs Purchase', fontsize=20)
plt.xlabel('Grupy_wiekowe')
plt.ylabel('Proportion of Customers')
plt.savefig('mariral_vs_pur_stack')
To był podział ręczny – teraz podizele klientów według wieku statystycznie
duration: wysoki poziom korelacji ze zmienną y = 0.405274
CZAS TRWANIA ROZMOWY TELEFONICZNEJ MA NAJWIĘKSZE ZNACZENIE PRZY WYNIKU SPRZEDAŻY
czas trwania ostatniego kontaktu, w sekundach (numerycznie) . Ważna uwaga: ten atrybut ma duży wpływ na docelowy wynik (np. Jeśli czas trwania = 0, to y = „nie”). Jednak czas trwania nie jest znany przed wykonaniem połączenia. Ponadto po zakończeniu połączenia y jest oczywiście znane. W związku z tym dane te należy uwzględnić wyłącznie do celów porównawczych i należy je odrzucić, jeżeli intencją jest stworzenie realistycznego modelu predykcyjnego.
Wyrzucam wszystkie zdarzenia gdzie duration był 0
df[df.duration==0]
Kasuje wszystkie zdarzenia, kiedy nie było rozmowy duration=0
#df['duration'] = df['duration'].replace(0,np.nan)
Nikt nie rozmawia z klientami przez sekundy tylko przez minuty, dlatego przeliczam sekundy na minuty.
df['duration'] = df['duration']/60
Tworzę przedziały trwania rozmowy telefonicznej jako oddzielną kolumnę
Analiza zależności minut do grup szczegółowych
df['CCzas_zaok'] = np.round(df['duration'], decimals=1)
df['CCzasy rozmów_40G'] = pd.qcut(df['CCzas_zaok'],40)
del df['CCzas_zaok']
#df['CCzasy rozmów_40G'].value_counts()
plt.style.use('seaborn')
sns.set(rc={'figure.figsize':(5, 16)})
table=pd.crosstab(df['CCzasy rozmów_40G'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='barh', stacked=True, fontsize=14)
plt.title('Czasy rozmów vs Purchase', fontsize=20)
plt.xlabel('Czas trwania rozmowy w minutach')
plt.ylabel('Proportion of Customers')
plt.savefig('mariral_vs_pur_stack')
Dzielimy grupę duration na 3 grupy:
- ‘Rozmowa poniżej 3 minut’,
- ‘Rozmowa 3-6 minut’,
- ‘Rozmowa powyżej 6 minut’
lst = [df]
for column in lst:
column.loc[column["duration"] < 3, "AA_3Czasy_kontaktu"] = 'Rozmowa poniżej 3 minut'
column.loc[(column["duration"] >= 3) & (column["duration"] <= 6), "AA_3Czasy_kontaktu"] = 'Rozmowa 3-6 minut'
column.loc[column["duration"] >= 6, "AA_3Czasy_kontaktu"] = 'Rozmowa powyżej 6 minut'
Kasuje czas rozmów df[‘CCzasy rozmów_40G’] bo już mi nie jest otrzebny
del df['CCzasy rozmów_40G']
Stworzyłem grupę 3Czasy_rozmów która istotnie wpływa na wynik procesu
sns.set(style="white")
sns.set(rc={'figure.figsize':(4, 4)})
table=pd.crosstab(df['AA_3Czasy_kontaktu'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Czas trwania kontaktu wg. trzech grup', fontsize=20)
plt.xlabel('Czas rozmowy')
plt.ylabel('Proportion of Customers')
previous: wysoki poziom korelacji ze zmienną y = 0.230181
liczba kontaktów wykonanych przed tą kampanią i dla tego klienta (numerycznie)
sns.set(style="white")
sns.set(rc={'figure.figsize':(4, 4)})
table=pd.crosstab(df['previous'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Czas trwania rozmó wg trzech grup', fontsize=20)
plt.xlabel('Ilość kontaktów')
plt.ylabel('Proportion of Customers')
Można obliczyć średni czas jednego kontaktu na klienta, który może się korelować ze zmienną y`
Zero znaczy że był jeden kontakt, jeden znaczy że jest 1 więcej niż standardowo. Zmieniam ten dziwny sposób liczenia kontaktów na 1 znaczy jedne kontakty, 2 znaczy 2 kontakty itd w kolumnie CC_previous
df['previous'] = df['previous']+1
df['AA_Średni czas kontaktu'] = df['duration']/df['previous']
Średni czas na jeden kontrakt
df.pivot_table(index=['duration','previous'], values='AA_Średni czas kontaktu').head(5)
Dzielimy na grupy i szukamy najbardziej efektywnego czasu komunikacji
df['AA_Średni czas kontaktu'] = np.round(df['AA_Średni czas kontaktu'], decimals=1)
df['CC_Średni czas jednego kontaktu_40G'] = pd.qcut(df['AA_Średni czas kontaktu'],40)
plt.style.use('seaborn')
sns.set(rc={'figure.figsize':(5, 16)})
table=pd.crosstab(df['CC_Średni czas jednego kontaktu_40G'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='barh', stacked=True, fontsize=14)
plt.title('Czasy jednego kontaktu vs Purchase', fontsize=20)
plt.xlabel('Czas trwania jednego kontaktu w minutach')
plt.ylabel('Proportion of Customers')
plt.savefig('mariral_vs_pur_stack')
lst = [df]
for column in lst:
column.loc[column["AA_Średni czas kontaktu"] < 3, "3Średni_czas_kontaktu"] = 'Rozmowa poniżej 3 minut'
column.loc[(column["AA_Średni czas kontaktu"] >= 3) & (column["AA_Średni czas kontaktu"] <= 4), "3Średni_czas_kontaktu"] = 'Rozmowa 3-4 minut'
column.loc[(column["AA_Średni czas kontaktu"] >= 4) & (column["AA_Średni czas kontaktu"] <= 6), "3Średni_czas_kontaktu"] = 'Rozmowa 4-6 minut'
column.loc[column["AA_Średni czas kontaktu"] >= 6, "3Średni_czas_kontaktu"] = 'Rozmowa powyżej 6 minut'
Kasuje df[‘CC_Średni czas jednego kontaktu_40G’]
del df['CC_Średni czas jednego kontaktu_40G']
sns.set(style="white")
sns.set(rc={'figure.figsize':(4, 4)})
table=pd.crosstab(df['3Średni_czas_kontaktu'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Średni czas trwania pojędyńczego kontaktu wg. trzech grup', fontsize=20)
plt.xlabel('Czas rozmowy')
plt.ylabel('Proportion of Customers')
df['emp_var_rate'].value_counts()
plt.style.use('seaborn')
table=pd.crosstab(df['emp_var_rate'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Zmiana miejsca pracy vs Purchase', fontsize=20)
plt.xlabel('Wskaźnik stałości zatrudnienia')
plt.ylabel('Proportion of Customers')
Zaokrąglam wskaźnik zatrudnienia do wartości całkowitych
df['emp_var_rate'] = np.round(df['emp_var_rate'], decimals=0)
df['emp_var_rate'].head(4)
plt.style.use('seaborn')
table=pd.crosstab(df['emp_var_rate'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Zaokrąglony wskaźnik stałości zatrudnienia vs Purchase', fontsize=20)
plt.xlabel('Wskaźnik stałości zatrudnienia')
plt.ylabel('Proportion of Customers')
euribor3 mln: Korelacja 0.3
stawka 3-miesięczna euribor – wskaźnik dzienny (liczbowy)
Stopa referencyjna dla kredytów w Euro. Stopa dla porzyczek i kredytów, służy do konstrukcji algorytmu oprocentowania.
df.columns
df.dtypes
df['euribor3m'] = np.round(df['euribor3m'], decimals=1)
table=pd.crosstab(df['emp_var_rate'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('euribor3m vs Purchase', fontsize=20)
plt.xlabel('Wskaźnik euribor3m')
plt.ylabel('Proportion of Customers')
pdays:¶
pdays: liczba dni, które upłynęły od ostatniego kontaktu klienta z poprzedniej kampanii (numerycznie; 999 oznacza, że klient nie był wcześniej skontaktowano się)
df['pdays'].value_counts().plot(kind='bar')
table=pd.crosstab(df['pdays'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Liczba dni od ostatniego kontaktu vs Purchase', fontsize=20)
plt.xlabel('Liczba dni od ostatniego kontaktu')
plt.ylabel('Proportion of Customers')
nr_employed: liczba zatrudnionych: liczba pracowników – wskaźnik kwartalny (liczbowy)
table=pd.crosstab(df['nr_employed'],df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Liczba zatrudnionych vs Purchase', fontsize=20)
plt.xlabel('Liczba zatrudnionych')
plt.ylabel('Proportion of Customers')
Podsumowanie analizy zmiennych ciągłych
- Utworzyłem nową zmienną dyskretną AA_3Czasy_kontaktu – 3 przedziały czasu kontaktu
- Utworzyłem nową zmienną dyskretną AA5_Grup_wiekowych
- Zmieniłem ‘duration’ z sekund na minuty
- Zmieniłem liczenie dni w ‘previous’ jeden kontakt oznacza 1, dwa kontakty oznacza 2
- Utworzyłem nową kategorię ciągłą ‘AA_Średni czas kontaktu’
- Utworzyłem nową kategorię dyskretną “3Średni_czas_kontaktu”
- Zaokrągliłem ’emp_var_rate’
- Zaokrągliłem ‘euribor3m’
- Pozycje df[‘education’] -illiterate daje do unknown
WYKRES MACIERZOWY ZALEŻNOŚCI ZMIENNYCH CIĄGŁYCH
categorical_vars = df.describe(include=["object"]).columns
continuous_vars = df.describe().columns
continuous_vars
CORREL = df.corr().sort_values('y')
CORREL['y'].to_frame().sort_values('y')
kot = ["#c0c2ce", "#e40c2b"]
sns.pairplot(data=df[['duration', 'campaign','age','y']], hue='y', dropna=True, palette=kot)
duration: czas trwania ostatniego kontaktu
campaign: liczba kontaktów wykonanych podczas tej kampanii i dla tego klienta
5. Analiza wpływu niezależnych zmiennych dyskretnych na zmienną zależną
plt.style.use('seaborn')
table=pd.crosstab(df.job,df.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True, fontsize=15)
plt.title('Zawód vs Purchase', fontsize=20)
plt.xlabel('Zawód')
plt.ylabel('Proportion of Customers')
plt.savefig('mariral_vs_pur_stack')
sns.set(style="white")
fig, ax = plt.subplots(figsize=(15,8))
sns.countplot(x="job", hue='y',data=df, palette="Set1")
ax.set_title("Occupations of Potential Clients", fontsize=20)
ax.set_xlabel("Types of Jobs")
plt.show()
sns.set(font_scale=1.5)
kot = ["#c0c7ce", "#e40c2b"]
sns.factorplot('age','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Wiek klienta banku', fontsize=20)
sns.set(font_scale=1.5)
sns.factorplot('AA_Średni czas kontaktu','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Średni czas kontaktu w minutach', fontsize=20)
sns.set(font_scale=1.5)
sns.factorplot('pdays','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Liczba dni od ostaniego kontaktu (?)', fontsize=20)
sns.set(font_scale=1.5)
df['AA_pdays'] = df['pdays'].apply(lambda x: 0 if x == 999 else x)
sns.set(font_scale=1.5)
sns.factorplot('AA_pdays','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Liczba dni od ostaniego kontaktu', fontsize=20)
sns.factorplot('nr_employed','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Sytuacja na rynku pracy', fontsize=20)
sns.set(font_scale=1.5)
sns.factorplot('previous','job',hue='y',data=df , palette=kot, height=6, aspect=1.2)
import matplotlib.pyplot as plt
plt.title('Liczba kontaktów WYKRES NIC NIE POKAZUJE', fontsize=20)
previous: liczba kontaktów wykonanych przed tą kampanią i dla tego klienta (numerycznie)
df['previous'].value_counts()
KOT = df[df['previous']>2]
sns.factorplot('previous','job',hue='y',data=KOT , palette=kot, height=6, aspect=1.2)
sns.set(font_scale=1.5)
import matplotlib.pyplot as plt
plt.title('Liczba kontaktów powyżej dwóch', fontsize=20)
previous: liczba kontaktów wykonanych przed tą kampanią i dla tego klienta (numerycz
categorical_vars = df.describe(include=["object"]).columns
continuous_vars = df.describe().columns
# zmienne dyskretne
categorical_vars
# zmienne ciągłe
continuous_vars
data_dummies_df = pd.get_dummies(df, columns=categorical_vars, drop_first=True)
Każda wartość niecyfrowa teraz jest pokazana jako wariant tzn jeżeli była kolumna płeć zawierająca Kobieta, Mężczyzna to teraz są kolumny płeć: Kobieta, płeć: Mężczyzna, a w kolumnach jest albo 0 albo 1
data_dummies_df.sample(5)
data_dummies_df.columns
7. Macierz korelacji categorical_dummies ze zmienną wynikową
CORREL = data_dummies_df.corr().sort_values('y')
CORREL['y'].to_frame().sort_values('y')
plt.figure(figsize=(10,16))
CORREL['y'].plot(kind='barh', color='red')
plt.title('Korelacja ze zmienną wynikową', fontsize=20)
plt.xlabel('Poziom korelacji')
plt.ylabel('Zmienne nezależne ciągłe')
5.7 Badanie poziomu zbilansowania zbioru
Oversampling odbywa się na zbiorze treningowym więc najpierw trzeba podzielić zbiór na treningowy i testowy
y = data_dummies_df['y']
X = data_dummies_df.drop('y', axis=1)
from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(X,y, test_size=0.33, stratify = y, random_state = 148)
print ('Zbiór X treningowy: ',Xtrain.shape)
print ('Zbiór X testowy: ', Xtest.shape)
print ('Zbiór y treningowy: ', ytrain.shape)
print ('Zbiór y testowy: ', ytest.shape)
Analiza poziomu zbilansowania zmiennej wynikowej
df.y.value_counts(dropna = False, normalize=True)
print("ytrain = 0: ", sum(ytrain == 0))
print("ytrain = 1: ", sum(ytrain == 1))
Proporcja = sum(ytrain == 0) / sum(ytrain == 1)
Proporcja = np.round(Proporcja, decimals=0)
Proporcja = Proporcja.astype(int)
Proporcja
Na jedną daną sybskrypcje przypada 8 nieprzedłużonych subskrypcji. Powiększamy liczbę próbek niezależnych.
ytrain_pos_OVSA = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0)
ytrain_pos_OVSA.count()
Teraz wprowadzamy nowe, dodatkowe zmienne 1 do zbioru treningowego.
Xtrain_pos_OVSA = pd.concat([Xtrain.loc[ytrain==1, :]] * Proporcja, axis = 0)
ytrain_pos_OVSA.count()
ytrain_OVSA = pd.concat([ytrain, ytrain_pos_OVSA], axis = 0).reset_index(drop = True)
Xtrain_OVSA = pd.concat([Xtrain, Xtrain_pos_OVSA], axis = 0).reset_index(drop = True)
print("ilość elementów w zbiorze Xtrain: ", Xtrain.age.count())
print("ilość elementów w zbiorze Xtrain_OVSA: ", Xtrain_OVSA.age.count())
print("ilość elementów w zbiorze ytrain: ", ytrain.count())
print("ilość elementów w zbiorze ytrain_OVSA: ", ytrain_OVSA.count())
Model regresji logistycznej po oversampling
from sklearn import model_selection
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
Parameteres = {'C': np.power(10.0, np.arange(-3, 3))}
LR = LogisticRegression(warm_start = True)
LR_Grid = GridSearchCV(LR, param_grid = Parameteres, scoring = 'roc_auc', n_jobs = 5, cv=2)
LR_Grid.fit(Xtrain_OVSA, ytrain_OVSA)
Ocena modelu regresji logistycznej po oversampling
ypred_OVS = LR_Grid.predict(Xtest)
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics
co_matrix = metrics.confusion_matrix(ytest, ypred_OVS)
co_matrix
print(classification_report(ytest, ypred_OVS))
print("Accuracy: ",np.round(metrics.accuracy_score(ytest, ypred_OVS), decimals=2))
print("Precision: ",np.round(metrics.precision_score(ytest, ypred_OVS), decimals=2))
print("Recall: ",np.round(metrics.recall_score(ytest, ypred_OVS), decimals=2))
print("F1 score: ",np.round(metrics.f1_score(ytest, ypred_OVS), decimals=2))
Model ma doskonałe własności dla przewidywania zakupu kredytu, pokazuje to recall y=1
Poszukiwanie najlepszych predyktorów w modalu regresji logistycznej RFE
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
rfe = RFE(logreg, 20)
rfe = rfe.fit(Xtrain_OVSA, ytrain_OVSA.values.ravel())
print(rfe.support_)
print(rfe.ranking_)
Xtrain_OVSA.columns
rfe.ranking_
rfe.support_
position = list(map(float, rfe.ranking_))
support = list(map(float, rfe.support_))
name = list(X.columns)
s1=pd.Series(name,name='name')
s2=pd.Series(support,name='support')
s3=pd.Series(position,name='position')
RFE_list = pd.concat([s1,s2,s3], axis=1)
RFE_list
Najlepsze zmienne predykcyjne według RFE
RFE_list[RFE_list['support']==1]
MODEL BEZ ZBILANSOWANIA ZBIORÓW
Skalowanie standardowe tylko dla wartości dyskretnych
Wybieram kolumny tekstowe, dyskretne, do głębszej analizy. Lepsze było to wybieranie dyskretne i ciągłe.
categorical_vars
df[categorical_vars] = df[categorical_vars].apply(LabelEncoder().fit_transform)
df_num = df[categorical_vars].head()
y = df['y']
X = df.drop('y', axis=1)
from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(X,y, test_size=0.33, stratify = y, random_state = 148)
wielkości zbiorów:
print ('Zbiór X treningowy: ',Xtrain.shape)
print ('Zbiór X testowy: ', Xtest.shape)
print ('Zbiór y treningowy: ', ytrain.shape)
print ('Zbiór y testowy: ', ytest.shape)
Xtrain.head(4)
Logistic Regression na zmiennych zcyfryzowanych bez oversampling
from sklearn.model_selection import GridSearchCV
Parameteres = {'C': np.power(10.0, np.arange(-3, 3))}
LR = LogisticRegression(warm_start = True)
LR_Grid = GridSearchCV(LR, param_grid = Parameteres, scoring = 'roc_auc', n_jobs = 5, cv=2)
LR_Grid.fit(Xtrain, ytrain)
Blok oceny jakości modelu Logistic Regression¶
# Podstawienie do wzoru
ypred = LR_Grid.predict(Xtest)
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics
co_matrix = metrics.confusion_matrix(ytest, ypred)
co_matrix
print(classification_report(ytest, ypred))
print("Accuracy: ",np.round(metrics.accuracy_score(ytest, ypred), decimals=2))
print("Precision: ",np.round(metrics.precision_score(ytest, ypred), decimals=2))
print("Recall: ",np.round(metrics.recall_score(ytest, ypred), decimals=2))
print("F1 score: ",np.round(metrics.f1_score(ytest, ypred), decimals=2))
Słabo wyszło recall a właśnie o recall dla 1 nam chodzi
Model nie jest dobry!¶
df.y.value_counts(dropna = False, normalize=True)
from sklearn.linear_model import LogisticRegression
print("ytrain = 0: ", sum(ytrain == 0))
print("ytrain = 1: ", sum(ytrain == 1))
Proporcja = sum(ytrain == 0) / sum(ytrain == 1)
Proporcja = np.round(Proporcja, decimals=0)
Proporcja = Proporcja.astype(int)
Proporcja
Na jedną daną sybskrypcje przypada 8 nieprzedłużonych subskrypcji. Powiększamy liczbę próbek niezależnych.
ytrain_pos_OVS = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0)
ytrain_pos_OVS.count()
Ilość zmiennych wynikowych: (1) zwiększyła się do liczby 24872 Mamy już wektor zmiennych wynikowych y, teraz trzeba zwiększyć liczbę zmiennych niezależnych
Powiększamy liczbę próbek zmiennych niezależnych X
Xtrain_pos_OVS = pd.concat([Xtrain.loc[ytrain==1, :]] * Proporcja, axis = 0)
Xtrain_pos_OVS.age.count()
Powiękzyliśmy ilość zmiennych gdzie wynik przedłużenia subskrypcji jest równy 1. Teraz mamy tą samą liczbę wierszy zmiennych wynikowych i zmiennych niezależnych.
Teraz wprowadzamy nowe, dodatkowe zmienne 1 do zbioru treningowego.
ytrain_OVS = pd.concat([ytrain, ytrain_pos_OVS], axis = 0).reset_index(drop = True)
Xtrain_OVS = pd.concat([Xtrain, Xtrain_pos_OVS], axis = 0).reset_index(drop = True)
Sprawdzamy ilość wierszy w zbiorach przed i po oversampling
print("ilość elementów w zbiorze Xtrain: ", Xtrain.age.count())
print("ilość elementów w zbiorze Xtrain_OVS: ", Xtrain_OVS.age.count())
print("ilość elementów w zbiorze ytrain: ", ytrain.count())
print("ilość elementów w zbiorze ytrain_OVS: ", ytrain_OVS.count())
Teraz podstawiamy nowy zbiór testowy oversampling do siatki grid według tej same formuły, którą użyliśmy wcześniej.
Parameteres = {'C': np.power(10.0, np.arange(-3, 3))}
LR = LogisticRegression(warm_start = True)
LR_Grid = GridSearchCV(LR, param_grid = Parameteres, scoring = 'roc_auc', n_jobs = 5, cv=2)
LR_Grid.fit(Xtrain_OVS, ytrain_OVS)
ypred_OVS = LR_Grid.predict(Xtest)
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics
co_matrix = metrics.confusion_matrix(ytest, ypred_OVS)
co_matrix
print(classification_report(ytest, ypred_OVS))
print("Accuracy: ",np.round(metrics.accuracy_score(ytest, ypred_OVS), decimals=2))
print("Precision: ",np.round(metrics.precision_score(ytest, ypred_OVS), decimals=2))
print("Recall: ",np.round(metrics.recall_score(ytest, ypred_OVS), decimals=2))
print("F1 score: ",np.round(metrics.f1_score(ytest, ypred_OVS), decimals=2))
Wynik modelu Logistic Regression przed Oversampling
- Accuracy: 0.9
- Precision: 0.63
- Recall: 0.35
- F1 score: 0.45
Wynik kodelu po Oversampling
- Accuracy: 0.84
- Precision: 0.41
- Recall: 0.88
- F1 score: 0.56
Random Forest Classifier bez oversampling
from sklearn.ensemble import RandomForestClassifier
forestVC = RandomForestClassifier (random_state = 1,
n_estimators = 750,
max_depth = 15,
min_samples_split = 5, min_samples_leaf = 1)
modelF = forestVC.fit(Xtrain, ytrain)
y_predF = modelF.predict(Xtest)
ypred = modelF.predict(Xtest)
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics
co_matrix = metrics.confusion_matrix(ytest, ypred)
co_matrix
print(classification_report(ytest, ypred))
print("Accuracy: ",np.round(metrics.accuracy_score(ytest, ypred), decimals=2))
print("Precision: ",np.round(metrics.precision_score(ytest, ypred), decimals=2))
print("Recall: ",np.round(metrics.recall_score(ytest, ypred), decimals=2))
print("F1 score: ",np.round(metrics.f1_score(ytest, ypred), decimals=2))
Random Forest Classifier z oversampling
ytrain_pos_OVS = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0)
ytrain_pos_OVS.count()
Ilość zmiennych wynikowych: (1) zwiększyła się do liczby 24872 Mamy już wektor zmiennych wynikowych y, teraz trzeba zwiększyć liczbę zmiennych niezależnych
Powiększamy liczbę próbek zmiennych niezależnych X
Xtrain_pos_OVS = pd.concat([Xtrain.loc[ytrain==1, :]] * Proporcja, axis = 0)
Xtrain_pos_OVS.age.count()
ytrain_OVS = pd.concat([ytrain, ytrain_pos_OVS], axis = 0).reset_index(drop = True)
Xtrain_OVS = pd.concat([Xtrain, Xtrain_pos_OVS], axis = 0).reset_index(drop = True)
print("ilość elementów w zbiorze Xtrain: ", Xtrain.age.count())
print("ilość elementów w zbiorze Xtrain_OVS: ", Xtrain_OVS.age.count())
print("ilość elementów w zbiorze ytrain: ", ytrain.count())
print("ilość elementów w zbiorze ytrain_OVS: ", ytrain_OVS.count())
Teraz podstawiamy nowy zbiór testowy oversampling do siatki grid według tej same formuły, którą użyliśmy wcześniej.
forestVC = RandomForestClassifier (random_state = 1,
n_estimators = 750,
max_depth = 15,
min_samples_split = 5, min_samples_leaf = 1)
modelF = forestVC.fit(Xtrain_OVS, ytrain_OVS)
ypred = modelF.predict(Xtest)
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics
co_matrix = metrics.confusion_matrix(ytest, ypred)
co_matrix
print(classification_report(ytest, ypred))
print("Accuracy: ",np.round(metrics.accuracy_score(ytest, ypred), decimals=2))
print("Precision: ",np.round(metrics.precision_score(ytest, ypred), decimals=2))
print("Recall: ",np.round(metrics.recall_score(ytest, ypred), decimals=2))
print("F1 score: ",np.round(metrics.f1_score(ytest, ypred), decimals=2))
Wynik modelu Random Forest Classifier przed Oversampling
- Accuracy: 0.91
- Precision: 0.64
- Recall: 0.51
- F1 score: 0.57
Wynik kodelu po Oversampling
- Accuracy: 0.89
- Precision: 0.52
- Recall: 0.81
- F1 score: 0.63
Cel zadania
- Zaproponują grupy klientów (segmenty), dla których Dep. Marketingu opracuje odrębne skrypty rozmów i argumenty sprzedażowe.
- Przygotują model predykcyjny, na podstawie którego do kolejnej akcji sprzedażowej zostaną wyselekcjonowani klienci cechujący się najwyższym prawdopodobieństwem kupna produktu.
ODPOWIEDŹ NA PIERWSZE i DRUGIE PYTANIE
Na podstawie Modelu regresji logistycznej o doskonałych właściwościach predykcyjnych:
- Accuracy: 0.84
- Precision: 0.41
- Recall: 0.9
- F1 score: 0.57
precision recall f1-score support 0 0.98 0.84 0.91 12062 1 0.41 0.90 0.57 1531
Algorytm eliminacji niezależnych zmiennych nieistotnych RFE (Recursive Feature Elimination) pozostawił następujące zmienne jako mające znaczenie w procesie planowania. Przerdstawione zmienne według algotymu są istotnymi predyktorami dla zmiennej endogenicznej.
RFE_list[RFE_list['support']==1]
Miesiące przewidywalne przez model
- Sprzedaż prowadzona w miesiącach: grudzień, marzec, maj, wrzesień, październik
Klienci przewidywalni przez model
- Klienci według zawodów: blue-collar, retired, student, unemployed, self-employed (unknown odrzucamy)
- Klienci według wykształcenia: university.degree (tylko tacy są przewidywalni przez model)
Czynniki makroekonomiczne ważne do podjęcia decyzji o wzięciu kredytu:
- euribor3m: stawka 3-miesięczna euribor – wskaźnik dzienny (liczbowy) ważny składnik tworzenia algorytmu odestkowego
- emp_var_rate: wskaźnik zmienności zatrudnienia – wskaźnik kwartalny (liczbowy) czyli ile razy klient zmieniał pracę
Wskazówki dla pracowników sprzedaży banku
- previous: liczba kontaktów wykonanych przed tą kampanią i dla tego klienta (numerycznie)
- AA_Średni czas kontaktu
- AA_3Czasy_kontaktu_Rozmowa poniżej 3 minut
- AA_3Czasy_kontaktu_Rozmowa powyżej 6 minut
Sukces rodzi sukces
- poutcome_success – sukces ostatniej kampanii marketingowej, poutcome: wynik poprzedniej kampanii marketingowej (kategorycznie: „porażka”, „nieistniejąca”, „sukces”)
W TYM KIERUNKU POWINNY BYĆ PROWADZONE BADANIA
Obszary analiz dla działu marketingu zostały wskazane w odpowiedzi powyżej.
Ogromne możliwości metod badawczych zostały przedstawione w początkowych rozdziałach, szczególnie w rozdziale:
- Analiza wpływu niezależnych zmiennych dyskretnych na zmienną zależną
oraz
- WYKRES MACIERZOWY ZALEŻNOŚCI ZMIENNYCH CIĄGŁYCH
Daje to ogromną potencjalną liczbę konfiguracji i możliwość poboru danych z innych powiązanych baz!
Artificial Intelligence w Marketingu Bankowym.