Въведение в проекта

Ние живеем и реагираме с другите въз основа на нашите и техните взаимодействия. Както в днешно време, дигитализацията заема огромно място в ежедневието ни, така че по-голямата част от комуникацията ни се извършва чрез цифрови устройства. Вярвам, че би било трудно да си спомните последното съобщение, написано на ръка, което сте получили от вашия партньор. По този начин повечето компании заменят класическия начин с модерни технологии като уеб и мобилни приложения, за да бъдат по-обширни. Въпросът е как да разберете дали клиентът ще бъде интерактивен положително или не. Случаят на проекта беше мобилното приложение за награди Starbucks.

Подобряването на технологията позволява AI и ML да се използват оптимално. Така че на Udacity беше предложено този случай да бъде основен проект в Data Scientist Nanodegree. Също така много благодаря на Starbucks за публикуването на набора от данни за разработване на решение по този случай.

Този набор от данни съдържа симулирани данни, които имитират поведението на клиентите в мобилното приложение за награди на Starbucks. Веднъж на всеки няколко дни Starbucks изпраща оферта до потребителите на мобилното приложение. Офертата може да бъде просто реклама за напитка или действителна оферта, като отстъпка или BOGO (купете едно, вземете едно безплатно). Някои потребители може да не получават оферти през определени седмици. Освен това този набор от данни е опростена версия на истинското приложение на Starbucks, тъй като базовият симулатор има само един продукт, докато Starbucks всъщност продава десетки продукти. Данните са представени в три файла:

  • portfolio.json: съдържащ идентификатори на оферти и метаданни за всяка оферта (продължителност, тип, трудност, награда, продължителност и канали)
  • profile.json: демографски данни за всеки клиент като (възраст, пол, доход и дата на членство)
  • transcript.json: записи за транзакции, получени оферти, прегледани оферти и завършени оферти

Постановка на проблема

Съгласно въпроса за нашия случай, разработих модел, който прогнозира, че потребителите ще изпълнят офертата или не ще подкрепят маркетинговия екип за избор на клиенти с висок потенциал.

Следните стъпки, които използвах за разработване на модел, са от решаващо значение:

1- Изследване на данни

2- Почистване на данни и EDA

3- Изграждане на модели

Всяка стъпка има подстъпки за по-дълбоко навлизане в данните за разбиране на всички основни перспективи. Следните подстъпки обаче ще бъдат показани на един набор от данни като пример. За повече подробности, моля, посетете бележника щракнете тук в GitHub.

1- Изследване на данни

Тази стъпка включва показване на зададените редове, колко липсващи стойности, обобщената статистика, типовете данни на характеристиките и формата във всеки файл с данни. Следователно първоначалните модели и важните характеристики ще бъдат известни и ще определят оптималната визуализация за всеки набор от данни за колона. Също така разбирането на високо ниво на данните и откриването на отклонения.

Първо, файловете трябва да бъдат анализирани във формат на рамка с данни.

# read in the json files
portfolio = pd.read_json('data/portfolio.json', orient='records', lines=True)
profile = pd.read_json('data/profile.json', orient='records', lines=True)
transcript = pd.read_json('data/transcript.json', orient='records', lines=True)

1.1 Показване на данни

Покажете първите пет реда от всеки набор от данни

portfolio.head()

1.2 Пълнота на данните

Покажете колко липсват стойности във всеки набор от данни

portfolio.isnull().sum()

В резултат на това в този набор от данни няма липсваща стойност.

1.3 Описателна статистика на данните

Показване на обобщената статистика за всеки набор от данни

portfolio.describe()

Според обобщената статистика portfolioима средна продължителност на офертата от 6,5 дни

1.4 Типове данни

Покажете типовете данни на характеристиките във всеки набор от данни

portfolio.info()

Всички колони в този набор от данни са в правилния формат.

1.5 Форма на данни

Покажете формата на всеки набор от данни

portfolio.shape

2- Почистване на данни и EDA

За да се създаде полезен набор от данни, етапът на предварителна обработка като почистване е задължителен. По този начин следващите подстъпки са получаване на речника на офертата и ID на лицата, коригиране на неправилните стойности на колони, попълване на липсващите стойности и премахване на дублираните редове. След това визуализирайте набора от данни, за да намерите прозрения.

2.1 Поправете неправилните стойности на колони

# get value of channels column
channels_data = portfolio.channels.values
# cast nd array to list
channels_list = list(itertools.chain(*channels_data))
# get a unique list of channels values
cleaned_channels_list = list(set(channels_list))
for item in cleaned_channels_list:
portfolio.insert(2,'is_'+item+'_channel',portfolio.channels.apply(lambda x: 1 if item in x else 0))
# drop a column
portfolio.drop(['channels'],axis=1,inplace=True)
portfolio.head()

