《Spark: The Definitive Guide 》Chapter 7:聚合操作

Chapter 7:聚合操作

聚合操作相關函數的性質是對每一個group而言是多輸入單輸出。Spark 有複雜和成熟的聚合操作,具有各種不同的使用方法和可能性。在 Spark 中也可以對任何數據類型進行聚合,包括複雜數據類型

Spark 允許我們創建以下group分組:

  • 最簡單的group分組僅僅是在select子句中通過聚合來彙總一個完整的DataFrame
  • 通過group by來指定一或多個key並通過一或多個聚合函數來轉換Columns的值
  • 通過窗口函數來分組,功能上和group by類似,但輸入聚合函數的rows和當前row有關(就是說窗口大小如何指定)
  • 通過分組集(grouping sets),這是可以用來在不同層級上進行聚合操作。在SQL中可以直接使用分組集,而在DataFrame中可通過rollupcube操作

我理解是這樣的,原文是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、操作系統、算法、網絡等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章