Spaces:
Runtime error
Runtime error
| import pandas as pd | |
| from sklift.metrics import uplift_at_k, uplift_by_percentile, qini_auc_score, qini_curve, uplift_curve | |
| from sklift.viz import plot_qini_curve, plot_uplift_curve | |
| import streamlit as st | |
| import tools | |
| # загрузим датасет | |
| dataset, target, treatment = tools.get_data() | |
| # загрузим предикты моделей | |
| ct_cbc = pd.read_csv('src/model_predictions/catboost/ct_cbc.csv', index_col='Unnamed: 0') | |
| sm_cbc = pd.read_csv('src/model_predictions/catboost/sm_cbc.csv', index_col='Unnamed: 0') | |
| tm_dependend_cbc = pd.read_csv('src/model_predictions/catboost/tm_dependend_cbc.csv', index_col='Unnamed: 0') | |
| tm_independend_cbc = pd.read_csv('src/model_predictions/catboost/tm_independend_cbc.csv', index_col='Unnamed: 0') | |
| tm_rfc = pd.read_csv('src/model_predictions/random_forest/tm_rfc.csv', index_col='Unnamed: 0') | |
| sm_xgboost = pd.read_csv('src/model_predictions/xgboost/sm_xgb.csv', index_col='Unnamed: 0') | |
| # загрузим данные | |
| data_train_index = pd.read_csv('data/data_train_index.csv') | |
| data_test_index = pd.read_csv('data/data_test_index.csv') | |
| treatment_train_index = pd.read_csv('data/treatment_train_index.csv') | |
| treatment_test_index = pd.read_csv('data/treatment_test_index.csv') | |
| target_train_index = pd.read_csv('data/target_train_index.csv') | |
| target_test_index = pd.read_csv('data/target_test_index.csv') | |
| # фиксируем выборки, чтобы результат работы ML был предсказуем | |
| data_train = dataset.loc[data_train_index['0']] | |
| data_test = dataset.loc[data_test_index['0']] | |
| treatment_train = treatment.loc[treatment_train_index['0']] | |
| treatment_test = treatment.loc[treatment_test_index['0']] | |
| target_train = target.loc[target_train_index['0']] | |
| target_test = target.loc[target_test_index['0']] | |
| st.title('Uplift lab') | |
| st.markdown( | |
| """ | |
| #### Рассмотрим применение одного из подходов прогнозирования _uplift_. | |
| Данные для примера взяты из [_The MineThatData E-Mail Analytics And Data Mining Challenge_](https://blog.minethatdata.com/2008/03/minethatdata-e-mail-analytics-and-data.html) | |
| Этот набор данных содержит 42 693 строк с данными клиентов, которые в последний раз совершали покупки в течение двенадцати месяцев. | |
| Из данных уже отделена тестовая выборка в виде 30% записей клиентов, так что данных в предоставленной выборке будет меньше. | |
| Среди клиентов была проведена рекламная кампания с помощью _email_ рассылки: | |
| - 1/2 клиентов были выбраны случайным образом для получения электронного письма, рекламирующего женскую продукцию; | |
| - С оставшейся 1/2 коммуникацию не проводили. | |
| Для каждого клиента из выборки замерили факт перехода по ссылке в письме, факт совершения покупки и сумму трат за | |
| две недели, следующими после получения письма. | |
| Пример данных приведен ниже. | |
| """ | |
| ) | |
| refresh = st.button('Обновить выборку') | |
| title_subsample = data_train.sample(7) | |
| if refresh: | |
| title_subsample = data_train.sample(7) | |
| st.dataframe(title_subsample, width=700) | |
| st.write(f"Всего записей: {data_train.shape[0]}") | |
| st.write('Описание данных') | |
| st.markdown( | |
| """ | |
| | Колонка | Обозначение | | |
| |-------------------|------------------------------------------------------------------------| | |
| | _recency_ | Месяцев с момента последней покупки | | |
| | _history_segment_ | Классификация клиентов в долларах, потраченных в прошлом году | | |
| | _history_ | Фактическая стоимость в долларах, потраченная в прошлом году | | |
| | _mens_ | Флаг 1/0, 1 = клиент приобрел мужские товары в прошлом году | | |
| | _womens_ | Флаг 1/0, 1 = клиент приобрел женские товары в прошлом году | | |
| | _zip_code_ | Классифицирует почтовый индекс как городской, пригородный или сельский | | |
| | _newbie_ | Флаг 1/0, 1 = Новый клиент за последние двенадцать месяцев | | |
| | _channel_ | Описывает каналы, через которые клиент приобрел тоовар в прошлом году | | |
| --- | |
| """ | |
| ) | |
| st.write("Для того, чтобы лучше понять на какую аудиторию лучше запустить рекламную кампанию, проведем небольшой \ | |
| анализ данных") | |
| with st.expander('Развернуть блок анализа данных'): | |
| st.plotly_chart(tools.get_newbie_plot(data_train), use_container_width=True) | |
| st.write(f'В данных примерно одинаковое количество новых и "старых клиентов". ' | |
| f'Отношение новых клиентов к старым: {(data_train["newbie"] == 1).sum() / (data_train["newbie"] == 0).sum():.2f}') | |
| st.plotly_chart(tools.get_zipcode_plot(data_train), use_container_width=True) | |
| tmp_res = data_train.zip_code.value_counts(normalize=True) * 100 | |
| st.write(f'Большинство клиентов из пригорода: {tmp_res["Surburban"]:.2f}%, из города: {tmp_res["Urban"]:.2f}% и из села: {tmp_res["Rural"]:.2f}%') | |
| tmp_res = data_train.channel.value_counts(normalize=True) * 100 | |
| st.plotly_chart(tools.get_channel_plot(data_train), use_container_width=True) | |
| st.write(f'В прошлом году почти одинаковое количество клиентов покупало товары через телефон и сайт, {tmp_res["Phone"]:.2f}% и {tmp_res["Web"]:.2f}% соответственно,' | |
| f' а {tmp_res["Multichannel"]:.2f}% клиентов покупали товары воспользовавшись двумя платформами.') | |
| tmp_res = data_train.history_segment.value_counts(normalize=True) * 100 | |
| st.plotly_chart(tools.get_history_segment_plot(data_train), use_container_width=True) | |
| st.write(f'Как мы видим, большинство пользователей относится к сегменту \$0-\$100 ({tmp_res[0]:.2f}%), второй и ' | |
| f'третий по количеству пользователей сегменты \$100-\$200 ({tmp_res[1]:.2f}%) и \$200-\$350 ({tmp_res[2]:.2f}%).') | |
| st.write(f'К сегментам \$350-\$500 и \$500-\$750 относится {tmp_res[3]:.2f}% и {tmp_res[4]:.2f}% пользователей соответственно.') | |
| st.write(f'Меньше всего пользователей в сегментах \$750-\$1.000 ({tmp_res[-2]:.2f}%) и \$1.000+ ({tmp_res[-1]:.2f}%).') | |
| tmp_res = list(data_train.recency.value_counts(normalize=True) * 100) | |
| st.plotly_chart(tools.get_recency_plot(data_train), use_container_width=True) | |
| st.write(f'Большинство клиентов являются активными клиентами платформы, и совершали покупки в течение месяца ({tmp_res[0]:.2f}%)') | |
| st.write('Также заметно, что 9 и 10 месяцев назад, много клиентов совершали покупки. Это может свидетельствовать о проведении' | |
| 'рекламной кампании в это время или чего-то еще.') | |
| st.write('Также интересно понаблюдать за долями новых клиентов в данном распределении.') | |
| st.plotly_chart(tools.get_history_plot(data_train), use_container_width=True) | |
| st.markdown('_График интерактивный. Двойной клик вернет в начальное состояние._') | |
| st.write('Абсолютное большинство клиентов тратят \$25-\$35 на покупки, но есть и малая доля тех, кто тратит более \$3.000') | |
| st.write('Интересный факт: все покупки более \$500 совершают только новые клиенты') | |
| filters = {} | |
| # блок фильтров | |
| with st.form(key='filter-clients'): | |
| st.subheader('Выберем клиентов, которым отправим рекламу.') | |
| col1, col2, col3 = st.columns(3) | |
| channel_filter = col1.radio('Канал покупки прошлом году', options=['Все', 'Phone', 'Web', 'Multichannel']) | |
| filters['channel_filter'] = channel_filter | |
| newbie_filter = col2.radio('Тип клиента', options=['Все', 'Только новые', 'Только старые']) | |
| filters['newbie_filter'] = newbie_filter | |
| mens_filter = col3.radio('Клиенты, приобретавшие товары', options=['Любые', 'Мужские', 'Женские']) | |
| filters['mens_filter'] = mens_filter | |
| filters['history_segments'] = {} | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.write('Класс клиентов по объему денег, потраченных в прошлом году (history segments)') | |
| first_group = st.checkbox('$0-$100', value=True) | |
| if first_group: | |
| filters['history_segments']['1) $0 - $100'] = True | |
| second_group = st.checkbox('$100-$200', value=True) | |
| if second_group: | |
| filters['history_segments']['2) $100 - $200'] = True | |
| third_group = st.checkbox('$200-$350', value=True) | |
| if third_group: | |
| filters['history_segments']['3) $200 - $350'] = True | |
| fourth_group = st.checkbox('$350-$500', value=True) | |
| if fourth_group: | |
| filters['history_segments']['4) $350 - $500'] = True | |
| fifth_group = st.checkbox('$500-$750', value=True) | |
| if fifth_group: | |
| filters['history_segments']['5) $500 - $750'] = True | |
| sixth_group = st.checkbox('$750-$1.000', value=True) | |
| if sixth_group: | |
| filters['history_segments']['6) $750 - $1,000'] = True | |
| seventh_group = st.checkbox('$1.000+', value=True) | |
| if seventh_group: | |
| filters['history_segments']['7) $1,000 +'] = True | |
| with col2: | |
| st.write('Каких пользователей по почтовому коду выберем') | |
| filters['zip_code'] = {} | |
| surburban = st.checkbox('Surburban', value=True) | |
| if surburban: | |
| filters['zip_code']['surburban'] = True | |
| urban = st.checkbox('Urban', value=True) | |
| if urban: | |
| filters['zip_code']['urban'] = True | |
| rural = st.checkbox('Rural', value=True) | |
| if rural: | |
| filters['zip_code']['rural'] = True | |
| recency = st.slider(label='Месяцев с момента покупки', min_value=int(data_test.recency.min()), max_value=int(data_test.recency.max()), value=(int(data_test.recency.min()), int(data_test.recency.max()))) | |
| filters['recency'] = recency | |
| st.write('Если известно на какой процент аудитории необходимо повлиять, измените значение') | |
| k = st.slider(label='Процент аудитории', min_value=1, max_value=100, value=100) | |
| filter_form_submit_button = st.form_submit_button('Применить фильтр') | |
| # проверка корректности заполнения форм | |
| if not first_group and not second_group and not third_group and not fourth_group and not fifth_group and not sixth_group and not seventh_group: | |
| st.error('Необходимо выбрать хотя бы один класс') | |
| st.stop() | |
| elif not surburban and not urban and not rural: | |
| st.error('Необходимо выбрать хотя бы один почтовый индекс') | |
| st.stop() | |
| # фильтруем тестовые данные по пользовательскому выбору | |
| filtered_dataset = tools.filter_data(data_test, filters) | |
| # проверяем, что данные отфильтровались | |
| if filtered_dataset is None: | |
| st.error('Не найдено пользователей для данных фильтров. Попробуйте изменить фильтры.') | |
| st.stop() | |
| # значение uplift для записей тех клиентов, который выбрал пользователь равен 1 | |
| uplift = [1 for _ in filtered_dataset.index] | |
| target_filtered = target_test.loc[filtered_dataset.index] | |
| treatment_filtered = treatment_test.loc[filtered_dataset.index] | |
| # блок с демонстрацией отфильтрованных данных | |
| with st.expander(label='Посмотреть пример пользователей, которым будет отправлена реклама'): | |
| sample_size = 7 if filtered_dataset.shape[0] >= 7 else filtered_dataset.shape[0] | |
| example = filtered_dataset.sample(sample_size) | |
| st.dataframe(example) | |
| st.info(f'Количество пользователей, попавших в выборку: {filtered_dataset.shape[0]} ({filtered_dataset.shape[0] / data_test.shape[0] * 100 :.2f}%)') | |
| res = st.button('Обновить') | |
| with st.expander('Результаты ручной фильтрации', expanded=True): | |
| # считаем метрики для пользователя | |
| user_metric_uplift_at_k = uplift_at_k(target_filtered, uplift, treatment_filtered, strategy='overall', k=k) | |
| user_metric_uplift_by_percentile = uplift_by_percentile(target_filtered, uplift, treatment_filtered) | |
| user_metric_qini_auc_score = qini_auc_score(target_filtered, uplift, treatment_filtered) | |
| user_metric_weighted_average_uplift = tools.get_weighted_average_uplift(target_filtered, uplift, treatment_filtered) | |
| qini_curve_user_score = qini_curve(target_filtered, uplift, treatment_filtered) | |
| uplift_curve_user_score = uplift_curve(target_filtered, uplift, treatment_filtered) | |
| # отображаем метрики | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric(label=f'Uplift для {k}% пользователей', value=f'{user_metric_uplift_at_k:.4f}') | |
| col2.metric(label=f'Qini AUC score', value=f'{user_metric_qini_auc_score:.4f}') | |
| col3.metric(label=f'Weighted average uplift', value=f'{user_metric_weighted_average_uplift:.4f}') | |
| st.write('Uplift по процентилям') | |
| st.write(user_metric_uplift_by_percentile) | |
| show_ml_reasons = st.checkbox('Показать решения с помощью ML') | |
| if show_ml_reasons: | |
| with st.expander('Решение с помощью CatBoost'): | |
| with st.form(key='catboost_metricks'): | |
| final_uplift = sm_cbc.loc[filtered_dataset.index]['0'] | |
| # считаем метрики для ML | |
| catboost_uplift_at_k = uplift_at_k(target_filtered, final_uplift, treatment_filtered, strategy='overall', k=k) | |
| catboost_uplift_by_percentile = uplift_by_percentile(target_filtered, final_uplift, treatment_filtered) | |
| catboost_qini_auc_score = qini_auc_score(target_filtered, final_uplift, treatment_filtered) | |
| catboost_weighted_average_uplift = tools.get_weighted_average_uplift(target_filtered, final_uplift, treatment_filtered) | |
| # отображаем метрики | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric( | |
| label=f'Uplift для {k}% пользователей', | |
| value=f'{catboost_uplift_at_k:.4f}', | |
| delta=f'{catboost_uplift_at_k - user_metric_uplift_at_k:.4f}' | |
| ) | |
| col2.metric( | |
| label=f'Qini AUC score', | |
| value=f'{catboost_qini_auc_score:.4f}', | |
| delta=f'{catboost_qini_auc_score - user_metric_qini_auc_score:.4f}' | |
| ) | |
| col3.metric( | |
| label=f'Weighted average uplift', | |
| value=f'{catboost_weighted_average_uplift:.4f}', | |
| delta=f'{catboost_weighted_average_uplift - user_metric_weighted_average_uplift:.4f}' | |
| ) | |
| st.write('Uplift по процентилям') | |
| st.write(catboost_uplift_by_percentile) | |
| st.form_submit_button('Обновить графики', help='При изменении флагов') | |
| perfect_qini = st.checkbox('Отрисовать идеальную метрику qini') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = qini_curve_user_score[0][1], qini_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| qini_fig = plot_qini_curve(target_test, sm_cbc['0'], treatment_test, perfect=perfect_qini) | |
| # добавляем пользовательскую метрику на оси графика | |
| qini_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| qini_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(qini_fig.figure_) | |
| prefect_uplift = st.checkbox('Отрисовать идеальную метрику uplift') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = uplift_curve_user_score[0][1], uplift_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| uplift_fig = plot_uplift_curve(target_test, sm_cbc['0'], treatment_test, perfect=prefect_uplift) | |
| # добавляем пользовательскую метрику на оси графика | |
| uplift_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| uplift_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(uplift_fig.figure_) | |
| with st.expander('Решение с помощью Random forest (sklearn)'): | |
| with st.form(key='sklearn_metricks'): | |
| final_rf_uplift = tm_rfc.loc[filtered_dataset.index]['0'] | |
| # считаем метрики для ML | |
| random_forest_uplift_at_k = uplift_at_k(target_filtered, final_rf_uplift, treatment_filtered, strategy='overall', k=k) | |
| random_forest_uplift_by_percentile = uplift_by_percentile(target_filtered, final_rf_uplift, treatment_filtered) | |
| random_forest_qini_auc_score = qini_auc_score(target_filtered, final_rf_uplift, treatment_filtered) | |
| random_forest_weighted_average_uplift = tools.get_weighted_average_uplift(target_filtered, final_rf_uplift, treatment_filtered) | |
| # отображаем метрики | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric( | |
| label=f'Uplift для {k}% пользователей', | |
| value=f'{random_forest_uplift_at_k:.4f}', | |
| delta=f'{random_forest_uplift_at_k - user_metric_uplift_at_k:.4f}' | |
| ) | |
| col2.metric( | |
| label=f'Qini AUC score', | |
| value=f'{random_forest_qini_auc_score:.4f}', | |
| delta=f'{random_forest_qini_auc_score - user_metric_qini_auc_score:.4f}' | |
| ) | |
| col3.metric( | |
| label=f'Weighted average uplift', | |
| value=f'{random_forest_weighted_average_uplift:.4f}', | |
| delta=f'{random_forest_weighted_average_uplift - user_metric_weighted_average_uplift:.4f}' | |
| ) | |
| st.write('Uplift по процентилям') | |
| st.write(random_forest_uplift_by_percentile) | |
| st.form_submit_button('Обновить графики', help='При изменении флагов') | |
| perfect_qini = st.checkbox('Отрисовать идеальную метрику qini') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = qini_curve_user_score[0][1], qini_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| qini_fig = plot_qini_curve(target_test, tm_rfc['0'], treatment_test, perfect=perfect_qini) | |
| # добавляем пользовательскую метрику на оси графика | |
| qini_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| qini_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(qini_fig.figure_) | |
| prefect_uplift = st.checkbox('Отрисовать идеальную метрику uplift') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = uplift_curve_user_score[0][1], uplift_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| uplift_fig = plot_uplift_curve(target_test, tm_rfc['0'], treatment_test, perfect=prefect_uplift) | |
| # добавляем пользовательскую метрику на оси графика | |
| uplift_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| uplift_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(uplift_fig.figure_) | |
| with st.expander('Решение с помощью XGBoost'): | |
| with st.form(key='xgboost_metricks'): | |
| final_xgboost_uplift = sm_xgboost.loc[filtered_dataset.index]['0'] | |
| # считаем метрики для ML | |
| xgboost_uplift_at_k = uplift_at_k(target_filtered, final_xgboost_uplift, treatment_filtered, strategy='overall', k=k) | |
| xgboost_uplift_by_percentile = uplift_by_percentile(target_filtered, final_xgboost_uplift, treatment_filtered) | |
| xgboost_qini_auc_score = qini_auc_score(target_filtered, final_xgboost_uplift, treatment_filtered) | |
| xgboost_weighted_average_uplift = tools.get_weighted_average_uplift(target_filtered, final_xgboost_uplift, treatment_filtered) | |
| # отображаем метрики | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric( | |
| label=f'Uplift для {k}% пользователей', | |
| value=f'{xgboost_uplift_at_k:.4f}', | |
| delta=f'{xgboost_uplift_at_k - user_metric_uplift_at_k:.4f}' | |
| ) | |
| col2.metric( | |
| label=f'Qini AUC score', | |
| value=f'{xgboost_qini_auc_score:.4f}', | |
| delta=f'{xgboost_qini_auc_score - user_metric_qini_auc_score:.4f}' | |
| ) | |
| col3.metric( | |
| label=f'Weighted average uplift', | |
| value=f'{xgboost_weighted_average_uplift:.4f}', | |
| delta=f'{xgboost_weighted_average_uplift - user_metric_weighted_average_uplift:.4f}' | |
| ) | |
| st.write('Uplift по процентилям') | |
| st.write(xgboost_uplift_by_percentile) | |
| st.form_submit_button('Обновить графики', help='При изменении флагов') | |
| perfect_qini = st.checkbox('Отрисовать идеальную метрику qini') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = qini_curve_user_score[0][1], qini_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| qini_fig = plot_qini_curve(target_test, sm_xgboost['0'], treatment_test, perfect=perfect_qini) | |
| # добавляем пользовательскую метрику на оси графика | |
| qini_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| qini_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(qini_fig.figure_) | |
| prefect_uplift = st.checkbox('Отрисовать идеальную метрику uplift') | |
| # получаем координаты пользовательской метрики для точки на графике | |
| x, y = uplift_curve_user_score[0][1], uplift_curve_user_score[1][1] | |
| # получаем объект UpliftCurveDisplay с осями и графиком matplotlib | |
| uplift_fig = plot_uplift_curve(target_test, sm_xgboost['0'], treatment_test, perfect=prefect_uplift) | |
| # добавляем пользовательскую метрику на оси графика | |
| uplift_fig.ax_.plot(x, y, 'ro', markersize=3, label='Analitic qini') | |
| # добавляем обозначение метрики пользователя в легенду | |
| uplift_fig.ax_.legend(loc=u'upper left', bbox_to_anchor=(1, 1)) | |
| st.pyplot(uplift_fig.figure_) |