websitelytics

Menu

Эксплораторный анализ данных c Python & Pandas в Jupyter notebook

Опубликовано: 21 фев 2019

Данный пример иллюстрирует, как можно анализировать данные веб аналитики с помощью Jupyter notebook и сделать некоторые отчеты автоматическими в данной среде.

Почему Jupyter notebook? Да, конечно, существует целый ряд замечательных инструментов специально предназначенных для аналитики и визуализации данных, такие как, Google Data Studio, Tableau, Power BI и другие, в которых наверное большую часть описанного в данной статье можно было бы реализовать более простыми шагами и где-то нагляднее. Что же предлагает данный метод, чего нет в перечисленных инструментах и что может стать дополнительной ценностью для аналитика данных?

Часть из перечисленных инструментов платные, что уже может повлиять на выбор в каких-то случаях. Но наверно основное, что предлагает данный метод — это больше возможностей для интерактивного анализа. Под интерактивным анализом я подразумеваю первую стадию исследования данных, когда возможно вы еще не знаете, как в точности будет сформирован ваш конечный отчет, и как на его вид повлияют промежуточные результаты. Или же возможно, вы хотите посмотреть на данные с разных сторон, получив их, к примеру, через разные алгоритмы или разные API прежде чем собирать результирующий отчет.

В этом примере мы будем анализировать данные Google Analytics из таблицы BigQuery, созданной через стриминг хитов в BigQuery с проектом в Google Cloud Platform (GCP), но подход естественно может быть применен к анализу любых других источников данных.

Чтобы не описывать базовые элементы, фигурирующие в этой статье, вот несколько ссылок для ознакомления:

Подразумевается, что у читателя есть некоторые представления о Python и Pandas. В данном примере также будут упоминаться некоторые элементы Google Cloud Platform (GCP). В качестве облачной платформы для работы с Jupyter notebook выбрана Microsoft Azure Notebooks.

Несколько предварительных действий

Чтобы иметь возможность обращаться к нашей таблице в BigQuery из Jupyter notebook, нам необходимо в нашем проекте в GCP создать так называемый ключ сервисного аккаунта и скачать соответствующий json файл для процесса авторизации. Это можно сделать, следуя инструкциям на странице введение в авторизацию в GCP.

Далее нам нужно зарегистрироваться в облачной платформе Microsoft Azure Notebooks, создать там проект и наш первый Python notebook:

azure notebook

Для наших целей нужно создать Python 3.6 notebook без публичного доступа, поскольку следующим шагом следует поместить json файл нашего сервисного ключа GCP в папку проекта ноутбука, и естественно к нему не должно быть доступа из вне. Параметры проета можно задавать и менять в разделе Project Settings.

Установка необходимых библиотек

Для решения задачи нам нужно будет установить несколько дополнительных пакетов. В первую очередь давайте установим библиотеки BigQuery:

In [1]:
%%capture
!pip install --upgrade google-cloud-bigquery

Проверить установился ли пакет можно с помощью следующей команды. Если установка успешна команда выведет данные о пакете google-cloud-bigquery:

In [2]:
!pip show google-cloud-bigquery
Name: google-cloud-bigquery
Version: 1.9.0
Summary: Google BigQuery API client library
Home-page: https://github.com/GoogleCloudPlatform/google-cloud-python
Author: Google LLC
Author-email: googleapis-packages@google.com
License: Apache 2.0
Location: /home/nbuser/anaconda3_501/lib/python3.6/site-packages
Requires: google-api-core, google-cloud-core, google-resumable-media
Required-by: 

Теперь можно импортировать данную библиотеку в ноутбук и авторизоваться для работы с API BigQuery с помощью сервисного ключа:

In [3]:
from google.cloud import bigquery
import os
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'yourkey.json'
client = bigquery.Client()
%load_ext google.cloud.bigquery

Эксплораторный анализ

После авторизации в GCP & BigQuery мы можем перейти к эксплораторному интерактивному анализу, продвигаясь при необходимости от простых запросов к более сложным, стараясь найти решение или оптимизировать алгоритм. Для иллюстрации, давайте, например, простым запросом узнаем количество посетителей за прошедшие 7 дней:

In [4]:
%%bigquery --project yourproject
SELECT
   COUNT(DISTINCT clientId) users
FROM
  `yourproject.gastreaming.hits`
WHERE
   _PARTITIONDATE BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY) AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
Out[4]:
users
0 148802

Как вы могли заметить, мы используем magic function %%bigquery --project yourproject, которая позволяет нам писать запросы к API BigQuery с минимумом кода, т.е. практически так же, как мы бы это делали в интерфейсе BigQuery и сразу помещать ответ в pandas dataframe.

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

In [5]:
%%bigquery --project yourproject
SELECT
   COUNT(DISTINCT clientId) users
FROM
  `yourproject.gastreaming.hits`
WHERE
   _PARTITIONDATE BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY) AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
   AND REGEXP_CONTAINS(page.path,'popup')
Out[5]:
users
0 7347

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

Открываем файл с нашим SQL запросом и сохраняем его содержимое в переменную:

In [6]:
f=open("funnel.sql", "r")
sql = f.read()

Отправляем SQL запрос в BigQuery и записываем ответ в pandas datafame:

