Панды - оптимальная стратегия сохранения для максимальной степени сжатия?

Вопрос

Учитывая большую серию DataFrames с небольшим разнообразием dtypes, какова оптимальная конструкция для сохранения/сериализации Pandas DataFrame, если мне важна степень сжатия в первую очередь, скорость распаковки во вторую и начальная скорость сжатия в третью?

Предыстория:

У меня есть примерно 200 тысяч кадров данных формы [2900,8], которые мне нужно хранить в логических блоках по ~ 50 кадров данных в файле. Фрейм данных содержит переменные типа np.int8, np.float64. Большинство фреймов данных являются хорошими кандидатами для разреженных типов, но разреженный не поддерживается в хранилищах формата «таблица» HDF ​​(не то, чтобы это даже помогло — см. Размер разреженного рассола, сжатого с помощью gzip, ниже). Данные генерируются ежедневно и в настоящее время составляют более 20 ГБ. Хотя я не привязан к HDF, мне еще предстоит найти лучшее решение, позволяющее считывать отдельные кадры данных в постоянном хранилище в сочетании с высококачественным сжатием. Опять же, я готов немного пожертвовать скоростью ради лучшей степени сжатия, тем более, что мне нужно будет отправить это по всей сети.

Есть несколько других тем SO и других ссылок, которые могут быть актуальны для тех, кто находится в аналогичном положении. Однако большая часть того, что я нашел, не ориентирована на минимизацию размера хранилища в качестве приоритета:

Рабочие потоки «больших данных» с использованием pandas

HDF5 и SQLite. Параллелизм, сжатие и производительность ввода-вывода [закрыто]

Окружающая среда:

OSX 10.9.5
Pandas 14.1
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
PyTables version:  3.1.1
HDF5 version:      1.8.13
NumPy version:     1.8.1
Numexpr version:   2.4 (not using Intel's VML/MKL)
Zlib version:      1.2.5 (in Python interpreter)
LZO version:       2.06 (Aug 12 2011)
BZIP2 version:     1.0.6 (6-Sept-2010)
Blosc version:     1.3.5 (2014-03-22)
Blosc compressors: ['blosclz', 'lz4', 'lz4hc', 'snappy', 'zlib']
Cython version:    0.20.2
Python version:    2.7.8 (default, Jul  2 2014, 10:14:46)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)]
Platform:          Darwin-13.4.0-x86_64-i386-64bit
Byte-ordering:     little
Detected cores:    8
Default encoding:  ascii
Default locale:    (en_US, UTF-8)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Пример:

import pandas as pd
import numpy as np
import random
import cPickle as pickle
import gzip


def generate_data():
    alldfs = {}
    n = 2800
    m = 8
    loops = 50
    idx = pd.date_range('1/1/1980',periods=n,freq='D')
    for x in xrange(loops):
        id = "id_%s" % x
        df = pd.DataFrame(np.random.randn(n,m) * 100,index=idx)
        # adjust data a bit..
        df.ix[:,0] = 0
        df.ix[:,1] = 0
        for y in xrange(100):
            i = random.randrange(n-1)
            j = random.randrange(n-1)
            df.ix[i,0] = 1
            df.ix[j,1] = 1
        df.ix[:,0] = df.ix[:,0].astype(np.int8)  # adjust datatype
        df.ix[:,1] = df.ix[:,1].astype(np.int8)
        alldfs[id] = df
    return alldfs

def store_all_hdf(x,format='table',complevel=9,complib='blosc'):
    fn = "test_%s_%s-%s.hdf" % (format,complib,complevel)
    hdfs = pd.HDFStore(fn,mode='w',format=format,complevel=complevel,complib=complib)
    for key in x.keys():
        df = x[key]
        hdfs.put(key,df,format=format,append=False)
    hdfs.close()

alldfs = generate_data()
for format in ['table','fixed']:
    for complib in ['blosc','zlib','bzip2','lzo',None]:
        store_all_hdf(alldfs,format=format,complib=complib,complevel=9)

# pickle, for comparison
with open('test_pickle.pkl','wb') as f:
    pickle.dump(alldfs,f)

with gzip.open('test_pickle_gzip.pklz','wb') as f:
    pickle.dump(alldfs,f)

