Чтение/запись фрейма данных Polars со столбцом списка из/в базу данных

Написание df с таким столбцом списка

df = pl.DataFrame({'a': [1,2,3], 'b':[['A','B'], ['C', 'D'], ['E', 'F']]})
df.write_database(
    "test", "sqlite:///test.db",
    if_table_exists = "replace",
)

работает нормально, но потом работает

pl.read_database_uri(query = "SELECT * FROM test", uri = "sqlite://test.db")

выдает ошибку

RuntimeError: Invalid column type Blob at index: 1, name: b

Кажется, я не могу обойти это, используя engine_options в write_database, чтобы указать, что мне нужно поле списка (а не большой двоичный объект), или используя schema_overrides в read_database_uri. Каков правильный способ записи/чтения такого типа фрейма данных со столбцами списка?

🤔 А знаете ли вы, что...
Python поддерживает множество парадигм программирования, включая процедурное, объектно-ориентированное и функциональное программирование.


3
51
1

Ответ:

Решено

Я считаю, что в вашем случае вам нужно будет сохранить список строк как BLOB в MySQL lite, а затем каким-то образом декодировать байты обратно в список. Однако есть две проблемы:

  1. Стандартный протокол Polars connectorx не может читать столбцы BLOB, поэтому вам нужно переключиться на adbc
  2. Даже после перехода на adbc непонятно, как преобразовать BLOB обратно в байты.

Таким образом, я бы предложил объединить список строк с любым разделителем и сохранить строки как VARCHAR, а затем преобразовать обратно:

import polars as pl

SEP = "\t"

df = pl.DataFrame({'a': [1,2,3], 'b':[['A','B'], ['C', 'D'], ['E', 'F']]})

# mapping: List[str] -> str
df = df.with_columns(b = pl.col('b').list.join(SEP))
df.write_database(
    "test", "sqlite:///test.db",
    if_table_exists = "replace",
)

ff = pl.read_database_uri(query = "SELECT a,b FROM test", 
                          uri = "sqlite://test.db",
                              )

# mapping: str -> List[str]
ff = ff.with_columns(pl.col('b').str.split(SEP))

Альтернативно:
Или сериализуйте столбец в json и снова сохраните его в базе данных как VARCHAR, а затем десериализуйте обратно.

import json
import polars as pl

df = pl.DataFrame({'a': [1,2,3], 'b':[['A','B'], ['C', 'D'], ['E', 'F']]})

# dump to json
f = lambda x: json.dumps(list(x))
df = df.with_columns(b = pl.col('b').apply(f))


df.write_database(
    "test", "sqlite:///test.db",
    if_table_exists = "replace",
)

ff = pl.read_database_uri(query = "SELECT a,b FROM test", 
                          uri = "sqlite://test.db",
                              )


ff = ff.with_columns(pl.col('b').str.json_decode())

Альтернатива 2:
Подготовьте столбец, преобразовав его в двоичный объект, прежде чем сохранять его в базе данных sql. Столбец будет сохранен как BLOB.

import pickle 
import polars as pl

df = pl.DataFrame({'a': [1,2,3], 'b':[['A','B'], ['C', 'D'], ['E', 'F']]})

# dump column to binary object
df = df.with_columns(b = pl.col('b').apply(pickle.dumps))


df.write_database(
    "test", "sqlite:///test.db",
    if_table_exists = "replace",
)

ff = pl.read_database_uri(query = "SELECT a,b FROM test", 
                          uri = "sqlite://test.db",
                          engine='adbc', # need to use adbc engine instead of standard
                              )
ff = ff.with_columns(pl.col('b').apply(pickle.loads))