【博学谷学习记录】超强总结,用心分享|大数据之Spark 函数

Spark SQL函数定义

5.1 如何使用窗口函数

回顾:

窗口函数格式:
	分析函数 over(partition by xxx order by xxx [asc|desc] [rows between xxx and xxx])

学习的相关分析函数有那些? 
	第一类: row_number() rank() dense_rank() ntile()
	第二类: 和聚合函数组合使用  sum() avg() max() min() count()
	第三类: lag() lead() first_value() last_value()

如何在Spark SQL中使用呢?

  • SQL中: 与HIVE中应用基本没啥区别, 更多关注的是DSL写法
from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
from pyspark.sql import Window as win
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示: 如何在Spark SQL中使用窗口函数...")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('df_write').master('local[*]').getOrCreate()

    # 2-读取外部文件的数据
    df = spark.read.csv(
        path='file:///export/data/workspace/ky06_pyspark/_03_SparkSql/data/pv.csv',
        header=True,
        inferSchema=True
    )
    df.createTempView('t1')
    # 3- 执行相关的操作
    # 需要: 统计每个cookie中, pv数量排名前二内容是哪些? (分组TOPN 问题)
    # SQL
    spark.sql("""
        with t2 as(
            select
                *,
                row_number() over (partition by cookieid order by pv desc) as rank1
            from t1 
        )
        select  * from  t2 where rank1 <=2
    """).show()

    # DSL:
    df.select(
        '*',
        F.row_number().over(win.partitionBy('cookieid').orderBy(F.desc('pv'))).alias('rank1')
    ).where('rank1 <= 2').show()

2 SQL函数的分类说明

整个SQL函数, 主要是分为以下三大类:

  • UDF函数: 用户自定义函数
    • 表示: 一进一出
    • 整个函数中, 大多数的函数都是属于一进一出的函数: split() substr()
  • UDAF函数: 用户自定义聚合函数
    • 表示: 多进一出
    • 例如: sum() avg() count() ….
  • UDTF函数: 用户自定义表生成函数
    • 表示: 一进多出
    • 指的: 进去一行数据, 最终产生多行 或者多列的数据
    • 例如: explode

在SQL中提供的内置函数, 都是属于以上三类中某一类函数

思考: 提供了那么多的函数, 为啥还需要自定义函数呢?

扩充函数. 在实际使用中, 并不能保证所有的操作可能用的函数都已经提前的内置好, 尤其是很多具有特殊业务处理功能, 其实并没有相对应函数 , 提供的函数更多是以公共的功能为主函数, 此时需要进行自定义, 从而扩充新的功能

​ 在Spark SQL中, 对于自定义函数, 原生支持的粒度并不是特别好, 目前原生的PY方案仅支持自定义UDF函数, 无法自定义UDAF函数和UDTF函数, 在1.6版本之后, Java 和scala语言支持了自定义UDAF函数,但是Python并不支持,Spark官方提供了解决的方案: 基于pandas来自定义UDF 和 UDAF函数. 但是对于UDTF函数, Spark是不支持自定义,但是Spark支持HIVE的函数定义, 所以可以通过HIVE自定义函数来解决

	虽然Python支持自定义UDF函数, 但是其效率并不是特别的高效, 因为在使用的时候, 传递一行处理一行, 返回一行的操作, 这样会带来非常大的序列化开销问题, 以及网络开销问题, 导致原生UDF函数效率不好
	
	早期解决方案: 基于 scala/Java来编写自定义UDF函数, 然后基于Python使用即可
	
	目前主要解决方案: 引入Arrow框架, 可以基于内存来完成数据传输工作, 可以大大降低了序列化开销问题, 提供传输的效率, 解决了原生问题, 同时还可以基于pandas的自定义函数, 利用pandas函数优势, 完成各种处理操作
	
	所以后期主推的方案: 基于pandas 自定义函数, 然后底层基于arrow完成数据传输工作

3 Spark SQL原生自定义函数

如何自定义原生函数流程(非常重要):

第一步: 在Python中创建一个python的函数, 在这个函数中书写自定义函数的功能逻辑代码即可

第二步: 将Python函数注册到Spark SQL中, 成为Spark SQL的函数
	注册方式一: udf对象 = sparkSession.udf.register(参数1,参数2,参数3)
		参数1: UDF函数的名称, 此名称用于后续在SQL语法中使用 , 可以任意定义名称, 但是要符合定义名称规范
		参数2: python函数的名称, 表示将哪个python的函数注册为Spark SQL的函数
		参数3: 返回值的类型, 用于表示当前这个Python的函数返回的类型对应的Spark SQL的数据类型
		udf对象: 此对象主要是用于DSL中
	
	注册方式二:  udf对象 = F.udf(参数1,参数2)
		参数1: python函数的名称, 表示将哪个python的函数注册为Spark SQL的函数
		参数2: 返回值的类型, 用于表示当前这个Python的函数返回的类型对应的Spark SQL的数据类型
		udf对象: 此对象主要是用于DSL中
		
		说明: 此种方式还支持语法糖写法:  @F.udf(returnType=返回值类型) 需要放置到对应函数上面
第三步: 在Spark SQL的DSL/SQL中进行使用即可

演示操作: 请自定义一个函数, 完成对数据统一添加一个后缀名的操作

from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql.types import *
import pyspark.sql.functions as F
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示原生的自定义函数:")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('df_write').master('local[*]').getOrCreate()

    # 2- 初始化一些数据
    df = spark.createDataFrame(
        data=[(1,'张三','北京'),(2,'李四','上海'),(3,'王五','广州'),(4,'赵六','深圳'),(5,'田七','杭州')],
        schema='id int,name string,address string'
    )
    df.createTempView('t1')
    # 3- 执行相关的操作:
    # 请自定义一个函数, 完成对数据统一添加一个后缀名的操作
    # 3.1 定义一个Python的函数, 接收一个数据, 给数据添加一个后缀返回
    @F.udf(returnType=StringType())
    def add_post(data):
        return data+'_boxuegu'

    # 3.2 对函数进行注册操作
    # 注册方式一
    # 当采用注解方式注册函数的使用, 如果依然想在SQL中使用, 可以再次使用方式一注册,但是不需要设置返回值类型了
    spark.udf.register('add_post_sql',add_post)

    # 注册方式二: 还有一种语法糖模式
    #add_post_dsl = F.udf(add_post,StringType())

    # 3.3 使用自定义函数
    # SQL
    spark.sql("""
        select
            *,
            add_post_sql(address) as r1
        from t1
    """).show()

    # DSL
    df.select(
        '*',
        add_post('address').alias('r1')
    ).show()

演示操作: 自定义一个函数, 让其返回值的类型为字典 列表 元组

from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql.types import *
import pyspark.sql.functions as F
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示原生的自定义函数:")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('df_write').master('local[*]').getOrCreate()

    # 2- 初始化一些数据
    df = spark.createDataFrame(
        data=[(1,'张三 北京'),(2,'李四 上海'),(3,'王五 广州'),(4,'赵六 深圳'),(5,'田七 杭州')],
        schema='id int,name_address string'
    )
    df.createTempView('t1')
    # 3- 执行相关的操作:
    # 需求: 自定义一个函数, 将姓名和地址拆分开
    schema = StructType().add('name',StringType()).add('address',StringType())

    @F.udf(returnType=schema)
    def split_data(data):
        res = data.split(' ')
        #return [res[0],res[1]]
        #return (res[0], res[1])
        return {'name':res[0],'address':res[1]}
    # 使用字典返回, key值 必须和schema中定义字段名称保持一致

    # 注册函数
    spark.udf.register('split_data',split_data)

    # 使用函数
    # SQL
    df1 = spark.sql("""
        select
            *,
            split_data(name_address)['name'] as name,
            split_data(name_address)['address'] as address
        from t1
    """)

    df1.printSchema()
    df1.show()

    # DSL
    df.select(
        '*',
        split_data('name_address')['name'].alias('name'),
        split_data('name_address')['address'].alias('address')
    ).show()

4 Pandas的UDF函数

4.1 Apache Arrow框架基本介绍

​ Apache Arrow是Apache旗下的一款顶级的项目, 是一个跨平台的在内存中以列式存储的数据层, 它的设计目标就是作为一个跨平台的数据层, 从而加快大数据分析项目的运行效率

​ Pandas 与PySpark SQL 进行交互的时候, 建立在Apache Arrow上, 带来低开销, 高性能的UDF函数

​ 用于Spark程序在JVM和Python的进程中进行有效的高效的传输

​ Arrow并不会自动使用, 在某些情况下, 需要配置一些相关的参数, 需要在代码中进行一些小的调整才可以使用

如何使用Apache Arrow呢?

1- 安装Apache Arrow的库 : 建议三个节点都要按照
	pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspark[sql]    
	
	注意: 以上操作, 仅需要在node1执行即可, 因为此操作要求必须先安装好pyspark 才可以进行安装,但是node2和node3并没有pyspark, 如何解决呢? 
	pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyarrow==11.0.0
	

2- 让程序使用Arrow框架: 
	sparkSession.conf.set('spark.sql.execution.arrow.pyspark.enabled',True)
