Практический пример того, как наука о данных приносит пользу вашему бизнесу

Обзор

Понимание оттока клиентов (отмены) услуги и выявление пользователей, которые могут уйти, являются ключевыми проблемами бизнеса во многих отраслях. Это требует анализа данных, но в некоторых отраслях это означает работу с большим объемом данных. Подумайте о потоковых сервисах (Spotify, Apple Music и т. д.). Каждое взаимодействие с сервисом миллионов пользователей по всему миру приводило к созданию записей данных. Представьте себе задачу хранения и анализа таких больших данных. Apache Spark — это аналитический движок, созданный для такой сложной крупномасштабной обработки данных.

В моем завершающем проекте я пытаюсь проанализировать отток клиентов для гипотетического сервиса потоковой передачи музыки под названием Sparkify. Как лица, принимающие решения в этих поставщиках услуг потоковой передачи музыки, мы хотели бы понять факторы, способствующие такому взбалтыванию, которые будут служить основной целью этого тематического проекта.

Понимание бизнеса

Sparkify(tm) — это веб-служба потоковой передачи музыки, которая требует от пользователей регистрации и входа в систему и имеет два типа пользовательских подписок:

  1. Бесплатная подписка:
    пользователи слушают музыку бесплатно, но также будут слушать рекламу.
  2. Платная подписка:
    платные пользователи этой подписки слушают песни без рекламы.

Подписанный пользователь в любой момент времени может выполнять одно из следующих действий:

  1. Перейти с бесплатной подписки на платную
  2. Перейти с платной подписки на бесплатную
  3. Отменить подписку

Мы хотим определить пользователей, которые могут потенциально отменить свою подписку. Заранее определяя эту группу населения, мы могли бы предоставить им скидки или другие вознаграждения, которые, как мы надеемся, могли бы превратить их в базу лояльных клиентов, что является ключевым фактором для роста бизнеса компании.

Понимание данных

Данные, которые мы получили от Sparkify, представляют собой лог-файл пользовательских событий в формате JSON. Каждое взаимодействие каждого пользователя с приложением отдано нам. Это означает, что каждый раз, когда пользователь переходит на домашнюю страницу, слушает песню, ставит палец вверх по песне и т. д., у нас есть соответствующее событие в данных. Каждая запись в журнале представляет собой событие пользователя.

Полный набор данных — это журнал пользователей, который хранится в файле json размером 12 ГБ. С такими размерами легко справится мощная инфраструктура Spark. Но поскольку у нас ограниченная виртуальная инфраструктура, подмножество, представляющее набор данных (128 МБ), будет использоваться для исследовательского анализа данных.

Мы загрузим наш набор данных с помощью следующей команды:

df = spark.read.json("mini_sparkify_event_data.json")

Скрытые 80 % усилий. Большая часть времени во многих работах специалистов по данным (и в моей работе над этим проектом) уходит на «инженерию данных». Идентификация пропущенных значений, очистка данных (когда userID = ‘ ’) и преобразование формата (для даты/времени). Это наименее привлекательная часть, если ее представить аудитории, но это важная основа, на которую будет опираться любой анализ данных.

Вернемся к пониманию данных..

Ключевые атрибуты в каждой записи о событии в журнале:

  1. userId : уникальный идентификатор, идентифицирующий пользователя службы. В нашем наборе данных у нас 226 пользователей.
  2. уровень . Это может быть бесплатный или платный . Указание того, была ли подписка пользователя платной или бесплатной при запуске этого события.
  3. sessionId: уникальный идентификатор определяет один непрерывный период использования пользователем службы.
  4. песня : песня, которую слушал пользователь.
  5. artist : исполнитель, создавший песню.
  6. ts : метка времени в эпохах для этого события.
  7. страница : какую страницу посещал пользователь, когда возникло это событие. Различные типы страниц подробно описаны в разделе ниже:
