Русский
Русский
English
Статистика
Реклама

Powerbi

Повторяем когортный анализ. Комплексный подход Python, SQL, Power BI

16.02.2021 12:20:24 | Автор: admin

Добрый день уважаемые читатели! Данная статья является продолжением публикации "Повторяем когортный анализ, выполненный в Power BI, силами Python" (ссылка). Настоятельно рекомендую познакомиться с ней хотя бы бегло, иначе последующее повествование будет вам малопонятным. С момента ее выхода на Хабр прошло достаточно времени. Я основательно пересмотрел методологию решения подобных задач. Первым желанием было просто переписать старый материал, но после недолгих размышлений я пришел к выводу, что более разумным шагом будет оформить наработки в новую рукопись.

Какова основная причина моего "недовольства" Python и Power BI? Язык Python/R c тематическими библиотеками и Power BI (Tableau, Qlik) могут на 70-80% закрыть потребности бизнеса в расчете сложных метрик и построении визуализаций. Но только если речь идет об обработке относительно небольших датасетов с уже агрегированными данными. Если мы говорим о предварительном манипулировании данными в промышленном масштабе, то здесь игра переходит на сторону сервера с БД и используется SQL. Данный момент я не осветил в предыдущей публикации, поэтому решил ликвидировать это упущение здесь.

Для разработки и тестирования запросов SQL я выбрал БД PostgreSQL. Данную БД установил локально на ноутбук. Никаких специфических настроек не проводил, оставил все параметры как есть. Для повторения действий, описанных в материале, подойдет и запуск контейнера c PostgreSQL, если вы дружите с Docker.

Датасет в формате csv и файлы со скриптами вы можете найти на GitHub (ссылка). Так как информация была заранее подготовлена для непосредственной загрузки, то мне оставалось только воспользоваться встроенной программой pgAdmin. Время загрузки чуть более 1 млн строк в режиме графического редактора 4-5 сек. Данный показатель стал эталоном, так как мне не удалось превзойти его с помощью кода Python. Загрузку данных в PostgreSQL с помощью скриптов для нужд демо-примера можно было бы и не реализовывать, но ведь мы не ищем легких путей в аналитике.

На первом этапе создаем таблицу sales. Сам код предельно прост и не требует каких-либо дополнительных комментариев.

import psycopg2# Подключение к БДconn = psycopg2.connect("dbname='db' user='postgres' password='gfhjkm' host='localhost' port='5432'")print("Database opened successfully")# Создаем курсорcursor = conn.cursor()with conn:    cursor.execute("""            DROP TABLE IF EXISTS sales;        """)    cursor.execute("""            CREATE TABLE IF NOT EXISTS sales (              id SERIAL PRIMARY KEY,              date DATE NOT NULL,               promo TEXT NOT NULL,              site TEXT NOT NULL,              user_id TEXT NOT NULL,              transaction_id INTEGER NOT NULL,              amount INTEGER NOT NULL);        """)print("Operation done successfully")# Закрываем соединение и курсорcursor.close()conn.close()

Таблица сформирована, запускаем следующий скрипт на запись данных в БД. Pandas и sqlalchemy работают в паре. Параллельно замеряем время с помощью datetime.

import osimport pandas as pdimport psycopg2from sqlalchemy import create_enginefrom datetime import datetimestart_time = datetime.now()# Подключение к БДengine = create_engine('postgresql://postgres:gfhjkm@localhost:5432/db')print("Database opened successfully")# Путь к исходнику с даннымиpath_to_data = "C:/Users/Pavel/PycharmProjects/database/"# Считываем данные в датафреймsale_records = pd.read_csv(os.path.join(path_to_data, "СohortAnalysis_2016_2018.csv"),                           sep=";", parse_dates=["date"], dayfirst=True)postgresql_table = "sales"# Записываем датасет в БДsale_records.to_sql(postgresql_table, engine, if_exists='append', index=False)print("Operation done successfully")end_time = datetime.now()print('Duration: {}'.format(end_time - start_time))