2.2 Попълнете липсващите стойности

# show the first 10 rows of nulls in gender column
profile[profile.gender.isna()].head(10)

Съгласно по-горе, липсващите стойности в пола и дохода са свързани с необичайни стойности във възрастта, която е 118. По този начин всички тези стойности се премахват от набора от данни.

2.3 Премахване на дублираните редове

len(portfolio[portfolio.duplicated('id')])

Няма дублирани редове в набора от данни за портфолио.

2.4 Визуализация на данни

Помощна функция за визуализиране на лентовата графика

# helper function for barplot
def barplot(data, title, rotation=0, width=8, height=4):
    """
    INPUT:
    data - (Dataframe) a dataset
    title - (Str) a title of plot
    rotation - (int) a degree of X-axis ticklabels rotation
    width - (float) a width of figure
    height - (float) a height of figure
OUTPUT:
    No outputs but it will show the bar-plot
    """
    
    # sort data in descending order
    data = data.sort_values(by=data.columns[0],ascending=False)
    
    # set the width and height of figure
    plt.figure(figsize=(width, height))
    
    # set the title of plot
    plt.title(title)
    
    # the degree of x-ticklabels rotation
    plt.xticks(rotation=rotation)
    
    # set the data of x and y axes
    plt.bar(data.index, data.iloc[:,0].values)    
    
    # Save the plot as an image
    plt.savefig('fig\\'+title+'.jpg', bbox_inches = 'tight')
    
    plt.show();

Използвал съм това за следните парцели:

data = portfolio[['is_email_channel','is_mobile_channel','is_social_channel','is_web_channel']].sum().to_frame()
barplot(data=data, title='Count of Offer per Channel')

Според горната графика всички оферти са изпратени по имейл канал и най-малко използваният канал е социалният. Следователно имейлът е най-подходящият канал за споделяне на оферти.

data = portfolio.groupby(by=['offer_type'])[['duration']].mean()
barplot(data=data, title='Duration Average of Offer per Type')

Както е показано по-горе, средната продължителност зависи от типа оферта, тъй като отстъпката има най-високата средна стойност, която е около 8,5 дни, докато BOGO има 6 дни.

data = profile.groupby(by=['gender'])[['id']].count()
barplot(data=data, title='Count of User per Gender')

По отношение на лентата, мъжът е най-големият пол на регистрирано лице от повече от 8 000 потребители, докато жената е на второ място от около 6 000 потребители. Въпреки това имаше потребители, които избраха „Други“ с около 200.

data = transcript.groupby(by=['offer_id'])[['is_offer_completed']].sum()
barplot(data=data, title='Count of Completed Offers per Offer', width=12, height=4, rotation=75)

Според горната графика, броят на завършените оферти надхвърли 2800 най-малко на оферта, с изключение на две оферти, които никога нямат завършени оферти. Въпреки това първите 2 оферти са толкова близки една до друга с около 100 като разлика.

Другите визуализации са:

data = profile.groupby(pd.Grouper(key='became_member_on',axis=0,freq='M'))[['id']].count()
plt.figure(figsize=(8,4))
plt.title('Count of New User per Month')
plt.savefig('fig\\Count of New User per Month.jpg', bbox_inches = 'tight')
plt.plot(data.index,data.id);

Линейният график показва, че като цяло броят на новите потребители нараства всяка година. Освен това в средата на 2015 г. имаше първия скок на броя от около 60 на 250 до края на годината. Най-много нови потребители има през втората половина на 2017 г. със 760 веднъж и 800 два пъти.

След това обединих тези набори от данни в един набор от данни, за да намеря повече прозрения. Следните стъпки представляват процеса на сливане:

  • Съпоставете лице и оферта с цифров идентификатор
# map person and offer IDs to their numeric version
portfolio['id'] = portfolio.id.apply(lambda x: list(offer_ids_dict.values()).index(x))
profile['id'] = profile.id.apply(lambda x: list(profile_ids_dict.values()).index(x))
transcript['person'] = transcript.person.apply(lambda x: list(profile_ids_dict.values()).index(x))
transcript['offer_id'] = transcript.offer_id.apply(lambda x: list(offer_ids_dict.values()).index(x))
  • Унифицирайте името на колоната
profile = profile.rename(columns={'id':'person_id'})
profile.head()

portfolio = portfolio.rename(columns={'id':'offer_id'})
portfolio.head()

transcript = transcript.rename(columns={'person':'person_id'})
transcript.head()

Всички ключови колони вече са обединени, нека ги обединим.

  • Обединете набори от данни
transcript_profile = pd.merge(transcript,profile,on='person_id')
raw_df = pd.merge(transcript_profile,portfolio,on='offer_id')
raw_df.drop(['reward_y'],axis=1,inplace=True)
raw_df.head()

