Spark DataFrame ETL教程
Spark Python API Docs
数据清洗
ETL的流程:抽取-转换-加载,在实际工作中是以批处理脚本的形式将一系列数据转化成目标形式。
"""初始化pySpark"""
from pyspark import SparkContext, HiveContext
sc = SparkContext(appName="extract")
spark = SparkSession(sc)
def readTable(tb,sp):
rdf = sp.read\
.format("org.apache.phoenix.spark")\
.option("***", ***)\
.option("***", "***:***")\
.load()
return rdf
首先啰嗦pySpark进行转换数据部分的特点,然后从基本操作对象切入介绍DataFrame有多重要,基本操作分类及部分重要操作的示例来展现DataFrame的pySpark操作有多简单方便。
操作很多,划重点
数据转换过程根据不同的需求有不同的处理方法,基本操作包括但不限于多列合并或计算、筛选、聚合。
条条大路通罗马
既可以选择用pyspark.sql里的代码拼凑实现【最近的项目常规使用select+when,filter,withColumn,groupBy,用来用去还是这些居多】,也可以在tables上进行SQL查询精简代码【pyspark倒腾不出来的时候试试SQL】。
向量化编程
Spark是分布式执行的,数据分散在各个机器上,背后有一套调度系统来控制数据计算负载。如果用for循环来处理,就是把负载都加在了执行脚本的机器上,一般来说执行脚本的机器都是不储存数据的master,实际上这一过程就会导致需要把数据从slave传到master上,无谓地增加了网络负担。所以,在Spark脚本里,严禁使用原生的python for循环来处理SparkData Frame,即使要用,也应该使用Spark提供的API接口。
向量化编程就是对列进行操作。之前用pandas的DataFrame时,选择用apply+自定义函数的方式优化for循环逐条处理行数据,运行时间从110+s缩短到5s,在pySpark里可以选择用map+自定义函数的方式逐条处理行数据 。
"""pySpark逐条处理行数据【map+自定义函数的方式】 """
rdd = sc.parallelize(["b", "a", "c"])
rdd.map(lambda x: (x, 1)).collect()
>>>[('a', 1), ('b', 1), ('c', 1)]
在Spark DataFrame里,操作对象主要有三个:DataFrame,Row,Column。其中
DataFrame是一张表,有字段(field)和若干行数据(记录)。
Row:DataFrame 集合中的行数据(记录)。
Column:DataFrame 集合中的列(field)。
一个ETL过程,实质就是从抽取一个DataFrame开始,经过一系列的DataFrame变换,得到一个与目标一致的DataFrame,然后写入到目标数据库中去。Column在其中扮演着中间点的角色,比如取DataFrame的多个列,拼接合成一个新列,然后把这个新列加到原本的DataFrame中去。
所有的DataFrame操作,都可以归类为两种基本操作:转化(Transformation)和行动(action)。
转换操作是不会触发Spark的实际计算的,即使转换过程中出现了错误,在执行到这一行代码时,也不会报错。直到执行了行动操作之后,才会真正让Spark执行计算。
Transform:典型的转换操作有读(read),筛选(filter)、拼接(union)等等,只要这个过程只改变DataFrame的形态,而不需要实际取出DataFrame的数据进行计算,都属于转换。理论上来说,ETL过程中的Transfrom过程,主干流程只会有转换操作,不会有Action操作。
Action:典型的动作操作有计数(count),打印表(show),写(write)等,这些操作都需要真正地取出数据,就会触发Spark的计算。
数据清洗去重、处理缺失值
多列合并或计算
"""dataframe列名重命名
pandas"""
df=df.rename(columns={'a':'aa'})
"""spark-1
在创建dataframe的时候重命名"""
data = spark.createDataFrame(data=[("Alberto", 2), ("Dakota", 2)],
schema=['name','length'])
data.show()
data.printSchema()
"""spark-2
使用selectExpr方法"""
color_df2 = color_df.selectExpr('color as color2','length as length2')
color_df2.show()
"""spark-3
withColumnRenamed方法"""
color_df2 = color_df.withColumnRenamed('color','color2')\
.withColumnRenamed('length','length2')
color_df2.show()
"""spark-4
alias 方法"""
color_df.select(color_df.color.alias('color2')).show()
"""spark-1
join方法"""
lookup = spark .createDataFrame([(1, "foo"), (2, "bar")], ("k", "v"))lookup = spark .createDataFrame([(1, "foo"), (2, "bar")], ("k", "v"))
df_with_x6 = (df_with_x5
.join(lookup, col("x1") == col("k"), "leftouter")
.drop("k")
.withColumnRenamed("v", "x6"));
df_with_x6.show()
"""spark-2
withColumn方法"""
from pyspark.sql.functions import rand
df_with_x7 = df_with_x6.withColumn("x7", rand())
df_with_x7.show()
3.筛选
"""spark-1
filter方法"""
df.filter("age > 3").collect()
>>>[Row(age=5, name='Bob')]
"""spark-2
where方法"""
df.where("age = 2").collect()
>>>[Row(age=2, name='Alice')]
"""pySpark条件分支语句 """
df.select(when(df['age'] == 2, 3).otherwise(4).alias("age")).take(1)
>>>[Row(age=3), Row(age=4)]
4.聚合
"""pySpark分组聚合"""
from pyspark.sql.functions import sum,max
df = spark.createDataFrame([[1,1,3],[4,1,2],[7,2,9]],[‘a‘,‘b‘,‘c‘])
t = df.groupBy("b").agg(sum("a"),max("c"))
t.show()
—创建窗口
使用pyspark.sql.Window对象可以创建一个窗口,可以使用partitionBy进行分组,使用orderBy进行排序,比如
"""pySpark创建窗口"""
from pyspark.sql import Window
window = Window.partitionBy("a").orderBy("b")
—窗口函数实现窗内排序、移动平均
"""pySpark窗口函数实现窗内排序、移动平均"""
from pyspark.sql import Window
window = Window.partitionBy("name").orderBy("age").rowsBetween(-1, 1)
from pyspark.sql.functions import rank, min
df.select(rank().over(window))#窗内排序
df.select(avg('age').over(window))#移动平均
DataFrameWriter,存储格式包括但不限于csv表、table或者jdbc
其中模式mode是写入方式,
append 追加: 在尾部追加数据
overwrite 覆写: 覆盖原有数据
error 错误: 抛出异常
ignore忽略 : 自动跳过
"""以追加csv文件为例"""
df.write.mode('append')..csv(os.path.join(tempfile.mkdtemp(), 'data'))
计划