Получаем 3 минуты 26 секунд. Очень долго. Я решил, что во всем виновата библиотека sqlalchemy и написал новый код без нее.

import psycopg2from datetime import datetimestart_time = datetime.now()# Подключение к БДconn = psycopg2.connect("dbname='db' user='postgres' password='gfhjkm' host='localhost' port='5432'")print("Database opened successfully")# Создаем курсорcursor = conn.cursor()# Путь к исходнику с даннымиpath_to_data = "C:/Users/Pavel/PycharmProjects/database/"# Считываем данные в датафреймsale_records = pd.read_csv(os.path.join(path_to_data, "СohortAnalysis_2016_2018.csv"),                           sep=";", parse_dates=["date"], dayfirst=True)query = "INSERT INTO sales (date, promo, site, user_id, transaction_id, amount) values (%s, %s, %s, %s, %s, %s)"dataset_for_db = sale_records.values.tolist()cursor.executemany(query, dataset_for_db)conn.commit()print("Operation done successfully")# Закрываем соединение и курсорcursor.close()conn.close()end_time = datetime.now()print('Duration: {}'.format(end_time - start_time))

В итоге я получил прирост в производительности в 10 секунд. Следующий кандидат на выбывание pandas.

import psycopg2from datetime import datetimestart_time = datetime.now()# Подключение к БДconn = psycopg2.connect("dbname='db' user='postgres' password='gfhjkm' host='localhost' port='5432'")print("Database opened successfully")# Создаем курсорcursor = conn.cursor()# Открываем файл. Считываем его построчно с записью в БДwith open('СohortAnalysis_2016_2018.csv', 'r', encoding='UTF8') as f:    next(f)    cursor.copy_from(f, 'sales', sep=';', columns=('date','promo','site','user_id','transaction_id','amount'))    conn.commit()f.close()print("Operation done successfully")# Закрываем соединение и курсорcursor.close()conn.close()end_time = datetime.now()print('Duration: {}'.format(end_time - start_time))

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

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

SELECT s3.date,s3.user_id,s3.date - s2.first_date AS delta_days,ceil((s3.date - s2.first_date)::real/30::real)*30 AS cohort_days,to_char(s2.first_date,'YYYY-MM') AS first_transactions3.amountFROM public.sales AS s3LEFT JOIN(SELECT s1.user_id,MIN(s1.date) AS first_dateFROM public.sales AS s1GROUP BY s1.user_id) AS s2 ON s3.user_id = s2.user_idORDER BY s3.user_id,s3.dateSELECT  s.date,s.user_id,s.date - FIRST_VALUE(s.date) OVER(PARTITION BY s.user_id ORDER BY s.date) AS delta_days,ceil((s.date - FIRST_VALUE(s.date) OVER(PARTITION BY s.user_id ORDER BY s.date))::real/30::real)*30 AS cohort_days,to_char(FIRST_VALUE(s.date) OVER(PARTITION BY s.user_id ORDER BY s.date),'YYYY-MM') AS first_transaction,s.amountFROM public.sales AS sORDER BY s.user_id,s.date

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

С отнесением временной дельты (разница между датой транзакции и датой первой покупки) к когорте дела обстоят несколько сложнее. Когда речь заходит о проверке попадания того или иного числового значения в какие-либо рамки, люди обычно вспоминают CASE. Данная конструкция выглядит относительно неплохо, но только если нужно одновременно реализовать не более 3 условий. В нашем случае их десятки и она просто технически невозможна. Вторая мысль, которая меня посетила, это найти решение среди оконных функций. Среди синтаксического сахара PostgreSQL также не было ответа на искомый вопрос. Решение этой нетривиальной для меня задачи лежало совершенно в другой области - в элементарной математике.

