快速应用一个函数到极地数据框架



将函数应用于极地数据框架-pl.DataFramepl.internals.lazy_frame.LazyFrame的最快方法是什么?这个问题是把Apply Function应用到一个polar - dataframe的所有列

我试图在python标准库中使用hashlib连接所有列并散列值。我使用的函数如下:

import hashlib
def hash_row(row):
os.environ['PYTHONHASHSEED'] = "0"
row = str(row).encode('utf-8')
return hashlib.sha256(row).hexdigest()

然而,考虑到这个函数需要一个字符串作为输入,意味着这个函数需要应用到pl.Series中的每个单元格。处理少量数据应该没问题,但是当我们有接近100万行的时候,这就变得非常成问题了。这个线程的问题是,我们如何在整个极地系列中以最高效的方式应用这样的函数?

熊猫

提供了一些创建新列的选项,其中一些比其他的性能更好。

df['new_col'] = df['some_col'] * 100 # vectorized calls

另一个选择是为逐行操作创建自定义函数。

def apply_func(row):
return row['some_col'] + row['another_col']
df['new_col'] = df.apply(lambda row: apply_func(row), axis=1) # using apply operations

根据我的经验,最快的方法是创建numpy矢量化解决方案。

import numpy as np
def np_func(some_col, another_col):
return some_col + another_col
vec_func = np.vectorize(np_func)
df['new_col'] = vec_func(df['some_col'].values, df['another_col'].values)

高偏振星

北极星的最佳解决方案是什么?

让我们从这些不同类型的数据开始:

import polars as pl
df = pl.DataFrame(
{
"col_int": [1, 2, 3, 4],
"col_float": [10.0, 20, 30, 40],
"col_bool": [True, False, True, False],
"col_str": pl.repeat("2020-01-01", 4, eager=True),
}
).with_column(pl.col("col_str").str.strptime(pl.Date).alias("col_date"))
df
shape: (4, 5)
┌─────────┬───────────┬──────────┬────────────┬────────────┐
│ col_int ┆ col_float ┆ col_bool ┆ col_str    ┆ col_date   │
│ ---     ┆ ---       ┆ ---      ┆ ---        ┆ ---        │
│ i64     ┆ f64       ┆ bool     ┆ str        ┆ date       │
╞═════════╪═══════════╪══════════╪════════════╪════════════╡
│ 1       ┆ 10.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2       ┆ 20.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 3       ┆ 30.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 4       ┆ 40.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 │
└─────────┴───────────┴──────────┴────────────┴────────────┘

高偏振星:DataFrame.hash_rows

我应该首先指出polar本身有一个hash_rows函数,它将对DataFrame的行进行散列,而不需要首先将每个列强制转换为字符串。

df.hash_rows()
shape: (4,)
Series: '' [u64]
[
16206777682454905786
7386261536140378310
3777361287274669406
675264002871508281
]

如果您觉得这是可以接受的,那么这将是性能最好的解决方案。如果需要,可以将结果的unsigned int强制转换为字符串。注意:hash_rows只能在DataFrame上使用,不能在LazyFrame上使用。

使用polars.concat_strapply

如果您需要使用自己的散列解决方案,那么我建议使用polars.concat_str函数将每一行中的值连接到一个字符串。来自文档:

高偏振星。concat_str(exps: Union[Sequence[Union[modules .internals.expr. expr, str]], modules .internals.expr. expr], sep: str = ")→modules .internals.expr. expr

在线性时间内水平连接Utf8系列。非utf8列被强制转换为utf8。

因此,例如,下面是我们数据集上的结果连接:

df.with_column(
pl.concat_str(pl.all()).alias('concatenated_cols')
)
shape: (4, 6)
┌─────────┬───────────┬──────────┬────────────┬────────────┬────────────────────────────────┐
│ col_int ┆ col_float ┆ col_bool ┆ col_str    ┆ col_date   ┆ concatenated_cols              │
│ ---     ┆ ---       ┆ ---      ┆ ---        ┆ ---        ┆ ---                            │
│ i64     ┆ f64       ┆ bool     ┆ str        ┆ date       ┆ str                            │
╞═════════╪═══════════╪══════════╪════════════╪════════════╪════════════════════════════════╡
│ 1       ┆ 10.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 ┆ 110.0true2020-01-012020-01-01  │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2       ┆ 20.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 ┆ 220.0false2020-01-012020-01-01 │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 3       ┆ 30.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 ┆ 330.0true2020-01-012020-01-01  │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 4       ┆ 40.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 ┆ 440.0false2020-01-012020-01-01 │
└─────────┴───────────┴──────────┴────────────┴────────────┴────────────────────────────────┘

采取下一步并使用apply方法和您的函数将产生:

df.with_column(
pl.concat_str(pl.all()).apply(hash_row).alias('hash')
)
shape: (4, 6)
┌─────────┬───────────┬──────────┬────────────┬────────────┬─────────────────────────────────────┐
│ col_int ┆ col_float ┆ col_bool ┆ col_str    ┆ col_date   ┆ hash                                │
│ ---     ┆ ---       ┆ ---      ┆ ---        ┆ ---        ┆ ---                                 │
│ i64     ┆ f64       ┆ bool     ┆ str        ┆ date       ┆ str                                 │
╞═════════╪═══════════╪══════════╪════════════╪════════════╪═════════════════════════════════════╡
│ 1       ┆ 10.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 ┆ 1826eb9c6aeb0abcdd2999a76eee576e... │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2       ┆ 20.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 ┆ ea50f5b11957bfc92b5ab7545b3ac12c... │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 3       ┆ 30.0      ┆ true     ┆ 2020-01-01 ┆ 2020-01-01 ┆ eef039d8dedadcc282d6fa9473e071e8... │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 4       ┆ 40.0      ┆ false    ┆ 2020-01-01 ┆ 2020-01-01 ┆ dcc5c57e0b5fdf15320a84c6839b0e3d... │
└─────────┴───────────┴──────────┴────────────┴────────────┴─────────────────────────────────────┘

请记住,任何时候Polars调用外部库或运行Python字节码,你都受到Python GIL的约束,这意味着单线程性能-无论你如何编码。从极地烹饪书部分不要杀死并行化!:

我们都听说Python很慢,而且不"缩放"。除了运行"慢"的开销之外在字节码中,Python必须保持在全局解释器锁(GIL)的约束之内。这意味着如果你要在并行化阶段使用lambda或自定义Python函数来应用,polar的速度会受到Python代码运行的限制,以防止任何多线程执行该函数。

这让人感觉非常受限,特别是因为我们经常需要在.groupby()步骤中使用这些lambda函数。Polars仍然支持这种方法,但要记住必须支付字节码和GIL成本。

为了缓解这个问题,Polars实现了一个强大的语法,不仅定义在它的惰性API中,也定义在它的惰性API中。

谢谢-不知道hash_rows。你的解几乎和我写的一样。我必须提到的一件事是——

concat_str不为我工作,如果有null在你的系列。因此,我必须在fill_null之前转换为Utf8。然后我可以concat_str并对结果应用hash_row。

def set_datatypes_and_replace_nulls(df, idcol="factset_person_id"):
return (
df
.with_columns([
pl.col("*").cast(pl.Utf8, strict=False),
pl.col("*").fill_null(pl.lit(""))
])
.with_columns([
pl.concat_str(pl.col("*").exclude(exclude_cols)).alias('concatstr'),
])
)
def hash_concat(df):
return (
df
.with_columns([
pl.col("concatstr").apply(hash_row).alias('sha256hash')
])
)

之后,我们需要按ID聚合哈希值。


df = (
df
.pipe(set_datatypes_and_replace_nulls)
.pipe(hash_concat)
)
# something like the below...
part1= (
df.lazy()
.groupby("id")
.agg(
[
pl.col("concatstr").unique().list(),
]
)
)

感谢pl.hash_rows的改进。

最新更新