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

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

Эти данные можно найти на github, и они содержат моделирование поведения клиентов в мобильном приложении Starbucks rewards, как это происходит в реальной жизни. Раз в несколько дней Starbucks рассылает предложения пользователям мобильного приложения.

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

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

Набор данных

Набор данных разделен на три файла JSON:

  • портфолио: содержит идентификаторы предложений и метаданные о каждом предложении (продолжительность, тип и т. д.)
  • профиль: демографические данные для каждого клиента.
  • стенограмма: записи транзакций, полученных предложений, просмотренных предложений и завершенных предложений.

Каждый файл посвящен теме процесса разработки функций. Наша конечная цель - направить три файла в комплексный фреймворк пользовательских функций и матрицу пользовательских элементов. Мы будем использовать библиотеки python 3.x, такие как pandas (pd), numpy (np), matplotlib (plt ) и seaborn (sns).

портфолио

Первый файл касается предложений, которые могут быть просто рекламой напитка или фактическим предложением, таким как скидка или покупка одного и получения одного бесплатно (BOGO). Некоторые пользователи могут не получать никаких предложений в течение определенных недель.

Загружаем портфолио методом pandas.read_json

Мы можем отображать портфолио в едином представлении:

Ниже приводится интерпретация наших исходных столбцов, рекомендуется использовать pandas.dtypes в начале для подтверждения типов данных.

  • id (строка) - идентификатор предложения
  • offer_type (строка) - тип предложения, т. е. BOGO, скидка, информационное
  • сложность (int) - минимальные затраты, необходимые для завершения предложения.
  • reward (int) - награда за выполнение предложения
  • duration (int) - время открытия оферты в днях
  • каналы (список строк)

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

Мы используем встроенные в python zip и dict constructor для сопоставления предложений с целыми числами. Мы создаем словарь для другого направления, если нам нужно вернуться назад.

Более того, мы видим, что channel может принимать только четыре значения: электронная почта, мобильный телефон, медиа и социальные сети. Учитывая, что это список, вложенный в столбец, мы используем pandas.get_dummies и pandas.Series:

В результате получается гораздо более читаемая таблица, где 1 означает истину, а 0 - ложь. Мы применяем тот же метод к функции offer_type. Мы применяем тот же метод к столбцу offer_type и создаем таблицу offer_type_dum.

Что касается награды и сложности, мы мало знаем о юнитах. Вознаграждение выражено в долларах США или в монетах приложения? Как измеряется сложность? Неопределенность - это часть процесса науки о данных, в этом случае мы оставим две функции без изменений.

Мы преобразуем продолжительность в часы, так как позже мы увидим, что она будет соответствовать единицам в наборе данных транскрипции. Мы можем создать чистый портфель с помощью pandas.concat, replace и reset_index:

В результате получается гораздо более чистый стол, который может стоять самостоятельно.

Мы можем использовать эти очищенные данные для создания тепловой карты корреляции Пирсона с помощью нескольких строк кода:

Тепловая карта выявляет некоторые интересные корреляции, например, предложения BOGO обычно коррелируют с более высокими вознаграждениями.

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

Профиль пользователя

Мы загружаем данные профиля так же, как и портфолио, ниже приведен пример снимка экрана:

У нас есть в общей сложности 17000 наблюдений в следующем пространстве функций:

  • age (int) - возраст покупателя (118, если неизвестно)
  • created_member_on (int) - дата, когда клиент создал учетную запись приложения
  • пол (str) - пол клиента (обратите внимание, что некоторые записи содержат "O" вместо M или F)
  • id (str) - идентификатор клиента
  • доход (плавающий) - доход клиента

С первого взгляда мы видим, что отсутствующие значения здесь вызывают беспокойство. Мы используем методы pandas.isna и pandas.mean, чтобы увидеть часть пропущенных значений:

Интересно, что и доход, и пол имеют одинаковую долю пропущенных значений. Может быть, для одних и тех же пользователей мы ничего не знаем о возрасте, поле и доходе? Мы можем проверить гипотезу с помощью утверждения assert:

Утверждение проходит: оказывается, это одни и те же пользователи.

Имея дело с отсутствующими значениями, мы должны спросить, каковы дисперсия и смещение, которые мы вносим в модель. Удаление всех пропущенных значений может лишить модель полезной информации. Что касается дохода и возраста, мы смотрим на распределение данных без пропущенных значений.

Мы видим, что среднее значение и медиана почти одинаковы. Следовательно, если мы вменяем медиану, мы не ожидаем изменения ни среднего, ни дисперсии.

