Ранее я задавал вопрос, похожий на этот (в том смысле, что для иллюстрации проблемы используется тот же пример): Эффективное хранение данных, которые еще не совсем столбчатые, в базе данных 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 Нет, это два отдельных поста. Форма проблемы та же самая, но механизмы, предоставляемые DuckDB, сильно отличаются от механизмов, предоставляемых Apache Arrow для решения рассматриваемой проблемы.
Спасибо за разъяснения и извините, что не заметили различий.
Я дал ответ на основе 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
. Спасибо!
Пожалуйста, не отправляйте одно и то же сообщение снова. Если вы хотите внести изменения, отредактируйте свое сообщение.