Как указать тип данных столбца

У меня есть следующий код:

import polars as pl
from typing import NamedTuple


class Event(NamedTuple):
    name: str
    description: str


def event_table(num) -> list[Event]:
    events = []
    for i in range(num):
        events.append(Event("name", "description"))
    return events


data = {"events": [1, 2]}
df = pl.DataFrame(data).select(events=pl.col("events").map_elements(event_table))

"""
shape: (2, 1)
┌───────────────────────────────────┐
│ events                            │
│ ---                               │
│ list[struct[2]]                   │
╞═══════════════════════════════════╡
│ [{"name","description"}]          │
│ [{"name","description"}, {"name"… │
└───────────────────────────────────┘
"""

Но если первый список пуст, я получаю list[list[str]] вместо нужного list[struct[2]]:

data = {"events": [0, 1, 2]}
df = pl.DataFrame(data).select(events=pl.col("events").map_elements(event_table))
print(df)

"""
shape: (3, 1)
┌───────────────────────────────────┐
│ events                            │
│ ---                               │
│ list[list[str]]                   │
╞═══════════════════════════════════╡
│ []                                │
│ [["name", "description"]]         │
│ [["name", "description"], ["name… │
└───────────────────────────────────┘
"""

Я попробовал использовать return_dtype функции map_elements, например:

data = {"events": [0, 1, 2]}
df = pl.DataFrame(data).select(
    events=pl.col("events").map_elements(
        event_table,
        return_dtype=pl.List(pl.Struct({"name": pl.String, "description": pl.String})),
    )
)

но это не удалось:

Traceback (most recent call last):
  File "script.py", line 18, in <module>
    df = pl.DataFrame(data).select(
         ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.11/site-packages/polars/dataframe/frame.py", line 8193, in select
    return self.lazy().select(*exprs, **named_exprs).collect(_eager=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.11/site-packages/polars/lazyframe/frame.py", line 1943, in collect
    return wrap_df(ldf.collect())
                   ^^^^^^^^^^^^^
polars.exceptions.SchemaError: expected output type 'List(Struct([Field { name: "name", dtype: String }, Field { name: "description", dtype: String }]))', got 'List(List(String))'; set `return_dtype` to the proper datatype

Как я могу заставить это работать? мне нужно, чтобы тип этого столбца был событием list[struct[2]], если первый список пуст.

Попытка return pl.Series(events, dtype=pl.List(pl.Struct({"name": pl.String, "description": pl.String}))) вызывает PanicException — так что это похоже на «ошибку». Если вы контролируете функцию if events: return events, она, кажется, работает, но вместо этого выдает вам null.

jqurious 01.04.2024 11:26

Похоже, действительно ошибка. Это работает при инициализации. Например, pl.DataFrame({'events': [[], [["name", "description"]]]}, schema = {'events': pl.List(pl.Struct({"name": pl.String, "description": pl.String}))}).

ouroboros1 01.04.2024 11:42
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
5
2
171
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как указано в комментариях, это похоже на ошибку в полярах. Однако мне удалось заставить это работать, используя pl.Field в определении pl.Struct dtype и передавая dict вместо NamedTuple.

def event_table(num) -> list[Event]:
    events = []
    for i in range(num):
        # note the usage of a plain dict here
        events.append({"name": "my_name", "description": "my_desc"})
    return events

data = {"events": [0, 1, 2]}
pl.DataFrame(data).select(
    events=pl.col("events").map_elements(
        event_table,
        return_dtype=pl.List(pl.Struct([pl.Field("name", pl.String), pl.Field("description", pl.String)])),
    )
)
shape: (3, 1)
┌───────────────────────────────────┐
│ events                            │
│ ---                               │
│ list[struct[2]]                   │
╞═══════════════════════════════════╡
│ []                                │
│ [{"my_name","my_desc"}]           │
│ [{"my_name","my_desc"}, {"my_nam… │
└───────────────────────────────────┘

Интересно, что в Jupyter последующий запуск следующей ячейки больше не вызывает ошибки.

data = {"events": [0, 1, 2]}
df = pl.DataFrame(data).select(
    events=pl.col("events").map_elements(
        event_table,
        return_dtype=pl.List(pl.Struct({"name": pl.String, "description": pl.String})),
    )
)

Это не работает для меня. Какая версия поляра у вас стоит? Я использую polars==0.20.16.

DJDuque 01.04.2024 19:07

У меня тоже не работает в 0.20.17. Думаю, я сообщу об ошибке в github и посмотрю, что они скажут.

DJDuque 01.04.2024 19:11

@DJDuque Интересно... Я был на polars=0.20.15. Теперь я попробовал версию 0.20.17, она тоже работает. Можете ли вы попробовать также последнюю версию 0.20.17?

Hericks 01.04.2024 19:14

Я открыла тему здесь. Возможно, сопровождающим будет полезно, если вы ответите там, указав вывод pl.show_versions() и то, как вы заставили его работать. Я не знаю.

DJDuque 01.04.2024 19:30

@DJDuque Спасибо, что подняли проблему на GitHub. Я добавил код и версии, которые использовал, в качестве комментария к проблеме. Пожалуйста, свяжитесь с нами, если я могу что-то еще предоставить - рад помочь!

Hericks 01.04.2024 19:36

@DJDuque Как уже упоминалось, мои изменения также включают использование обычного dict вместо Event типа. Конечно, вы можете получить dict от объекта Event. Однако, если это сделает решение неполным, я также буду рад удалить ответ.

Hericks 01.04.2024 22:16
Ответ принят как подходящий

Быстрое решение прямо сейчас

Вот реализация map_batches, которая должна быть хотя бы немного быстрее.

def event_table(col: pl.Series) -> pl.Series:
    return pl.Series(
        [
            [
                Event("name", "description")._asdict() #note ._asdict()
                for _ in range(num)
                ]
            for num in col
            ]
        )

Он использует вложенные списки, которые должны быть немного быстрее, чем добавление к списку в явном цикле for, но это оптимизация Python, а не поляры.

pl.DataFrame(data).select(events=pl.col("events").map_batches(event_table))
shape: (3, 1)
┌───────────────────────────────────┐
│ events                            │
│ ---                               │
│ list[struct[2]]                   │
╞═══════════════════════════════════╡
│ []                                │
│ [{"name","description"}]          │
│ [{"name","description"}, {"name"… │
└───────────────────────────────────┘

На самом деле вам просто нужно использовать _asdict(), а не полагаться на поляры, чтобы сделать вывод, каким должен быть NamedTuple.

Среднесрочное и долгосрочное решение

Проблема здесь, а именно в том, что на определенных путях кортежи и списки обрабатываются одинаково, и поскольку NamedTuple является кортежем, поэтому он возвращается в виде списка.

Этот PR заставляет его проверять метод _asdict и начинает рассматривать его как словарь/структуру.

С помощью этого PR вы можете сделать

class Event(NamedTuple):
    name: str
    description: str

def event_table(num: int) -> list[Event]:
    return [Event("name", "desc") for _ in range(num)]

data = {"events": [0, 1, 2]}
pl.DataFrame(data).select(
    events=pl.col("events").map_elements(
        event_table,
        return_dtype=pl.List(
            pl.Struct({"name": pl.String, "description": pl.String})
        ),
    )
)
shape: (3, 1)
┌───────────────────────────────────┐
│ events                            │
│ ---                               │
│ list[struct[2]]                   │
╞═══════════════════════════════════╡
│ []                                │
│ [{"name","desc"}]                 │
│ [{"name","desc"}, {"name","desc"… │
└───────────────────────────────────┘

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