因为机器学习等算法的快速发展,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常见操作

关键词:

config(“spark.default.parallelism”, 3000):设置shuffle个数

read.csv,read.format:读取文件

withColumn:添加一列;withColumn(“id”, monotonically_increasin):自动插入index

select:选取数据

join:合并

toDF(“id:long,day: int”):转换成pyspark的dataframe

persist():持久化

data.schema:表头数据结构

sort_values(by=[“日期”,”小时”],ascending=[1, 1]):排序

data.groupBy(“小时”,”日期”).agg({“流量”:”sum”})

foreach:apply

repartition(1).write.csv(“test/ok”,mode=”overwrite”):写入硬盘

data.createOrReplaceTempView(“plan_AllData”):创造表名

开始spark的快速入门,强烈多看官方原始文档:

头文件

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.读文件

参考链接:

方法一:

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、 操作

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结构的数据