专栏名称: Python编程
人生苦短,我用 Python !关注 Python 编程技术和运用。分享 Python 相关技术文章、开发工具资源、热门信息等。
目录
相关文章推荐
51好读  ›  专栏  ›  Python编程

超强 Pandas 平替 -- Polars

Python编程  · 公众号  ·  · 2024-06-04 09:36

正文

本文来源:网络

Polars是一个用于操作结构化数据的高性能DataFrame库,可以说是平替pandas最有潜质的包。Polars其核心部分是用Rust编写的,但该库也提供了Python接口。它的主要特点包括:

  • 快速: Polars是从零开始编写的,紧密与机器结合,没有外部依赖。

  • I/O: 对所有常见数据存储层提供一流支持:本地、云存储和数据库。

  • 易于使用: 以原始意图编写查询。Polars 在内部会使用其查询优化器确定执行最有效的方式。

  • 离线处理: Polars支持通过其流式API进行离线数据转换。这使您能够处理结果,而无需同时将所有数据存储在内存中。

  • 并行处理: Polars通过在可用的CPU核心之间分配工作负载,充分利用计算机性能,而无需额外配置。

  • 矢量化查询引擎: Polars使用 Apache Arrow,一种列式数据格式,以矢量化方式处理查询。它使用 SIMD 来优化CPU使用。

User guide: https://pola-rs.github.io/polars/user-guide/
API reference: https://pola-rs.github.io/polars/py-polars/html/reference/io.html

介绍

Polars 的目标是提供一个闪电般快速的 DataFrame 库,具有以下特点:

  • 利用计算机上所有可用的核心。

  • 通过优化查询来减少不必要的工作/内存分配。

  • 处理比可用 RAM 更大得多的数据集。

  • 具有一致且可预测的 API。

  • 具有严格的模式(在运行查询之前应该知道数据类型)。

Polars 是用 Rust 编写的,这使得它具有 C/C++ 性能,并允许它完全控制查询引擎中的性能关键部分。因此,Polars 为此付出了很大的努力:

  • 减少冗余的复制。

  • 高效地遍历内存缓存。

  • 在并行性中最小化争用。

  • 以块处理数据。

  • 重用内存分配。

# 1. 基础

Series & DataFrames

Series 是一个一维数据结构。在一个 Series 中,所有元素都具有相同的数据类型(例如,整数、字符串)。下面的片段展示了如何创建一个简单的带有名称的 Series 对象。

import polars as pl
import numpy as np

s = pl.Series("a", [1, 2, 3, 4, 5])
print(s)
s = pl.Series("a", [1, 2, 3, 4, 5])
print(s.min())
print(s.max())
s = pl.Series("a", ["polar""bear""arctic""polar fox""polar bear"])
s2 = s.str.replace("polar""pola")
print(s2)
from datetime import date

start = date(2001, 1, 1)
stop = date(2001, 1, 9)
s = pl.date_range(start, stop, interval="2d", eager=True)
print(s.dt.day())

DataFrame 是一个二维数据结构,由一个或多个 Series 支持,可以看作是对一系列(例如列表)Series的抽象。在 DataFrame 上可以执行的操作与在 SQL 查询中执行的操作非常相似。您可以进行 GROUP BY、JOIN、PIVOT,还可以定义自定义函数。

from datetime import datetime

df = pl.DataFrame(
    {
        "integer": [12345],
        "date": [
            datetime(202211),
            datetime(202212),
            datetime(202213),
            datetime(202214),
            datetime(202215),
        ],
        "float": [4.05.06.07.08.0],
    }
)

print(df)
print(df.head(3))
print(df.describe())

Reading & writing

import polars as pl
from datetime import datetime

df = pl.DataFrame(
    {
        "integer" : [123],
        "date": [
            datetime(202211),
            datetime(202212),
            datetime(202213),
        ],
        "float": [4.05.06.0],
    }
)

print(df)
df.write_csv("output.csv")
df_csv = pl.read_csv("output.csv")
print(df_csv)
df_csv = pl.read_csv("output.csv", try_parse_dates=True)
print(df_csv)

Expressions

import polars as pl

# 创建一个简单的 DataFrame
data = {'column1': [123],
        'column2': ['a''b''c']}
df = pl.DataFrame(data)

# 使用表达式进行选择
selected_df = df.select(['column1'])

# 使用表达式进行过滤
filtered_df = df.filter(df['column1'] > 1)
selected_df
filtered_df

# 2. 拼接

df = pl.DataFrame(
    {
        "a": np.arange(0, 8),
        "b": np.random.rand(8),
        "d": [1, 2.0, np.NaN, np.NaN, 0, -5, -42, None],
    }
)

df2 = pl.DataFrame(
    {
        "x": np.arange(0, 8),
        "y": ["A""A""A""B""B""C""X""X"],
    }
)
joined = df.join(df2, left_on="a", right_on="x")
print(joined)
stacked = df.hstack(df2)
print(stacked)

# 3. 概念

Data types

Polars 完全基于 Arrow 数据类型,并由 Arrow 内存数组支持。这使得数据处理在缓存效率和跨进程通信方面得到良好支持。大多数数据类型都与 Arrow 的实现完全一致,但有一些例外 ,如 Utf8 (实际上是 LargeUtf8 )、 Categorical Object (支持有限)等。以下是一些数据类型:

分组 类型 详细信息
数值 Int8 8 位有符号整数。

Int16 16 位有符号整数。

Int32 32 位有符号整数。

Int64 64 位有符号整数。

UInt8 8 位无符号整数。

UInt16 16 位无符号整数。

UInt32 32 位无符号整数。

UInt64 64 位无符号整数。

Float32 32 位浮点数。

