Spark早期的API中(即RDD),由於Java JVM和Py4J之間的通信,每當使用RDD執行PySpark程序時,潛在地需要巨大的開銷來執行作業。
DataFrame和Catalyst優化器(以及Tungsten項目)的意義是在和非優化的RDD查詢比較時增加PySpark查詢的性能。
使用DataFrame,過去不僅有明顯的Python性能改進,現在還有Python、Scale、SQL和R之間的性能校驗。
通過RDD轉換創建DataFrame
首先創建我們的jsonRDD:
jsonRDD = sc.textFile(r'C:\Users\Guitar\Desktop\json.txt',2)
json.txt文件中我們按行存放了一些格式化的json字符串:
{"title": "第1章 黃山真君和九洲一號羣","link": "http://m.baidu.com/tc?appui=alaxs&gid=3965456126&tn=utouch&page=ct&url=http%3A%2F%2Fwww.uukanshu.com%2Fb%2F33933%2F55150.html#2","totalpage": 0,"partsize": 0,"order": 0,"currency": 0,"unreadble": false,"isVip": false}
{"title": "第2章 且待本尊算上一卦","link": "http://m.baidu.com/tc?appui=alaxs&gid=3965456126&tn=utouch&page=ct&url=http%3A%2F%2Fwww.92txt.net%2Fbook%2F25%2F25882%2F10074048.html#2","totalpage": 0,"partsize": 0,"order": 0,"currency": 0,"unreadble": false,"isVip": false}
{"title": "第3章 一張丹方","link": "http://m.baidu.com/tc?appui=alaxs&gid=3965456126&tn=utouch&page=ct&url=http%3A%2F%2Fwww.92txt.net%2Fbook%2F25%2F25882%2F10074053.html#2","totalpage": 0,"partsize": 0,"order": 0,"currency": 0,"unreadble": false,"isVip": false}
...
創建 DataFrame
現在,我們已經創建了RDD,利用SparkSession的read.json
方法,RDD將會被轉換成一個DataFrame,以下是創建DataFrame的代碼:
xs_chapter = spark.read.json(jsonRDD)
創建一個臨時表
利用DataFrame的.createOrReplaceTempView
方法創建一個臨時視圖表:
xs_chapter.createOrReplaceTempView("xs_chapter")
- 創建臨時表是一次DataFrame轉換,我們可以通過臨時表用SQL查詢DataFrame中的數據。
簡單的DataFrame查詢
show()
運行.show
方法默認顯示前10行,當然也可以傳入參數n來控制顯示的行數。
+--------+-----+--------------------+-----+--------+--------------+---------+---------+
|currency|isVip| link|order|partsize| title|totalpage|unreadble|
+--------+-----+--------------------+-----+--------+--------------+---------+---------+
| 0|false|http://m.baidu.co...| 0| 0|第1章 黃山真君和九洲一號羣| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第2章 且待本尊算上一卦| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第3章 一張丹方| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第4章 h市三品後天雷劫| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第5章 要相信科學!| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第6章 銅卦仙師| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第7章 被羣滅的不良們| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第8章 羽柔子和羅信街區| 0| false|
| 0|false|http://m.baidu.co...| 0| 0| 第9章 另一個羅信街區| 0| false|
+--------+-----+--------------------+-----+--------+--------------+---------+---------+
SQL查詢
spark.sql("select isVip,partsize,title from xs_chapter").take(3)
# Out:
[Row(isVip=False, partsize=0, title='第1章 黃山真君和九洲一號羣'),
Row(isVip=False, partsize=0, title='第2章 且待本尊算上一卦'),
Row(isVip=False, partsize=0, title='第3章 一張丹方')]
RDD的交互操作
事實上有兩種從RDD變換到DataFrame(或者Dataset[T])的不同方法:使用反射推斷模式或以編程方式指定模式。
- 模式可以理解爲DataFrame字段的數據類型
使用反射來推斷模式
在建立DataFrame和運行查詢的過程中,DataFrame的模式是自動定義的。
最初,行對象通過傳遞一列鍵/值對作爲行類的**kwargs來構造。然後,SparkSQL將行對象的RDD轉變爲一個DataFrame,在DataFrame中鍵就是列,數據類型通過採樣數據來推斷。
利用DataFrame的printSchema
方法查看模式的定義:
xs_chapter.printSchema()
root
|-- currency: long (nullable = true)
|-- isVip: boolean (nullable = true)
|-- link: string (nullable = true)
|-- order: long (nullable = true)
|-- partsize: long (nullable = true)
|-- title: string (nullable = true)
|-- totalpage: long (nullable = true)
|-- unreadble: boolean (nullable = true)
假設某個字段的類型不是我們預期的,該如何調整?
編程指定模式
我們通過在SparkSQL中引入數據類型(pyspark.sql.types),以編程方式來指定模式,並生成一些.csv數據,如下例所示:
# 導入數據類型
from pyspark.sql.types import *
# 生成逗號分隔的數據
csvRDD = sc.parallelize([(123,'張三',18,'游泳'),(456,'李四',19,'游泳'),(789,'小王',23,'游泳')])
csvRDD.take(3)
Out[33]: [(123, '張三', 18, '游泳'), (456, '李四', 19, '游泳'), (789, '小王', 23, '游泳')]
# 指定對應字段的模式(數據類型)
schema = StructType([StructField('id',LongType(),True),StructField('name',StringType(),True),StructField('age',LongType(),True),StructField('hobby',StringType(),True)])
# 創建DataFrame
swimmers = spark.createDataFrame(csvRDD,schema)
# 創建臨時表
swimmers.createOrReplaceTempView('swimmers')
swimmers.printSchema()
root
|-- id: long (nullable = true)
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- hobby: string (nullable = true)
swimmers.show()
+---+----+---+-----+
| id|name|age|hobby|
+---+----+---+-----+
|123| 張三| 18| 游泳|
|456| 李四| 19| 游泳|
|789| 小王| 23| 游泳|
+---+----+---+-----+
使用DataFrame API查詢
行數
swimmers.count()
# Out: 3
篩選
swimmers.select('id','name','age').filter('age=18').show()
+---+----+---+
| id|name|age|
+---+----+---+
|123| 張三| 18|
+---+----+---+
swimmers.select(swimmers.id,swimmers.name,swimmers.age).filter(swimmers.age==18).show()
+---+----+---+
| id|name|age|
+---+----+---+
|123| 張三| 18|
+---+----+---+
swimmers.select('id','name','age').filter('age like "1%"').show()
+---+----+---+
| id|name|age|
+---+----+---+
|123| 張三| 18|
|456| 李四| 19|
+---+----+---+
使用SQL查詢
如上文,我們可以使用SparkSession的sql方法查詢swimmers,前提是針對swimmers建立了臨時視圖表
行數
spark.sql('select count(*) from swimmers').show()
+--------+
|count(1)|
+--------+
| 3|
+--------+
篩選
spark.sql('select * from swimmers where age>18').show()
+---+----+---+-----+
| id|name|age|hobby|
+---+----+---+-----+
|456| 李四| 19| 游泳|
|789| 小王| 23| 游泳|
+---+----+---+-----+
spark.sql('select * from swimmers where age like "1%"').show()
+---+----+---+-----+
| id|name|age|hobby|
+---+----+---+-----+
|123| 張三| 18| 游泳|
|456| 李四| 19| 游泳|
+---+----+---+-----+
結語
使用Spark DataFrame,Python開發人員可以利用一個簡單的並且潛在地加快速度的抽象層。最初Spark中的Python速度較慢的一個主要原因源自於Python子進程和JVM之間的通信層。對於Python DataFrame的用戶,我們有一個在Scala DataFrame周圍的Python包裝器,Scala DataFrame避免了Python子進程/JVM的通信開銷。