In [7]:
df = client.query(sql).to_dataframe()
df.head()
Out[7]:
date base_clientId login_clientId reg_clientId checkout_clientId
0 2019-02-19 1853171964.1525565236 1853171964.1525565236 None None
1 2019-02-14 177021085.1550175576 None None None
2 2019-02-12 1164938621.1549465343 1164938621.1549465343 None None
3 2019-02-13 1343696691.1542957416 1343696691.1542957416 None None
4 2019-02-07 615289473.1486555774 615289473.1486555774 None None

Данный запрос был сконструирован в ходе интерактивного анализа, в результате он дает список пользовательских clientId на разных этапах прохождения воронки. Этапы отражены в таблице слева направо.

Чтобы найти количество пользователей на каждом этапе воронки в течение каждого дня на выбранном интервале найдем количество пользовательских clientId с помощью функций агрегирования:

In [8]:
fdaily=df.groupby('date').agg({'base_clientId' : 'count','login_clientId' : 'count',
                               'reg_clientId' : 'count','checkout_clientId' : 'count'}).reset_index()
fdaily.head()
Out[8]:
date base_clientId login_clientId reg_clientId checkout_clientId
0 2019-02-07 582 441 201 134
1 2019-02-08 475 351 165 112
2 2019-02-09 313 242 114 70
3 2019-02-10 361 272 140 85
4 2019-02-11 555 420 206 131

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

In [9]:
fdaily = fdaily[['date','base_clientId','login_clientId','reg_clientId','checkout_clientId']]
fdaily = fdaily.rename(columns={"reg_clientId": "users on registration page", "checkout_clientId": "users on checkout page", 
                                "base_clientId": "users visited funnel", "login_clientId": "users on login page"})
fdaily.head()
Out[9]:
date users visited funnel users on login page users on registration page users on checkout page
0 2019-02-07 582 441 201 134
1 2019-02-08 475 351 165 112
2 2019-02-09 313 242 114 70
3 2019-02-10 361 272 140 85
4 2019-02-11 555 420 206 131
In [10]:
fdaily.tail()
Out[10]:
date users visited funnel users on login page users on registration page users on checkout page
9 2019-02-16 316 226 112 72
10 2019-02-17 341 265 122 78
11 2019-02-18 546 403 210 136
12 2019-02-19 664 503 214 129
13 2019-02-20 722 572 254 168

Трансформируем таблицу, объединив четыре колонки шагов воронки в одну колонку:

In [11]:
fmelt = fdaily.melt('date', var_name='Funnel', value_name='Users')
fmelt.head()
Out[11]:
date Funnel Users
0 2019-02-07 users visited funnel 582
1 2019-02-08 users visited funnel 475
2 2019-02-09 users visited funnel 313
3 2019-02-10 users visited funnel 361
4 2019-02-11 users visited funnel 555

Визуализация данных

Перейдем к построению графиков, для этого импортируем пакеты matplotlib и seaborn и построим с их помощью график прохождения нашей воронки по дням на выбранном временном интервале.

In [12]:
%%capture
import matplotlib.pyplot as plt
import seaborn as sns
In [13]:
from IPython.display import set_matplotlib_formats
set_matplotlib_formats('svg')
sns.set_style("darkgrid")
sns.set_context(rc={"lines.linewidth":0.8})
ax = sns.catplot(x="date", y="Users", hue='Funnel', height=4, aspect=2, kind='point', data=fmelt, legend_out = True)
ax.set_xticklabels(rotation=30)
ax.set(xlabel='Date', ylabel='Users')
plt.show()
site funnel matplotlib

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

In [14]:
fusers = fdaily[fdaily.filter(like='users').columns.tolist()]
ftotals = dict(zip(fusers.columns.tolist(),fusers.sum().tolist()))
ftotals
Out[14]:
{'users visited funnel': 7166,
 'users on login page': 5485,
 'users on registration page': 2613,
 'users on checkout page': 1679}

Построим график воронки с помощью обычной столбчатой диаграммы:

In [15]:
plt.figure(figsize=(7,3))
fig = plt.bar(range(len(ftotals)), list(ftotals.values()), align='center',color=sns.color_palette("Blues",7))
plt.xticks(range(len(ftotals)), list(ftotals.values()))
b2 = str(round(ftotals['users on login page']/ftotals['users visited funnel']*100,2))+'%'
b3 = str(round(ftotals['users on registration page']/ftotals['users visited funnel']*100,2))+'%'
b4 = str(round(ftotals['users on checkout page']/ftotals['users visited funnel']*100,2))+'%'
plt.xticks(range(len(ftotals)), ['100%',b2,b3,b4])
plt.legend(fig, list(ftotals.keys()), loc = "upper right", title = "Funnel")
for rect in fig:
    height = rect.get_height()
    plt.text(rect.get_x() + rect.get_width()/2.0, height/2, '%d' % int(height), ha='center', va='bottom')
plt.show()
users-funnel

Несколько возможных шагов к автоматизации

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

Для начала давайте экспортируем наш ноутбук в html файл

In [16]:
%%capture
!pip install jupyter_contrib_nbextensions
In [17]:
!jupyter nbconvert funnel1.ipynb --to html --log-level WARN

Теперь отправим его на наш сервер через ftp-соединение в раздел отчетов:

In [18]:
import ftplib
ftp = ftplib.FTP('ftp.byethost10.com','b10_22228720','website1lytics')
ftp.cwd('/lytics.website/htdocs/reports')
with open('funnel1.html', 'rb') as ftpup:
    ftp.storbinary('STOR ' + 'funnel1.html', ftpup)
    ftp.close()

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

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