PySpark DataFrame 快速入门创建、查询、分组、读写、SQL 实战一篇讲透

1. 什么是 PySpark DataFrame?

PySpark DataFrame 是一种分布式的、带结构信息的数据集

你可以把它理解成:

一张运行在 Spark 上的"超级数据表"。

它和数据库中的表很像,也和 Pandas DataFrame 很像,因为它同样有:

  • 列名
  • 数据类型
  • 行数据
  • 表结构

但它和 Pandas 最大的区别在于:
PySpark DataFrame 是分布式的,数据并不一定都放在一台机器里,而是可以由 Spark 调度到多个节点一起计算。

例如,一个简单的 DataFrame 看起来像这样:

text 复制代码
+---+---+-------+----------+-------------------+
|  a|  b|      c|         d|                  e|
+---+---+-------+----------+-------------------+
|  1|2.0|string1|2000-01-01|2000-01-01 12:00:00|
|  2|3.0|string2|2000-02-01|2000-01-02 12:00:00|
|  3|4.0|string3|2000-03-01|2000-01-03 12:00:00|
+---+---+-------+----------+-------------------+

这种结构化形式,让数据处理比直接操作原始 RDD 更直观,也更贴近业务开发。

2. DataFrame 为什么这么重要?

在 Spark 生态里,DataFrame 之所以成为主流,不是偶然的。

它至少有 3 个非常明显的优势。

2.1 结构化更强

DataFrame 自带 schema,知道每一列叫什么、是什么类型,这让程序更可读、更容易维护。

2.2 性能更好

DataFrame 底层会交给 Spark 的优化器去生成执行计划,很多操作会自动优化,不需要开发者手工做太多底层控制。

2.3 更符合日常开发习惯

它既可以像表一样查询,也可以像链式 API 一样写代码,还能无缝切换到 SQL,所以非常适合日常数据开发。

简单说:

RDD 更底层,DataFrame 更实用。

3. DataFrame 最核心的特点:惰性执行

这是 PySpark DataFrame 最重要的概念之一。

所谓惰性执行(Lazy Evaluation),意思是:

当你对 DataFrame 做转换操作时,Spark 不会立刻执行,而是先把这些操作记录下来,等真正需要结果时再统一计算。

比如这句代码:

python 复制代码
df.select("a")

它不会马上计算,只是告诉 Spark:

后面我要取 a 这一列,你先记住。

而下面这些操作,才会真正触发计算:

  • show()
  • collect()
  • count()
  • take()
  • toPandas()

为什么 Spark 要这么设计?

因为这样可以:

  • 合并多个操作
  • 减少中间结果
  • 自动优化执行计划
  • 提高整体性能

你可以把它理解成:

平时先记账,最后统一结算。

这也是为什么有时候你写了很多行 DataFrame 代码,控制台却没动静,因为 Spark 还没开始真正执行。

4. PySpark 的入口:SparkSession

几乎所有 PySpark 程序,都是从 SparkSession 开始的。

python 复制代码
from pyspark.sql import SparkSession

spark = SparkSession.builder.getOrCreate()

这个 spark 可以理解成整个 PySpark 程序的总入口。

后面很多操作都依赖它:

  • 创建 DataFrame
  • 读取文件
  • 执行 SQL
  • 修改配置
  • 注册视图

如果你是用 pyspark 命令进入交互式环境,系统一般会自动帮你创建好 spark 变量。

所以很多示例代码都可以直接写:

python 复制代码
df = spark.createDataFrame(...)

5. DataFrame 怎么创建?

PySpark DataFrame 的创建方式很多,常见包括:

  • 从列表创建
  • 从元组创建
  • 从字典创建
  • Row 创建
  • 从 Pandas DataFrame 创建
  • 从 RDD 创建

下面看几种最常见的写法。

5.1 从 Row 列表创建

python 复制代码
from datetime import datetime, date
from pyspark.sql import Row

df = spark.createDataFrame([
    Row(a=1, b=2., c='string1', d=date(2000, 1, 1), e=datetime(2000, 1, 1, 12, 0)),
    Row(a=2, b=3., c='string2', d=date(2000, 2, 1), e=datetime(2000, 1, 2, 12, 0)),
    Row(a=3, b=4., c='string3', d=date(2000, 3, 1), e=datetime(2000, 1, 3, 12, 0))
])

这种写法字段名明确,可读性很好,适合教程和小规模示例。

