unsplash.com/@genessapana
因為業務需要(項目技術棧為
spark 2+
),七八月份興沖沖從學校圖書館借了書,學了
scala + spark
,還寫了不少博文,其中有幾篇被拿來發推送:Scala,一門「特立獨行」的語言!、【疑惑】如何從 Spark 的 DataFrame 中取出具體某一行? ...
但實際操作起來,還是遇到不少問題。
收獲經驗有二:
- 看書(尤其國内教材)了解了解概念還行,但是對于實際操作沒啥大用
- 接觸一門新的程式設計語言,首先應該閱讀大量優秀的案例代碼,還有了解清楚資料類型
舉個例子,我昨天上午一直被這個糾纏着:請你給
spark
中
dataframe
的某一列數
x_i
取為
sigmoid(x_i) = \frac{1}{1 - e^{-x_i}}
。
按理說不難吧。要是
python
的
pandas
就直接上了:
# pandas
df['sig_x'] = df['x'].apply(lambda x: 1 / (1 - np.exp(-x)))
複制
但是
spark
不行。
spark
中,建立一列使用的函數是
withColumn
,首先傳入函數名,接下來傳入一個 col 對象。
這個
col
對象就有講究了,雖然我今天看來還是比較直覺好了解的,但是昨天可就在裡面周旋了好一陣子。
首先,如果我想使用列 x ,我不可以直接 "x" ,因為這是一個字元串,我需要調用隐式轉換的函數
值得注意的是,
spark
是你的
SparkSession
執行個體。
上述内容不清楚,則需要花一陣子找資料。
import spark.implicits._
val df_new = df.withColumn("x_new", $"x")
複制
上述代碼構造了一個新
df_new
對象,其中有
x_new
列與
x
列,兩列數值完全一緻。
其次,我的運算函數在哪裡找呢?
答案是
org.apache.spark.sql.functions
,因為是
col
對象,其可能沒有重載與常數資料類型的
+
-
*
/
運算符,是以,如果我們
1 - $"x"
可能會報錯:因為
#"x"
是
col
,而
1
隻是一個
Int
。
我們要做的就是把
1
變成一個
col
:苦苦查閱資料後,我找到了
lit
方法,也是在
org.apache.spark.sql.functions
中。最終的方案如下。
import spark.implicits._
import org.apache.spark.sql.functions.{fit, exp, negate}
val df_result = df_raw_result
.withColumn("x_sig",
lit(1.0) / (lit(1.0) + exp(negate($"x")))
)
複制
其實,實際的代碼比上面的還要複雜,因為 "x" 列裡面其實是一個 vector 對象,我直接
import spark.implicits._
import org.apache.spark.sql.functions.{fit, exp, negate, udf}
// 取向量中的第一個元素
val getItem = udf((v: org.apache.spark.ml.linalg.DenseVector, i: Int) => v(i))
val df_result = df_raw_result
.withColumn("x_sig",
lit(1.0) / (lit(1.0) + exp(negate(getItem($"x", lit(0)))))
)
複制
python 和 scala ?
看起來,似乎
python
下的操作更加簡潔優雅,但我更喜歡用
scala
書寫這種級别的項目。
原因很簡單,
scala
對于類型的嚴格要求已經其從函數式程式設計那裡借鑒來的思想,讓代碼寫得太爽了。大部分問題,編譯期就能發現,而且配合上 IDEA 的自動補全,真的很舒服。
目前為止,還沒有弄懂
udf
代表着什麼,基礎文法與架構思想這裡還是有待查缺補漏。