Для пола вопрос более деликатный. В 2018 году Amazon отказалась от своего инструмента найма на основе ИИ из-за предвзятого отношения к женщинам. Пол указывается самостоятельно, мы будем считать, что те, кто ответил другой, относятся к той же категории, что и те, кто не желает отвечать.

Пользователь id представляет собой последовательность строк без определенного значения, мы можем создать словарь human_to_int, как и раньше.

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

Стенограмма

Окончательный набор данных также является наиболее интересным и сложным. Ниже представлен образец стенограммы:

Особенности:

  • событие (str) - описание записи (т.е. транзакция, полученное предложение, просмотренное предложение и т. д.)
  • person (str) - идентификатор клиента
  • time (int) - время в часах с момента запуска теста. Данные начинаются в момент времени t = 0
  • значение - (словарный запас) - либо идентификатор предложения, либо сумма транзакции в зависимости от записи

Мы можем сказать, что стенограмма потребует большой чистки. В качестве первого шага полезно применить метод get_dummies к столбцу event.

Мы можем разбить значения в столбце значений, превратив его в ряд.

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

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

Теперь мы можем создать фрейм данных transcript_comb, который объединяет эти столбцы и использует словари human_to_int и offer_to_int. Кроме того, мы предлагаем использовать pandas.map вместо pandas.replace для повышения производительности.

Объединенный фрейм данных transcript_comb выглядит так:

Мы можем исследовать человека 2 и ознакомиться с его историей:

Обратите внимание, что часы не начинают тикать в time = 0. Это конфликтует с таблицей данностей, напомните, чтобы всегда проверять утверждения и не предполагайте истинность, пока это не будет доказано. У пользователя также есть покупки, которые были совершены до предложения, и у нас нет точного способа узнать, были ли совершены последующие покупки из-за предложения.

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

Рассмотрим другого человека, показанного ниже: человек 35. Этот клиент получил предложение в time = 0, открыл его сразу и сделал это в течение 6 часов.

С другой стороны, они получили предложение в time = 168, открыли его сразу, получили другое предложение в time = 336, открыли его снова и потратили деньги в течение этого периода. не заполнив ни одно предложение.

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

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

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

Рассмотрим одного пользователя, который получит два предложения O1 и O2, так что время записывается в момент времени T0, когда получен O1. В T0 пользователь не находится под влиянием какого-либо предложения. Начиная с T1, пользователь видел O1, и поэтому любые покупки совершаются из-за O1.

В T1 пользователь также получает O2, но еще не видел его. В момент T2 видно предложение O2, однако пользователь все еще находится под влиянием O1. Только по истечении срока действия O1 мы можем считать покупку совершенной из-за O2.

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

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

Мы начинаем с попарной функции, которая возвращает каждую часть итерации.

Мы определяем person_fill_na.py для присвоения идентификатора предложения транзакциям следующим образом:

Обратите внимание, что в приведенной выше функции мы определили time_to_open и time_to_complete как доли продолжительности предложения. Действительно, имеет смысл измерять реакцию пользователя относительно предложения, а не в абсолютном выражении.

Мы основываем вышеупомянутую функцию с помощью transcript_fill_na.py.

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

В будущих приложениях мы можем обрабатывать стенограмму одной строкой:

Собираем все вместе

Создать фрейм данных пользователя

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

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

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

  • amount_pct: процент денег, потраченных на продвижение
  • visible_ratio: соотношение просмотренных и полученных предложений.
  • completed_ratio: отношение завершенных предложений к полученным.

Мы также заполняем time_to_complete и time_to_open значением 1, так что 0 указывает на очень высокую частоту отклика, а 1 - на низкий уровень отклика.

User_df теперь представляет собой обширный набор функций профиля каждого пользователя и его привычек в отношении расходов. На рисунке ниже показаны некоторые из 23 функций.

Матрица элементов пользователя

Создание матрицы пользовательских элементов менее сложное, поскольку мы используем transcript_full из функции очистки:

Результирующий фрейм данных user_by_item:

Резюме

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

Цитируя Кэти О’Нил и Рэйчел Шутт, специалист по анализу данных тратит свое время на:

Как данные будут использоваться для принятия решений и как они будут встроены в продукт […] Она тратит много времени на сбор, очистку и анализ данных, потому что данные никогда не бывают чистыми . Этот процесс требует настойчивости, статистики и навыков разработки программного обеспечения - навыков, которые также необходимы для понимания предвзятости в данных и для отладки вывода журнала из кода.

Надеюсь, эта статья была вам полезна! Особая благодарность Udacity и Starbucks за то, что они объединились и сделали эти данные доступными.