Bootstrap

Polars简明基础教程一:Polars快速入门

为了帮助您入门,这篇文章介绍了一些使Polars成为一个强大的数据分析工具的关键概念。

我们遇到的关键概念包括:

  1. 使用Polars中的Expression API进行快速灵活的分析
  2. 易于进行的并行计算
  3. 在惰性模式下自动查询优化
  4. 在Polars中进行流处理以处理超过内存大小的数据集

导入Polars

我们首先导入polars作为‘pl’。遵循这个约定将允许您使用官方文档中的示例进行工作。

import polars as pl

配置选项设置

我们想要控制DataFrame中有多少行会被输出到屏幕上。Polars允许我们通过 pl.Config 命名空间中的方法来控制配置选项。

在这里,我们希望Polars打印出DataFrame的6行数据,所以我们使用pl.Config.set_tbl_rows来设置这个选项。

pl.Config.set_tbl_rows(6)

在这里可以找到polars官方完整的配置选项范围:

https://pola-rs.github.io/polars/py-polars/html/reference/config.html

在课程中,我们将看到如何在各种上下文中应用正确的配置选项。

加载数据

Polars可以从多种数据格式中读取数据,包括CSV、Parquet、Arrow、JSON、Excel和数据库连接。我们将在课程的I/O部分中涵盖所有这些内容。

对于本次介绍,我们使用包含泰坦尼克号乘客数据集的CSV文件。该数据集提供了泰坦尼克号上所有乘客的详细信息以及他们是否存活。

