Введение
В этом кратком путешествии мы рассмотрим небольшой и простой набор данных с основными метриками маркетинга веб-сайта, такими как «пользователи», «сеансы» и «отскоки», за период в пять месяцев.
Цель этой настройки — не столько понять производительность веб-сайта, сколько получить базовые, но полезные знания для ответа на ряд обязательных операционных маркетинговых вопросов.
Мы сосредоточимся на двух мощных и наиболее используемых цифровых инструментах, исследуя два пути, которые приведут нас к одинаковым результатам в конце дня.
С одной стороны, мы изучим синтаксис MySQL Workbench с различными запросами, параллельно для каждого вопроса будем использовать синтаксис Python с графическими и визуальными ресурсами. Обе среды будут озаглавлены как # MySQL и # Python соответственно. Для каждого вопроса — примечания и объяснения по обоим кодам для более глубокого понимания.
MySQL
SELECT * FROM case_sql;
Python
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.style as style
%matplotlib inline
color = sns.color_palette()
from pandas.plotting import table
from datetime import datetime
df = pd.read_csv("case.csv", sep=";")
df.shape
(31507, 7)
df.sample(15)
1. Давайте посмотрим на распределение пользователей по типу устройства
MySQL
SELECT
device_category,
ROUND(COUNT(users) / (SELECT
COUNT(users)
FROM
case_sql) * 100,
1) AS percent
FROM
case_sql
GROUP BY 1
ORDER BY 1;
Мы видим, что распределение по устройствам показывает мобильные и настольные устройства как наиболее частые типы доступа.
# Python
df.device_category.value_counts(dropna=False).plot(kind='pie', figsize=(8,4),
explode = (0.02, 0.02, 0.02),
autopct='%1.1f%%',
startangle=150);
plt.xticks(rotation=0, horizontalalignment="center")
plt.title("Device distribution", fontsize=10, loc="right");
2. Каков этот сценарий с точки зрения бренда?
MySQL
SELECT
brand,
COUNT(users) AS users,
ROUND(COUNT(users) / (SELECT
COUNT(users)
FROM
case_sql) * 100,
2) AS percent
FROM
case_sql
GROUP BY 1;
На уровне бренда у бренда 2 наибольшее количество посещений — 56,28 % против 43,72 % от общего числа посещений у бренда 1.
Python
absolut = df["brand"].value_counts().to_frame()
absolut.plot(kind='pie', subplots=True, autopct='%1.2f%%',
explode= (0.05, 0.05), startangle=20,
legend=False, fontsize=12, figsize=(8,4))
plt.xticks(rotation=0, horizontalalignment="center")
plt.title("Brand's distribution", fontsize=10, loc="right");
display(absolut)
3. В какой день недели на сайт бренда 1 пришло больше всего пользователей?
MySQL
SELECT
date,
DAYNAME(date) AS day_name,
SUM(users) AS users
FROM
case_sql
WHERE
brand = 'Brand 1'
GROUP BY 1
ORDER BY 3 DESC
LIMIT 1;
Из 298 412 пользователей с сентября 2019 года по январь 2020 года день, когда на сайт бренда 1 пришло больше всего пользователей, — 22.11.2019, всего 885 посещений, это была пятница.
Python
brand_1 = df[df["brand"] == "Brand 1"].copy()
''' sum total users that came from all "channelgrouping" for the same date,
assign it 'brandgroup' no matter the type of device '''
brandgroup = brand_1.groupby(["date","weekday"])[["default_channel_grouping","users"]].sum()
users = brandgroup[brandgroup["users"] == brandgroup.users.max()].copy()
users.reset_index(["date"], inplace=True)
users.reset_index(["weekday"], inplace=True)
print(f"""Date: {users.date} nnTotal users: {users.users} nnDay of week: {users.weekday}""")
3.1 Сколько пользователей пришло на бренд 2 в тот же день?
MySQL
SELECT
DATE(date) AS date,
DAYNAME(date) AS dayofweek,
SUM(CASE
WHEN brand = 'Brand 1' THEN users
ELSE NULL
END) AS b1_users,
SUM(CASE
WHEN brand = 'Brand 2' THEN users
ELSE NULL
END) AS b2_users
FROM
case_sql
GROUP BY 1, 2
ORDER BY 3 DESC
LIMIT 1;
Фактически, оба бренда зафиксировали наибольшее количество посещений в один и тот же день.
Python
brand_2 = df[df["brand"] == "Brand 2"].copy()
brandgroup.rename(columns = {'users':'brand1_users'}, inplace = True)
brandgroup["brand2_users"] = brand_2.groupby(["date","weekday"])[["default_channel_grouping","users"]].sum()
users2 = brandgroup[brandgroup["brand2_users"] == brandgroup.brand2_users.max()].copy()
4. Из всех групп каналов, какая внесла наибольшее количество пользователей?
MySQL
SELECT
default_channel_grouping AS channels,
SUM(users) AS total_users,
ROUND(SUM(users) / (SELECT
SUM(users)
FROM
case_sql) * 100,
1) AS percent
FROM
case_sql
GROUP BY 1
ORDER BY 2 DESC;
Органический поиск — это канал, который генерирует больше всего пользователей (почти 141 000), представляя почти половину от общего числа посещений на обоих сайтах, за ним следуют Платный поиск и Прямой. Дисплей занимает 4-е место, а Социальные сети — 6-е, внося 6722 пользователя.
Python
ax = df.groupby("default_channel_grouping")["users"].sum().sort_values(ascending=True)
.plot(kind="bar", figsize=(9,6), fontsize=12, linewidth=2,
color=sns.color_palette("rocket"), grid=False, table=False)
for p in ax.patches:
ax.annotate("%.0f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', xytext=(0, 7), textcoords='offset points')
plt.xlabel("Channel groups", fontsize=10)
plt.xticks(rotation=90, horizontalalignment="center")
plt.ylabel("Absolute values", fontsize=10)
plt.title("Best channel group (highest number of users)", fontsize=10, loc="right");
4.1 Из всех групп каналов, какая внесла наибольшее количество пользователей с разбивкой по брендам?
MySQL
SELECT
default_channel_grouping AS channels,
SUM(CASE
WHEN brand = 'brand 1' THEN users
ELSE NULL
END) AS Brand_1,
SUM(CASE
WHEN brand = 'brand 2' THEN users
ELSE NULL
END) AS Brand_2
FROM
case_sql
GROUP BY 1
ORDER BY 3 DESC;
Python
type_pivot = df.pivot_table(
columns="brand",
index="default_channel_grouping",
values="users", aggfunc=sum)
display(type_pivot)
type_pivot.sort_values(by=["Brand 2"], ascending=True).plot(kind="bar", figsize=(12,8) ,fontsize = 15)
plt.xlabel("Channel groups", fontsize=10)
plt.xticks(rotation=90, horizontalalignment="center")
plt.ylabel("Absolute values", fontsize=10)
plt.title("Channel groups by brand (highest number of users)", fontsize=10, loc="right");
Органический поиск привлёк 105 062 пользователей для бренда 2 и 35 911 пользователей для бренда 1. За исключением «Other», в котором бренд 1 превосходит, бренд 2 вносит наибольший вклад, привлекая пользователей на сайт по всем каналам.
5. Среди всех каналов, какой бренд внёс процент платных сеансов не менее 5 % в течение 2019 года?
MySQL
SELECT
brand,
default_channel_grouping AS channels,
ROUND(SUM(sessions) / (SELECT
SUM(sessions)
FROM
case_sql) * 100,
1) AS percent
FROM
case_sql
WHERE
default_channel_grouping IN ('Paid Search' , 'Paid Social', 'Display', 'Other Advertising')
AND date < '2020-01-01'
GROUP BY 1 , 2
HAVING percent > 5
ORDER BY 1 , 3 DESC
Python
df = df.groupby(["date","brand","default_channel_grouping"])["sessions"].sum().to_frame().copy()
df["percent"] = (df.apply(lambda x: x/x.sum())*100).round(2)
df = df.reset_index().copy()
df.sample(5)
6. Сколько посещений получили оба бренда по типу устройства?
MySQL
SELECT
brand,
SUM(CASE
WHEN device_category = 'Desktop' THEN users
ELSE NULL
END) AS desktop,
SUM(CASE
WHEN device_category = 'Mobile' THEN users
ELSE NULL
END) AS mobile,
SUM(CASE
WHEN device_category = 'Tablet' THEN users
ELSE NULL
END) AS tablet
FROM
case_sql
GROUP BY 1
ORDER BY 1;
Python
type_pivot = df.pivot_table(
columns="device_category",
index="brand",
values="users", aggfunc=sum)
display(type_pivot)
ax = type_pivot.sort_values(by=["brand"], ascending=False).plot(kind="bar", figsize=(12,8) ,fontsize = 15);
for p in ax.patches:
ax.annotate("%.0f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()), ha='center', va='center', xytext=(0, 7), textcoords='offset points')
plt.xlabel("Brands", fontsize=10)
plt.xticks(rotation=0, horizontalalignment="center")
plt.ylabel("Absolute values", fontsize=10)
plt.title("Brand by type of device", fontsize=10, loc="right")
plt.legend(["Desktop","Mobile","Tablet"]);
6.1 Каков средний уровень использования устройства по каналам?
MySQL
SELECT
default_channel_grouping,
AVG(CASE
WHEN device_category = 'Desktop' THEN users
ELSE NULL
END) AS desktop,
AVG(CASE
WHEN device_category = 'Mobile' THEN users
ELSE NULL
END) AS mobile,
AVG(CASE
WHEN device_category = 'Tablet' THEN users
ELSE NULL
END) AS tablet
FROM
case_sql
GROUP BY 1
ORDER BY 1;
Python
type_pivot = df.pivot_table(
columns="device_category",
index="default_channel_grouping",
values="users", aggfunc=np.mean)
display(type_pivot)
type_pivot.sort_values(by=["default_channel_grouping"], ascending=False).plot(kind="bar", figsize=(12,8) ,fontsize = 15);
plt.xlabel("Date", fontsize=10)
plt.xticks(rotation=90, horizontalalignment="center")
plt.ylabel("Absolute values", fontsize=10)
plt.title("Average use of device types by channel grouping", fontsize=10, loc="right")
plt.legend(["Desktop","Mobile","Tablet"]);
7. Как оценить показатель отказов групп каналов?
Показатель отказов рассчитывается как общее количество отказов, делённое на общее количество сеансов.
MySQL
SELECT
default_channel_grouping,
SUM(sessions) AS sessions,
SUM(bounces) AS bounces,
ROUND(SUM(bounces) / SUM(sessions) * 100, 2) AS bounces_r
FROM
case_sql
GROUP BY 1
ORDER BY 4 DESC;
Средний показатель отказов: 54,93 % (avg_bounces_r)
SELECT
SUM(sessions) AS sessions,
SUM(bounces) AS bounces,
ROUND(SUM(bounces) / SUM(sessions) * 100, 2) AS bounces_r,
AVG(ROUND(bounces/sessions*100, 2)) AS avg_bounces_r
FROM
case_sql;
Python
dfbounce = df.groupby("default_channel_grouping")["users"].sum().to_frame()
dfbounce["sessions"] = df.groupby("default_channel_grouping")["sessions"].sum()
dfbounce["bounces"] = df.groupby("default_channel_grouping")["bounces"].sum()
dfbounce["bounces_r"] = dfbounce.apply(lambda x: 0.0 if x["sessions"] == 0.0 else (x["bounces"] / x["sessions"])*100, axis=1).round(2)
dff = dfbounce.copy()
dfbounce.drop(["users"],axis=1,inplace=True)
dfbounce.sort_values(by="bounces_r", ascending=False)
7.1 Похоже ли, что показатель отказов на сайте улучшается или ухудшается с течением времени?
MySQL
SELECT
YEAR(date) AS year,
MONTH(date) AS month,
DATE_FORMAT(date, '%b') AS month_,
ROUND(SUM(bounces) / SUM(sessions) * 100, 2) AS bounces_r
case_sql
GROUP BY 1 , 2 , 3
ORDER BY 1 , 2 , 3;
Python
df_date = df.groupby("date")[['sessions','bounces']].sum()
''' create function to assess the bounce rate, assign it as 'bounce_r'
Return 0 if session's value is 0, else divide the bounces by sessions
for each date and multiply it by 100 to get the percentage '''
def div(bounces, sessions):
return lambda row: 0.0 if row[sessions] == 0.0 else float((row[bounces]/(row[sessions])))*100
df_date["bounce_r"] = (df_date.apply(div('bounces', 'sessions'), axis=1)).round(1)
df_date.drop(["sessions","bounces"], axis=1, inplace=True)
ax = df_date.plot(kind="line", figsize=(14,6), fontsize=12, linewidth=2)
plt.xlabel("Date", fontsize=10)
plt.xticks(rotation=90, horizontalalignment="center")
plt.ylabel("Rate", fontsize=10)
plt.title("Evolution of the bounce rate over time", fontsize=10, loc="right");
Показатель отказов на сайте улучшается с течением времени.
7.2 Показатель отказов по каналам и брендам
Python
b1 = df[df["brand"] == "Brand 1"]
b2 = df[df["brand"] == "Brand 2"]
dfbrand = b1.groupby("default_channel_grouping")["sessions"].sum().to_frame()
dfbrand.rename(columns={"sessions":"sessions1"}, inplace=True)
dfbrand["bounces1"] = b1.groupby("default_channel_grouping")["bounces"].sum()
dfbrand["1bounces_r"] = dfbrand.apply(lambda x: 0.0 if x["sessions1"] == 0.0 else (x["bounces1"] / x["sessions1"]*100), axis=1).round(2)
dfbrand["sessions2"] = b2.groupby("default_channel_grouping")["sessions"].sum()
dfbrand["bounces2"] = b2.groupby("default_channel_grouping")["bounces"].sum()
dfbrand["2bounces_r"] = dfbrand.apply(lambda x: 0.0 if x["sessions2"] == 0.0 else (x["bounces2"] / x["sessions2"]*100), axis=1).round(2)
dfbrand.sort_values(by="1bounces_r", ascending=False)
8. Пропорции между входящими и платными медиа
MySQL
SELECT
brand,
CASE
WHEN
default_channel_grouping IN ('Paid Search',
'Paid Social',
'Display',
'Other Advertising')
THEN
'Paid'
WHEN
default_channel_grouping IN ('Direct',
'Native',
'Organic Search',
'Referral',
'Social',
'Email',
'(Other)')
THEN
'Organic'
ELSE NULL
END AS media,
ROUND(SUM(bounces) / SUM(sessions) * 100, 2) AS bounce_r
FROM
case_sql
GROUP BY brand , media
ORDER BY 1;
Python
media_dict =
{
'Display': 'paid',
'Paid Search': 'paid',
'Paid Social': 'paid',
'Other Advertising': 'paid',
'Direct': 'organic',
'Native': 'organic',
'Organic Search': 'organic',
'Referral': 'organic',
'Social': 'organic',
'Email': 'organic',
'(Other)': 'organic'
}
df['media'] = df['default_channel_grouping'].map(media_dict)
cols = ['brand','media','sessions','bounces']
df = df.reindex(columns = cols)
df = df.groupby(["brand","media"])[["sessions","bounces"]].sum()
df["bounces_r"] = df.apply(lambda x: 0.0 if x["sessions"] == 0.0 else (x["bounces"] / x["sessions"])*100, axis=1).round(2)
Заключение
Как и было обещано, мы рассмотрели пошаговый подход к проведению простого цифрового маркетингового анализа, используя MySQL Workbench и Python.
Оба инструмента имеют свои особенности, требования, но рассуждения относительно схожи, если не учитывать их графические возможности и ограничения.
Не стесняйтесь загружать наборы данных и исследовать их, практикуясь в технических деталях, рассмотренных здесь, и реализуя новый код для дальнейших маркетинговых вопросов.
