Как назначить произвольные метаданные столбцам pyarrow.Table / Parquet - PullRequest
2 голосов
/ 06 апреля 2019

Вариант использования

Я использую файлы Apache Parquet в качестве быстрого формата ввода-вывода для пространственных данных большого размера, над которыми я работаю в Python с GeoPandas.Я сохраняю геометрические объекты в виде WKB и хотел бы записать систему координат координат (CRS) в виде метаданных, связанных с данными WKB.

Проблема кода

Я пытаюсьназначить произвольные метаданные объекту pyarrow.Field.

То, что я пробовал

Предположим, table является pyarrow.Table, созданным из df,pandas.DataFrame:

df = pd.DataFrame({
        'foo' : [1, 3, 2],
        'bar' : [6, 4, 5]
        })

table = pa.Table.from_pandas(df)

В соответствии с документами pyarrow метаданные столбца содержатся в field, который принадлежит schema ( source ), и необязательных метаданныхможет быть добавлен к field ( source ).

Если я пытаюсь присвоить значение атрибуту metadata, возникает ошибка:

>>> table.schema.field_by_name('foo').metadata = {'crs' : '4283'}
AttributeError: attribute 'metadata' of 'pyarrow.lib.Field' objects is not writable

>>> table.column(0).field.metadata = {'crs' : '4283'}
AttributeError: attribute 'metadata' of 'pyarrow.lib.Field' objects is not writable

Если я пытаюсь назначить поле (с метаданными, связанными с помощью метода add_metadata) для поля, оно возвращает ошибку:

>>> table.schema.field_by_name('foo') = (
           table.schema.field_by_name('foo').add_metadata({'crs' : '4283'})
           )
SyntaxError: can't assign to function call

>>> table.column(0).field = table.column(0).field.add_metadata({'crs' : '4283'})
AttributeError: attribute 'field' of 'pyarrow.lib.Column' objects is not writable

Я даже пытался назначить метаданные для pandas.Series object например,

df['foo']._metadata.append({'crs' : '4283'})

, но это не возвращается в метаданных при вызове метода pandas_metadata ( docs ) на атрибуте schemaibute объекта table.

Исследования

По стеку этот вопрос остается без ответа, а этот связанвопрос касается Scala, а не Python и pyarrow. В другом месте Я видел метаданные, связанные с pyarrow.Field объектом, но только путем создания экземпляров pyarrow.Field и pyarrow.Table объектов с нуля.

PS

Это моя первая публикация в stackoverflow, поэтому заранее спасибо и приносим извинения за любые ошибки.

1 Ответ

0 голосов
/ 16 мая 2019

«Все» в Arrow является неизменным, поэтому, как вы уже видели, вы не можете просто изменить метаданные любого поля или схемы.Единственный способ сделать это - создать "новую" таблицу с добавленными метаданными.Я поставил new между кавычками, поскольку это можно сделать без фактического копирования таблицы, поскольку за кулисами это просто перемещает указатели.Вот некоторый код, показывающий, как хранить произвольные словари (если они являются json-serializable) в метаданных Arrow и как их извлекать:

def set_metadata(tbl, col_meta={}, tbl_meta={}):
    """Store table- and column-level metadata as json-encoded byte strings.

    Table-level metadata is stored in the table's schema.
    Column-level metadata is stored in the table columns' fields.

    To update the metadata, first new fields are created for all columns.
    Next a schema is created using the new fields and updated table metadata.
    Finally a new table is created by replacing the old one's schema, but
    without copying any data.

    Args:
        tbl (pyarrow.Table): The table to store metadata in
        col_meta: A json-serializable dictionary with column metadata in the form
            {
                'column_1': {'some': 'data', 'value': 1},
                'column_2': {'more': 'stuff', 'values': [1,2,3]}
            }
        tbl_meta: A json-serializable dictionary with table-level metadata.
    """
    # Create updated column fields with new metadata
    if col_meta or tbl_meta:
        fields = []
        for col in tbl.itercolumns():
            if col.name in col_meta:
                # Get updated column metadata
                metadata = col.field.metadata or {}
                for k, v in col_meta[col.name].items():
                    metadata[k] = json.dumps(v).encode('utf-8')
                # Update field with updated metadata
                fields.append(col.field.add_metadata(metadata))
            else:
                fields.append(col.field)

        # Get updated table metadata
        tbl_metadata = tbl.schema.metadata
        for k, v in tbl_meta.items():
            tbl_metadata[k] = json.dumps(v).encode('utf-8')

        # Create new schema with updated field metadata and updated table metadata
        schema = pa.schema(fields, metadata=tbl_metadata)

        # With updated schema build new table (shouldn't copy data)
        # tbl = pa.Table.from_batches(tbl.to_batches(), schema)
        tbl = pa.Table.from_arrays(list(tbl.itercolumns()), schema=schema)

    return tbl


def decode_metadata(metadata):
    """Arrow stores metadata keys and values as bytes.
    We store "arbitrary" data as json-encoded strings (utf-8),
    which are here decoded into normal dict.
    """
    if not metadata:
        # None or {} are not decoded
        return metadata

    decoded = {}
    for k, v in metadata.items():
        key = k.decode('utf-8')
        val = json.loads(v.decode('utf-8'))
        decoded[key] = val
    return decoded


def table_metadata(tbl):
    """Get table metadata as dict."""
    return decode_metadata(tbl.schema.metadata)


def column_metadata(tbl):
    """Get column metadata as dict."""
    return {col.name: decode_metadata(col.field.metadata) for col in tbl.itercolumns()}


def get_metadata(tbl):
    """Get column and table metadata as dicts."""
    return column_metadata(tbl), table_metadata(tbl)

Короче говоря, вы создаете новые поля с добавленными метаданнымиВы объединяете поля в новую схему, а затем создаете новую таблицу из существующей таблицы и новой схемы.Это все немного скучно.В идеале у pyarrow должны быть вспомогательные функции, позволяющие делать это с меньшим количеством строк кода, но в последний раз я проверял, что это единственный способ сделать это.

Единственное другое осложнение заключается в том, что метаданные хранятся в виде байтов в Arrow, поэтомув приведенном выше коде я храню метаданные в виде json-сериализуемых словарей, которые кодирую в utf-8.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...