跳到内容

字符串

以下部分讨论了对字符串数据执行的操作,字符串数据是使用数据帧时常用的数据类型。字符串处理函数可在 `str` 命名空间中找到。

在其他数据帧库中处理字符串可能会非常低效,因为字符串的长度不可预测。Polars 通过遵循 Arrow 列式格式规范来缓解这些低效问题,因此您也可以对字符串数据编写高性能的数据查询。

字符串命名空间

处理字符串数据时,您可能需要访问 `str` 命名空间,该命名空间聚合了 40 多个允许您处理字符串的函数。作为如何从该命名空间中访问函数的示例,下面的代码片段展示了如何计算列中字符串的字节长度和字符长度

str.len_bytes · str.len_chars

import polars as pl

df = pl.DataFrame(
    {
        "language": ["English", "Dutch", "Portuguese", "Finish"],
        "fruit": ["pear", "peer", "pêra", "päärynä"],
    }
)

result = df.with_columns(
    pl.col("fruit").str.len_bytes().alias("byte_count"),
    pl.col("fruit").str.len_chars().alias("letter_count"),
)
print(result)

str.len_bytes · str.len_chars

use polars::prelude::*;

let df = df! (
    "language" => ["English", "Dutch", "Portuguese", "Finish"],
    "fruit" => ["pear", "peer", "pêra", "päärynä"],
)?;

let result = df
    .clone()
    .lazy()
    .with_columns([
        col("fruit").str().len_bytes().alias("byte_count"),
        col("fruit").str().len_chars().alias("letter_count"),
    ])
    .collect()?;

println!("{result}");

shape: (4, 4)
┌────────────┬─────────┬────────────┬──────────────┐
│ language   ┆ fruit   ┆ byte_count ┆ letter_count │
│ ---        ┆ ---     ┆ ---        ┆ ---          │
│ str        ┆ str     ┆ u32        ┆ u32          │
╞════════════╪═════════╪════════════╪══════════════╡
│ English    ┆ pear    ┆ 4          ┆ 4            │
│ Dutch      ┆ peer    ┆ 4          ┆ 4            │
│ Portuguese ┆ pêra    ┆ 5          ┆ 4            │
│ Finish     ┆ päärynä ┆ 10         ┆ 7            │
└────────────┴─────────┴────────────┴──────────────┘

注意

如果您只处理 ASCII 文本,那么这两种计算的结果将相同,建议使用 `len_bytes`,因为它更快。

解析字符串

Polars 提供了多种方法来检查和解析字符串列的元素,即检查给定子字符串或模式是否存在,以及计数、提取或替换它们。我们将在接下来的示例中演示其中一些操作。

检查模式是否存在

我们可以使用 `contains` 函数检查字符串中是否存在模式。默认情况下,`contains` 函数的参数被解释为正则表达式。如果您想指定一个字面子字符串,请将参数 `literal` 设置为 `True`。

对于您想检查字符串是否以固定子字符串开头或结尾的特殊情况,您可以分别使用 `starts_with` 或 `ends_with` 函数。

str.contains · str.starts_with · str.ends_with

result = df.select(
    pl.col("fruit"),
    pl.col("fruit").str.starts_with("p").alias("starts_with_p"),
    pl.col("fruit").str.contains("p..r").alias("p..r"),
    pl.col("fruit").str.contains("e+").alias("e+"),
    pl.col("fruit").str.ends_with("r").alias("ends_with_r"),
)
print(result)

str.contains · str.starts_with · str.ends_with · 在 regex 特性下可用

let result = df
    .clone()
    .lazy()
    .select([
        col("fruit"),
        col("fruit")
            .str()
            .starts_with(lit("p"))
            .alias("starts_with_p"),
        col("fruit").str().contains(lit("p..r"), true).alias("p..r"),
        col("fruit").str().contains(lit("e+"), true).alias("e+"),
        col("fruit").str().ends_with(lit("r")).alias("ends_with_r"),
    ])
    .collect()?;

println!("{result}");

