spark常用功能:使用Spark計算數列統計值

參考 :

先來回顧一下數據和對應的統計結果:

本文使用的是iris分類數據集,數據下載地址爲:

http://archive.ics.uci.edu/ml/datasets/Iris

下載後轉換爲xlsx格式的文件,數據如下:

對應的統計結果如下:

在介紹之前,我還是想先說明一點,這一篇只是想先帶大家體驗一把Spark SQL,相關更多關於原理相關的知識,咱們會在後面的文章中詳細介紹。

1、數據導入
這裏咱們通過讀取Excel的方式讀取出相應的數據,並得到一個DataFrame:

def createDFByCSV(spark:SparkSession) = {
    val df = spark.sqlContext.read.format("com.databricks.spark.csv")
      .option("header","true") //這裏如果在csv第一行有屬性的話,沒有就是"false"
      .option("inferSchema",true.toString)//這是自動推斷屬性列的數據類型。
      .load("resources/iris.csv")

    df.show()
  }

結果如下:

2、使用Spark SQL計算統計值
2.1 最大值、最小值
使用Spark SQL統計最大值或者最小值,首先使用agg函數對數據進行聚合,這個函數一般配合group by使用,不使用group by的話就相當於對所有的數據進行聚合。

隨後,直接使用max和min函數就可以,想要輸出多個結果的話,中間用逗號分開,而使用as給聚合後的結果賦予一個列名,相當於sql中的as:

import spark.implicits._

    df.agg(max($"feature1") as "max_feature1",
        min($"feature2") as "min_feature2")
      .show()

結果輸出如下:

上面的$代表一列的意思,相當於col函數:

df.agg(max(col("feature1")) as "max_feature1",
        min(col("feature2")) as "min_feature2")
      .show()

1.2 平均值
平均值的計算使用mean函數:

df.agg(mean($"feature1") as "mean_feature1",
      mean($"feature2") as "mean_feature2").show()

輸出爲:

1.3 樣本標準差&總體標準差
樣本標準差的計算有兩個函數可以使用,分別是stddev函數和stddev_samp函數,而總體標準差使用stddev_pop方法。需要注意的一點是,這裏和hive sql是有區別的,在hive sql中,stddev函數代表的是總體標準差,而在spark sql中,stddev函數代表的是樣本標準差,可以查看一下源代碼:

通過代碼驗證一下:

df.agg(stddev($"feature1") as "stddev_feature1",
      stddev_pop($"feature1") as "stddev_pop_feature1",
      stddev_samp($"feature1") as "stddev_samp_feature1").show()

輸出結果爲:

1.4 中位數
SparkSQL中也沒有直接計算中位數的方法,所以我們還是借鑑上一篇中的思路,再來回顧一下:

計算中位數也好,計算四分位數也好,無非就是要取得兩個位置嘛,假設我們的數據從小到大排,按照1、2、3、.. 、n進行編號,當數量n爲奇數時,取編號(n + 1)/2位置的數即可,當n爲偶數時,取(int)(n + 1)/2位置和(int)(n + 1)/2 + 1位置的數取平均即可。但二者其實可以統一到一個公式中:

1)假設n = 149 ,(n+1)/2 = 75 ,小數部分爲0,那麼中位數=75位置的數 (1 - 0)+ 76位置的數 (0 - 0)
2)假設n = 150,(n+1)/2 = 75,小數部分爲0.5,那麼中位數=75位置的數 (1 - 0.5)+ 76位置的數 (0.5 - 0)

所以,可以把這個過程分解爲三個步驟,第一步是給數字進行一個編號,spark中同樣使用row_number()函數(該函數的具體用法後續再展開,這裏只提供一個簡單的例子),第二步是計算(n+1)/2的整數部分和小數部分,第三步就是根據公式計算中位數。

首先使用row_number()給數據進行編號:

val windowFun = Window.orderBy(col("feature3").asc)
df.withColumn("rank",row_number().over(windowFun)).show(false)

輸出如下:

