Эффективное хранение данных, которые еще не являются чисто столбчатыми, в формате Arrow

Ранее я задавал вопрос, похожий на этот (в том смысле, что для иллюстрации проблемы используется тот же пример): Эффективное хранение данных, которые еще не совсем столбчатые, в базе данных DuckDB.

Однако DuckDB не является Apache Arrow. В частности, невозможно вставлять данные в таблицу или массив Apache Arrow с помощью SQL-запросов, если только они не делают это косвенно.

Массивы стрелок создаются напрямую так же, как массивы Numpy. . Исходя из того, что я понял из документации, я могу предположить, что, скорее всего, я захочу каким-то образом использовать Тензоры стрелок. Я далеко не уверен, отсюда и этот вопрос.

У меня есть некоторые частично столбчатые данные, подобные этим:

"hello", "2024 JAN", "2024 FEB"
"a", 0, 1

Если бы он был чисто столбчатым, он выглядел бы так:

"hello", "year", "month", "value"
"a", 2024, "JAN", 0
"a", 2024, "FEB", 1

Предположим, что данные представлены в виде массива numpy, например:

import numpy as np

import numpy as np

data = np.array(
    [["hello", "2024 JAN", "2024 FEB"], ["a", "0", "1"]], dtype = "<U"
)
array([['hello', '2024 JAN', '2024 FEB'],
       ['a', '0', '1']], dtype='<U8')

Как я могу эффективно сохранить data в формате Apache Arrow, в чисто столбчатом формате?

Наивным/простым способом было бы грубое преобразование данных в столбчатый формат в Python перед сохранением их в виде массива Apache Arrow.

Здесь я имею в виду конкретно:

import re

data_header = data[0]
data_proper = data[1:]

date_pattern = re.compile(r"(?P<year>[\d]+) (?P<month>JAN|FEB)")
common_labels: list[str] = []
known_years: set[int] = set()
known_months: set[str] = set()
header_to_date: Dict[str, tuple[int, str]] = dict()
for header in data_header:
    if matches := date_pattern.match(header):
        year, month = int(matches["year"]), str(matches["month"])
        known_years.add(year)
        known_months.add(month)
        header_to_date[header] = (year, month)
    else:
        common_labels.append(header)

# hello, year, month, value
new_rows_per_old_row = len(known_years) * len(known_months)
new_headers = ["year", "month", "value"]
purely_columnar = np.empty(
    (
        1 + data_proper.shape[0] * new_rows_per_old_row,
        len(common_labels) + len(new_headers),
    ),
    dtype=np.object_,
)
purely_columnar[0] = common_labels + ["year", "month", "value"]
for rx, row in enumerate(data_proper):
    common_data = []
    ym_data = []
    for header, element in zip(data_header, row):
        if header in common_labels:
            common_data.append(element)
        else:
            year, month = header_to_date[header]
            ym_data.append([year, month, element])

    for yx, year_month_value in enumerate(ym_data):
        purely_columnar[
            1 + rx * new_rows_per_old_row + yx, : len(common_labels)
        ] = common_data
        purely_columnar[
            1 + rx * new_rows_per_old_row + yx, len(common_labels) :
        ] = year_month_value

print(f"{purely_columnar=}")
purely_columnar=
array([[np.str_('hello'), 'year', 'month', 'value'],
       [np.str_('a'), 2024, 'JAN', np.str_('0')],
       [np.str_('a'), 2024, 'FEB', np.str_('1')]], dtype=object)

Теперь достаточно легко создать массив Arrow из этих реорганизованных данных:

import pyarrow as pa

column_types = [pa.string(), pa.int64(), pa.string(), pa.string()]
pa.table(
    [
        pa.array(purely_columnar[1:, cx], type=column_types[cx])
        for cx in range(purely_columnar.shape[1])
    ],
    names=purely_columnar[0],
)
pyarrow.Table
hello: string
year: int64
month: string
value: string
----
hello: [["a","a"]]
year: [[2024,2024]]
month: [["JAN","FEB"]]
value: [["0","1"]]

Но могу ли я еще что-нибудь сделать для хранения данных в чисто столбчатой ​​форме в формате Apache Arrow, кроме первого грубого перевода данных в чисто столбчатый формат?

Пожалуйста, не отправляйте одно и то же сообщение снова. Если вы хотите внести изменения, отредактируйте свое сообщение.

tripleee 29.06.2024 09:46

@tripleee Нет, это два отдельных поста. Форма проблемы та же самая, но механизмы, предоставляемые DuckDB, сильно отличаются от механизмов, предоставляемых Apache Arrow для решения рассматриваемой проблемы.

bzm3r 30.06.2024 04:28

Спасибо за разъяснения и извините, что не заметили различий.

tripleee 30.06.2024 08:23
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я дал ответ на основе SQL на ваш вопрос о DuckDB. Если вас это устраивает, вы можете использовать функцию arrow из Python API, чтобы получить pyarrow.Table.

tbl = con.execute("<query>").arrow()

Если вы используете только Python, возможно, лучшим способом сделать это будет использование pandas с движком pyarrow. Поскольку я не думаю, что pyarrow предоставляет аналитические API более высокого уровня (хотя есть некоторые вычислительные функции, которые могут выполнять низкоуровневые манипуляции).

In [1]: _df = pd.read_csv("pivoted-data.csv", engine = "pyarrow")

In [2]: _df
Out[2]: 
  hello  2024 JAN  2024 FEB
0     a         0         1
1     b         1         0

In [3]: _df.melt(id_vars=["hello"], value_vars=_df.columns[1:])
Out[3]: 
  hello  variable  value
0     a  2024 JAN      0
1     b  2024 JAN      1
2     a  2024 FEB      1
3     b  2024 FEB      0

In [4]: _df = _df.melt(id_vars=["hello"], value_vars=_df.columns[1:])

In [5]:  _df.variable.str.split(" ", expand=True)
Out[5]: 
      0    1
0  2024  JAN
1  2024  JAN
2  2024  FEB
3  2024  FEB

In [6]: pd.concat([
    ...:   _df[["hello", "value"]],
    ...:   _df.variable.str.split(" ", expand=True).rename(columns = {0: "year", 1: "month"})
    ...: ], axis=1)
Out[6]: 
  hello  value  year month
0     a      0  2024   JAN
1     b      1  2024   JAN
2     a      1  2024   FEB
3     b      0  2024   FEB

In [7]: res = pd.concat([
    ...:   _df[["hello", "value"]],
    ...:   _df.variable.str.split(" ", expand=True).rename(columns = {0: "year", 1: "month"})
    ...: ], axis=1)

In [8]: pa.Table.from_pandas(res)
Out[8]: 
pyarrow.Table
hello: string
value: int64
year: string
month: string
----
hello: [["a","b","a","b"]]
value: [[0,1,1,0]]
year: [["2024","2024","2024","2024"]]
month: [["JAN","JAN","FEB","FEB"]]

Я думаю, что лучше всего сделать это через duckdb, если вычислительные функции в Arrow слишком низкого уровня, чтобы обрабатывать «отмену поворота». Я не фанат pandas. Спасибо!

bzm3r 30.06.2024 19:35

Другие вопросы по теме