1、DataFrame
的组成
DataFrame
是一个二维表结构,那么表格结构就有无法绕开的三个点:
- 行
- 列
- 表结构描述
在MySQL
中的一张表:
- 由许多行组成
- 数据也被分成多个列
- 表也有表结构信息(列、列名、列类型、列约束等)
基于这个前提,DataFrame
的组成如下:
-
在结构层面:
StructType
对象描述整个DataFrame
的表结构StructField
对象描述一个列的信息
-
在数据层面
Row
对象记录一行数据Column
对象记录一列数据并包含列的信息
示例
如图, 在表结构层面,DataFrame
的表结构由:StructType
描述,如下图
一个StructField
记录:列名、列类型、列是否运行为空
多个StructField
组成一个StructType
对象。
一个StructType
对象可以描述一个DataFrame
:有几个列、每个列的名字和类型、每个列是否为空
同时,一行数据描述为Row
对象,如Row(1, 张三, 11)
一列数据描述为Column
对象,Column
对象包含一列数据和列的信息;Row
、Column
、StructType
、StructField
的编程我们在后面编码阶段会接触。
2、DataFrame
的代码构建
DataFrame
对象可以从RDD
转换而来,都是分布式数据集,其实就是转换一下内部存储的结构,转换为二维表结构。
2.1、基于RDD
方式
2.1.1、调用Spark
# 首先构建一个RDD rdd[(name, age), ()]
rdd = sc.textFile("../data/sql/people.txt").
map(lambda x: x.split(',')).
map(lambda x: [x[0], int(x[1])]) # 需要做类型转换, 因为类型从RDD中探测
# 构建DF方式1
df = spark.createDataFrame(rdd, schema = ['name', 'age'])
通过SparkSession
对象的createDataFrame
方法来将RDD
转换为DataFrame
;这里只传入列名称,类型从RDD
中进行推断,是否允许为空默认为允许(True
)。
# coding:utf8
# 演示DataFrame创建的三种方式
from pyspark.sql import SparkSession
if __name__ == '__main__':
spark = SparkSession.builder.\
appName("create df").\
master("local[*]").\
getOrCreate()
sc = spark.sparkContext
# 首先构建一个RDD rdd[(name, age), ()]
rdd = sc.textFile("../data/sql/people.txt").\
map(lambda x: x.split(',')).\
map(lambda x: [x[0], int(x[1])]) # 需要做类型转换, 因为类型从RDD中探测
# 构建DF方式1
df = spark.createDataFrame(rdd, schema = ['name', 'age'])
# 打印表结构
df.printSchema()
# 打印20行数据
df.show()
df.createTempView("ttt")
spark.sql("select * from ttt where age< 30").show()
2.1.2、StructType
对象转化
将RDD
转换为DataFrame
方式2:通过StructType
对象来定义DataFrame
的“表结构”转换RDD
。
# 创建DF , 首先创建RDD 将RDD转DF
rdd = sc.textFile("../data/sql/stu_score.txt").\
map(lambda x:x.split(',')).\
map(lambda x:(int(x[0]), x[1], int(x[2])))
# StructType 类
# 这个类 可以定义整个DataFrame中的Schema
schema = StructType().\
add("id", IntegerType(), nullable=False).\
add("name", StringType(), nullable=True).\
add("score", IntegerType(), nullable=False)
# 一个add方法 定义一个列的信息, 如果有3个列, 就写三个add, 每一个add代表一个StructField
# add方法: 参数1: 列名称, 参数2: 列类型, 参数3: 是否允许为空
df = spark.createDataFrame(rdd, schema)
示例代码
# coding:utf8
# 需求: 基于StructType的方式构建DataFrame 同样是RDD转DF
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
spark = SparkSession.builder.\
appName("create_df"). \
config("spark.sql.shuffle.partitions", "4"). \
getOrCreate()
# SparkSession对象也可以获取 SparkContext
sc = spark.sparkContext
# 创建DF , 首先创建RDD 将RDD转DF
rdd = sc.textFile("../data/sql/stu_score.txt").\
map(lambda x:x.split(',')).\
map(lambda x:(int(x[0]), x[1], int(x[2])))
# StructType 类
# 这个类 可以定义整个DataFrame中的Schema
schema = StructType().\
add("id", IntegerType(), nullable=False).\
add("name", StringType(), nullable=True).\
add("score", IntegerType(), nullable=False)
# 一个add方法 定义一个列的信息, 如果有3个列, 就写三个add
# add方法: 参数1: 列名称, 参数2: 列类型, 参数3: 是否允许为空
df = spark.createDataFrame(rdd, schema)
df.printSchema()
df.show()
2.1.3、使用RDD
的toDF
方法转换RDD
2.2、基于Pandas
的DataFrame
将Pandas
的DataFrame
对象,转变为分布式的SparkSQL
;DataFrame
对象。
# 构建Pandas的DF
pdf = pd.DataFrame({
"id": [1, 2, 3],
"name": ["张大仙", '王晓晓', '王大锤'],
"age": [11, 11, 11]
})
# 将Pandas的DF对象转换成Spark的DF
df = spark.createDataFrame(pdf)
示例代码
# coding:utf8
# 演示将Panda的DataFrame转换成Spark的DataFrame
from pyspark.sql import SparkSession
# 导入StructType对象
from pyspark.sql.types import StructType, StringType, IntegerType
import pandas as pd
if __name__ == '__main__':
spark = SparkSession.builder.\
appName("create df").\
master("local[*]").\
getOrCreate()
sc = spark.sparkContext
# 构建Pandas的DF
pdf = pd.DataFrame({
"id": [1, 2, 3],
"name": ["张大仙", '王晓晓', '王大锤'],
"age": [11, 11, 11]
})
# 将Pandas的DF对象转换成Spark的DF
df = spark.createDataFrame(pdf)
df.printSchema()
df.show()
2.3、读取外部数据
通过SparkSQL
的统一API
进行数据读取构建DataFrame
。
sparksession.read.format("text|csv|json|parquet|orc|avro|jdbc|......")
.option("K", "V") # option可选
.schema(StructType | String) # STRING的语法如.schema("name STRING", "age INT")
.load("被读取文件的路径, 支持本地文件系统和HDFS")
2.3.1、读取text
数据源
使用format("text")
读取文本数据;读取到的DataFrame
只会有一个列,列名默认称之为:value
。
schema = StructType().add("data", StringType(), nullable=True)
df = spark.read.format("text")\
.schema(schema)\
`.load("../data/sql/people.txt")
2.3.2、读取json
数据源
使用format("json")
读取json
数据。
df = spark.read.format("json").\
load("../data/sql/people.json")
# JSON 类型 一般不用写.schema, json自带, json带有列名 和列类型(字符串和数字)
df.printSchema()
df.show()
2.3.3、读取csv
数据源
使用format("csv")
读取csv
数据。
df = spark.read.format("csv")\
.option("sep", ";")\ # 列分隔符
.option("header", False)\ # 是否有CSV标头
.option("encoding", "utf-8")\ # 编码
.schema("name STRING, age INT, job STRING")\ # 指定列名和类型
.load("../data/sql/people.csv") # 路径
df.printSchema()
df.show()
2.3.4、读取parquet
数据源
使用format("parquet")
读取parquet
数据
# parquet 自带schema, 直接load啥也不需要了
df = spark.read.format("parquet").\
load("../data/sql/users.parquet")
df.printSchema()
df.show()
parquet: 是Spark中常用的一种列式存储文件格式和Hive
中的ORC
差不多,他俩都是列存储格式
parquet
对比普通的文本文件的区别:
-
parquet
内置schema
(列名\ 列类型\ 是否为空) -
存储是以列作为存储格式
-
存储是序列化存储在文件中的(有压缩属性体积小)
3、DataFrame
的入门操作
DataFrame
支持两种风格进行编程:
DSL
语法风格,DSL
称之为:领域特定语言。其实就是指DataFrame
的特有API
,DSL
风格意思就是以调用API
的方式来处理Data
。
df.where().limit()
SQL
语法风格: 使用SQL
语句处理DataFrame
的数据。
spark.sql("SELECT * FROM xxx")
3.1、DSL - show
方法
展示DataFrame
中的数据, 默认展示20条。
df.show(参数1, 参数2)
- 参数1: 默认是20, 控制展示多少条
- 参数2: 是否阶段列, 默认只输出20个字符的长度, 过长不显示, 要显示的话 请填入 truncate = True
3.2、DSL - printSchema
方法
打印输出df
的schema
信息。
df.printSchema()
3.3、DSL - select
方法
选择DataFrame
中的指定列(通过传入参数进行指定)。
- 可变参数的
cols
对象,cols
对象可以是Column
对象来指定列或者字符串列名来指定列; List[Column]
对象或者List[str]
对象, 用来选择多个列。
#column 对象的获取
id_column = df['id']
subject_column = df['subject']
# select
#支持字符串形式传入
df.select( ["id","subject"]).show()
df.select("id","subject").show()
#也支持column对象的方式传入
df.select(df['id'],df['subject']).show()
3.4、DSL - filter
和where
过滤DataFrame
内的数据,返回一个过滤后的DataFrame
。df.filter()
和df.where()
,where
和filter
功能上是等价的。
# filter
#传字符串的形式
df.filter("score < 99").show()
#传column的形式
df.filter(df['score'] <99).show()
#where和filter等价
df.where("score < 99").show()
df.where(df['score'] < 99).show()
3.5、DSL - groupBy
分组
按照指定的列进行数据的分组, 返回值是GroupedData
对象。传入参数和select
一样,支持多种形式,不管怎么传意思就是告诉Spark按照哪个列分组。
# groupBy
df.groupBy("subject").count().show()
df.groupBy(df['subject']).count().show()
3.6、GroupedData
对象
GroupedData
对象是一个特殊的DataFrame
数据集
其类全名:<class 'pyspark.sql.group.GroupedData'>
这个对象是经过groupBy
后得到的返回值, 内部记录了 以分组形式存储的数据。GroupedData
对象其实也有很多API
,比如前面的count
方法就是这个对象的内置方法。除此之外,像:min
、max
、avg
、sum
等等许多方法都存在。
3.7、SQL
风格语法 - 注册DataFrame
成为表
DataFrame
的一个强大之处就是我们可以将它看作是一个关系型数据表,然后可以通过在程序中使用spark.sql()
来执行SQL语句查询,结果返回一个DataFrame
。如果想使用SQL
风格的语法,需要将DataFrame
注册成表,采用如下的方式:
df.createTempView("score")#注册一个临时视图(表)
df.create0rReplaceTempView("score")#注册一个临时表,如果存在进行替换。
df.createGlobalTempView("score")
#注册一个全局表
- 全局表: 跨
SparkSession
对象使用,在一个程序内的多个SparkSession
中均可调用,查询前带上前缀:global_temp
; - 临时表: 只在当前
SparkSession
中可用。
3.8、SQL
风格语法 - 使用SQL
查询
注册好表后,可以通过:sparksession.sql
(sql
语句)来执行sql
查询,返回值是一个新的df
。
#注册好表后就可以写sql查询
df2 =spark.sql("SELECT * FROM score WHERE score < 99")
df2.show()
3.9、pyspark.sql.functions
包
from pyspark.sql import functions as F
,然后就可以用F
对象调用函数计算了。这些功能函数, 返回值多数都是Column
对象.
4、SparkSQL Shuffle
分区数目
在SparkSQL
中当Job
中产生Shuffle
时,默认的分区数(spark.sql.shuffle.partitions)
为200,在实际项目中要合理的设置。
- 配置文件:
conf/spark-defaultsconfsparksqlshufflepartitions 100
- 在客户端提交参数中:
bin/spark-submit--conf "sparksqlshufflepartitions=100"
- 在代码中可以设置:
spark=SparkSession.builder.
appName("create df").\
master("local[*]").\
config("spark.sgl.shuffle.partitions","2").\
getorCreate()
5、SparkSQL
数据清洗API
5.1、去重方法dropDuplicates
对DF
的数据进行去重,如果重复数据有多条,取第一条。
# 去重APIdropDuplicates,无参数是对数据进行整体去重
df.dropDuplicates().show()
# API 同样可以针对字段进行去重,如下传入aae字段,表示只要年龄一样就认为你是重复数据
df.dropDuplicates(['age', 'job']).show()
5.2、删除有缺失值的行方法dropna
如果数据中包含null
,通过dropna
来进行判断,符合条件就删除这一行数据
#如果有缺失,进行数据删除
#无参数为how=any执行,只要有一个列是null数据整行删除,如果填入how='all'
# 表示全部列为空才会删除,how参数默认是a
df.dropna().show()
#指定阀值进行删除,thresh=3表示,有效的列最少有3个,这行数据才保留
#设定thresh后,how参数无效了
df.dropna(thresh=3).show()
#可以指定阀值以及配合指定列进行工作
#thresh=2,subset=['name,'age]表示针对这2个列,有效列最少为2个才保留数据
df.dropna(thresh=2, subset=['name', 'age']).show()
5.3、填充缺失值数据fillna
根据参数的规则,来进行null
的替换。
#将所有的空,按照你指定的值进行填充,不理会列的任何空都被填充
df.fillna("loss").show()
#指定列进行填充
df.fillna("loss", subset=['job']).show()
#给定字典 设定各个列的填充规则
df.fillna({"name":"未知姓名", "age": 1, "job":"worker"}).show()
6、DataFrame
数据写出
SparkSQL
统一API
写出DataFrame
数据。
6.1、统一API
语法
df.write.mode().format().option(K,V).save(PATH)
#mode,传入模式字符串可选:append追加,overwrite覆盖,ignore忽略,error重复就报异常(默认的)
#format,传入格式字符串,可选:text,csv,json,parquet,orc,avro, jdbc
#注意text源只支持单列df写出
# option 设置属性,如:.option("sep",",")r
#save写出的路径,支持本地文件和HDFS
6.2、常见源写出
# Write text 写出,只能写出一个单列数据
df.select(F.concat_ws("---",“user_id","movie_id","rank","ts")).\
write.\
mode("overwrite").\
format("text").\
save("../data/output/sql/text")
# Write CSV 写出
df.write.mode("overwrite").\
format("csv").\
option("sep",",").\
option("header",True).\
save("../data/output/sql/csv")
# Write Json 写出
df.write.mode("overwrite").\
format("json").\
save("../data/output/sql/json")
# Write Parquet 写出
df.write.mode("overwrite").\
format("parquet").\
save("../data/output/sql/parquet")
#不给format,默认以parquet写出
df.write.mode("overwrite").save("../data/output/sql/default")
7、DataFrame通过JDBC读写数据库
读取JDBC
是需要有驱动的,我们读取的是jdbc:mysql://
这个协议,也就是读取的是MySQL
的数据既然如此,就需要有mysql
的驱动jar
包给spark
程序用。如果不给驱动jar
包,会提示:No suitableDriver
。
-
对于
Windows
系统(使用本地解释器)(以Anaconda
环境演示):将jar
包放在:Anaconda3
的安装路径下\envs\虚拟环境\Lib\site-packages\pyspark\jars
; -
对于
Linux
系统(使用远程解释器执行)(以Anaconda
环境演示):将jar
包放在:Anaconda3
的安装路径下\envs/虚拟环境/lib/python3.8/site-packages/pyspark/jars
。
7.1、写出
#写DF通过JDBC到数据库中
df.write.mode("overwrite").\
format("jdbc").\
option("url","jdbc:mysql://node1:3306/test?useSSL=false&useUnicode=true").\
option("dbtable","u_data").\
option("user","root").\
option("password","123456").\
save()
jdbc
连接字符串中,建议使用useSSL=false
确保连接可以正常连接(不使用SSL
安全协议进行连接);
jdbc
连接字符串中,建议使用useUnicode=true
来确保传输中不出现乱码;
save()
不要填参数,没有路径,是写出数据库;
dbtable
属性:指定写出的表名。
7.2、读取
df = spark.read.format("jdbc").\
option("url","jdbc:mysql://node1:3306/test?useSSL=false&useUnicode=true").\
option("dbtable","u_data").\
option("user","root"). \
option("password","123456").\
load()
读出来是自带
schema
,不需要设置schema
,因为数据库就有schema
load()
不需要加参数,没有路径,从数据库中读取的啊
dbtable
是指定读取的表名