У меня есть следующий код.
import polars as pl
class Summary:
def __init__(self, value: float, origin: str):
self.value = value
self.origin = origin
def __repr__(self) -> str:
return f'Summary({self.value},{self.origin})'
def __mul__(self, x: float | int) -> 'Summary':
return Summary(self.value * x, self.origin)
def __rmul__(self, x: float | int) -> 'Summary':
return self * x
mapping = {
'CASH': Summary( 1, 'E'),
'ITEM': Summary(-9, 'A'),
'CHECK': Summary(46, 'A'),
}
df = pl.DataFrame({'quantity': [7, 4, 10], 'type': mapping.keys(), 'summary': mapping.values()})
Кадр данных df
выглядит следующим образом.
shape: (3, 3)
┌──────────┬───────┬───────────────┐
│ quantity ┆ type ┆ summary │
│ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ object │
╞══════════╪═══════╪═══════════════╡
│ 7 ┆ CASH ┆ Summary(1,E) │
│ 4 ┆ ITEM ┆ Summary(-9,A) │
│ 10 ┆ CHECK ┆ Summary(46,A) │
└──────────┴───────┴───────────────┘
В частности, столбец summary
содержит объект класса Summary
, который поддерживает умножение. Теперь я хотел бы умножить этот столбец на столбец quantity
.
Однако наивный подход приводит к ошибке.
df.with_columns(pl.col('quantity').mul(pl.col('summary')).alias('qty_summary'))
SchemaError: failed to determine supertype of i64 and object
Есть ли способ умножить эти столбцы?
Причина, по которой я пытался использовать объект Summary
(который в моем реальном коде намного больше), заключается в том, что у меня есть некоторые поля, которые нужно умножать, например value
, а некоторые не любят origin
. Поэтому в идеале я сохраняю объект Summary
, чтобы мне не приходилось вручную выбирать подмножество столбцов, которые необходимо умножить.
Я думаю, что это можно сделать с map_elements()
, но я думаю, что применение ООП к кадрам данных Polars - это немного неправильное направление - вы не сможете получить выгоду от векторизации
@RomanPekar Я думаю, что применить map_elements()
из коробки непросто. Вам нужно будет применить его к столбцу структуры (что позволит вам получить доступ к мультипликативному коэффициенту), но все поля структуры должны иметь действительный тип поляры.
Понятно, возможно, я мог бы использовать 2 столбца struct
: один для полей, которые можно умножать, а другой для полей, к которым умножение не применяется?
Вам даже не нужен столбец структуры. Просто сохраните данные в обычных столбцах и получите список имен столбцов, к которым можно применить умножение.
В комментариях вы упомянули, что хотели бы иметь возможность умножать определенные поля столбца Summary
на другой столбец.
Возможно, вы могли бы сохранить свои данные в виде структуры:
df = pl.DataFrame({
"type": ["CASH","ITEM","CHECK"],
"summary": [{'value': 1.0, 'origin': 'E'}, {'value': -9.0, 'origin': 'A'}, {'value': 46.0, 'origin': 'A'}],
"quantity": [7,4,10]
})
┌───────┬────────────┬──────────┐
│ type ┆ summary ┆ quantity │
│ --- ┆ --- ┆ --- │
│ str ┆ struct[2] ┆ i64 │
╞═══════╪════════════╪══════════╡
│ CASH ┆ {1.0,"E"} ┆ 7 │
│ ITEM ┆ {-9.0,"A"} ┆ 4 │
│ CHECK ┆ {46.0,"A"} ┆ 10 │
└───────┴────────────┴──────────┘
И просто отдельно определите список полей, на которые вы хотите повлиять. Вы можете использовать with_fields() для умножения некоторых полей структуры:
fields_to_multiply = ["value"]
df.with_columns(
pl.col.summary.struct.with_fields(
pl.field(fields_to_multiply) * pl.col.quantity
)
)
┌───────┬─────────────┬──────────┐
│ type ┆ summary ┆ quantity │
│ --- ┆ --- ┆ --- │
│ str ┆ struct[2] ┆ i64 │
╞═══════╪═════════════╪══════════╡
│ CASH ┆ {7.0,"E"} ┆ 7 │
│ ITEM ┆ {-36.0,"A"} ┆ 4 │
│ CHECK ┆ {460.0,"A"} ┆ 10 │
└───────┴─────────────┴──────────┘
Помните, что Polars спроектирован так, что вычисления выполняются на Rust, а не на Python, где они примерно в 1000 раз быстрее. Если у вас есть операции Python, которые вы хотите выполнить, вы в первую очередь теряете большую часть преимуществ использования Polars.
Но, к счастью, у Polars есть очень приятная функция, которая здесь актуальна, а именно «родная» обработка классов данных .
import polars as pl
from dataclasses import dataclass
@dataclass
class Summary:
value: float
origin: str
def __mul__(self, x: float | int) -> "Summary":
return Summary(self.value * x, self.origin)
def __rmul__(self, x: float | int) -> "Summary":
return self * x
mapping = {
"CASH": Summary(1, "E"),
"ITEM": Summary(-9, "A"),
"CHECK": Summary(46, "A"),
}
df = pl.DataFrame(
{
"quantity": [7, 4, 10],
"type": mapping.keys(),
"summary": mapping.values(),
}
)
df
Поскольку Summary
— это dataclass
, вам 1. не нужны __init__
и __repr__
(они приходят бесплатно) и 2. не нужно делать для Polars ничего особенного, чтобы их структурировать.
shape: (3, 3)
┌──────────┬───────┬────────────┐
│ quantity ┆ type ┆ summary │
│ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ struct[2] │
╞══════════╪═══════╪════════════╡
│ 7 ┆ CASH ┆ {1.0,"E"} │
│ 4 ┆ ITEM ┆ {-9.0,"A"} │
│ 10 ┆ CHECK ┆ {46.0,"A"} │
└──────────┴───────┴────────────┘
Теперь вы можете просто выполнять обычные операции структуры Polars:
df.with_columns(
qty_summary=pl.struct(
pl.col("summary").struct.field("value") * pl.col("quantity"),
pl.col("summary").struct.field("origin"),
)
)
Я не знал о готовой dataclass
поддержке. Это потрясающе!
Обычно вам следует избегать использования столбцов типа
object
в вашем фрейме данных, поскольку нельзя применить встроенную операцию поляры. Будет ли возможность хранить объектыSummary
какstruct
с полямиvalue
иorigin
?