Pandasって?
Pandasは、DataFrameという表形式のデータ構造を中心に、様々なデータ処理ができるライブラリ。
データベースでいうところのテーブルなので、SQL知ってればすぐに入門できる。Pythonでデータ分析する人にはお馴染み。
どういう経緯で導入したの?
伝聞情報も多いけれど、こんな流れで開発チームに導入されたのだと思う。
- オンプレミスとクラウドのハイブリッド化が進み、どんどんデータベース・ストレージが分散していく。
- データフローの管理が課題となり、Pythonでデータフローを組めるLuigiが導入される。
- 当初、Luigiは主にデータベース・ストレージへの入出力を担う予定だった。
- チームの共通言語がScalaな事もあり、ロジックは外に切りだして堅く実装する予定だった。
- Luigiから各データベース・ストレージへ接続できる環境を作る。
- 簡単な転送やレポートのデータフローがLuigiで組まれていく。
- これは便利ということで移行や改修が進み、だんだんとフィルタ・結合・集計といった処理が入りこんで、自然とPandasが使われる。
- 気づけば、いくつかのバッチ処理がPandasに依存している状態になり、色々とハマる。
主にハマったところ
欠損値問題
欠損値NaNがfloat扱いのため、intの列に欠損値が混ざった瞬間に、列ごとfloatへキャストされる。
型情報が壊されると、とくにデータベースへ投入するとき問題になりやすい。
>>> s = pd.Series([0, 1, 2])
>>> s[2]
2
>>> s[1] = np.nan
>>> s[2]
2.0
参照問題
ちょっとしたインデックス操作しただけで、viewかcopyか不確定な状況に追いこまれる(!?)
def do_something(df):
foo = df[['bar', 'baz']] # Is foo a view? A copy? Nobody knows!
# ... many lines here ...
foo['quux'] = value # We don't know whether this will modify df or not!
return foo
こうなると、いくらテストをしても品質は保証されない。
実行時にWarning吐くこともあるけど、怪しいところは明示的にcopyメソッドを呼びだすしか……。
突然の死
とあるバッチのログを見ると、1%くらいの確率で死んでる。
メモリ関連が多く、コアダンプが増殖したりする。あとフリーズもする。
*** glibc detected *** /usr/local/anaconda/bin/python: free(): invalid pointer:
Fatal Python error: GC object already tracked
_人人 人人_
> 突然の死 <
 ̄Y^Y^Y^Y ̄
Python 2.7 & Pandas 0.17 環境なので、アップデートしたら解決するかも……。
今後どうしていくか
これからの新規開発では、できるだけLuigiごとPandasは使わない方針。
やはりPandasは分析用であって、バッチで使ってしまったのがそもそも良くなかった……。
とはいえ分析目的であっても、個人的には参照問題が致命的に感じるので、今後DataFrameが欲しくなったらSparkを使うつもりです。
ただ静的型付けなScalaで書けるとはいえど、肝心のスキーマ操作にはコンパイルチェック効かないので注意。
catsを使ったライブラリframelessもありますが、あくまでproof-of-conceptです。
ちなみにLuigiは、タスクごとに冪等性があって1つの出力データを前提としているので、組むデータフローによって向き不向きがあります。
そしてLuigiの開発元Spotifyは、Google Cloud Dataflowに移行して、Scalaのラッパライブラリscioを開発してるみたい……。