shape: (4, 5)
┌─────────┬───────────────┬───────┬───────┬─────────────┐
│ fruit   ┆ starts_with_p ┆ p..r  ┆ e+    ┆ ends_with_r │
│ ---     ┆ ---           ┆ ---   ┆ ---   ┆ ---         │
│ str     ┆ bool          ┆ bool  ┆ bool  ┆ bool        │
╞═════════╪═══════════════╪═══════╪═══════╪═════════════╡
│ pear    ┆ true          ┆ true  ┆ true  ┆ true        │
│ peer    ┆ true          ┆ true  ┆ true  ┆ true        │
│ pêra    ┆ true          ┆ false ┆ false ┆ false       │
│ päärynä ┆ true          ┆ true  ┆ false ┆ false       │
└─────────┴───────────────┴───────┴───────┴─────────────┘

正则表达式规范

Polars 依赖 Rust crate `regex` 来处理正则表达式,因此您可能需要参考语法文档以查看支持哪些特性和标志。特别需要注意的是,Polars 支持的正则表达式风格与 Python 的 `re` 模块不同。

提取模式

`extract` 函数允许我们从列中的字符串值中提取模式。`extract` 函数接受一个带有一个或多个捕获组的正则表达式模式,并提取作为第二个参数指定的捕获组。

str.extract

df = pl.DataFrame(
    {
        "urls": [
            "http://vote.com/ballon_dor?candidate=messi&ref=polars",
            "http://vote.com/ballon_dor?candidat=jorginho&ref=polars",
            "http://vote.com/ballon_dor?candidate=ronaldo&ref=polars",
        ]
    }
)
result = df.select(
    pl.col("urls").str.extract(r"candidate=(\w+)", group_index=1),
)
print(result)

str.extract

let df = df! (
    "urls" => [
        "http://vote.com/ballon_dor?candidate=messi&ref=polars",
        "http://vote.com/ballon_dor?candidat=jorginho&ref=polars",
        "http://vote.com/ballon_dor?candidate=ronaldo&ref=polars",
    ]
)?;

let result = df
    .clone()
    .lazy()
    .select([col("urls").str().extract(lit(r"candidate=(\w+)"), 1)])
    .collect()?;

println!("{result}");

shape: (3, 1)
┌─────────┐
│ urls    │
│ ---     │
│ str     │
╞═════════╡
│ messi   │
│ null    │
│ ronaldo │
└─────────┘

要提取字符串中模式的所有出现,我们可以使用 `extract_all` 函数。在下面的示例中,我们使用正则表达式模式 `(\d+)` 从字符串中提取所有数字,该模式匹配一个或多个数字。`extract_all` 函数的输出是一个列表,其中包含字符串中匹配模式的所有实例。

str.extract_all

df = pl.DataFrame({"text": ["123 bla 45 asd", "xyz 678 910t"]})
result = df.select(
    pl.col("text").str.extract_all(r"(\d+)").alias("extracted_nrs"),
)
print(result)

str.extract_all

let df = df! (
    "text" => ["123 bla 45 asd", "xyz 678 910t"]
)?;

let result = df
    .clone()
    .lazy()
    .select([col("text")
        .str()
        .extract_all(lit(r"(\d+)"))
        .alias("extracted_nrs")])
    .collect()?;

println!("{result}");

shape: (2, 1)
┌────────────────┐
│ extracted_nrs  │
│ ---            │
│ list[str]      │
╞════════════════╡
│ ["123", "45"]  │
│ ["678", "910"] │
└────────────────┘

替换模式

类似于 `extract` 和 `extract_all` 函数,Polars 提供了 `replace` 和 `replace_all` 函数。它们接受正则表达式模式或字面子字符串(如果参数 `literal` 设置为 `True`),并执行指定的替换。`replace` 函数最多进行一次替换,而 `replace_all` 函数将进行它找到的所有非重叠替换。

str.replace · str.replace_all

df = pl.DataFrame({"text": ["123abc", "abc456"]})
result = df.with_columns(
    pl.col("text").str.replace(r"\d", "-"),
    pl.col("text").str.replace_all(r"\d", "-").alias("text_replace_all"),
)
print(result)

str.replace · str.replace_all · 在 regex 特性下可用

let df = df! (
    "text" => ["123abc", "abc456"]
)?;

let result = df
    .clone()
    .lazy()
    .with_columns([
        col("text").str().replace(lit(r"\d"), lit("-"), false),
        col("text")
            .str()
            .replace_all(lit(r"\d"), lit("-"), false)
            .alias("text_replace_all"),
    ])
    .collect()?;

