表达式扩展
正如您在表达式与上下文一节中看到的,表达式展开是一个特性,它使您能够编写一个可以展开为多个不同表达式的单个表达式,这可能取决于表达式所使用的上下文的模式。
此功能不仅仅是装饰或语法糖。它允许在您的代码中非常强大地应用 DRY(Don't Repeat Yourself) 原则:指定多列的单个表达式会展开为一个表达式列表,这意味着您可以编写一个单一表达式并重用其所代表的计算。
在本节中,我们将展示几种表达式展开的形式,我们将为此使用您在下面看到的数据框:
import polars as pl
df = pl.DataFrame(
{ # As of 14th October 2024, ~3pm UTC
"ticker": ["AAPL", "NVDA", "MSFT", "GOOG", "AMZN"],
"company_name": ["Apple", "NVIDIA", "Microsoft", "Alphabet (Google)", "Amazon"],
"price": [229.9, 138.93, 420.56, 166.41, 188.4],
"day_high": [231.31, 139.6, 424.04, 167.62, 189.83],
"day_low": [228.6, 136.3, 417.52, 164.78, 188.44],
"year_high": [237.23, 140.76, 468.35, 193.31, 201.2],
"year_low": [164.08, 39.23, 324.39, 121.46, 118.35],
}
)
print(df)
use polars::prelude::*;
// Data as of 14th October 2024, ~3pm UTC
let df = df!(
"ticker" => ["AAPL", "NVDA", "MSFT", "GOOG", "AMZN"],
"company_name" => ["Apple", "NVIDIA", "Microsoft", "Alphabet (Google)", "Amazon"],
"price" => [229.9, 138.93, 420.56, 166.41, 188.4],
"day_high" => [231.31, 139.6, 424.04, 167.62, 189.83],
"day_low" => [228.6, 136.3, 417.52, 164.78, 188.44],
"year_high" => [237.23, 140.76, 468.35, 193.31, 201.2],
"year_low" => [164.08, 39.23, 324.39, 121.46, 118.35],
)?;
println!("{df}");
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
函数 col
函数 col
是在 Polars 中使用表达式展开功能的最常见方式。它通常用于引用数据框中的一个列,在本节中,我们将探讨使用 col
(或在 Rust 中的其变体)的其他方式。
按列名显式展开
最简单的表达式展开形式是当您向函数 col
提供多个列名时。
下面的示例使用单个函数 col
和多个列名将美元值转换为欧元
eur_usd_rate = 1.09 # As of 14th October 2024
result = df.with_columns(
(
pl.col(
"price",
"day_high",
"day_low",
"year_high",
"year_low",
)
/ eur_usd_rate
).round(2)
)
print(result)
let eur_usd_rate = 1.09; // As of 14th October 2024
let result = df
.clone()
.lazy()
.with_column(
(cols(["price", "day_high", "day_low", "year_high", "year_low"]) / lit(eur_usd_rate))
.round(2, RoundMode::default()),
)
.collect()?;
println!("{result}");
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 210.92 ┆ 212.21 ┆ 209.72 ┆ 217.64 ┆ 150.53 │
│ NVDA ┆ NVIDIA ┆ 127.46 ┆ 128.07 ┆ 125.05 ┆ 129.14 ┆ 35.99 │
│ MSFT ┆ Microsoft ┆ 385.83 ┆ 389.03 ┆ 383.05 ┆ 429.68 ┆ 297.61 │
│ GOOG ┆ Alphabet (Google) ┆ 152.67 ┆ 153.78 ┆ 151.17 ┆ 177.35 ┆ 111.43 │
│ AMZN ┆ Amazon ┆ 172.84 ┆ 174.16 ┆ 172.88 ┆ 184.59 ┆ 108.58 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
当您列出希望表达式展开到的列名时,您可以预测表达式将展开成什么。在这种情况下,执行货币转换的表达式被展开为五个表达式的列表。
exprs = [
(pl.col("price") / eur_usd_rate).round(2),
(pl.col("day_high") / eur_usd_rate).round(2),
(pl.col("day_low") / eur_usd_rate).round(2),
(pl.col("year_high") / eur_usd_rate).round(2),
(pl.col("year_low") / eur_usd_rate).round(2),
]
result2 = df.with_columns(exprs)
print(result.equals(result2))
let exprs = [
(col("price") / lit(eur_usd_rate)).round(2, RoundMode::default()),
(col("day_high") / lit(eur_usd_rate)).round(2, RoundMode::default()),
(col("day_low") / lit(eur_usd_rate)).round(2, RoundMode::default()),
(col("year_high") / lit(eur_usd_rate)).round(2, RoundMode::default()),
(col("year_low") / lit(eur_usd_rate)).round(2, RoundMode::default()),
];
let result2 = df.clone().lazy().with_columns(exprs).collect()?;
println!("{}", result.equals(&result2));
True
按数据类型展开
在前面的示例中,我们必须输入五个列名,但是函数 col
也可以方便地接受一个或多个数据类型。如果您提供数据类型而不是列名,则表达式将展开为与所提供数据类型之一匹配的所有列。
下面的示例执行与之前完全相同的计算
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 210.92 ┆ 212.21 ┆ 209.72 ┆ 217.64 ┆ 150.53 │
│ NVDA ┆ NVIDIA ┆ 127.46 ┆ 128.07 ┆ 125.05 ┆ 129.14 ┆ 35.99 │
│ MSFT ┆ Microsoft ┆ 385.83 ┆ 389.03 ┆ 383.05 ┆ 429.68 ┆ 297.61 │
│ GOOG ┆ Alphabet (Google) ┆ 152.67 ┆ 153.78 ┆ 151.17 ┆ 177.35 ┆ 111.43 │
│ AMZN ┆ Amazon ┆ 172.84 ┆ 174.16 ┆ 172.88 ┆ 184.59 ┆ 108.58 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
当我们使用数据类型进行表达式展开时,我们无法预先知道单个表达式将展开成多少列。如果我们要确定最终要应用的表达式列表,我们需要输入数据框的模式(schema)。
如果我们不确定价格列是 Float64
类型还是 Float32
类型,我们可以同时指定这两种数据类型
result2 = df.with_columns(
(
pl.col(
pl.Float32,
pl.Float64,
)
/ eur_usd_rate
).round(2)
)
print(result.equals(result2))
let result2 = df
.clone()
.lazy()
.with_column(
(dtype_cols([DataType::Float32, DataType::Float64]) / lit(eur_usd_rate))
.round(2, RoundMode::default()),
)
.collect()?;
println!("{}", result.equals(&result2));
True
按模式匹配展开
您还可以使用正则表达式来指定用于匹配列名的模式。为了区分常规列名和通过模式匹配进行的展开,正则表达式分别以 ^
和 $
开始和结束。这也意味着模式必须匹配整个列名字符串。
正则表达式可以与常规列名混合使用
shape: (5, 5)
┌────────┬──────────┬───────────┬─────────┬──────────┐
│ ticker ┆ day_high ┆ year_high ┆ day_low ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪══════════╪═══════════╪═════════╪══════════╡
│ AAPL ┆ 231.31 ┆ 237.23 ┆ 228.6 ┆ 164.08 │
│ NVDA ┆ 139.6 ┆ 140.76 ┆ 136.3 ┆ 39.23 │
│ MSFT ┆ 424.04 ┆ 468.35 ┆ 417.52 ┆ 324.39 │
│ GOOG ┆ 167.62 ┆ 193.31 ┆ 164.78 ┆ 121.46 │
│ AMZN ┆ 189.83 ┆ 201.2 ┆ 188.44 ┆ 118.35 │
└────────┴──────────┴───────────┴─────────┴──────────┘
参数不能混合类型
在 Python 中,函数 col
接受任意数量的字符串(作为列名或正则表达式)或任意数量的数据类型,但不能在同一个函数调用中混合使用两者。
try:
df.select(pl.col("ticker", pl.Float64))
except TypeError as err:
print("TypeError:", err)
TypeError: argument 'names': 'DataTypeClass' object cannot be converted to 'PyString'
选择所有列
Polars 提供了函数 all
作为引用数据框所有列的简写符号
True
注意
函数 all
是 col("*")
的语法糖,但由于参数 "*"
是一个特殊情况,并且 all
读起来更像英语,因此首选使用 all
。
排除列
Polars 还提供了一种机制来从表达式展开中排除某些列。为此,您可以使用函数 exclude
,它接受与 col
完全相同的参数类型。
shape: (5, 5)
┌────────┬───────────────────┬────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴───────────┴──────────┘
当然,函数 exclude
也可以在函数 col
之后使用。
shape: (5, 3)
┌────────┬───────────┬──────────┐
│ price ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════╪══════════╡
│ 229.9 ┆ 237.23 ┆ 164.08 │
│ 138.93 ┆ 140.76 ┆ 39.23 │
│ 420.56 ┆ 468.35 ┆ 324.39 │
│ 166.41 ┆ 193.31 ┆ 121.46 │
│ 188.4 ┆ 201.2 ┆ 118.35 │
└────────┴───────────┴──────────┘
列重命名
默认情况下,当您将表达式应用于列时,结果将保留与原始列相同的名称。
保留列名在语义上可能是错误的,在某些情况下,如果出现重复名称,Polars 甚至可能会引发错误。
from polars.exceptions import DuplicateError
gbp_usd_rate = 1.31 # As of 14th October 2024
try:
df.select(
pl.col("price") / gbp_usd_rate, # This would be named "price"...
pl.col("price") / eur_usd_rate, # And so would this.
)
except DuplicateError as err:
print("DuplicateError:", err)
let gbp_usd_rate = 1.31; // As of 14th October 2024
let result = df
.clone()
.lazy()
.select([
col("price") / lit(gbp_usd_rate),
col("price") / lit(eur_usd_rate),
])
.collect();
match result {
Ok(df) => println!("{df}"),
Err(e) => println!("{e}"),
};
DuplicateError: the name 'price' is duplicate
It's possible that multiple expressions are returning the same default column name. If this is the case, try renaming the columns with `.alias("new_name")` to avoid duplicate column names.
为了防止此类错误,并允许用户在适当时候重命名其列,Polars 提供了一系列函数,可让您更改列或列组的名称。
使用 alias
重命名单个列
函数 alias
在文档中已被广泛使用,它允许您重命名单个列。
result = df.select(
(pl.col("price") / gbp_usd_rate).alias("price (GBP)"),
(pl.col("price") / eur_usd_rate).alias("price (EUR)"),
)
let _result = df
.clone()
.lazy()
.select([
(col("price") / lit(gbp_usd_rate)).alias("price (GBP)"),
(col("price") / lit(eur_usd_rate)).alias("price (EUR)"),
])
.collect()?;
为列名添加前缀和后缀
使用表达式展开时,您不能使用函数 alias
,因为函数 alias
专门用于重命名单个列。
当只需在现有名称中添加静态前缀或静态后缀时,我们可以使用 name
命名空间中的 prefix
和 suffix
函数。
name namespace
· prefix
· suffix
result = df.select(
(pl.col("^year_.*$") / eur_usd_rate).name.prefix("in_eur_"),
(pl.col("day_high", "day_low") / gbp_usd_rate).name.suffix("_gbp"),
)
print(result)
name namespace
· prefix
· suffix
· 可在 feature lazy 中使用
let result = df
.clone()
.lazy()
.select([
(col("^year_.*$") / lit(eur_usd_rate))
.name()
.prefix("in_eur_"),
(cols(["day_high", "day_low"]) / lit(gbp_usd_rate))
.name()
.suffix("_gbp"),
])
.collect()?;
println!("{result}");
shape: (5, 4)
┌──────────────────┬─────────────────┬──────────────┬─────────────┐
│ in_eur_year_high ┆ in_eur_year_low ┆ day_high_gbp ┆ day_low_gbp │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞══════════════════╪═════════════════╪══════════════╪═════════════╡
│ 217.642202 ┆ 150.53211 ┆ 176.572519 ┆ 174.503817 │
│ 129.137615 ┆ 35.990826 ┆ 106.564885 ┆ 104.045802 │
│ 429.678899 ┆ 297.605505 ┆ 323.694656 ┆ 318.717557 │
│ 177.348624 ┆ 111.431193 ┆ 127.954198 ┆ 125.78626 │
│ 184.587156 ┆ 108.577982 ┆ 144.908397 ┆ 143.847328 │
└──────────────────┴─────────────────┴──────────────┴─────────────┘
动态名称替换
如果静态前缀/后缀不够,name
命名空间还提供了 map
函数,它接受一个可调用对象,该对象接受旧列名并生成新列名。
# There is also `.name.to_uppercase`, so this usage of `.map` is moot.
result = df.select(pl.all().name.map(str.upper))
print(result)
name namespace
· map
· 可在 feature lazy 中使用
// There is also `name().to_uppercase()`, so this usage of `map` is moot.
let result = df
.clone()
.lazy()
.select([all()
.name()
.map(|name| Ok(PlSmallStr::from_string(name.to_ascii_uppercase())))])
.collect()?;
println!("{result}");
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ TICKER ┆ COMPANY_NAME ┆ PRICE ┆ DAY_HIGH ┆ DAY_LOW ┆ YEAR_HIGH ┆ YEAR_LOW │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
有关 name
命名空间的完整内容,请参阅 API 参考。
程序化生成表达式
表达式展开是一个非常有用的功能,但它并不能解决所有问题。例如,如果我们想计算数据框中股票价格的每日和年度振幅,表达式展开将无济于事。
起初,您可能会考虑使用 for
循环
result = df
for tp in ["day", "year"]:
result = result.with_columns(
(pl.col(f"{tp}_high") - pl.col(f"{tp}_low")).alias(f"{tp}_amplitude")
)
print(result)
let mut result = df.clone().lazy();
for tp in ["day", "year"] {
let high = format!("{tp}_high");
let low = format!("{tp}_low");
let aliased = format!("{tp}_amplitude");
result = result.with_column((col(high) - col(low)).alias(aliased))
}
let result = result.collect()?;
println!("{result}");
shape: (5, 9)
┌────────┬──────────────┬────────┬──────────┬───┬───────────┬──────────┬─────────────┬─────────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ … ┆ year_high ┆ year_low ┆ day_amplitu ┆ year_amplit │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ de ┆ ude │
│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ --- ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 │
╞════════╪══════════════╪════════╪══════════╪═══╪═══════════╪══════════╪═════════════╪═════════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ … ┆ 237.23 ┆ 164.08 ┆ 2.71 ┆ 73.15 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ … ┆ 140.76 ┆ 39.23 ┆ 3.3 ┆ 101.53 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ … ┆ 468.35 ┆ 324.39 ┆ 6.52 ┆ 143.96 │
│ GOOG ┆ Alphabet ┆ 166.41 ┆ 167.62 ┆ … ┆ 193.31 ┆ 121.46 ┆ 2.84 ┆ 71.85 │
│ ┆ (Google) ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ … ┆ 201.2 ┆ 118.35 ┆ 1.39 ┆ 82.85 │
└────────┴──────────────┴────────┴──────────┴───┴───────────┴──────────┴─────────────┴─────────────┘
请勿这样做。相反,请程序化地生成您要计算的所有表达式,并在一个上下文中仅使用它们一次。粗略地说,您希望将 for
循环与上下文 with_columns
交换。实际上,您可以这样做:
def amplitude_expressions(time_periods):
for tp in time_periods:
yield (pl.col(f"{tp}_high") - pl.col(f"{tp}_low")).alias(f"{tp}_amplitude")
result = df.with_columns(amplitude_expressions(["day", "year"]))
print(result)
let mut exprs: Vec<Expr> = vec![];
for tp in ["day", "year"] {
let high = format!("{tp}_high");
let low = format!("{tp}_low");
let aliased = format!("{tp}_amplitude");
exprs.push((col(high) - col(low)).alias(aliased))
}
let result = df.clone().lazy().with_columns(exprs).collect()?;
println!("{result}");
shape: (5, 9)
┌────────┬──────────────┬────────┬──────────┬───┬───────────┬──────────┬─────────────┬─────────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ … ┆ year_high ┆ year_low ┆ day_amplitu ┆ year_amplit │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ de ┆ ude │
│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ --- ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 │
╞════════╪══════════════╪════════╪══════════╪═══╪═══════════╪══════════╪═════════════╪═════════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ … ┆ 237.23 ┆ 164.08 ┆ 2.71 ┆ 73.15 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ … ┆ 140.76 ┆ 39.23 ┆ 3.3 ┆ 101.53 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ … ┆ 468.35 ┆ 324.39 ┆ 6.52 ┆ 143.96 │
│ GOOG ┆ Alphabet ┆ 166.41 ┆ 167.62 ┆ … ┆ 193.31 ┆ 121.46 ┆ 2.84 ┆ 71.85 │
│ ┆ (Google) ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ … ┆ 201.2 ┆ 118.35 ┆ 1.39 ┆ 82.85 │
└────────┴──────────────┴────────┴──────────┴───┴───────────┴──────────┴─────────────┴─────────────┘
这会产生相同的最终结果,并且通过一次性指定所有表达式,我们让 Polars 有机会:
- 更好地优化查询;以及
- 并行执行实际计算。
更灵活的列选择
Polars 附带 selectors
子模块,该模块提供了一些函数,允许您为表达式展开编写更灵活的列选择。
警告
此功能尚未在 Rust 中提供。请参阅 Polars 问题 #10594。
作为第一个示例,下面展示了我们如何使用函数 string
和 ends_with
,以及 selectors
函数支持的集合操作,来选择所有字符串列和名称以 "_high"
结尾的列:
import polars.selectors as cs
result = df.select(cs.string() | cs.ends_with("_high"))
print(result)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
shape: (5, 4)
┌────────┬───────────────────┬──────────┬───────────┐
│ ticker ┆ company_name ┆ day_high ┆ year_high │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪══════════╪═══════════╡
│ AAPL ┆ Apple ┆ 231.31 ┆ 237.23 │
│ NVDA ┆ NVIDIA ┆ 139.6 ┆ 140.76 │
│ MSFT ┆ Microsoft ┆ 424.04 ┆ 468.35 │
│ GOOG ┆ Alphabet (Google) ┆ 167.62 ┆ 193.31 │
│ AMZN ┆ Amazon ┆ 189.83 ┆ 201.2 │
└────────┴───────────────────┴──────────┴───────────┘
selectors
子模块提供了一些基于列数据类型进行匹配的选择器,其中最有用的是匹配整个类型类别的函数,例如用于所有数值数据类型的 cs.numeric
或用于所有时间数据类型的 cs.temporal
。
selectors
子模块还提供了一些基于列名模式进行匹配的选择器,这使得指定您可能想要检查的常见模式更加方便,例如上面所示的函数 cs.ends_with
。
使用集合操作组合选择器
我们可以使用集合操作和常用的 Python 运算符组合多个选择器
运算符 | 操作 |
---|---|
A | B |
并集 |
A & B |
交集 |
A - B |
差集 |
A ^ B |
对称差集 |
~A |
补集 |
下一个示例匹配所有名称中包含下划线的非字符串列
result = df.select(cs.contains("_") - cs.string())
print(result)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
shape: (5, 4)
┌──────────┬─────────┬───────────┬──────────┐
│ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞══════════╪═════════╪═══════════╪══════════╡
│ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└──────────┴─────────┴───────────┴──────────┘
解决运算符歧义
表达式函数可以在选择器之上链式调用
result = df.select((cs.contains("_") - cs.string()) / eur_usd_rate)
print(result)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
shape: (5, 4)
┌────────────┬────────────┬────────────┬────────────┐
│ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════════╪════════════╪════════════╪════════════╡
│ 212.211009 ┆ 209.724771 ┆ 217.642202 ┆ 150.53211 │
│ 128.073394 ┆ 125.045872 ┆ 129.137615 ┆ 35.990826 │
│ 389.027523 ┆ 383.045872 ┆ 429.678899 ┆ 297.605505 │
│ 153.779817 ┆ 151.174312 ┆ 177.348624 ┆ 111.431193 │
│ 174.155963 ┆ 172.880734 ┆ 184.587156 ┆ 108.577982 │
└────────────┴────────────┴────────────┴────────────┘
然而,某些运算符被重载,既可作用于 Polars 选择器,也可作用于表达式。例如,选择器上的运算符 ~
表示集合操作“补集”,而表达式上的运算符则表示布尔运算的否定。
当您使用选择器,然后想在表达式的上下文中,使用作用于选择器的集合运算符之一时,您可以使用函数 as_expr
。
下面,我们想对“has_partner”、“has_kids”和“has_tattoos”列中的布尔值取反。如果我们不小心,运算符 ~
和选择器 cs.starts_with("has_")
的组合实际上会选择我们不关心的列。
people = pl.DataFrame(
{
"name": ["Anna", "Bob"],
"has_partner": [True, False],
"has_kids": [False, False],
"has_tattoos": [True, False],
"is_alive": [True, True],
}
)
wrong_result = people.select((~cs.starts_with("has_")).name.prefix("not_"))
print(wrong_result)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
shape: (2, 2)
┌──────────┬──────────────┐
│ not_name ┆ not_is_alive │
│ --- ┆ --- │
│ str ┆ bool │
╞══════════╪══════════════╡
│ Anna ┆ true │
│ Bob ┆ true │
└──────────┴──────────────┘
正确的解决方案是使用 as_expr
result = people.select((~cs.starts_with("has_").as_expr()).name.prefix("not_"))
print(result)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
shape: (2, 3)
┌─────────────────┬──────────────┬─────────────────┐
│ not_has_partner ┆ not_has_kids ┆ not_has_tattoos │
│ --- ┆ --- ┆ --- │
│ bool ┆ bool ┆ bool │
╞═════════════════╪══════════════╪═════════════════╡
│ false ┆ true ┆ false │
│ true ┆ true ┆ true │
└─────────────────┴──────────────┴─────────────────┘
调试选择器
当您不确定是否手头有 Polars 选择器时,可以使用函数 cs.is_selector
进行检查
print(cs.is_selector(~cs.starts_with("has_").as_expr()))
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
False
这应该有助于您避免任何模糊不清的情况,即您认为正在使用表达式进行操作,但实际上却在使用选择器进行操作。
另一个有用的调试工具是函数 expand_selector
。给定一个目标数据框或模式,您可以检查给定选择器将展开到哪些列。
print(
cs.expand_selector(
people,
cs.starts_with("has_"),
)
)
// Selectors are not available in Rust yet.
// Refer to https://github.com/pola-rs/polars/issues/10594
('has_partner', 'has_kids', 'has_tattoos')
完整参考
下表按行为类型对 selectors
子模块中可用的函数进行分组。
数据类型选择器
根据列数据类型进行匹配的选择器
选择器函数 | 匹配的数据类型 |
---|---|
binary |
Binary (二进制) |
boolean |
Boolean (布尔) |
by_dtype |
指定为参数的数据类型 |
categorical |
Categorical (范畴) |
date |
Date (日期) |
datetime |
Datetime ,可选按时间单位/时区过滤 |
decimal |
Decimal (小数) |
duration |
Duration ,可选按时间单位过滤 |
float |
所有浮点类型,不考虑精度 |
integer |
所有整数类型,包括有符号和无符号,不考虑精度 |
numeric |
所有数值类型,即整数、浮点数和 Decimal |
signed_integer |
所有有符号整数类型,不考虑精度 |
string |
String (字符串) |
temporal |
所有时间数据类型,即 Date 、Datetime 和 Duration |
time |
Time (时间) |
unsigned_integer |
所有无符号整数类型,不考虑精度 |
列名模式选择器
根据列名模式进行匹配的选择器
选择器函数 | 选择的列 |
---|---|
alpha |
名称为字母的列 |
alphanumeric |
名称为字母数字的列(字母和数字 0-9) |
by_name |
指定为参数的列名 |
contains |
名称包含指定子字符串的列 |
digit |
名称为数字的列(仅包含数字 0-9) |
ends_with |
名称以给定子字符串结尾的列 |
matches |
名称匹配给定正则表达式模式的列 |
starts_with |
名称以给定子字符串开头的列 |
位置选择器
根据列位置进行匹配的选择器
选择器函数 | 选择的列 |
---|---|
all |
所有列 |
by_index |
指定索引处的列 |
first |
上下文中的第一列 |
last |
上下文中的最后一列 |
杂项函数
selectors
子模块还提供以下函数:
函数 | 行为 |
---|---|
as_expr * |
将选择器转换为表达式 |
exclude |
选择除匹配给定名称、数据类型或选择器之外的所有列 |
expand_selector |
相对于特定数据框或目标模式展开选择器以匹配列 |
is_selector |
检查给定对象/表达式是否为选择器 |
*as_expr
不是在 selectors
子模块上定义的函数,而是选择器上定义的方法。