Сравнение построения сводных таблиц в PostgreSQL и Pandas

Введение

В данный момент я работаю над системой отчетности, которая должна аггрегировать большое количество данных, причем аггрегировать таким образом, что нужно строить сводные таблицы. Раньше задачи подобно рода я делал с помощью Pandas, но недавно я узнал что это можно делать сразу в PostgreSQL c помощью модуля tablefunc и я решил проверить что будет оптимальней для моего случая.

Тестирование

Я написал следующий тест для сравнения производительности:

import pandas
import sqlalchemy
from pandas.io import sql
from datetime import datetime
import logging

pg_engine = sqlalchemy.create_engine('postgresql://test:test@localhost/vts')
report_date = datetime(2018, 10, 6, 6, 0, 0)

def pandas_pivot(report_date):
    df = pandas.read_sql_query("""SELECT code,
               CASE WHEN delay >= 24 THEN 24 ELSE delay END as delay,
               COUNT(*)                                     AS packages_count
        FROM (SELECT code, nph_id, min(delay) as delay
              FROM (SELECT code,
                           nph_id,
                           floor(extract(epoch from (receive_date - create_date)) / 3600) as delay
                    FROM test_table
                    WHERE create_date >= %(today)s :: timestamp without time zone - interval '2 day'
                      AND create_date < %(today)s :: timestamp without time zone - interval '1 day') as grouped
              GROUP BY code, nph_id) as t
        GROUP BY code, CASE WHEN delay >= 24 THEN 24 ELSE delay END
        order by code;""", pg_engine, params={"today": report_date})
    df.loc[df.delay < 0, 'delay'] = 0
    return df.pivot_table('packages_count', ['code'], ['delay'], aggfunc=sum)

def postgresql_pivot(report_date):
    df = pandas.read_sql_query("""select *
        from crosstab('
            SELECT code,
                   CASE WHEN delay > 24 THEN 24 WHEN delay < 0 THEN 0 ELSE delay END as delay,
                   COUNT(*)                                     AS packages_count
            FROM (SELECT code, nph_id, min(delay) as delay
                  FROM (SELECT code,
                               nph_id,
                               floor(extract(epoch from (receive_date - create_date)) / 3600) as delay
                        FROM test_table
                        WHERE create_date >= ' || quote_literal(%(today)s) || ' :: timestamp without time zone - interval ''2 day''
                      AND create_date < ' || quote_literal(%(today)s) || ' :: timestamp without time zone - interval ''1 day'') as grouped
                  GROUP BY code, nph_id) as t
            GROUP BY code, CASE WHEN delay > 24 THEN 24 WHEN delay < 0 THEN 0 ELSE delay END
            order by code', 'select h from generate_series(0,24) h') as r(
                       code varchar,
                       delay_0 int,
                       delay_1 int,
                       delay_2 int,
                       delay_3 int,
                       delay_4 int,
                       delay_5 int,
                       delay_6 int,
                       delay_7 int,
                       delay_8 int,
                       delay_9 int,
                       delay_10 int,
                       delay_11 int,
                       delay_12 int,
                       delay_13 int,
                       delay_14 int,
                       delay_15 int,
                       delay_16 int,
                       delay_17 int,
                       delay_18 int,
                       delay_19 int,
                       delay_20 int,
                       delay_21 int,
                       delay_22 int,
                       delay_23 int,
                       delay_24 int)""", pg_engine, params={"today": report_date})
    return df

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

Анализ результатов

Для замера я запустил данные функции 6 раз, замерил их время выполнения на каждой итерации и получил следующие результаты:

Параметры pandas postgresql
count 6.00 6.00
mean 2.65 2.68
std 0.04 0.05
min 2.58 2.61
25% 2.61 2.65
50% 2.66 2.68
75% 2.67 2.73
max 2.70 2.75

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

Заключение

Как показал экперимент оба варианта по скорости работают одинаково. При построении сводной в postgresql вам должны быть известны все варианты значений поля, идущего в столбцы иначе запрос завершится ошибкой, в pandas же это знает за вас. Однако pandas ограничен оперативной памятью на вашем пк, тогда как размер данных для таблицы в постгрес не ограничен.

Также надо отметить что pandas умеет строить сводные со сложными столбцами (из нескольких полей) в postgresql я не разобрался как это сделать.

Ссылки

  1. Python Data Analysis Library
  2. tablefunc
 
comments powered by Disqus