+--------------------+------+
|                Page| count|
+--------------------+------+
|              Cancel|    52|
|    Submit Downgrade|    63|
|         Thumbs Down|  2546|
|                Home| 10082|
|           Downgrade|  2055|
|         Roll Advert|  3933|
|              Logout|  3226|
|       Save Settings|   310|
|Cancellation Conf...|    52|
|               About|   495|
|            Settings|  1514|
|     Add to Playlist|  6526|
|          Add Friend|  4277|
|            NextSong|228108|
|           Thumbs Up| 12551|
|                Help|  1454|
|             Upgrade|   499|
|               Error|   252|
|      Submit Upgrade|   159|
+--------------------+------+

Что определяет ушедших пользователей Sparkify?

Он определяется событием посещения пользователем страницы «Подтверждение отмены». Это указывает на то, что пользователи отменили свою учетную запись и покинули платформу. По результату видно, что ушли 52 пользователя (из 225).

Разработка функций

Мы успешно определили ушедших пользователей, теперь пришло время подумать о факторах, способствующих оттоку пользователей. Функции, значения которых, по-видимому, значительно различаются между пользователями, которые ушли, и теми, кто этого не сделал. Наш анализ сосредоточился на следующем:

  • Отношение уровня подписки пользователя к взбалтыванию:

  • Средняя продолжительность сеанса в часах
+-----+------------------+
|churn|       avg(length)|
+-----+------------------+
|    1| 248.6327956440622|
|    0|249.20913538880814|
+-----+------------------+
  • Распределение продолжительности сеансов между удаленными и неиспользованными.
    В среднем одинаково, но мы включим его в нашу модель, так как распределение между обеими группами различается.

  • Распределение количества песен в сессии между обеими группами.

  • Среднемесячные и ежедневные сеансы
+-----+---------------------+
|churn|avg(monthly_sessions)|
+-----+---------------------+
|    1|   10.472394347360913|
|    0|   14.987535542315017|
+-----+---------------------+

+-----+-------------------+
|churn|avg(daily_sessions)|
+-----+-------------------+
|    1|  1.427520820895794|
|    0| 1.6932722878596427|
+-----+-------------------+

Пользователи, которые прекратили отток, приходили на платформу 10 раз в месяц, а оставшиеся — около 15 раз. Эти результаты зависят от «TimeStamp», поэтому он также будет включен в модель.

  • Поведение пользователей в приложении:
    - количество понижений
    - обновлений
    - лайков-< br /> - поставить палец вниз
    - добавить друга
    - добавить в плейлист
    - роликовая реклама
    Эти функции фиксируют скрытую переменную, связанную с вовлеченностью пользователей, при этом более низкая вовлеченность связана с более высокой вероятностью оттока. Поэтому будет включен в модель.
  • Расположение пользователей «Штат»
+-----+-----+-----+
|churn|state|count|
+-----+-----+-----+
|    0|   CA|39158|
|    0|   PA|23708|
|    0|   TX|22200|
|    0|   NH|18637|
|    0|   FL|11427|
|    0|   NC|10572|
|    0|   WI| 8097|
|    0|   SC| 7954|
|    0|   NJ| 7816|
|    0|   IN| 7312|
|    0|   MD| 6752|
|    0|   CT| 6720|
|    0|   VA| 6514|
|    0|   WV| 6192|
|    0|   IL| 6112|
|    0|   MI| 5258|
|    0|   AZ| 4595|
|    0|   GA| 4236|
|    0|   NY| 4079|
|    0|   AK| 3563|
+-----+-----+-----+
only showing top 20 rows

+-----+-----+-----+
|churn|state|count|
+-----+-----+-----+
|    1|   CA| 7613|
|    1|   CO| 4317|
|    1|   MS| 3839|
|    1|   WA| 3526|
|    1|   OH| 3173|
|    1|   KY| 3016|
|    1|   PA| 2899|
|    1|   AL| 2102|
|    1|   MI| 1958|
|    1|   MD| 1848|
|    1|   FL| 1763|
|    1|   TX| 1294|
|    1|   NC| 1199|
|    1|   MO| 1003|
|    1|   IN|  918|
|    1|   IL|  848|
|    1|   WI|  600|
|    1|   AR|  582|
|    1|   KS|  513|
|    1|   SC|  494|
+-----+-----+-----+
only showing top 20 rows
  • Пользовательские агенты (браузер)
+-----+--------------------+-----+
|churn|           userAgent|count|
+-----+--------------------+-----+
|    0|"Mozilla/5.0 (Win...|18226|
|    0|"Mozilla/5.0 (Mac...|16298|
|    0|"Mozilla/5.0 (Mac...|15914|
|    0|"Mozilla/5.0 (Win...|15237|
|    0|Mozilla/5.0 (Wind...|15224|
|    0|"Mozilla/5.0 (Mac...|14875|
|    0|"Mozilla/5.0 (Win...|12823|
|    0|"Mozilla/5.0 (iPa...| 8912|
|    0|Mozilla/5.0 (comp...| 8624|
|    0|Mozilla/5.0 (Maci...| 7838|
|    0|"Mozilla/5.0 (Mac...| 7638|
|    0|"Mozilla/5.0 (Win...| 6531|
|    0|"Mozilla/5.0 (Mac...| 6165|
|    0|"Mozilla/5.0 (Mac...| 5716|
|    0|"Mozilla/5.0 (Win...| 5553|
|    0|"Mozilla/5.0 (iPh...| 5407|
|    0|"Mozilla/5.0 (Win...| 5238|
|    0|Mozilla/5.0 (Wind...| 4925|
|    0|"Mozilla/5.0 (Win...| 3985|
|    0|"Mozilla/5.0 (Win...| 3432|
+-----+--------------------+-----+
only showing top 20 rows

+-----+--------------------+-----+
|churn|           userAgent|count|
+-----+--------------------+-----+
|    1|"Mozilla/5.0 (Mac...| 4736|
|    1|"Mozilla/5.0 (Win...| 4525|
|    1|Mozilla/5.0 (Wind...| 3437|
|    1|"Mozilla/5.0 (Mac...| 2534|
|    1|Mozilla/5.0 (Maci...| 2462|
|    1|"Mozilla/5.0 (Win...| 2168|
|    1|Mozilla/5.0 (Wind...| 2149|
|    1|"Mozilla/5.0 (Win...| 2071|
|    1|"Mozilla/5.0 (Mac...| 2064|
|    1|"Mozilla/5.0 (Mac...| 1929|
|    1|Mozilla/5.0 (Wind...| 1781|
|    1|"Mozilla/5.0 (Win...| 1775|
|    1|Mozilla/5.0 (X11;...| 1557|
|    1|Mozilla/5.0 (Maci...| 1502|
|    1|Mozilla/5.0 (Wind...| 1476|
|    1|"Mozilla/5.0 (Win...| 1392|
|    1|Mozilla/5.0 (Wind...| 1102|
|    1|Mozilla/5.0 (Wind...| 1064|
|    1|"Mozilla/5.0 (Mac...| 1050|
|    1|"Mozilla/5.0 (iPh...| 1010|
+-----+--------------------+-----+
only showing top 20 rows

Построение модели

После того, как мы разработали соответствующие функции, мы протестируем характеристики подогнанных моделей и выберем ту, которая имеет наилучшие характеристики, в качестве окончательной модели. Эти модели классификации:

  1. Логистическая регрессия
  2. Случайный лес
  3. Деревья, усиленные градиентом

На начальном этапе я тестировал следующие алгоритмы, но решил их не использовать

  • BinaryClassificationEvaluator: не подходит для этого набора данных.
  • Машина опорных векторов: время выполнения очень велико. Бежать вечность. Я ждал несколько часов безрезультатно, и много раз мой ноутбук заканчивал работу.

Чтобы избежать написания избыточного кода, мы начнем с создания функции специально для этой цели:

# Function to train and estimate a model
def fitEstimate(train, test, model):
    '''
    Fits and estimates different classification models with the default parameters
    
    INPUT:
    train (Spark df): a Spark data frame with training data
    test (Spark df): a Spark data frame with testing data
    model (string): a string specifing the models to fitted
    
    OUTPUT:
    None, prints accuracy of the model
    '''
    # Choose the model
    if model == 'logistic_regression':
        ml = LogisticRegression()
    elif model == 'random_forest':
        ml = RandomForestClassifier()
    elif model == 'gradient_boosting':
        ml = GBTClassifier()
    else:
        return "Choose appropriate model"
    
    # Fit and calculate predictions
    classification = ml.fit(train)
    results = classification.transform(test)
    
    # Calculate accuracy and F-1 score
    accuracy_evaluator = MulticlassClassificationEvaluator(metricName='accuracy')
    accuracy = accuracy_evaluator.evaluate(results.select(col('label'), col('prediction')))
    
    f1_score_evaluator = MulticlassClassificationEvaluator(metricName='f1')
    f1_score = f1_score_evaluator.evaluate(results.select(col('label'), col('prediction')))
    
    print('{} accuracy on the test set is {:.2%} and the F-1 score is {}'\
    .format(model, accuracy, f1_score))

Затем мы разделяем данные на наборы для обучения, проверки и тестирования:

# Split this data between train, validation and test sets
train, test = model_data.randomSplit([0.8, 0.2], seed=42)

# Due to the class imbalance, we upsample the categories who churned in the training dataset
print('In our training set, before upsampling we have {} users who churned and {} who did not.'.format(train.where(col('label') == 1).count(), train.where(col('label') == 0).count()))

train_churn = train.where(col('label') == 1).sample(True, train.where(col('label') == 0).count()/train.where(col('label') == 1).count())
train_no_churn = train.where(col('label') == 0)
train = train_churn.unionAll(train_no_churn)

Оценка модели

Мы вызовем указанную выше функцию для оценки трех моделей классификации:

# Fit various models and evalute their accuracies
for model in ['logistic_regression', 'random_forest', 'gradient_boosting']:
    fitEstimate(train, test, model)

Результаты, которые мы получили:

  • Логистическая регрессия
    Точность: 58,82 %
    Оценка F-1: 0,6003016591251885
  • Случайный лес
    Точность: 70,59%
    Оценка F-1: 0,6973039215686275
  • Усиление градиента
    Точность: 76,47%
    Оценка F-1: 0,7578431372549018

Чтобы выбрать лучшую из них, мы используем Оценку F1, поскольку она дает равный вес как точности, так и отзыву. В этом контексте это имеет наибольший смысл, поскольку и точность, и полнота важны с точки зрения бизнеса.

Организация собирается тратить деньги на вознаграждение людей, которые, по прогнозам, уйдут. Поэтому высокая точность является ключом к эффективному управлению капиталом. В долгосрочной перспективе без точности прогнозирования организация потеряет ценных клиентов и прибыль.

Лучшей моделью из вышеперечисленных является классификатор Gradient Boosting. Он обеспечивает оценку F-1 0,76.

Будущие улучшения

  • Запустите код на полном наборе данных с помощью облачного сервиса
    Честно говоря, результаты моделей нестабильны. В том смысле, что каждый раз, когда мы запускаем Fit-Estimator, мы получаем немного разные числа. Окончательная оценка была получена после получения оценок в пределах диапазона по нескольким рунам. Это может быть связано с небольшой выборкой подмножества набора данных (128 МБ из 12 ГБ), поскольку у нас ограниченная память и мощность процессора. Я бы рекомендовал запускать код на полном наборе данных, используя облачный сервис, такой как AWS, Azure, ..
  • Больше внимания функциям поведения пользователей
    Например, если у пользователя изначально была платная подписка, затем он перешел на бесплатную подписку, а затем вернулся обратно. Это не отслеживается в нашем анализе. Было бы разумнее добавить функцию, отслеживающую смену подписки пользователем. Также может помочь отслеживание влияния поведения друга на пользователя, хотя его анализ сложен.
  • Роль системы рекомендаций в обеспечении лояльности клиентов
    Использование пользовательского поведения, такого как поднятие/опускание большого пальца, поскольку оно отражает общее отношение к услуге в целом и указывает на снижение удовлетворенности и вероятность отказа от услуги. Именно здесь добавление системы рекомендаций поможет компаниям сократить отток клиентов. Это выходит за рамки этого проекта, но я подчеркиваю это, потому что работа над этим проектом заставила меня осознать важность такой системы.

Вывод

Мы прошли путь, в котором Spark использовался для анализа больших данных. Изучаемый здесь бизнес-кейс — прогноз оттока клиентов. Это было достигнуто после прохождения процесса науки о данных, который включает в себя исследовательский анализ данных, разработку функций и моделирование. Весь код, используемый для этого проекта, можно найти на моем GitHub.