Я пишу программу python-3.10, которая предсказывает временные ряды различных свойств для большого количества объектов. Мой текущий выбор структуры данных для сбора результатов внутри кода, а затем для записи в файлы — это вложенный словарь словарей массивов. Например, для двух объектов с временными рядами из 3 свойств:
properties = {'obj1':{'time':np.arange(10),'x':np.random.randn(10),'vx':np.random.randn(10)},
'obj2': {'time':np.arange(15),'x':np.random.randn(15),'vx':np.random.randn(15)}}
Причина, по которой мне нравится этот формат вложенного словаря, заключается в том, что доступ к нему интуитивно понятен: внешний ключ — это имя объекта, а внутренние ключи — это имена свойств. Элементы, соответствующие каждому из внутренних ключей, представляют собой пустые массивы, дающие значение некоторого свойства как функцию времени. Мой фактический код генерирует dict из ~ 100 000 объектов (внешних ключей), каждый из которых имеет ~ 100 свойств (внутренние ключи), записанных примерно в 1000 раз (массивы с плавающей запятой).
Я заметил, что когда я делаю np.savez('filename.npz',**properties)
в своем собственном огромном словаре свойств (или его подмножествах), это занимает некоторое время, а размер выходного файла составляет несколько ГБ (вероятно, потому, что np.savez вызывает pickle под капотом, так как мой вложенный словарь это не массив).
Существует ли более эффективная структура данных, широко применимая для моего варианта использования? Стоит ли переключаться с моего вложенного dict на кадры данных pandas, numpy ndarrays или массивы записей или список каких-то табличных объектов? Было бы неплохо иметь возможность сохранять/загружать файл в формате двоичного вывода, который сохраняет сопоставление имен объектов с их свойствами dict/array/table/dataframe и, конечно же, именами каждого из массивов временных рядов свойств. .
С этим savez
он создает файл npy
для каждого внешнего ключа. И да, каждый файл будет рассолом dict с внутренними ключами. Я не думаю, что это слишком сильно повредит файловому пространству, хотя я не исследовал использование памяти dict
pickle. pickle
массива — это его save
npy
файл.
Меня интересует эффективность сохранения (скорость записи и размер файла) и просто узнать, является ли мой вложенный подход к диктовке разумным или нет. Внутренние массивы имеют одинаковый размер да. Таким образом, я мог бы просто создать огромный трехмерный массив numpy, где каждый столбец представляет собой другое свойство, каждая строка дает значения свойств в разное время, и вы складываете эти двухмерные массивы для разных объектов в 3-м измерении. это должна быть более эффективная структура данных, но я хотел бы сохранить «заголовок» имени каждого столбца и идентификатор каждого объекта по 3-й оси. Некоторые столбцы могут быть строками или иметь nan/inf
массивы не имеют «заголовков». Датафреймы делают. Но является ли фрейм с элементами массива более «эффективным», неизвестно.
Зная размеры, вы можете оценить общее использование памяти, по крайней мере, для данных массива, а вместе с ним и общее количество файлов. Например. 100000*100*1000*8/1e9
составляет 80 ГБ данных. Я предполагаю, что вложенная dict
часть хранилища будет измеряться в МБ, размер keys
, в памяти dict
использовать какую-то хеш-таблицу сортировки, которая занимает некоторое место, но я не знаю, как это закодировано, если вообще в pickle
. При распаковке может потребоваться воссоздание хеш-таблицы из эквивалентных списков (list(d.items())
).
Хм интересно. Я думаю, что одна вещь, которую я хочу изучить, это как преобразовать мой вложенный дикт в фрейм данных pandas с мультииндексацией (чтобы, как я уже сказал, я все еще мог получить доступ к 2D-массиву каждого объекта, используя его имя, а затем фрейм данных также может позвольте мне сохранить имена, связанные с каждым из моих столбцов, а не просто числовые индексы). И затем, наконец, просто используйте собственную функцию .to_hdf pandas, чтобы сохранить фрейм данных в файл hdf5. (И я могу разделить свой большой файл размером 80 ГБ на отдельные файлы с группами объектов, чтобы ограничить размер отдельного файла.) Таким образом, я также получаю сжимаемость hdf5.
комментарии к принятому ответу здесь актуальны (о вложенном дикторе против мультииндексного фрейма данных pandas): stackoverflow.com/questions/22661764/…
Использование dicts не является хорошей идеей для больших наборов данных: это потребляет много памяти (из-за повторяющихся ключей), а объекты dicts неэффективны. Кадр данных Pandas намного компактнее и часто более эффективен, за исключением строк или других столбцов на основе объектов. Здесь похоже, что содержимое представляет собой массив Numpy разного размера, поэтому Pandas сохранит их как объект. Таким образом, это будет еще немного компактнее, чем dict (нет необходимости повторять ключ для каждой строки), но неэффективно из-за объектов, хранящихся в каждом столбце. Зубчатые массивы также неэффективны в Numpy.
Хотя существуют более компактные и более эффективные структуры данных, они также могут быть значительно менее удобными для пользователя. Кроме того, для информации не хватает: что вы планируете делать со структурой данных? Только читается или планируете менять, и если да, то как? Все ли массивы типа float? Возможно ли для вас уменьшить точность с 64-битных чисел с плавающей точкой до 362-битных? Какую операцию вы планируете делать на нем?
@JérômeRichard, я не уверен, что этот дикт настолько неэффективен, особенно в таком случае. Если все ключи субдиктов одинаковы, и особенно если они представляют собой короткие строки, как в примере, они не будут занимать много памяти. Каждый подраздел будет просто иметь ссылки на один и тот же небольшой набор строк. В своем ответе я обнаружил, что pickle.dumps для одного из этих субдиктов примерно того же размера, что и его list(dd.values())
, и меньше, чем фрейм данных или даже повторный перенос.
@hpaulj Дело в том, что использование рассола в первую очередь, безусловно, неэффективно, хотя для диктов не намного лучше. Я не думаю, что он эффективно записывает массив Numpy (это то, на что указывает ваш ответ). Похоже, что данные также неэффективно хранятся. Похоже, что данные хранятся как объекты, например, для списков. Для кадра данных HDF5, вероятно, лучше, хотя он может быть не оптимален для небольших массивов и в этом случае. Для датафреймов есть паркет, который должен быть лучше.
Возможно, вместо этого внутренние словари могли бы быть namedtuple. После того, как вы собрали данные, вы создаете один массив numpy. Тогда есть более эффективные способы хранения, например, паркет.
@JérômeRichard, хотя np.save
этого массива составляет 612 байт; массив достаточно мал, чтобы заголовок npy
добавлял 50%. Я предполагал или читал, что, кроме заголовков, рассол массивов numpy был таким же, как np.save.
Давайте посмотрим на ваше значение obj2
, дикт:
In [307]: dd = {'time':np.arange(15),'x':np.random.randn(15),'vx':np.random.randn(15)}
In [308]: dd
Out[308]:
{'time': array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]),
'x': array([-0.48197915, 0.15597792, 0.44113401, 1.38062753, -1.21273378,
-1.27120008, 1.53072667, 1.9799255 , 0.13647925, -1.37056793,
-2.06470784, 0.92314969, 0.30885371, 0.64860014, 1.30273519]),
'vx': array([-1.60228105, -1.49163002, -1.17061046, -0.09267467, -0.94133092,
1.86391024, 1.006901 , -0.16168439, 1.5180135 , -1.16436363,
-0.20254291, -1.60280149, -1.91749387, 0.25366602, -1.61993012])}
Из этого легко сделать фрейм данных:
In [309]: df = pd.DataFrame(dd)
In [310]: df
Out[310]:
time x vx
0 0 -0.481979 -1.602281
1 1 0.155978 -1.491630
2 2 0.441134 -1.170610
3 3 1.380628 -0.092675
4 4 -1.212734 -0.941331
5 5 -1.271200 1.863910
6 6 1.530727 1.006901
7 7 1.979926 -0.161684
8 8 0.136479 1.518014
9 9 -1.370568 -1.164364
10 10 -2.064708 -0.202543
11 11 0.923150 -1.602801
12 12 0.308854 -1.917494
13 13 0.648600 0.253666
14 14 1.302735 -1.619930
Мы также можем сделать структурированный массив из этого фрейма. Я также мог бы сделать массив непосредственно из вашего словаря, определив тот же составной тип dtype. Но так как у меня уже есть рама, я пойду по этому пути. Различие между структурированным массивом и переназначением незначительно.
In [312]: arr = df.to_records()
In [313]: arr
Out[313]:
rec.array([( 0, 0, -0.48197915, -1.60228105),
( 1, 1, 0.15597792, -1.49163002),
( 2, 2, 0.44113401, -1.17061046),
( 3, 3, 1.38062753, -0.09267467),
( 4, 4, -1.21273378, -0.94133092),
( 5, 5, -1.27120008, 1.86391024),
( 6, 6, 1.53072667, 1.006901 ),
( 7, 7, 1.9799255 , -0.16168439),
( 8, 8, 0.13647925, 1.5180135 ),
( 9, 9, -1.37056793, -1.16436363),
(10, 10, -2.06470784, -0.20254291),
(11, 11, 0.92314969, -1.60280149),
(12, 12, 0.30885371, -1.91749387),
(13, 13, 0.64860014, 0.25366602),
(14, 14, 1.30273519, -1.61993012)],
dtype=[('index', '<i8'), ('time', '<i4'), ('x', '<f8'), ('vx', '<f8')])
Теперь давайте сравним строки огурцов:
In [314]: import pickle
In [315]: len(pickle.dumps(dd))
Out[315]: 561
In [316]: len(pickle.dumps(df)) # df.to_pickle makes a 1079 byte file
Out[316]: 1052
In [317]: len(pickle.dumps(arr)) # arr.nbytes is 420
Out[317]: 738 # np.save writes a 612 byte file
И другая кодировка - список:
In [318]: alist = list(dd.items())
In [319]: alist
Out[319]:
[('time', array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])),
('x',
array([-0.48197915, 0.15597792, 0.44113401, 1.38062753, -1.21273378,
-1.27120008, 1.53072667, 1.9799255 , 0.13647925, -1.37056793,
-2.06470784, 0.92314969, 0.30885371, 0.64860014, 1.30273519])),
('vx',
array([-1.60228105, -1.49163002, -1.17061046, -0.09267467, -0.94133092,
1.86391024, 1.006901 , -0.16168439, 1.5180135 , -1.16436363,
-0.20254291, -1.60280149, -1.91749387, 0.25366602, -1.61993012]))]
In [320]: len(pickle.dumps(alist))
Out[320]: 567
Спасибо! Что, если бы я хотел преобразовать свой вложенный словарь properties
во «вложенный фрейм данных панд» (или, я думаю, серию кадров данных?). Была бы это эффективной структурой данных, особенно если бы все объекты имели массивы одинакового размера, так что вы могли бы просто складывать по 3-му измерению? Другими словами: ключ внешнего фрейма данных/серии = объект #/строка, а значение для любого объекта представляет собой 2D-фрейм данных, такой как ваш df
выше, и далее предположим, что все объекты имеют столбцы одинаковой длины для своих собственных df. Я бы, наверное, сохранил как hdf5 (с сжатием или без, в зависимости от времени сохранения/загрузки).
Возможно, вам нужна мультииндексация pandas. Ячейки Pandas (серии) могут быть типа объекта dtype и содержать списки, массивы или строки, но они неэффективны (ничего похожего на многомерные массивы), и их может быть сложно сохранить/загрузить (csv
формат файла по своей сути 2d). Но это далеко за пределами моего опыта работы с пандами.
Вы обеспокоены в основном "эффективностью" сохранения/загрузки, или есть расчеты, которые вы делаете с массивами, которые работали бы лучше, если бы они были объединены в один многомерный массив? Все ли самые внутренние массивы имеют одинаковую форму?