println!("{result}");

shape: (2, 2)
┌────────┬──────────────────┐
│ text   ┆ text_replace_all │
│ ---    ┆ ---              │
│ str    ┆ str              │
╞════════╪══════════════════╡
│ -23abc ┆ ---abc           │
│ abc-56 ┆ abc---           │
└────────┴──────────────────┘

修改字符串

大小写转换

转换字符串的大小写是常见的操作,Polars 通过 `to_lowercase`、`to_titlecase` 和 `to_uppercase` 函数开箱即用地支持此功能。

str.to_lowercase · str.to_titlecase · str.to_uppercase

addresses = pl.DataFrame(
    {
        "addresses": [
            "128 PERF st",
            "Rust blVD, 158",
            "PoLaRs Av, 12",
            "1042 Query sq",
        ]
    }
)

addresses = addresses.select(
    pl.col("addresses").alias("originals"),
    pl.col("addresses").str.to_titlecase(),
    pl.col("addresses").str.to_lowercase().alias("lower"),
    pl.col("addresses").str.to_uppercase().alias("upper"),
)
print(addresses)

str.to_lowercase · str.to_titlecase · str.to_uppercase · 在 nightly 特性下可用

let addresses = df! (
    "addresses" => [
        "128 PERF st",
        "Rust blVD, 158",
        "PoLaRs Av, 12",
        "1042 Query sq",
    ]
)?;

let addresses = addresses
    .clone()
    .lazy()
    .select([
        col("addresses").alias("originals"),
        col("addresses").str().to_titlecase(),
        col("addresses").str().to_lowercase().alias("lower"),
        col("addresses").str().to_uppercase().alias("upper"),
    ])
    .collect()?;

println!("{addresses}");

shape: (4, 4)
┌────────────────┬────────────────┬────────────────┬────────────────┐
│ originals      ┆ addresses      ┆ lower          ┆ upper          │
│ ---            ┆ ---            ┆ ---            ┆ ---            │
│ str            ┆ str            ┆ str            ┆ str            │
╞════════════════╪════════════════╪════════════════╪════════════════╡
│ 128 PERF st    ┆ 128 Perf St    ┆ 128 perf st    ┆ 128 PERF ST    │
│ Rust blVD, 158 ┆ Rust Blvd, 158 ┆ rust blvd, 158 ┆ RUST BLVD, 158 │
│ PoLaRs Av, 12  ┆ Polars Av, 12  ┆ polars av, 12  ┆ POLARS AV, 12  │
│ 1042 Query sq  ┆ 1042 Query Sq  ┆ 1042 query sq  ┆ 1042 QUERY SQ  │
└────────────────┴────────────────┴────────────────┴────────────────┘

剥离字符串两端字符

Polars 在 `str` 命名空间中提供了五个函数,允许您从字符串两端剥离字符

函数 行为
strip_chars 删除指定字符的前导和尾随出现项。
strip_chars_end 删除指定字符的尾随出现项。
strip_chars_start 删除指定字符的前导出现项。
strip_prefix 如果存在,删除精确的子字符串前缀。
strip_suffix 如果存在,删除精确的子字符串后缀。
与 Python 字符串方法的相似性

strip_chars 类似于 Python 的字符串方法 strip,而 strip_prefix/strip_suffix 分别类似于 Python 的字符串方法 removeprefixremovesuffix

重要的是要理解,前三个函数将其字符串参数解释为一组字符,而 `strip_prefix` 和 `strip_suffix` 函数则将其字符串参数解释为字面字符串。

str.strip_chars · str.strip_chars_end · str.strip_chars_start · str.strip_prefix · str.strip_suffix

addr = pl.col("addresses")
chars = ", 0123456789"
result = addresses.select(
    addr.str.strip_chars(chars).alias("strip"),
    addr.str.strip_chars_end(chars).alias("end"),
    addr.str.strip_chars_start(chars).alias("start"),
    addr.str.strip_prefix("128 ").alias("prefix"),
    addr.str.strip_suffix(", 158").alias("suffix"),
)
print(result)

str.strip_chars · str.strip_chars_end · str.strip_chars_start · str.strip_prefix · str.strip_suffix

