Spark——一文理解SparkSQL的DataFrame概念以及操作

1、DataFrame的组成

DataFrame是一个二维表结构,那么表格结构就有无法绕开的三个点:

  • 表结构描述

MySQL中的一张表:

  • 由许多行组成
  • 数据也被分成多个列
  • 表也有表结构信息(列、列名、列类型、列约束等)

基于这个前提,DataFrame的组成如下:

  • 在结构层面:

    • StructType对象描述整个DataFrame的表结构
    • StructField对象描述一个列的信息
  • 在数据层面

    • Row对象记录一行数据
    • Column对象记录一列数据并包含列的信息

示例

在这里插入图片描述

如图, 在表结构层面,DataFrame的表结构由:StructType描述,如下图

在这里插入图片描述

一个StructField记录:列名、列类型、列是否运行为空

多个StructField组成一个StructType对象。

一个StructType对象可以描述一个DataFrame:有几个列、每个列的名字和类型、每个列是否为空

同时,一行数据描述为Row对象,如Row(1, 张三, 11)

一列数据描述为Column对象,Column对象包含一列数据和列的信息;RowColumnStructTypeStructField的编程我们在后面编码阶段会接触。

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、使用RDDtoDF方法转换RDD

2.2、基于PandasDataFrame

PandasDataFrame对象,转变为分布式的SparkSQLDataFrame对象。

# 构建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的特有APIDSL风格意思就是以调用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方法

打印输出dfschema信息。

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 - filterwhere

过滤DataFrame内的数据,返回一个过滤后的DataFramedf.filter()df.where()wherefilter功能上是等价的。

# 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方法就是这个对象的内置方法。除此之外,像:minmaxavgsum等等许多方法都存在。

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.sqlsql语句)来执行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是指定读取的表名

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值