Описание проекта
Основная цель проекта: проанализировать данные сервиса аренды самокатов GoFast и проверить некоторые гипотезы, которые могут помочь бизнесу вырасти.
Чтобы совершать поездки по городу, пользователи сервиса GoFast пользуются мобильным приложением.
Сервисом можно пользоваться:
- без подписки:
- абонентская плата отсутствует;
- стоимость одной минуты поездки — 8 рублей;
- стоимость старта (начала поездки) — 50 рублей;
- с подпиской Ultra:
- абонентская плата — 199 рублей в месяц;
- стоимость одной минуты поездки — 6 рублей;
- стоимость старта — бесплатно.
Описание исходных данных
В исходных данных есть информация о пользователях, их поездках и подписках, сгруппированных в файлы.
Пользователи — ‘users_go.csv’:
- ‘user_id’ — уникальный идентификатор пользователя;
- ‘name’ — имя пользователя;
- ‘age’ — возраст;
- ‘city’ — город;
- ‘subscription_type’ — тип подписки (free, ultra).
Поездки — ‘rides_go.csv’:
- user_id’ — уникальный идентификатор пользователя;
- ‘distance’ — расстояние, которое пользователь проехал в текущей сессии (в метрах);
- ‘duration’ — продолжительность сессии (в минутах) — время с того момента, как пользователь нажал кнопку «Начать поездку» до момента, как он нажал кнопку «Завершить поездку»;
- ‘date’ — дата совершения поездки.
Подписки — ‘subscriptions_go.csv’:
- ‘subscription_type’ — тип подписки;
- ‘minute_price’ — стоимость одной минуты поездки по данной подписке;
- ‘start_ride_price’ — стоимость начала поездки;
- ‘subscription_fee’ — стоимость ежемесячного платёж.
Содержание
- 1 Шаг 0. Загрузка и настройка библиотек
- 2 Шаг 1. Загрузка данных
- 3 Шаг 2. Предобработка данных
- 4 Шаг 3. Исследовательский анализ данных
- 5 Шаг 4. Объединение данных
- 6 Шаг 5. Подсчёт выручки
- 7 Шаг 6. Проверка гипотез
- 7.1 Тратят ли пользователи с подпиской больше времени на поездки, чем без неё?
- 7.2 Расстояние, которое проезжают пользователи с подпиской за одну поездку, не превышает 3130 метров?
- 7.3 Помесчячная выручка от пользователей с подпиской выше, чем выручка от пользователей без подписки? Сделать выводы
- 7.4 Каким тестом проверить гипотезу о снижении количества пользовательских обращений после обновления сервиса, с которым взаимодействует мобильное приложение?
- 8 Шаг 7. Распределения
- 9 Выводы проекта
Шаг 0. Загрузка и настройка библиотек¶
# Датасеты
import pandas as pd
# Математика
import numpy as np
# Факториалы для комбинатрики
from math import factorial
# Статистика и тервер
from scipy import stats as st
# Графики
import matplotlib
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import rcParams, rcParamsDefault
import seaborn as sns
# Копирование значений, а не ссылок, через b = copy.deepcopy(a)
import copy
# Обязательно для нормального отображения графиков plt
rcParams['figure.figsize'] = 10, 6
%config InlineBackend.figure_format = 'svg'
# Дополнительно и не обязательно для декорирования графиков
factor = .8
default_dpi = rcParamsDefault['figure.dpi']
rcParams['figure.dpi'] = default_dpi * factor
Шаг 1. Загрузка данных¶
# Чтение данных
try:
users_go = pd.read_csv('/datasets/users_go.csv')
rides_go = pd.read_csv('/datasets/rides_go.csv')
subscriptions_go = pd.read_csv('/datasets/subscriptions_go.csv')
except:
users_go = pd.read_csv('datasets/users_go.csv')
rides_go = pd.read_csv('datasets/rides_go.csv')
subscriptions_go = pd.read_csv('datasets/subscriptions_go.csv')
# Отображение всех колонок при выводе на печать
pd.set_option('display.max_columns', None)
# Изучение датафрейма 'users_go'
print(users_go.info(memory_usage='deep'))
print()
print(users_go.describe())
users_go.hist()
users_go.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1565 entries, 0 to 1564 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 1565 non-null int64 1 name 1565 non-null object 2 age 1565 non-null int64 3 city 1565 non-null object 4 subscription_type 1565 non-null object dtypes: int64(2), object(3) memory usage: 412.6 KB None user_id age count 1565.000000 1565.000000 mean 762.633866 24.922045 std 443.260155 4.553496 min 1.000000 12.000000 25% 378.000000 22.000000 50% 762.000000 25.000000 75% 1146.000000 28.000000 max 1534.000000 43.000000
user_id | name | age | city | subscription_type | |
---|---|---|---|---|---|
0 | 1 | Кира | 22 | Тюмень | ultra |
1 | 2 | Станислав | 31 | Омск | ultra |
2 | 3 | Алексей | 20 | Москва | ultra |
3 | 4 | Константин | 26 | Ростов-на-Дону | ultra |
4 | 5 | Адель | 28 | Омск | ultra |
Датафрейм ‘users_go’ имеет 5 столбцов и 1565 строк без пропусков и выбросов. Вес датафрейма 412.6 Кб. Типы столбцов ‘user_id’, ‘age’, ‘subscription_type’ имеют не оптимальные типы данных, их корректировка может снизить вес датафрейма. Названия столбцов соответствуют ‘змеиному’ стилю. Есть дубликаты идентификаторов в столбце ‘user_id’.
# Уникальные идентификаторы
print(len(users_go['user_id'].unique()), 'уникальных значений из', users_go['user_id'].count(), 'строк')
print('От', users_go['user_id'].min(), 'до', users_go['user_id'].max())
1534 уникальных значений из 1565 строк От 1 до 1534
В ‘user_id’ обнаружены дубликаты идентификаторов. Имеющиеся значения позволяют использовать для этого столбца тип данных ‘uint16’.
# Уникальные возрасты
print(len(users_go['age'].unique()), 'уникальных значений из', users_go['age'].count(), 'строк')
print('От', users_go['age'].min(), 'до', users_go['age'].max())
29 уникальных значений из 1565 строк От 12 до 43
Данные в столбце ‘age’ позволяют использовать для него тип данных ‘uint8’.
# Уникальные названия населенных пунктов
users_go['city'].unique()
array(['Тюмень', 'Омск', 'Москва', 'Ростов-на-Дону', 'Краснодар', 'Пятигорск', 'Екатеринбург', 'Сочи'], dtype=object)
Неявные дубликаты в столбце ‘city’ не обнаружены.
# Уникальные типы
users_go['subscription_type'].unique()
array(['ultra', 'free'], dtype=object)
Имеющиеся значения в столбце ‘subscription_type’ позволяют изменить ‘ultra’ и ‘free’ на True и False соответственно, а также использовать для этого столбца тип данных ‘bool’.
# Изучение датафрейма 'rides_go'
print(rides_go.info())
print()
print(rides_go.describe())
rides_go.hist()
rides_go.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 18068 entries, 0 to 18067 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 18068 non-null int64 1 distance 18068 non-null float64 2 duration 18068 non-null float64 3 date 18068 non-null object dtypes: float64(2), int64(1), object(1) memory usage: 564.8+ KB None user_id distance duration count 18068.000000 18068.000000 18068.000000 mean 842.869936 3070.659976 17.805011 std 434.734317 1116.831209 6.091051 min 1.000000 0.855683 0.500000 25% 487.000000 2543.226360 13.597563 50% 889.000000 3133.609994 17.678395 75% 1213.250000 3776.222735 21.724800 max 1534.000000 7211.007745 40.823963
user_id | distance | duration | date | |
---|---|---|---|---|
0 | 1 | 4409.919140 | 25.599769 | 2021-01-01 |
1 | 1 | 2617.592153 | 15.816871 | 2021-01-18 |
2 | 1 | 754.159807 | 6.232113 | 2021-04-20 |
3 | 1 | 2694.783254 | 18.511000 | 2021-08-11 |
4 | 1 | 4028.687306 | 26.265803 | 2021-08-28 |
В датафрейме ‘rides_go’ обнаружены 4 столбца и 18068 строк без пропусков и выбросов. Вес датафрейма 564.8 Кб. Типы столбцов ‘user_id’ и ‘date’ не оптимальны. Корректировка типов этих столбцов снизит потребление памяти и позволит раскрыть их потенциал. Названия столбцов соответствуют ‘змеиному’ стилю. Возможно наличие выбросов в столбцах ‘distance’ и ‘duration’.
# Уникальные идентификаторы
print(len(rides_go['user_id'].unique()), 'уникальных идентификаторов из', rides_go['user_id'].count(), 'строк')
print('От', rides_go['user_id'].min(), 'до', rides_go['user_id'].max())
1534 уникальных идентификаторов из 18068 строк От 1 до 1534
Имеющиеся значения в столбце ‘user_id’ позволяют использовать для этого столбца тип данных ‘uint16’.
# Изучение датафрейма 'subscriptions_go'
print(subscriptions_go.info())
subscriptions_go.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2 entries, 0 to 1 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 subscription_type 2 non-null object 1 minute_price 2 non-null int64 2 start_ride_price 2 non-null int64 3 subscription_fee 2 non-null int64 dtypes: int64(3), object(1) memory usage: 192.0+ bytes None
subscription_type | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|
0 | free | 8 | 50 | 0 |
1 | ultra | 6 | 0 | 199 |
В датафрейме обнаружены 4 столбца и 2 строки без пропусков и выбросов. Не оптимальные типы данных во всех столбцах. Это не является критичным при весе датафрейма в 192 байта. Если тип данных в столбце ‘subscription_type’ датафрейма ‘users_go’ будет изменён, то и в данном датафрейме одноимённый столбец требуется привести в соответствие. Названия столбцов соответствуют ‘змеиному’ стилю.
Шаг 2. Предобработка данных¶
Корректировка типов данных столбцов¶
# Корректировка столбцов в 'users_go'
users_go['user_id'] = users_go['user_id'].astype('uint16')
users_go['age'] = users_go['age'].astype('uint8')
users_go.loc[users_go['subscription_type'] == 'ultra', 'subscription_type'] = True # 'ultra'
users_go.loc[users_go['subscription_type'] == 'free', 'subscription_type'] = False # 'free'
users_go['subscription_type'] = users_go['subscription_type'].astype('bool')
# Корректировка столбца 'date' в датафейме 'rides_go'
rides_go['date'] = pd.to_datetime(rides_go['date'], format='%Y-%m-%d')
# Корректировка столбца 'subscription_type' в датафейме 'subscriptions_go'
subscriptions_go.loc[subscriptions_go['subscription_type'] == 'ultra', 'subscription_type'] = True # 'ultra'
subscriptions_go.loc[subscriptions_go['subscription_type'] == 'free', 'subscription_type'] = False # 'free'
subscriptions_go['subscription_type'] = subscriptions_go['subscription_type'].astype('bool')
for i in ['minute_price', 'start_ride_price', 'subscription_fee']:
subscriptions_go[i] = subscriptions_go[i].astype('uint8')
Добавление столбцов¶
# Добавление столбца 'month' с номером месяца в датафейм 'rides_go'
rides_go['month'] = pd.DatetimeIndex(rides_go['date']).month
Обработка неявных дубликатов¶
# Удаление дубликатов идентификаторов в столбце 'user_id' датафрейма 'users_go'
users_go = users_go.sort_values(by='user_id').loc[users_go['user_id'].duplicated() == False]
# Проверка наличия дубликатов в 'users_go'
users_go.sort_values(by='user_id').loc[users_go['user_id'].duplicated() == True].count()
user_id 0 name 0 age 0 city 0 subscription_type 0 dtype: int64
Строки с дубликатами в поле ‘user_id’ удалены из ‘users_go’.
Проверка результатов предобработки данных¶
# Датафейм 'users_go'
users_go.info()
users_go.head()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1534 entries, 0 to 1533 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 1534 non-null uint16 1 name 1534 non-null object 2 age 1534 non-null uint8 3 city 1534 non-null object 4 subscription_type 1534 non-null bool dtypes: bool(1), object(2), uint16(1), uint8(1) memory usage: 41.9+ KB
user_id | name | age | city | subscription_type | |
---|---|---|---|---|---|
0 | 1 | Кира | 22 | Тюмень | True |
1 | 2 | Станислав | 31 | Омск | True |
2 | 3 | Алексей | 20 | Москва | True |
3 | 4 | Константин | 26 | Ростов-на-Дону | True |
4 | 5 | Адель | 28 | Омск | True |
Предобработка датафрейма сократила количество строк в нем с 1565 до 1534, а его вес почти в 10 раз с 412.6 Кб до 41.9 Кб.
# Датафейм 'rides_go'
print(rides_go.info())
print()
print(rides_go.head())
print()
print(rides_go.describe())
print()
print('Анализ дат:')
print('Минимальная дата :', rides_go['date'].min())
print('Максимальная дата:', rides_go['date'].max())
rides_go['date'].hist()
plt.show()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 18068 entries, 0 to 18067 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 18068 non-null int64 1 distance 18068 non-null float64 2 duration 18068 non-null float64 3 date 18068 non-null datetime64[ns] 4 month 18068 non-null int64 dtypes: datetime64[ns](1), float64(2), int64(2) memory usage: 705.9 KB None user_id distance duration date month 0 1 4409.919140 25.599769 2021-01-01 1 1 1 2617.592153 15.816871 2021-01-18 1 2 1 754.159807 6.232113 2021-04-20 4 3 1 2694.783254 18.511000 2021-08-11 8 4 1 4028.687306 26.265803 2021-08-28 8 user_id distance duration month count 18068.000000 18068.000000 18068.000000 18068.000000 mean 842.869936 3070.659976 17.805011 6.486772 std 434.734317 1116.831209 6.091051 3.437410 min 1.000000 0.855683 0.500000 1.000000 25% 487.000000 2543.226360 13.597563 4.000000 50% 889.000000 3133.609994 17.678395 6.000000 75% 1213.250000 3776.222735 21.724800 9.000000 max 1534.000000 7211.007745 40.823963 12.000000 Анализ дат: Минимальная дата : 2021-01-01 00:00:00 Максимальная дата: 2021-12-30 00:00:00
В процессе предобработки данных в датафрейме ‘rides_go’ был добавлен столбец ‘month’ и изменены типы данных столбцов. В результате вес датафрейма вырос с 564.8 Кб до 705.9 Кб. Однако, без корректировки типов данных, вес датафрейма был бы еще больше. Также проверены данные в столбце с датами. Диапазон дат с 01.01.2021 по 30.12.2021. В пределах этого диапазона дат количество значений относительно равномерно распределено.
# Датафейм 'subscriptions_go'
subscriptions_go.info()
subscriptions_go.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2 entries, 0 to 1 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 subscription_type 2 non-null bool 1 minute_price 2 non-null uint8 2 start_ride_price 2 non-null uint8 3 subscription_fee 2 non-null uint8 dtypes: bool(1), uint8(3) memory usage: 136.0 bytes
subscription_type | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|
0 | False | 8 | 50 | 0 |
1 | True | 6 | 0 | 199 |
# Функция для более наглядного отображения значений
# 'ultra' и 'free' в поле 'subscription_type' разных датафреймов
def subscriptions_go_translate(data):
result = copy.deepcopy(data)
result.loc[result['subscription_type'] == True, 'subscription_type'] = 'ultra'
result.loc[result['subscription_type'] == False, 'subscription_type'] = 'free'
return result
subscriptions_go_translate(subscriptions_go).head()
subscription_type | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|
0 | free | 8 | 50 | 0 |
1 | ultra | 6 | 0 | 199 |
Предобработка датафрейма сократила его вес почти в 10 раз с 192 до 136 байт.
Шаг 3. Исследовательский анализ данных¶
Описание и визуализация общей информации о пользователях и поездках
# Группировка значений
def quality_go_groupby(data, column):
if 'subscription_fee' in subscriptions_go.columns:
result = subscriptions_go_translate(data).groupby([column])[column].agg(['count']).sort_values(by='count')
else:
result = data.groupby([column])[column].agg(['count']).sort_values(by='count')
#result = data.groupby([column])[column].agg(['count']).sort_values(by='count')
#, ascending=False)
result['percent'] = result['count'] / result['count'].sum()
return result
# Функции визуализации столбцов с качественными данными
def quality_go(data, column, xlabel, ylabel, title):
print('Название столбца:', column)
go = quality_go_groupby(data, column)
go.columns = {'Количество':'count', 'Доля':'percent'}
#quality_go_groupby(data, column).plot(kind='bar')
plt.barh(
go.index,
go['Количество'].values
)
plt.gca().set(xlabel=f'${xlabel}$', ylabel=f'${ylabel}$')
plt.title(f'{title}')
plt.show()
print('Значения датафрейма:')
print(go)
print('\nОписание датафрейма:')
print(go.describe())
# Функция визуализации столбцов с количественными данными
def quantity_go(data, column, bins_num, xlabel, ylabel, title):
print('Название столбца:', column)
data[column].hist(bins=bins_num)
plt.gca().set(xlabel=f'${xlabel}$', ylabel=f'${ylabel}$')
plt.title(f'{title}')
plt.show()
print('Описание датафрейма:')
print(data[column].describe().round(0))
Частота встречаемости городов¶
quality_go(
users_go,
'city',
'Количество_ использований_ сервиса',
'Города',
'Количество использований сервиса в разных городах'
)
Название столбца: city
Значения датафрейма: Количество Доля city Москва 168 0.109518 Тюмень 180 0.117340 Омск 183 0.119296 Сочи 189 0.123207 Краснодар 193 0.125815 Ростов-на-Дону 198 0.129074 Екатеринбург 204 0.132986 Пятигорск 219 0.142764 Описание датафрейма: Количество Доля count 8.000000 8.000000 mean 191.750000 0.125000 std 15.672998 0.010217 min 168.000000 0.109518 25% 182.250000 0.118807 50% 191.000000 0.124511 75% 199.500000 0.130052 max 219.000000 0.142764
Лидером по частотности является Пятигорск (219 строк). Следом идут Екатеринбург (204 строки) и Ростов-на-Дону (198 строк). Антилидер — Москва (168 строк).
Соотношение пользователей с подпиской и без нее¶
quality_go(
users_go,
'subscription_type',
'Количество_ значений',
'Типы_ подписки',
'Количество использований разного типа подписок'
)
Название столбца: subscription_type
Значения датафрейма: Количество Доля subscription_type ultra 699 0.455671 free 835 0.544329 Описание датафрейма: Количество Доля count 2.000000 2.000000 mean 767.000000 0.500000 std 96.166522 0.062690 min 699.000000 0.455671 25% 733.000000 0.477836 50% 767.000000 0.500000 75% 801.000000 0.522164 max 835.000000 0.544329
Пользователей с подпиской «free» всего 835, что составляет 54.43% от общего количества и на 136 больше, чем с подпиской «ultra».
Возраст пользователей¶
quantity_go(
users_go,
'age',
users_go['age'].max()-users_go['age'].min(),
'Возраст',
'Количество_ пользователей',
'Распределение пользователей по возрастам'
)
Название столбца: age
Описание датафрейма: count 1534.0 mean 25.0 std 5.0 min 12.0 25% 22.0 50% 25.0 75% 28.0 max 43.0 Name: age, dtype: float64
Услугами сервиса аренды самокатов пользуются люди от 12 до 43 лет. Распределение возрастной кривой имеет нормальное распределение. Максимальное количество пользователей в возрасте 25 лет, этот же возраст является медианным.
Расстояние, которое пользователь преодолел за одну поездку¶
quantity_go(
rides_go,
'distance',
int((rides_go['distance'].max()-rides_go['distance'].min()) / 100),
'Дистанция_ одной_ поездки',
'Количество_ поездок',
'Распределение количества поездок по их дистанции'
)
Название столбца: distance
Описание датафрейма: count 18068.0 mean 3071.0 std 1117.0 min 1.0 25% 2543.0 50% 3134.0 75% 3776.0 max 7211.0 Name: distance, dtype: float64
За одну поездку пользователи преодолевают от 1 до 7211 метров. Медиана всех значений равна 3134 метров. Наблюдаются два пика. Малый пик около 700 метров, большой пик около 3200 метров. Между пиками значения нормально распределены.
Продолжительность поездок¶
quantity_go(
rides_go,
'duration',
int((rides_go['duration'].max()-rides_go['duration'].min())),
'Продолжительность_ одной_ поездки',
'Количество_ поездок',
'Распределение количества поездок по их продолжительности'
)
Название столбца: duration
Описание датафрейма: count 18068.0 mean 18.0 std 6.0 min 0.0 25% 14.0 50% 18.0 75% 22.0 max 41.0 Name: duration, dtype: float64
Продолжительность поездок нормально распределена от 0 до 41 минуты. Продолжительность в 18 минут является как наиболее распространенной так и медианным значением.
Наблюдается анамалия размером менее 100 поездок с продолжительностью в 1 минуту. Однако, они не являются выбрасами, т.к. подкреплены данными о поездках со столь же маленькими дистанциями.
Промежуточные выводы исследовательского анализа¶
Услугами сервиса аренды самокатов пользуются люди от 12 до 43 лет. Максимальное количество пользователей в возрасте 25 лет, этот же возраст является медианным.
Большая часть всех пользователей используют подписку «free». Всего их 835, что составляет 54.43% от общего количества и на 136 больше, чем с подпиской «ultra».
Максимальное количество в 219 поездок совершено в Пятигорске. Минимальное количество в 168 поездок совершено в Москве.
За одну поездку пользователи преодолевают от 1 до 7211 метров. Медиана всех значений равна 3134 метров. Наблюдаются два пика. Малый пик около 700 метров, большой пик около 3200 метров. Между пиками значения нормально распределены.
Продолжительность поездок нормально распределена от 0 до 41 минуты. Продолжительность в 18 минут является как наиболее распространенной так и медианным значением. Наблюдается выброс размером менее 100 поездок с продолжительностью в 1 минуту.
Шаг 4. Объединение данных¶
Объединение данных о пользователях, поездках и подписках в один датафрейм методом merge()¶
# Общая таблица с данными
total_go = users_go.merge(rides_go, on='user_id').merge(subscriptions_go, on='subscription_type')
total_go.head()
user_id | name | age | city | subscription_type | distance | duration | date | month | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Кира | 22 | Тюмень | True | 4409.919140 | 25.599769 | 2021-01-01 | 1 | 6 | 0 | 199 |
1 | 1 | Кира | 22 | Тюмень | True | 2617.592153 | 15.816871 | 2021-01-18 | 1 | 6 | 0 | 199 |
2 | 1 | Кира | 22 | Тюмень | True | 754.159807 | 6.232113 | 2021-04-20 | 4 | 6 | 0 | 199 |
3 | 1 | Кира | 22 | Тюмень | True | 2694.783254 | 18.511000 | 2021-08-11 | 8 | 6 | 0 | 199 |
4 | 1 | Кира | 22 | Тюмень | True | 4028.687306 | 26.265803 | 2021-08-28 | 8 | 6 | 0 | 199 |
print("Количество строк (поездок) в 'rides_go':", rides_go['user_id'].count())
print("Количество строк (поездок) в 'total_go':", total_go['user_id'].count())
print()
print("Количество уникальных пользователей в 'users_go':", len(users_go['user_id'].unique()))
print("Количество уникальных пользователей в 'total_go':", len(total_go['user_id'].unique()))
Количество строк (поездок) в 'rides_go': 18068 Количество строк (поездок) в 'total_go': 18068 Количество уникальных пользователей в 'users_go': 1534 Количество уникальных пользователей в 'total_go': 1534
Количество строк (записей о поездках) в ‘rides_go’ и в ‘total_go’ одинаково и равно 18068. Количество уникальных пользователей в ‘users_go’ и в ‘total_go’ одинаково и равно 1534.
Т.к. исходные датафреймы не содержали пропусков и количество значений в них соответствует количеству в итоговом датафрейме, то последний также не содержит пропусков.
Создание двух датафреймов с пользователями, имеющими подписку и без нее, на основе общего датафрейма¶
# Датафрейм данных с пользователями с подпиской
ultra_go = total_go.loc[total_go['subscription_type'] == True]
# Датафрейм данных с пользователями без подписки
free_go = total_go.loc[total_go['subscription_type'] == False]
Визуализация данных из датафреймов, разделенных на пользователей с подпиской и без¶
# Функция сравнения одинаковых столбцов
# с количественными данными в разных датафреймах
def quantity_go_comparison(data1, data2, column, bins_num, xlabel, ylabel, title, dataname1, dataname2):
print('Сравнение столбцов:', column)
data1.rename(columns = {column:dataname1})[dataname1].plot.hist(
bins=bins_num,
#xlim=(data_min, data_max),
alpha=.5,
legend=True,
grid=True
)
data2.rename(columns = {column:dataname2})[dataname2].plot.hist(
bins=bins_num,
#xlim=(data_min, data_max),
alpha=.5,
legend=True,
grid=True
)
plt.gca().set(xlabel=f'${xlabel}$', ylabel=f'${ylabel}$')
plt.title(f'{title}')
plt.show()
print(f"Описание датафрейма 'go_{dataname1}':")
print(data1[column].describe().round(0))
print(f"\nОписание датафрейма 'go_{dataname2}':")
print(data2[column].describe().round(0))
# Функции сравнения одинаковых столбцов
# с качественными данными в разных датафреймах
def quality_go_comparison(data1, data2, column, xlabel, ylabel, title, dataname1, dataname2, sort=False):
print('Сравнение столбцов:', column)
go_count = pd.DataFrame([
data1.groupby(column)[column].agg('count'),
data2.groupby(column)[column].agg('count')
])
go_count.index = [dataname1, dataname2]
go_count = go_count.transpose()
if sort == True:
if go_count[dataname1].sum() >= go_count[dataname2].sum():
go_count = go_count.sort_values(by=dataname1)
else:
go_count = go_count.sort_values(by=dataname2)
go_count.plot.barh(grid=True)
plt.gca().set(xlabel=f'${xlabel}$', ylabel=f'${ylabel}$')
plt.title(f'{title}')
plt.show()
go_percent_sum = go_count[dataname1].sum() + go_count[dataname2].sum()
go_percent = go_count / go_percent_sum
print('Значения датафрейма:')
print(go_count)
print('\nРаспределение долей всего датафрейма:')
print(go_percent)
print('\nОписание датафрейма:')
print(go_count.describe())
ultra_go.head()
user_id | name | age | city | subscription_type | distance | duration | date | month | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Кира | 22 | Тюмень | True | 4409.919140 | 25.599769 | 2021-01-01 | 1 | 6 | 0 | 199 |
1 | 1 | Кира | 22 | Тюмень | True | 2617.592153 | 15.816871 | 2021-01-18 | 1 | 6 | 0 | 199 |
2 | 1 | Кира | 22 | Тюмень | True | 754.159807 | 6.232113 | 2021-04-20 | 4 | 6 | 0 | 199 |
3 | 1 | Кира | 22 | Тюмень | True | 2694.783254 | 18.511000 | 2021-08-11 | 8 | 6 | 0 | 199 |
4 | 1 | Кира | 22 | Тюмень | True | 4028.687306 | 26.265803 | 2021-08-28 | 8 | 6 | 0 | 199 |
free_go.head()
user_id | name | age | city | subscription_type | distance | duration | date | month | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
6500 | 700 | Айдар | 22 | Омск | False | 2515.690719 | 14.944286 | 2021-01-02 | 1 | 8 | 50 | 0 |
6501 | 700 | Айдар | 22 | Омск | False | 846.932642 | 16.234663 | 2021-02-01 | 2 | 8 | 50 | 0 |
6502 | 700 | Айдар | 22 | Омск | False | 4004.434142 | 20.016628 | 2021-02-04 | 2 | 8 | 50 | 0 |
6503 | 700 | Айдар | 22 | Омск | False | 1205.911290 | 9.782872 | 2021-02-10 | 2 | 8 | 50 | 0 |
6504 | 700 | Айдар | 22 | Омск | False | 3047.379435 | 17.427673 | 2021-02-14 | 2 | 8 | 50 | 0 |
ultra_go.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 6500 entries, 0 to 6499 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 6500 non-null uint16 1 name 6500 non-null object 2 age 6500 non-null uint8 3 city 6500 non-null object 4 subscription_type 6500 non-null bool 5 distance 6500 non-null float64 6 duration 6500 non-null float64 7 date 6500 non-null datetime64[ns] 8 month 6500 non-null int64 9 minute_price 6500 non-null uint8 10 start_ride_price 6500 non-null uint8 11 subscription_fee 6500 non-null uint8 dtypes: bool(1), datetime64[ns](1), float64(2), int64(1), object(2), uint16(1), uint8(4) memory usage: 399.9+ KB
free_go.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 11568 entries, 6500 to 18067 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 11568 non-null uint16 1 name 11568 non-null object 2 age 11568 non-null uint8 3 city 11568 non-null object 4 subscription_type 11568 non-null bool 5 distance 11568 non-null float64 6 duration 11568 non-null float64 7 date 11568 non-null datetime64[ns] 8 month 11568 non-null int64 9 minute_price 11568 non-null uint8 10 start_ride_price 11568 non-null uint8 11 subscription_fee 11568 non-null uint8 dtypes: bool(1), datetime64[ns](1), float64(2), int64(1), object(2), uint16(1), uint8(4) memory usage: 711.7+ KB
print("Разница описательных значений столбцов с числовыми данными датафреймов 'free_go' и 'ultra_go'")
free_go.describe() - ultra_go.describe()
Разница описательных значений столбцов с числовыми данными датафреймов 'free_go' и 'ultra_go'
user_id | age | distance | duration | month | minute_price | start_ride_price | subscription_fee | |
---|---|---|---|---|---|---|---|---|
count | 5068.000000 | 5068.000000 | 5068.000000 | 5068.000000 | 5068.000000 | 5068.0 | 5068.0 | 5068.0 |
mean | 771.801846 | 0.000711 | -69.950106 | -1.151076 | 0.100687 | 2.0 | 50.0 | -199.0 |
std | 40.066290 | -0.276108 | 409.278476 | 0.754755 | 0.035154 | 0.0 | 0.0 | 0.0 |
min | 699.000000 | 0.000000 | -243.354755 | -3.260353 | 0.000000 | 2.0 | 50.0 | -199.0 |
25% | 740.000000 | 0.000000 | -418.881328 | -1.566217 | 0.000000 | 2.0 | 50.0 | -199.0 |
50% | 772.000000 | 0.000000 | -33.988764 | -0.851263 | 1.000000 | 2.0 | 50.0 | -199.0 |
75% | 803.000000 | 0.000000 | 345.123469 | -0.290058 | 0.000000 | 2.0 | 50.0 | -199.0 |
max | 835.000000 | 5.000000 | 1511.238083 | -5.875464 | 0.000000 | 2.0 | 50.0 | -199.0 |
Выводы из анализа разницы использования обоих типов подписки:
- Общее количество. Поездок с исползьзованием подписки ‘free’ на 5068 больше, чем с использованием подписки ‘ultra’, и составляет 11568.
- Возраст. Возрастные показатели пользователей с разным типом подписки в целом схожи, за исключением максимального возраста, который на 5 лет больше у пользователей с подпиской ‘free’.
- Дистанция. Дистанция поездок при использовании подписки ‘free’ более разнообразна. Так максимальная дистанция больше на 1511 метров, а минимальная на 243 метра меньше, чем при использовании подписки ‘ultra’. Об этом же свидетельствует и стандартное отклонение, которое при использовании подписки ‘free’ больше на 409 метров, чем при использовании подписки ‘ultra’. Средняя дистанция при использовании подписки ‘free’ короче. Это следует из того, что среднее значениее на 70 метров, а медиана на 34 метра, меньше, чем при использовании ‘ultra’.
- Продолжительность. Продолжительность поездок при использовании подписки ‘free’ немного меньше по всем показателям, чем при использовании ‘ultra’. Например, минимальное время меньше на 3.26 минут, а максимальное меньше на 5.88 минут.
- Месяцы. Сезонность использования разного типа подписки схожа за исключением медианы, которая у подписчиков ‘ultra’ приходится на июнь, а у подписчиков ‘free’ на июль. При использовании обоих типов подписок наблюдаются спады пользовательской активности в феврале и ноябре.
# Срвнение столбцов 'age' в 'ultra_go' и 'free_go'
quantity_go_comparison(
ultra_go,
free_go,
'age',
int(free_go['age'].max()),
'Возраст',
'Количество_ использований_ сервиса',
'Количество использований сервиса в зависимости от возраста',
'ultra',
'free'
)
Сравнение столбцов: age
Описание датафрейма 'go_ultra': count 6500.0 mean 25.0 std 5.0 min 12.0 25% 22.0 50% 25.0 75% 28.0 max 38.0 Name: age, dtype: float64 Описание датафрейма 'go_free': count 11568.0 mean 25.0 std 4.0 min 12.0 25% 22.0 50% 25.0 75% 28.0 max 43.0 Name: age, dtype: float64
Возрастные показатели пользователей с разным типом подписки в целом схожи, за исключением максимального возраста, который на 5 лет больше у пользователей с подпиской ‘free’ и равен 43 годам.
# Срвнение столбцов 'city' в 'ultra_go' и 'free_go'
quality_go_comparison(
ultra_go,
free_go,
'city',
'Город',
'Количество_ использований_ сервиса',
'Количество использований сервиса в зависимости от города',
'ultra',
'free',
True
)
Сравнение столбцов: city
Значения датафрейма: ultra free city Москва 700 1175 Омск 827 1250 Тюмень 767 1354 Сочи 813 1446 Краснодар 780 1521 Ростов-на-Дону 885 1565 Екатеринбург 876 1585 Пятигорск 852 1672 Распределение долей всего датафрейма: ultra free city Москва 0.038743 0.065032 Омск 0.045772 0.069183 Тюмень 0.042451 0.074939 Сочи 0.044997 0.080031 Краснодар 0.043170 0.084182 Ростов-на-Дону 0.048982 0.086617 Екатеринбург 0.048484 0.087724 Пятигорск 0.047155 0.092539 Описание датафрейма: ultra free count 8.000000 8.000000 mean 812.500000 1446.000000 std 61.923916 173.412802 min 700.000000 1175.000000 25% 776.750000 1328.000000 50% 820.000000 1483.500000 75% 858.000000 1570.000000 max 885.000000 1672.000000
Во всех городах доминирует подписка ‘free’. Пятигорск является лидером по использованию этиго типа подписки (1672 поездок, это 9.25% от общего количества поездок). Ростов-на-Дону является лидером по использованию подписки ‘ultra’ (885 поездок, это 4.9% от общего количества поездок). Минимальное количество поездок совершено в Москве с использованием подписки ‘ultra’ (700 поездок, это 3.87% от общего количества поездок).
# Срвнение столбцов 'distance' в 'ultra_go' и 'free_go'
quantity_go_comparison(
ultra_go,
free_go,
'distance',
int(free_go['distance'].max()/100),
'Расстояние',
'Количество_ использований_ сервиса',
'Количество использований сервиса в зависимости от расстояния поездки',
'ultra',
'free'
)
Сравнение столбцов: distance
Описание датафрейма 'go_ultra': count 6500.0 mean 3115.0 std 837.0 min 244.0 25% 2785.0 50% 3149.0 75% 3561.0 max 5700.0 Name: distance, dtype: float64 Описание датафрейма 'go_free': count 11568.0 mean 3045.0 std 1246.0 min 1.0 25% 2367.0 50% 3115.0 75% 3906.0 max 7211.0 Name: distance, dtype: float64
Дистанция поездок при использовании подписки ‘free’ более разнообразна. Так максимальная дистанция больше на 1511 метров, а минимальная на 243 метра меньше, чем при использовании подписки ‘ultra’. Об этом же свидетельствует и стандартное отклонение, которое при использовании подписки ‘free’ больше на 409 метров, чем при использовании подписки ‘ultra’. Средняя дистанция при использовании подписки ‘free’ короче. Это следует из того, что среднее значениее на 70 метров, а медиана на 34 метра, меньше, чем при использовании ‘ultra’.
При использовании обоих подписок наблюдается два пика. Первый небольшой пик на меньших дистанциях и второй основной пик на больших дистанциях. Так меньший пик ‘ultra’ находится в районе 1000 метров, а основной в районе 3100 метров с небольшой коррекцией около 4000 метров. Меншьй пик ‘free’ в районе 700 метров, а основной 2500-3500 метров. В целом пики ‘ultra’ более выраженные и имеют меньший разрбос.
# Срвнение столбцов 'duration' в 'ultra_go' и 'free_go'
quantity_go_comparison(
ultra_go,
free_go,
'duration',
int(free_go['duration'].max()),
'Продоложительность',
'Количество_ использований_ сервиса',
'Количество использований сервиса в зависимости от продолжительности поездки',
'ultra',
'free'
)
Сравнение столбцов: duration
Описание датафрейма 'go_ultra': count 6500.0 mean 19.0 std 6.0 min 4.0 25% 15.0 50% 18.0 75% 22.0 max 41.0 Name: duration, dtype: float64 Описание датафрейма 'go_free': count 11568.0 mean 17.0 std 6.0 min 0.0 25% 13.0 50% 17.0 75% 22.0 max 35.0 Name: duration, dtype: float64
Продолжительность поездок при использовании подписки ‘free’ немного меньше по всем показателям, чем при использовании ‘ultra’. Например, минимальное время меньше на 3.26 минут, а максимальное меньше на 5.88 минут. В обоих случаях количество поездок относительно нормально распределено в зависимости от их продолжительности. Пик количества поездок приходится на 17-18 минут. При использовании подписки ‘free’ наблюдается пик в около 100 поездок с продолжительностью около 1 минуты.
# Срвнение столбцов 'month' в 'ultra_go' и 'free_go'
quality_go_comparison(
ultra_go,
free_go,
'month',
'Месяц',
'Количество_ использований_ сервиса',
'Количество использований сервиса в зависимости от месяца',
'ultra',
'free',
)
Сравнение столбцов: month
Значения датафрейма: ultra free month 1 584 1001 2 468 868 3 561 983 4 551 955 5 597 963 6 565 988 7 574 955 8 540 978 9 515 1002 10 529 946 11 492 926 12 524 1003 Распределение долей всего датафрейма: ultra free month 1 0.032322 0.055402 2 0.025902 0.048041 3 0.031049 0.054406 4 0.030496 0.052856 5 0.033042 0.053299 6 0.031271 0.054682 7 0.031769 0.052856 8 0.029887 0.054129 9 0.028503 0.055457 10 0.029278 0.052358 11 0.027230 0.051251 12 0.029002 0.055513 Описание датафрейма: ultra free count 12.000000 12.000000 mean 541.666667 964.000000 std 38.120224 38.863397 min 468.000000 868.000000 25% 521.750000 952.750000 50% 545.500000 970.500000 75% 567.250000 991.250000 max 597.000000 1003.000000
Сезонность использования разного типа подписки схожа за исключением медианы, которая у подписчиков ‘ultra’ приходится на июнь, а у подписчиков ‘free’ на июль. При использовании обоих типов подписок наблюдаются спады пользовательской активности в феврале и ноябре. Максимум поездок с подпиской ‘ultra’ в мае (597 поездок, это 3.3% от всех поездок). Максимумы поездок с подпиской ‘free’ в январе (1001 поездка, это 5.54% от всех поездок), сентябре (1002 поездки) и декабре (1003 поездки). При использовании подписки ‘free’ нет выраженной сезонности, кроме спдов в феврале и ноябре. При использовании подписки ‘ultra’ наблюдается более выраженная сезонность. Количество поездок растет с февраля по май, а затем медленно снижается до ноября.
Выводы из сравнения использования разных типов подписки:
- Общее количество. Поездок с исползьзованием подписки ‘free’ на 5068 больше, чем с использованием подписки ‘ultra’, и составляет 11568.
- Возраст. Возрастные показатели пользователей с разным типом подписки в целом схожи, за исключением максимального возраста, который на 5 лет больше у пользователей с подпиской ‘free’.
- Дистанция. Дистанция поездок при использовании подписки ‘free’ более разнообразна. Так максимальная дистанция больше на 1511 метров, а минимальная на 243 метра меньше, чем при использовании подписки ‘ultra’. Об этом же свидетельствует и стандартное отклонение, которое при использовании подписки ‘free’ больше на 409 метров, чем при использовании подписки ‘ultra’. Средняя дистанция при использовании подписки ‘free’ короче. Это следует из того, что среднее значениее на 70 метров, а медиана на 34 метра, меньше, чем при использовании ‘ultra’. При использовании обоих подписок наблюдается два пика. Первый небольшой пик на меньших дистанциях и второй основной пик на больших дистанциях. Так меньший пик ‘ultra’ находится в районе 1000 метров, а основной в районе 3100 метров с небольшой коррекцией около 4000 метров. Меншьй пик ‘free’ в районе 700 метров, а основной 2500-3500 метров. В целом пики ‘ultra’ более выраженные и имеют меньший разрбос.
- Продолжительность. Продолжительность поездок при использовании подписки ‘free’ немного меньше по всем показателям, чем при использовании ‘ultra’. Например, минимальное время меньше на 3.26 минут, а максимальное меньше на 5.88 минут. В обоих случаях количество поездок относительно нормально распределено в зависимости от их продолжительности. Пик количества поездок приходится на 17-18 минут. При использовании подписки ‘free’ наблюдается пик в около 100 поездок с продолжительностью около 1 минуты.
- Месяцы. Сезонность использования разного типа подписки схожа за исключением медианы, которая у подписчиков ‘ultra’ приходится на июнь, а у подписчиков ‘free’ на июль. При использовании обоих типов подписок наблюдаются спады пользовательской активности в феврале и ноябре. Максимум поездок с подпиской ‘ultra’ в мае (597 поездок, это 3.3% от всех поездок). Максимумы поездок с подпиской ‘free’ в январе (1001 поездка, это 5.54% от всех поездок), сентябре (1002 поездки) и декабре (1003 поездки). При использовании подписки ‘free’ нет выраженной сезонности, кроме спдов в феврале и ноябре. При использовании подписки ‘ultra’ наблюдается более выраженная сезонность. Количество поездок растет с февраля по май, а затем медленно снижается до ноября.
- Города. Во всех городах доминирует подписка ‘free’. Пятигорск является лидером по использованию этиго типа подписки (1672 поездок). Ростов-на-Дону является лидером по использованию подписки ‘ultra’ (885 поездок).
Шаг 5. Подсчёт выручки¶
Создание датафрейма, агрегированного по пользователям, на основе ‘total_go’¶
Требуется найти для каждого пользователя за каждый месяц:
- Суммарное расстояние;
- Количество поездок;
- Суммарное время.
# Округление продолжительности поездок в 'duration'
# в общей таблице 'total_go'
total_go['duration'] = np.ceil(total_go['duration'])
# Создание сводной таблицы
users_months_go = total_go.pivot_table(
index=('user_id', 'month'),
values=(
'distance',
'duration',
'minute_price',
'start_ride_price',
'subscription_fee'
),
aggfunc=('count', 'sum'))
# Переименование "двухэтажных" названий столбцов
users_months_go.columns = [
'count', 'distance',
'count2', 'duration',
'count3', 'minute_price',
'count4', 'start_ride_price',
'count5', 'subscription_fee'
]
# Удаление дублирующих столбцов с подсчитанным количеством строк
users_months_go = users_months_go.drop(['count2', 'count3', 'count4', 'count5'], axis=1)
# Приведение столбцов с ценами к целевому состоянию
users_months_go['start_ride_price'] = users_months_go['start_ride_price'] / users_months_go['count']
users_months_go['minute_price'] = users_months_go['minute_price'] / users_months_go['count']
#users_months_go.loc[users_months_go['minute_price'] > 0] = 8
users_months_go['subscription_fee'] = users_months_go['subscription_fee'] / users_months_go['count']
users_months_go
count | distance | duration | minute_price | start_ride_price | subscription_fee | ||
---|---|---|---|---|---|---|---|
user_id | month | ||||||
1 | 1 | 2 | 7027.511294 | 42.0 | 6.0 | 0.0 | 199.0 |
4 | 1 | 754.159807 | 7.0 | 6.0 | 0.0 | 199.0 | |
8 | 2 | 6723.470560 | 46.0 | 6.0 | 0.0 | 199.0 | |
10 | 2 | 5809.911100 | 32.0 | 6.0 | 0.0 | 199.0 | |
11 | 3 | 7003.499363 | 56.0 | 6.0 | 0.0 | 199.0 | |
… | … | … | … | … | … | … | … |
1534 | 6 | 2 | 3409.468534 | 26.0 | 8.0 | 50.0 | 0.0 |
8 | 2 | 7622.453034 | 48.0 | 8.0 | 50.0 | 0.0 | |
9 | 1 | 4928.173852 | 23.0 | 8.0 | 50.0 | 0.0 | |
11 | 4 | 13350.015305 | 78.0 | 8.0 | 50.0 | 0.0 | |
12 | 1 | 2371.711192 | 16.0 | 8.0 | 50.0 | 0.0 |
11331 rows × 6 columns
Добавление столбца в агрегированный датафрейм с помесечной выручкой по каждому пользователю¶
Найдите суммарное расстояние, кличество поездок и суммарное время для каждого пользователя за каждый месяц.
# Расчет и округление выручки
users_months_go['revenue'] = (
(users_months_go['start_ride_price'] * users_months_go['count'])
+ (users_months_go['minute_price'] * users_months_go['duration'])
+ users_months_go['subscription_fee']
)
users_months_go['revenue'] = users_months_go['revenue'].astype('uint16')
# Удаление столбцов со вспомогательными данными
users_months_go = users_months_go.drop(['start_ride_price', 'minute_price'], axis=1)
users_months_go.head(10)
count | distance | duration | subscription_fee | revenue | ||
---|---|---|---|---|---|---|
user_id | month | |||||
1 | 1 | 2 | 7027.511294 | 42.0 | 199.0 | 451 |
4 | 1 | 754.159807 | 7.0 | 199.0 | 241 | |
8 | 2 | 6723.470560 | 46.0 | 199.0 | 475 | |
10 | 2 | 5809.911100 | 32.0 | 199.0 | 391 | |
11 | 3 | 7003.499363 | 56.0 | 199.0 | 535 | |
12 | 2 | 6751.629942 | 28.0 | 199.0 | 367 | |
2 | 3 | 3 | 10187.723006 | 63.0 | 199.0 | 577 |
4 | 2 | 6164.381824 | 40.0 | 199.0 | 439 | |
6 | 1 | 3255.338202 | 14.0 | 199.0 | 283 | |
7 | 2 | 6780.722964 | 48.0 | 199.0 | 487 |
Шаг 6. Проверка гипотез¶
Тратят ли пользователи с подпиской больше времени на поездки, чем без неё?¶
Нулевая и альтернативная гипотезы:
Гипотеза Н0: Средняя продолжительность поездок с подпиской ‘ultra’ и ‘free’ равна.
Гипотеза Н1: Средняя продолжительность поездок с подпиской ‘ultra’ меньше, чем с подпиской ‘free’.
results = st.ttest_ind(ultra_go['duration'], free_go['duration'], equal_var=False, alternative='less')
print(results.pvalue)
if results.pvalue < .05:
print('Отвергаем нулевую гипотезу')
else:
print('Не получилось отвергнуть нулевую гипотезу')
1.0 Не получилось отвергнуть нулевую гипотезу
Вывод. Не получилось отвергнуть гипотезу о том, что средняя продолжительность поездок с подпиской ‘ultra’ равна (или больше), чем с подпиской ‘free’.
Расстояние, которое проезжают пользователи с подпиской за одну поездку, не превышает 3130 метров?¶
3130 метров — оптимальное расстояние с точки зрения износа самоката
Нулевая и альтернативная гипотезы:
Гипотеза Н0: Среднее расстояние поездок с использованием подписки ‘ultra’ равно оптимальному расстоянию в 3130 метров.
Гипотеза Н1: Среднее расстояние поездок с использованием подписки ‘ultra’ более 3130 метров.
results = st.ttest_1samp(ultra_go['distance'], 3130, alternative='greater')
print(results.pvalue)
if results.pvalue < .05:
print('Отвергаем нулевую гипотезу')
else:
print('Не получилось отвергнуть нулевую гипотезу')
0.9195368847849785 Не получилось отвергнуть нулевую гипотезу
Вывод. Не получилось отвергнуть гипотезу о том, что среднее расстояние поездок с использованием подписки ‘ultra’ (меньше или) равно 3130 метров.
Помесчячная выручка от пользователей с подпиской выше, чем выручка от пользователей без подписки? Сделать выводы¶
Нулевая и альтернативная гипотезы:
Гипотеза Н0: Средняя выручка от поездок с разной подпиской равна.
Гипотеза Н1: Средняя выручка от поездок с использованием подписки ‘ultra’ меньше, чем выручка с ипользованием подписки ‘free’.
results = st.ttest_ind(
users_months_go.loc[users_months_go['subscription_fee'] > 0, 'subscription_fee'],
users_months_go.loc[users_months_go['subscription_fee'] == 0, 'subscription_fee'],
equal_var=False,
alternative='less'
)
print(results.pvalue)
if results.pvalue < .05:
print('Отвергаем нулевую гипотезу')
else:
print('Не получилось отвергнуть нулевую гипотезу')
1.0 Не получилось отвергнуть нулевую гипотезу
/tmp/ipykernel_324/3775444445.py:1: RuntimeWarning: Precision loss occurred in moment calculation due to catastrophic cancellation. This occurs when the data are nearly identical. Results may be unreliable. results = st.ttest_ind(
Вывод. Не получилось отвергнуть нулевую гипотезу о том, что средняя выручка от поездок с использованием подписки ‘ultra’ (больше или) равна средней выручке с ипользованием подписки ‘free’.
Значит, как минимум, выручка от подписки ‘ultra’ не меньше, чем от плитки ‘free’, и её следует развивать.
Каким тестом проверить гипотезу о снижении количества пользовательских обращений после обновления сервиса, с которым взаимодействует мобильное приложение?¶
Сначала требуется провести сравнение выборок на предмет соответствия уникальных пользователей в них.
Если списки уникальных пользователей в них совпадают, то выборки зависимы и для проверки гипотезы потребуется тест st.ttest_rel(before, after, alternative=’less’)
В ином случае, выборки не могут быть зависимы и для проверки гипотезы потребуется тест: st.ttest_ind(before, after, equal_var=False, alternative=’less’). Где, equal_var=False, т.к. выборки не равны, а alternative=’less’, т.к. основная гипотеза отвергается, если изначально обращений было меньше, чем после обновлений сервиса.
Шаг 7. Распределения¶
Задача №1. Требуется выполнить план по продлению подписки 100 клиентами путем рассылки промокодов. Ранее вероятность продления была 10%. Требуется обеспечить выполнение плана с вероятностью неуспеха не более 5%. Построить график и ответить на вопрос. Сколько нужно разослать промокодов?
# Функция расчета количества успешных экспериментов Бернулли
# путем аппроксимации биномиального распределения нормальным
def cnsbe(n, p, x):
# Расшифровка названия функции:
# cnsbe - calculation of the number of successful Bernoulli experiments
# by approximating the binomial distribution by the normal
# (расчет количества успешных экспериментов Бернулли
# путем аппроксимации биномиального распределения нормальным)
# Параметры биноминального распределения:
# n - количество экспериментов
# p - вероятность успеха
# x - вероятность расчитываемого значения
# Расчет параметров нормального распределения
mu = n * p
sigma = (n * p * (1 - p))**.5
# Расчет нормального распределения
distr = st.norm(mu, sigma)
# Расчет значения для заданной вероятности x
result = distr.ppf(x)
# Построение графика
arange = np.arange(
(mu - 4 * sigma),
(mu + 4 * sigma),
1
)
plt.plot(arange, st.norm.pdf(arange, mu, sigma), 'b-')
plt.axvline(x=result, color='red')
plt.show()
return result
cnsbe(1169, .1, .05)
100.02841955223582
Требуется разослать 1169 промокодов, которые приводят к продлению подписки с вероятностью 10%, чтобы 100 клиентов продлили подписку с вероятностью неуспеха не более 5%.
Альтернативное решение
# Функция поиска количества всех попыток экспериментов Бернули n
# которые приводят к желаемому количеству успешных попыток kn
# с определенной вероятностью kp при вероятносте успеха p
def search_binom_n(kn, kp, p):
# kn - требуемое количество успешных попыток
# p - известная вероятность успешных попыток
# kp - с какой вероятностью нужно получить kn
kp = 1 - kp
# Общее количество попыток n должно быть не менее
# количества успешных попыток kn
n = kn
# Тригер успеха
result = False
# Повторять цикл, пока количество всех попыток n не станет достаточным
# для получения требуемого количества успешных попыток kn с вероятностью kp
while result == False:
# Вероятности
distr = []
# Значения
k_nums = []
# Построение биноминального распределения
for k in range(0, n + 1):
choose = factorial(n) / (factorial(k) * factorial(n - k))
prob = choose * p**k * (1 - p)**(n - k)
distr.append(prob)
k_nums.append(k)
# Проверка параметров распределения
# на соответствие условиям
p_sum = 0
j = 0
# Сложение вероятностей полученного биноминального распределения
# до получения значения
for i in distr:
p_sum += i
j += 1
# Если сумма сложенных вероятностей равна
# ожидаемой вероятности pk получения pn,
# то завершить цикл сложения
if p_sum >= kp: break
# Если значением k при вероятности kp меньше kn,
# то увеличить n и продолжить расчеты
if k_nums[j] < kn:
n += 1
# Если найдено такое n, которое дает результат kn
# с вероятностью kp, то завершить расчеты
else:
result = True
plt.bar(k_nums, distr);
plt.show()
break
return n
k = 10
kp = .95
p = .1
print(f'Требуется произвести {search_binom_n(k, kp, p)} действия для получения {k} успешных результатов с вероятностью {kp*100}% при вероятности успеха {p*100}%.')
Требуется произвести 142 действия для получения 10 успешных результатов с вероятностью 95.0% при вероятности успеха 10.0%.
Идея фунцкии верная. Однако, для получения результата не хватает вычислительных ресурсов при расчете факториалов с заданными параметрами. Вероятно, есть схожая схема расчета, менее требовательная к вычислительным ресурсам.
С помощью созданной функции получилось произвести расчеты для выполнения плана в 10 раз меньше. Для 10 продлений подписки с вероятностью 95%, требуется разослать 142 промокода, которые приводят к продлению подписки с вероятностью 10%. Исходя из полученного результата можно предположить, что при увеличении количества плановых продлений в 10 раз, также увеличится количество разосланных промокодов приблизительно в 10 раз. Если это так, то требуется разослать 1420 промокодов.
Задача №2. Планируется разослать клиентам 1 млн. уведомлений, которые клиенты открывают с вероятностью 40%. Требуется с помощью аппроксимации построить график распределения и оценить вероятность того, что не более 399.5 тыс. клиентов откроют уведомления.
# Функция расчета вероятности проведения успешных экспериментов Бернули
# путем аппроксимации биномиального распределения нормальным
def cpsbe(n, p, x):
# Расшифровка названия функции:
# cpsbe - calculation of the probability of successful Bernoulli experiments
# by approximating the binomial distribution by the normal
# (расчет вероятности проведения успешных экспериментов Бернули
# путем аппроксимации биномиального распределения нормальным)
# Параметры биноминального распределения:
# n - количество экспериментов
# p - вероятность успеха
# x - заданное значение, для которого расчитывается вероятность
# Расчет параметров нормального распределения
mu = n * p
sigma = (n * p * (1 - p))**.5
# Расчет нормального распределения
distr = st.norm(mu, sigma)
# Расчет вероятности для заданного значения x
result = distr.cdf(x)
# Построение графика
arange = np.arange(
(mu - 4 * sigma),
(mu + 4 * sigma),
1
)
plt.plot(arange, st.norm.pdf(arange, mu, sigma), 'b-')
plt.axvline(x=x, color='red')
plt.show()
return result
cpsbe(1000000, .4, 399500)
0.15371708296369768
399.5 тыс. клиентов откроют уведомления с вероятностью 15.37%, если разослать 1 млн. уведомлений, которые открывают клиенты с вероятностью 40%.
Выводы проекта¶
Основная цель проекта достигнута. Проанализированы данные сервиса аренды самокатов GoFast и проверены некоторые гипотезы, которые могут помочь бизнесу вырасти.
Описание исходных данных
В исходных данных есть информация о пользователях, их поездках и подписках, сгруппированных в три файла.
Пользователи — ‘users_go.csv’:
- ‘user_id’ — уникальный идентификатор пользователя;
- ‘name’ — имя пользователя;
- ‘age’ — возраст;
- ‘city’ — город;
- ‘subscription_type’ — тип подписки (free, ultra).
Поездки — ‘rides_go.csv’:
- user_id’ — уникальный идентификатор пользователя;
- ‘distance’ — расстояние, которое пользователь проехал в текущей сессии (в метрах);
- ‘duration’ — продолжительность сессии (в минутах) — время с того момента, как пользователь нажал кнопку «Начать поездку» до момента, как он нажал кнопку «Завершить поездку»;
- ‘date’ — дата совершения поездки.
Подписки — ‘subscriptions_go.csv’:
- ‘subscription_type’ — тип подписки;
- ‘minute_price’ — стоимость одной минуты поездки по данной подписке;
- ‘start_ride_price’ — стоимость начала поездки;
- ‘subscription_fee’ — стоимость ежемесячного платёж.
Выполненные работы
В ходе проекта были проделаны следующие шаги:
- Загрузка данных.
- Предобработка данных.
- Корректировка типов данных столбцов.
- Добавление столбцов.
- Обработка неявных дубликатов.
- Проверка результатов предобработки данных.
- Исследовательский анализ данных:
- Частота встречаемости городов.
- Соотношение пользователей с подпиской и без нее.
- Возраст пользователей.
- Расстояние, которое пользователь преодолел за одну поездку.
- Продолжительность поездок.
- Сделаны промежуточные выводы исследовательского анализа.
- Объединение данных.
- Объединение данных о пользователях, поездках и подписках в один датафрейм методом merge().
- Создание двух датафреймов с пользователями, имеющими подписку и без нее, на основе общего датафрейма.
- Визуализация данных из датафреймов, разделенных на пользователей с подпиской и без.
- Подсчёт выручки.
- Создание датафрейма, агрегированного по пользователям, на основе ‘total_go’
- Добавление столбца в агрегированный датафрейм с помесечной выручкой по каждому пользователю
- Проверка гипотез:
- Тратят ли пользователи с подпиской больше времени на поездки?
- Расстояние, которое проезжают пользователи с подпиской за одну поездку, не превышает 3130 метров?
- Помесчячная выручка от пользователей с подпиской по месяцам выше, чем выручка от пользователей без подписки? Сделать выводы.
- Каким тестом проверить гипотезу о снижении количества пользовательских обращений после обновления сервиса, с которым взаимодействует мобильное приложение?
- Распределения:
- Задача №1.
- Задача №2.
Выводы
Все исходные данные во всех файлах не содержали пропусков, значительных выбросов, а также дубликатов, кроме тех, которые были обнаружены в файле ‘users_go.csv’. При копировании данных из файлов в датафреймы, данные столбцов были не в оптимальном состоянии. В ходе предобработки данных они были оптимизированы для целей данного проекта.
В ходе исследовательского анализа данных было выявлено:
- Пользователей с подпиской «free» всего 835, что составляет 54.43% от общего количества и на 136 больше, чем с подпиской «ultra».
- Услугами сервиса аренды самокатов пользуются люди от 12 до 43 лет. Максимальное количество пользователей в возрасте 25 лет, этот же возраст является медианным.
- Большая часть всех пользователей используют подписку «free». Всего их 835, что составляет 54.43% от общего количества и на 136 больше, чем с подпиской «ultra».
- Максимальное количество в 219 поездок совершено в Пятигорске. Минимальное количество в 168 поездок совершено в Москве.
- За одну поездку пользователи преодолевают от 1 до 7211 метров. Медиана всех значений равна 3134 метров. Наблюдаются два пика. Малый пик около 700 метров, большой пик около 3200 метров. Между пиками значения нормально распределены.
- Продолжительность поездок нормально распределена от 0 до 41 минуты. Продолжительность в 18 минут является как наиболее распространенной так и медианным значением. Наблюдается выброс размером менее 100 поездок с продолжительностью в 1 минуту.
В результате группировки данных выявлено:
- Поездок с исползьзованием подписки ‘free’ на 5068 больше, чем с использованием подписки ‘ultra’, и составляет 11568.
- Возрастные показатели пользователей с разным типом подписки в целом схожи, за исключением максимального возраста, который на 5 лет больше у пользователей с подпиской ‘free’.
- Дистанция поездок при использовании подписки ‘free’ более разнообразна. Так максимальная дистанция больше на 1511 метров, а минимальная на 243 метра меньше, чем при использовании подписки ‘ultra’. Об этом же свидетельствует и стандартное отклонение, которое при использовании подписки ‘free’ больше на 409 метров, чем при использовании подписки ‘ultra’. Средняя дистанция при использовании подписки ‘free’ короче. Это следует из того, что среднее значениее на 70 метров, а медиана на 34 метра, меньше, чем при использовании ‘ultra’. При использовании обоих подписок наблюдается два пика. Первый небольшой пик на меньших дистанциях и второй основной пик на больших дистанциях. Так меньший пик ‘ultra’ находится в районе 1000 метров, а основной в районе 3100 метров с небольшой коррекцией около 4000 метров. Меншьй пик ‘free’ в районе 700 метров, а основной 2500-3500 метров. В целом пики ‘ultra’ более выраженные и имеют меньший разрбос.
- Продолжительность поездок при использовании подписки ‘free’ немного меньше по всем показателям, чем при использовании ‘ultra’. Например, минимальное время меньше на 3.26 минут, а максимальное меньше на 5.88 минут. В обоих случаях количество поездок относительно нормально распределено в зависимости от их продолжительности. Пик количества поездок приходится на 17-18 минут. При использовании подписки ‘free’ наблюдается пик в около 100 поездок с продолжительностью около 1 минуты.
- Сезонность использования разного типа подписки схожа за исключением медианы, которая у подписчиков ‘ultra’ приходится на июнь, а у подписчиков ‘free’ на июль. При использовании обоих типов подписок наблюдаются спады пользовательской активности в феврале и ноябре. Максимум поездок с подпиской ‘ultra’ в мае (597 поездок, это 3.3% от всех поездок). Максимумы поездок с подпиской ‘free’ в январе (1001 поездка, это 5.54% от всех поездок), сентябре (1002 поездки) и декабре (1003 поездки). При использовании подписки ‘free’ нет выраженной сезонности, кроме спдов в феврале и ноябре. При использовании подписки ‘ultra’ наблюдается более выраженная сезонность. Количество поездок растет с февраля по май, а затем медленно снижается до ноября.
- Во всех городах доминирует подписка ‘free’. Пятигорск является лидером по использованию этиго типа подписки (1672 поездок). Ростов-на-Дону является лидером по использованию подписки ‘ultra’ (885 поездок).
Не получилось отвергнуть ни одну из выдвинутых нулевых гипотез. Например, гипотезу о том, что средняя продолжительность поездок с подпиской ‘ultra’ равна (или больше) средней продолжительности с подпиской ‘free’. Или гипотезу о том, что среднее расстояние поездки с использованием подписки ‘ultra’ равно (или менее) 3130 метрам. Или гипотезу о том, что средняя выручка от поездок с использованием подписки ‘ultra’ равна (или больше) средней выручке с ипользованием подписки ‘free’.
В задачах на распределение подсчитано:
- Требуется разослать 1169 промокодов, которые приводят к продлению подписки с вероятностью 10%, чтобы 100 клиентов продлили подписку с вероятностью неуспеха не более 5%.
- 399.5 тыс. клиентов откроют уведомления с вероятностью 15.37%, если разослать 1 млн. уведомлений, которые открывают клиенты с вероятностью 40%.