在现代数据科学领域,选择正确的工具往往决定了项目的成败。如果你正在处理大规模数据集,或者对 Pandas 的性能瓶颈感到沮丧,那么 Polars 无疑是一个值得你关注的替代方案。作为一个基于 Rust 构建的高性能 DataFrame 库,Polars 利用多线程和惰性求值技术,在处理海量数据时展现出了惊人的速度。
在日常的数据处理工作中,添加新列是一项极其频繁的操作。无论是进行数据清洗、特征工程,还是简单的业务逻辑计算,掌握如何灵活地向 DataFrame 中添加数据,都是精通 Polars 的必经之路。在这篇文章中,我们将像经验丰富的开发者一样,深入探讨向 Polars DataFrame 添加新列的各种实用方法,并融入 2026 年最新的开发理念和技术趋势。
准备工作
在开始之前,我们需要确保已经安装了 Polars 库。如果你还没有安装,可以使用 pip 命令轻松完成:
pip install polars
一旦安装完成,我们就可以导入库并开始探索了。在本指南中,我们主要使用 pl 作为 Polars 的别名。
创建一个基础 DataFrame
为了演示各种操作,让我们先创建一个包含姓名和年龄的简单 DataFrame。这将是我们在后续所有示例中操作的基础数据。
import polars as pl
# 定义一个简单的字典数据集
data = {
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 30, 35],
"city": ["New York", "Los Angeles", "Chicago"]
}
# 创建 DataFrame
df = pl.DataFrame(data)
print("原始 DataFrame:")
print(df)
输出结果:
原始 DataFrame:
shape: (3, 3)
┌─────────┬─────┬─────────────┐
│ name ┆ age ┆ city │
│ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str │
╞═════════╪═════╪═════════════╡
│ Alice ┆ 25 ┆ New York │
│ Bob ┆ 30 ┆ Los Angeles │
│ Charlie ┆ 35 ┆ Chicago │
└─────────┴─────┴─────────────┘
核心 API 解析:with_columns
在 Polars 中,with_columns 是我们要掌握的最重要的方法之一。正如其名,它允许我们在保留原有列的同时添加新列,这也是 Polars 推荐的不可变数据操作模式。让我们看看它能做什么。
方法 1:添加常量值列
最简单的场景是添加一个所有行都相同的常量值。这在添加默认标记、数据来源标识或默认分类时非常常见。
应用场景: 假设我们需要给每一行数据打上“2026年”的数据批次标签,或者给所有用户添加一个默认的“未验证”状态。
代码示例:
# 使用 pl.lit 创建一个字面量(常量),并使用 .alias 为新列命名
df_with_constant = df.with_columns(
pl.lit("Active").alias("status")
)
print("添加常量列后:")
print(df_with_constant)
代码解析:
- INLINECODEd3535fdb:这是 Polars 中的字面量表达式,它告诉 Polars 我们要插入的是一个固定的字符串值。你也可以用它来插入数字、布尔值或 INLINECODE5bb98935。
-
.alias("status"):这是 Polars 中非常重要的语法,用于给计算结果或值指定新的列名。
输出结果:
添加常量列后:
shape: (3, 4)
┌─────────┬─────┬─────────────┬────────┐
│ name ┆ age ┆ city ┆ status │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str │
╞═════════╪═════╪═════════════╪════════╡
│ Alice ┆ 25 ┆ New York ┆ Active │
│ Bob ┆ 30 ┆ Los Angeles ┆ Active │
│ Charlie ┆ 35 ┆ Chicago ┆ Active │
└─────────┴─────┴─────────────┴────────┘
方法 2:基于现有列进行计算
更常见的情况是,我们需要根据现有的数据生成新的特征。这在特征工程中尤为重要。
应用场景: 电商数据中,我们可能拥有“单价”和“数量”,需要计算“总价”;或者拥有“出生日期”,需要计算“年龄”。在我们的示例中,让我们计算每个人的“出生年份”,假设当前年份是 2026 年。
代码示例:
# 使用 pl.col("age") 选择现有的列,并进行数学运算
# 我们可以直接对列进行加减乘除操作
df_calculated = df.with_columns(
(2026 - pl.col("age")).alias("birth_year")
)
print("添加计算列后:")
print(df_calculated)
深入理解:
这里的核心是 INLINECODEf8399f3f,它构建了一个针对 age 列的表达式。Polars 的强大之处在于这些表达式可以在 Rust 端并行计算,速度非常快。当你执行 INLINECODE311a0048 时,Polars 会自动对整列进行向量化运算。
方法 3:从列表或 Series 中添加数据
有时,你的新数据可能来自 Python 的列表或外部的计算结果,而不是基于 DataFrame 内部的计算。
应用场景: 你可能刚刚调用了一个外部的机器学习模型 API,获得了一组预测分数,现在需要把这些分数作为一个新列合并回去。
代码示例:
# 假设我们从外部获得了这些分数
scores = [88.5, 92.0, 79.5]
# 使用 pl.Series 将列表转换为 Polars Series
# 注意:Series 的长度必须与 DataFrame 的行数完全一致,否则会报错
df_with_list = df.with_columns(
pl.Series("performance_score", scores)
)
print("从列表添加新列后:")
print(df_with_list)
常见错误提示:
- 形状不匹配: 如果你提供的列表长度与 DataFrame 的行数不一致,Polars 会抛出错误。这实际上是好事,它能防止数据对齐错误。在添加之前,务必使用
df.shape[0]检查行数,或者使用列表切片确保数据对齐。
2026技术视点:构建 AI 原生的特征工程流水线
在我们最近的一个项目中,我们开始意识到数据工程不仅仅是关于 ETL(抽取、转换、加载)。随着 2026 年Agentic AI(自主智能体)的兴起,DataFrame 操作正成为 AI Agent 的“双手”。当我们与 Cursor 或 Windsurf 这类现代 AI IDE 协作时,我们不仅是写代码,更是在定义数据契约。
让我们看一个更进阶的例子。假设我们要为一个预测房价的 Agent 准备数据。我们需要处理异常值,并添加一些风险控制列。这展示了如何将复杂的业务逻辑封装在 with_columns 中,这正是现代开发所追求的——声明式编程。
import polars.selectors as cs
# 模拟一个包含房价和面积的数据集
real_estate_data = {
"district": ["Downtown", "Suburb", "Downtown", "Suburb", "Rural"],
"area_sqft": [500, 1200, 480, 1500, 3000],
"price": [500000, 400000, 490000, 600000, 300000],
"transaction_date": ["2026-01-01", "2026-01-02", "2026-01-03", None, "2026-01-05"]
}
df_house = pl.DataFrame(real_estate_data)
# 这是一个典型的生产级列添加逻辑:
# 1. 计算单价
# 2. 处理可能的空值(Null Safety)
# 3. 添加基于多列的业务逻辑标签
df_enhanced = df_house.with_columns(
# 1. 安全的数学运算:处理除零错误和空值
(pl.col("price") / pl.col("area_sqft")).fill_null(0).alias("price_per_sqft"),
# 2. 动态标记:使用表达式进行多条件判断
pl.when(pl.col("district") == "Downtown")
.then(pl.lit("Tier 1"))
.when(pl.col("area_sqft") > 1000)
.then(pl.lit("Tier 2"))
.otherwise(pl.lit("Tier 3"))
.alias("investment_tier"),
# 3. 数据质量监控列:AI 调试的重要依据
pl.col("transaction_date").is_null().alias("is_data_missing")
)
print("AI 增强的特征视图:")
print(df_enhanced)
为什么这很重要?
在 2026 年,我们编写代码时要考虑到可观测性。通过添加 is_data_missing 这样的列,我们不仅得到了处理后的数据,还为下游的监控系统和 AI 模型提供了“元数据”,让系统能够自我诊断数据质量问题。
深度实战:处理复杂逻辑与结构化数据
随着数据结构变得越来越复杂,我们经常需要处理 JSON 或嵌套数据。Polars 在这方面表现得比以往任何时候都出色。让我们探讨一种在物联网数据流中常见的场景。
场景: 设备传感器上传的数据包含了状态信息,我们需要解析这些状态并计算设备的健康指数。
# 模拟 IoT 传感器数据
iot_data = {
"device_id": ["sensor_01", "sensor_02", "sensor_03"],
"status_raw": [
‘{"temp": 45, "vibration": 0.2}‘,
‘{"temp": 88, "vibration": 0.9}‘,
‘{"temp": 32, "vibration": 0.1}‘
],
"battery_level": [85, 12, 99]
}
df_iot = pl.DataFrame(iot_data)
# 我们将在一次 with_columns 调用中完成:
# 1. JSON 解析
# 2. 结构化字段提取
# 3. 跨类型计算(JSON字段 + 常规列)
df_iot_analytics = df_iot.with_columns(
# 提取 JSON 字段为结构体
pl.col("status_raw").str.json_decode().alias("parsed_status")
).with_columns(
# 从结构体中提取具体值,并与 battery_level 结合
pl.col("parsed_status").struct.field("temp").alias("temperature"),
pl.col("parsed_status").struct.field("vibration").alias("vibration"),
# 计算综合健康指数 (模拟复杂公式)
# 注意:这里混合了结构体字段和普通列
(pl.col("battery_level") * 0.5 - pl.col("parsed_status").struct.field("temp") * 0.1).alias("health_score")
)
print("IoT 数据分析结果:")
print(df_iot_analytics.select(["device_id", "temperature", "health_score"]))
在这个例子中,我们不仅展示了如何添加列,还展示了 Polars 处理半结构化数据的能力。这种能力在处理现代 API 返回的 JSON 数据时至关重要。
最佳实践与常见陷阱
通过上面的示例,我们已经掌握了大部分基础技能。但是,要成为真正的 Polars 专家,还需要了解一些最佳实践和潜在的陷阱。
#### 1. 不可变性与链式调用
你可能已经注意到,我们没有使用 INLINECODE95b5654c 这种赋值方式。这是因为 Polars 的 DataFrame 通常是不可变的。INLINECODEe19b584c 返回的是一个新的 DataFrame,而不是在原地修改旧的。虽然这看起来像是创建了很多副本,但 Polars 在底层通过“零拷贝”或“引用计数”技术优化了内存使用,所以在大多数情况下不用担心内存问题。
你可以像管道一样链式调用,这在 Pandas 中也是推荐的做法:
result = (df
.with_columns(pl.lit(1).alias("col1"))
.filter(pl.col("age") > 20)
.select(["name", "col1"])
)
#### 2. 处理空值
当你的数据中包含空值,或者你想插入空值时,使用 pl.lit(None) 非常有用。
# 添加一个全为 Null 的列,常用于后续填充
df_null = df.with_columns(
pl.lit(None, dtype=pl.Float64).alias("salary_estimate")
)
注意指定 dtype(数据类型),因为有时候 Polars 无法推断出你想要的 Null 类型。
#### 3. 性能优化:惰性求值
如果你处理的数据量达到了 GB 级别,仅仅使用 INLINECODEd50f5b19 可能还不够。你应该尝试使用 INLINECODEc77d8154。当你使用 INLINECODEdceacb5c 而不是 INLINECODE5463c3bb 时,Polars 不会立即执行操作,而是构建一个查询计划。当你使用 .collect() 时,它会优化整个计划并执行。这可以消除不必要的中间步骤,极大地提升速度。
# 懒加载模式的示例
lf = pl.scan_csv("large_dataset.csv")
# 定义操作(此时并未真正执行)
result_df = lf.with_columns(
(pl.col("price") * 1.1).alias("price_with_tax")
).filter(
pl.col("price_with_tax") > 100
).collect() # 只有在这里才会真正执行计算
前瞻性思考:Polars 与边缘计算
展望未来,随着边缘计算的普及,数据处理逻辑正在从中心服务器下沉到用户的设备端。这意味着我们需要更轻量、更高效的代码。Polars 的 Rust 内核使其非常适合打包到 WebAssembly (WASM) 或 PyAndroid 应用中。
当我们在添加新列时,我们实际上是在定义数据流的转换逻辑。想象一下,同样的 with_columns 代码,不仅可以在你的高性能服务器上运行,还可以直接在用户的浏览器中处理离线数据,而无需修改一行代码。这就是 2026 年“Write Once, Run Anywhere”的数据工程愿景。
总结
在这篇文章中,我们深入探讨了如何在 Polars DataFrame 中添加新列。我们涵盖了从简单的常量插入、基于现有列的数学运算、列表数据的合并,到复杂的条件逻辑、字符串处理以及 JSON 解析。
Polars 的语法设计得非常直观,特别是它的 INLINECODE51c9e67e 方法和表达式 API (INLINECODE6877f1f2, pl.lit),让我们能够像写自然语言一样描述数据操作。相比传统的迭代方法,使用这些原生表达式不仅能让你写出更易读的代码,还能充分利用 Rust 和多线程带来的极致性能。
下一步,建议你尝试使用 Polars 处理一些手头真实的数据集,尝试将你习惯的 Pandas 操作用 Polars 重新实现。随着你对 INLINECODE1f30f8ea、INLINECODE57ed920e 和 join 等操作越来越熟悉,你会发现 Polars 是一个既强大又优雅的数据处理利器,更是构建下一代 AI 应用的坚实基础。