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 的门了。

相关推荐
科技小花2 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸2 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain2 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希3 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神3 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员3 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java3 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿3 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴4 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU4 小时前
三大范式和E-R图
数据库