获取极坐标数据框中所有重复行的最佳方法

Optimal way to get all duplicated rows in a polars dataframe

我想从 polars 数据框中过滤所有重复的行。我尝试过的:

df = pl.DataFrame([['1', '1', '1', '1'], ['7', '7', '2', '7'], ['3', '9', '3', '9']])
df
shape: (4, 3)
┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ str      ┆ str      ┆ str      │
╞══════════╪══════════╪══════════╡
│ 1        ┆ 7        ┆ 3        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 2        ┆ 3        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
└──────────┴──────────┴──────────┘

df.filter(pl.all().is_duplicated())
shape: (3, 3)
┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ str      ┆ str      ┆ str      │
╞══════════╪══════════╪══════════╡
│ 1        ┆ 7        ┆ 3        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
└──────────┴──────────┴──────────┘

这会选择第一行,因为它似乎是逐列进行的,并且 returns 每行的所有列在各自的列中都有对应的重复项 - 不是预期的结果。

布尔索引有效:

df[df.is_duplicated(), :]
shape: (2, 3)
┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ str      ┆ str      ┆ str      │
╞══════════╪══════════╪══════════╡
│ 1        ┆ 7        ┆ 9        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
└──────────┴──────────┴──────────┘

但这让我很疑惑

我不确定这是否是最优的,但您可以连接所有行并检查重复项,即:

import polars as pl

df = pl.DataFrame([['1', '1', '1', '1'], ['7', '7', '2', '7'], ['3', '9', '3', '9']], columns=["a", "b", "c"])

df.filter(pl.concat_str(["a", "b", "c"]).is_duplicated())

shape: (2, 3)
┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ str │
╞═════╪═════╪═════╡
│ 1   ┆ 7   ┆ 9   │
├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌┤
│ 1   ┆ 7   ┆ 9   │
└─────┴─────┴─────┘

一般来说,is_duplicated 方法的效果可能最好。让我们看一下实现此目的的一些替代方法。我们将进行一些(非常)non-rigorous 基准测试 - 只是为了看看哪些表现相当好。

一些备选方案

一种替代方法是在所有列上使用 over(窗口化)表达式的 filter 语句。窗口表达式的一个警告 - 它们很方便,但可能代价高昂 performance-wise.

df.filter(pl.count("column_1").over(df.columns) > 1)
shape: (2, 3)
┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ str      ┆ str      ┆ str      │
╞══════════╪══════════╪══════════╡
│ 1        ┆ 7        ┆ 9        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
└──────────┴──────────┴──────────┘

另一种选择是 groupby,然后是 join。基本上,我们将计算列组合出现的次数。我在这里使用 semi 连接,只是因为我不想在最终结果中包含 count 列。

df.join(
    df=df.groupby(df.columns)
    .agg(pl.count().alias("count"))
    .filter(pl.col("count") > 1),
    on=df.columns,
    how="semi",
)
shape: (2, 3)
┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ str      ┆ str      ┆ str      │
╞══════════╪══════════╪══════════╡
│ 1        ┆ 7        ┆ 9        │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 1        ┆ 7        ┆ 9        │
└──────────┴──────────┴──────────┘

一些(非常)non-rigorous 基准测试

查看哪些备选方案表现相当不错的一种方法是对可能类似于您将使用的数据集的测试数据集的性能进行计时。由于缺乏更好的东西,我会坚持使用看起来接近您问题中的数据集的东西。

nbr_rows 设置为可以挑战您机器的东西。 (我的机器是32核的系统,所以我会选择比较高的行数。)

import numpy as np
import string

nbr_rows = 100_000_000
df = pl.DataFrame(
    {
        "col1": np.random.choice(1_000, nbr_rows,),
        "col2": np.random.choice(1_000, nbr_rows,),
        "col3": np.random.choice(list(string.ascii_letters), nbr_rows,),
        "col4": np.random.choice(1_000, nbr_rows,),
    }
)
print(df)
shape: (100000000, 4)
┌──────┬──────┬──────┬──────┐
│ col1 ┆ col2 ┆ col3 ┆ col4 │
│ ---  ┆ ---  ┆ ---  ┆ ---  │
│ i64  ┆ i64  ┆ str  ┆ i64  │
╞══════╪══════╪══════╪══════╡
│ 955  ┆ 186  ┆ j    ┆ 851  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 530  ┆ 199  ┆ d    ┆ 376  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 109  ┆ 609  ┆ G    ┆ 115  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 886  ┆ 487  ┆ d    ┆ 479  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ ...  ┆ ...  ┆ ...  ┆ ...  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 837  ┆ 406  ┆ Y    ┆ 60   │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 467  ┆ 769  ┆ P    ┆ 344  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 548  ┆ 372  ┆ F    ┆ 410  │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌┤
│ 379  ┆ 578  ┆ t    ┆ 287  │
└──────┴──────┴──────┴──────┘

现在让我们对一些备选方案进行基准测试。由于这些可能类似于也可能不类似于您的数据集(或您的计算平台),因此我不会多次 运行 基准测试。出于我们的目的,我们只是试图淘汰可能表现很差的替代方案。

选择:is_duplicated

import time
start = time.perf_counter()
df[df.is_duplicated(),:]
end = time.perf_counter()
print(end - start)
>>> print(end - start)
7.834882180000932

由于 is_duplicated 方法是由 Polars API 提供的,我们可以有理由相信它会表现得很好。事实上,这应该是我们比较其他选择的标准。

备选方案:filter 使用 over(窗口化)表达式

start = time.perf_counter()
df.filter(pl.count("col1").over(df.columns) > 1)
end = time.perf_counter()
print(end - start)
>>> print(end - start)
18.136289041000055

正如预期的那样,over(窗口化)表达式相当昂贵。

备选方案:groupby 后跟 join

start = time.perf_counter()
df.join(
    df=df.groupby(df.columns)
    .agg(pl.count().alias("count"))
    .filter(pl.col("count") > 1),
    on=df.columns,
    how="semi",
)
end = time.perf_counter()
print(end - start)
>>> print(end - start)
9.419006452999383

稍微好一些...但不如使用 Polars API.

提供的 is_duplicated 方法好

选择:concat_str

我们也来看看另一个答案中建议的替代方案。公平地说,@FBruzzesi 确实说过“我不确定这无论如何都是最优的”。但让我们看看它的表现如何。

start = time.perf_counter()
df.filter(pl.concat_str(df.columns, sep='|').is_duplicated())
end = time.perf_counter()
print(end - start)
>>> print(end - start)
37.238660977998734

编辑

其他选择:filteris_duplicated

我们还可以使用 filteris_duplicated。由于当过滤器为 运行 时 df.is_duplicated() 不是 DataFrame 中的列,我们需要将其包装在 polars.lit 表达式中。

start = time.perf_counter()
df.filter(pl.lit(df.is_duplicated()))
end = time.perf_counter()
print(end - start)
>>> print(end - start)
8.115436136999051

这与使用 is_duplicated 和布尔索引一样好。

这有帮助吗?如果不出意外,这显示了一些使用 Polars API.

的不同方法