Порассуждаем вместе. Шаг когорты 30 дней. Значит все значения нужно делить на 30. Берем 0 и делим на 30, получаем 0, это желаемый результат. Далее берем любое значение от 0 до 30 и делим на 30. Получаем дробь, но ее обязательно нужно округлить до ближайшего целого. Еще шаг, 30 делим на 30, получаем 1, и ничего округлять не нужно. Следовательно, нам нужна функция, которая округляет дроби до целого в большую сторону. В PostgreSQL это ceil(). Если результат округления умножить на 30 получается номер когорты.

Остается заострить ваше внимание еще на одном интересном нюансе. Если число INTEGER разделить на число INTEGER, то мы получим только целочисленный остаток. Но ведь нам критически важна дробная часть! Это не беда, просто добавляем в нужные места конструкцию ::real и все будет считаться правильно.

Ключевую мысль по данному разделу можно сформулировать следующим образом: если вам сходу не удается подобрать ключик к решению на языке SQL просто посмотрите на проблему под другим углом.

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

Второй отчет сводится к тому, что нужно посчитать нарастающий итог по строке в процентном отношении.

SELECT r2.first_transaction,r2.cohort_days,--r2.total_amount,--sum(r2.total_amount) OVER (PARTITION BY r2.first_transaction ORDER BY r2.first_transaction, r2.cohort_days) as cumsum_amount,--first_value(r2.total_amount) OVER (PARTITION BY r2.first_transaction ORDER BY r2.first_transaction, r2.cohort_days) as first_total_amount,round((sum(r2.total_amount) OVER (PARTITION BY r2.first_transaction ORDER BY r2.first_transaction, r2.cohort_days)/ first_value(r2.total_amount) OVER (PARTITION BY r2.first_transaction ORDER BY r2.first_transaction, r2.cohort_days)-1),3) as percent_cumsum_amountFROM (SELECT r.first_transaction, r.cohort_days, sum(r.amount) AS total_amountFROM public.report_cohort_analysis AS rGROUP BY r.first_transaction, r.cohort_daysORDER BY r.first_transaction, r.cohort_days) as r2

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

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

По идее оба отчета нужно выводить конечному пользователю в виде сводной таблицы. Но данный вопрос слабое место SQL. В PostgreSQL есть функция CROSSTAB, но как с помощью нее быстро и легко создать таблицу с десятками столбцов я себе не представляю. Поэтому на финальном этапе работы я решил обратиться за помощью к BI платформе. Забрать данные из БД для Power BI не представляет никакого труда, достаточно при первом подключении правильно прописать все параметры (значения аналогичны тем, что мы указывали при коннекте к БД через скрипт Python). Представления отображаются как физические таблицы (не нужно повторно вводить скрипт SQL). Нам остается только отметить необходимые отчеты галочками и дождаться загрузки данных.

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

На этом все. Всем здоровья, удачи и профессиональных успехов!

Подробнее..
Категории: Python , Postgresql , Sql , Data mining , Data engineering , Powerbi

Подключение БД с SSH-туннелем к PowerBI

28.02.2021 16:06:27 | Автор: admin

Всем привет!

Оказалось, что PowerBI не имеет встроенной возможности настроить доступ к БД, защищённой SSH-туннелем. Приходится выкручиваться. Мне очень помогла эта статья - спасибо тебе добрый и компетентный в написании инструкций человек, без тебя я бы впала в отчаяние.

И тем не менее, в ней раскрыты не все нюансы. В своём материале я добавлю следующее:

  • Два уникальных совета, как сделать так, чтобы установленный туннель не падал после авторизации

  • Дополнительная инструкция для подключения к SSH при помощи приватного ключа, а не логина и пароля

  • Скрины из самого PowerBI с настройкой БД и советы о том, как работает выборка из подключенной БД и как обновлять данные, полученные по SQL-запросам.

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

Итак, поехали.

