こんにちは、電通総研、Xイノベーション本部 AIトランスフォーメーションセンター所属の徳原光です。
普段は、主にPythonでPandasを使ってデータ分析やAI学習・推論のための特徴量を生成を行っていますが、最近KotlinでKotlin DataFrameを操作してデータ処理を実装する機会が増えました。
これまでローカル上で開発してきた機械学習モデルをONNXを使ってアンドロイドスマホで利用するために、スマホ上で動く前処理のコードを実装しているのですが、Kotlinを開発しているJetBrains社が開発しているKotlin DataFrameはデータサイエンスに必要な基本的な処理をサポートしているので非常に有効なライブラリですね。
ただ今年に入るまで、Kotlin DataFrameはおろかKotlinさえも書いたことがなかったので、PythonやPandasとの使用感の違いに戸惑いましたね。そこで、早く慣れるために、UdemyでKotlinの基礎を学びつつ、PandasとKotlin DataFrameの操作の対応表を実装時のチートシートとして公式ドキュメントから作成しました。
日ごろPandasの操作に慣れていると、一覧になっているPandasの処理からKotlin DataFrameの処理を引けると便利ですね。毎回DataFrameやDataColumn、それらが提供するメソッドの仕様を調べるより、実装速度が格段に上がりました。
Pandas ⇔ Kotlin DataFrame対応表
今回の実装で自分が使用した処理から(Pandas的に)基本的なものを抜粋しています。無理やり表にしているので改行が変ですがご容赦ください。
処理 | pandas | Kotlin DataFrame | ドキュメント |
---|---|---|---|
データフレーム生成 | df = pd.DataFrame({ "A": [2, 4, 6, 8, 10], "B": [3, 6, 9, 12, 15], "C": [5, 10, 15, 20, 25], "G": ["a", "b", "c", "a", "b"] }) | var df: DataFrame<*> = dataFrameOf("A", "B", "C", "G")( 2, 3, 5, "a", 4, 6, 10, "b", 6, 9, 15, "c", 8, 12, 20, "a", 10, 15, 25,"b") | https://kotlin.github.io/dataframe/createdataframe.html |
データフレーム読み込み | df = pd.read_csv('hogehoge.csv') | var df:DataFrame<*> = DataFrame.read("hogehoge.csv") | https://kotlin.github.io/dataframe/read.html |
冒頭表示 | df.head() | df.head() | |
データフレームを縦に結合 | pd.concat([df1, df2], axis=0) | listOf(df1, df2).concat()df1.concat(df2) | https://kotlin.github.io/dataframe/concat.html |
列を取得 | df[”A”] | df.getColumn {A} | https://kotlin.github.io/dataframe/getcolumn.html |
行を取得 | df.loc[0] | df[0]df[0..0](データフレームとして取得) | |
行をforループで一づつ取得 |
for index, row in row_df.iterrows(): print(row["A"]) |
df.forEach { println(it["A"]) } |
https://kotlin.github.io/dataframe/iterate.html |
行数を取得 | len(df) | df.rowsCount() | https://kotlin.github.io/dataframe/rowscount.html |
条件にあうデータを抽出 | df.loc[df["G"] == "a", :] | df.filter{ G == "a"} | https://kotlin.github.io/dataframe/filter.html |
カラムの値を更新 | df[”A”] = df[”A”] - 2 | df = df.update{ A }.with{it -2} | https://kotlin.github.io/dataframe/update.html |
カラムの値をDataColumnで取得 | df[”A”] | df.getColumn {A}df[”A”] | https://kotlin.github.io/dataframe/getcolumn.html |
カラム名のリストに該当するカラムを選択 |
list_col = ["A", "B"] df[list_col] |
val list_col = listOf("A", "B") df.select(*list_col.toTypedArray()) |
https://kotlin.github.io/dataframe/select.html |
カラム追加 | df['D'] = 1df['D'] = df['C'] - df['B'] | df.add("D") { 1 }df.add(”D”) { C - B } | https://kotlin.github.io/dataframe/add.html |
カラムを削除 | df.drop("A") | df.remove( "A" ) | https://kotlin.github.io/dataframe/remove.html |
カラム名を変更 | df.rename(columns={'A': 'K'}) | df.rename { A }.into( K ) | https://kotlin.github.io/dataframe/rename.html |
列の値を編集 | df[”A”] - 5 | df.getColumn {A}.map{ it -5} | https://kotlin.github.io/dataframe/getcolumn.html#getcolumngroup |
カラム名をリストで取得 | df.columns | df.columnNames() | https://kotlin.github.io/dataframe/columnnames.html |
カラム名をリストで変更 | df.columns = ["a", "b", "c"] |
val listCol = df.columnNames() for ( (oldCol, newCol) in listCol.zip(listOf("a", "b", "c"))) {df = df.rename { it[oldCol] }.into(newCol)} |
https://kotlin.github.io/dataframe/rename.html |
値を書き換え |
# 2 - B列の値を計算し列XとしてAに上書き df["A"] = 2 - df["B"]df = df.rename(columns={"A": "X"}) |
// 2 - B列の値を計算し列XとしてAに上書き df = df.replace("A").with{ 2 - B named "X" } |
https://kotlin.github.io/dataframe/replace.html |
集計 | df.groupby('G').agg(["count", "sum", "mean" ]) | df.groupBy("G").aggregate {count() into "count"count{ C > 6 } into "over 6"sum() into "sum"mean() into "mean"} | https://kotlin.github.io/dataframe/groupby.html#aggregation |
移動平均を計算 | window_size = 2df.rolling(window_size).mean().dropna() |
var rolling_df = dfval windowSize = 2 for (col in df.columnNames()){rolling_df = rolling_df.replace(col).with {it.values.map { (it as Number).toDouble() }.windowed(windowSize){ it.average() }.plus(List(windowSize - 1) { null }).toValueColumn(col)}}rolling_df = rolling_df.dropNulls() |
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/windowed.html |
1つ思ったのが、Pandas → Kotlin DataFrameと変換していくとKotlin DataFrame独自の便利な機能が身につきませんね。selectメソッドなんか後述の動画では使いこなせれば便利だと思うんですが、Pandas移植ではあまり使わなかったので残念です(これからめちゃ使う可能性はありますが)。
あと、KotlinはPyhton以上にラムダ式が重要だなと感じました。Pythonでは普段あまりラムダ式は使わないので、ラムダ式が頻繁に出てくることに苦戦しましたが、使いこなせればより効果的な実装ができるのではないでしょうか?
あたりまえですが、Kotlin DataFrameを使いこなすにはKotlin自体の理解が大切だと思いました。Javaは少し書いたことがありますが、基本Pythonしか書かない(書けない)人間なのでKotlinがもっと分かれば自由に実装できるのになと思う場面が多かったです。
Kotlin DataFrameによるスマホ上での前処理の実施
今回は、アプリを使用するユーザーの個人情報をモデルに多く入力するので、モデルによる推論をサーバー上で実行することができず、データの前処理もスマホ上で処理できるようにしましたが、ONNXとKotlin DataFrameを使えばモバイル端末上でAIモデルによる一連の処理を実行することも可能だと感じました。
もちろん、PCとスマートフォンでは処理能力が異なるので、PCでは一瞬で終わる処理もスマホ上では予想以上に時間がかかります。また、Pythonと比較するとモバイル端末上で利用できるKotlinやAndroid Javaのデータサイエンス系のライブラリは充実していないので、OSSを利用できず自分で実装しないといけなくなる処理がPythonよりも増えます。
あと、コンパイル言語なのでいちいちコンパイルしないといけないのも開発効率が下がる原因になりますね。逆に、今回Pythonのありがたみが身にしみて感じました。
Jupyter Kotlin kernelを使用すれば、Jupytern notebookでインタラクティブにKotlinのコードをセルごとに実行でき、開発効率がかなり向上しました。PytrhonからKotlinを学ぶ人には必須の環境なんじゃないでしょうか?
なんとなくKotlin DataFrameの雰囲気を理解したいなら
Kotlin DataFrameのキャッチアップには、こちらの動画がおすすめです。よくデータサイエンス関係の技術の説明で用いられるKaggleチュートリアルのTitanicコンペを題材にKotlin DataFrameでデータ分析をどのように進めるか解説されています。
まずは、PandasとKotlin DataFrameの違いはなにか感覚をつかむことが、重要かなと思います。といいつつ、まだまだ自分もよくわかってないですね。ただ、今回の経験でKotlinやAndroid アプリ開発に興味を持ちましたし(もともとモバイルアプリの開発はやりたかった)、AIモデルの応用先として、どこにも持ち運びができて、バッテリーで長時間稼働でき、カメラやマイクと言った入力データを収集するセンサーを持つスマートフォンは非常に重要なAIモデルの実行環境だと感じました。
最近のスマートフォンの処理能力は以前と比較して格段に向上しましたし、エッジデバイスとしてスマートフォンを利用するのではなく、AIサービスをモバイルデバイス上で完結して実行するのも今回の件でありだなと感じました。
そのためには、Kotlin DataFrameのようなデータサイエンスに不可欠な処理をサポートするライブラリはより重要性を増すのではないでしょうか?
最後に、私が所属しているAIトランスフォーメーションセンターでは、一緒に働いてくださる方を募集しております。こちらのページに採用に関する内容がまとめられております。また、カジュアル面談の募集もこちらのページからできますので、是非ご覧ください。
https://aitc.dentsusoken.com/recruit/recruit.html
それでは。
執筆:@tokuhara.hikaru、レビュー:@kinjo.ryuki
(Shodoで執筆されました)