Как получить статистику по данным и графики из PostgreSQL: пример создания своего PDF-отчета средствами Python-библиотеки matplotlib. Пишем простой скрипт в Google Colab для анализа операционной деятельности интернет-магазина.
Постановка задачи
Сегодня рассмотрим не слишком типичный для работы системного и бизнес-аналитика случай, когда необходимо вручную получить данных из БД, чтобы сделать предложить варианты улучшения бизнеса. Чтобы выполнить такой анализ и сформировать рекомендации, необходимо иметь аналитику, т.е. фактические данные. Причем их получить сразу в агрегированном и презентативном виде, например, PDF-отчет. Как обычно, в качестве примера я возьму базу данных своего ранее спроектированного и реализованного интернет-магазина, которая имеет следующую схему для PostgreSQL:
Предположим, необходимо сделать выводы об операционной деятельности магазина по следующим данным за определенный период:
- количество заказов;
- максимальная, минимальная и средняя сумма чека;
- общая сумма дохода, т.е. выручки от продажи товаров;
- помесячная выручка и количество заказов;
- самые часто покупаемые товары.
Итоговый отчет в виде неизменяемого PDF-файла должен содержать все вышеперечисленные сведения в текстовой и графической формах. Например, для отображения помесячной выручки отлично подходят гистограммы, а для оценки популярности конкретного товара относительно общего числа продаж – круговая диаграмма. Все эти и другие виды графиков поддерживает Python-библиотека matplotlib. Кроме функций для работы с двумерной и трехмерной графикой, она также позволяет формировать PDF-документ с текстовыми, табличными, численными и графическими данными. Поэтому для решаемой задачи будем использовать именно ее, что и рассмотрим далее, сделав небольшой переход из роли аналитика в роль разработчика.
Разработка ТЗ на информационную систему по ГОСТ и SRS
Код курса
TTIS
Ближайшая дата курса
2 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Реализация PDF-отчета с matplotlib
Как обычно, напишу и запущу Python-скрипт в Google Colab. Скрипт будет состоять из 3-х ячеек:
- ячейка, выполняемая один раз для импорта пакетов, создания директории для сохранения отчетов и установки значений глобальных переменных;
- ячейка с объявлением функций обращения к БД для выполнения SQL-запросов – разумеется, при отладке эта ячейка выполняется несколько раз, но после того, как достигнута корректная работа всех функций, ее можно запускать однократно;
- ячейка с формированием PDF-отчета по заданному периоду. Поскольку именно эта ячейка генерирует PDF-отчет, она запускается многократно, каждый раз, когда необходимо получить этот документ. Имя файла будет содержать текущую дату.
Python-код выглядит следующим образом:
###############################################ячейка №1 в Google Colab############################# #импорт пакетов import matplotlib.pyplot as plt from matplotlib.backends.backend_pdf import PdfPages import datetime import pandas as pd import psycopg2 import traceback import threading from google.colab import files ############################################# global connection_string #глобальная переменная строка подключения connection_string = 'postgres://username:password@v-host.neon.tech/neondb' #ваша строка подключения к БД #создание директории для сохранения отчетов !mkdir -p /content/reports ###################################################ячейка №2 в Google Colab######################################################################## # Функция получения помесячной выручки из БД (сумма всех заказов, сгруппированных по месяцам) def db_read_monthly_income_count(start, finish): conn = psycopg2.connect(connection_string) cursor = conn.cursor() try: cursor.execute(""" SELECT to_char(orders.date, 'Month') AS mes, SUM(orders.sum) AS income, COUNT(orders.sum) AS quantity FROM orders WHERE EXTRACT(MONTH FROM orders.date) BETWEEN %s::integer AND %s::integer GROUP BY mes ORDER BY (MIN(orders.date)) """, (start, finish)) results = cursor.fetchall() return results except Exception as e: # ловить конкретные исключения и сохранять информацию об исключении в переменную 'e' traceback.print_exc() return 'Ошибка при получении данных из базы данных' finally: cursor.close() #закрыть курсор, который выполняет запросы conn.close() #закрыть соединение с базой данных # Функция получения больше всего востребованных товаров из БД (тех товаров во всех заказах, которых купили больше 500 единиц) def db_read_products_statiscics_count(start, finish): conn = psycopg2.connect(connection_string) cursor = conn.cursor() try: cursor.execute(""" SELECT product.name AS tovar, SUM(order_product.quantity) AS kolichestvo FROM order_product JOIN product on product.id=order_product.product JOIN orders on orders.id=order_product.order WHERE (EXTRACT(MONTH FROM orders.date) BETWEEN %s::integer AND %s::integer) GROUP BY tovar HAVING SUM(order_product.quantity)>500 ORDER BY kolichestvo """, (start, finish)) results = cursor.fetchall() return results except Exception as e: # ловить конкретные исключения и сохранять информацию об исключении в переменную 'e' traceback.print_exc() return 'Ошибка при получении данных из базы данных' finally: cursor.close() #закрыть курсор, который выполняет запросы conn.close() #закрыть соединение с базой данных # Функция получения распределения данных по сумме заказа def db_read_orders_stat(start, finish): conn = psycopg2.connect(connection_string) cursor = conn.cursor() try: cursor.execute(""" SELECT orders.sum AS summa FROM orders WHERE EXTRACT(MONTH FROM orders.date) BETWEEN %s::integer AND %s::integer """, (start, finish)) results = cursor.fetchall() return results except Exception as e: # ловить конкретные исключения и сохранять информацию об исключении в переменную 'e' traceback.print_exc() return 'Ошибка при получении данных из базы данных' finally: cursor.close() #закрыть курсор, который выполняет запросы conn.close() #закрыть соединение с базой данных ###############################################ячейка №3 в Google Colab############################################################################ start = 1 #месяц начала сбора статистики finish = 12 #месяц окончания сбора статистики # Создание DataFrame помесячной выручки и вывод его на экран df = pd.DataFrame(db_read_monthly_income_count(start, finish), columns=['МЕСЯЦ', 'ДОХОД', 'КОЛИЧЕСТВО ЗАКАЗОВ']) # Создание DataFrame самых востребованных товаров из БД и вывод его на экран df2 = pd.DataFrame(db_read_products_statiscics_count(start, finish), columns=['ТОВАР', 'КОЛИЧЕСТВО']) # Создание DataFrame суммы заказа и вывод его на экран df3 = pd.DataFrame(db_read_orders_stat(start, finish), columns=['summa']) # Получаем текущую дату current_date = datetime.datetime.now() # Форматируем дату в строку (например, '2023-03-15') date_str = current_date.strftime('%Y-%m-%d') # Создание PDF-файла для сохранения графиков и таблиц. В название файла включаем текущую дату filename = '/content/reports/report_' + date_str + '.pdf' # Запись данных в PDF-файл with PdfPages(filename) as pdf: # Рассчитаем статистику по суммам заказа kolvo=len(df3['summa']) #количество наблюдений df_max = df3['summa'].max() #максимум df_min=df3['summa'].min() #минимум df_mean = df3['summa'].mean() #среднее значение df_sum=df3['summa'].sum() #сумма # упаковываем статистику в датафрейм для отображения в таблице df_stats = pd.DataFrame({ 'Показатель': ['ВСЕГО заказов', 'Максимальный чек', 'Минимальный чек', 'Среднее значение', 'ИТОГО общая сумма дохода'], 'Значение': [kolvo, df_max, df_min, df_mean, df_sum] }) # Создание фигуры и осей для таблицы fig_table, ax_table = plt.subplots(figsize=(8, 3)) # Выберите подходящий размер фигуры # Задание заголовка ax_table.set_title(f"Статистика по сумме заказов, с {start}-го месяца по {finish}-й месяц") # Задание содержимого таблицы со статистикой ax_table.axis('tight') #задаем границы области для таблицы так, чтобы они плотно обрамляли содержимое ax_table.axis('off') #выключаем отображение осей для таблицы (нет границ и делений) table = ax_table.table(cellText=df_stats.values, colLabels=df_stats.columns, loc='center') #задаем содержимое ячеек таблицы, заголовки столбцов и расположение таблицы table.set_fontsize(10) #устанавливаем размер шрифта для текста в таблице вручную table.scale(1.2, 1.2) # Можно изменить масштаб таблицы для лучшего отображения # Сохранение в pdf и закрытие страницы в файле, чтобы освободить память, связанную с этим объектом Figure в Matplotlib pdf.savefig(fig_table) plt.close(fig_table) # подробности по месячной выручке # Создание фигуры и осей для таблицы fig_table, ax_table = plt.subplots(figsize=(8, 6)) # Выберите подходящий размер фигуры # Задание заголовка ax_table.set_title(f"Доходы по месяцам, с {start}-го месяца по {finish}-й месяц") # Задание содержимого таблицы со статистикой ax_table.axis('tight') # задаем границы области для таблицы так, чтобы они плотно обрамляли содержимое ax_table.axis('off') # выключаем отображение осей для таблицы (нет границ и делений) table = ax_table.table(cellText=df.values, colLabels=df.columns, loc='center') #задаем содержимое ячеек таблицы, заголовки столбцов и расположение таблицы table.set_fontsize(10) # устанавливаем размер шрифта для текста в таблице вручную table.scale(1.2, 1.2) # Можно изменить масштаб таблицы для лучшего отображения # Сохранение в pdf и закрытие страницы в файле, чтобы освободить память, связанную с этим объектом Figure в Matplotlib pdf.savefig(fig_table) plt.close(fig_table) # Создание фигуры и осей для гистограммы fig_hist, ax_hist = plt.subplots(figsize=(14, 6)) # Выберите подходящий размер фигуры # Установим метки на оси X с названиями месяцев ax_hist.set_xticks(range(1+len(df['МЕСЯЦ']))) # Строим столбики гистограммы ax_hist.bar(df['МЕСЯЦ'], df['ДОХОД'], width=0.4, edgecolor="white", label='доход за месяц', linewidth=0.7) # Рисуем число заказов ax_hist.plot(df['МЕСЯЦ'], 5000*df['КОЛИЧЕСТВО ЗАКАЗОВ'], 'r', label='количество заказов (в масштабе x5000)', linewidth=2.0) # Задание содержимого таблицы со статистикой ax_hist.set_title(f"Доход от заказов и их количество по месяцам, с {start}-го месяца по {finish}-й месяц") #Задание осей гистограммы ax_hist.set_xlabel('Месяц') ax_hist.set_ylabel('Доход') # Добавляем легенду на график ax_hist.legend() #Сохранение в pdf и закрытие страницы в файле, чтобы освободить память, связанную с этим объектом Figure в Matplotlib pdf.savefig(fig_hist) plt.close(fig_hist) # Создание фигуры и осей для таблицы fig_table, ax_table = plt.subplots(figsize=(8, 6)) # Выберите подходящий размер фигуры # для самых часто покупаемых товаров #Задание заголовка ax_table.set_title(f"Самые часто покупаемые товары, с {start}-го месяца по {finish}-й месяц") # Задание содержимого таблицы со статистикой ax_table.axis('tight') #задаем границы области для таблицы так, чтобы они плотно обрамляли содержимое ax_table.axis('off') #выключаем отображение осей для таблицы (нет границ и делений) table = ax_table.table(cellText=df2.values, colLabels=df2.columns, loc='center') #задаем содержимое ячеек таблицы, заголовки столбцов и расположение таблицы #table.auto_set_font_size(True) table.set_fontsize(10) #устанавливаем размер шрифта для текста в таблице вручную table.scale(1.2, 1.2) # Можно изменить масштаб таблицы для лучшего отображения #Сохранение в pdf и закрытие страницы в файле, чтобы освободить память, связанную с этим объектом Figure в Matplotlib pdf.savefig(fig_table) plt.close(fig_table) # Создание фигуры и осей для круговой диаграммы fig_circle, ax_circle = plt.subplots(figsize=(10, 8)) # 10 - ширина, 8 - высота фигуры в дюймах #Задание заголовка ax_circle.set_title(f"Самые часто покупаемые товары по месяцам, с {start}-го месяца по {finish}-й месяц") # Строим круговую диаграмму с отображением данных ax_circle.pie(df2['КОЛИЧЕСТВО'], labels=df2['ТОВАР'], autopct='%1.1f%%', startangle=90) # Устанавливаем соотношение сторон диаграммы как 1, чтобы круг выглядел как круг, а не эллипс plt.axis('equal') #Сохранение в pdf и закрытие страницы в файле, чтобы освободить память, связанную с этим объектом Figure в Matplotlib pdf.savefig(fig_circle) plt.close(fig_circle) # Скачиваем файл на локальную машину files.download(filename)
Результат выглядит довольно симпатично.
Пример итогового PDF-отчета можно посмотреть здесь, а представленный код также доступен в моем Github-репозитории.
Основы архитектуры и интеграции информационных систем
Код курса
OAIS
Ближайшая дата курса
20 января, 2025
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Больше примеров, когда аналитику пригодится умение программировать и проектировать ИС я показываю на моих курсах по архитектуре и интеграции информационных систем в Школе прикладного бизнес-анализа на базе нашего лицензированного учебного центра обучения и повышения квалификации системных и бизнес-аналитиков в Москве: