У меня есть фрейм данных Polars, который содержит некоторый идентификатор, действия и значения:
Пример фрейма данных:
data = {
"ID" : [1, 1, 2,2,3,3],
"Action" : ["A", "A", "B", "B", "A", "A"],
"Where" : ["Office", "Home", "Home", "Office", "Home", "Home"],
"Value" : [1, 2, 3, 4, 5, 6]
}
df = pl.DataFrame(data)
Я хочу выбрать для каждого идентификатора и действия наибольшее значение, чтобы знать, где он предпочитает выполнять действие.
Я использую следующий подход:
(
df
.select(
pl.col("ID"),
pl.col("Action"),
pl.col("Where"),
TOP = pl.col("Value").max().over(["ID", "Action"]))
)
После этого я отсортировал значения и сохранил уникальные значения (первые), чтобы сохранить нужную информацию, однако введенные данные неверны:
(
df
.select(
pl.col("ID"),
pl.col("Action"),
pl.col("Where"),
TOP = pl.col("Value").max().over(["ID", "Action"]))
.sort(
pl.col("*"), descending =True
)
.unique(
subset = ["ID", "Action"],
maintain_order = True,
keep = "first"
)
)
Текущий выход:
shape: (3, 4)
┌─────┬────────┬────────┬─────┐
│ ID ┆ Action ┆ Where ┆ TOP │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 │
╞═════╪════════╪════════╪═════╡
│ 3 ┆ A ┆ Home ┆ 6 │
│ 2 ┆ B ┆ Office ┆ 4 │
│ 1 ┆ A ┆ Office ┆ 2 │
└─────┴────────┴────────┴─────┘
Ожидаемый результат:
shape: (3, 4)
┌─────┬────────┬────────┬─────┐
│ ID ┆ Action ┆ Where ┆ TOP │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 │
╞═════╪════════╪════════╪═════╡
│ 3 ┆ A ┆ Home ┆ 6 │
│ 2 ┆ B ┆ Office ┆ 4 │
│ 1 ┆ A ┆ Home ┆ 2 │
└─────┴────────┴────────┴─────┘
Кроме того, я думаю, что этот подход не является оптимальным способом
@jqurious Это отличный момент! В моем случае это не является серьезной проблемой, поскольку вероятность их равенства невелика, а точность не имеет решающего значения для моей задачи.
Невероятное и уникальное можно объединить в group_by
(df.group_by("ID", "Action")
.agg(
pl.all().get(pl.col("Value").arg_max())
)
)
shape: (3, 4)
┌─────┬────────┬────────┬───────┐
│ ID ┆ Action ┆ Where ┆ Value │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 │
╞═════╪════════╪════════╪═══════╡
│ 1 ┆ A ┆ Home ┆ 2 │
│ 2 ┆ B ┆ Office ┆ 4 │
│ 3 ┆ A ┆ Home ┆ 6 │
└─────┴────────┴────────┴───────┘
Чтобы легко это сделать, вы можете использовать следующую встроенную функцию в полярах.
with_columns: это предоставляет вам информацию на основе col (значения) для взятых как ID
, так и Action
.
сортировка: сортировка просто по убыванию
уникальный: позволяет удалить все дубликаты.
import polars as pl
data = {
"ID": [1, 1, 2, 2, 3, 3],
"Action": ["A", "A", "B", "B", "A", "A"],
"Where": ["Office", "Home", "Home", "Office", "Home", "Home"],
"Value": [1, 2, 3, 4, 5, 6]
}
df = pl.DataFrame(data)
result = (
df
.with_columns(
TOP=pl.col("Value").max().over(["ID", "Action"])
)
.sort(by = "TOP", descending=True)
.unique(subset=["ID", "Action"], maintain_order=True, keep = "first")
)
print(result)
shape: (3, 5)
┌─────┬────────┬────────┬───────┬─────┐
│ ID ┆ Action ┆ Where ┆ Value ┆ TOP │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 ┆ i64 │
╞═════╪════════╪════════╪═══════╪═════╡
│ 3 ┆ A ┆ Home ┆ 5 ┆ 6 │
│ 2 ┆ B ┆ Home ┆ 3 ┆ 4 │
│ 1 ┆ A ┆ Office ┆ 1 ┆ 2 │
└─────┴────────┴────────┴───────┴─────┘
В качестве альтернативы вы можете использовать groupby
и filter
, что исключает использование уникальных символов, даже если они звучат одинаково.
result = (
df.group_by(["ID", "Action"]).agg(
[
pl.col("Value").max().alias("TOP"),
pl.col("Where")
.filter(pl.col("Value") == pl.col("Value").max())
.first()
.alias("Where"),
]
)
.sort("TOP", descending=True)
)
еще один результат:
shape: (3, 4)
┌─────┬────────┬─────┬────────┐
│ ID ┆ Action ┆ TOP ┆ Where │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ i64 ┆ str │
╞═════╪════════╪═════╪════════╡
│ 3 ┆ A ┆ 6 ┆ Home │
│ 2 ┆ B ┆ 4 ┆ Office │
│ 1 ┆ A ┆ 2 ┆ Home │
└─────┴────────┴─────┴────────┘
Другой способ сделать это — поставить over
в filter
, а затем rename
.
(
df
.filter(pl.col("Value")==pl.col("Value").max().over('ID'))
.rename({"Value":"TOP"})
)
shape: (3, 4)
┌─────┬────────┬────────┬─────┐
│ ID ┆ Action ┆ Where ┆ TOP │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 │
╞═════╪════════╪════════╪═════╡
│ 1 ┆ A ┆ Home ┆ 2 │
│ 2 ┆ B ┆ Office ┆ 4 │
│ 3 ┆ A ┆ Home ┆ 6 │
└─────┴────────┴────────┴─────┘
Другой подход — использовать pl.Expr.top_k_by.
В контексте select
(используя pl.Expr.over).
(
df
.select(
pl.all().top_k_by("Value", k=1).over("ID", "Action", mapping_strategy = "explode")
)
)
В контексте group_by
/agg
(нужен дополнительный мн.выражение.первый).
(
df
.group_by("ID", "Action")
.agg(
pl.all().top_k_by("Value", k=1).first()
)
)
Выход.
shape: (3, 4)
┌─────┬────────┬────────┬───────┐
│ ID ┆ Action ┆ Where ┆ Value │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ i64 │
╞═════╪════════╪════════╪═══════╡
│ 1 ┆ A ┆ Home ┆ 2 │
│ 2 ┆ B ┆ Office ┆ 4 │
│ 3 ┆ A ┆ Home ┆ 6 │
└─────┴────────┴────────┴───────┘
Что произойдет, если на одном и том же максимуме будет ничья
Value
? Текущий подход игнорирует ничьи и выбирает один максимум, но ответ может измениться в зависимости от того, как вы хотите с этим справиться.