一、SparkSQL案例(电影数据统计)
数据源:http://files.grouplens.org/datasets/movielens/ml-100k/u.data
复制网页中数据到本地的文件中
ctrl+a 全部选中
ctrl+c 复制
ctrl+v 粘贴
ctrl+s 保存
将本地的数据文件上传的hdfs
字段: 用户id 电影id 评分 时间
需求:
- 查询每个用户平均分
- 查询每个电影平均分
- 查询大于平均分的电影的数量
- 查询高分电影中(>3)打分次数最多的用户, 并求出此人打的平均分
- 查询每个用户的平均打分, 最低打分, 最高打分
- 查询被评分超过100次的电影, 的平均分 排名 TOP10
from pyspark.sql import SparkSession, functions as F
from pyspark.sql.types import *
ss = SparkSession.builder.getOrCreate()
sc = ss.sparkContext
rdd = sc.textFile('/movie')
# 对每行数据进行切割转为二维嵌套列表
rdd_map = rdd.map(lambda x: [x.split('\t')[0], x.split('\t')[1], int(x.split('\t')[2]), x.split('\t')[3]])
# 将rdd转为df
schema_type = StructType(). \
add('user_id', StringType()). \
add('movie_id', StringType()). \
add('score', IntegerType()). \
add('date_time', StringType())
df = rdd_map.toDF(schema_type)
df.show()
# 查询每个用户平均分
df_avg_user = df.groupby('user_id').agg(F.avg('score').alias('avg_data'))
df_avg_user.show()
# 查询每个电影平均分
df_avg_movie = df.groupby('movie_id').agg(F.avg('score').alias('avg_data'))
df_avg_movie.show(30, truncate=False)
# 查询大于平均分的电影的数量
# 1- 求整体平均分
df_avg_data = df.select(F.avg('score'))
df_avg_data.show()
# 取出df中的第一行数据 就是一个row对象
data = df_avg_data.first()[0]
# 2、通过每个电影的平均分大于总的平均分进行数据的过滤,统计电影数量
res = df_avg_movie.where(df_avg_movie['avg_data'] > data).count()
print(res)
# * 查询高分电影中(>3)打分次数最多的用户, 并求出此人打的平均分
# 1、找到大高分次数最多的用户
# df.where('score > 3').select(['user_id','movie_id','score']).orderBy('user_id').show()
user_r = df.where('score > 3').groupby('user_id').agg(F.count('user_id').alias('user_cnt')).orderBy('user_cnt',
ascending=False).first()
user_id = user_r['user_id']
print(user_id)
# 2、求出改用所有打分的平均分
df_user_avg = df.where(df['user_id'] == user_id).select(F.avg('score'))
df_user_avg.show()
# * 查询每个用户的平均打分, 最低打分, 最高打分
df_user = df_avg_user = df.groupby('user_id').agg(F.avg('score').alias('avg_data'),F.max('score').alias('max_data'),F.min('score').alias('min_data'))
df_user.show()
# * 查询被评分超过100次的电影, 的平均分 排名 TOP10
df_data = df.groupby('movie_id').agg(F.count('movie_id').alias('movie_cnt'),F.round(F.avg('score'),2).alias('moive_avg')).where('movie_cnt > 100').orderBy('moive_avg',ascending=False).limit(10)
df_data.show()
二、分区数目
在SparkSQL中当Job中产生Shuffle时,默认的分区数(spark.sql.shuffle.partitions )为200,在实际项目中要合理的设置
# spark计算配置
# config()
ss = SparkSession.builder.config('spark.sql.shuffle.partitions','4').getOrCreate()
三、数据清洗
- 重复数据
- 去重 dropDuplicates
# 使用sparksql都要SparkSession
from pyspark.sql import SparkSession
from pyspark.sql.types import *
# 创建SparkSession对象
ss = SparkSession.builder.getOrCreate()
sc = ss.sparkContext
rdd = sc.parallelize([
[1,'张三',20,None],
[2,'李四',22,'男'],
[3, '王五', None, '男'],
[4, '赵六', 22, '男'],
[2, '李四', 22, '男']
])
schema_type = StructType().\
add('id',IntegerType()).\
add('name',StringType()).\
add('age',IntegerType()).\
add('gender',StringType())
df= rdd.toDF(schema_type)
# 去重
df.show()
# 去重后返回一个新的df
# 判断整行数据是否相同,整行数据相同,就去除
df_new = df.dropDuplicates()
df_new.show()
# 判断指定的字段数据是否重复,如果重复则删除整行数据
df_new2 = df.dropDuplicates(['id','age'])
df_new2.show()
- 空数据
- 去除空值
- 空值替换
- 建议空数据进行替换,避免删除一些有效数据 20字段 2个为空 省下18个字段是有效数据
- 替换建议是随机值 sql方式 case when
# 使用sparksql都要SparkSession
from pyspark.sql import SparkSession,functions as F
from pyspark.sql.types import *
# 创建SparkSession对象
ss = SparkSession.builder.getOrCreate()
sc = ss.sparkContext
rdd = sc.parallelize([
[1,'张三',20,None],
[2,'李四',22,'男'],
[3, '王五', None, None],
[4, '赵六', 22, '男'],
[2, '李四', 22, '男']
])
schema_type = StructType().\
add('id',IntegerType()).\
add('name',StringType()).\
add('age',IntegerType()).\
add('gender',StringType())
df= rdd.toDF(schema_type)
# 去空
df.show()
# 返回新的df
# 只要数据中有空值,就删除整行数据
df_new = df.dropna()
df_new.show()
# thresh 判断非空数据个数 thresh=2,当前行数据中至少有两个非空数据,如果低于两个,就删出整行数据
df_new1 = df.dropna(thresh=3)
df_new1.show()
# subset 指定判断非空数据的字段
df_new2 = df.dropna(thresh=3,subset=['name','age','gender'])
df_new2.show()
df_new2 = df.dropna(thresh=1,subset=['name'])
df_new2.show()
df.where('name is not null').show()
# 空值替换 返回一个新的df
df_new3 = df.fillna({'age':0,'gender':'0'})
df_new3.show()
四、数据源与格式
读取不同格式的文件数据转为dataframe
read方法进行读取
write方法将dataframe写入不同格式文件
4-1 数据获取
本地spark安装包中有测试文件可以读取
/export/server/spark/examples/src/main/resources
- json {‘name’:‘张三’}
- text ‘1,张三’
- csv ‘1,张三’ 可以设置表头
- csv文件有两个部分构成 头部数据,也就是字段数据,行数数据
- orc
- parquet
- mysql的连接操作
- 实现etl,实现sqoop的操作 mysql数据导入数仓
- 拷贝连接驱动包
- cp /export/server/hive/lib/mysql-connector-java-5.1.32.jar /export/server/spark/jars/
from pyspark.sql import SparkSession,functions as f
ss = SparkSession.builder.getOrCreate()
# 读数据
df = ss.read.text('file:///export/server/spark/examples/src/main/resources/people.txt')
df.show()
df_new = df.select(f.split('value',',').alias('data'))
df_new.show()
df_new.select(df_new['data'][0],df_new['data'][1]).show()
# json 数据
df2 = ss.read.json('file:///export/server/spark/examples/src/main/resources/people.json')
df2.show()
# csv header 是否保留头部数据 sep 指定分割
df3 = ss.read.csv('file:///export/server/spark/examples/src/main/resources/people.csv',sep=';',header=True)
df3.show()
# orc
df4 = ss.read.orc('file:///export/server/spark/examples/src/main/resources/users.orc')
df4.show()
df5 = ss.read.parquet('file:///export/server/spark/examples/src/main/resources/users.parquet')
df5.show()
# 读取mysql数据
# url= jdbc:mysql://node1:3306/itcast 连接mysql的信息 itcast是库名
# table='student' 指定表的名字
# properties 指定连接信息
df6 = ss.read.jdbc(url='jdbc:mysql://node1:3306/itcast?characterEncoding=UTF-8',table='student',properties={'user':'root','password':'123456','dirver':'com.mysql.jdbc.Driver'})
df6.show()
4-2 数据写入
因为数据是在df中存储,所以使用dataframe进行数据写入
写入文件有个模式,覆盖和追加两种方式
- text
- json
- csv
- orc
# 使用sparksql都要SparkSession
from pyspark.sql import SparkSession,functions as F
from pyspark.sql.types import *
# 创建SparkSession对象
ss = SparkSession.builder.getOrCreate()
sc = ss.sparkContext
rdd = sc.parallelize([
[1,'张三'],
[2,'李四']
])
schema_type = StructType().\
add('id',IntegerType()).\
add('name',StringType())
df= rdd.toDF(schema_type)
# 数据写入
# text 写入数据需要将字段数据拼接一个字符串形式一个字段写
# df.select(F.concat_ws(',','id','name')).write.text('/text_data')
# json
# 写入模式 mode
# df.write.json('/json_data',mode='append')
df.write.json('/json_data',mode='overwrite')
# csv
df.write.csv('/csv_data',mode='overwrite',sep=',',header=True)
df.write.orc('/orc_data',mode='overwrite')
df.write.parquet('/parquet_data',mode='overwrite')
- mysql
# mysql数据写入
df.write.jdbc(url='jdbc:mysql://node1:3306/itcast?characterEncoding=UTF-8',mode='overwrite',table='student',properties={'user':'root','password':'123456','dirver':'com.mysql.jdbc.Driver'})
数据读取 sparksession
ss.read.jdbc(路径)
数据写入 dataframe
mode 指定写入模式 append 追加写入 overwrite 覆盖写
df.write.orc(路径,mode=写入模式)
五、窗口函数
over(partiton by 分组字段 order by 排序字段 rows 计算范围)
计算范围 between 。。。 and 。。。
between unbounded preceding and current row
between 1 preceding and current row
between current row and current row 一般不指定
between current row and 1 following
between current row and unbounded following
between 1 preceding and 1 following
between unbounded preceding and unbounded following
聚合函数(sum,avg,max,min,count) over()
排序函数(rank,dense_rank,row_number,ntile) over()
取值函数(first_value,lead,lag,last_value) over()
窗口函数计算的结果会单独创建一列保存
# 导入window类 定义窗口
from pyspark.sql import SparkSession, Window, functions as F
from pyspark.sql.types import *
# 1、生成SparkSession对象
ss = SparkSession.builder.getOrCreate()
# 2、获取sparkcontext对象
sc = ss.sparkContext
# 3、 读取文件数据转为rdd
rdd = sc.textFile('/student')
# 4、查看rdd数据
# 5、对每行数据进行切割
rdd_map = rdd.map(
lambda x: [int(x.split(',')[0]), x.split(',')[1], x.split(',')[2], int(x.split(',')[3]), x.split(',')[4]])
# 6、rdd转df
# 7、定义 表信息
schema_type = StructType(). \
add('id', IntegerType()). \
add('name', StringType()). \
add('gender', StringType()). \
add('age', IntegerType()). \
add('cls', StringType())
df = rdd_map.toDF(schema_type)
df.show()
# 1、定义窗口
# partitionBy 指定分组字段 over(partition by 分组字段)
# orderBy 指定排序字段 over(order by 分组字段)
# rowsBetween 指定计算范围 over(row )
w = Window.partitionBy('gender').orderBy('id').rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
w1 = Window.partitionBy('gender').orderBy('age')
# 2、将窗口计算后的结果单独创建一列保存
# withColumn
# 参数一 指定增加的字段名称
# 参数二 指定计算方法
df_new = df.withColumn('window_data', F.sum('age').over(w))
df_new.show()
df_new1 = df.withColumn('window_data', F.rank().over(w1))
df_new1.show()
六、自定义函数
用户可以根据需求自己封装计算的逻辑,对字段数据进行计算
6-1 函数分类
- udf 一进一出 传递一行数据返回一行计算的结果
- udaf 多进一出 传递多行返回一行 聚合函数 借助pandas
- udtf 一进多出 传递一行返回多行 爆炸函数
6-2 自定义函数使用–UDF
# 导入window类 定义窗口
from pyspark.sql import SparkSession, Window, functions as F
from pyspark.sql.types import *
# 1、生成SparkSession对象
ss = SparkSession.builder.getOrCreate()
# 2、获取sparkcontext对象
sc = ss.sparkContext
# 3、 读取文件数据转为rdd
rdd = sc.textFile('/student')
# 4、查看rdd数据
# 5、对每行数据进行切割
rdd_map = rdd.map(
lambda x: [int(x.split(',')[0]), x.split(',')[1], x.split(',')[2], int(x.split(',')[3]), x.split(',')[4]])
# 6、rdd转df
# 7、定义 表信息
schema_type = StructType(). \
add('id', IntegerType()). \
add('name', StringType()). \
add('gender', StringType()). \
add('age', IntegerType()). \
add('cls', StringType())
df = rdd_map.toDF(schema_type)
df.show()
# 自定义函数
def func(x,y):
# x,y 接受传递字段的数据
# 每次接受一行数据
data = x+y
return data
# 注册到spark中使用
# 第一个参数 指定注册的函数名
# 第二个参数 指定自己定义的函数名
# 第三个参数 指定返回结果的类型
# 注册完成后会返回注册的函数
concat_it = ss.udf.register('concat_it',func,StringType())
# DSL方法中使用
df_new = df.select(concat_it(df['name'],df['gender']))
df_new.show()
# SQL中使用
df.createTempView('stu')
df_new2 = ss.sql('select concat_it(name,gender) from stu ')
df_new2.show()
- 装饰器注册
- 只能在DSL方法中使用,在sql语句中无法使用
# 导入window类 定义窗口
from pyspark.sql import SparkSession, Window, functions as F
from pyspark.sql.types import *
# 1、生成SparkSession对象
ss = SparkSession.builder.getOrCreate()
# 2、获取sparkcontext对象
sc = ss.sparkContext
# 3、 读取文件数据转为rdd
rdd = sc.textFile('/student')
# 4、查看rdd数据
# 5、对每行数据进行切割
rdd_map = rdd.map(
lambda x: [int(x.split(',')[0]), x.split(',')[1], x.split(',')[2], int(x.split(',')[3]), x.split(',')[4]])
# 6、rdd转df
# 7、定义 表信息
schema_type = StructType(). \
add('id', IntegerType()). \
add('name', StringType()). \
add('gender', StringType()). \
add('age', IntegerType()). \
add('cls', StringType())
df = rdd_map.toDF(schema_type)
df.show()
# 自定义函数
@F.udf(returnType=StringType())
def func(x,y):
# x,y 接受传递字段的数据
# 每次接受一行数据
data = x+y
return data
# DSL方法中使用
df_new = df.select(func(df['name'],df['gender']))
df_new.show()