Экономичное хранилище данных для списка списков. Элементы являются целыми числами, и размер всех списков различается по длине. - PullRequest
1 голос
/ 08 мая 2020

Скажем, мои данные выглядят так

thisList = [
     [[13, 43, 21, 4], [33, 2, 111, 33332, 23, 43, 2, 2], [232, 2], [23, 11]] ,
     [[21, 2233, 2], [2, 3, 2,1, 32, 22], [3]], 
     [[3]], 
     [[23, 12], [55, 3]],
     ....
]

Каков наиболее экономичный способ хранения данных на этот раз?

Я посмотрел на Numpy файлы, но numpy поддерживает только данные одинаковой длины

Я посмотрел на Hdf5, который поддерживает 1d рваные тензоры, но не 2d

{ ссылка }

Таким образом, есть возможность создать отдельный файл hdf5 для каждого списка в thisList, но я потенциально мог бы иметь 10-20 миллионов этих списков.

1 Ответ

2 голосов
/ 08 мая 2020

Я провел тесты, сохранив рваный вложенный список с JSON, BSON, Numpy и HDF5.

TL; DR: используйте сжатый JSON, потому что он наиболее экономичен и проще всего кодировать / декодировать.

Для синтетических данных c здесь результаты (с du -sh test*):

4.5M    test.json.gz
7.5M    test.bson.gz
8.5M    test.npz
261M    test_notcompressed.h5
1.3G    test_compressed.h5

Сжатый JSON является наиболее эффективным с точки зрения хранения, а также его проще всего кодировать и декодировать, потому что рваный список не должен быть преобразован в отображение. На втором месте идет BSON, но его нужно преобразовать в отображение, что усложняет кодирование и декодирование (и сводит на нет преимущества скорости кодирования / декодирования BSON по сравнению с JSON). Сжатый формат NPZ в Numpy занимает третье место, но, как и в BSON, перед сохранением разорванный список необходимо преобразовать в словарь. HDF5 на удивление большой, особенно сжатый. Вероятно, это связано с тем, что существует много разных наборов данных, а сжатие увеличивает накладные расходы на каждый набор данных.


Тесты

Вот соответствующий код для тестирования. Пакет bson является частью pymongo. Я провел эти тесты на машине Debian Buster с файловой системой ext4.

def get_ragged_list(length=100000):
    """Return ragged nested list."""
    import random

    random.seed(42)
    l = []
    for _ in range(length):
        n_sublists = random.randint(1, 9)
        sublist = []
        for i in range(n_sublists):
            subsublist = [random.randint(0, 1000) for _ in range(random.randint(1, 9))]
            sublist.append(subsublist)
        l.append(sublist)
    return l

def save_json_gz(obj, filepath):
    import gzip
    import json

    json_str = json.dumps(obj)
    json_bytes = json_str.encode()
    with gzip.GzipFile(filepath, mode="w") as f:
        f.write(json_bytes)

def save_bson(obj, filepath):
    import gzip
    import bson

    d = {}
    for ii, n in enumerate(obj):
        for jj, nn in enumerate(n):
            key = f"{ii}/{jj}"
            d[key] = nn
    b = bson.BSON.encode(d)
    with gzip.GzipFile(filepath, mode="w") as f:
        f.write(b)

def save_numpy(obj, filepath):
    import numpy as np

    d = {}
    for ii, n in enumerate(obj):
        for jj, nn in enumerate(n):
            key = f"{ii}/{jj}"
            d[key] = nn
    np.savez_compressed(filepath, d)

def save_hdf5(obj, filepath, compression="lzf"):
    import h5py

    with h5py.File(filepath, mode="w") as f:
        for ii, n in enumerate(obj):
            for jj, nn in enumerate(n):
                name = f"{ii}/{jj}"
                f.create_dataset(name, data=nn, compression=compression)
ragged = get_ragged_list()

save_json_gz(ragged, "ragged.json.gz")
save_bson(ragged, "test.bson.gz")
save_numpy(ragged, "ragged.npz")
save_hdf5(ragged, "test_notcompressed.h5", compression=None)
save_hdf5(ragged, "test_compressed.h5", compression="lzf")

Версии соответствующих пакетов:

python 3.8.2 | packaged by conda-forge | (default, Mar 23 2020, 18:16:37) [GCC 7.3.0]
pymongo bson 3.10.1
numpy 1.18.2
h5py 2.10.0
...