(示例中用到的csv数据文件可在这里免费下载:“泰坦尼克号生还者数据集”

我们首先设置该CSV文件的路径。

csv_file = "../data/titanic.csv"

我们使用read_csv函数将CSV文件读取到Polars的DataFrame中。

然后,我们调用head方法来打印出DataFrame的前几行。

df = pl.read_csv(csv_file)
df.head(3)

DataFrame的每一行都包含泰坦尼克号上乘客的详细信息,包括他们乘坐的舱位等级(Pclass)、姓名(Name)和年龄(Age)。

或者,我们可以使用glimpse来垂直查看前几个数据点。当数据框有很多列时,我经常使用这个方法。

print(df.glimpse())

表达式

在Polars中,您可以使用方括号来选择行和列...

df[:3,["Pclass","Name","Age"]]

...但是,使用这种方括号方法意味着您无法获得并行化和查询优化的所有好处。

我们将以后的课程中深入了解方括号索引。

为了真正利用Polars,我们使用Expression API(表达式API)。

使用Expression API选择和转换列

在这里,我们看到一个使用表达式API的简单示例,其中我们在select语句中选择Pclass、Name和Age列(我们将在第3节中深入了解select语句)。

(
    df
    .select(
        [
            pl.col("Pclass"),
            pl.col("Name"),
            pl.col("Age"),
        ]
    )
)

 在Expression API中,我们使用pl.col来引用一个列。

我们想要Name列中的字符串打印得更宽一些。我们可以这样做:

pl.Config.set_fmt_str_lengths(100)

在后续的课程部分中,我们将更深入地了解pl.Config命名空间,以配置Polars的外观和行为。

什么是表达式?

表达式是一个函数,它接受一个Series(或DataFrame中的列)作为输入,并返回一个Series(或DataFrame中的列)。

表达式是数据转换的核心构建块,包括:

  1. 恒等表达式,其中输出与输入相同
  2. 算术表达式,用于对Series中的所有元素进行加、乘等操作
  3. 对Series中的所有元素进行四舍五入
  4. 将Series中的所有字符串转换为大写
  5. 从datetime Series的所有元素中提取日期

等等

在这个例子中,我们选择了相同的三个列,但这次我们:

  1. 将名字转换为小写
  2. 将年龄四舍五入到小数点后两位
(
    df
    .select(
        [
             Identity expression
            pl.col("Pclass"),
             Names to lowercase
            pl.col("Name").str.to_lowercase(),
             Round the ages
            pl.col("Age").round(2)
        ]
    )
)

当我们有多个这样的表达式时,Polars会并行运行它们。

表达式也可以返回一个较短的Series,如使用head来返回前几行,或者使用聚合表达式如mean来获取Series中值的平均值。表达式还可以返回更长的Series,如使用explode将列表Series转换为单独的行。

我们将在后续章节中学习更多关于表达式的知识。

方法链式和代码格式化

在上面的单元格中,代码被包裹在括号()中。在Python中(而不是特指Polars),当我们将代码包裹在括号中时,我们可以在新行上调用一个新方法——在这种情况下是select。

在Polars中,我们经常通过多次调用新方法来分多步构建查询。我发现,如果每个方法都在新行上开始,那么阅读一系列查询会更容易,所以我通常会将代码块包裹在括号中。

表达式链式调用

除了链式调用方法外,我们还可以将表达式链式调用在一起,以便在单个步骤中进行更多转换。

在此示例中,我们返回三列:

  1. 原始的Name列
  2. 将Name列拆分为单词列表的列
  3. 当Name列拆分为单词列表时,单词数量的计数

Polars DataFrame中的列名始终是字符串,并且必须是唯一的。我们在第二个和第三个表达式的末尾使用alias方法,以确保不会出现多个名为Name的列。

(
    df
    .select(
        [
             Get the Name column without changes
            pl.col("Name"),
             Take the Name column and split it into a list of separate words
            pl.col("Name").str.split(" ").alias("Name_split"),
             Take the Name column, split it into a list of separate words and count the number of words
            pl.col("Name").str.split(" ").list.len().alias("Name_word_count"),
        ]
    )
)

在整个课程中,我们将详细探讨表达式,以找到适合许多不同场景的正确表达式。

表达式可能看起来冗长,但它们也允许我们一次选择多组列。例如,要选择所有的64位整型列,我们可以使用pl.

(
    df
    .select(
        pl.col(pl.Int64)
    )
    .head(3)
)

在第3节中,我们将遇到其他快速选择多个列的方法。

使用表达式API过滤DataFrame

我们通过将条件应用于表达式来过滤DataFrame。

在此示例中,我们找到所有年龄超过70岁的乘客。

(
    df
    .filter(
        pl.col("Age") > 70
    )
)

我们并不局限于仅使用表达式API进行这些操作。如我们所见,表达式API是Polars中所有数据转换的核心。

我们将在课程的后面章节中深入了解如何应用过滤条件。

分析

Polars具有广泛的数据分析功能。在课程中,我们将探讨更广泛的分析方法,以及如何使用表达式以简洁的方式编写更复杂的分析。

我们首先使用describe函数来获取DataFrame的概述。

df.describe()

describe的输出向我们展示了有多少条记录,多少null值以及一些关键的统计数据。null_count帮助我在机器学习流程中识别出现的数据质量问题。

列的值计数

我们使用value_counts来计算列中值的出现次数。

在这个例子中,我们使用value_counts来计算每个舱位中有多少名乘客。

df["Pclass"].value_counts()

分组和聚合

Polars有一个快速的并行算法用于group_by操作。

在这里,我们首先根据Survived和Pclass列进行分组。然后,在agg中通过计算每个组的乘客数量来进行聚合。

(
    df
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
)

我们在agg中的每个聚合操作都使用Expression API。

Polars中的groupby操作很快,因为Polars有一个用于获取groupby键的并行算法。聚合操作也很快,因为Polars在agg中并行运行多个表达式。

窗口操作

当我们想要添加一个列,该列不仅反映那一行的数据,还反映与之相关的一组行的数据时,就会发生窗口操作。窗口在许多上下文中出现,包括滚动统计或时间统计,而Polars覆盖了这些用例。

窗口操作的另一个例子是,我们想要在每一行上获得一组行的统计数据。为此,我们使用over表达式(相当于Pandas中的groupby-transform)。

在这个例子中,我们将添加一个列,其中包含每个舱位中乘客的最大年龄。要添加一列,我们在with_columns方法中使用一个表达式(我们将在第2节中看到更多关于这个方法的内容)。在表达式中,我们计算最大Age,并指定我们想要使用over来根据乘客舱位计算这个最大值。

(
    df
    .with_columns(
        pl.col("Age").max().over("Pclass").alias("MaxAge")
    )
    .select("Pclass","Age","MaxAge")
    .head(3)
)

我们在后面会深入了解分组和聚合。

可视化

我们可以直接使用流行的绘图库,如Matplotlib、Seaborn、Altair和Plotly,与Polars结合使用。

在这个例子中,我们使用Altair(版本5及以上)创建了一个年龄和费用的散点图和条形图。

import altair as alt

alt.Chart(
    df,
    title="Scatter plot of Age and Fare"
).mark_circle().encode(
    x="Age:Q",
    y="Fare:Q"
)

在本节的可视化讲座中,我们将学习如何使用Matplotlib、Seaborn、Altair和Plotly。

懒加载模式与查询优化

在上面的例子中,我们使用的是即时(eager)模式。在即时模式下,Polars会逐步运行查询的每一部分。

Polars具有一个强大的功能,称为懒加载(lazy)模式。在这种模式下,Polars会将查询视为一个整体来构建查询图。在运行查询之前,Polars会将查询图通过其查询优化器进行处理,以查看是否有方法使查询更快。

当我们处理CSV文件时,我们可以通过将read_csv替换为scan_csv来从即时模式切换到懒加载模式。

(
    pl.scan_csv(csv_file)
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
)

懒查询的输出是LazyFrame,当我们输出LazyFrame时,我们看到的是未优化的查询计划。

查询优化器

我们可以在查询的末尾添加explain来查看Polars实际将要运行的优化后的查询计划。

print(
    pl.scan_csv(csv_file)
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
    .explain()
)

在这个例子中,Polars已经识别了一个优化项:

 PROJECT 3/12 COLUMNS

CSV文件中有12列,但查询优化器发现查询只需要其中的3列。当查询被评估时,Polars会“投影”(PROJECT)出12列中的3列:Polars只会从CSV中读取所需的3列。这种投影节省了内存和计算时间。

当我们对查询应用一个filter时,会发生另一种优化。在这种情况下,我们想要对按舱位划分的生存情况进行同样的分析,但只针对年龄超过50岁的乘客。

print(
    pl.scan_csv(csv_file)
    .filter(pl.col("Age") > 50)
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
    .explain()
)

在这个例子中,查询优化器已经看到:

        1.现在需要12列中的4列 PROJECT 4/12 COLUMNS

        2.只应选择年龄超过50岁的乘客 SELECTION: [(col("Age")) > (50.0)]

查询评估

要评估完整的查询并输出一个DataFrame,我们调用collect方法。

(
    pl.scan_csv(csv_file)
    .filter(pl.col("Age") > 50)
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
    .collect()
)

在本课程的这一部分中,我们将更深入地了解懒加载模式(lazy mode)和查询评估(evaluating queries)。

流式处理大于内存的数据集

默认情况下,Polars在评估一个懒加载查询时,会将整个数据集读入内存。但是,如果你的数据集太大而无法放入内存,Polars可以在流式处理模式下运行许多操作。在流式处理模式下,Polars会分批次处理你的查询,而不是一次性处理所有内容。

要启用流式处理,我们向collect方法传递streaming = True参数。

(
    pl.scan_csv(csv_file)
    .filter(pl.col("Age") > 50)
    .group_by(["Survived","Pclass"])
    .agg(
        pl.col("PassengerId").count().alias("counts")
    )
    .collect(streaming = True)
)

在后面的文章中,我们将探讨流式处理可以用于哪些查询。

总结

本次教程简要概述了使Polars成为强大的数据分析工具的关键思想:

        1.表达式允许我们简洁地编写复杂的转换,并并行运行它们

        2.懒加载模式允许Polars应用查询优化,以减少内存使用和计算时间

        3.流式处理让我们能够使用Polars处理大于内存的数据集

 往期热门文章:

从 Pandas 到 Polars 二十六:在Polars中,不要遍历列

从 Pandas 到 Polars 二十三:如果你的数据已经排序,Polars可以为你提供助力

从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望

从 Pandas 到 Polars 十三:流式处理的关键参数

从 Pandas 到 Polars 十:“Polars 表达式“是什么?

从 Pandas 到 Polars 六:在 Polars 中流式处理大型数据集

从 Pandas 到 Polars 0:理解Polars嵌套列类型

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;