Spark實現行列轉換pivot和unpivot

wait4friend發表於2019-03-03

背景

做過資料清洗ETL工作的都知道,行列轉換是一個常見的資料整理需求。在不同的程式語言中有不同的實現方法,比如SQL中使用case+group,或者Power BI的M語言中用拖放元件實現。今天正好需要在pyspark中處理一個資料行列轉換,就把這個方法記錄下來。

首先明確一下啥叫行列轉換,因為這個叫法也不是很統一,有的地方叫轉置,有的地方叫透視,不一而足。我們就以下圖為例,定義如下:

  • 從左邊這種變成右邊這種,叫透視(pivot)
  • 反之叫逆透視(unpivot)
image-20180611160900344

Spark實現

構造樣本資料

首先我們構造一個以格式儲存資料的資料集

from pyspark.sql import SparkSession
spark = SparkSession.builder.appName(`JupyterPySpark`).enableHiveSupport().getOrCreate()

import pyspark.sql.functions as F

# 原始資料 
df = spark.createDataFrame([(`2018-01`,`專案1`,100), (`2018-01`,`專案2`,200), (`2018-01`,`專案3`,300),
                            (`2018-02`,`專案1`,1000), (`2018-02`,`專案2`,2000), (`2018-03`,`專案x`,999)
                           ], [`年月`,`專案`,`收入`])
複製程式碼

樣本資料如下,我們可以看到,每一個專案在指定月份都只有一行記錄,並且專案是稀疏的。即,不是每個專案都會出現在每一個月份中,如專案2僅出現在2018-01當中。

+-------+---+----+
|  年月| 專案|  收入|
+-------+---+----+
|2018-01|專案1| 100|
|2018-01|專案2| 200|
|2018-01|專案3| 300|
|2018-02|專案1|1000|
|2018-02|專案2|2000|
|2018-03|專案x| 999|
+-------+---+----+
複製程式碼

透視Pivot

透視操作簡單直接,邏輯如下

  • 按照不需要轉換的欄位分組,本例中是年月
  • 使用pivot函式進行透視,透視過程中可以提供第二個引數來明確指定使用哪些資料項;
  • 彙總數字欄位,本例中是收入

程式碼如下

df_pivot = df.groupBy(`年月`)
                .pivot(`專案`, [`專案1`,`專案2`,`專案3`,`專案x`])
                .agg(F.sum(`收入`))
                .fillna(0)
複製程式碼

結果如下

+-------+----+----+---+---+
| 年月| 專案1| 專案2|專案3|專案x|
+-------+----+----+---+---+
|2018-03|   0|   0|  0|999|
|2018-02|1000|2000|  0|  0|
|2018-01| 100| 200|300|  0|
+-------+----+----+---+---+
複製程式碼

逆透視Unpivot

Spark沒有提供內建函式來實現unpivot操作,不過我們可以使用Spark SQL提供的stack函式來間接實現需求。有幾點需要特別注意:

  • 使用selectExpr在Spark中執行SQL片段;
  • 如果欄位名稱有中文,要使用反引號**`** 把欄位包起來;

程式碼如下

df_pivot.selectExpr("`年月`", 
                    "stack(4, `專案1`, `專案1`,`專案2`, `專案2`, `專案3`, `專案3`, `專案x`, `專案x`) as (`專案`,`收入`)")
            .filter("`收入` > 0 ")
            .orderBy(["`年月`", "`專案`"])
            .show()
複製程式碼

結果如下

+-------+---+----+
|     年月| 專案|  收入|
+-------+---+----+
|2018-01|專案1| 100|
|2018-01|專案2| 200|
|2018-01|專案3| 300|
|2018-02|專案1|1000|
|2018-02|專案2|2000|
|2018-03|專案x| 999|
+-------+---+----+複製程式碼

相關文章