接下來是確定中位數的位置,這裏我們分別拿到(n + 1)/2的整數部分和小數部分:

val median_index = df.agg(
  ((count($"feature3") + 1) / 2).cast("int") as "rank",
  ((count($"feature3") + 1) / 2 %  1) as "float_part"
)

median_index.show()

輸出如下:

這裏小數部分不爲0,意味着我們不僅要拿到rank=75的數,還要拿到rank=76的數,我們最好把其放到一行上,這裏使用同樣lead函數,lead函數的作用就是拿到分組排序後,下一個位置或下n個位置的數,咱們在後面的博客中還會細講,這裏也只是拋磚引玉:

val windowFun = Window.orderBy(col("feature3").asc)
df.withColumn("next_feature3",lead(col("feature3"),1).over(windowFun)).show(false)

輸出如下:

接下來,join兩個表,按公式計算中位數就可以啦,完整的代碼如下:

val median_index = df.agg(
  ((count($"feature3") + 1) / 2).cast("int") as "rank",
  ((count($"feature3") + 1) / 2 %  1) as "float_part"
)

val windowFun = Window.orderBy(col("feature3").asc)

df.withColumn("rank",row_number().over(windowFun))
  .withColumn("next_feature3",lead(col("feature3"),1).over(windowFun))
  .join(median_index,Seq("rank"),"inner")
  .withColumn("median" ,($"float_part" - lit(0)) * $"next_feature3" + (lit(1) - $"float_part") * $"feature3")
  .show()

輸出如下:

1.5 四分位數
先來複習下四分位數的兩種解法,n+1方法和n-1方法:

對於n+1方法,如果數據量爲n,則四分位數的位置爲:

Q1的位置= (n+1) × 0.25
Q2的位置= (n+1) × 0.5
Q3的位置= (n+1) × 0.75

對於n-1方法,如果數據量爲n,則四分位數的位置爲:

Q1的位置=1+(n-1)x 0.25
Q2的位置=1+(n-1)x 0.5
Q3的位置=1+(n-1)x 0.75

這裏的思路和求解中位數是一樣的,我們分別實現一下兩種方法,首先是n+1方法:

val q1_index = df.agg(
  ((count($"feature3") + 1) * 0.25).cast("int") as "rank",
  ((count($"feature3") + 1) * 0.25 %  1) as "float_part"
)

val windowFun = Window.orderBy(col("feature3").asc)

df.withColumn("rank",row_number().over(windowFun))
  .withColumn("next_feature3",lead(col("feature3"),1).over(windowFun))
  .join(q1_index,Seq("rank"),"inner")
  .withColumn("q1" ,($"float_part" - lit(0)) * $"next_feature3" + (lit(1) - $"float_part") * $"feature3")
  .show()

輸出爲:

接下來是n-1方法:

val q1_index = df.agg(
  ((count($"feature3") - 1) * 0.25).cast("int") + 1 as "rank",
  ((count($"feature3") - 1) * 0.25 %  1) as "float_part"
)

val windowFun = Window.orderBy(col("feature3").asc)

df.withColumn("rank",row_number().over(windowFun))
  .withColumn("next_feature3",lead(col("feature3"),1).over(windowFun))
  .join(q1_index,Seq("rank"),"inner")
  .withColumn("q1" ,($"float_part" - lit(0)) * $"next_feature3" + (lit(1) - $"float_part") * $"feature3")
  .show()

輸出爲:

參考。
spark常用功能:使用Spark計算數列統計值

3、踩坑總結
在計算中位數或者四分位數時,我一開始的寫法如下:

很奇怪的一點是,$"float_part" - 0沒有報錯,1 - $"float_part"卻報錯了,報的錯誤是:

看這裏大家應該明白了,$"float_part" - 0中,減號左右兩邊的數據都應該是列名,與$"float_part" 類型相同,但是1 - $"float_part"兩邊都應該是個數字,與1的類型相同,所以後面一個報錯了。

因此修改的方法是:

使用lit方法創建了一個全爲0或者全爲1的列,使得減號左右兩邊類型匹配。

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