let addr = col("addresses");
let chars = lit(", 0123456789");
let result = addresses
    .clone()
    .lazy()
    .select([
        addr.clone().str().strip_chars(chars.clone()).alias("strip"),
        addr.clone()
            .str()
            .strip_chars_end(chars.clone())
            .alias("end"),
        addr.clone()
            .str()
            .strip_chars_start(chars.clone())
            .alias("start"),
        addr.clone().str().strip_prefix(lit("128 ")).alias("prefix"),
        addr.clone()
            .str()
            .strip_suffix(lit(", 158"))
            .alias("suffix"),
    ])
    .collect()?;

println!("{result}");

shape: (4, 5)
┌───────────┬───────────────┬────────────────┬────────────────┬───────────────┐
│ strip     ┆ end           ┆ start          ┆ prefix         ┆ suffix        │
│ ---       ┆ ---           ┆ ---            ┆ ---            ┆ ---           │
│ str       ┆ str           ┆ str            ┆ str            ┆ str           │
╞═══════════╪═══════════════╪════════════════╪════════════════╪═══════════════╡
│ Perf St   ┆ 128 Perf St   ┆ Perf St        ┆ Perf St        ┆ 128 Perf St   │
│ Rust Blvd ┆ Rust Blvd     ┆ Rust Blvd, 158 ┆ Rust Blvd, 158 ┆ Rust Blvd     │
│ Polars Av ┆ Polars Av     ┆ Polars Av, 12  ┆ Polars Av, 12  ┆ Polars Av, 12 │
│ Query Sq  ┆ 1042 Query Sq ┆ Query Sq       ┆ 1042 Query Sq  ┆ 1042 Query Sq │
└───────────┴───────────────┴────────────────┴────────────────┴───────────────┘

如果未提供参数,`strip_chars`、`strip_chars_end` 和 `strip_chars_start` 这三个函数默认删除空白字符。

切片

除了根据模式提取子字符串之外,您还可以按指定的偏移量对字符串进行切片以生成子字符串。通用的切片函数是 `slice`,它接受起始偏移量和可选的切片*长度*。如果未指定切片长度或超出字符串末尾,Polars 会将字符串一直切片到末尾。

`head` 和 `tail` 函数是专门用于分别切取字符串开头和结尾的特殊版本。

str.slice · str.head · str.tail

df = pl.DataFrame(
    {
        "fruits": ["pear", "mango", "dragonfruit", "passionfruit"],
        "n": [1, -1, 4, -4],
    }
)

result = df.with_columns(
    pl.col("fruits").str.slice(pl.col("n")).alias("slice"),
    pl.col("fruits").str.head(pl.col("n")).alias("head"),
    pl.col("fruits").str.tail(pl.col("n")).alias("tail"),
)
print(result)

str.str_slice · str.str_head · str.str_tail

let df = df! (
    "fruits" => ["pear", "mango", "dragonfruit", "passionfruit"],
    "n" => [1, -1, 4, -4],
)?;

let result = df
    .clone()
    .lazy()
    .with_columns([
        col("fruits")
            .str()
            .slice(col("n"), lit(NULL))
            .alias("slice"),
        col("fruits").str().head(col("n")).alias("head"),
        col("fruits").str().tail(col("n")).alias("tail"),
    ])
    .collect()?;

println!("{result}");

shape: (4, 5)
┌──────────────┬─────┬─────────┬──────────┬──────────┐
│ fruits       ┆ n   ┆ slice   ┆ head     ┆ tail     │
│ ---          ┆ --- ┆ ---     ┆ ---      ┆ ---      │
│ str          ┆ i64 ┆ str     ┆ str      ┆ str      │
╞══════════════╪═════╪═════════╪══════════╪══════════╡
│ pear         ┆ 1   ┆ ear     ┆ p        ┆ r        │
│ mango        ┆ -1  ┆ o       ┆ mang     ┆ ango     │
│ dragonfruit  ┆ 4   ┆ onfruit ┆ drag     ┆ ruit     │
│ passionfruit ┆ -4  ┆ ruit    ┆ passionf ┆ ionfruit │
└──────────────┴─────┴─────────┴──────────┴──────────┘

API 文档

除了上面介绍的示例之外,Polars 还提供了各种其他字符串操作函数。要探索这些额外的方法,您可以查阅您选择的 Polars 编程语言的 API 文档。