Float64 64 位浮点数。
嵌套 Struct 结构数组表示为 Vec ,用于在单个列中打包多个/异构值。

List 列表数组包含包含列表值的子数组和一个偏移数组(在内部实际上是 Arrow 的 LargeList )。
时间 Date 日期表示,内部表示为距离 UNIX 纪元的天数,由 32 位有符号整数编码。

Datetime 日期时间表示,内部表示为距离 UNIX 纪元的微秒数,由 64 位有符号整数编码。

Duration 表示时间差的类型,内部表示为微秒。通过减去 Date/Datetime 创建。

Time 时间表示,内部表示为距午夜的纳秒数。
其他 Boolean 布尔类型,实际上是按位打包的。

Utf8 字符串数据(实际上在内部是 Arrow 的 LargeUtf8 )。

Binary 以字节形式存储数据。

Object 有限支持的数据类型,可以是任何值。

Categorical 一组字符串的分类编码。

Contexts

Polars 已经开发了自己的领域特定语言(DSL)用于数据转换。该语言非常易于使用,允许进行复杂的查询,同时保持人类可读性。该语言的两个核心组件是上下文(Contexts)和表达式(Expressions),我们将在下一部分介绍表达式。

正如名称所示,上下文指的是需要评估表达式的上下文。有三个主要的上下文 [^1^]:

  1. 选择(Selection): df.select([..]) , df.with_columns([..])

  2. 过滤(Filtering): df.filter()

  3. 分组/聚合(Group by / Aggregation): df.group_by(..).agg([..])

df = pl.DataFrame(
    {
        "nrs": [123, None, 5],
        "names": ["foo""ham""spam""egg", None],
        "random": np.random.rand(5),
        "groups": ["A""A""B""C""B"],
    }
)
print(df)
# 基于df 进行计算,得到新的表格
out = df.select(
    pl.sum("nrs"), # nrs的和
    pl.col("names").sort(), # names排序后的结果
    pl.col("names").first().alias("first name"), # names第一个值
    (pl.mean("nrs") * 10).alias("10xnrs"), # nrs的均值 * 10
)
print(out)
# 原始df 新增列
df = df.with_columns(
    pl.sum("nrs").alias("nrs_sum"),
    pl.col("random").count().alias("count"),
)
print(df)
out = df.filter(pl.col("nrs") > 2)
print(out)
out = df.group_by("groups").agg(
    pl.sum("nrs"),  # sum nrs by groups
    pl.col("random").count().alias("count"),  # count group members
    # sum random where name != null
    pl.col("random").filter(pl.col("names").is_not_null()).sum().name.suffix("_sum"),
    pl.col("names").reverse().alias("reversed names"),
)
print(out)

Lazy / eager API

Polars支持两种操作模式:lazy(延迟)和eager(即时)。在eager API中,查询会立即执行,而在lazy API中,查询只有在“需要”时才会被评估。

!wget https://mirror.coggle.club/dataset/heart.csv
!head heart.csv
df = pl.read_csv("heart.csv")
df_small = df.filter(pl.col("age") > 5)
df_agg = df_small.group_by("sex").agg(pl.col("chol").mean())
print(df_agg)
q = (
    pl.scan_csv("heart.csv")
    .filter(pl.col("age") > 5)
    .group_by("sex")
    .agg(pl.col("chol").mean())
)
# 生成了计算逻辑

df = q.collect() # 真正计算
print(df)

Streaming API

https://pola-rs.github.io/polars/user-guide/concepts/streaming/

Lazy API的另一个额外好处是它允许以流式方式执行查询。与一次性处理所有数据不同,Polars可以按批执行查询,使您能够处理大于内存的数据集。

q = (
    pl.scan_csv("heart.csv" )
    .filter(pl.col("age") > 5)
    .group_by("sex")
    .agg(pl.col("chol").mean())
)

df = q.collect(streaming=True)
print(df)

# 4. 表达式

Basic operators

df = pl.DataFrame(
    {
        "nrs": [1, 2, 3, None, 5],
        "names": ["foo""ham""spam""egg", None],
        "random": np.random.rand(5),
        "groups": ["A""A""B""C""B"],
    }
)
print(df)
df_numerical = df.select(
    (pl.col("nrs") + 5).alias("nrs + 5"),
    (pl.col("nrs") - 5).alias("nrs - 5"),
    (pl.col("nrs") * pl.col("random")).alias("nrs * random"),
    (pl.col("nrs") / pl.col("random")).alias("nrs / random"),
)
print(df_numerical)
df_logical = df.select(
    (pl.col("nrs") > 1).alias("nrs > 1"),
    (pl.col("random") <= 0.5).alias("random <= .5"),
    (pl.col("nrs") != 1).alias("nrs != 1"),
    (pl.col("nrs") == 1).alias("nrs == 1"),
    ((pl.col("random") <= 0.5) & (pl.col("nrs") > 1)).alias("and_expr"),  # and
    ((pl.col("random") <= 0.5) | (pl.col("nrs") > 1)).alias("or_expr"),  # or
)
print(df_logical)

Column selections

from datetime import date, datetime

df = pl.DataFrame(
    {
        "id": [942],
        "place": ["Mars""Earth""Saturn"],
        "date": pl.date_range(date(202211), date(202213), "1d", eager=True),
        "sales": [33.42142134.144.7],
        "has_people": [False, True, False],
        "logged_at": pl.datetime_range(
            datetime(2022121), datetime(2022121002), "1s", eager=True
        ),
    }
).with_row_count("rn")
print(df)
out = df.select(pl.col("*"))

# Is equivalent to
out = df.select(pl.all())
print(out)
out = df.select






请到「今天看啥」查看全文