きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

PySparkのadd_monthsでカラムを使う

EMRのバージョンの関係で今、PySpark 2.4.5を使っている。

Pythonという言語にはオーバーロードがないためなのか、Scalaでは用意されてるメソッドが呼び出せないなんてことが稀にある。

PySparkのadd_months(start, months)の
docstringの例で、startはColumnでmonthsは数値リテラルになっていて、monthsにColumnを渡すと、「TypeError: Column is not iterable」が発生してしまう。


解決策はexprを使うことが一番楽だった。
Is there an add_months with data driven number of months? - Databricks Community Forum

PySparkのコードを読む限りでは、
monthsをjavaカラムに変換してない。
Columnが来たら変換してくれればいいのに。
そのため、現状では絶対に数値リテラルしか無理だと言える。(litで包んでも意味がない)

Scala側は、add_months(Colum, int)の他に、
add_months(Column, Column)を持っている。

というわけで、PySpark→Py4Jのルートを一部迂回するのにexprを使うのが一番早い。


そもそもなんで「Column is not iterable」なんかになる理由は、深くは追えてないけど、だいたいこんな理由だろう。

py4jのpython側は、コマンドを作って、java側と通信する。コマンドを作る際、すでにJavaObjectになってるもの(javaカラムになっているもの)はコンバートしないが、そうではないものは複数あるコンバーターをループさせて変換をかける。コンバーターはコンバート可能かをチェックする関数があり、ListConverterは__iter__を持っていることをチェックしている。python側のColumnは__iter__を持っているので、ListConverterが適用されるのだが、いざ__iter__を呼び出してみるとTypeErrorが即座に返される。



私の使い方では、日付に対してたくさん計算するので、数値リテラルで実現するのは困難なので方法を調べていた。

日付
2020-01

みたいなデータに一旦arrayで加算したい月数を作る。リスト内包表記とlitを使って、arrayに渡す形。

日付 加算月
2020-01 [1,2,3,4,...]

ここからexplodeで行を増やす。

日付 加算月
2020-01 1
2020-01 2
2020-01 3
2020-01 4
2020-01 ...

で、これらを加算するのにadd_monthsを使う。