文章目錄
Chapter 7:聚合操作
聚合操作相關函數的性質是對每一個group而言是多輸入單輸出。Spark 有複雜和成熟的聚合操作,具有各種不同的使用方法和可能性。在 Spark 中也可以對任何數據類型進行聚合,包括複雜數據類型
Spark 允許我們創建以下group分組:
- 最簡單的group分組僅僅是在select子句中通過聚合來彙總一個完整的DataFrame
- 通過
group by
來指定一或多個key並通過一或多個聚合函數來轉換Columns的值 - 通過窗口函數來分組,功能上和
group by
類似,但輸入聚合函數的rows和當前row有關(就是說窗口大小如何指定) - 通過分組集(grouping sets),這是可以用來在不同層級上進行聚合操作。在SQL中可以直接使用分組集,而在DataFrame中可通過
rollup
和cube
操作
我理解是這樣的,原文是
Grouping sets are available as a primitive in SQL and via rollups and cubes in DataFrames.
- 通過
rollup
(彙總)來指定一或多個key並通過一或多個聚合函數來轉換Columns的值,這些列的值將按照層次結構進行彙總 - 通過
cube
(多維數據集)來指定一或多個key並通過一或多個聚合函數來轉換Columns的值,不過這些列的值將在所有列組合中進行彙總
每個分組都返回一個 RelationalGroupedDataset,我們在其上指定聚合操作
其實上面講的這麼多都是SQL中哪些進行分組的聚合函數,rollup也是,cube也是
作者這裏提了一個注意點:
一個需要考慮的重要事情是你需要一個多麼精確的答案。 在對大數據進行計算時,要得到一個問題的精確答案可能相當昂貴,而且簡單地要求一個近似到合理程度的精確度通常要便宜得多。 你會注意到我們在整本書中提到了一些近似函數,通常這是一個很好的機會來提高 Spark 作業的速度和執行,特別是對於交互式和特別分析
就是用近似值代替精確值
這次用的數據集是目錄retail-data/
下的數據,這裏還指定了DataFrame的分區數並且cache持久化緩存了它
# 通過coalesce指定分區,因爲coalesce只能減少分區數,而我初識讀入這個df時只有4個分區(和機器有關),不過可以通過repartition來增加分區
# df.rdd.getNumPartitions 可以獲取DataFrame的分區數
scala> val df = spark.read.format("csv").option("header","true").option("inferSchema","true").load("data/retail-data/all/*.csv").coalesce(5)
df: org.apache.spark.sql.Dataset[org.apache.spark.sql.Row] = [InvoiceNo: string, StockCode: string ... 6 more fields]
# 緩存
scala> df.cache
res11: df.type = [InvoiceNo: string, StockCode: string ... 6 more fields]
#創建臨時表
scala> df.createOrReplaceTempView("dfTable")
scala> df.show(5)
+---------+---------+--------------------+--------+----------------+---------+----------+--------------+
|InvoiceNo|StockCode| Description|Quantity| InvoiceDate|UnitPrice|CustomerID| Country|
+---------+---------+--------------------+--------+----------------+---------+----------+--------------+
| 548704| 84077|WORLD WAR 2 GLIDE...| 576| 4/3/2011 11:45| 0.21| 17381|United Kingdom|
| 538641| 21871| SAVE THE PLANET MUG| 12|12/13/2010 14:36| 1.25| 15640|United Kingdom|
| 546406| 22680|FRENCH BLUE METAL...| 1| 3/11/2011 16:21| 2.46| null|United Kingdom|
| 538508| 22577|WOODEN HEART CHRI...| 6|12/12/2010 13:32| 0.85| 15998|United Kingdom|
| 543713| 22624|IVORY KITCHEN SCALES| 1| 2/11/2011 11:46| 16.63| null|United Kingdom|
+---------+---------+--------------------+--------+----------------+---------+----------+--------------+
最基本也是最簡單的作用於整個DataFrame上的聚合操作就是統計行數
scala> df.count
res15: Long = 541909
count
是一個Action算子,它不僅用來統計數據集的大小,這裏另一個作用是執行df的持久化到內存的cache操作(不過我之前用了show
,它也是Action算子,所以我這裏之前就緩存過了)
Now, this method is a bit of an outlier because it exists as a method (in this case) as opposed to a function and is eagerly evaluated instead of a lazy transformation. In the next section, we will see count used as a lazy function, as well.
Group分組和聚合函數
聚合函數
除了可能出現的特殊情況之外,比如在DataFrames或通過.stat
,Chapter 6中有相關描述,所有聚合操作都可作爲一個函數。你可以在org.apache.spark.sql.functions
的包下找到大多數聚合函數
作者提出一個注意點:
可用的SQL函數與我們可以在Scala和Python中導入的函數之間存在一些差距。這會更改每個版本,因此沒有包含明確的差異函數列表。在本節中會介紹最常見的聚合操作
count 和 countDistinct
就是統計行數,後者是去掉重複值後的行數,用法同SQL中一樣
# import org.apache.spark.sql.functions._
scala> df.select(count($"StockCode")).show
+----------------+
|count(StockCode)|
+----------------+
| 541909|
+----------------+
scala> df.select(countDistinct($"StockCode")).show
+-------------------------+
|count(DISTINCT StockCode)|
+-------------------------+
| 4070|
+-------------------------+
當你要統計整個DataFrame的行數時,SQL 中可以用count(*)/count(1)
,而DataFrame 中我測試是可以df.select(count("*"))/df.select(count(lit(1)))
(因爲count接受Column參數,只傳個1進去會以爲1是Column的名字,會報錯)
還有就是 countDistinct
是一個DataFrame函數,在SQL不能這樣用的,SQL 中是 count(distinct xxx)
approx_count_distinct
這個函數我也是頭一次見,從字面意思看是近似無重複統計。書上也說是,如果你的數據集非常大,但準確的去重統計是無關緊要的,而某種精度的近似統計值也可以正常工作,你就可以使用approx_count_distinct
函數:
scala> df.select(approx_count_distinct($"StockCode",0.1)).show
+--------------------------------+
|approx_count_distinct(StockCode)|
+--------------------------------+
| 3364|
+--------------------------------+
approx_count_distinct
的第二個參數(rsd:Double
)是指定“允許的最大估計誤差”(默認0.05)。在這種情況下,我們指定了一個相當大的錯誤,並因此得到一個相差甚遠的答案,但比countDistinct
完成得更快。如果使用更大的數據集,性能將提升更多
Saprk 2.1.0之前還有一個
approxCountDistinct
函數,不過之後廢棄了
first 和 last
這個和SQL中用法一樣,返回組內Column列中第一個和最後一個value,當然first和last取決於窗口rows。其次這個函數的返回結果是不確定性的,取決於rows順序(如果進行shuffle操作,rows的順序就是不確定性的)
first(columnName: String, ignoreNulls: Boolean): Column
,ignoreNulls是否忽略null,即返回第一個非null值,但全爲null時只能返回null
first(columnName: String): Column
,默認ignoreNulls爲false
last(columnName: String): Column
,同上
last(columnName: String, ignoreNulls: Boolean): Column
min 和 max
沒啥說的
df.select(min("Quantity"), max("Quantity")).show()
+-------------+-------------+
|min(Quantity)|max(Quantity)|
+-------------+-------------+
| -80995| 80995|
+-------------+-------------+
sum 和 sumDistinct
也沒啥說的,求組內總和以及不含重複值的組內總和
scala> df.select(sum("Quantity"),sumDistinct("Quantity")).show
+-------------+----------------------+
|sum(Quantity)|sum(DISTINCT Quantity)|
+-------------+----------------------+
| 5176450| 29310|
+-------------+----------------------+
avg 和 mean
求平均值的,當然也可以用sum/count表示
scala> df.select(avg("Quantity"),mean("Quantity")).show
+----------------+----------------+
| avg(Quantity)| avg(Quantity)|
+----------------+----------------+
|9.55224954743324|9.55224954743324|
+----------------+----------------+
方差和標準差
方差和標準差是統計中另外兩個評估量,書裏這裏寫了一大串廢話,不過提到了方差是平方差與均值的平均值,標準差是方差的平方根。Spark 也提供了樣本標準差(the sample standard deviation)公式和總體標準差(the population standard deviation)公式,默認在使用方差或 stddev 函數時是用的樣本標準差公式
# var 是方差,stddev是標準差,samp結尾的就是樣本xxx,pop結尾就是總體xxx
df.select(var_pop("Quantity"),var_samp("Quantity"),stddev_pop("Quantity"),stddev_samp("Quantity")).show()
+-----------------+------------------+--------------------+---------------------+
|var_pop(Quantity)|var_samp(Quantity)|stddev_pop(Quantity)|stddev_samp(Quantity)|
+-----------------+------------------+--------------------+---------------------+
|47559.30364660923| 47559.39140929892| 218.08095663447835| 218.08115785023455|
+-----------------+------------------+--------------------+---------------------+
偏度和峯度
這兩是極值點的度量值,偏度度量的是數據中圍繞平均值的不對稱性,而峯度度量的是數據的尾部,Spark 中提供相關函數:
df.select(skewness("Quantity"), kurtosis("Quantity")).show()
+--------------------+------------------+
| skewness(Quantity)|kurtosis(Quantity)|
+--------------------+------------------+
|-0.26407557610528376|119768.05495530753|
+--------------------+------------------+
協方差和相關性
這兩方法涉及兩列之間聯繫
covar_samp(columnName1: String, columnName2: String): Column
,返回兩列的樣本協方差covar_pop(columnName1: String, columnName2: String): Column
,返回兩列的總體協方差corr(columnName1: String, columnName2: String): Column
,返回兩列間的皮爾遜相關係數
複雜數據類型的聚合
Spark 不僅可以在數值上聚合操作,也可以在複雜類型上執行聚合。比如收集給定列的值列表list,或者只收集給定列的唯一值集合set
scala> df.agg(collect_set("country"),collect_list("Country")).show
+--------------------+---------------------+
|collect_set(country)|collect_list(Country)|
+--------------------+---------------------+
|[Portugal, Italy,...| [United Kingdom, ...|
+--------------------+---------------------+
在表達式中使用分組(Grouping with Expressions)
表達式中使用和使用agg函數都是一樣用的
// in Scala
import org.apache.spark.sql.functions.count
df.groupBy("InvoiceNo").agg(
count("Quantity").alias("quan"),
expr("count(Quantity)")).show()
通過Maps映射使用分組(Grouping with Maps)
有時,可以更容易地將轉換指定爲一系列映射,其中Key爲列,Value爲希望執行的聚合函數(以字符串形式)。 如果你在行內指定多個列名,你也可以重複使用它們:
df.groupBy("InvoiceNo").agg("Quantity"->"avg","UnitPrice"->"max").show
df.groupBy("InvoiceNo").agg(expr("mean(Quantity)"),expr("max(UnitPrice)")).show
# sql
spark.sql("select mean(Quantity),max(UnitPrice) from dfTable group by InvoiceNo").show
待續。。。
窗口函數
分組集(Grouping Sets)
用戶自定義聚合函數(UDAF)
其他
收錄於此:josonle/Spark-The-Definitive-Guide-Learning
同步更新在掘金:《Spark 權威指南學習計劃》
更多推薦:
Coding Now學習記錄的一些筆記,以及所看得一些電子書eBooks、視頻資源和平常收納的一些自己認爲比較好的博客、網站、工具。涉及大數據幾大組件、Python機器學習和數據分析、Linux、操作系統、算法、網絡等