Polars struct.field(list[str]) возвращает один столбец при работе со списком[Struct]

Некоторые из моих столбцов в моем кадре данных Polars имеют тип dtype pl.List(pl.Struct). Я пытаюсь заменить эти столбцы, чтобы получить несколько столбцов, представляющих собой списки скалярных значений.

Вот пример столбца, который я пытаюсь изменить:

import polars as pl

df = pl.DataFrame({
    "column_0": [
        [{"field_1": "a", "field_2": 1}, {"field_1": "b", "field_2":2}],
        [{"field_1": "c", "field_2":3}]
    ]
})

col_name = "column_0"
df.select(
    pl.col(col_name).list.eval(
        pl.element().struct.field("*")
    )
)

Я ожидал, что получу что-то вроде этого:

shape: (2, 2)
┌────────────┬───────────┐
│ field_1    ┆ field_2   │
│ ---        ┆ ---       │
│ list[str]  ┆ list[i64] │
╞════════════╪═══════════╡
│ ["a", "b"] ┆ [1, 2]    │
│ ["c"]      ┆ [3]       │
└────────────┴───────────┘

Вместо этого я получаю только последнее поле (в данном случае «field_2»):

shape: (2, 1)
┌───────────┐
│ column_0  │
│ ---       │
│ list[i64] │
╞═══════════╡
│ [1, 2]    │
│ [3]       │
└───────────┘

Спасибо за добавление данных, но наиболее полезной формой будет пример исполняемого кода, который настраивает фрейм данных без дополнительной настройки.

Anerdw 16.08.2024 06:39

Один .list.eval() не создаст несколько новых столбцов, но такой результат выглядит странно. (Возможно, ошибка?) Знаете ли вы количество полей в структуре? Или вы пытаетесь сделать это «динамически» для любого количества полей?

jqurious 16.08.2024 10:05

Я пытаюсь сделать это динамически. Количество полей в структуре зависит от столбца.

Pain 16.08.2024 22:15
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
77
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

struct.field("field_name") извлекает определенные поля, поэтому вы можете использовать field_name вместо * для достижения результата.

result = df.select([
    pl.col("column_0").list.eval(pl.element().struct.field("field_1")).alias("field_1"),
    pl.col("column_0").list.eval(pl.element().struct.field("field_2")).alias("field_2"),
])

Если у вас больше полей, таких как field_2, вы можете использовать обобщенную функцию:

def split_struct_column(df: pl.DataFrame, col_name: str) -> pl.DataFrame:
    first_struct = df[col_name].to_list()[0][0]
    field_names = list(first_struct.keys())

    exprs = [
        pl.col(col_name).list.eval(pl.element().struct.field(field)).alias(field)
        for field in field_names
    ]

    return df.select(exprs)


result = split_struct_column(df, "column_0")

Я получаю вывод ниже,

┌────────────┬───────────┐
│ field_1    ┆ field_2   │
│ ---        ┆ ---       │
│ list[str]  ┆ list[i64] │
╞════════════╪═══════════╡
│ ["a", "b"] ┆ [1, 2]    │
│ ["c"]      ┆ [3]       │
└────────────┴───────────┘

Вы можете получить доступ к именам полей через атрибут pl.DataFrame.schema и создать подходящие выражения для создания столбцов списка следующим образом.

df.with_columns(
    pl.col("column_0").list.eval(
        pl.element().struct.field(field.name)
    ).alias(field.name)
    for field in df.schema["column_0"].inner.fields
)
shape: (2, 3)
┌────────────────────┬────────────┬───────────┐
│ column_0           ┆ field_1    ┆ field_2   │
│ ---                ┆ ---        ┆ ---       │
│ list[struct[2]]    ┆ list[str]  ┆ list[i64] │
╞════════════════════╪════════════╪═══════════╡
│ [{"a",1}, {"b",2}] ┆ ["a", "b"] ┆ [1, 2]    │
│ [{"c",3}]          ┆ ["c"]      ┆ [3]       │
└────────────────────┴────────────┴───────────┘

Ваше решение работает и очень полезно, но можно ли не использовать цикл for? Кроме того, где вы нашли атрибуты «.inner.fields» и «.name»? Я не могу найти их в документации Polars.

Pain 16.08.2024 22:34

@Pain Я также пытался избежать цикла, но не нашел способа обойти его. По крайней мере, небольшое утешение можно найти в том, что вы также можете получить доступ к схеме pl.LazyFrames. Тем более, что решение все еще актуально. Атрибуты .inner.fields и .name типов данных pl.Struct и pl.Field я нашел, вручную проверив атрибуты объекта (используя .__dir__).

Hericks 17.08.2024 12:23

@Pain Я только что заметил, что ответ jqurious ниже полностью избегает итераций.

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

Вы можете распаковать списки/структуры с помощью .explode() + .unnest() и снова сгруппировать строки.

(df.with_row_index()
   .explode("column_0")
   .unnest("column_0")
   .group_by("index", maintain_order=True)
   .all()
)
shape: (2, 3)
┌───────┬────────────┬───────────┐
│ index ┆ field_1    ┆ field_2   │
│ ---   ┆ ---        ┆ ---       │
│ u32   ┆ list[str]  ┆ list[i64] │
╞═══════╪════════════╪═══════════╡
│ 0     ┆ ["a", "b"] ┆ [1, 2]    │
│ 1     ┆ ["c"]      ┆ [3]       │
└───────┴────────────┴───────────┘

Очень хорошее решение, позволяющее избежать каких-либо итераций.

Hericks 17.08.2024 14:21

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