Pandas REPLACE equivalent in Python Polars



有没有一种优雅的方法可以在极坐标数据帧中重新编码值。

例如

1->0, 
2->0, 
3->1... 

在熊猫中,它很简单:

df.replace([1,2,3,4,97,98,99],[0,0,1,1,2,2,2])

编辑 2022-02-12

截至polars >=0.16.4,有一个map_dict表达式。

df = pl.DataFrame({
"a": [1, 2, 3, 4, 5]
})
mapper = {
1: 0,
2: 0,
3: 10,
4: 10
}
df.select(
pl.all().map_dict(mapper, default=pl.col("a"))
)
shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 0   │
│ 0   │
│ 10  │
│ 10  │
│ 5   │
└─────┘

编辑前

在极坐标中,您可以构建称为if -> then -> otherwise表达式的柱状if else statetements

所以假设我们有这个DataFrame.

df = pl.DataFrame({
"a": [1, 2, 3, 4, 5]
})

我们希望将这些值替换为以下值:

from_ = [1, 2]
to_ = [99, 12]

我们可以这样写:

df.with_column(
pl.when(pl.col("a") == from_[0])
.then(to_[0])
.when(pl.col("a") == from_[1])
.then(to_[1])
.otherwise(pl.col("a")).alias("a")
)
shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 99  │
├╌╌╌╌╌┤
│ 12  │
├╌╌╌╌╌┤
│ 3   │
├╌╌╌╌╌┤
│ 4   │
├╌╌╌╌╌┤
│ 5   │
└─────┘

不要重复自己

现在,编写得非常快变得非常乏味,因此我们可以编写一个生成这些表达式以供使用的函数,我们是程序员不是吗!

因此,要替换为您建议的值,您可以执行以下操作:

from_ = [1,2,3,4,97,98,99]
to_ = [0,0,1,1,2,2,2]

def replace(column, from_, to_):
# initiate the expression with `pl.when`
branch =  pl.when(pl.col(column) == from_[0]).then(to_[0])

# for every value add a `when.then`
for (from_value, to_value) in zip(from_, to_):
branch = branch.when(pl.col(column) == from_value).then(to_value)
# finish with an `otherwise`
return branch.otherwise(pl.col(column)).alias(column)


df.with_column(replace("a", from_, to_))

哪些输出:

shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 0   │
├╌╌╌╌╌┤
│ 0   │
├╌╌╌╌╌┤
│ 1   │
├╌╌╌╌╌┤
│ 1   │
├╌╌╌╌╌┤
│ 5   │
└─────┘

以防万一您也喜欢 pandas 文档字符串并希望将其作为 utils 函数放置在存储库中的某个位置

def replace(column: str, mapping: dict) -> pl.internals.expr.Expr:
"""
Create a polars expression that replaces a columns values.
Parameters
----------
column : str
Column name on which values should be replaced.
mapping : dict
Can be used to specify different replacement values for different existing values. For example,
``{'a': 'b', 'y': 'z'}`` replaces the value ‘a’ with ‘b’ and ‘y’ with ‘z’. Values not mentioned in ``mapping``
will stay the same.
Returns
-------
pl.internals.expr.Expr
Expression that contains instructions to replace values in ``column`` according to ``mapping``.
Raises
------
Exception
* If ``mapping`` is empty.
TypeError
* If ``column`` is not ``str``.
* If ``mapping`` is not ``dict``.
polars.exceptions.PanicException
* When ``mapping`` has keys or values that are not mappable to arrows format. Only catchable via BaseException.
See also https://pola-rs.github.io/polars-book/user-guide/datatypes.html.
Examples
--------
>>> import polars as pl
>>> df = pl.DataFrame({'fruit':['banana', 'apple', 'pie']})
>>> df
shape: (3, 1)
┌────────┐
│ fruit  │
│ ---    │
│ str    │
╞════════╡
│ banana │
├╌╌╌╌╌╌╌╌┤
│ apple  │
├╌╌╌╌╌╌╌╌┤
│ apple  │
└────────┘
>>> df.with_column(replace(column='fruit', mapping={'apple': 'pomegranate'}))
shape: (3, 1)
┌─────────────┐
│ fruit       │
│ ---         │
│ str         │
╞═════════════╡
│ banana      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pomegranate │
├╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pomegranate │
└─────────────┘
"""
if not mapping:
raise Exception("Mapping can't be empty")
elif not isinstance(mapping, dict):
TypeError(f"mapping must be of type dict, but is type: {type(mapping)}")
if not isinstance(column, str):
raise TypeError(f"column must be of type str, but is type: {type(column)}")
branch = pl.when(pl.col(column) == list(mapping.keys())[0]).then(
list(mapping.values())[0]
)
for from_value, to_value in mapping.items():
branch = branch.when(pl.col(column) == from_value).then(to_value)
return branch.otherwise(pl.col(column)).alias(column)

这也可以通过以下方式完成

  1. 将映射字典转换为polars.DataFrame
  2. 将原始数据LEFT JOIN到映射器DataFrame
  3. 用原始值填充任何缺失值(映射器字典未考虑这些值)
    • .map_dict()使用default=...执行此操作
  4. 删除原始列

这是显示此内容的代码

数据

df = pl.DataFrame({"a": [1, 2, 3, 4, 5]})
print(df)
shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
│ 2   │
│ 3   │
│ 4   │
│ 5   │
└─────┘

定义映射器字典

mapper = {1: 0, 2: 0, 3: 10, 4: 10}

创建映射器DataFrame

df_mapper = pl.DataFrame([{"a": k, "values": v} for k, v in mapper.items()])
print(df_mapper)
shape: (4, 2)
┌─────┬────────┐
│ a   ┆ values │
│ --- ┆ ---    │
│ i64 ┆ i64    │
╞═════╪════════╡
│ 1   ┆ 0      │
│ 2   ┆ 0      │
│ 3   ┆ 10     │
│ 4   ┆ 10     │
└─────┴────────┘

使用LEFT JOIN.fill_null()映射值,然后删除原始列

df = (
df
# LEFT JOIN
.join(df_mapper, on=["a"], how="left")
# fill missing values in mapped column with values from original column
.with_columns([pl.col("values").fill_null(pl.col("a"))])
# drop original column and replace with mapped column
.drop(["a"])
.rename({"values": "a"})
)
print(df)
shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 0   │
│ 0   │
│ 10  │
│ 10  │
│ 5   │
└─────┘

您也可以将applydict一起使用,只要为每个from_选项指定详尽的映射:

df = pl.DataFrame({"a": [1, 2, 3, 4, 5]})
from_ = [1, 2, 3, 4, 5]
to_ = [99, 12, 4, 18, 64]
my_map = dict(zip(from_, to_))
df.select(pl.col("a").apply(lambda x: my_map[x]))

其中输出:

shape: (5, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 99  │
├╌╌╌╌╌┤
│ 12  │
├╌╌╌╌╌┤
│ 4   │
├╌╌╌╌╌┤
│ 18  │
├╌╌╌╌╌┤
│ 64  │
└─────┘

它会比 ritchie46 的答案慢,但它要简单得多。

不能在注释中使用代码片段,所以我会发布这个轻微的概括作为答案。

如果映射中缺少要映射的值,则接受默认值(如果提供),否则它将充当映射是标识映射。

import polars as pl
def apply_map(
column: str, mapping: dict, default = None
) -> pl.Expr:
branch = pl
for key, value in mapping.items():
branch = branch.when(pl.col(column) == key).then(value)
default = pl.lit(default) if default is not None else pl.col(column)
return branch.otherwise(default).alias(column)

最新更新