2025, Dec 28 18:03

Сохранение «рваных» массивов из pandas в HDF5 с h5py и dtype переменной длины

Как сохранить pandas DataFrame с «рваными» массивами в HDF5: используем h5py и dtype переменной длины (vlen) для numpy-столбца, получая совместимый файл.

Сохранить pandas DataFrame, где один столбец хранит массивы разной длины, на словах легко — пока дело не доходит до HDF5. Типичные подходы либо привязывают вас к специфическому формату pandas, либо падают на «рваных» формах. Ниже — короткое руководство, как положить такие данные в пригодный для повторного использования HDF5‑файл и при этом не потерять переменную длину массивов.

Постановка задачи

У вас есть DataFrame: в одном столбце — NumPy‑массивы переменной длины, в другом — обычные числовые значения. Прямая попытка сохранить это в интероперабельном виде через pandas или чистый h5py заканчивается неудачей.

payload = {
    'alpha': [np.array([1., 2.]), np.array([6., 7., .6]), np.array([np.nan])],
    'beta': np.array([99., 66., 88.])
}
frame = pd.DataFrame(payload)

# Pandas HDFStore, table format
frame.to_hdf('store.h5', mode='w', key='tbl', format='table')

Возникает ошибка, потому что столбец с массивами имеет тип object и смешанное содержимое.

TypeError: Невозможно сериализовать столбец [alpha], поскольку его содержимое — не [string], а [mixed] object dtype

Переход к наивной записи через h5py со списком строк тоже проваливается: по умолчанию HDF5 ожидает прямоугольные формы наборов данных.

with h5py.File('store.h5', 'w') as f:
    f.create_dataset('tbl', data=frame.to_numpy().tolist())

ValueError: попытка присвоить элементу массива последовательность. Запрошенный массив имеет неоднородную форму после 2 измерений. Обнаруженная форма: (3, 2) + неоднородная часть.

Что на самом деле происходит

Наборы данных HDF5 по умолчанию не поддерживают «рваные» структуры. Формат pandas «table» рассчитывает на однородные по типу, колонко-ориентированные данные, а столбец типа object с NumPy‑массивами разной длины этому требованию не соответствует. При попытке записать всю 2D‑представление целиком через h5py HDF5 вынужден выводить одну прямоугольную форму, что рушится из‑за столбца с последовательностями разных размеров. Формат pandas «fixed» действительно записывается, но результат завязан на внутренности pandas и не предназначен как общий макет для повторного использования в других инструментах.

Решение с типом переменной длины

HDF5 поддерживает элементы переменной длины через специальный dtype. В h5py можно объявить тип «variable-length» для float и сохранять столбец с массивами как набор данных, где каждый элемент — это собственный одномерный вектор float. Второй столбец остаётся обычным числовым набором данных.

# build the same data
payload = {
    'alpha': [np.array([1., 2.]), np.array([6., 7., .6]), np.array([np.nan])],
    'beta': np.array([99., 66., 88.])
}
frame = pd.DataFrame(payload)

# write to HDF5 with variable-length dtype for the ragged column
store = h5py.File('archive.h5', mode='w')
varlen_f64 = h5py.vlen_dtype(np.dtype('float64'))
store.create_dataset('alpha', data=frame['alpha'], dtype=varlen_f64)
store['beta'] = frame['beta']

Так первый столбец остаётся набором векторов float переменной длины, а второй записывается как стандартный числовой датасет.

Почему это важно

Правильное представление в HDF5 критично, когда ваш DataFrame сочетает обычные столбцы с «рваными» массивами. Формат pandas «table» ориентирован на равномерно сериализуемые колонки и отвергнет смешанные данные типа object. Пытаться «слить» всю 2D‑структуру одним махом тоже нельзя — HDF5 попытается сделать её прямоугольной. Явное объявление типа переменной длины согласует память с нативной моделью HDF5 и сохраняет различия форм от строки к строке.

Практические выводы

Если нужно архивировать данные pandas, где на строку приходится массив переменной длины, не полагайтесь на специфичный для pandas макет, когда важна совместимость. Записывайте колонки по отдельности и используйте тип переменной длины для «рваного» столбца. Такой приём сохраняет структуру данных и остаётся в рамках модели HDF5.

Проще говоря, DataFrame остаётся как есть, а при сериализации разделяйте обязанности: обычные числовые колонки — это стандартные датасеты, а «рваный» столбец — датасет переменной длины. Этого достаточно, чтобы получить надёжный и переиспользуемый HDF5‑файл.