因为机器学习等算法的快速发展,python已经成为重要的开发语言。利用python做数据挖掘时,Pandas、numpy是常见的依赖库,Pandas、nump在本质上是将数据一次性读入内存后再处理。因pc资源,尤其是内存等资源的限制,当数据量变大后,再用上述方法时,可能发生内存不足,或者剩余内存很少,导致无法处理,或者处理速度很慢的情况。hadoop和spark是主流的大数据平台,主流语言是Java和Scala,为应对python的快速发展,spark推出了python版:pyspark。pyspark可以在单机上使用,解决大数据问题。
本文主要从pyspark原理、pyspark常见操作两个角度进行讲解。
pyspark原理
a.以时间换空间进行大数据处理
关键词:shuffle
在Pandas、numpy进行数据处理时,一次性将数据读入内存中,当数据很大时内存溢出,无法处理;同时而且很多执行算法是单线程处理,不能充分利用cpu性能。
spark在中最关键的概念之一是shuffle,他将数据集分成数据块:shuffle。在读取数据时,每个进程(线程)读取一个数据块,轮片将所有的数据处理完,解决了数据量巨大问题,极大的利用了CPU资源。同时,支持分布式结构,弹性拓展硬件资源。
小结:
1:不是将数据一次性全部读入内存中,而是分片,用时间换空间。
2:支持分布式,弹性拓展硬件资源。
- 个人使用时,完全可以使用standalone模型,进行单机处理。
安装方式:pip install pyspark
a.类sql、类pandas操作方式更易上手
在数据结构上Spark支持dataframe、sql和rdd模型。
Dataframe与pandas很像
C. 速度更快
在pandas中,我们所有的动作都是写完理解执行。spark引入了两个概念:算子和转换。
算子和转换是Spark中最重要的两个动作。算子好比是盖房子中的画图纸,转换是搬砖盖房子。有时候我们做一个统计是多个动作结合的组合拳,spark常将一系列的组合写成算子的组合执行,执行时,spark会对算子进行简化等优化动作,执行速度更快。
pyspark常见操作
关键词:
1. config("spark.default.parallelism", 3000):设置shuffle个数
2. read.csv,read.format:读取文件
3. withColumn:添加一列;withColumn("id", monotonically_increasin):自动插入index
4. select:选取数据
5. join:合并
6. toDF("id:long,day: int"):转换成pyspark的dataframe
7. persist():持久化
8. data.schema:表头数据结构
9. sort_values(by=["日期","小时"],ascending=[1, 1]):排序
10. data.groupBy("小时","日期").agg({"流量":"sum"})
11. foreach:apply
12. repartition(1).write.csv("test/ok",mode="overwrite"):写入硬盘
13. data.createOrReplaceTempView("plan_AllData"):创造表名
14. ...
开始spark的快速入门,强烈多看官方原始文档:
Getting Started - Spark 2.4.1 Documentationspark.apache.org
Welcome to Spark Python API Docs!spark.apache.org
头文件
import numpy as np
import pandas as pd
import os
import math
import pyspark
from pyspark.sql import SQLContext
from pyspark.sql import SparkSession,Row
from pyspark.sql.types import *
from time import time
from pyspark.ml.linalg import Vectors
from pyspark.sql.functions import min, max,monotonically_increasing_id
from pyspark.sql.types import *
1.配置
Spark使用时需要对每次都对脚本进行配置,也就是spark代码中进行设置。
spark=SparkSession.builder.config("spark.default.parallelism", 3000).appName("taSpark").getOrCreate()
解释:
A: config("spark.default.parallelism", 3000)
对数据进行切片,专业名词叫shuffle。因为我读取的数据是20g,所以设置成3000份,每次每个进程(线程)读取一个shuffle,避免内存不足的异常。
B: appName("taSpark")
设置程序的名字
2.读文件
参考链接:
pyspark.sql modulespark.apache.org
方法一:
data = spark.read.csv(cc,header=None, inferSchema="true")
方法二
data = spark.read.format(csv).load(cc, header=None, inferSchema="true")
解释:
方法一:
该方法是只读csv格式的文件,其中cc是文件的路径,spark支持list格式,既cc是一个list,每个元素是一个csv文件的路径。
header=None,同pandas,指定表头
inferSchema="true",csv中的数据格式在读取时自动推断。注意,与pandas不同,这个必须要指定,且每个列只是一种数据类型。
查看读取数据的数据类型:data.printSchema()
|-- 用户手机号: long (nullable = true)
|-- 开始时间: integer (nullable = true)
|-- TAI: string (nullable = true)
|-- 次数: integer (nullable = true)
|-- 持续时间: integer (nullable = true)
|-- 流量: long (nullable = true)
|-- 日期: long (nullable = true)
|-- 小时: integer (nullable = true)
修改dataframe的表头,重定义dataframe
data=data.toDF(u"用户手机号",u"开始时间",u"TAI",u"次数",u"持续时间",u"流量")
3、 操作
Spark与Pandas中DataFrame对比(详细)
Spark与Pandas中DataFrame对比(详细) - 宁哥的小站www.lining0806.com
a: 添加一列数据
方法一:
# 添加id
data=data.withColumn("id", monotonically_increasing_id())
b=data.select(data.id,data["开始时间"].astype("string")).rdd.map(lambda x:[x[0],int(x[1][-4:-2])]).
toDF("id:long,day: int")
# 插入列
data=data.join(b,data.id==b.id)
解释:
因为spark是在每个shuffle中,不能直接插入,要进行类似合表的动作。
思路:
添加index,然后在以index为key,对数据进行合并。
data=data.withColumn("id", monotonically_increasin)
withColumn函数是添加一列数据,但是这个数据必须是原始表中的数据。"id"是新添加列的名称,monotonically_increasin是给每个行一个key,到达添加index的效果。
data.select(data.id,data["开始时间"].astype("string")).rdd.map(lambda x:[x[0],int(x[1][-4:-2])]).toDF("id:long,day: int")
select(data.id,data["开始时间"].astype("string")),从数据中读取id列和“开始时间”列,同时把开始“开始时间”转换成string数据类型。此时的数据类型是dataframe,把他们转成rdd然后操作。Rdd的map是对每一行,每行的数据类型是Row,最后要转换城dataframe要转换成list或者row,本次转换,我们将其转换成list。
toDF("id:long,day: int")
将RDD构建成dataframe,指定列名和数据类型。注意,数据类型要与原始的一致。
缺点:因为join,该方法时间较久。
方法二
使用rdd的map方法,该方法速度快,但是不适合其他表的数据插入。
# 加入一个表名
# 保留原始的名字
StructType = data.schema
# 添加一个字段
StructType.add(StructField("日期", LongType()))
StructType.add(StructField("小时", IntegerType()))
def f(x):
xtmp=list(x.asDict().values())
c=int(str(x["开始时间"])[-4:-2])
xtmp.append(c)
c=int(str(x["开始时间"])[-2:])
xtmp.append(c)
return xtmp
pass
datardd=data.rdd.map(f)
data=spark.createDataFrame(datardd,StructType)
b: 修改一列数据:
# 日期替换成星期
placedict = {24: 7, 25: 1, 26: 2, 27: 3, 28: 4, 29: 5, 30: 6}
def f(x):
x=x.asDict()
try:
x['日期']=placedict[x['日期']]
pass
except:
x['日期']=-1
pass
return list(x.values())
pass
# 保存原来的头
StructType = data.schema
# 转换
datardd = data.rdd.map(f)
# 转换完成
data = spark.createDataFrame(datardd, StructType)
c: 统计
1. c=data.groupby(["小时","日期","用户手机号"]).count().groupBy("小时","日期").count().toPandas()
2. c=c.sort_values(by=["日期","小时"])
3. a=data.groupBy("小时","日期").agg({"流量":"sum"})
4. e=e.withColumn("avg(sum(流量))", e["avg(sum(流量))"]/(1024*1024)).rderBy(["日期", "小时"], ascending=[1, 1]).toPandas()
5. data.foreach(lambda x: int(str(x["开始时间"])[-4:-2]))
groupby与pandas一致
sort_values降序排列,ascending=[1, 1],设置某列是升序还是降序
agg({"流量":"sum"}),因为shuffle比较多,每次历遍都很繁琐,所以可以对特定列做特定的动作。
data.foreach(lambda x: int(str(x["开始时间"])[-4:-2])),与pandas的apply一样。
持久化
d: 做一次持久化
算子每次都要从头开始执行,有时候很多动作是重发的,也就是起始点是一样的,因此可以做一个持久化,也就是把动作提前做完,保存好生成的数据。包括cache和presist,其中cache是把数据保存在内存,不推荐。Persist与read类似,推荐。
e: 写入
data.repartition(1).write.csv("test/ok",mode="overwrite")
写入是写入一个文件中,其中repartition(1)是告诉写入的文件个数,默认是与shuffle个数相同,可以不用这个参数。
4、 SQL模式
# sql
#创造表名
data.createOrReplaceTempView("plan_AllData")
# 获取最大最小值
longitudeMAX_MIN=spark.sql("SELECT MAX(longitude),MIN(longitude) FROM plan_AllData").collect()
latitudeMAX_MIN=spark.sql("SELECT MAX(latitude),MIN(latitude) FROM plan_AllData").collect()
与使用sql相同,返回的是dataframe结构的数据