エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Kotlin版pandas !? Kotlin Dataframeを使ってデータ分析

この記事はエムスリー Advent Calendar 2022 12日目の記事です。

エンジニアの星川 (id:oboenikui) です。以前はAndroidアプリを書いてましたが、現在はサーバーサイドKotlinを書いています。

Kotlinといえば、Kotlin Festが12/10にありましたがご覧になったでしょうか? 弊社はひよこスポンサーで協賛しており、また私自身は運営スタッフとして参加しました。 まだご覧になっていない方は、アーカイブをぜひご覧ください。

閑話休題、Kotlinを開発しているJetBrains社が、最近Pythonの人気ライブラリやツールを意識したKotlinライブラリをいくつか開発していることはご存知でしょうか?

GitHubのKotlinオーガナイゼーション配下で公開されているものだけでも、以下のライブラリやツールが見つかります。

本記事では、この中でも特に今積極的に開発されているKotlin Dataframeを使ってみて、pandasとの機能比較に触れつつ基本的な操作について理解を深めていきます。

なお2022/12/12現在alpha版のため、ご使用の際はご注意ください。私はまだ業務では使ったことはありません。

うちで飼っているテールちゃんです。この記事にtail関数が出てくるので載せました。嘘です。ただのねこ自慢です。

Kotlin Jupyter Kernelをインストールする

Kotlin Dataframeはもちろん通常のKotlinプロジェクトで使うこともできますが、Jupyter上で真価を発揮します。そのためKotlin Jupyter Kernelをインストールしていきます。

以下ではPythonやJupyter Notebookなどが既にインストールされているものとして説明していきます。まだの場合はそれらを先にインストールしてください。

Kotlin Jupyter Kernelは以下のコマンドを実行するだけでインストールできます。

# condaの場合
conda install -c jetbrains kotlin-jupyter-kernel
# pipの場合
pip install kotlin-jupyter-kernel

実際にJupyterLabで開いたときの画面は以下のようになっています。

Kotlin Kernel導入後のJupyterLabのランチャー画面

JupyterLabのNotebook画面

また、もちろんブラウザで利用してもいいのですが、IntelliJ IDEAのKotlin Notebookプラグインをインストールすると、補完機能などが充実し、Kotlinユーザーに馴染みのある操作が可能になります。

IntelliJ IDEA上でKotlinのJupyter Notebookを開いた様子

Kotlin Dataframeをセットアップする

Jupyter Kernelをセットアップできたので、Kotlin Dataframeを使っていきましょう。

KotlinやJavaユーザーは「どうやってライブラリを追加するのか」と疑問に思うかもしれません。Kotlin Jupyter Kernelでは、2つの方法でライブラリのインポートが可能です。

1. %use を使う方法

一部の著名なライブラリは簡単にインポートできるようになっています。

例えば、今回使うKotlin Dataframeを使うには以下のように書くだけでインポートされます。

%use dataframe

実際に動かした様子

これは、パッケージ情報を定義するJSONファイルを公式で提供しているからです。

github.com

同じ形式のJSONで記述すれば、ここにないライブラリも以下のように追加可能です。

%use @https://raw.githubusercontent.com/Kotlin/kotlin-jupyter-libraries/master/dataframe.json

2. Mavenリポジトリのライブラリを使う方法

Mavenリポジトリのライブラリの追加も可能です。その場合は、import文に以下のようにアノテーションを付けます。

@file:Repository("https://repo1.maven.org/maven2/")
@file:DependsOn("org.jetbrains.kotlinx:dataframe:0.8.1")
import org.jetbrains.kotlinx.dataframe.*

(正確には内部でApache Ivyを使っているので、Ivyリポジトリも使えるそうです)

Kotlin Dataframeでファイルを読み込む

ここまででKotlin Dataframeを使えるようになったので、実際にデータを読み込んでみます。

ちなみに今回スクリーンショットで使っているのは、私が住む埼玉県のオープンデータから適当に持ってきた「建設工事・業務委託契約状況」のデータです。

まず、ファイルの読み込みは以下のように行います。

// 拡張子が適切に設定されている場合
DataFrame.read(/* file path */)
// 任意の拡張子でCSVファイルとして開く場合
DataFrame.readCSV(/* file path */)

Jupyter上で実行すると、Pandasと同じようにテーブル形式で表示されます。

ちなみにデフォルトでは、CSVの他にTSV, Excel, JSON, そしてApache ArrowのFeather形式に対応しています。

データの絞り込み

次にデータの絞り込みをしてみます。

特定の行・列を取得する

まず特定の行、列を取得する処理をpandasと比較します。

前提として、以下のようにdfという変数に格納されている状態を想定しています。

val df = DataFrame.readCSV(/* file path */)