След това правя визуализация за обединения набор от данни.

fig = plt.figure(figsize=(8,4))
sns.countplot(x='offer_type',hue='gender',data=raw_df)
plt.title('Count of Gender per Offer Type')
plt.savefig('fig\\Count of Gender per Offer Type.jpg', bbox_inches = 'tight')
plt.show();

Като показан график, най-изпращаният тип оферта е отстъпка, където открихме най-голям брой от всеки тип пол. Освен това мъжките достигат над 10 000, което е голям брой в сравнение с други видове предложения. Въпреки това, женската има същото поведение в типовете отстъпка и BOGO, за разлика от мъжете.

data = raw_df.groupby(by=['is_offer_completed'])[['time']].sum()
plt.figure(figsize=(12,4))
plt.title('Sum of The Time Completed vs Incompleted Offers')
plt.bar(list(map(str,data.index)),data.iloc[:,0].values)
plt.xticks([0,1],['Incompleted','Completed'])
plt.savefig('fig\\Sum of The Time Completed vs Incompleted Offers.jpg', bbox_inches = 'tight')
plt.show();

По отношение на горния сюжет завършените оферти са изразходвали двойно повече време от незавършените оферти. С други думи, потребителите предпочитат да отделят време, за да завършат, като например да прочетат подробностите и да попитат приятелите си за самата оферта, ако наистина са заинтересувани.

# change the format from yyyy-mm-dd to yyyy
raw_df['became_member_on'] = pd.DatetimeIndex(raw_df['became_member_on']).year
fig = plt.figure(figsize=(12,4))
sns.countplot(x='became_member_on',hue='gender',data=raw_df)
plt.title('Count of Gender per Year')
plt.savefig('fig\\Count of Gender per Year.jpg', bbox_inches = 'tight')
plt.show();

Преградете към показаната графика, полът на новите потребители най-вероятно е мъжки като цяло през последната година. През 2015 г. броят на новите потребители от женски пол се е увеличил с около 2000 в сравнение с 2014 г. През следващите години мъжът е бил лидер с изключение на 2016 г., а най-големият брой нови потребители от мъжки пол е между 9700 и 9800 през 2017 г. друг тип пол се появи от 2015 г. насам с малък брой.

3. Изграждане на модели

Тази част от проекта включва еднократно кодиране, разделяне на влак-тест, данни за мащабиране, разработване на модели и сравнение.

3.1 One-Hot кодиране

raw_df['is_male'] = raw_df.gender.apply(lambda x: 1 if x == 'M' else 0)
raw_df['is_female'] = raw_df.gender.apply(lambda x: 1 if x == 'F' else 0)
raw_df['is_discount'] = raw_df.offer_type.apply(lambda x: 1 if x == 'discount' else 0)
raw_df['is_bogo'] = raw_df.offer_type.apply(lambda x: 1 if x == 'bogo' else 0)
# because the most of new users are after 2015, we decide to map became_member_on into 1's if year after 2015 or 0's
raw_df['is_after2015'] = raw_df.became_member_on.apply(lambda x: 1 if x > 2015 else 0)
# drop the used columns in one-hot encoding
cleaned_raw_df = raw_df.drop(['person_id','gender','offer_type','offer_id','became_member_on'],axis=1)
cleaned_raw_df.head()

3.2 70–30 Трениране/Тестово разделение

# select all features for dataset
X = cleaned_raw_df.iloc[:,1:]
# select the target feature for dataset
y = cleaned_raw_df.iloc[:,0]
# define the train and test datasets with the test_size 0.3 and random state 5
X_train, X_test, y_train,y_test = train_test_split(X,y,test_size=0.3, random_state=5)

3.3 Мащабиране на тренировъчни и тестови данни

# Do Scaling for data
ss = StandardScaler()
Xs_train = ss.fit_transform(X_train)
Xs_test = ss.transform(X_test)

Данните са готови за моделиране след еднократно горещо кодиране и мащабиране за подобряване на процеса на обучение на модела.

Метрика

В частта за моделиране има много показатели, които могат да се използват за оценка на ефективността на модела, но аз използвах точността за сравнение между моделите.

Точност: измервайте правилните прогнози спрямо общите прогнози в класификацията. Това е най-често срещаният начин за оценка на модела, ако целта ви е балансирана. В противен случай трябва да се използват другите начини като f-резултат и прецизност.

Точност = истински положителни резултати + фалшиви положителни резултати / истински положителни резултати + фалшиви положителни резултати + истински отрицателни резултати + фалшиви отрицателни резултати

Ето помощната функция за изграждане на модел