5.2 显式指定 schema

python 复制代码
from datetime import datetime, date

df = spark.createDataFrame([
    (1, 2., 'string1', date(2000, 1, 1), datetime(2000, 1, 1, 12, 0)),
    (2, 3., 'string2', date(2000, 2, 1), datetime(2000, 1, 2, 12, 0)),
    (3, 4., 'string3', date(2000, 3, 1), datetime(2000, 1, 3, 12, 0))
], schema='a long, b double, c string, d date, e timestamp')

这种方式更推荐在工程场景使用,因为类型完全由你控制,避免自动推断出错。

5.3 从 Pandas DataFrame 创建

python 复制代码
import pandas as pd
from datetime import datetime, date

pandas_df = pd.DataFrame({
    'a': [1, 2, 3],
    'b': [2., 3., 4.],
    'c': ['string1', 'string2', 'string3'],
    'd': [date(2000, 1, 1), date(2000, 2, 1), date(2000, 3, 1)],
    'e': [datetime(2000, 1, 1, 12, 0), datetime(2000, 1, 2, 12, 0), datetime(2000, 1, 3, 12, 0)]
})

df = spark.createDataFrame(pandas_df)

这对于熟悉 Pandas 的同学非常友好,适合本地测试和快速验证。

5.4 自动推断 schema 的优缺点

如果不写 schema,PySpark 会自动推断字段类型。

优点是省事。

缺点是:

  • 小样本推断可能不准
  • 空值可能影响类型判断
  • 生产环境可控性较差

所以建议是:

学习时可省略,实战中尽量显式指定 schema。

6. 如何查看 DataFrame 数据?

创建完 DataFrame 后,第一件事通常就是先看看数据。

6.1 使用 show()

python 复制代码
df.show()

默认显示前 20 行。

只看 1 行可以这样写:

python 复制代码
df.show(1)

6.2 垂直显示

当字段很多、单行特别长时,可以改成垂直显示:

python 复制代码
df.show(1, vertical=True)

输出会更清晰:

text 复制代码
-RECORD 0------------------
 a   | 1
 b   | 2.0
 c   | string1
 d   | 2000-01-01
 e   | 2000-01-01 12:00:00

6.3 查看列名

python 复制代码
df.columns

6.4 查看 schema

python 复制代码
df.printSchema()

输出示例:

text 复制代码
root
 |-- a: long (nullable = true)
 |-- b: double (nullable = true)
 |-- c: string (nullable = true)
 |-- d: date (nullable = true)
 |-- e: timestamp (nullable = true)

6.5 查看摘要统计

python 复制代码
df.select("a", "b", "c").describe().show()

这一步很适合快速看字段的:

  • count
  • mean
  • stddev
  • min
  • max

7. 为什么 collect() 要慎用?

这是一个非常典型的新手坑。

7.1 collect() 的作用

python 复制代码
df.collect()

它会把整个 DataFrame 的数据全部拉回 Driver 端,变成 Python 本地对象。

看起来很方便,但问题也非常明显:

数据量一大,Driver 内存很可能直接爆掉。

因为它的本质就是:

  • 从各个 Executor 收集数据
  • 拉回 Driver
  • 放进本地 Python 进程里

7.2 更安全的替代方式

如果你只是想取几条数据,建议用:

python 复制代码
df.take(1)

或者:

python 复制代码
df.tail(1)

它们只取少量数据,更安全。

7.3 toPandas() 也要小心

python 复制代码
df.toPandas()

这同样会把全部数据拉回 Driver,再转成 Pandas DataFrame。

所以要记住一句话:

collect() 和 toPandas() 在小数据时很好用,在大数据时很危险。

8. DataFrame 中的列到底是什么?

这是理解 PySpark 的一个关键点。

很多初学者以为:

python 复制代码
df.a

拿到的是 a 列的数据。

其实不是。

它返回的是一个 Column 对象,也就是"列的表达式"。

python 复制代码
df.a
# Column<'a'>

这意味着什么?

意味着在 PySpark 里,很多列操作本质上都不是在立刻算值,而是在构造表达式。

例如:

python 复制代码
from pyspark.sql.functions import upper

upper(df.c)
df.c.isNull()

这些操作返回的仍然是 Column

你可以把它理解成:

Column 不是值本身,而是后续计算的描述。

9. 如何选择列、添加列、过滤数据?

