字符串

了解ArrowPolars使用的内存格式可以真正提高查询的性能. 对于大型字符串数据尤其如此。下图显示了Arrow UTF8数组在内存中的布局。

数组[“foo”、“bar”、“ham”]由以下编码:

  • 连接字符串foobarham
  • 一个偏移数组,指示每个字符串[0,2,5,8]的开始(和结束),
  • 空位图,指示空值。

如果我们要读取字符串值,这种内存结构的缓存效率非常高。 尤其是如果我们将它与Vec<String>(在Rust中由堆分配的字符串数据组成的数组)进行比较。

然而,如果我们需要对Arrow UTF8数组重新排序,我们需要交换字符串值的所有字节,这在处理大型字符串时可能会非常昂贵。另一方面,对于Vec<String>,我们只需要交换指针,只需移动8字节的数据,成本很低。 由于一项操作(过滤、连接、分组)而嵌入大量Utf8 SeriesDataFrame的重新排序可能很快变得非常昂贵。

范畴型

因此,Polars有一个CategoricalTypecategory Series是一个数组,其中填充了u32值,每个值代表一个唯一的字符串值。因此,在保持缓存效率的同时,移动值的成本也很低。

在下面的示例中,我们将演示如何将一个Utf8 Series列强制转换为一个Categorical Series

import polars as pl

df["utf8-column"].cast(pl.Categorical)

在分类数据上加入多个数据帧

当需要基于字符串数据连接两个DataFrame时,需要同步Category数据(df1A列中的数据需要指向与df2中的B列相同的底层字符串数据)。可以通过在StringCache上下文管理器中强制转换数据来实现。这将在该上下文管理器期间同步所有可发现的字符串值。如果希望全局字符串缓存在整个运行期间存在,可以将toggle_string_cache设置为True

import polars as pl

df1 = pl.DataFrame({"a": ["foo", "bar", "ham"], "b": [1, 2, 3]})
df2 = pl.DataFrame({"a": ["foo", "spam", "eggs"], "c": [3, 2, 2]})

with pl.StringCache():
    df1.with_columns(pl.col("a").cast(pl.Categorical))
    df2.with_columns(pl.col("a").cast(pl.Categorical))

惰性连接分类数据上的多个数据帧

在查询期间(直到调用.collect()),惰性查询始终具有全局字符串缓存(除非您选择退出)。下面的示例显示了如何将两个DataFrameCategory类型连接起来。

import polars as pl

lf1 = pl.DataFrame({"a": ["foo", "bar", "ham"], "b": [1, 2, 3]}).lazy()
lf2 = pl.DataFrame({"a": ["foo", "spam", "eggs"], "c": [3, 2, 2]}).lazy()

lf1 = lf1.with_columns(pl.col("a").cast(pl.Categorical))
lf2 = lf2.with_columns(pl.col("a").cast(pl.Categorical))

lf1.join(lf2, on="a", how="inner")