Учитывая этот фрейм данных:
polars_df = pl.DataFrame({
"name": ["A","B","C"],
"group": ["a","a","b"],
"val1": [1, None, 3],
"val2": [1, 5, None],
"val3": [None, None, 3],
})
Я хочу рассчитать среднее значение и подсчитать количество NA в трех столбцах val* для каждой группы. Итак, результат должен выглядеть так:
pl.DataFrame([
{'group': 'a', 'mean': 2.0, 'percentage_na': 0.5},
{'group': 'b', 'mean': 3.0, 'percentage_na': 0.3333333333333333}
])
В Pandas я смог сделать это с помощью этого (довольно уродливого и неоптимизированного) кода:
df = polars_df.to_pandas()
pd.concat([
df.groupby(["group"]).apply(lambda g: g.filter(like = "val").mean().mean()).rename("mean"),
df.groupby(["group"]).apply(lambda g: g.filter(like = "val").isna().sum().sum() / (g.filter(like = "val").shape[0] * g.filter(like = "val").shape[1])).rename("percentage_na")
], axis=1)
Вы можете использовать melt
и concat
:
In [43]: pl.concat(
...: [
...: polars_df.groupby("group")
...: .agg(pl.exclude("name").mean())
...: .melt("group")
...: .groupby("group")
...: .agg(pl.col("value").mean())
...: .rename({"value": "mean"}),
...: polars_df.groupby("group")
...: .agg(pl.exclude("name").is_null().mean())
...: .melt("group")
...: .groupby("group")
...: .agg(pl.col("value").mean())
...: .drop("group")
...: .rename({"value": "percentage_na"}),
...: ],
...: how = "horizontal",
...: )
Out[43]:
shape: (2, 3)
┌───────┬──────┬───────────────┐
│ group ┆ mean ┆ percentage_na │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 │
╞═══════╪══════╪═══════════════╡
│ a ┆ 2.0 ┆ 0.5 │
│ b ┆ 3.0 ┆ 0.333333 │
└───────┴──────┴───────────────┘
Не самый простой, посмотрим, есть ли более простой способ
Похоже, должен быть более простой способ - вот пара попыток:
df.select("group",
mean =
pl.concat_list(pl.mean(r"^val.+$").over("group"))
.arr.mean(),
percentage_na =
pl.sum(pl.col("^val.+$").null_count().over("group"))
/ pl.sum(pl.col("^val.+$").count().over("group"))
).unique(subset = "group")
(
df
.select(r"^(group|val.+)$")
.with_columns(
mean =
pl.concat_list(pl.mean("^val.+$").over("group"))
.arr.mean())
.melt(id_vars=["group", "mean"])
.groupby("group")
.agg(
pl.first("mean"),
percentage_na =
pl.col("value").null_count() / pl.col("value").count())
)
shape: (2, 3)
┌───────┬──────┬───────────────┐
│ group | mean | percentage_na │
│ --- | --- | --- │
│ str | f64 | f64 │
╞═══════╪══════╪═══════════════╡
│ a | 2.0 | 0.5 │
│ b | 3.0 | 0.333333 │
└───────┴──────┴───────────────┘
@DeanMacGregor Это .mean()
из pl.col("val1").mean(), pl.col("val2").mean(), pl.col("val3").mean()
- так что 1, (1, 5)
-> 1, 3
-> 2
Я считаю, что когда среднее значение вычисляется по столбцу, мы получаем 2. среднее значение val1 для группы a = avg (1, null) = 1 среднее значение val2 для группы a = avg (5,1) = 3 среднее значение val3 для группа a = avg(null, null) = нулевое общее среднее значение для группы a = avg(1,3, null) = 2 это все?
Я откатил свой ответ, когда ответ будет 2.33
all_cols_except_val=[x for x in df.columns if "val" not in x]
df.melt(id_vars=all_cols_except_val) \
.groupby('group') \
.agg(
mean=pl.col('value').mean(),
percent_na=pl.col('value').is_null().sum()/pl.col('value').count()
)
shape: (2, 3)
┌───────┬──────────┬────────────┐
│ group ┆ mean ┆ percent_na │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 │
╞═══════╪══════════╪════════════╡
│ b ┆ 3.0 ┆ 0.333333 │
│ a ┆ 2.333333 ┆ 0.5 │
└───────┴──────────┴────────────┘
Мне нравится использовать расплав в начале, это хорошее чистое решение.
Вот мое предложение, сильно вдохновленное первым ответом @jqurious.
combined_lists = pl.concat_list(r"^val.+$").list().over("group")
df.select(
'group',
mean =
combined_lists.arr.eval(pl.element().arr.explode()).arr.mean(),
percentage_na =
combined_lists.arr.eval(pl.element().arr.explode().null_count()
/ pl.element().arr.explode().count()).flatten()
).unique(subset = 'group')
shape: (2, 3)
┌───────┬──────────┬───────────────┐
│ group ┆ mean ┆ percentage_na │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 │
╞═══════╪══════════╪═══════════════╡
│ a ┆ 2.333333 ┆ 0.5 │
│ b ┆ 3.0 ┆ 0.333333 │
└───────┴──────────┴───────────────┘
Как упомянул @Dean MacGregor, я также понимаю, что среднее значение группы a должно быть 2,33 вместо 2.
Вдохновленный Дином МакГрегором и приняв ваше исправление, что я случайно вычислил среднее значение среднего в моем коде панд, и на самом деле это должно быть просто среднее значение по группе, я наконец придумал это решение:
all_cols_except_val=[x for x in df.columns if "val" not in x]
df.melt(id_vars=all_cols_except_val).groupby("group").agg([
pl.col('value').mean().alias("mean"),
(pl.col('value').is_null().sum()/pl.col('value').count()).alias("percent_na"),
])
Всем спасибо :)
Я откатил свой ответ до того, как это было средством средства.
Как ваша группа имеет среднее значение = 2? Значения 1,1 и 5. Должно быть 7/3=2,33333, не так ли?