这部分是日常开发的高频操作。

9.1 选择列

python 复制代码
df.select(df.c).show()

9.2 添加新列

python 复制代码
from pyspark.sql.functions import upper

df.withColumn("upper_c", upper(df.c)).show()

这会新增一列 upper_c,值是 c 列的大写版本。

withColumn() 特别常用,常见场景包括:

  • 新增派生字段
  • 类型转换
  • 数据清洗
  • 字符串、时间处理

9.3 过滤行

python 复制代码
df.filter(df.a == 1).show()

这和 SQL 里的 WHERE 非常像。

10. 如何在 PySpark 中应用 Python 函数?

PySpark 支持多种让你使用 Python 原生逻辑的方式,其中比较常见的是:

  • pandas_udf
  • mapInPandas
  • applyInPandas

10.1 pandas_udf

python 复制代码
import pandas as pd
from pyspark.sql.functions import pandas_udf

@pandas_udf("long")
def pandas_plus_one(series: pd.Series) -> pd.Series:
    return series + 1

df.select(pandas_plus_one(df.a)).show()

这个函数适合列级处理。

你可以把它理解为:

Spark 把某一列分批交给 Pandas Series 处理,再把结果拼回来。

10.2 mapInPandas

python 复制代码
def pandas_filter_func(iterator):
    for pandas_df in iterator:
        yield pandas_df[pandas_df.a == 1]

df.mapInPandas(pandas_filter_func, schema=df.schema).show()

mapInPandas() 更灵活,因为它处理的是 Pandas DataFrame,而不是单列 Series。

适合:

  • 多列联合处理
  • 输出行数不固定
  • 复杂转换逻辑

11. DataFrame 如何分组聚合?

分组聚合是数据分析的核心场景之一。

先构造一份示例数据:

python 复制代码
df = spark.createDataFrame([
    ['red', 'banana', 1, 10],
    ['blue', 'banana', 2, 20],
    ['red', 'carrot', 3, 30],
    ['blue', 'grape', 4, 40],
    ['red', 'carrot', 5, 50],
    ['black', 'carrot', 6, 60],
    ['red', 'banana', 7, 70],
    ['red', 'grape', 8, 80]
], schema=['color', 'fruit', 'v1', 'v2'])

11.1 groupBy 聚合

python 复制代码
df.groupby("color").avg().show()

输出类似:

text 复制代码
+-----+-------+-------+
|color|avg(v1)|avg(v2)|
+-----+-------+-------+
|  red|    4.8|   48.0|
|black|    6.0|   60.0|
| blue|    3.0|   30.0|
+-----+-------+-------+

这就是典型的 split-apply-combine 思路。

11.2 applyInPandas 对每组做处理

python 复制代码
def plus_mean(pandas_df):
    return pandas_df.assign(v1=pandas_df.v1 - pandas_df.v1.mean())

df.groupby("color").applyInPandas(plus_mean, schema=df.schema).show()

这表示每个颜色分组内部,把 v1 减去该组平均值。

这种方式在特征工程、归一化、复杂统计处理里很有用。

12. DataFrame 如何读写数据?

Spark 的一个核心用途,就是把数据读进来、处理完、再写出去。

常见格式有:

  • CSV
  • Parquet
  • ORC

12.1 CSV

写出:

python 复制代码
df.write.csv("foo.csv", header=True)

读入:

python 复制代码
spark.read.csv("foo.csv", header=True).show()

CSV 的优点是:

  • 通用
  • 可读
  • 易交换

缺点是:

  • 存储效率低
  • 读取性能一般
  • 类型信息不够强

12.2 Parquet

写出:

python 复制代码
df.write.parquet("bar.parquet")

读入:

python 复制代码
spark.read.parquet("bar.parquet").show()

Parquet 是 Spark 生态里非常常见的列式存储格式,优点包括:

  • 压缩率高
  • 读取快
  • 更适合分析型查询

12.3 ORC

写出:

python 复制代码
df.write.orc("zoo.orc")

读入:

python 复制代码
spark.read.orc("zoo.orc").show()

ORC 和 Parquet 类似,也是一种高效列式存储格式。

13. DataFrame 与 SQL 为什么能无缝切换?

这是 Spark 非常强的一点。

因为 DataFrame 和 Spark SQL 共享同一套执行引擎,所以你既可以用 API,也可以写 SQL,而且二者可以自由切换。

