21  Практичні задачі

Data Miorsh Ihor Miroshnychenko Youtube Monobank

У практичній частині ми продовжимо аналізувати наш сервіс та розрахуємо кілька важливих показників, що характеризують його роботу. Вам буде запропоновано написати кілька SQL-запитів та візуалізувати їх.

21.1 Задача 1

Для початку давайте проаналізуємо, наскільки швидко зростає аудиторія нашого сервісу та подивимося на динаміку числа користувачів та кур’єрів.

Завдання 21.1
Для кожного дня, представленого в таблицях user_actions та courier_actions, розрахуйте такі показники:

  1. Число нових користувачів.
  2. Число нових кур’єрів.
  3. Загальна кількість користувачів на сьогодні.
  4. Загальна кількість кур’єрів на цей день.

Колонки з показниками назвіть відповідно new_users, new_couriers, total_users, total_couriers. Колонку з датами назвіть date. Простежте, щоб показники були виражені цілими числами. Результат має бути відсортований за зростанням дати.

Поля в результуючій таблиці: date, new_users, new_couriers, total_users, total_couriers

Пояснення:

Новими вважатимемо тих користувачів та кур’єрів, які в цей день здійснили свою першу дію в нашому сервісі. Загальна кількість користувачів/кур’єрів на поточний день — це результат додавання числа нових користувачів/кур’єрів у поточний день зі значеннями аналогічного показника всіх попередніх днів.

Щоб порахувати кількість нових користувачів/кур’єрів на кожну дату спочатку необхідно для кожного користувача/кур’єра визначити мінімальну дату, тобто дату першої дії в нашому сервісі. Потім потрібно провести групування і для кожної отриманої дати порахувати кількість користувачів/кур’єрів із цією датою. Далі до отриманої таблиці достатньо застосувати віконну функцію і кожної дати обчислити накопичувальну суму числа користувачів/кур’єрів.

