从 Pandas 到 Polars 一:常用命令的比较
例如,如果在一个可以提前完成的查询后期进行过滤(从而减少处理的数据量),那么Polars查询优化器将在查询中前移过滤器。更好的是,如果对存储在云中的Parquet数据应用过滤器,Polars就会尝试在数据通过网络传输之前在云存储层中应用这些过滤器。在Pandas中,聚合的输出可以是一个Series,而在Polars中,它总是一个DataFrame。如果输出是Pandas中的一个Series,则会存
这里展示了如何将一些熟悉的Pandas命令转换为Polars。另外也介绍Pandas和Polars之间一些根本区别。
在以下示例中,将Polars v1.2.1与Pandas v2.2.0进行比较。
首先在Polars中创建一个样本数据集
import polars as pl
import polars.selectors as cs
import pandas as pd
import numpy as np
df = pl.DataFrame(
{
'grp': [1, 2, 1, 2, 1, 2],
'x': list(range(6, 0, -1)),
'y': list(range(4, 10)),
'z': [3, 4, 5, 6, 7, None],
"ref" : list('abcdef')
})
shape: (6, 5)
┌─────┬─────┬─────┬──────┬─────┐
│ grp ┆ x ┆ y ┆ z ┆ ref │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ str │
╞═════╪═════╪═════╪══════╪═════╡
│ 1 ┆ 6 ┆ 4 ┆ 3 ┆ a │
│ 2 ┆ 5 ┆ 5 ┆ 4 ┆ b │
│ 1 ┆ 4 ┆ 6 ┆ 5 ┆ c │
│ 2 ┆ 3 ┆ 7 ┆ 6 ┆ d │
│ 1 ┆ 2 ┆ 8 ┆ 7 ┆ e │
│ 2 ┆ 1 ┆ 9 ┆ null ┆ f │
└─────┴─────┴─────┴──────┴─────┘
访问DataFrame中的数据
有两种方法可以访问Polars DataFrame中的数据:
- 1.使用带有 [ ] 的方括号(另称为“索引”)
- 2.将表达式API与filter、select和withcolumns等方法一起使用
这些方括号和表达式API方法有不同的用法。除非您正在执行一次性操作,例如在这些情况下,使用[ ]方法。:
- 检查某些行或列的值
- 将DataFrame列转换为Series
表达式API比[ ]方法更强大,因为:
- 使用表达式API的操作是并行运行
- 表达式API中的操作可以在惰性模式下进行优化
使用表达式API访问数据
选择和转换DataFrame
操作 | Pandas | Polars |
选择列的一个子集 | df[["x","y"]] | df.select("x","y") |
选择和转换列 | df[["x","y"]].astype(float) | df.select(pl.col("x","y").cast(pl.Float64)) |
从一个常量中添加一个列 | df["w"] = 1 | df.with_columns(w = pl.lit(1)) |
df.with_columns(pl.lit(1).alias("w")) | ||
从一个列表中添加一个列 | df["w"] = list(range(1,7)) | df.with_columns(pl.Series("w",list(range(1,7)))) |
从其他列中添加一个列 | df["w"] = df["x"] + df["y"] | df.with_columns(w = pl.col("x") + pl.col("y")) |
更改列的数据类型 | df.assign(x = lambda df: df["x"].astype("float")) | df.with_columns(pl.col("x").cast(pl.Float64)) |
重命名列 | df.rename(columns={"x":"x2"}) | df.rename({"x":"x2"}) |
删除列 | df.drop(columns=["x","y"]) | df.drop(["x","y"]) |
df.drop("x","y") | ||
排序 | df.sort_values("x") | df.sort("x") |
复制 | df.copy() | df.clone() |
在上面的示例中,我们已经显式地输入了列名。 Polars也有很多方法可以根据其名称中的字段类型或模式来选择多个列。
选择 | Polars |
所有列 | df.select(pl.all()) |
所有整数列 | df.select(pl.col(pl.INTEGER_DTYPES)) |
...除了“x” | df.select(pl.col(pl.INTEGER_DTYPES).exclude('x')) |
所有整数列 | df.select(cs.integer()) |
按正则表达式 | df.select(cs.matches('x\|y')) |
按名称模式 | df.select(cs.starts_with('r')) |
Polars不支持就地操作,所以当在做任何转换时,会重新分配DataFrame变量。例如,如果添加一个新的常量列,必须这样做:
df = df.with_columns(w = pl.lit(1))
像这样创建一个新的DataFrame在Polars中几乎不耗资源,因为它不复制底层数据,Polars只是创建一个对底层数据的新引用。
筛选行
操作 | Pandas | Polars | |
过滤器行 | df.loc[df.ref == 'c'] | df.filter(pl.col("ref") == "c") | |
df.query("ref == 'c'") | df.filter(ref = "c") [1] | ||
筛选行(文本运算符) | df.loc[df.ref.eq('c')] | df.filter(pl.col("ref").eq("c")) | |
多个过滤器 | df.loc[(df.ref == 'c') & (df.x > 1)] | df.filter((pl.col("ref") == "c") & pl.col("x") > 1)) | |
df.filter(pl.col("ref") == "c", pl.col("x") > 1) [2] | |||
多个过滤器(优化) | df.lazy().filter(pl.col("ref") == "c").filter(pl.col("x") > 1) [3] | ||
多个过滤器(或条件) | df.loc[(df.ref == 'c') ` (df.x > 1)]` | df.filter((pl.col("ref") == "c") | ` pl.col(“x”) > 1))` |
处于状态 | df.loc[df.ref.isin(['a','b']) ] | ` df.filter(pl.col(“ref”).is_in([‘a’,’b’]))` | |
在条件之间 | df.loc[df.ref.between(2,4) ] | ` df.filter(pl.col(“x”).is_between(2,4))` |
[1] 在Polars列的第二行,创建了一个过滤器,没有使用pl.col。这是因为Polars有一种特殊的语法,可以按列名进行过滤。这种方法利用函数的Python关键字参数,将设置关键字参数类似ref =“c”视为过滤条件。这种方法只适用于相等的条件。
[2] 这里应用了多个过滤器条件,没有烦人的括号和&,而是一个以逗号分隔的条件列表传递给过滤器方法。
[3] 在优化的Polars的例子中,使用了单独的过滤器调用。然而,由于处于延迟模式,Polars查询优化器将它们合并为应用于通过数据的单次传递的单一过滤条件。
在Polars中还有很多需要过滤的东西。例如,如果在一个可以提前完成的查询后期进行过滤(从而减少处理的数据量),那么Polars查询优化器将在查询中前移过滤器。更好的是,如果对存储在云中的Parquet数据应用过滤器,Polars就会尝试在数据通过网络传输之前在云存储层中应用这些过滤器。
使用方括号[ ]访问数据
操作 | Pandas | Polars |
将列作为系列获取 | df["grp"] | df["grp"] |
按位置划分的单元格索引 | df.iloc[1, 1] | df[1, 1] |
按位置进行行切片 | df.iloc[1:3] | df[1:3] |
按位置划分列切片 | df.iloc[:, 1:] | df[:, 1:] |
按标签排列索引 | df.loc['c'] | df.filter(pl.col("index") == "c") |
按标签列索引 | df.loc[:, 'x'] | df[:, "x"] |
df.select("x") | ||
按标签列索引 | df.loc[:, ['x', 'z']] | df[:, ['x', 'z']] |
df.select(['x', 'z']) | ||
按标签切片列 | df.loc[:, 'x':'z'] | df[:, "x":"z"] |
混合索引 | df.loc['c'][1] | df.filter(pl.col("index") == "c")[0, 1] |
注意:当Pandas中的查询返回一行时,该行将作为一个Series返回。如果行同时包含浮点数和整数,则Pandas将整数强制转换为Series中的浮点数。Polars返回一个DataFrame,其中一行保持原始数据类型。
重复值和缺失值
操作 | Pandas | Polars |
保留唯一的行 | df.drop_duplicates() | df.unique() [1] |
…基于列的一个子集 | df.drop_duplicates(subset=["ref"]) | df.unique(subset=["ref"]) |
删除缺少值的行 | df.dropna() | df.drop_nulls() |
[1] 请注意,在Polars中,df.unique() 输出的顺序通常与输入的顺序不相同。此外,每个重复行中要保留的默认选项是any,而不是Pandas中的first。
在Pandas中缺失的值取决于列的数据类型,而在Polars中缺失的值对于所有的数据类型都为空。
分组数据和聚合
Polars有一个group_by函数来对行进行分组。下表说明了一些常见的分组和聚合用法。
操作 | Pandas | Polars |
分组聚合 | df.groupby('grp')['x'].mean() | df.group_by('grp').agg(pl.col("x").mean() |
聚合多列 | df.agg({'x': max, 'y': min}) | df.select([pl.col("x").max(),pl.col("y").min()]) |
df[['x', 'y']].mean() | df.select(["x","y"]).mean() | |
df.filter(regex=("^x")).mean() | df.select(pl.col("^x$").mean() | |
df.select(cs.starts_with("x").mean() | ||
聚合后重命名 | df.groupby('grp')['x'].mean() | df.group_by("grp").agg(pl.col("x").mean() |
以列的形式增加聚合的数据 | df.assign(x_mean=df.groupby("grp")["x"] | df_polars.with_column(pl.col("x").mean() |
在Pandas中,聚合的输出可以是一个Series,而在Polars中,它总是一个DataFrame。如果输出是Pandas中的一个Series,则会存在改变数据类型的风险,如对浮动的提示。
另外,Polars中组的输出中的行的顺序在默认情况下是随机的。
往期热门文章:
从 Pandas 到 Polars 二十六:在Polars中,不要遍历列
从 Pandas 到 Polars 二十三:如果你的数据已经排序,Polars可以为你提供助力
从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望
从 Pandas 到 Polars 十三:流式处理的关键参数
从 Pandas 到 Polars 十:“Polars 表达式“是什么?
更多推荐
所有评论(0)