4.2 如何基于Arrow完成Pandas DF 和 Spark SQL DF互转
from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示: 基于Arrow下, Pandas的DF 和 Spark DF的互转操作")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('df_write').master('local[*]').getOrCreate()

    # 添加Arrow的相关的配置
    spark.conf.set("spark.sql.execution.arrow.pyspark.enabled",True)

    # 2- 初始化一个Spark SQL的DF对象
    spark_df = spark.createDataFrame(
        data=[(1,'张三','北京'),(2,'李四','上海'),(3,'王五','广州'),(4,'赵六','深圳'),(5,'田七','武汉')],
        schema='id int,name string,address string'
    )

    # 3- 将Spark DF 转换为Pandas的DF
    pd_df = spark_df.toPandas()

    print(pd_df)
    pd_df = pd_df[pd_df['id']>=2]
    # 4- Pandas的DF 如何转换为Spark DF
    df = spark.createDataFrame(pd_df)

    df.show()


总结:

	Pandas df --> Spark df : df = spark.createDataFrame(pd_df)
	
	spark df ---> pandas df: pd_df = spark_df.toPandas()
4.3 基于Pandas完成UDF函数

​ 基于Pandas的UDF函数来转换为Spark SQL的UDF函数, 底层是基于Arrow框架来完成数据传输, 允许向量化(可以充分利用计算机CPU性能)操作,在使用的时候, 主要是通过 一个API:pandas_udf() 来完成对pandas函数的一个包装操作, 将其注册为一个Spark SQL的函数

​ pandas_udf() 其实是Spark SQL中提供的一个SQL函数, 在使用的时候, 需要通过F.pandas_udf()完成注册, 支持代码形式或者采用语法糖模式

​ pandas的UDF函数其实本质上就是python的函数, 只不过函数的传入类型为pandas的类型

​ 基于Panads的UDF函数既可以定义UDF函数也可以定义UDAF函数

演示: 如何基于pandas自定义UDF函数

  • 自定义Python函数的要求: SeriesToSeries
    • 表示: 定义一个函数, 这个函数传入的参数类型必须是series, 输出的返回的类型必须也是series类型
    • 需求: 自定义两列数据, 完成 a列和b列的求和计算操作
import pandas as pd
from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
from pyspark.sql.types import  *
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示: 如何基于Pandas 自定义Spark SQL的UDF函数")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('pandas_udf').master('local[*]').getOrCreate()

    # 添加Arrow的相关的配置 : 在pandas的 UDF中, 底层默认会采用Arrow来进行数据传输的
    spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", True)

    # 2- 初始化量两列数据集: a 和 b
    df = spark.createDataFrame(
        data=[(1,2),(2,5),(3,2),(4,7),(8,3),(5,2)],
        schema='a int,b int'
    )
    df.createTempView('t1')

    df.printSchema()
    df.show()

    # 3- 执行相关的操作:  自定义函数, 完成对 a 和 b的求和

    # 3.1 自定义一个python函数, 完成对两列数据计算操作
    @F.pandas_udf(returnType=IntegerType())
    def sum_a_b(a:pd.Series,b:pd.Series) -> pd.Series:
        return a + b

    # 3.2 将py函数进行注册:
    # 语法糖模式: 仅支持 DSL
    # 支持在SQL中使用
    spark.udf.register('sum_a_b',sum_a_b)

    # 3.3 在Spark SQL中进行测试使用
    spark.sql("""
        select
            *,
            sum_a_b(a,b) as sum_ab
        from t1
    """).show()

    df.select('a','b',sum_a_b('a','b').alias('sum_ab')).show()

演示: 如何基于Pandas实现自定义UDAF函数

  • 自定义Python函数的要求: SeriesTo标量

    • 表示: 自定义python函数, 要求传入的数据类型必须为series, 函数的返回类型必须是标量(python的基本数据类型 str int float )类型

    • 需求: 对某一列数据求平均值

import pandas as pd
from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
from pyspark.sql.window import Window as win
import os

# 锁定远端环境, 确保环境统一
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

if __name__ == '__main__':
    print("演示: pandas的UDAF函数实现")

    # 1- 创建SparkSession对象
    spark = SparkSession.builder.appName('pandas_udf').master('local[*]').getOrCreate()

    # 2- 初始化相关的数据集
    df = spark.createDataFrame(
        data=[(1,39.5),(2,63.5),(3,72.0),(4,85.0),(5,23.0),(6,25.0)],
        schema='id int,score float'
    )
    df.createTempView('t1')

    df.printSchema()
    df.show()

    # 3- 执行相关操作:
    # 需求: 对某一列数据求平均值
    # 3.1 自定义一个python的函数
    @F.pandas_udf(returnType='float')
    def score_avg(score:pd.Series) -> float:
        return score.mean()

    # 3.2 注册函数
    spark.udf.register('score_avg',score_avg)

    # 3.3  使用函数
    spark.sql("""
        select
            round(score_avg(score),2) as score_avg
        from t1
    """).show()

    df.select(score_avg('score').alias('score_avg')).show()

    # 支持在窗口函数中使用
    spark.sql("""
        select
            *,
            score_avg(score) over(order by score) as score_avg
        from t1
    """).show()

    df.select(
        '*',
        score_avg('score').over(win.orderBy('score')).alias('score_avg')
    ).show()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值