Рішення
%%sql
SELECT start_date as date,
       new_users,
       new_couriers,
       (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
       (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
FROM   (SELECT start_date,
               count(courier_id) as new_couriers
        FROM   (SELECT courier_id,
                       min(time::date) as start_date
                FROM   courier_actions
                GROUP BY courier_id) t1
        GROUP BY start_date) t2
    LEFT JOIN (SELECT start_date,
                      count(user_id) as new_users
               FROM   (SELECT user_id,
                              min(time::date) as start_date
                       FROM   user_actions
                       GROUP BY user_id) t3
               GROUP BY start_date) t4 using (start_date)
date new_users new_couriers total_users total_couriers
0 2022-08-24 134 95 134 95
1 2022-08-25 829 242 963 337
2 2022-08-26 1017 219 1980 556
3 2022-08-27 1225 186 3205 742
4 2022-08-28 1487 213 4692 955
5 2022-08-29 1309 109 6001 1064
6 2022-08-30 1204 127 7205 1191
7 2022-08-31 1420 195 8625 1386
8 2022-09-01 1513 205 10138 1591
9 2022-09-02 1887 229 12025 1820
10 2022-09-03 1924 161 13949 1981
11 2022-09-04 1952 205 15901 2186
12 2022-09-05 1397 120 17298 2306
13 2022-09-06 1020 98 18318 2404
14 2022-09-07 1412 220 19730 2624
15 2022-09-08 1671 202 21401 2826

Запишемо результат запиту у змінну results.

Код
%%sql
results << SELECT start_date as date,
       new_users,
       new_couriers,
       (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
       (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
FROM   (SELECT start_date,
               count(courier_id) as new_couriers
        FROM   (SELECT courier_id,
                       min(time::date) as start_date
                FROM   courier_actions
                GROUP BY courier_id) t1
        GROUP BY start_date) t2
    LEFT JOIN (SELECT start_date,
                      count(user_id) as new_users
               FROM   (SELECT user_id,
                              min(time::date) as start_date
                       FROM   user_actions
                       GROUP BY user_id) t3
               GROUP BY start_date) t4 using (start_date)

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date, y=results.total_users, mode="lines+markers", name="total_users"
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.total_couriers,
        mode="lines+markers",
        name="total_couriers",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date, y=results.new_users, mode="lines+markers", name="new_users"
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.new_couriers,
        mode="lines+markers",
        name="new_couriers",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()
(a) Динаміка загальної кількості користувачів
(b) Динаміка нових користувачів та кур’єрів
Рисунок 21.1: Графік за результатами SQL-запиту

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. Що зростає швидше: кількість користувачів чи кількість кур’єрів?
  2. Наскільки стабільні показники числа нових користувачів та кур’єрів? Чи немає даних таких днів, коли показники сильно вибивалися із загальної динаміки?
  3. Чи можна сказати, що кількість нових кур’єрів більш стабільна, ніж кількість нових користувачів?

21.2 Задача 2

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

Завдання 21.2
Доповніть запит із попереднього завдання і тепер для кожного дня, поданого в таблицях user_actions та courier_actions, додатково розрахуйте такі показники:

  1. Приріст числа нових користувачів.
  2. Приріст числа нових кур’єрів.
  3. Приріст загальної кількості користувачів.
  4. Приріст загальної кількості кур’єрів.

Показники, розраховані на попередньому кроці, також включіть у результуючу таблицю.

Колонки з новими показниками назвіть відповідно new_users_change, new_couriers_change, total_users_growth, total_couriers_growth. Колонку з датами назвіть date.

Усі показники приросту рахуйте у відсотках відносно попереднього дня. Під час розрахунку показників округляйте значення до двох знаків після коми.

Результуюча таблиця має бути відсортована за зростанням дати.

Поля в результуючій таблиці: date, new_users, new_couriers, total_users, total_couriers, new_users_change, new_couriers_change, total_users_growth, total_couriers_growth

Пояснення:

Не забувайте при діленні заздалегідь наводити значення до потрібного типу даних. Пропущені значення приростів для першої дати не заповнюйте - просто залиште поля в цьому рядку порожніми.

Для розрахунку приростів скористайтесь віконними функціями та функціями зсунення.

Рішення
%%sql
SELECT date,
       new_users,
       new_couriers,
       total_users,
       total_couriers,
       round(100 * (new_users - lag(new_users, 1) OVER (ORDER BY date)) / lag(new_users, 1) OVER (ORDER BY date)::decimal,
             2) as new_users_change,
       round(100 * (new_couriers - lag(new_couriers, 1) OVER (ORDER BY date)) / lag(new_couriers, 1) OVER (ORDER BY date)::decimal,
             2) as new_couriers_change,
       round(100 * new_users::decimal / lag(total_users, 1) OVER (ORDER BY date),
             2) as total_users_growth,
       round(100 * new_couriers::decimal / lag(total_couriers, 1) OVER (ORDER BY date),
             2) as total_couriers_growth
FROM   (SELECT start_date as date,
               new_users,
               new_couriers,
               (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
               (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
        FROM   (SELECT start_date,
                       count(courier_id) as new_couriers
                FROM   (SELECT courier_id,
                               min(time::date) as start_date
                        FROM   courier_actions
                        GROUP BY courier_id) t1
                GROUP BY start_date) t2
            LEFT JOIN (SELECT start_date,
                              count(user_id) as new_users
                       FROM   (SELECT user_id,
                                      min(time::date) as start_date
                               FROM   user_actions
                               GROUP BY user_id) t3
                       GROUP BY start_date) t4 using (start_date)) t5
date new_users new_couriers total_users total_couriers new_users_change new_couriers_change total_users_growth total_couriers_growth
0 2022-08-24 134 95 134 95 NaN NaN NaN NaN
1 2022-08-25 829 242 963 337 518.66 154.74 618.66 254.74
2 2022-08-26 1017 219 1980 556 22.68 -9.50 105.61 64.99
3 2022-08-27 1225 186 3205 742 20.45 -15.07 61.87 33.45
4 2022-08-28 1487 213 4692 955 21.39 14.52 46.40 28.71
5 2022-08-29 1309 109 6001 1064 -11.97 -48.83 27.90 11.41
6 2022-08-30 1204 127 7205 1191 -8.02 16.51 20.06 11.94
7 2022-08-31 1420 195 8625 1386 17.94 53.54 19.71 16.37
8 2022-09-01 1513 205 10138 1591 6.55 5.13 17.54 14.79
9 2022-09-02 1887 229 12025 1820 24.72 11.71 18.61 14.39
10 2022-09-03 1924 161 13949 1981 1.96 -29.69 16.00 8.85
11 2022-09-04 1952 205 15901 2186 1.46 27.33 13.99 10.35
12 2022-09-05 1397 120 17298 2306 -28.43 -41.46 8.79 5.49
13 2022-09-06 1020 98 18318 2404 -26.99 -18.33 5.90 4.25
14 2022-09-07 1412 220 19730 2624 38.43 124.49 7.71 9.15
15 2022-09-08 1671 202 21401 2826 18.34 -8.18 8.47 7.70

Запишемо результат запиту у змінну results.

Код
%%sql
results << SELECT date,
       new_users,
       new_couriers,
       total_users,
       total_couriers,
       round(100 * (new_users - lag(new_users, 1) OVER (ORDER BY date)) / lag(new_users, 1) OVER (ORDER BY date)::decimal,
             2) as new_users_change,
       round(100 * (new_couriers - lag(new_couriers, 1) OVER (ORDER BY date)) / lag(new_couriers, 1) OVER (ORDER BY date)::decimal,
             2) as new_couriers_change,
       round(100 * new_users::decimal / lag(total_users, 1) OVER (ORDER BY date),
             2) as total_users_growth,
       round(100 * new_couriers::decimal / lag(total_couriers, 1) OVER (ORDER BY date),
             2) as total_couriers_growth
FROM   (SELECT start_date as date,
               new_users,
               new_couriers,
               (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
               (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
        FROM   (SELECT start_date,
                       count(courier_id) as new_couriers
                FROM   (SELECT courier_id,
                               min(time::date) as start_date
                        FROM   courier_actions
                        GROUP BY courier_id) t1
                GROUP BY start_date) t2
            LEFT JOIN (SELECT start_date,
                              count(user_id) as new_users
                       FROM   (SELECT user_id,
                                      min(time::date) as start_date
                               FROM   user_actions
                               GROUP BY user_id) t3
                       GROUP BY start_date) t4 using (start_date)) t5

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure()
fig.add_trace(
    go.Bar(
        x=results.date,
        y=results.new_users_change,
        name="new_users_change",
        marker_color="indianred",
    )
)
fig.add_trace(
    go.Bar(
        x=results.date,
        y=results.new_couriers_change,
        name="new_couriers_change",
        marker_color="lightsalmon",
    )
)

fig.update_layout(
    barmode="group",
    xaxis_tickangle=-45,
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()

fig = go.Figure()
fig.add_trace(
    go.Bar(
        x=results.date,
        y=results.total_users_growth,
        name="total_users_growth",
        marker_color="indianred",
    )
)
fig.add_trace(
    go.Bar(
        x=results.date,
        y=results.total_couriers_growth,
        name="total_couriers_growth",
        marker_color="lightsalmon",
    )
)

fig.update_layout(
    barmode="group",
    xaxis_tickangle=-45,
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()
(a) Динаміка приросту числа нових користувачів та кур’єрів
(b) Динаміка приросту загальної кількості користувачів та кур’єрів
Рисунок 21.2: Графік за результатами SQL-запиту

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. Як змінилися темпи приросту загальної кількості користувачів і кур’єрів за проміжок часу, що розглядається? Яка загалом динаміка цих показників: вони ростуть чи, навпаки, згасають?
  2. У які дні темп приросту числа нових кур’єрів помітно випереджав темп приросту нових користувачів?
  3. Чи можна, дивлячись на графіки з відносними показниками, сказати, що кількість нових кур’єрів більш стабільний, ніж показник числа нових користувачів?

21.3 Задача 3

Тепер пропонуємо вам подивитися на нашу аудиторію трохи під іншим кутом - давайте порахуємо не просто всіх користувачів, а саме ту частину, яка оформлює та оплачує замовлення у нашому сервісі. Заодно з’ясуємо, яку частку користувачі, що платять, становлять від їх загального числа.

Завдання 21.3
Для кожного дня, представленого в таблицях user_actions та courier_actions, розрахуйте такі показники:

  1. Число користувачів, які оплачують замовлення.
  2. Число активних кур’єрів.
  3. Частку користувачів, що платять, в загальній кількості користувачів на поточний день.
  4. Частку активних кур’єрів у кількості кур’єрів на поточний день.

Колонки з показниками назвіть відповідно paying_users, active_couriers, paying_users_share, active_couriers_share. Колонку з датами назвіть date. Простежте, щоб абсолютні показники були виражені цілими числами. Усі показники часток необхідно виразити у відсотках. Під час їх розрахунку округляйте значення до двох знаків після коми.

Результат має бути відсортований за зростанням дати.

Поля в результуючій таблиці: date, paying_users, active_couriers, paying_users_share, active_couriers_share

Пояснення:

Користувачів, які оплачують замовлення будемо вважати тих, які в даний день оформили хоча б одне замовлення, яке надалі не було скасовано.

Кур’єрів вважатимемо активними, якщо в даний день вони прийняли хоча б одне замовлення, яке було доставлено (можливо вже наступного дня), або доставили будь-яке замовлення.

Загальна кількість користувачів/кур’єрів на поточний день – це, як і раніше, результат складання числа нових користувачів/кур’єрів у поточний день зі значеннями аналогічного показника всіх попередніх днів. Ми рахували цей показник на попередніх кроках.

Не забувайте при діленні заздалегідь наводити значення до потрібного типу даних.

Для вирішення завдання необхідно спочатку для кожного дня порахувати кількість користувачів, які оплачують замовлення, і активних кур’єрів, а потім об’єднати отримані таблиці з даними про загальну кількість користувачів і кур’єрів на поточну дату і далі розрахувати всі необхідні відносні показники. Загальну кількість користувачів та кур’єрів на поточну дату ми вже рахували раніше.

Рішення
%%sql
SELECT date,
       paying_users,
       active_couriers,
       round(100 * paying_users::decimal / total_users, 2) as paying_users_share,
       round(100 * active_couriers::decimal / total_couriers, 2) as active_couriers_share
FROM   (SELECT start_date as date,
               new_users,
               new_couriers,
               (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
               (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
        FROM   (SELECT start_date,
                       count(courier_id) as new_couriers
                FROM   (SELECT courier_id,
                               min(time::date) as start_date
                        FROM   courier_actions
                        GROUP BY courier_id) t1
                GROUP BY start_date) t2
            LEFT JOIN (SELECT start_date,
                              count(user_id) as new_users
                       FROM   (SELECT user_id,
                                      min(time::date) as start_date
                               FROM   user_actions
                               GROUP BY user_id) t3
                       GROUP BY start_date) t4 using (start_date)) t5
    LEFT JOIN (SELECT time::date as date,
                      count(distinct courier_id) as active_couriers
               FROM   courier_actions
               WHERE  order_id not in (SELECT order_id
                                       FROM   user_actions
                                       WHERE  action = 'cancel_order')
               GROUP BY date) t6 using (date)
    LEFT JOIN (SELECT time::date as date,
                      count(distinct user_id) as paying_users
               FROM   user_actions
               WHERE  order_id not in (SELECT order_id
                                       FROM   user_actions
                                       WHERE  action = 'cancel_order')
               GROUP BY date) t7 using (date)
date paying_users active_couriers paying_users_share active_couriers_share
0 2022-08-24 127 93 94.78 97.89
1 2022-08-25 820 333 85.15 98.81
2 2022-08-26 1137 526 57.42 94.60
3 2022-08-27 1548 721 48.30 97.17
4 2022-08-28 2083 927 44.39 97.07
5 2022-08-29 2202 1022 36.69 96.05
6 2022-08-30 2253 1133 31.27 95.13
7 2022-08-31 2430 1286 28.17 92.78
8 2022-09-01 2710 1474 26.73 92.65
9 2022-09-02 3429 1733 28.52 95.22
10 2022-09-03 3794 1906 27.20 96.21
11 2022-09-04 4104 2069 25.81 94.65
12 2022-09-05 3362 2047 19.44 88.77
13 2022-09-06 2701 1883 14.75 78.33
14 2022-09-07 3517 2245 17.83 85.56
15 2022-09-08 3912 2491 18.28 88.15

Запишемо результат запиту у змінну results.

Код
%%sql
results << SELECT date,
       paying_users,
       active_couriers,
       round(100 * paying_users::decimal / total_users, 2) as paying_users_share,
       round(100 * active_couriers::decimal / total_couriers, 2) as active_couriers_share
FROM   (SELECT start_date as date,
               new_users,
               new_couriers,
               (sum(new_users) OVER (ORDER BY start_date))::int as total_users,
               (sum(new_couriers) OVER (ORDER BY start_date))::int as total_couriers
        FROM   (SELECT start_date,
                       count(courier_id) as new_couriers
                FROM   (SELECT courier_id,
                               min(time::date) as start_date
                        FROM   courier_actions
                        GROUP BY courier_id) t1
                GROUP BY start_date) t2
            LEFT JOIN (SELECT start_date,
                              count(user_id) as new_users
                       FROM   (SELECT user_id,
                                      min(time::date) as start_date
                               FROM   user_actions
                               GROUP BY user_id) t3
                       GROUP BY start_date) t4 using (start_date)) t5
    LEFT JOIN (SELECT time::date as date,
                      count(distinct courier_id) as active_couriers
               FROM   courier_actions
               WHERE  order_id not in (SELECT order_id
                                       FROM   user_actions
                                       WHERE  action = 'cancel_order')
               GROUP BY date) t6 using (date)
    LEFT JOIN (SELECT time::date as date,
                      count(distinct user_id) as paying_users
               FROM   user_actions
               WHERE  order_id not in (SELECT order_id
                                       FROM   user_actions
                                       WHERE  action = 'cancel_order')
               GROUP BY date) t7 using (date)

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.paying_users,
        mode="lines+markers",
        name="paying_users",
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.active_couriers,
        mode="lines+markers",
        name="active_couriers",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.paying_users_share,
        mode="lines+markers",
        name="paying_users_share",
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.active_couriers_share,
        mode="lines+markers",
        name="active_couriers_share",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()
(a) Динаміка користувачів, які платять та активних кур’єрів:
(b) Динаміка часток користувачів, які платять та активних кур’єрів:
Рисунок 21.3: Графік за результатами SQL-запиту

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. Чи можна сказати, що разом із загальною кількістю користувачів і кур’єрів зростає кількість користувачів, що платять, і активних кур’єрів?
  2. Як водночас поводяться показники часток користувачів, що платять, і активних кур’єрів? Чи можна вважати їхню поточну динаміку в цілому нормальною та закономірною?

21.4 Задача 4

Давайте докладніше зупинимося на користувачах, які оплачують замовлення, копнем трохи глибше і з’ясуємо, як багато користувачів, які оплачують замовленняь, здійснюють більше одного замовлення в день. Зрештою нам важливо розуміти, як здебільшого поводяться наші користувачі — вони заходять у додаток, щоб зробити всього одне замовлення, або наш сервіс настільки хороший, що вони готові користуватися ним кілька разів на день.

Завдання 21.4
Для кожного дня, поданого в таблиці user_actions, розрахуйте такі показники:

  1. Частку користувачів, які зробили в цей день всього одне замовлення, від загальної кількості користувачів, які оплачують замовлення.
  2. Частку користувачів, які зробили цього дня кілька замовлень, від загальної кількості користувачів, які оплачують замовлення.

Назвіть колонки з показниками відповідно single_order_users_share, several_orders_users_share. Колонку з датами назвіть date. Усі показники із частками необхідно виразити у відсотках. При розрахунку часток округляйте значення до двох знаків після коми.

Результат має бути відсортований за зростанням дати.

Поля в результуючій таблиці: date, single_order_users_share, several_orders_users_share

Пояснення:

Користувачами, що оплачують замовлення, як і раніше, вважаємо тих користувачів, які в даний день оформили (і не скасували) хоча б одне замовлення.

Не забувайте при діленні заздалегідь наводити значення до потрібного типу даних.

Для вирішення завдання спочатку необхідно для кожного дня окремо розрахувати загальну кількість користувачів, що оплачують замовлення, кількість користувачів з одним замовленням і кількість користувачів з кількома замовленнями. Потім необхідно поєднати таблиці з цими даними за датою та порахувати всі необхідні показники.

Рішення
%%sql
SELECT day::date AS date,
       ROUND(100 * COUNT(DISTINCT user_id) FILTER(WHERE count_orders = 1) / COUNT(DISTINCT user_id)::numeric, 2) AS single_order_users_share,
       ROUND(100 * COUNT(DISTINCT user_id) FILTER(WHERE count_orders != 1) / COUNT(DISTINCT user_id)::numeric, 2) AS several_orders_users_share
FROM(

SELECT user_id,
       COUNT(DISTINCT order_id) AS count_orders,
       DATE_TRUNC('day', time) AS day
FROM user_actions
WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action = 'cancel_order')
GROUP BY user_id, day
) AS t1
GROUP BY day
ORDER BY day
date single_order_users_share several_orders_users_share
0 2022-08-24 92.91 7.09
1 2022-08-25 76.46 23.54
2 2022-08-26 78.45 21.55
3 2022-08-27 70.93 29.07
4 2022-08-28 67.83 32.17
5 2022-08-29 65.58 34.42
6 2022-08-30 64.67 35.33
7 2022-08-31 70.41 29.59
8 2022-09-01 71.70 28.30
9 2022-09-02 67.02 32.98
10 2022-09-03 63.94 36.06
11 2022-09-04 65.94 34.06
12 2022-09-05 69.96 30.04
13 2022-09-06 77.67 22.33
14 2022-09-07 72.65 27.35
15 2022-09-08 69.61 30.39

Запишемо результат запиту у змінну results:

Код
%%sql
results << SELECT day::date AS date,
       ROUND(100 * COUNT(DISTINCT user_id) FILTER(WHERE count_orders = 1) / COUNT(DISTINCT user_id)::numeric, 2) AS single_order_users_share,
       ROUND(100 * COUNT(DISTINCT user_id) FILTER(WHERE count_orders != 1) / COUNT(DISTINCT user_id)::numeric, 2) AS several_orders_users_share
FROM(

SELECT user_id,
       COUNT(DISTINCT order_id) AS count_orders,
       DATE_TRUNC('day', time) AS day
FROM user_actions
WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action = 'cancel_order')
GROUP BY user_id, day
) AS t1
GROUP BY day
ORDER BY day

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure(
    data=[
        go.Bar(
            name="single_order_users_share",
            x=results.date,
            y=results.single_order_users_share,
        ),
        go.Bar(
            name="several_orders_users_share",
            x=results.date,
            y=results.several_orders_users_share,
        ),
    ]
)
fig.update_layout(
    barmode="stack",
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()
Рисунок 21.4: Частки користувачів з одним та кількома замовленнями

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. На якому рівні у середньому тримається частка користувачів із кількома замовленнями?
  2. Чи можна вважати значення показника в перший день аномально низьким, якщо взяти до уваги загальну кількість користувачів цього дня?

21.5 Задача 5

Продовжимо вивчати наш сервіс та розрахуємо декілька показників, пов’язаних із замовленнями.

Завдання 21.5
Для кожного дня, поданого в таблиці user_actions, розрахуйте такі показники:

  1. Загальна кількість замовлень.
  2. Число перших замовлень (замовлень, зроблених користувачами вперше).
  3. Число замовлень нових користувачів (замовлень, зроблених користувачами того ж дня, коли вони вперше скористалися сервісом).
  4. Частку перших замовлень у кількості замовлень (частку п.2 в п.1).
  5. Частку замовлень нових користувачів у кількості замовлень (частку п.3 в п.1).

Назвіть колонки з показниками відповідно orders, first_orders, new_users_orders, first_orders_share, new_users_orders_share. Колонку з датами назвіть date. Простежте, щоб у всіх випадках кількість замовлень була виражена цілим числом. Усі показники із частками необхідно виразити у відсотках. При розрахунку часток округляйте значення до двох знаків після коми.

Результат має бути відсортований за зростанням дати.

Поля в результуючій таблиці: date, orders, first_orders, new_users_orders, first_orders_share, new_users_orders_share

Пояснення:

При розрахунку числа перших замовлень враховуйте, що у кожного користувача може бути лише одне перше замовлення (що цілком логічно).

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

У всіх випадках при розрахунку числа замовлень враховуйте лише фактично здійснені замовлення, скасовані замовлення не враховуйте.

Не забувайте при діленні заздалегідь приводити значення до потрібного типу даних.

Для вирішення завдання спочатку необхідно для кожного дня окремо розрахувати загальну кількість замовлень, кількість перших замовлень і кількість замовлень нових користувачів, а потім об’єднати отримані таблиці в одну і порахувати всі відносні показники. Для розрахунку числа перших замовлень для кожного користувача потрібно знайти дату оформлення першого нескасованого замовлення і потім здійснити групування за датою, порахувавши для кожного дня кількість користувачів, які зробили перше замовлення. Для розрахунку числа замовлень нових користувачів спочатку потрібно для кожного користувача знайти дату здійснення першої дії, а потім доповнити цю таблицю даними про кількість замовлень, зроблених користувачем свого першого дня. Це можна зробити, приєднавши до таблиці з датами перших дій таблицю із загальною кількістю замовлень на кожну дату для кожного користувача. Зверніть увагу, що в цій таблиці для деяких користувачів можуть бути відсутні дати здійснення першої дії оскільки користувач міг скасувати замовлення і фактично не здійснити жодної покупки свого першого дня. Після об’єднання таблиць для таких днів із пропущеними значеннями слід вказати кількість замовлень рівним 0. Це можна зробити, наприклад, за допомогою функції COALESCE.

Рішення
%%sql
SELECT date, orders, first_orders, new_users_orders::int as new_users_orders,
        ROUND(100 * first_orders::decimal / orders, 2) AS first_orders_share,
        ROUND(100 * new_users_orders::decimal / orders, 2) AS new_users_orders_share
    FROM (
        SELECT creation_time::date AS date, COUNT(DISTINCT order_id) AS orders
        FROM orders
        WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
        AND order_id IN (SELECT order_id FROM courier_actions WHERE action='deliver_order')
        GROUP BY date
    ) t5
    LEFT JOIN (
        SELECT first_order_date AS date, COUNT(user_id) AS first_orders
        FROM (
            SELECT user_id, MIN(time::date) AS first_order_date
            FROM user_actions
            WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
            GROUP BY user_id
        ) t4
        GROUP BY first_order_date
    ) t7
    USING (date)
    LEFT JOIN (
        SELECT start_date AS date, SUM(orders) AS new_users_orders
        FROM (
            SELECT t1.user_id, t1.start_date, COALESCE(t2.orders, 0) AS orders
            FROM (
                SELECT user_id, MIN(time::date) AS start_date
                FROM user_actions
                GROUP BY user_id
            ) t1
            LEFT JOIN (
                SELECT user_id, time::date AS date, COUNT(DISTINCT order_id) AS orders
                FROM user_actions
                WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
                GROUP BY user_id, date
            ) t2
            ON t1.user_id = t2.user_id AND t1.start_date = t2.date
        ) t3
        GROUP BY start_date
    ) t6
    USING (date)
    ORDER BY date
date orders first_orders new_users_orders first_orders_share new_users_orders_share
0 2022-08-24 138 127 138 92.03 100.00
1 2022-08-25 1059 802 1032 75.73 97.45
2 2022-08-26 1447 984 1250 68.00 86.39
3 2022-08-27 2141 1192 1624 55.67 75.85
4 2022-08-28 2998 1460 2102 48.70 70.11
5 2022-08-29 3267 1281 1832 39.21 56.08
6 2022-08-30 3371 1180 1714 35.00 50.85
7 2022-08-31 3410 1380 1908 40.47 55.95
8 2022-09-01 3688 1492 1988 40.46 53.90
9 2022-09-02 5001 1864 2655 37.27 53.09
10 2022-09-03 5709 1907 2830 33.40 49.57
11 2022-09-04 6010 1943 2763 32.33 45.97
12 2022-09-05 4675 1387 1865 29.67 39.89
13 2022-09-06 3451 1012 1264 29.32 36.63
14 2022-09-07 4777 1416 1865 29.64 39.04
15 2022-09-08 5474 1661 2300 30.34 42.02

Запишемо результат запиту у змінну results:

Код
%%sql
results << SELECT date, orders, first_orders, new_users_orders::int as new_users_orders,
        ROUND(100 * first_orders::decimal / orders, 2) AS first_orders_share,
        ROUND(100 * new_users_orders::decimal / orders, 2) AS new_users_orders_share
    FROM (
        SELECT creation_time::date AS date, COUNT(DISTINCT order_id) AS orders
        FROM orders
        WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
        AND order_id IN (SELECT order_id FROM courier_actions WHERE action='deliver_order')
        GROUP BY date
    ) t5
    LEFT JOIN (
        SELECT first_order_date AS date, COUNT(user_id) AS first_orders
        FROM (
            SELECT user_id, MIN(time::date) AS first_order_date
            FROM user_actions
            WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
            GROUP BY user_id
        ) t4
        GROUP BY first_order_date
    ) t7
    USING (date)
    LEFT JOIN (
        SELECT start_date AS date, SUM(orders) AS new_users_orders
        FROM (
            SELECT t1.user_id, t1.start_date, COALESCE(t2.orders, 0) AS orders
            FROM (
                SELECT user_id, MIN(time::date) AS start_date
                FROM user_actions
                GROUP BY user_id
            ) t1
            LEFT JOIN (
                SELECT user_id, time::date AS date, COUNT(DISTINCT order_id) AS orders
                FROM user_actions
                WHERE order_id NOT IN (SELECT order_id FROM user_actions WHERE action='cancel_order')
                GROUP BY user_id, date
            ) t2
            ON t1.user_id = t2.user_id AND t1.start_date = t2.date
        ) t3
        GROUP BY start_date
    ) t6
    USING (date)
    ORDER BY date

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure()
fig.add_trace(
    go.Scatter(x=results.date, y=results.orders, mode="lines+markers", name="orders")
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.first_orders,
        mode="lines+markers",
        name="first_orders",
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.new_users_orders,
        mode="lines+markers",
        name="new_users_orders",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.first_orders_share,
        mode="lines+markers",
        name="first_orders_share",
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.new_users_orders_share,
        mode="lines+markers",
        name="new_users_orders_share",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()
(a) Динаміка загальної кількості замовлень, числа перших замовлень та числа замовлень нових користувачів
(b) Динаміка частки перших замовлень та частки замовлень нових користувачів у кількості замовлень
Рисунок 21.5: Графік за результатами SQL-запиту

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. Яка загалом динаміка у абсолютних показників? Чи можна сказати, що разом із зростанням кількості всіх замовлень зростають показники числа перших замовлень та кількості замовлень нових користувачів?
  2. Що можна сказати про динаміку відносних показників? Чи можна вважати її загалом закономірною? Як, на ваш погляд, поводитимуться ці показники у довгостроковій перспективі: вони зростатимуть чи знижуватимуться?

21.6 Задача 6

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

Завдання 21.6
На основі даних у таблицях user_actions, courier_actions та orders для кожного дня розрахуйте такі показники:

  1. Кількість користувачів, що оплатили замовлення на одного активного кур’єра.
  2. Кількість замовлень на одного активного кур’єра.

Колонки з показниками назвіть відповідно users_per_courier та orders_per_courier. Колонку з датами назвіть date. Під час розрахунку показників округляйте значення до двох знаків після коми.

Результуюча таблиця має бути відсортована за зростанням дати.

Поля в результуючій таблиці: date, users_per_courier, orders_per_courier

Пояснення:

Користувачів, що оплатили замовлення, як і раніше, вважаємо тих користувачів, які в даний день оформили хоча б одне замовлення, яке надалі не було скасовано.

Кур’єрів вважаємо активними, якщо в даний день вони прийняли хоча б одне замовлення, яке було доставлено (можливо вже наступного дня), або доставили будь-яке замовлення.

У розрахунках враховуйте лише нескасовані замовлення.

Не забувайте при діленні заздалегідь наводити значення до потрібного типу даних.

Для вирішення завдання спочатку необхідно за допомогою групування та агрегації розрахувати кількість користувачів, кур’єрів та замовлень на кожну дату, а потім об’єднати дані в одну таблицю та провести всі необхідні розрахунки.

Рішення
%%sql
SELECT date,
       round(paying_users::decimal / couriers, 2) as users_per_courier,
       round(orders::decimal / couriers, 2) as orders_per_courier
FROM   (SELECT time::date as date,
               count(distinct courier_id) as couriers
        FROM   courier_actions
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY date) t1 join (SELECT creation_time::date as date,
                               count(distinct order_id) as orders
                        FROM   orders
                        WHERE  order_id not in (SELECT order_id
                                                FROM   user_actions
                                                WHERE  action = 'cancel_order')
                        GROUP BY date) t2 using (date) join (SELECT time::date as date,
                                            count(distinct user_id) as paying_users
                                     FROM   user_actions
                                     WHERE  order_id not in (SELECT order_id
                                                             FROM   user_actions
                                                             WHERE  action = 'cancel_order')
                                     GROUP BY date) t3 using (date)
ORDER BY date
date users_per_courier orders_per_courier
0 2022-08-24 1.37 1.48
1 2022-08-25 2.46 3.18
2 2022-08-26 2.16 2.75
3 2022-08-27 2.15 2.97
4 2022-08-28 2.25 3.23
5 2022-08-29 2.15 3.20
6 2022-08-30 1.99 2.98
7 2022-08-31 1.89 2.65
8 2022-09-01 1.84 2.50
9 2022-09-02 1.98 2.89
10 2022-09-03 1.99 3.00
11 2022-09-04 1.98 2.90
12 2022-09-05 1.64 2.28
13 2022-09-06 1.43 1.83
14 2022-09-07 1.57 2.13
15 2022-09-08 1.57 2.20

Запишемо результат запиту у змінну results:

Код
%%sql
results << SELECT date,
       round(paying_users::decimal / couriers, 2) as users_per_courier,
       round(orders::decimal / couriers, 2) as orders_per_courier
FROM   (SELECT time::date as date,
               count(distinct courier_id) as couriers
        FROM   courier_actions
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY date) t1 join (SELECT creation_time::date as date,
                               count(distinct order_id) as orders
                        FROM   orders
                        WHERE  order_id not in (SELECT order_id
                                                FROM   user_actions
                                                WHERE  action = 'cancel_order')
                        GROUP BY date) t2 using (date) join (SELECT time::date as date,
                                            count(distinct user_id) as paying_users
                                     FROM   user_actions
                                     WHERE  order_id not in (SELECT order_id
                                                             FROM   user_actions
                                                             WHERE  action = 'cancel_order')
                                     GROUP BY date) t3 using (date)
ORDER BY date

Побудуємо візуалізацію за отриманими даними:

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.users_per_courier,
        mode="lines+markers",
        name="users_per_courier",
    )
)
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.orders_per_courier,
        mode="lines+markers",
        name="orders_per_courier",
    )
)
fig.update_layout(
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.show()
Рисунок 21.6: Динаміка числа користувачів та замовлень на одного кур’єра

Проаналізуйте побудовані графіки та спробуйте відповісти на такі питання:

  1. Чи збігається загалом динаміка аналізованих показників? Якщо так, то чому так відбувається?
  2. Як змінюються показники? Вони швидше зростають чи, навпаки, падають? Про що може говорити така динаміка?
  3. Як ви вважаєте, чи високе навантаження у кур’єрів нашого сервісу? Чи варто сервісу продовжувати збільшувати кількість кур’єрів чи, навпаки, зараз краще призупинити нарощування їх чисельності?

21.7 Задача 7

Розрахуємо ще один корисний показник, що характеризує якість роботи кур’єрів.

Завдання 21.7
На основі даних у таблиці courier_actions для кожного дня розрахуйте, за скільки хвилин у середньому кур’єри доставляли свої замовлення.

Назвіть колонку з показником minutes_to_deliver. Колонку з датами назвіть date. При розрахунку середнього часу доставки округляйте кількість хвилин до цілих значень. Враховуйте лише доставлені замовлення, скасовані замовлення не враховуйте.

Результуюча таблиця має бути відсортована за зростанням дати.

Поля в результуючій таблиці: date, minutes_to_deliver

Пояснення:

Для вирішення завдання вам, можливо, доведеться згадати, як визначити кількість хвилин, що містяться в інтервалі часу. Ми вже вирішували схожу задачу у завданні 20.7.

Деякі замовлення оформляють одного дня, а доставляють вже наступного. При розрахунку середнього часу доставки в якості днів, для яких рахується середнє, використовуйте дні фактичної доставки замовлень.

Для вирішення завдання спочатку необхідно за допомогою групування та агрегації для кожного замовлення розрахувати час прийняття замовлення кур’єром (мінімальна позначка часу) та час доставки (максимальна позначка часу), обчислити різницю між цими значеннями та за допомогою функції EXTRACT витягти з неї секунди (epoch), перевівши їх за хвилини. Потім можна провести групування за датою доставки та обчислити середній час доставки у кожний із днів.

Рішення
%%sql
SELECT date,
       round(avg(delivery_time))::int as minutes_to_deliver
FROM   (SELECT order_id,
               max(time::date) as date,
               extract(epoch
        FROM   max(time) - min(time))/60 as delivery_time
        FROM   courier_actions
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY order_id) t
GROUP BY date
ORDER BY date
date minutes_to_deliver
0 2022-08-24 20
1 2022-08-25 20
2 2022-08-26 20
3 2022-08-27 20
4 2022-08-28 20
5 2022-08-29 20
6 2022-08-30 20
7 2022-08-31 20
8 2022-09-01 20
9 2022-09-02 20
10 2022-09-03 20
11 2022-09-04 20
12 2022-09-05 20
13 2022-09-06 20
14 2022-09-07 20
15 2022-09-08 20

Запишемо результат запиту у змінну results:

Код
%%sql
results << SELECT date,
       round(avg(delivery_time))::int as minutes_to_deliver
FROM   (SELECT order_id,
               max(time::date) as date,
               extract(epoch
        FROM   max(time) - min(time))/60 as delivery_time
        FROM   courier_actions
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY order_id) t
GROUP BY date
ORDER BY date

Візуалізуємо отримані дані:

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=results.date,
        y=results.minutes_to_deliver,
        mode="lines+markers",
        name="users_per_courier",
    )
)
fig.show()
Рисунок 21.7: Динаміка середнього часу доставки замовлень

Проаналізуйте побудований графік та спробуйте відповісти на наступні питання:

  1. Який, найімовірніше, час очікування доставки замовлення в нашому сервісі?
  2. Чи вдається кур’єрам дотримуватися цього цільового показника?

21.8 Задача 8

І нарешті, давайте оцінимо погодинне навантаження на наш сервіс, з’ясуємо, в які години користувачі оформлюють найбільше замовлень, і заразом проаналізуємо, як змінюється частка скасування залежно від часу оформлення замовлення.

Завдання 21.8
На основі даних у таблиці orders для кожної години на добу розрахуйте такі показники:

  1. Число успішних (доставлених) замовлень.
  2. Число скасованих замовлень.
  3. Частку скасованих замовлень у загальній кількості замовлень (cancel rate).

Колонки з показниками назвіть відповідно successful_orders, canceled_orders, cancel_rate. Колонку із годиною оформлення замовлення назвіть hour. При розрахунку частки скасованих замовлень округляйте значення до трьох знаків після коми.

Результуюча таблиця має бути відсортована за зростанням колонки з годиною оформлення замовлення.

Поля в результуючій таблиці: hour, successful_orders, canceled_orders, cancel_rate

Для вирішення завдання можна спочатку за допомогою групування за датою та агрегацією окремо розрахувати кількість успішних та скасованих замовлень, потім об’єднати дві таблиці в одну за датою і потім розрахувати cancel rate.

Рішення
%%sql
SELECT hour,
       successful_orders,
       canceled_orders,
       round(canceled_orders::decimal / (successful_orders + canceled_orders),
             3) as cancel_rate
FROM   (SELECT date_part('hour', creation_time)::int as hour,
               count(order_id) as successful_orders
        FROM   orders
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY hour) t1
    LEFT JOIN (SELECT date_part('hour', creation_time)::int as hour,
                      count(order_id) as canceled_orders
               FROM   orders
               WHERE  order_id in (SELECT order_id
                                   FROM   user_actions
                                   WHERE  action = 'cancel_order')
               GROUP BY hour) t2 using (hour)
ORDER BY hour
hour successful_orders canceled_orders cancel_rate
0 0 2297 139 0.057
1 1 1867 98 0.050
2 2 1422 62 0.042
3 3 958 56 0.055
4 4 926 48 0.049
5 5 1363 77 0.053
6 6 1583 101 0.060
7 7 2101 100 0.045
8 8 2195 117 0.051
9 9 2606 151 0.055
10 10 2641 135 0.049
11 11 2584 121 0.045
12 12 2440 124 0.048
13 13 2424 122 0.048
14 14 2351 120 0.049
15 15 2513 128 0.048
16 16 2480 158 0.060
17 17 2966 168 0.054
18 18 3149 162 0.049
19 19 3496 189 0.051
20 20 3414 163 0.046
21 21 3197 172 0.051
22 22 2978 127 0.041
23 23 2665 141 0.050

Запишемо результат запиту у змінну results:

Код
%%sql
results << SELECT hour,
       successful_orders,
       canceled_orders,
       round(canceled_orders::decimal / (successful_orders + canceled_orders),
             3) as cancel_rate
FROM   (SELECT date_part('hour', creation_time)::int as hour,
               count(order_id) as successful_orders
        FROM   orders
        WHERE  order_id not in (SELECT order_id
                                FROM   user_actions
                                WHERE  action = 'cancel_order')
        GROUP BY hour) t1
    LEFT JOIN (SELECT date_part('hour', creation_time)::int as hour,
                      count(order_id) as canceled_orders
               FROM   orders
               WHERE  order_id in (SELECT order_id
                                   FROM   user_actions
                                   WHERE  action = 'cancel_order')
               GROUP BY hour) t2 using (hour)
ORDER BY hour

Візуалізуємо отримані дані:

from plotly.subplots import make_subplots

fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(
    go.Bar(
        name="successful_orders",
        x=results.hour,
        y=results.successful_orders,
        offsetgroup=1,
    ),
    secondary_y=False,
)
fig.add_trace(
    go.Bar(
        name="canceled_orders", x=results.hour, y=results.canceled_orders, offsetgroup=1
    ),
    secondary_y=False,
)
fig.add_trace(
    go.Scatter(
        name="cancel_rate", x=results.hour, y=results.cancel_rate, offsetgroup=2
    ),
    secondary_y=True,
)
fig.update_yaxes(rangemode="tozero", secondary_y=True)
fig.update_layout(
    barmode="stack",
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)

fig.show()
Рисунок 21.8: Динаміка показника cancel rate та числа успішних/скасованих замовлень:

Проаналізуйте побудований графік та спробуйте відповісти на такі питання:

  1. В які години спостерігаються пікові значення числа замовлень? В які години користувачі здійснюють найменше замовлень?
  2. Чи простежується якийсь взаємозв’язок між кількістю оформлених замовлень і часткою скасованих замовлень? Чи зростає зі збільшенням числа замовлень показник cancel rate?

21.9 Побудова дашборду

Здається, ми порахували вже багато різних показників і збудували достатньо графіків, щоб зібрати з них повноцінний дашборд!

Побудова дашбордів — творче завдання, тому ми не нічим вас обмежуватимемо. Можете вигадати свої візуалізації, додати графіки з лекції і навіть порахувати свої метрики. Головне — не забувайте про те, що дашборди мають бути зрозумілими та зручними для користувачів.

(a) New Users and Couriers by Date
(b) Total Users and Couriers Growth
(c) New Users and Couriers Change by Date, %
(d) Total Users and Couriers Change by Date, %
(e) Paying Users and Active Couriers
(f) Orders, First Orders and Orders of New Users by Date
(g) Paying Users and Active Couriers Share, %
(h) First Orders and Orders of New Users Share, %
(i) Single Order Users and Several Orders Users Share, %
(j) Orders by Hour
(k) Users and Orders per Courier
(l) Average Delivery Time by Date
Рисунок 21.9: Приклад дашборду з результатами SQL-запитів

Data Miorsh Ihor Miroshnychenko Youtube Monobank