如何过滤polars数据框中与另一数据框部分匹配的所有行?

1 投票
2 回答
129 浏览
提问于 2025-04-14 18:34

我想删除一个数据表中所有与另一个过滤数据表中一行或多行匹配的行。

是的,我知道如何通过一个正则表达式来过滤数据,我也知道在某一列完全匹配时,如何利用连接来处理。这并不是直接匹配,除非逐行遍历过滤数据表。

在sql中,这个问题相对简单,可以在服务器上批量应用这个过滤,而不需要用客户端代码循环处理:

给定:

data.csv
filename,col2
keep.txt,bar
skip.txt,foo
keep2.txt,zoom
skip3.txt,custom1
discard.txt,custom2
file3.txt,custom3
discard2.txt,custom4
file4.txt,custom5
filter.csv:
skip
discard
skip

这是使用postgres的sql。关键点是,它的扩展性非常好。

withsql.sql
\c test;

DROP TABLE IF EXISTS data;
DROP TABLE IF EXISTS filter;

CREATE TABLE data (
    filename CHARACTER(50),
    col2 CHARACTER(10),
    skip BOOLEAN DEFAULT FALSE
);

\copy data (filename,col2) FROM './data.csv' WITH (FORMAT CSV);

CREATE TABLE filter (
    skip VARCHAR(20)
);

\copy filter FROM './filter.csv' WITH (FORMAT CSV);

update filter set skip = skip || '%';

update data set skip = TRUE where exists (select 1 from filter s where filename like s.skip);
delete from data where skip = TRUE;

select * from data;

psql -f withsql.sql

这将输出:

You are now connected to database "test" as user "djuser".
...
UPDATE 4
DELETE 4
                      filename                      |    col2    | skip 
----------------------------------------------------+------------+------
 filename                                           | col2       | f
 keep.txt                                           | bar        | f
 keep2.txt                                          | zoom       | f
 file3.txt                                          | custom3    | f
 file4.txt                                          | custom5    | f
(5 rows)

现在,我可以用polars来做,但我唯一能想到的就是在filter.csv上使用循环:

withpolars.py
import polars as pl

df_data = pl.read_csv("data.csv")
df_filter = pl.read_csv("filter.csv")

for row in df_filter.iter_rows():
    df_data = df_data.filter(~pl.col('filename').str.contains(row[0]))

print("data after:\n", df_data)

输出是正确的,但我想知道有没有办法不使用循环来实现?而且……我很好奇这些批量sql的方法如何映射到数据表上。

data after:
 shape: (4, 2)
┌───────────┬─────────┐
│ filename  ┆ col2    │
│ ---       ┆ ---     │
│ str       ┆ str     │
╞═══════════╪═════════╡
│ keep.txt  ┆ bar     │
│ keep2.txt ┆ zoom    │
│ file3.txt ┆ custom3 │
│ file4.txt ┆ custom5 │
└───────────┴─────────┘

2 个回答

2

你可以直接从过滤后的数据框(df)手动构建正则表达式(regex):

data_df = pl.read_csv(data_csv)
filter_df = pl.read_csv(filter_csv, has_header=False, new_columns=["filter"])


filter_re = "|".join(re.escape(s) for s in filter_df.get_column("filter"))

data_df.filter(~pl.col("filename").str.contains(filter_re))
4

这里有一个专门用来匹配部分字符串的函数:

df_data.filter(
    pl.col("filename")
      .str.contains_any(df_filter.get_column("skip"))
      .not_()
)
shape: (4, 2)
┌───────────┬─────────┐
│ filename  ┆ col2    │
│ ---       ┆ ---     │
│ str       ┆ str     │
╞═══════════╪═════════╡
│ keep.txt  ┆ bar     │
│ keep2.txt ┆ zoom    │
│ file3.txt ┆ custom3 │
│ file4.txt ┆ custom5 │
└───────────┴─────────┘

至于你现在的方法,我觉得问题出在每次都调用 .filter

其实,你应该构建一个表达式,然后只调用一次。这样可以让 Polars 更有效地并行处理工作。

df_data.filter(
   pl.all_horizontal(
      pl.col("filename").str.contains(row).not_()
      for row in df_filter.get_column("skip")
   )
)

撰写回答