Streamを捨てて、JDFrameでJavaデータ分析を書き換える

Java 8のStream APIは強力だが、覚えるべきメソッドが多く、チェーンが長くなると可読性が落ちる。そこで、SparkやPandasのDataFrameライクな操作をJVM上で実現する軽量ライブラリ「JDFrame」を紹介する。SQLに近い直感的なAPIで、集計・フィルタ・ソート・結合を数行で記述できる。

導入

<dependency>
    <groupId>io.github.burukeyou</groupId>
    <artifactId>jdframe</artifactId>
    <version>0.1.7</version>
</dependency>

動作イメージ

以下のような学生リストを考える。

class Pupil {
    int id;
    String name;
    String campus;
    String grade;
    Integer age;
    BigDecimal point;
}

「年齢が9~16歳でポイントが存在する生徒をキャンパス別に合計し、上位2キャンパスを抽出」という処理は、

SDFrame<FI2<String, BigDecimal>> summary =
    SDFrame.read(pupils)
           .whereNotNull(Pupil::getAge)
           .whereBetween(Pupil::getAge, 9, 16)
           .groupBySum(Pupil::getCampus, Pupil::getPoint)
           .sortDesc(FI2::getC2)
           .head(2);

summary.show();

出力例

c1   c2
三中 10
二中  7

主なAPI一覧

行列情報

  • show(n):先頭n行をテーブル形式で表示
  • columns():列名一覧を取得
  • head()/tail():先頭/末尾の要素を取得

フィルタリング

SDFrame.read(pupils)
       .whereBetween(Pupil::getAge, 3, 6)      // 閉区間
       .whereBetweenR(Pupil::getAge, 3, 6)     // 左開区間
       .whereNotNull(Pupil::getName)           // null/空文字除外
       .whereIn(Pupil::getAge, List.of(3,7,8))
       .whereLike(Pupil::getName, "a");        // %a%

集約

JDFrame<Pupil> df = JDFrame.from(pupils);
Pupil eldest   = df.max(Pupil::getAge);        // 最大レコード
BigDecimal avg = df.avg(Pupil::getAge);        // 平均年齢
MaxMin<Pupil> range = df.maxMin(Pupil::getAge);

重複除去

SDFrame.read(pupils)
       .distinct(Pupil::getCampus)             // キャンパス名で重複除去
       .distinct(p -> p.getCampus() + p.getGrade());

グループ化集計

// select campus, sum(point) ...
List<FI2<String, BigDecimal>> sumByCampus =
    df.groupBySum(Pupil::getCampus, Pupil::getPoint).toLists();

// select campus, grade, avg(age) ...
List<FI3<String, String, BigDecimal>> avgByCampusGrade =
    df.groupByAvg(Pupil::getCampus, Pupil::getGrade, Pupil::getAge).toLists();

ソート

SDFrame.read(pupils)
       .sortDesc(Pupil::getAge)                // age DESC
       .sortAsc(Pupil::getGrade);              // grade ASC

結合

SDFrame<Pupil> left  = SDFrame.read(pupils);
SDFrame<Reward> right = SDFrame.read(rewards);

SDFrame<Joined>> result = left.join(
        right,
        (l, r) -> l.getId() == r.getPupilId(),   // ON条件
        (l, r) -> new Joined(l, r.getReward())   // 射影
);

便利ユーティリティ

  • mapPercent:値をパーセント変換
  • partition(n):n件ずつに分割
  • addSortNoCol:連番を付与
  • addRankingSameColDesc:同値同順のランキング付与
  • replenish:不足ディメンションを埋める

補完例:欠損月の自動追加

List<String> allMonths = List.of("2023-01","2023-02","2023-03");
SDFrame.read(monthlySales)
       .replenish(Sale::getMonth, allMonths,
                  m -> new Sale(m, BigDecimal.ZERO));

JDFrameは、冗長なStream記述をSQLライクに置き換え、メンテナンスしやすいコードへと導いてくれる。

タグ: JDFrame DataFrame Java8 Stream Aggregation

6月25日 00:23 投稿