with gzip.open('test_pickle_gzip_sparse.pklz','wb') as f:
    sparsedfs = {}
    for key in alldfs.keys():
        sdf = alldfs[key].to_sparse(fill_value=0)
        sparsedfs[key] = sdf
    pickle.dump(sparsedfs,f)

Результаты

-rw-r--r--   1 bazel  staff  10292760 Oct 17 14:31 test_fixed_None-9.hdf
-rw-r--r--   1 bazel  staff   9531607 Oct 17 14:31 test_fixed_blosc-9.hdf
-rw-r--r--   1 bazel  staff   7867786 Oct 17 14:31 test_fixed_bzip2-9.hdf
-rw-r--r--   1 bazel  staff   9506483 Oct 17 14:31 test_fixed_lzo-9.hdf
-rw-r--r--   1 bazel  staff   8036845 Oct 17 14:31 test_fixed_zlib-9.hdf
-rw-r--r--   1 bazel  staff  26627915 Oct 17 14:31 test_pickle.pkl
-rw-r--r--   1 bazel  staff   8752370 Oct 17 14:32 test_pickle_gzip.pklz
-rw-r--r--   1 bazel  staff   8407704 Oct 17 14:32 test_pickle_gzip_sparse.pklz
-rw-r--r--   1 bazel  staff  14464924 Oct 17 14:31 test_table_None-9.hdf
-rw-r--r--   1 bazel  staff   8619016 Oct 17 14:31 test_table_blosc-9.hdf
-rw-r--r--   1 bazel  staff   8154716 Oct 17 14:31 test_table_bzip2-9.hdf
-rw-r--r--   1 bazel  staff   8481631 Oct 17 14:31 test_table_lzo-9.hdf
-rw-r--r--   1 bazel  staff   8047125 Oct 17 14:31 test_table_zlib-9.hdf

Учитывая приведенные выше результаты, кажется, что лучшим решением для «сжатия в первую очередь» является хранение данных в фиксированном формате HDF с помощью bzip2. Есть ли лучший способ организации данных, возможно, без HDF, который позволил бы мне сэкономить еще больше места?

Обновление 1

Согласно приведенному ниже комментарию Джеффа, я использовал ptrepack в HDF-файле хранилища таблиц без начального сжатия, а затем повторно сжал. Результаты ниже:

-rw-r--r--   1 bazel  staff   8627220 Oct 18 08:40 test_table_repack-blocsc-9.hdf
-rw-r--r--   1 bazel  staff   8627620 Oct 18 09:07 test_table_repack-blocsc-blosclz-9.hdf
-rw-r--r--   1 bazel  staff   8409221 Oct 18 08:41 test_table_repack-blocsc-lz4-9.hdf
-rw-r--r--   1 bazel  staff   8104142 Oct 18 08:42 test_table_repack-blocsc-lz4hc-9.hdf
-rw-r--r--   1 bazel  staff  14475444 Oct 18 09:05 test_table_repack-blocsc-snappy-9.hdf
-rw-r--r--   1 bazel  staff   8059586 Oct 18 08:43 test_table_repack-blocsc-zlib-9.hdf
-rw-r--r--   1 bazel  staff   8161985 Oct 18 09:08 test_table_repack-bzip2-9.hdf

Как ни странно, повторное сжатие с помощью ptrepack, по-видимому, увеличивает общий размер файла (по крайней мере, в этом случае с использованием табличного формата с аналогичными компрессорами).


person bazel    schedule 17.10.2014    source источник
comment
обычно я не пишу со сжатием при сохранении, а вместо этого перепаковываю файл ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5 со сжатием (хотя не уверен, что это повлияет на фактическое соотношение, это функция самих ваших данных). случайные данные вообще не сильно сжимаются, тем не менее, более обычные данные   -  person Jeff    schedule 17.10.2014
comment
Спасибо, Джефф - проверил переупаковку в магазинах формата таблицы, но нашел противоречивые результаты. Тем не менее, смоделированные данные являются довольно хорошим приближением к тому, с чем мне приходится работать (стандартный нормальный, с несколькими столбцами np.int8, которые явно не нормальные).   -  person bazel    schedule 18.10.2014