13.1 注册临时视图

python 复制代码
df.createOrReplaceTempView("tableA")

13.2 执行 SQL

python 复制代码
spark.sql("SELECT count(*) FROM tableA").show()

所以你完全可以:

  • 先用 DataFrame 做清洗
  • 再用 SQL 做聚合
  • 或者反过来

这在复杂业务场景里特别实用。

13.3 UDF 也能注册到 SQL 中

python 复制代码
import pandas as pd
from pyspark.sql.functions import pandas_udf

@pandas_udf("integer")
def add_one(s: pd.Series) -> pd.Series:
    return s + 1

spark.udf.register("add_one", add_one)
spark.sql("SELECT add_one(v1) FROM tableA").show()

这说明 Spark 里的 API、SQL、UDF 并不是三套割裂体系,而是彼此互通的。

13.4 SQL 表达式也能直接给 DataFrame 用

python 复制代码
from pyspark.sql.functions import expr

df.selectExpr("add_one(v1)").show()
df.select(expr("count(*)") > 0).show()

这意味着你写 PySpark 时,可以非常灵活地混合使用 API 和 SQL 风格。

14. 初学者必须记住的 6 个关键点

学完这篇之后,建议你把下面 6 句话记住。

14.1 DataFrame 是结构化分布式数据集

它像数据库表,也像 Pandas,但底层是 Spark 的分布式计算能力。

14.2 DataFrame 是惰性执行的

很多转换操作不会立刻计算,只有 Action 才会真正触发执行。

14.3 SparkSession 是入口

没有 spark,后面的 DataFrame 操作几乎无从开始。

14.4 collect() 和 toPandas() 要慎用

它们会把数据拉回 Driver,大数据场景很容易 OOM。

14.5 Column 不是值,而是表达式

df.a 返回的是列对象,而不是某一列的实际值集合。

14.6 DataFrame 和 SQL 是互通的

它们共用执行引擎,可以自由切换、混合使用。

15. 一个完整的入门示例

下面给你一个很适合新手练手的完整示例:

python 复制代码
from pyspark.sql import SparkSession
from pyspark.sql.functions import upper

spark = SparkSession.builder.appName("DataFrameQuickstart").getOrCreate()

data = [
    (1, "alice", 18),
    (2, "bob", 20),
    (3, "cindy", 22)
]

df = spark.createDataFrame(data, ["id", "name", "age"])

print("=== 原始数据 ===")
df.show()

print("=== Schema ===")
df.printSchema()

print("=== 选择 name 列 ===")
df.select("name").show()

print("=== 新增大写列 ===")
df.withColumn("name_upper", upper(df.name)).show()

print("=== 过滤 age >= 20 ===")
df.filter(df.age >= 20).show()

print("=== 注册临时视图并执行 SQL ===")
df.createOrReplaceTempView("users")
spark.sql("SELECT id, name FROM users WHERE age >= 20").show()

spark.stop()

这个例子串起了最常见的一套流程:

  • 创建 DataFrame
  • 查看数据
  • 查看 schema
  • 选择列
  • 新增列
  • 过滤数据
  • 注册视图
  • 执行 SQL

新手把这段代码跑通,基本就摸到 PySpark DataFrame 的门了。

相关推荐
qq_417695051 小时前
Python深度学习入门:TensorFlow 2.0/Keras实战
jvm·数据库·python
只能是遇见2 小时前
ERROR 1524 (HY000) Plugin ‘mysql_native_password‘ is not loaded
android·数据库·mysql
番茄去哪了2 小时前
从0到1独立开发一个论坛项目(一)
java·数据库·oracle·maven
API开发2 小时前
一个MCP操作所有的数据库
数据库·api·api接口·apisql·mcp·mcpserver·openclaw
zone7_2 小时前
008-01:RAG 入门-向量存储与企业级向量数据库 milvus
数据库·milvus
iMingzhen2 小时前
不想引入 Redis,我用一张 SQLite 表实现了消息队列
数据库·redis·ai·sqlite
冷小鱼2 小时前
Milvus 向量数据库完全指南:开源架构与生产级部署实战
数据库·开源·milvus
Curvatureflight2 小时前
Redis实战:缓存设计与高频场景全解析
数据库·redis·缓存
1688red2 小时前
基于Canal实现MySQL到Elasticsearch的数据同步
数据库·mysql·elasticsearch