処理内容 Kotlin Dataframeでの例 (参考) pandasでの例
行操作
特定の1行を取得 df[0] df[0:1]
特定の複数行を取得 df[10..14] df[10:15]
特定の複数区間の行を取得 df[20..24, 30..34] pd.concat([df[20:25], df[30:35]])
先頭N行を取得 df.head(3) df.head(3)
末尾N行を取得 df.tail(3) df.tail(3)
列操作
特定の1列をカラム名で取得 df["工事名"],
df.工事名 (Jupyter上のみ)
など
df["工事名"]
特定の連続する複数列をカラム名で取得 df["工事名".."契約金額"],
df.select { 工事名..契約金額 } (Jupyter)
など
df.loc[:, "工事名":"契約金額"]
特定の連続しない複数列をカラム名で取得 df["工事名", "契約金額"],
df.select { 工事名 and 契約金額 } (Jupyter)
など
df[["工事名", "契約金額"]]
特定の連続する複数行をインデックスで取得 df.select { cols(1..4) } df.iloc[:,1:5]

Kotlin Dataframeにあってpandasにない特徴として、カラムの指定方法の多様性が挙げられます。

上の表で書いたものも含め、以下のアクセス方法が提供されています。

  1. String API
  2. Column Accessors API
  3. Accessors API
  4. Extension properties API (df.工事名 などがこれにあたります)

これらに加え、上の表にも書いた df["工事名"] のようなシンプルなカラム名指定のアクセス方法もありますが、こちらは単純なカラム指定しかできないため、特に名前はついていないようです。

各APIについて解説すると長くなってしまうため割愛します。詳しくは公式ドキュメントをご覧ください。

Jupyter上では、Extension properties APIを使うのが非常に便利です。

このAPIは、Jupyter上で読み込んだCSVのカラム名から自動で拡張プロパティを生成してくれるという優れた機能です。カラムのデータから型情報も自動で設定してくれるため、コーディングのミスを減らすことも期待できます。

一応カラム名が日本語の場合も利用可能ですが、英数字で統一されているとより書きやすいと思います。

シンプルなカラム名指定とExtension properties APIで加工した様子

データの内容でフィルタする

たとえば、「発注課所が高校のデータ(高等学校という文字列を含むデータ)」に絞るには、以下のように書きます。

df.filter { 発注課所.contains("高等学校") }

発注課所が高等学校のデータ

「契約金額が1億円以上のデータ」に絞るには、以下のように書けます。

df.filter { 契約金額 >= 100_000_000 }

契約金額が1億円以上のデータ

上の両方を満たすデータは以下のように書けます。

df.filter { 発注課所.contains("高等学校") and (契約金額 >= 100_000_000) }

発注課所が高校かつ契約金額が1億円以上のデータ

まず、この書き方はKotlinエンジニアには慣れ親しんだものであり、またメソッドチェーンで別の処理も後に続けた場合に、pandasよりも読みやすいと私は感じます。

さらに、例えばデータ型がDoubleの「契約金額」カラムではStringの処理である contains はエラーとなり、逆もまた然りです。これをコンパイル時チェックでエラーとなるため、そもそもコーディング中に誤りに気付けるのです。これこそ静的型付け言語であるKotlinを使うメリットではないかと思います。

IDE上でエラーとなる

他にも様々な操作用の関数が提供されています。こちらも詳しくは公式ドキュメントをご覧ください。

統計

統計用関数もいくつか提供されています。例えば、最も高額な契約金額を取得するには以下のように書けます。

df.max { 契約金額 }

最も高額な契約金額

もちろん、フィルタと組み合わせることもできます。

// 高校の工事契約金額合計
df.filter { 発注課所.contains("高等学校") }.sum { 契約金額 }

高校の工事契約金額合計

統計用関数については、特に目新しいものは無いため、他の関数については公式ドキュメントをご覧ください。

グラフの作成

Kotlin Dataframe自体にはグラフのプロット機能はありませんが、これもJetBrains製のLets-Plot for Kotlinを使うことでグラフの描画が可能です。

例えば、発注課所ごとの契約金額合計上位20件のグラフは以下のようにプロットできます。

%use lets-plot
val sumPrices = df.groupBy { 発注課所 }
    .aggregate { sum { 契約金額 } into "合計契約金額" }
    .sortByDesc { "合計契約金額"<Double>() }
    .head(20)
letsPlot(sumPrices.toMap()) { x = "発注課所"; y = "合計契約金額" } +
    geomBar(stat = Stat.identity) +
    ggsize(850, 500)

Lets-Plot for Kotlinでグラフ描画

Lets-Plotはggplot2のAPIをベースに開発されているため、Pythonのggplot2に馴染みのあるエンジニアであればとっつきやすいかもしれません。

まとめと感想

Kotlin Dataframeを触ってみましたが、現在開発中とはいえ既に完成度が高いと感じました。ただ一部ドキュメントがまだTODOコメントのみとなっているため、そのあたりは今後に期待です。 特にJupyter連携で拡張プロパティを自動生成してくれる機能などにより、pandasより便利に使える場面がありそうで、機会があれば業務でも利用してみたいと感じさせられました。

また、KotlinのJupyter Kernelは以前からあったものの、ライブラリを使いづらいだろうという固定観念から私は全く触ってこなかったため、想像より完成度が高く驚きました。 こちらも今後活用していきたいです。

We are hiring!!

業務とは関係のない話をしてしまいましたが、KotlinエンジニアについてはAndroid, サーバーサイドどちらも絶賛募集中です!

カジュアル面談からでもぜひご応募ください!

jobs.m3.com