Вам понадобится(этап подготовки):

  1. Установленный Putty. Можно взять здесь - https://www.putty.org/

  2. Данные от вашего бекенда или девопса по списку:

    1. IP-адрес SSH-сервера;

    2. порт SSH-сервера;

    3. username для доступа на SSH-сервер;

    4. пароль для доступа или связка приватного и публичного ключа*

    5. IP-адрес самой БД (обычно 127.0.0.1);

    6. порт самой БД;

    7. название БД;

    8. логин доступа к БД (не то же самое, что username для доступа на SSH-сервер);

    9. пароль для доступа к БД.

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

Поднимаем SSH-туннель

  1. Открываем Putty

  2. В Category/Session вводим IP-адрес SSH-сервера, порт SSH-сервера и выставляем радио-баттон Close window on exit на позицию Never

  3. Переходим в Category/Connection/SSH и ставим галочку на Dont start a shell or command at all

  4. Переходим в Category/Connection/SSH/Tunnels, в поле Source port вбиваем порт самой БД, в поле Destination IP-адрес самой БД:порт самой БД. Жмём Add.

  5. *пункт для тех, кто подключается с приватным ключом, если у вас случай с логином и паролем, то переходите сразу к 6 пункту инструкции

    1. Запустите PuttyGen (установился на ваш компьютер вместе с Putty)

    2. Выберите в верхнем меню Conversions/Import Key

    3. В открывшемся окне Проводника откройте папку, куда вы сохранили файлы приватного и публичного ключа (пункт 2d списка Вам понадобится) и выберите файл приватного ключа. Иногда Windows делает этот файл скрытым. Возможно, вам надо будет включить отображение скрытых файлов в Проводнике, нажав на Вид и поставив галочку напротив Скрытые элементы

    4. Нажимаем Save private key. Даём ключу любое имя на латинице и сохраняем в папку с ключами.

    5. Возвращаемся в Putty. Переходим в Category/Connection/SSH/Auth и нажимаем Browse рядом с Private key file for authentication

    6. В открывшемся окне Проводника выбираем сохранённый в пункте 5d файл приватного ключа.

  6. Переходим в Category/Session, в поле Saved Session вводим имя нашего туннеля (любое), жмём Save. Это позволит нам не вводить все настройки каждый раз заново. После чего жмём Open

  7. В открывшемся окне Терминала рядом с Login as вводим username для доступа на SSH-сервер и жмём Enter

  8. *пункт для тех, у кого авторизация по паролю, если вы авторизовались по связке ключей, то пропускайте этот пункт и переходите сразу к 9.

    1. Вводим пароль для доступа на SSH-сервер и жмём Enter

Настройка PowerBI

SSH-туннель настроен, не закрывайте окно терминала Putty. Теперь переходим в PowerBI. Жмём Получить данные и выбираем База данных MySQL или База данных PostgreSQL в зависимости от того, что у вас. Интерфейс будет одинаковым, а вот вероятность успеха - разной, потому что MySQL И PostgreSQL используют разные драйвера. Убедитесь, что выбрали свою БД правильно.

  1. В поле Сервер вводим IP-адрес самой БД:порт самой БД

  2. В поле База данных вводим название БД

  3. Жмём Расширенные настройки и в поле Инструкция SQL вставляем запрос, по которому нужно импортировать данные. Если вы его не напишете, PowerBI приконнектится ко всей БД, но не позволит вам вытаскивать из неё данные запросами и не позволит построить модели (или я не нашла как, если у вас есть успешный опыт, с удовольствием прочитаю его в комментах)

  4. Жмём ok

  5. Вводим логин доступа к БД и пароль для доступа к БД, жмём Подключение

  6. Возможна вот такая ошибка, это ok

Как обновить данные из БД в PowerBI

  1. Поднимаем SSH-туннель в Putty

  2. Переходим в PowerBI и жмём Обновить. Все созданные запросы ещё раз отправятся на сервер и выгрузят свежую информацию

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru