【Pyspark教程】SQL、MLlib、Core等模块基础使用

文章目录

零、Spark基本原理

  • 不同于MapReduce将中间计算结果放入磁盘中,Spark采用内存存储中间计算结果,减少了迭代运算的磁盘IO,并通过并行计算DAG图的优化,减少了不同任务之间的依赖,降低了延迟等待时间。内存计算下,Spark 比 MapReduce 快100倍。
  • Spark可以用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。

在这里插入图片描述

如果是pyspark,为了不破坏Spark已有的运行时架构,Spark在外围包装一层Python API。在Driver端,借助Py4j实现Python和Java的交互,进而实现通过Python编写Spark应用程序。在Executor端,则不需要借助Py4j,因为Executor端运行的Task逻辑是由Driver发过来的,那是序列化后的字节码。

Spark的存储架构(具体参考Spark存储原理):
在这里插入图片描述

0.1 pyspark.sql 核心类

  • pyspark.SparkContext: Spark 库的主要入口点,它表示与Spark集群的一个连接,其他重要的对象都要依赖它.SparkContext存在于Driver中,是Spark功能的主要入口。代表着与Spark集群的连接,可以在集群上创建RDD,accumulators和广播变量
  • pyspark.RDD: 是Spark的主要数据抽象概念,是Spark库中定义的一个抽象类。
  • pyspark.streaming.StreamingContext 一个定义在Spark Streaming库中定义的类, 每一个Spark Streaming 应用都必须创建这个类
  • pyspark.streaming.DStrem:离散数据流,是Spark Streaming处理数据流的主要对象
  • pyspark.sql.SparkSession: 是DataFrame和SQL函数的主要入口点。
  • pyspark.sql.DataFrame: 是Spark SQL的主要抽象对象,若干行的分布式数据,每一行都要若干个有名字的列。 跟R/Python中的DataFrame 相像,有着更丰富的优化。DataFrame可以有很多种方式进行构造,例如: 结构化数据文件,Hive的table, 外部数据库,RDD。
  • pyspark.sql.Column DataFrame 的列表达.
  • pyspark.sql.Row DataFrame的行数据

0.2 spark的基本概念

RDD:是弹性分布式数据集(Resilient Distributed Dataset)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型。

DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系。

Driver Program:控制程序,负责为Application构建DAG图。

Cluster Manager:集群资源管理中心,负责分配计算资源。

Worker Node:工作节点,负责完成具体计算。

Executor:是运行在工作节点(Worker Node)上的一个进程,负责运行Task,并为应用程序存储数据。

Application:用户编写的Spark应用程序,一个Application包含多个Job。

Job:作业,一个Job包含多个RDD及作用于相应RDD上的各种操作。

Stage:阶段,是作业的基本调度单位,一个作业会分为多组任务,每组任务被称为“阶段”。

Task:任务,运行在Executor上的工作单元,是Executor中的一个线程。

总结:Application由多个Job组成,Job由多个Stage组成,Stage由多个Task组成。Stage是作业调度的基本单位。

0.3 spark部署方式

Local显然就是本地运行模式,非分布式。

  • Standalone:使用Spark自带集群管理器,部署后只能运行Spark任务,与MapReduce 1.0框架类似。
  • Mesos:是目前spark官方推荐的模式,目前也很多公司在实际应用中使用该模式,与Yarn最大的不同是Mesos 的资源分配是二次的,Mesos负责分配一次,计算框架可以选择接受或者拒绝。
  • Yarn:Haoop集群管理器,部署后可以同时运行MapReduce,Spark,Storm,Hbase等各种任务,其架构如下图所指示,资源管理和调度依赖YARN,分布式存储依赖于HDFS:

在这里插入图片描述

0.4 RDD数据结构

RDD全称Resilient Distributed Dataset,弹性分布式数据集,它是记录的只读分区集合,是Spark的基本数据结构。RDD代表一个不可变、可分区、里面的元素可并行计算的集合。这里也列出RDD最常用的函数:

  • map
  • flatMap
  • mapPartitions
  • filter
  • count
  • reduce
  • take
  • saveAsTextFile
  • collect
  • join
  • union
  • persist
  • repartition
  • reduceByKey
  • aggregateByKey

(1)创建RDD的2种方式

一般有两种方式创建RDD,第一种是读取文件中的数据生成RDD,第二种则是通过将内存中的对象并行化得到RDD。

#通过读取文件生成RDD
rdd = sc.textFile("hdfs://hans/data_warehouse/test/data")

#通过将内存中的对象并行化得到RDD
arr = [1,2,3,4,5]
rdd = sc.parallelize(arr)

创建RDD之后,可以使用各种操作对RDD进行编程。

(2)RDD操作的2种类型

RDD的操作有两种类型,即Transformation操作和Action操作。转换操作是从已经存在的RDD创建一个新的RDD,而行动操作是在RDD上进行计算后返回结果到 Driver。

  • Transformation操作:
    • 用于对RDD的创建,还包括大量的操作方法,如map、filter、groupBy、join等,RDD利用这些操作生成新的RDD。
    • transformation都具有 Lazy 特性,即 Spark 不会立刻进行实际的计算,只会记录执行的轨迹,只有触发Action操作的时候,它才会根据 DAG 图真正执行。
  • action操作:数据执行部分,通过执行count、reduce、collect等真正执行数据的计算。RDD的lazy模式,使得大部分前期工作都在transformation时已经完成。
# -*- coding:utf-8 -*-
from pyspark import SparkContext, SparkConf
from pyspark.streaming import StreamingContext
import math
appName ="jhl_spark_1" #你的应用程序名称
master= "local"#设置单机
conf = SparkConf().setAppName(appName).setMaster(master)#配置SparkContext
sc = SparkContext(conf=conf)

# parallelize:并行化数据,转化为RDD
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data, numSlices=10)  # numSlices为分块数目,根据集群数进行分块

# textFile读取外部数据
rdd = sc.textFile("./c2.txt")  # 以行为单位读取外部文件,并转化为RDD
print rdd.collect()

# map:迭代,对数据集中数据进行单独操作
def my_add(l):
    return (l,l)
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)  # 并行化数据集
result = distData.map(my_add)
print (result.collect())  # 返回一个分布数据集

# filter:过滤数据
def my_add(l):
    result = False
    if l > 2:
        result = True
    return result
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)#并行化数据集,分片
result = distData.filter(my_add)
print (result.collect())#返回一个分布数据集

# zip:将两个RDD对应元素组合为元组
x = sc.parallelize(range(0,5))
y = sc.parallelize(range(1000, 1005))
print x.zip(y).collect()
#union 组合两个RDD
print x.union(x).collect()
# Aciton操作

# collect:返回RDD中的数据
rdd = sc.parallelize(range(1, 10))
print rdd
print rdd.collect()

# collectAsMap:以rdd元素为元组,以元组中一个元素作为索引返回RDD中的数据
m = sc.parallelize([('a', 2), (3, 4)]).collectAsMap()
print m['a']
print m[3]

# groupby函数:根据提供的方法为RDD分组:
rdd = sc.parallelize([1, 1, 2, 3, 5, 8])
def fun(i):
    return i % 2
result = rdd.groupBy(fun).collect()
print [(x, sorted(y)) for (x, y) in result]

# reduce:对数据集进行运算
rdd = sc.parallelize(range(1, 10))
result = rdd.reduce(lambda a, b: a + b)
print result

(3)RDD之间的依赖关系

RDD之间的依赖关系有两种类型,即窄依赖和宽依赖:

  • 窄依赖时,父RDD的分区和子RDD的分区的关系是一对一或者多对一的关系。而宽依赖时,父RDD的分区和自RDD的分区是一对多或者多对多的关系。
  • 宽依赖关系相关的操作一般具有shuffle过程,即通过一个Patitioner函数将父RDD中每个分区上key不同的记录分发到不同的子RDD分区。

在这里插入图片描述

依赖关系确定了DAG切分成Stage的方式。
切割规则:从后往前,遇到宽依赖就切割Stage。

RDD之间的依赖关系形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分成相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,每个stage包含一个或多个task任务。然后将这些task以taskSet的形式提交给TaskScheduler运行。

在这里插入图片描述
其中RDD的迭代过程也可以参考下图:
在这里插入图片描述

WordCount范例

import findspark

#指定spark_home为刚才的解压路径,指定python路径
spark_home = "/Users/liangyun/ProgramFiles/spark-3.0.1-bin-hadoop3.2"
python_path = "/Users/liangyun/anaconda3/bin/python"
findspark.init(spark_home,python_path)
import pyspark 
from pyspark import SparkContext, SparkConf
conf = SparkConf().setAppName("test").setMaster("local[4]")
sc = SparkContext(conf=conf)

只需要5行代码就可以完成WordCount词频统计。

rdd_line = sc.textFile("./data/hello.txt")
rdd_word = rdd_line.flatMap(lambda x:x.split(" "))
rdd_one = rdd_word.map(lambda t:(t,1))
rdd_count = rdd_one.reduceByKey(lambda x,y:x+y)
rdd_count.collect() 
[('world', 1),
 ('love', 3),
 ('jupyter', 1),
 ('pandas', 1),
 ('hello', 2),
 ('spark', 4),
 ('sql', 1)]

0.5 yarn-client模式和yarn-cluster模式

Client和cluster区别:diver在集群内(cluster),还是在集群外(client)。

正常的集群启动过程:

(1)yarn client(集群外)

在这里插入图片描述

(2)yarn cluster(集群内)

在这里插入图片描述

一、Pyspark.SQL部分

  • SparkSQL提供方便的api让我们和hive、HDFS、mysql、Cassandra、Hbase等存储媒介进行数据交换,但其默认只是的数据类型只有Int,Long,Float,Double,String,Boolean等;
  • 如果是SQL中不直接支持的功能,可以通过用户自定义函数udf来实现;如果功能更加复杂,可以转为RDD数据结构来实现。

1.窗口函数

# 数据的分组聚合,找到每个用户最近的3次收藏beat(用window开窗函数)
from pyspark.sql.window import Window
import pyspark.sql.functions as F


window_out = Window.partitionBy("user_id") \
                   .orderBy(F.desc("collect_time")) 
# user_feed.withColumn("rank", F.rank().over(window_out)).show(truncate = False)
# user_feed.withColumn("rank", F.rank().over(window_out)).show(40)
user_feed_test = user_feed.withColumn("rank", F.row_number().over(window_out)) \
                          .where(F.col('rank') <= 3) 
user_feed_test.show(30)

结果如下,和mysql的窗口函数类似的,以每个user_id分组,然后组内排序,这里我只获取排序后collect_time前3的数据,即最近3次的用户收藏数据:

+--------+-------+------------+--------------------+----+
| user_id|beat_id|collect_type|        collect_time|rank|
+--------+-------+------------+--------------------+----+
|10065188| 827272|           4|2021-08-22 04:54:...|   1|
|10065188| 885812|           5|2020-10-23 18:53:...|   2|
|10068979|1069390|           5|2021-06-20 07:44:...|   1|
|10074915|     -2|           4|2021-11-27 13:42:...|   1|
|10074915|1122682|           4|2021-09-07 14:26:...|   2|
|10075397| 947751|           4|2022-01-30 07:30:...|   1|
|10075397| 336641|           5|2022-01-30 07:23:...|   2|
|10075397| 886179|           4|2022-01-05 10:35:...|   3|
|10104842| 886462|           1|2021-02-28 17:04:...|   1|
|10122654|1531961|           4|2022-03-16 11:09:...|   1|
|10122654| 893655|           4|2022-03-15 04:32:...|   2|
|10122654| 303121|           4|2022-03-14 05:59:...|   3|
|10134095|      0|           3|2021-07-24 13:02:...|   1|
|10134095|1023250|           4|2021-07-22 00:31:...|   2|
|10139927|      0|           5|2020-09-05 19:14:...|   1|
|10139927|      0|           5|2020-09-03 17:51:...|   2|
|10245428| 889915|           5|2020-05-18 14:41:...|   1|
|10245428|1073074|           5|2020-05-18 14:07:...|   2|
+--------+-------+------------+--------------------+----+

2.更换列名:

如现在有个人员信息表,新加上一列coun try字段信息:

# 修改列名
from pyspark.sql.functions import col
# df2 = df1.withColumn("avg_resp_rate", col("sum_def_imps")/col("sum_count")).withColumn("avg_ctr", col("sum_clicks")/col("sum_imps"))
 
# another example
from pyspark.sql.functions import col, lit
from pyspark.sql.types import StructType, StructField, StringType,IntegerType


data = [('James','','Smith','1991-04-01','M',3000),
  ('Michael','Rose','','2000-05-19','M',4000),
  ('Robert','','Williams','1978-09-05','M',4000),
  ('Maria','Anne','Jones','1967-12-01','F',4000),
  ('Jen','Mary','Brown','1980-02-17','F',-1)
]
print(data)

"""
[('James', '', 'Smith', '1991-04-01', 'M', 3000), 
('Michael', 'Rose', '', '2000-05-19', 'M', 4000), 
('Robert', '', 'Williams', '1978-09-05', 'M', 4000), 
('Maria', 'Anne', 'Jones', '1967-12-01', 'F', 4000), 
('Jen', 'Mary', 'Brown', '1980-02-17', 'F', -1)]
"""

先给出对应的字段,创建我们的DataFrame格式,然后通过withColumn新加上一列,其中lit("ABC")是指整列的数据都是对应的ABC字符串:

# schema只需要给出列名即可
columns = ["firstname","middlename","lastname","dob","gender","salary"]

# 增加
df = spark.createDataFrame(data=data, schema = columns)
df.show()

# 增加or修改列
df2 = df.withColumn("salary",col("salary").cast("Integer"))
df2.show()

df3 = df.withColumn("salary",col("salary")*100)
df3.show()

# lit默认均是USA
df5 = df.withColumn("Coun try", lit("ABC"))
df5.show()

结果如下:

+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname|       dob|gender|salary|
+---------+----------+--------+----------+------+------+
|    James|          |   Smith|1991-04-01|     M|  3000|
|  Michael|      Rose|        |2000-05-19|     M|  4000|
|   Robert|          |Williams|1978-09-05|     M|  4000|
|    Maria|      Anne|   Jones|1967-12-01|     F|  4000|
|      Jen|      Mary|   Brown|1980-02-17|     F|    -1|
+---------+----------+--------+----------+------+------+

+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname|       dob|gender|salary|
+---------+----------+--------+----------+------+------+
|    James|          |   Smith|1991-04-01|     M|  3000|
|  Michael|      Rose|        |2000-05-19|     M|  4000|
|   Robert|          |Williams|1978-09-05|     M|  4000|
|    Maria|      Anne|   Jones|1967-12-01|     F|  4000|
|      Jen|      Mary|   Brown|1980-02-17|     F|    -1|
+---------+----------+--------+----------+------+------+

+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname|       dob|gender|salary|
+---------+----------+--------+----------+------+------+
|    James|          |   Smith|1991-04-01|     M|300000|
|  Michael|      Rose|        |2000-05-19|     M|400000|
|   Robert|          |Williams|1978-09-05|     M|400000|
|    Maria|      Anne|   Jones|1967-12-01|     F|400000|
|      Jen|      Mary|   Brown|1980-02-17|     F|  -100|
+---------+----------+--------+----------+------+------+

+---------+----------+--------+----------+------+------+-------+
|firstname|middlename|lastname|       dob|gender|salary|Country|
+---------+----------+--------+----------+------+------+-------+
|    James|          |   Smith|1991-04-01|     M|  3000|    ABC|
|  Michael|      Rose|        |2000-05-19|     M|  4000|    ABC|
|   Robert|          |Williams|1978-09-05|     M|  4000|    ABC|
|    Maria|      Anne|   Jones|1967-12-01|     F|  4000|    ABC|
|      Jen|      Mary|   Brown|1980-02-17|     F|    -1|    ABC|
+---------+----------+--------+----------+------+------+-------+

3.sql将一个字段根据某个字符拆分成多个字段显示

可以通过withColumnsplit进行分隔,参考上次写的【Pyspark基础】sql获取user最近3次使用的item

更多参考:
https://www.cnblogs.com/360aq/p/13269417.html
https://www.pythonheidong.com/blog/article/690421/aa556949151c244e81f8/

4.pd和spark的dataframe进行转换:

  • 当需要把Spark DataFrame转换成Pandas DataFrame时,可以调用toPandas()
  • 当需要从Pandas DataFrame创建Spark DataFrame时,可以采用createDataFrame(pandas_df)

更多参考:
厦大数据实验室-借助arrow实现pyspark和pandas之间的数据转换:http://dblab.xmu.edu.cn/blog/2752-2/

5.报错ValueError: Some of types cannot be determined after inferring

是因为有字段类型spark识别不了:

(1)可以提高数据采样率:

sqlContext.createDataFrame(rdd, samplingRatio=0.01)

(2)显式声明要创建的 DataFrame 的数据结构schema

from pyspark.sql.types import *
schema = StructType([
    StructField("c1", StringType(), True),
    StructField("c2", IntegerType(), True)
])
df = sqlContext.createDataFrame(rdd, schema=schema)

# 方法二: 使用toDF
from pyspark.sql.types import *
schema = StructType([
    StructField("c1", StringType(), True),
    StructField("c2", IntegerType(), True)
])
df = rdd.toDF(schema=schema)

参考:https://www.codeleading.com/article/64083243294/

6.DF按行打乱

每行生成随机数后排序,然后删除这一随机数的列。

import pyspark.sql.functions as F

# 从rdd生成dataframe
schema = StructType(fields)
df_1 = spark.createDataFrame(rdd, schema)

# 乱序: pyspark.sql.functions.rand生成[0.0, 1.0]中double类型的随机数
df_2 = df_1.withColumn('rand', F.rand(seed=42))

# 按随机数排序
df_rnd = df_2.orderBy(df_2.rand)

# 删除随机数的一列
df = df_rnd.drop(df_rnd.rand)

7.表格的联结

最好的材料:PySpark Join Types | Join Two DataFrames
Spark DataFrame理解和使用之两个DataFrame的关联操作
SQL数据库语言基础之SqlServer多表连接查询与INNER JOIN内连接查询
SQL的表格之间的join连接方式——inner join/left join/right join/full join语法及其用法实例
pyspark join用法总结

8.dataframe的操作

如用filter进行筛选满足id为1、2的数据:

# 筛选出 id 为 1 和 2 的用户信息
user_info_df.filter(user_info_df['id'].isin([1, 2]))

9.createDataFrame的几种方法

(1)注:structtype的格式(官方文档):
可以通过StructType设置schema,关于其使用:

# 读取beat数据
schema = StructType([StructField("beatid", StringType(), True)\
                   ,StructField("name", StringType(), True)\
                   ,StructField("language", StringType(), True)])
beats = spark.read.csv("filepath", header=False, schema=schema)
# print(beats.show())
beats.show()

(2)从pandas dataframe创建spark dataframe

# 从pandas dataframe创建spark dataframe
colors = ['white','green','yellow','red','brown','pink']
color_df=pd.DataFrame(colors,columns=['color'])
color_df['length']=color_df['color'].apply(len)

color_df=spark.createDataFrame(color_df)
color_df.show()

https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.types.StructType.html?highlight=structtype#pyspark.sql.types.StructType

注意:如果报错Can not merge type <class 'pyspark.sql.types.DoubleType'> and <class 'pyspark.sql.types.StringType'>
根本原因:并非数据类型不匹配,而是数据中存在空值,将空值进行填充后成功创建。

10.pd dataframe与spark dataframe转换,通过sql语句间接对pandas的dataframe进行处理

pandasDF_out.createOrReplaceTempView("pd_data")
# %%
spark.sql("select * from pd_data").show()
# %%
res = spark.sql("""select * from pd_data 
                where math>= 90 
                order by english desc""")
res.show()
# %%
output_DF = res.toPandas()
print(type(output_DF))

更多参考:https://blog.csdn.net/weixin_46408961/article/details/120407900

11.filter筛选

可以通过filter进行筛选,如找出category_title00后的对应行:

# 方法一:filter
category_beat.filter(" category_title = '00后' ").head(5)

https://spark.apache.org/docs/latest/api/python/reference/pyspark.pandas/api/pyspark.pandas.DataFrame.filter.html

12. 新增或者修改spark.sql中dataframe的某列

官方文档pyspark.sql.DataFrame.withColumn描述:https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.withColumn.html?highlight=withcolumn#pyspark.sql.DataFrame.withColumn

13.将dataframe保存为csv文件

这里的repartition(num)是一个可选项:

# save positive_res_data
file_location = "file:///home/hadoop/development/RecSys/data/"
positive_res_data.repartition(2).write.csv(file_location + 'positive_res_data.csv')
# 保存成功,有多个文件

也可以使用save:
https://wenku.baidu.com/view/1329194ea75177232f60ddccda38376baf1fe078.html

14. 取出对应表项内容

首先初始表category_beat是这样的:

+--------------------------+----------------------------------+
|            category_title|                 collect_set(name)|
+--------------------------+----------------------------------+
|                      00|[白月光与朱砂痣, 致姗姗来迟的你...|
|                04年唱的歌|  [祝我生日快乐, 旅行的意义,...|
|10年前没有iPhone但有这些歌|    [逆光, 改变自己, 达尔文,...|
+--------------------------+----------------------------------+

现在要得到第一行的collect_set表项内容,即对应的集合:

import random

ans = category_beat.where(col("category_title") == '00后').select(col("collect_set(name)"))
# type(ans)
ans.show()

# 取出对应行列里的set,并且转为对应的list
lst = ans.take(1)[0][0]
# take是取出前1行

lst = list(lst)
print(lst, "\n")

print("推荐的歌曲:", lst[random.randint(0, len(lst))])

"""
+----------------------------------+
|                 collect_set(name)|
+----------------------------------+
|[白月光与朱砂痣, 致姗姗来迟的你...|
+----------------------------------+

['白月光与朱砂痣', '致姗姗来迟的你', '陨落', '踏山河', '花.间.酒', '山海', '

推荐的歌曲: 山海
"""

15.agg和groupby结合使用

16.统计每列对应的缺失值

#检查每一列中缺失数据的百分比
import pyspark.sql.functions as fn
test_ans1 = vec_ans1.agg(*[(1-(fn.count(c) /fn.count('*'))).alias(c+'_missing') for c in vec_ans1.columns])
test_ans1.head()

17.观察缺失值

(1)如果df中num1和num2字段中有缺失值,现在按照规则如果一个为空, 另一个非空则返回false;如果两个都是空,或者两个都不为空则返回true,逻辑很简单,就是注意pyspark中的这种写法,whenotherwise

df.withColumn(
  "num1_eq_num2",
  when(df.num1.isNull() & df.num2.isNull(), True)
    .when(df.num1.isNull() | df.num2.isNull(), False)
    .otherwise(df.num1 == df.num2)
).show()
+----+----+------------+
|num1|num2|num1_eq_num2|
+----+----+------------+
|   1|null|       false|
|   2|   2|        true|
|null|null|        true|
+----+----+------------+

(2)【udf的使用】有时候在没有缺失值的df中使用udf没问题,但是有缺失值的df中则报错,比如下面新生成一列,如果country为空时则新列的值也为空。

@udf(returnType=StringType())
def good_funify(s):
     return None if s == None else s + " is fun!"
countries2.withColumn("fun_country", good_funify("country")).show()

+--------+---+----------------+
| country| id|     fun_country|
+--------+---+----------------+
|Thailand|  3|Thailand is fun!|
|    null|  4|            null|
+--------+---+----------------+

18.填充缺失值

如果字符串的空值是None,如果是数值类型的空值是nan
在这里插入图片描述

df = df.na.replace('', 'unkown')  # 将空字符串填充为unkown
df = df.fillna('unkown', subset = string_tz)  # 将string的NULL填充为unkown
df = df.fillna(0, subset = int_tz)

参考:
(1)pyspark填充缺失值(spark.ml库)
(2)数据清洗之缺失值处理

19.在sql中使用外部变量替换

q25 = 500
var2 = 50
Q1 = spark.sql("SELECT col1 from table where col2>{0} limit {1}".format(var2,q25))

20.遍历df的每行row

# 可以使用udf进行遍历df
def customFunction(row):
   return (row.name, row.age, row.city)
sample2 = sample.rdd.map(customFunction)

# example2
sample2 = sample.rdd.map(lambda x: (x.name, x.age, x.city))

经典分析:How to loop through each row of dataFrame in PySpark(6种方法)

21.增加新一列的4种方法

dataframe新增一列有如下四种常用方法:
方法一:利用createDataFrame方法,新增列的过程包含在构建rdd和schema中
方法二:利用withColumn方法,新增列的过程包含在udf函数中
方法三:利用SQL代码,新增列的过程直接写入SQL代码中
方法四:以上三种是增加一个有判断的列,如果想要增加一列唯一序号,可以使用monotonically_increasing_id

(以下是scala版spark代码)

//dataframe新增一列方法1,利用createDataFrame方法
val trdd = input.select(targetColumns).rdd.map(x=>{
  if (x.get(0).toString().toDouble > critValueR || x.get(0).toString().toDouble < critValueL) 
    Row(x.get(0).toString().toDouble,"F")
  else Row(x.get(0).toString().toDouble,"T")      
  })      
val schema = input.select(targetColumns).schema.add("flag", StringType, true)
val sample3 = ss.createDataFrame(trdd, schema).distinct().withColumnRenamed(targetColumns, "idx")

//dataframe新增一列方法2
val code :(Int => String) = (arg: Int) => {if (arg > critValueR || arg < critValueL) "F" else "T"}
val addCol = udf(code)
val sample3 = input.select(targetColumns).withColumn("flag", addCol(input(targetColumns)))
.withColumnRenamed(targetColumns, "idx")

//dataframe新增一列方法3
input.select(targetColumns).createOrReplaceTempView("tmp")
val sample3 = ss.sqlContext.sql("select distinct "+targetColname+
    " as idx,case when "+targetColname+">"+critValueR+" then 'F'"+
    " when "+targetColname+"<"+critValueL+" then 'F' else 'T' end as flag from tmp")

//添加序号列
import org.apache.spark.sql.functions.monotonically_increasing_id
val inputnew = input.withColumn("idx", monotonically_increasing_id)

22.求df的最小值、最大值和平均值

from pyspark.sql.functions import mode
from pyspark.sql.functions import max

# 1. 用众数填充当前列的缺失值
mode = df.select(mode('col')).collect()[0][0]
df = df.fillna(mode, 'col')

# 2. 用最大值填充当前列的缺失值
max_value = df.select(max('col')).collect()[0][0]
df = df.fillna(max_value, 'col')

# 3. 用平均值
mean_value = df.select(avg('col')).collect()[0][0]
df = df.fillna(mean_value, 'col')

参考

23. 某列类型为vector 提取其中元素

先转为array后像数组用下标一样提取。

from pyspark.ml.functions import vector_to_array
 
(df.withColumn("xs", vector_to_array("vector")))
    .select(["word"] + [col("xs")[i] for i in range(3)]))
 
## +-------+-----+-----+-----+
## |   word|xs[0]|xs[1]|xs[2]|
## +-------+-----+-----+-----+
## | assert|  1.0|  2.0|  3.0|
## |require|  0.0|  2.0|  0.0|
## +-------+-----+-----+-----+

24. 定义udf 返回类型为字典类型

# 用StructType模拟字典
import string
 
import pyspark.sql.functions as F
from pyspark.sql.types import *
import os
 
import numpy as np
import pandas as pd
from pyspark.sql import SparkSession
import pandas as pd
import os
 
if __name__ == '__main__':
    os.environ['SPARK_HOME'] = "/Users/jingjing.ma/Documents/spark-3.3.1-bin-hadoop3"
    spark = SparkSession \
        .builder \
        .master('local[*]') \
        .appName('try_test') \
        .getOrCreate()
    sc = spark.sparkContext
 
    # 创建RDD 并转换
    rdd = sc.parallelize([[1], [2], [3]])
    df = rdd.toDF(['line'])
 
 
    # 注册UDF
    def get_dict(data):
        return {'num': data, 'letter': string.ascii_letters[data]}
 
 
    func2 = spark.udf.register('func1', get_dict, StructType()
                               .add("num", IntegerType(), nullable=True)
                               .add("letter", StringType()))
    df.select(func2(df['line'])).show()
    df.selectExpr("func1(line)").show(truncate=False) # SQL风格
+-----------+
|func1(line)|
+-----------+
|     {1, b}|
|     {2, c}|
|     {3, d}|
+-----------+

+-----------+
|func1(line)|
+-----------+
|{1, b}     |
|{2, c}     |
|{3, d}     |
+-----------+

参考:
(1)在Python中将 PySpark DataFrame 转换为字典
(2)如何将PySpark dataframe转换为字典:第一列作为主键,其他列及其内容key-value对

二、Spark Core模块

2.1 udf函数的传参:

https://blog.csdn.net/yeshang_lady/article/details/121570361

2.2 pandas core dataframe

可以使用rdd的api。

2.3 rdd操作

RDD是spark的重点,包括两个算子:
【transformation变换】map、flatMap、groupByKey、reduceByKey等。
【action动作】action有懒惰执行的特性,出发基于RDD依赖关系的计算,只有当action操作触发到该依赖才会计算,如collect、count、take、top、first等。

2.4 filter操作

rdd = sc.parallelize([1, 2, 3, 4, 5, 6])
rdd = rdd.filter(lambda x: x % 2 == 0)
rdd.collect()
# [2, 4, 6]

2.5 flatMap

对rdd中每个元素按照函数操作,并将结果进行扁平化处理。

rdd = sc.parallelize([3, 4, 5])
fm = rdd.flatMap(lambda x: range(1, x))
fm.collect()
# [1, 2, 1, 2, 3, 1, 2, 3, 4]

2.6 take

take操作将前若干个数据汇集到Driver,相比collect安全

rdd = sc.parallelize(range(10),5) 
part_data = rdd.take(4)
part_data

# [0, 1, 2, 3]

2.7 map

最简单的应用栗子:

rdd = sc.parallelize(["a", "b", "c", "d", "d"])
rdd2 = rdd.map(lambda x: (x, 1))
print(rdd2.collect())
# [('a', 1), ('b', 1), ('c', 1), ('d', 1), ('d', 1)]

而如果是对dataframe进行map操作,比如想获得当前字段,这列的所有元素(组成一个列表):

import matplotlib.pyplot as plt
from pyspark.sql.functions import collect_set
import pyspark.sql.functions as F

from pylab import mpl
# 设置显示中文字体 
mpl.rcParams["font.sans-serif"] = ["SimHei"]

# 防止中文乱码
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体
plt.rcParams["axes.unicode_minus"]=False #该语句解决图像中的“-”负号的乱码问题

plt.style.use('fivethirtyeight')
num_list = cat_df.rdd.map(lambda x: x[1]).collect()
label_list = cat_df.rdd.map(lambda x: x[0]).collect()

# test = beat_category.select(F.collect_set(F.col("category_title"))).take(1)
plt.pie(num_list, labels = label_list, autopct = '%1.1f%%')

plt.tight_layout()
plt.show()

参考:
(1)Introduction to PySpark Column to List,另外里面有关于pyspark的很多技巧。
(2)具体的RDD操作经常容易忘,可以查阅pyspark RDD详细教程
(3)对df上操作udf的极好教程:PySpark Row using on DataFrame and RDD

【栗子】将df中当前列的所有内容转为一个列表的元素(以online_account字段为例子)如下,更多方法参考Converting a PySpark DataFrame Column to a Python List

df3.select('online_account').rdd.flatMap(lambda x: x).collect()

三、MLlib模块

MLlib (DataFrame-based)
MLlib (RDD-based)

基于pyspark的模型开发流程:
在这里插入图片描述
(1)需求沟通和问题的确立
(2)数据整合和特征工程:
在这里插入图片描述
(3)模型开发和效果评估:
在这里插入图片描述
注意需要筛选合适的特征,不是特征越多越好。可以使用卡方检验对特征与因变量进行独立性检验,如果独立性高就表示两者没太大关系,特征可以舍弃;如果独立性小,两者相关性高,则说明该特征会对应变量产生比较大的影响,应当选择。

ChiSqSelector代表卡方特征选择。ChiSqSelector根据独立卡方检验,然后选取类别标签主要依赖的特征。 selectorType Supported options: numTopFeatures (default), percentile and fpr.

  • 1、numTopFeatures:通过卡方检验选取最具有预测能力的Top(num)个特征
  • 2、percentile:类似于上一种方法,但是选取一小部分特征而不是固定(num)个特征
  • 3、fpr:选择P值低于门限值的特征,这样就可以控制false positive rate来进行特征选择
def ChiSqSelector(df, featuresCol='features', labelCol='label',numTopFeatures=50,outputCol="selectedFeatures",
    selectorType='numTopFeatures', percentile=0.1, fpr=0.05):
    """
    ChiSqSelector代表卡方特征选择。ChiSqSelector根据独立卡方检验,然后选取类别标签主要依赖的特征。
    """
    # selectorType Supported options: numTopFeatures (default), percentile and fpr.
    # 1、numTopFeatures:通过卡方检验选取最具有预测能力的Top(num)个特征
    # 2、percentile:类似于上一种方法,但是选取一小部分特征而不是固定(num)个特征
    # 3、fpr:选择P值低于门限值的特征,这样就可以控制false positive rate来进行特征选择
    from pyspark.ml.feature import ChiSqSelector
    selector = ChiSqSelector(
                            numTopFeatures = numTopFeatures,
                            featuresCol = featuresCol,
                            outputCol = outputCol,
                            labelCol = labelCol,
                            selectorType = selectorType,
                            percentile = percentile,
                            fpr = fpr
                            )
    result = selector.fit(df).transform(df)
    print("ChiSqSelector output with top %d features selected" % selector.getNumTopFeatures())
    return result

在这里插入图片描述
(4)模型应用和迭代优化:
在这里插入图片描述

3.1 kmeans聚类分析

api的使用本身不难,和sklearn的使用差不多:

from pyspark.ml.clustering import KMeans
kMeans = KMeans(k=25, seed=1)

model = kMeans.fit(kmeans_data.select('features'))
model.transform(kmeans_data).show(1000)

# 分析模型训练的结果

# 训练得到的模型是否有summary
print(model.hasSummary, "\n")

# 获得聚类中心
print(model.clusterCenters(), "\n")

# 每个簇的大小(含有的数据点数)
print(model.summary.clusterSizes)

3.2 gbdt分类和回归

这里以GBDT分类举例,使用libsvm数据集:

# test libsvm dataset
#!usr/bin/env python
#encoding:utf-8
from __future__ import division
import findspark
findspark.init()
import pyspark
from pyspark import SparkConf
from pyspark.ml import Pipeline
from pyspark.context import SparkContext
from pyspark.sql.session import SparkSession
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
from pyspark.ml.feature import StringIndexer, VectorIndexer,IndexToString

"""
conf=SparkConf().setAppName('MLDemo')
sc = SparkContext('local')
spark = SparkSession(sc)
"""

def gradientBoostedTreeClassifier(data="data/sample_libsvm_data.txt"):
    '''
    GBDT分类器
    '''
    #加载LIBSVM格式的数据集
    data = spark.read.format("libsvm").load(data)  
    print("data数据集前5行:\n", data.show(n = 3))
    
    labelIndexer = StringIndexer(inputCol="label", outputCol="indexedLabel").fit(data)
    
    #自动识别类别特征并构建索引,指定maxCategories,因此具有> 4个不同值的特征被视为连续的
    featureIndexer=VectorIndexer(inputCol="features", outputCol="indexedFeatures", maxCategories=4).fit(data)
    
    #训练集、测试集划分
    (trainingData, testData) = data.randomSplit([0.7, 0.3])
    
    # 构建10个基分类器 maxIter = 10
    gbt = GBTClassifier(labelCol="indexedLabel", featuresCol="indexedFeatures", maxIter=10)
    
    pipeline = Pipeline(stages=[labelIndexer, featureIndexer, gbt])
    model = pipeline.fit(trainingData)
    # transformer是一个pipelineStage,将一个df转为另一个df,对testData进行
    predictions = model.transform(testData)
    
    #展示前5行数据,或者show进行打印
    predictions.select("prediction", "indexedLabel", "features").show(5)
    
    #展示预测标签与真实标签,计算测试误差
    evaluator = MulticlassClassificationEvaluator(
        labelCol="indexedLabel", predictionCol="prediction", metricName="accuracy")
    
    # 评估model
    accuracy = evaluator.evaluate(predictions)
    print("Test Error = %g" % (1.0 - accuracy))
    gbtModel = model.stages[2]
    print('gbtModelSummary: ',gbtModel)  #模型摘要
 
 
if __name__=='__main__':
    filepath = "file:///home/hadoop/development/RecSys/"
    gradientBoostedTreeClassifier(filepath + "data/sample_libsvm_data.txt")

这里的结果如下,可以发现accuary的值可以达到97%。

+-----+--------------------+
|label|            features|
+-----+--------------------+
|  0.0|(692,[127,128,129...|
|  1.0|(692,[158,159,160...|
|  1.0|(692,[124,125,126...|
+-----+--------------------+
only showing top 3 rows

data数据集前5行:
 None
+----------+------------+--------------------+
|prediction|indexedLabel|            features|
+----------+------------+--------------------+
|       1.0|         1.0|(692,[95,96,97,12...|
|       1.0|         1.0|(692,[122,123,124...|
|       1.0|         1.0|(692,[124,125,126...|
|       1.0|         1.0|(692,[124,125,126...|
|       1.0|         1.0|(692,[125,126,127...|
+----------+------------+--------------------+
only showing top 5 rows

Test Error = 0.03125
gbtModelSummary:  GBTClassificationModel: uid = GBTClassifier_d0de0e9263e7, numTrees=10, numClasses=2, numFeatures=692

3.3 tf-idf英文关键词确定

TF-IDF(Term Frequency/Inverse Document Frequency,词频-逆文档频率)算法,可以找出文档中的关键词,

顾名思义,TF-IDF 分数由两部分组成:
第一部分是TF词语频率(Term Frequency),
第二部分是IDF逆文档频率(Inverse Document Frequency)。
其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。

TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数
IDF(t)= log_e(文档总数 / 出现该词语的文档总数)即:
I D F ( x ) = log ⁡ N N ( x ) I D F(x)=\log \frac{N}{N(x)} IDF(x)=logN(x)N

IDF反应了一个词在所有文本中出现的频率,如果一个词在很多的文本中出现,那么它的IDF值应该低,比如I come to China to travel中的“to”。而反过来如果一个词在比较少的文本中出现,那么它的IDF值应该高。一个极端的情况,如果一个词在所有的文本中都出现,那么它的IDF值应该为0。

如果之前跑过数据,需要删除缓存中的rdd数据再跑:normalized_document_tfidf_rdd.unpersist()。具体代码如下:

from pyspark import SparkConf, SparkContext
import math
 
#以下为计算过程中需要用到的几个函数
# 该函数主要是统计一个文档中包含哪些单词
def word_contains(words_list):
      words_set=set(words_list)#将列表转为set,去除重复的单词
      return list(words_set)#再将set转为列表返回
 
# 计算每个单词的逆文档频率idf
def computeIDF(word_df_tuple,num_document):
      word=word_df_tuple[0]
      df=word_df_tuple[1]
      #根据逆文档频率计算公式计算idf值
      word_idf = math.log(float(num_document+1) / float(df+1), 2)
      return (word, word_idf)#以一个元组tuple的形式返回一个单词的dif值
 
# 计算每个文档中单词的tf值,并将文档转成向量
def computeTF(words_list, all_words_list):
      words_num=len(words_list)#获取文档中出现的单词的个数
      words_dic={}
      for word in words_list:#统计文档中每个单词出现的次数
            if word in words_dic.keys():
                  words_dic[word]+=1
            else:
                  words_dic[word]=1
 
      tf_vector=[]
      for word in all_words_list:#将文档转为一个tf值向量并返回
            if word in words_dic.keys():
                  tf=float(words_dic[word])/words_num
                  tf_vector.append(tf)
            else:
                  tf_vector.append(0)
      return tf_vector
      
# 计算每个文档向量中每个单词的tfidf值
def computeTFIDF(tf_vector, words_idf_dic,all_words_list):
      i=0
      tfidf_vector=[]
      for word in all_words_list:#将每个单词的tf值和idf值相乘
            tfidf=tf_vector[i]*words_idf_dic[word]
            tfidf_vector.append(tfidf)
            i+=1
      return tfidf_vector
 
# 对每个tfidf向量进行归一化
def nomoralize(tfidf_vector):
      new_vector=[]
      sum=0
      for item in tfidf_vector:
            sum+=math.pow(item,2)
      sqrt_sum=math.sqrt(sum)
      for item in tfidf_vector:
            new_item=item/sqrt_sum
            new_vector.append(new_item)
      return new_vector

#主程序
if __name__ == "__main__":
    #conf = SparkConf().setAppName("tfidf")
    #sc = SparkContext(conf=conf)
    
    # 删除缓存中的rdd数据
    # normalized_document_tfidf_rdd.unpersist()
    
    #示例文档数据,每个文档是一个单词列表
    documents_list=[["hello","world","china","good","spark","good"],
                    ["hello","china","china","great","love","china"],
                    ["love","spark","spark","good","hello","spark"]]
    
    #documents_list=[["hello","friends","today","is","my","holiday"],
    #                ["hello","china","china","great","love","china"],
    #                ["love","spark","spark","good","hello","spark"]]
    #创建RDD并进行缓存
    tokenized_document_rdd=sc.parallelize(documents_list).cache()

    print ("*************************** compute idf************************************")
    #这个阶段的主要操作是计算单词的idf值

    #获取文档的个数用来计算逆文档频率
    num_document=tokenized_document_rdd.count()

    #计算每个单词的文档支持度
    #实现思路是,针对每个文本文档,通过将单词列表转成set来获取每个文档中出现的单词,然后
    #通过flatMap操作,将每个文档出现的单词合并成一个新的集合。在新的集合中,一个单词出现
    #的次数即是其文档支持度。因此,我们可以在flatMap操作之后应用map和reducebykey操作来统
    #计每个单词的文档支持度。
    words_df_rdd=tokenized_document_rdd.flatMap(lambda words_list:word_contains(words_list)) \
                                       .map(lambda word:(word,1)) \
                                       .reduceByKey(lambda a,b:a+b)

    #根据单词的文档频率和文档的总数计算每个单词的idf
    # computeIDF函数实现的是具体计算idf的值
    words_idf_rdd=words_df_rdd.map(lambda word_df_tuple:
                                   computeIDF(word_df_tuple, num_document))
    print ("*********************************** compute tf *******************************")
    #计算每个文本中每个单词出现的频次,进而计算tf值
    #返回包含所有单词的列表
    #flatMap是将所有文档中的单词合并成一个大的列表,distinct是将列表中重复的单词去除
    all_words_list= tokenized_document_rdd.flatMap(lambda words_list:words_list) \
                                       .distinct() \
                                       .collect()

    #考虑到单词可能很多,我们将包含所有单词的all_words_list变量做出广播变量,使得一个executor
    #上的多个Task可以共享该变量
    all_words_broadcast=sc.broadcast(all_words_list)

    #计算单词的tf,得到文档的tf向量
    document_tf_rdd= tokenized_document_rdd.map(lambda words_list:
                                                computeTF(words_list, all_words_broadcast.value))

    print ("******************************* compute tfidf*********************************")
    #提取从rdd中提取每个单词的idf值,并将提取的列表变量转成字典变量,进而转成广播变量,以
    #供发送给各个executor计算每个文档中每个单词的tfidf值
    words_idf_list= words_idf_rdd.collect()
    words_idf_dic={}
    for item in words_idf_list:#将单词的idf值列表转为字典易于获取每个单词的idf值
           words_idf_dic[item[0]]=item[1]
    words_idf_broadcast=sc.broadcast(words_idf_dic)

    #计算每个文本中每个单词的tfidf值
    document_tfidf_rdd= document_tf_rdd.map(lambda words_tf_list:computeTFIDF(words_tf_list,
     words_idf_broadcast.value,all_words_broadcast.value))

    #将每个文本对应的列表向量进行归一化
    normalized_document_tfidf_rdd= document_tfidf_rdd.map(lambda tfidf_vector:
     nomoralize(tfidf_vector))

    print ("************************** print tfidf vectors*********************************")
    #打印输出每个tfidf向量
    tfidf_vectors= normalized_document_tfidf_rdd.collect()
    num = 0
    for item in tfidf_vectors:
        print (item)
        num = num + 1
        print("第%d条文本:" % num)
        print("当前文本的tfidf向量: \n", item)
        print(documents_list[num - 1])
        print("最大值是:", p.max(item), "所在的下标是:", item.index(p.max(item)))
        # tf-idf值最大的单词
        print("tfidf值最大的单词", documents_list[num - 1][ item.index(p.max(item)) ], "\n")        

结果分析:每条句子(文档)的tf-idf最大的单词也打印出来了

*************************** compute idf************************************
*********************************** compute tf *******************************
******************************* compute tfidf*********************************
************************** print tfidf vectors*********************************
[0.5820915838854853, 0.0, 0.29104579194274266, 0.0, 0.29104579194274266, 0.7012517964002163, 0.0]1条文本:
当前文本的tfidf向量: 
 [0.5820915838854853, 0.0, 0.29104579194274266, 0.0, 0.29104579194274266, 0.7012517964002163, 0.0]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.7012517964002163 所在的下标是: 5
tfidf值最大的单词 china 

[0.0, 0.0, 0.0, 0.6060537877905645, 0.7546051455392007, 0.0, 0.2515350485130669]2条文本:
当前文本的tfidf向量: 
 [0.0, 0.0, 0.0, 0.6060537877905645, 0.7546051455392007, 0.0, 0.2515350485130669]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.7546051455392007 所在的下标是: 4
tfidf值最大的单词 love 

[0.30151134457776363, 0.0, 0.9045340337332908, 0.0, 0.0, 0.0, 0.30151134457776363]3条文本:
当前文本的tfidf向量: 
 [0.30151134457776363, 0.0, 0.9045340337332908, 0.0, 0.0, 0.0, 0.30151134457776363]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.9045340337332908 所在的下标是: 2
tfidf值最大的单词 china 

3.4 特征工程

(1)vectorindexer

这里还有其他常见的转换器StringIndexer、IndexToString、OneHotEncoder、VectorIndexer:
[1] Spark2.1.0 入门:特征变换–标签和索引的转化(Python版)
[2] 手把手带你玩转Spark机器学习-使用Spark构建分类模型
[3] Spark2.1.0 入门:特征变换–标签和索引的转化(Python版)
[4] Spark入门:标签和索引的转化:StringIndexer- IndexToString-VectorIndexer
[5] Spark ML 中 VectorIndexer, StringIndexer等用法

PS:字符型特征用stringindexer,而数值型特征用vectorassembler直接和其他特征拼接。

(2)特征筛选

MLlib 卡方检验

3.5 GBDT分类栗子

#!usr/bin/env python
#encoding:utf-8
from __future__ import division

import findspark
findspark.init()
import pyspark
from pyspark import SparkConf
from pyspark.ml import Pipeline
from pyspark.context import SparkContext
from pyspark.sql.session import SparkSession
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
from pyspark.ml.feature import StringIndexer, VectorIndexer,IndexToString
 
 
 
conf=SparkConf().setAppName('MLDemo')
sc = SparkContext('local')
spark = SparkSession(sc)
 
 
def gradientBoostedTreeClassifier(data="mllib/sample_libsvm_data.txt"):
    '''
    GBDT分类器
    '''
    #加载LIBSVM格式的数据集
    data = spark.read.format("libsvm").load(data)  
    labelIndexer = StringIndexer(inputCol="label", outputCol="indexedLabel").fit(data)
    #自动识别类别特征并构建索引,指定maxCategories,因此具有> 4个不同值的特征被视为连续的
    featureIndexer=VectorIndexer(inputCol="features", outputCol="indexedFeatures", maxCategories=4).fit(data)
    #训练集、测试集划分
    (trainingData, testData) = data.randomSplit([0.7, 0.3])
    gbt = GBTClassifier(labelCol="indexedLabel", featuresCol="indexedFeatures", maxIter=10)
    pipeline = Pipeline(stages=[labelIndexer, featureIndexer, gbt])
    model = pipeline.fit(trainingData)
    predictions = model.transform(testData)
    #展示前5行数据
    predictions.select("prediction", "indexedLabel", "features").show(5)
    #展示预测标签与真实标签,计算测试误差
    evaluator = MulticlassClassificationEvaluator(
        labelCol="indexedLabel", predictionCol="prediction", metricName="accuracy")
    accuracy = evaluator.evaluate(predictions)
    print("Test Error = %g" % (1.0 - accuracy))
    gbtModel = model.stages[2]
    print('gbtModelSummary: ',gbtModel)  #模型摘要
 
 
if __name__=='__main__':
    gradientBoostedTreeClassifier(data="mllib/sample_libsvm_data.txt")

3.6 labeledPoint使用

这个也可以不适用,如果使用可以参考 labeledPoint栗子

这个
的输入也是将特征处理成labeledPoint格式了。

3.7 解决二分类问题

很多机器学习的应用场景都是二分类问题,比如CTR点击率预估等,可以参考Machine Learning with PySpark and MLlib — Solving a Binary Classification Problem

3.8 保存模型为pmml格式

可以使用pyspark2pmml库

3.9 天池比赛流程(缺失值填充,数据分桶、模型训练、模型融合的练习)

这里有个非常典型的栗子(阿里天池新人赛,幸福感挖掘)可以学习。

3.10 在pyspark上使用xgboost

完整的xgboost链路(pyspark版)栗子

3.11 spark上有MLP嘛

有的。比如MultilayerPerceptronClassifier,参考
(1)MultilayerPerceptronClassifier官方文档
(2)spark上的mlp使用栗子

3.12 对某列字符串建立索引,然后还原

from pyspark.ml.feature import IndexToString, StringIndexer
from pyspark.sql import SparkSession

spark = SparkSession\
    .builder\
    .appName("IndexToStringExample")\
    .getOrCreate()

df = spark.createDataFrame(
    [(0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c")],
    ["id", "category"])
# 对字符串建立索引(按出现频率大小给定0,1,2...)
indexer = StringIndexer(inputCol="category", outputCol="categoryIndex")
model = indexer.fit(df)
indexed = model.transform(df)

print("Transformed string column '%s' to indexed column '%s'"
      % (indexer.getInputCol(), indexer.getOutputCol()))
indexed.show()

print("StringIndexer will store labels in output column metadata\n")
# 将索引还原为字符串
converter = IndexToString(inputCol="categoryIndex", outputCol="originalCategory")
converted = converter.transform(indexed)

print("Transformed indexed column '%s' back to original string column '%s' using "
      "labels in metadata" % (converter.getInputCol(), converter.getOutputCol()))
converted.select("id", "categoryIndex", "originalCategory").show()

四、其他部分

4.1 正负样本分层抽样

seed = 10
sampleby = 'colA'
fraction=0.8
franctions = df.select(sampleby).distinct().withColumn('fraction', lit(fraction)).rdd.collectAsMap()
print(fractions)
sampled_df = df.stat.sampleBy(sampleby, franctions, seed)

比如按照userid进行分组,找到每个组内的前一个,前五个数据:

from pyspark.sql import Window
from pyspark.sql.functions import col
import pyspark.sql.functions as F

#Segregate into Positive n negative 
df_0=df.filter(df.label == 0)
df_1=df.filter(df.label == 1)

#Create a window groups together records of same userid with random order
window_random = Window.partitionBy(col('userid')).orderBy(F.rand())

# For Negative Dataframe , rank and choose rank <= 5
data_0 = df_0.withColumn('rank', F.rank().over(window_random)).filter(F.col('rank') <= 5).drop('rank')

# For Positive Dataframe , rank and choose rank <= 1
data_1 = df_1.withColumn('rank', F.rank().over(window_random)).filter(F.col('rank') <= 1).drop('rank')

#Finally union both results 
final_result = data_1.union(data_0)

上下采样(unionconcat都是直接进行上下拼接):

df_class_0 = df_train[df_train['label'] == 0]
df_class_1 = df_train[df_train['label'] == 1]
df_class_1_over = df_class_1.sample(count_class_0, replace=True)
df_test_over = pd.concat([df_class_0, df_class_1_over], axis=0)

或者:

train_1= train_initial.where(col('label')==1).sample(True, 10.0, seed = 2018)
#step 2. Merge this data with label = 0 data

train_0=train_initial.where(col('label')==0)
train_final = train_0.union(train_1)

如果是每组抽取前几个样本(比如按照rank排序):

# 数据的分组聚合,找到每个用户最近的3次收藏(使用)beat(用window开窗函数)
from pyspark.sql.window import Window
import pyspark.sql.functions as F


window_out = Window.partitionBy("user_id") \
                   .orderBy(F.desc("behaviour_time")) 
user_feed_test = user_feed.withColumn("rank", F.row_number().over(window_out)) \
                          .where(F.col('rank') <= 3) 
user_feed_test.show(7)

可以参考某研究员:推荐系统负采样的几种实现

4.2 Spark中的基本类型与Python数据类型、Hive表数据类型的对应关系

比如在读取df数据时候可以指定schema格式。

这里的“Spark中的基本类型”指Spark-Sql模块下的,DataFrame里的Schema中的数据类型。

Spark中的基本类型Python数据类型Hive数据类型值范围
NullTypeNone
StringTypebasestringSTRING、VARCHAR、CHAR
BinaryTypebytearrayBINARY字节数组
BooleanTypeboolBOOLEAN
DateTypedatetime.dateDATEyyyy-MM-dd格式,其余格式都是错误的,会变为NULL
TimestampTypedatetime.datetimeTIMESTAMPSyyyy-MM-dd HH:mm:ss.fffffffff,即最多支持纳秒级,如果长度超出,则会变成NULL
DecimalTypedecimal.Decimaldecimal任意精度的最大38位的十进制数字
DoubleTypefloat(double precision floats)double[4.9E-324, 1.7976931348623157E308]
FloatTypefloat(single precision floats)float[1.4E-45, 3.4028235E38]
ByteTypeint(a signed integer)tinyint[-128, 127](-2^7, 2^7-1)
ShortTypeint(a signed 16-bit integer)smallint[-32768, 32767](-2^15, 2^15-1)
IntegerTypeint(a signed 32-bit integer)int[-2147483648, 2147483647](-231,231-1)
LongTypelong(a signed 64-bit integer)bigint[-9223372036854775808, 9223372036854775807](-263,263-1)

4.3 上传本地文件到hdfs上

如果使用的是集群,即yarn模式,则保存在hdfs上的方法是和以前保存在本地服务器不一样的,具体关于hdfs的操作可以参考后面的一篇【大数据】Hadoop分布式文件系统HDFS

hadoop fs -put <local file> <hdfs file>

4.4 保存为parquet文件

pred_recall_ans_rank.repartition(1).write.mode("overwrite").parquet(predict_ans_path + '/pred_rank_ans')

Parquet是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。Parquet是语言无关的,而且不与任何一种数据处理框架绑定在一起,适配多种语言和组件,能够与Parquet配合的组件有:

  • 查询引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
  • 计算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite
  • 数据模型: Avro, Thrift, Protocol Buffers, POJOs

参考:Spark入门:读写Parquet(DataFrame)
注意:如果dataframe过大,可以通过df.repartition(k)然后再进一步处理,防止OOM。不过也要具体问题具体分析,参考Spark Repartition 使用

五、推荐算法

5.1 达观数据竞赛:3种改进DL算法

http://www.360doc.com/content/17/0615/21/16619343_663476259.shtml

六、Spark性能优化

6.0 资源参数调优

回顾spark运行机制和流程:
在这里插入图片描述

6.1 基础篇

参考:Spark性能优化指南——基础篇

原则一:避免创建重复的RDD

// 需要对名为“hello.txt”的HDFS文件进行一次map操作,再进行一次reduce操作。也就是说,需要对一份数据执行两次算子操作。

// 错误的做法:对于同一份数据执行多次算子操作时,创建多个RDD。
// 这里执行了两次textFile方法,针对同一个HDFS文件,创建了两个RDD出来,然后分别对每个RDD都执行了一个算子操作。
// 这种情况下,Spark需要从HDFS上两次加载hello.txt文件的内容,并创建两个单独的RDD;第二次加载HDFS文件以及创建RDD的性能开销,很明显是白白浪费掉的。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd2.reduce(...)

// 正确的用法:对于一份数据执行多次算子操作时,只使用一个RDD。
// 这种写法很明显比上一种写法要好多了,因为我们对于同一份数据只创建了一个RDD,然后对这一个RDD执行了多次算子操作。
// 但是要注意到这里为止优化还没有结束,由于rdd1被执行了两次算子操作,第二次执行reduce操作的时候,还会再次从源头处重新计算一次rdd1的数据,因此还是会有重复计算的性能开销。
// 要彻底解决这个问题,必须结合“原则三:对多次使用的RDD进行持久化”,才能保证一个RDD被多次使用时只被计算一次。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
rdd1.reduce(...)

原则二:尽可能复用同一个RDD

原则三:对多次使用的RDD进行持久化

原则四:尽量避免使用shuffle类算子

如果有可能的话,要尽量避免使用shuffle类算子。因为Spark作业运行过程中,最消耗性能的地方就是shuffle过程。shuffle过程,简单来说,就是将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或join等操作。比如reduceByKey、join等算子,都会触发shuffle操作。

原则五:使用map-side预聚合的shuffle操作

在这里插入图片描述
在这里插入图片描述

原则六:使用高性能的算子

比如:
使用reduceByKey/aggregateByKey替代groupByKey;
使用mapPartitions替代普通map;
使用foreachPartitions替代foreach;
使用filter之后进行coalesce操作;
使用repartitionAndSortWithinPartitions替代repartition与sort类操作。

原则七:广播大变量

// 以下代码在算子函数中,使用了外部的变量。
// 此时没有做任何特殊操作,每个task都会有一份list1的副本。
val list1 = ...
rdd1.map(list1...)

// 以下代码将list1封装成了Broadcast类型的广播变量。
// 在算子函数中,使用广播变量时,首先会判断当前task所在Executor内存中,是否有变量副本。
// 如果有则直接使用;如果没有则从Driver或者其他Executor节点上远程拉取一份放到本地Executor内存中。
// 每个Executor内存中,就只会驻留一份广播变量副本。
val list1 = ...
val list1Broadcast = sc.broadcast(list1)
rdd1.map(list1Broadcast...)

原则八:使用Kryo优化序列化性能

原则九:优化数据结构

【安装pyspark】
在Ubuntu上安装pyspark:https://zhuanlan.zhihu.com/p/34635519
apache官网上的安装包:https://www.apache.org/dyn/closer.lua/spark/spark-3.2.1/spark-3.2.1-bin-hadoop3.2.tgz

6.2 高级篇

参考:Spark性能优化指南——高级篇

以下是数据倾斜的解决方案:

解决方案一:使用Hive ETL预处理数据

解决方案二:过滤少数导致倾斜的key

解决方案三:提高shuffle操作的并行度

方案适用场景:如果我们必须要对数据倾斜迎难而上,那么建议优先使用这种方案,因为这是处理数据倾斜最简单的一种方案。

方案实现思路:在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于Spark SQL中的shuffle类语句,比如group by、join等,需要设置一个参数,即spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是200,对于很多场景来说都有点过小。

方案实现原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个key,每个key对应10条数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时间都会变短了。具体原理如下图所示。

方案优点:实现起来比较简单,可以有效缓解和减轻数据倾斜的影响。

方案缺点:只是缓解了数据倾斜而已,没有彻底根除问题,根据实践经验来看,其效果有限。

方案实践经验:该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么无论你的task数量增加到多少,这个对应着100万数据的key肯定还是会分配到一个task中去处理,因此注定还是会发生数据倾斜的。所以这种方案只能说是在发现数据倾斜时尝试使用的第一种手段,尝试去用嘴简单的方法缓解数据倾斜而已,或者是和其他方案结合起来使用。
在这里插入图片描述

解决方案四:两阶段聚合(局部聚合+全局聚合)

解决方案五:将reduce join转为map join

解决方案六:采样倾斜key并分拆join操作

Reference

[1] https://spark.apache.org/docs/latest/api/python/reference/index.html
[2] 算法工程师的数据分析:https://zhuanlan.zhihu.com/p/343375787
[3] 用createDataFrame创建表的几种方法
[4] spark dataframe按行随机打乱
[5] dataframe的常用操作:
0)https://blog.csdn.net/xc_zhou/article/details/118617642
1)https://blog.csdn.net/yeshang_lady/article/details/89528090
2)https://www.jianshu.com/p/acd96549ee15
3)https://www.dandelioncloud.cn/article/details/1441272697576869890
[6] 厦门大学数据库实验室:Spark部署模式
[7] Spark-RDD宽窄依赖及Stage划分
[8] pyspark分类算法之梯度提升决策树分类器模型GBDT实践【gradientBoostedTreeClassifier】
[9] PySpark之RDD入门最全攻略
[10] Spark Schema、Hive和Python的数据类型关系,以及Pyspark数据类型详解
[11] 将csv文件上传到hdfs集群详细步骤
[12] python pyspark-submit 保存模型到hdfs
[13] pyspark填充缺失值(spark.ml库)
[14] PySpark之SparkSQL基本操作
[15] Pyspark DataFrame操作笔记
[16] https://stackoverflow.com/questions/44582450/how-to-pass-variables-in-spark-sql-using-python
[17] https://stackoverflow.com/questions/36349281/how-to-loop-through-each-row-of-dataframe-in-pyspark
[18] 推荐算法工程笔记:PySpark特征工程入门总结
[19] python与Spark结合,PySpark的机器学习环境搭建和模型开发
[20] 用PySpark开发时的调优思路(下)
[21] spark的yarn-client模式和yarn-cluster模式的简单运行流程
[22] Spark on YARN的两种模式:YARN-Client,YARN-Cluster
[23] 厦大林子雨.大数据处理技术课程主页
[24] 大数据技术原理与应用.林子雨慕课
[25] DataFrame中如果存在某列类型为vector,如何提取vector中的元素
[26] PySpark数据分析基础:PySpark基础功能及DataFrame操作基础语法详解

  • 11
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山顶夕景

小哥哥给我买个零食可好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值