Polars 行列选择与数据转换实战:从基础到高效数据处理

发布时间:2026/8/18 3:06:01
Polars 行列选择与数据转换实战:从基础到高效数据处理 最近在数据分析项目中处理大规模数据集时你是否也遇到过这样的困扰使用 Pandas 处理几百万行的数据时内存占用飙升运行速度缓慢尤其是在进行复杂的行列筛选和类型转换时等待时间让人焦虑。如果你正在寻找一个高性能的替代方案那么 Polars 绝对是你的不二之选。作为一个用 Rust 编写的数据处理库Polars 凭借其惰性执行、多线程和查询优化在处理海量数据时性能远超 Pandas。本文将聚焦于 Polars 中最核心、最常用的操作之一行列选择与数据转换。无论你是从 Pandas 迁移过来还是初次接触 Polars掌握这些操作都是高效进行数据清洗、特征工程和分析的前提。我们将从基础概念讲起通过大量可运行的代码示例详细拆解如何精准地选择行与列以及如何灵活地进行各种数据转换。文章内容涵盖从环境搭建、基础语法到实战案例和性能优化的完整闭环确保新手能看懂有经验的开发者也能快速找到高效的使用技巧和避坑指南。1. Polars 核心概念与为何选择它在深入行列选择与转换之前我们有必要先理解 Polars 的设计哲学和核心优势这能帮助我们更好地运用其特性。Polars 是什么Polars 是一个用于数据处理的快速 DataFrame 库使用 Rust 语言编写并通过 PyO3 为 Python 提供了接口。它提供了两种执行模式惰性Lazy和即时Eager。惰性模式是 Polars 性能强大的关键它允许构建一个查询计划并在最后时刻进行优化和执行避免了不必要的中间计算。与 Pandas 的核心区别内存与性能Polars 的内存效率更高默认使用 Apache Arrow 列式内存格式并且原生支持多线程并行计算。对于大型数据集其速度通常是 Pandas 的数倍甚至数十倍。API 设计Polars 的 API 更倾向于函数式编程链式调用非常流畅。它的表达式Expression系统是其灵魂几乎所有数据转换操作都通过表达式完成这使得代码更简洁且易于优化。惰性求值Polars LazyFrame 可以构建复杂的查询计划并进行全局优化如谓词下推、投影下推最后再执行这在处理多步转换时优势巨大。行列选择与转换在数据流程中的位置在任何数据分析项目中数据加载后的第一步往往是查看和筛选数据选择行/列然后是根据业务逻辑进行清洗和转换类型转换、计算新列等。因此熟练掌握 Polars 的行列选择与转换是构建高效数据管道的基石。2. 环境准备与数据概览为了确保示例代码可以复现我们首先需要搭建环境并创建一个示例 DataFrame。2.1 安装 Polars通过 pip 可以轻松安装 Polars。建议同时安装pyarrow以支持更丰富的功能。pip install polars pyarrow2.2 创建示例 DataFrame我们将创建一个包含多种数据类型的 DataFrame用于后续演示。import polars as pl import numpy as np # 创建一个示例 DataFrame df pl.DataFrame({ “id”: list(range(1, 11)), # 整数列 “name”: [“Alice”, “Bob”, “Charlie”, “David”, “Eve”, “Frank”, “Grace”, “Henry”, “Ivy”, “Jack”], # 字符串列 “department”: [“HR”, “Engineering”, “Engineering”, “HR”, “Marketing”, “Engineering”, “HR”, “Marketing”, “Engineering”, “Sales”], “salary”: [50000, 85000, 82000, 48000, 60000, 90000, 52000, 75000, 88000, 65000], # 整数列 “join_date”: [“2020-01-15”, “2019-03-22”, “2021-07-30”, “2020-11-11”, “2022-02-28”, “2018-05-17”, “2021-09-01”, “2022-06-10”, “2019-12-05”, “2023-01-20”], # 字符串日期 “performance_score”: [3.8, 4.5, 4.2, 3.5, 4.0, 4.7, 3.9, 4.1, 4.6, 3.7] # 浮点数列 }) print(“原始 DataFrame:”) print(df) print(f“\nShape: {df.shape}”) print(f“Schema: {df.schema}”)运行上述代码你将看到一个包含 10 行、6 列的 DataFrame并了解其结构Schema。这是我们的“实验田”接下来所有操作都基于它进行。3. 核心操作一列Column的选择选择特定的列是数据分析中最常见的操作。Polars 提供了多种灵活的方式。3.1 选择单列或多列使用select方法并传入列名字符串或列表。# 选择单列 single_col df.select(“name”) print(“选择单列 ‘name’:”) print(single_col) # 选择多列 multi_cols df.select([“name”, “department”, “salary”]) print(“\n选择多列 [‘name’, ‘department’, ‘salary’]:”) print(multi_cols)3.2 使用pl.col对象进行高级选择pl.col是一个表达式对象它提供了更强大的列选择和能力可以用于后续的转换操作。# 使用 pl.col 选择列 cols_by_expr df.select(pl.col(“name”), pl.col(“salary”)) print(“使用 pl.col 选择列:”) print(cols_by_expr) # 选择所有列虽然不常用但可以这样写 all_cols df.select(pl.col(“*”)) print(“\n选择所有列:”) print(all_cols) # 选择列名包含特定字符串的列例如包含 ‘date’ 的列 date_cols df.select(pl.col(“*”).name.contains(“date”)) print(“\n选择列名包含 ‘date’ 的列:”) print(date_cols)3.3 按数据类型选择列在数据清洗时我们常常需要统一处理某一类型的所有列例如将所有整数列转换为浮点数。# 选择所有整数类型的列 int_cols df.select(pl.col(pl.Int64)) print(“选择所有整数类型 (Int64) 的列:”) print(int_cols) # 选择所有数值类型的列整数和浮点数 numeric_cols df.select(pl.col(pl.NUMERIC_DTYPES)) print(“\n选择所有数值类型的列:”) print(numeric_cols)3.4 列的重命名与重排序选择列的同时我们经常需要重命名或调整顺序。# 选择列并重命名 renamed df.select( pl.col(“id”).alias(“employee_id”), # 将 ‘id’ 重命名为 ’employee_id’ pl.col(“name”).alias(“full_name”), pl.col(“salary”) ) print(“选择并重命名列:”) print(renamed) # 调整列的顺序 reordered df.select([“name”, “id”, “salary”, “department”]) print(“\n调整列的顺序:”) print(reordered)4. 核心操作二行Row的选择与过滤过滤行是基于条件筛选数据子集的关键操作。Polars 使用filter方法并配合强大的表达式系统。4.1 基础条件过滤使用比较运算符 (,,,!,,) 和逻辑运算符 (表示 AND,|表示 OR,~表示 NOT)。# 筛选薪资大于 80000 的员工 high_salary df.filter(pl.col(“salary”) 80000) print(“薪资大于 80000 的员工:”) print(high_salary) # 筛选 Engineering 部门且薪资大于 80000 的员工 eng_high df.filter((pl.col(“department”) “Engineering”) (pl.col(“salary”) 80000)) print(“\nEngineering 部门且薪资大于 80000 的员工:”) print(eng_high) # 筛选部门是 HR 或 Marketing 的员工 hr_market df.filter(pl.col(“department”).is_in([“HR”, “Marketing”])) print(“\n部门是 HR 或 Marketing 的员工:”) print(hr_market) # 筛选绩效分数不是最高分假设最高为5的员工 not_top_performer df.filter(pl.col(“performance_score”) ! 5.0) print(“\n绩效分数不等于 5.0 的员工:”) print(not_top_performer)4.2 处理缺失值与空值在实际数据中处理缺失值NaN/Null是必不可少的。# 假设我们有一个包含缺失值的 DataFrame df_with_nulls pl.DataFrame({ “A”: [1, 2, None, 4], “B”: [“x”, None, “z”, “w”], “C”: [1.1, 2.2, 3.3, None] }) print(“包含缺失值的 DataFrame:”) print(df_with_nulls) # 筛选某列不为空的记录 non_null_a df_with_nulls.filter(pl.col(“A”).is_not_null()) print(“\nA 列不为空的记录:”) print(non_null_a) # 筛选所有列都不为空的记录完全无缺失的记录 complete_rows df_with_nulls.filter(pl.all().is_not_null()) print(“\n所有列都不为空的记录:”) print(complete_rows)4.3 基于字符串模式的过滤对于文本列可以使用字符串方法进行模糊匹配。# 筛选名字以 ‘A’ 开头的员工 name_starts_with_a df.filter(pl.col(“name”).str.starts_with(“A”)) print(“名字以 ‘A’ 开头的员工:”) print(name_starts_with_a) # 筛选名字中包含 ‘a’不区分大小写的员工 # 先转换为小写再比较 name_contains_a df.filter(pl.col(“name”).str.to_lowercase().str.contains(“a”)) print(“\n名字中包含字母 ‘a’ 的员工:”) print(name_contains_a)4.4 切片与按索引选择行虽然 Polars 不推荐像 Pandas 那样依赖隐式整数索引它更强调显式数据但仍然支持按行位置选择。# 选择前 3 行 first_three df.slice(0, 3) # 从偏移量0开始取3行 print(“前 3 行:”) print(first_three) # 选择最后 2 行 last_two df.tail(2) print(“\n最后 2 行:”) print(last_two) # 选择第 3 到第 7 行不包含第7行即索引2到6 middle_rows df.slice(2, 5) print(“\n第 3 到第 7 行:”) print(middle_rows)5. 核心操作三数据转换Transformation数据转换是数据分析的核心包括类型转换、计算新列、分组聚合等。Polars 的表达式系统让这些操作异常高效和简洁。5.1 列的数据类型转换这是数据准备中最常见的转换之一。例如将字符串日期转换为日期类型或将整数转换为浮点数。# 查看原始 schema注意 join_date 是 Utf8 (字符串) print(“原始 Schema:”, df.schema) # 将 join_date 列从字符串转换为日期类型 df_with_date df.with_columns( pl.col(“join_date”).str.strptime(pl.Date, “%Y-%m-%d”).alias(“join_date”) ) print(“\n转换日期列后的 Schema:”, df_with_date.schema) print(df_with_date.select([“name”, “join_date”])) # 将 salary 列从整数转换为浮点数 df_with_float_salary df.with_columns( pl.col(“salary”).cast(pl.Float64).alias(“salary_float”) ) print(“\n将 salary 转换为浮点数:”) print(df_with_float_salary.select([“name”, “salary”, “salary_float”]))5.2 创建新的计算列使用表达式在with_columns方法中创建新列。这是 Polars 非常强大的特性。# 创建一个新列薪资等级 (salary 70000 为 ‘High’否则为 ‘Low’) df_with_grade df.with_columns( pl.when(pl.col(“salary”) 70000) .then(pl.lit(“High”)) .otherwise(pl.lit(“Low”)) .alias(“salary_grade”) ) print(“添加薪资等级列:”) print(df_with_grade.select([“name”, “salary”, “salary_grade”])) # 创建一个新列年薪 (假设为月薪*12这里用月薪代替) # 注意这里只是演示原数据是年薪。我们创建一个‘月薪’列。 df_with_monthly df.with_columns( (pl.col(“salary”) / 12).round(2).alias(“monthly_salary_approx”) ) print(“\n添加近似月薪列:”) print(df_with_monthly.select([“name”, “salary”, “monthly_salary_approx”])) # 复杂的表达式计算一个综合得分 df_with_score df.with_columns( (pl.col(“performance_score”) * 20 pl.col(“salary”) / 1000).alias(“composite_score”) ) print(“\n添加综合得分列:”) print(df_with_score.select([“name”, “performance_score”, “salary”, “composite_score”]))5.3 使用apply函数进行自定义转换虽然 Polars 的表达式已经非常强大但有时仍需要自定义 Python 函数。这时可以使用apply但要注意性能因为它会按行执行 Python 函数速度较慢。# 示例自定义一个函数根据部门生成部门代码 def dept_code(dept_name): code_map {“HR”: “D01”, “Engineering”: “D02”, “Marketing”: “D03”, “Sales”: “D04”} return code_map.get(dept_name, “D00”) # 使用 apply (注意对于大数据集尽量避免) df_with_code df.with_columns( pl.col(“department”).apply(dept_code).alias(“dept_code”) ) print(“使用 apply 添加部门代码:”) print(df_with_code.select([“name”, “department”, “dept_code”]))重要提示apply会逐行调用 Python 函数破坏了 Polars 的向量化优化在数据量大时性能很差。应优先使用 Polars 的内置表达式或map_elements如果必须使用自定义函数。5.4 条件替换与映射使用when-then-otherwise结构或replace进行条件替换。# 使用 when-then-otherwise 进行条件替换上面已演示 # 使用 replace 进行值映射 df_replaced df.with_columns( pl.col(“department”).replace({“HR”: “Human Resources”, “Engineering”: “Eng”}).alias(“dept_short”) ) print(“使用 replace 映射部门名称:”) print(df_replaced.select([“name”, “department”, “dept_short”]))6. 综合实战案例员工数据分析报告现在我们将所有知识融合到一个实战案例中。目标从一个“原始”的员工数据 DataFrame 开始通过一系列行列选择和转换操作生成一份简洁的分析报告。步骤 1加载并查看原始数据使用我们最初创建的df步骤 2数据清洗与增强将join_date转换为日期类型。计算员工在职天数假设当前日期为 ‘2023-12-31’。创建薪资等级。只保留我们关心的列。步骤 3按部门分析按部门分组计算平均薪资、最高绩效分、员工数量。筛选出平均薪资高于 70000 的部门。步骤 4输出最终报告# 步骤 1 2: 数据清洗与增强 from datetime import datetime current_date datetime(2023, 12, 31).date() cleaned_df ( df .with_columns( # 转换日期 pl.col(“join_date”).str.strptime(pl.Date, “%Y-%m-%d”).alias(“join_date”), # 计算在职天数 ((pl.lit(current_date) - pl.col(“join_date”)).dt.total_days()).alias(“days_in_company”), # 创建薪资等级 pl.when(pl.col(“salary”) 80000).then(“High”) .when(pl.col(“salary”) 60000).then(“Medium”) .otherwise(“Low”) .alias(“salary_band”) ) # 选择需要的列并重排序 .select([“id”, “name”, “department”, “join_date”, “days_in_company”, “salary”, “salary_band”, “performance_score”]) ) print(“ 清洗与增强后的员工数据 ) print(cleaned_df) # 步骤 3: 按部门分析 dept_summary ( cleaned_df .group_by(“department”) .agg([ pl.col(“name”).count().alias(“employee_count”), pl.col(“salary”).mean().round(2).alias(“avg_salary”), pl.col(“performance_score”).max().alias(“max_performance”), pl.col(“salary_band”).value_counts().alias(“band_distribution”) # 统计各薪资段人数 ]) .sort(“avg_salary”, descendingTrue) # 按平均薪资降序排序 ) print(“\n 部门汇总分析 ) print(dept_summary) # 步骤 4: 筛选高平均薪资部门 high_avg_dept dept_summary.filter(pl.col(“avg_salary”) 70000) print(“\n 平均薪资 70000 的部门 “) print(high_avg_dept.select([“department”, “employee_count”, “avg_salary”]))这个案例展示了如何将select、filter、with_columns、group_by、agg等操作流畅地链式调用形成一个完整的数据处理管道。7. 惰性执行Lazy Execution下的行列选择与转换惰性执行是 Polars 处理大规模数据的利器。它不会立即计算而是构建一个查询计划最终通过collect执行。# 创建 LazyFrame lazy_df df.lazy() # 构建一个复杂的惰性查询计划 lazy_query ( lazy_df .filter(pl.col(“department”).is_in([“Engineering”, “Marketing”])) # 过滤行 .select([“name”, “department”, “salary”, “performance_score”]) # 选择列 .with_columns( # 转换列 (pl.col(“salary”) * 1.1).round(0).alias(“salary_after_raise”) ) .filter(pl.col(“salary_after_raise”) 80000) # 再次过滤 .sort(“salary_after_raise”, descendingTrue) # 排序 ) print(“惰性查询计划未执行:”) print(lazy_query) # 执行查询计划 result lazy_query.collect() print(“\n执行惰性查询后的结果:”) print(result)惰性执行的优点在于Polars 可以优化整个计划比如将过滤条件filter下推到最早可能的位置减少中间数据量从而大幅提升性能。8. 常见问题与性能优化指南8.1 常见错误与排查问题现象可能原因解决方案ColumnNotFoundError列名拼写错误或列不存在。使用df.columns检查所有列名。注意大小写。ComputeError: dtype mismatch表达式中的数据类型不兼容例如将字符串与数字比较。使用df.schema检查列数据类型。在操作前使用cast进行类型转换。PanicException或 Rust 错误通常是由于数据问题如解析失败或 Polars 内部错误。检查输入数据是否有异常值如格式错误的日期。尝试升级 Polars 到最新版本。filter结果为空条件逻辑错误或使用了错误的运算符如而不是。仔细检查过滤条件。对于字符串比较使用。使用print(df.filter(condition))调试条件。select后列顺序不对select按照传入列表的顺序输出列。确保传入select的列名列表顺序符合你的要求。apply函数运行极慢对大数据集使用了逐行处理的apply。尽可能使用 Polars 内置表达式。如果逻辑复杂且必须用 Python考虑使用map_elements或对数据分块处理。8.2 性能优化最佳实践优先使用惰性执行对于多步数据处理流程始终从scan_csv、scan_parquet或.lazy()开始并在最后调用.collect()。这允许 Polars 进行全局优化。避免逐行操作坚决避免在大型 DataFrame 上使用apply。99% 的操作都可以用 Polars 的向量化表达式重写速度会快几个数量级。选择正确的数据类型使用最节省内存的数据类型。例如对于小的整数使用pl.Int8、pl.Int16而不是pl.Int64。对于分类字符串使用pl.Categorical类型。投影下推在惰性模式中尽早使用select只选取需要的列减少内存中流动的数据量。谓词下推在惰性模式中尽早使用filter过滤掉不需要的行同样是为了减少数据处理量。使用内置函数Polars 的字符串处理、日期时间处理等功能都非常强大且优化过总是优先使用它们而不是自己用 Python 实现。注意链式顺序虽然惰性执行会优化但将filter放在with_columns尤其是计算量大的之前在逻辑上更清晰且有时能帮助优化器。9. 从 Pandas 迁移的特别注意事项如果你熟悉 Pandas以下对比能帮助你更快适应 Polars选择列Pandas:df[[‘col1’, ‘col2’]]或df.loc[:, [‘col1’, ‘col2’]]Polars:df.select([‘col1’, ‘col2’])过滤行Pandas:df[df[‘salary’] 50000]Polars:df.filter(pl.col(‘salary’) 50000)创建新列Pandas:df[‘new_col’] df[‘col1’] df[‘col2’]Polars:df.with_columns((pl.col(‘col1’) pl.col(‘col2’)).alias(‘new_col’))索引Polars 没有 Pandas 那样的隐式整数索引概念。选择行主要靠filter条件或slice。df[0]这样的操作在 Polars 中不存在。就地修改Polars 的绝大多数操作如with_columns、select都是返回一个新的 DataFrame/LazyFrame而不是修改原对象。这符合函数式编程的不可变性原则有助于避免意外错误。掌握 Polars 的行列选择与转换你就拿到了高效处理数据的钥匙。关键在于从 Pandas 的“索引思维”转向 Polars 的“表达式思维”。开始时可能会觉得链式调用有些冗长但一旦习惯你会发现它的表达力更强且能更自然地转化为高性能的查询计划。建议你打开 Python 环境将本文的示例代码逐一运行并修改观察结果的变化。尝试用 Polars 重写你之前用 Pandas 写的某个数据清洗脚本亲自体验其性能提升。对于更复杂的操作如连接join、透视pivot、窗口函数等Polars 文档是极好的学习资源。记住在数据处理的道路上选择正确的工具并深入理解它能让你的工作效率事半功倍。