
2026/03/22 18:55
Clojure におけるデータ操作:R と Python と比較して
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本記事では、Clojure の
、R のtablecloth、Python のtidyverse/dplyr、そして Polars の 4 つのデータサイエンスライブラリを比較し、https://codewithkira.com/assets/penguins.csv から取得した Palmer Penguin CSV データセットを使用しています。pandas
読み込み:は文字列 “NA” を自動的に欠損(tablecloth)として扱います;nilはデフォルトでそれを認識します;R ではpandasにread_csvが必要です;Polars はna = "NA"を使用します。null_values="NA"
探索: 各ライブラリは同様のコマンド(、列名表示、ソート)を提供しますが、欠損値を扱う際の構文や行選択方法に違いがあります。head
フィルタリング:のtableclothは明示的にselect-rowsを除外できる予測関数を受け取ります;nilはブールマスクを使用します;dplyr と Polars も同等のフィルタ構文を提供します。pandas
機能チェーン: Clojure は一次関数とスレッドファーストマクロ()を利用して簡潔なパイプラインを作成し、対照的に->/Polars は命令型の変更スタイル、tidyverse は宣言型動詞を採用します。pandas
列選択: 各エコシステムで「すべての列から 1 列除外」「接頭辞一致」「数値列」「範囲フィルタ」など高度なセレクタが示されています。
リシェイプ: ピボットは(tablecloth)、pivot->longer(dplyr)、pivot_longer(pandas)、およびpd.melt(Polars)で行われます。unpivot
列追加:は不変に列を追加します(tablecloth);一方、ratio = bill_length_mm / flipper_length_mmは DataFrame をインプレースで変更します。pandas
リネーム: すべての列はで大文字に変換できます;他のライブラリでも同等のマッピングや変換関数が存在します。:all str/upper-case
グルーピングと集計: 例として、種別ごとのカウントや各種別で最小体重を求める方法(/group-by、aggregate、group_by/summarise、および.agg())が示されています。group_by().agg()
この比較は、すべてのライブラリが同様のタスクを実行できる一方で、設計哲学(関数型不変性対命令型変更)がコードの可読性・保守性・パフォーマンスに影響することを強調しています。今後はより詳細なベンチマークやライブラリセットの拡張を行い、のような不変アプローチが長期的にメリットを提供できるかどうかをチームが判断できるようにすることが検討されます。tablecloth
本文
発行日 2024‑07‑18
私はデータを扱うための Clojure のオープンソースツールを開発・教えることに多くの時間を費やしています。
この種の作業に Clojure を使おうとするほぼすべての人は、別の言語環境(主に R か Python)から来ています。Daniel Slutsky と共に、こうした背景を持つ方々向けに「翻訳」も含めた講座を体系化しています。
その一環として、このブログ記事で初期プレビューを共有したいと思います。
形式は、R と Polars を並べて比較した素晴らしい投稿(ここでの R は tidyverse ― データサイエンス向けに設計された R ライブラリ群)を読んだことからインスパイアされました。Pandas もデータセット操作ライブラリとして非常に人気があるため入れ、Clojure では tablecloth(我々のエコシステムで主要なデータ操作ライブラリ)を加えます。
元記事と同じ Palmer Penguin データセットを使います。
簡略化のため、CSV ファイルとしてコピーし、このサイトに公開しています。また、本記事では「dataset」という言葉を繰り返し使用しますが、Clojure ではテーブル型(列主導)のデータ構造であり、他言語の dataframe や data table と同等です。
必要なパッケージのインストール方法は既にご存知だと仮定し、初回出現時点でコードスニペットに必要なインポート・要件を含めます。では始めましょう!
データ読み込み
データを読むことはすべての言語で簡単ですが、便利なのは「欠損値」として解釈する値を即座に指定できる点です。このデータセットでは文字列
"NA" が欠損値を表します。そこで、できるだけ早くコンストラクタに伝えます。
| ライブラリ | コード |
|---|---|
| Tablecloth | |
| R | |
| Pandas | |
| Polars | |
注:tablecloth はデフォルトで
を欠損値(Clojure では "NA"
)として解釈します。nil
データ探索の基本操作
以下は各ライブラリで基本的な探索タスクを行う方法を比較したものです。
| 操作 | Tablecloth | dplyr | Pandas | Polars |
|---|---|---|---|---|
| 先頭10行を見る | | | | |
| 全列名を確認 | | | | |
| 1列選択 | | | | |
| 複数列選択 | | | | |
| 行をフィルタ | | | | |
| 列でソート | | | | |
ソート時の欠損値処理はライブラリごとに異なります。
tablecloth と polars では昇順で先頭、降順で末尾に配置されますが、dplyr と pandas はどちらも最後に置きます。
Tablecloth の行選択
Clojure の匿名関数を短縮書式で書くと次のようになります。
(tc/select-rows ds #(> (% "year") 2008))
Clojure の関数は第一級オブジェクトなので、
select-rows の第3引数は行(列名→値のマップ)を受け取る predicate です。Tablecloth は行を一つずつ処理せず、カラム単位で最適化された操作を実行します。
基本操作の組み合わせ例
body_mass_g が 3800 以下のすべてのペンギンに対し bill_length_mm を取得する方法です。
| ライブラリ | コード |
|---|---|
| Tablecloth | ```clojure (-> ds |
| (tc/select-rows #(and (% "body_mass_g") (> (% "body_mass_g") 3800))) | |
| (tc/select-columns "bill_length_mm"))``` | |
| dplyr | ```r ds |
| Pandas | |
| Polars | |
Tablecloth では欠損値(
)は数値と比較できないため、明示的に除外する必要があります。nil
これは Clojure が動的でありながらも強く型付けされている性質を示しています。
thread-first マクロ
Tablecloth は
->(thread‑first)マクロを使用し、R のパイプや Unix パイプに似た形で関数の出力を次へ渡します。
より高度なフィルタリングと選択
| タスク | ライブラリ | コード |
|---|---|---|
| 1つの列を除外して全列選択 | Tablecloth | |
| dplyr | | |
| Pandas | | |
| Polars | | |
| 特定文字列で始まる全列選択 | Tablecloth | |
| dplyr | | |
| Pandas | | |
| Polars | | |
| 数値列のみ選択 | Tablecloth | |
| dplyr | | |
| Pandas | | |
| Polars | | |
| 値の範囲で行をフィルタ | Tablecloth | |
| dplyr | | |
| Pandas | | |
| Polars | |
Tablecloth の欠損値処理は柔軟で、マップ検索時にデフォルト値を指定できます(例:
)。(% "body_mass_g" 0)
データセットの再構成
| 操作 | ライブラリ | コード |
|---|---|---|
| 長形式へピボット | Tablecloth | |
| dplyr | `ds | |
| Pandas | | |
| Polars | |
列の作成・リネーム
既存列に基づく新列追加
| ライブラリ | コード |
|---|---|
| Tablecloth | |
| dplyr | |
| Pandas | |
| Polars |
Clojure のデータ構造は不変なので、tablecloth は元のデータセットを変更せず新しいものを生成します。
Pandas はインプレースで変更し、認知的負荷が増す場合があります。
列名リネーム
| ライブラリ | コード |
|---|---|
| Tablecloth | |
| dplyr | |
| Pandas | |
| Polars |
列名の変換
| タスク | ライブラリ | コード |
|---|---|---|
| 全列を大文字に | Tablecloth | |
| dplyr | | |
| Pandas | | |
| Polars | | |
| 単位を列名から除去 | Tablecloth | `tc/rename-columns ds #".+_(mm |
| dplyr | `rename_with(penguins, ~ str_replace(.x, "^(.+)_(mm | |
| Pandas | ```python import re ds = ds.rename(columns=lambda x: re.sub(r"(.+)_(mm | |
| Polars | |
グループ化と集約
Tablecloth のグループ化は少し特殊です。
単一列名、複数列名、あるいは任意関数でグループ化できます。結果は「group name」「group id」「ネストされたデータセット」の3列からなる新しいデータセットになります。
| タスク | ライブラリ | コード |
|---|---|---|
| 種別ごとの件数集計 | Tablecloth | ```clojure (-> ds |
| (tc/group-by ["species"]) | ||
| (tc/aggregate {"count" tc/row-count}))``` | ||
| dplyr | `ds | |
| Pandas | | |
| Polars | | |
| 種別ごとの最低体重 | Tablecloth | ```clojure (-> ds |
| (tc/group-by ["species"]) | ||
| (tc/aggregate {"lowest_body_mass_g" #(->> (% "body_mass_g") |
tcc/drop-missing (apply tcc/min))}))``` |
| | dplyr |
ds |> group_by(species) |> summarise(lowest_body_mass_g = min(body_mass_g, na.rm = TRUE)) |
| | Pandas | python ds.groupby("species").agg( lowest_body_mass_g=("body_mass_g", lambda x: x.min(skipna=True)) ).reset_index() |
| | Polars | ds.group_by("species").agg(pl.col("body_mass_g").min().alias("lowest_body_mass_g")) |
結論
これらすべてのライブラリは共通のデータ操作タスクを実行できます。
言語とライブラリの選択は、コードの可読性・保守性・パフォーマンスに影響します。
Clojure の tablecloth は関数型プログラミングと不変性を重視し、より予測可能で再利用性の高いコードを提供しますが、新しいパラダイムへの適応が必要です。
この比較が「翻訳ガイド」としてだけでなく、各データサイエンスツールに根ざす哲学を紹介する一助となれば幸いです。
読んでいただきありがとうございます! 🙂
バージョン
| ツール | バージョン |
|---|---|
| MacOS | Sonoma 14.5 |
| JVM | 21.0.2 |
| Clojure | 1.11.1 |
| Tablecloth | 7.02.1 |
| R | 4.4.1 (tidyverse 2.0.0) |
| Python | 3.12.3 |
| Pandas | 2.1.4 |
| Polars | 1.1.0 |