def build_model(model,model_name,y_test=y_test,y_train=y_train):
    """
    INPUT:
    model - (Dataframe) a model
    model_name - (Str) a name of model
  
    OUTPUT:
    accuracy_score_train - (int) an accuracy score for train dataset
    accuracy_score_test - (int) an accuracy score for test dataset
    """
    
    # Fit train data into model
    model.fit(Xs_train, y_train)
# Predict y train and test
    y_pred_train = model.predict(Xs_train)
    y_pred_test = model.predict(Xs_test)
cm = confusion_matrix(y_test, y_pred_test)
    plt.figure(figsize=(5, 3))
    sns.heatmap(cm, annot=True, fmt='d')
    plt.title('Test Confusion Matrix of - '+model_name)
    plt.savefig('fig\\Test Confusion Matrix of - '+model_name+'.jpg', bbox_inches = 'tight')
    plt.show()
return accuracy_score(y_train,y_pred_train), accuracy_score(y_test,y_pred_test)

Разработените модели са:

Логистична регресия

# instantiate Logistic Regression Model
lr = LogisticRegression()
# build the model and get accuracy score of train and test dataset
accuracy_score_train, accuracy_score_test = build_model(lr,"LR")
# define the dictionaries of train and test scores
train_score = {}
test_score = {}
train_score['LR'] = accuracy_score_train
test_score['LR'] = accuracy_score_test

K-най-близки съседи

# instantiate KNN Model
knn = KNeighborsClassifier(n_neighbors=7)
# build the model and get accuracy score of train and test dataset
accuracy_score_train, accuracy_score_test = build_model(knn,"KNN")
train_score['KNN'] = accuracy_score_train
test_score['KNN'] = accuracy_score_test

Дърво на решенията

# Instantiate DT Model
dt = DecisionTreeClassifier()
# build the model and get accuracy score of train and test dataset
accuracy_score_train, accuracy_score_test = build_model(dt,"DT")
train_score['DT'] = accuracy_score_train
test_score['DT'] = accuracy_score_test

Поддържаща векторна машина

# Instantiate SVC Model
svc = SVC()
# build the model and get accuracy score of train and test dataset
accuracy_score_train, accuracy_score_test = build_model(svc,"SVC")
train_score['SVC'] = accuracy_score_train
test_score['SVC'] = accuracy_score_test

Случайна гора

# Instantiate RF Model
rf = RandomForestClassifier()
# build the model and get accuracy score of train and test dataset
accuracy_score_train, accuracy_score_test = build_model(rf,"RF")
train_score['RF'] = accuracy_score_train
test_score['RF'] = accuracy_score_test

Резюме на модела

# Define dataframes has the all model with their train and test scores
score_df = pd.DataFrame({'Models':train_score.keys(),'Train Score':train_score.values(),'Test Score':test_score.values()})
score_df.sort_values('Train Score',ascending=False).\
plot(kind='bar',x='Models',y=['Train Score','Test Score'],title='Train/Test Accuracy',ylabel='Accuracy');
plt.savefig('fig\\Train_Test Accuracy.jpg', bbox_inches = 'tight')

Съгласно горната графика, разработихме модел, за да предвидим, че офертата ще бъде изпълнена или не от много модели. От гледна точка на точността всички модели се представят добре, тъй като най-ниската точност беше 84,4%, както и „Дървото на решенията“, „Случайната гора“ и „K-най-близките съседи“ имат проблем с пренастройването. С други думи, тези модели се представят добре в набор от влакове, докато в набор от тестове произвеждат повече грешки в набор от тестове (невидими данни). По този начин ще направим настройване на параметри за „Дървото на решенията“, ще опитаме различни размери на разделяне на тестове за влак, за да се справим с този проблем и ще направим избор на функции, за да намерим оптималния набор от функции.

Заключение

Starbucks е добре позната компания в индустрията за храни и напитки, по-специално в кафето. За да обобщим невероятните констатации, броят на клиентите в приложението се е увеличил бързо през 2016 и 2017 г. Също така, потребителите, които прекарват повече време в офертата, е по-вероятно да изпълнят офертата. Повечето нови или стари потребители са мъже и те са интерактивни с оферти за отстъпки главно, докато жените имат същото отношение към отстъпките и типовете BOGO. Освен това имейлът е най-ефективният канал за изпращане на оферти. Офертата за отстъпка се предлага за дълъг период от време в сравнение с другите видове.

В частта за машинно обучение разработих модел, който предвижда, че потребителите ще изпълнят офертата или не ще подкрепят маркетинговия екип за избор на клиенти с висок потенциал. Цената за намиране на повлиян клиент ще бъде намалена.

Следващата стъпка ще бъде разработването на интерактивен уебсайт, който да предскаже дали даден потребител ще завърши офертата или не, за да се възползва от модела.