matten: 不均一なデータを `--features dynamic` で扱う

rust dev.to

これは matten を紹介する全 4 回シリーズの第 3 回です。前回は数値計算のコア部分を紹介しました。今回は雑然とした実世界のデータを取り込むしくみについてです。


課題事項

前回紹介した Tensor (数値テンソル)は、つくられた時点からクリーンです。すべてのセルが f64 です。データがすでにクリーンであれば、それで問題ありません。しかし、データが JSON API や CSV ファイル経由で投入され、その中に欠損セルや、整数と浮動小数点の混在、あるいは bool 値(フラグ)が含まれているような場合となると、どうでしょうか。話は変わって来ます。

dynamic feature は、そのような場合の助けになります。データ取り込みとクリーニングのレイヤーを追加します。ただし、これは第二の計算エンジンではありません — dynamic テンソルで直接算術演算はできません。考え方はもっとシンプルです。不均一なデータを取り込み、検査してクリーニングし、データが整ったと確信したら数値テンソルに明示的に変換します。

次のようにして有効化できます:

matten = { version = "0.28", features = ["dynamic"] }
Enter fullscreen mode Exit fullscreen mode

混在データの取り込み

from_json_dynamicfrom_csv_dynamic は、型が混在したデータを受け付けます。各セルは Element のバリアントに格納されます:FloatIntBoolText、または None(JSON の null や CSV の空フィールド)。

use matten::{NumericPolicy, Tensor};

// 数値の種類が混在し、かつ欠損セルが存在する JSON テーブル
let json = "[[1, 2.5, null], [4.0, 5, 6]]";
let t = Tensor::from_json_dynamic(json)?;

assert!(t.is_dynamic());
assert_eq!(t.shape(), &[2, 3]);
assert_eq!(t.count_none(), 1);
Enter fullscreen mode Exit fullscreen mode

同様の処理は CSV でも動きます:

use matten::Tensor;

let csv = "10.0,20.0,30.0\n40.0,,60.0\n70.0,80.0,\n"; // 空セルが 2 つ
let t = Tensor::from_csv_dynamic(csv)?;

assert_eq!(t.count_none(), 2);
Enter fullscreen mode Exit fullscreen mode

フォーマットが異なるだけで、ワークフローは同じです。

欠損値の検査

クリーニングの前に、どこに欠損があるか確認できます:

// none_mask: セルごとに 0.0 / 1.0 の数値テンソル
let mask = t.none_mask();
assert_eq!(mask.get(&[0, 2]), Some(1.0)); // [0,2] に null
assert_eq!(mask.get(&[0, 0]), Some(0.0)); // 値あり

// schema_summary で型の内訳を読みやすく確認
println!("{}", t.schema_summary());
// 例: "Float: 4, Int: 1, None: 1"
Enter fullscreen mode Exit fullscreen mode

数値テンソルへの変換

変換ステップは意図的に明示的にしています。try_numeric() は厳格です。NoneBoolText が 1 つでもあれば変換を拒否します:

// データに null があるため失敗する
assert!(t.try_numeric().is_err());
Enter fullscreen mode Exit fullscreen mode

try_numeric_with(policy) を使うと、各バリアントをどう扱うかを指定できます:

use matten::NumericPolicy;

// None を 0.0 として扱い、Int と Float はそのまま f64 に
let clean = t.try_numeric_with(NumericPolicy::default().none_as(0.0))?;

assert!(!clean.is_dynamic());
assert_eq!(clean.as_slice(), &[1.0, 2.5, 0.0, 4.0, 5.0, 6.0]);
Enter fullscreen mode Exit fullscreen mode

その他の指定オプション:

// none_as_nan: 欠損を指定した値ではなく f64::NAN に
let p = NumericPolicy::default().none_as_nan();

// allow_bool: true → 1.0, false → 0.0
let p = NumericPolicy::default().allow_bool();

// allow_text_parse: テキストセルを f64 としてパースを試みる
let p = NumericPolicy::default().allow_text_parse();

// オプションを組み合わせる
let p = NumericPolicy::default().none_as(0.0).allow_bool();

// すべてのバリアントを受け入れる
let p = NumericPolicy::permissive();
Enter fullscreen mode Exit fullscreen mode

変換前のクリーニング

変換ステップの前に欠損値を埋めることもできます:

// None をすべて 0.0 で埋める
let filled = t.fill_none(0.0);
assert!(filled.is_numeric_convertible());

// そのあと厳格に変換
let numeric = filled.try_numeric()?;
Enter fullscreen mode Exit fullscreen mode

時系列データ向けに forward_fill_none(前の値で補完)も使えます。

dynamic でできないこと

dynamic feature には意図的に含まれていないものがあります:

  • dynamic テンソルでの算術演算は不可。事前に try_numeric() を実行してください。
  • dynamic テンソルの reshape、slice、reduction は不可。
  • dynamic テンソルの serde シリアライズは不可。

狙いは、きれいな受け渡しです。雑然とした入力 → 検査とクリーニング → 数値 Tensor → 通常の数値計算。この境界は意図的なものです。

リンク: crates.io · docs.rs · mdBook · リポジトリ

Source: dev.to

arrow_back Back to Tutorials