| 56 | } |
| 57 | |
| 58 | pub fn mean_decrease_accuracy<C: SimpleClassifier>( |
| 59 | model: &mut C, |
| 60 | x: &[Vec<f64>], |
| 61 | y: &[f64], |
| 62 | feature_names: &[String], |
| 63 | splits: &[(Vec<usize>, Vec<usize>)], |
| 64 | sample_weight: Option<&[f64]>, |
| 65 | scoring: Scoring, |
| 66 | ) -> Result<BTreeMap<String, ImportanceStats>, String> { |
| 67 | validate_xy(x, y, feature_names)?; |
| 68 | |
| 69 | let n_features = feature_names.len(); |
| 70 | let mut per_feature = vec![Vec::new(); n_features]; |
| 71 | |
| 72 | for (train_idx, test_idx) in splits { |
| 73 | let x_train = rows(x, train_idx); |
| 74 | let y_train = vals(y, train_idx); |
| 75 | let sw_train = sample_weight.map(|sw| vals(sw, train_idx)); |
| 76 | model.fit(&x_train, &y_train, sw_train.as_deref()); |
| 77 | |
| 78 | let x_test = rows(x, test_idx); |
| 79 | let y_test = vals(y, test_idx); |
| 80 | let sw_test = sample_weight.map(|sw| vals(sw, test_idx)); |
| 81 | |
| 82 | let base = score_model(model, &x_test, &y_test, sw_test.as_deref(), scoring); |
| 83 | |
| 84 | for j in 0..n_features { |
| 85 | let mut x_perm = x_test.clone(); |
| 86 | permute_col(&mut x_perm, j); |
| 87 | let perm = score_model(model, &x_perm, &y_test, sw_test.as_deref(), scoring); |
| 88 | let imp = match scoring { |
| 89 | Scoring::NegLogLoss => { |
| 90 | if -perm == 0.0 { |
| 91 | 0.0 |
| 92 | } else { |
| 93 | (base - perm) / (-perm) |
| 94 | } |
| 95 | } |
| 96 | Scoring::Accuracy | Scoring::F1 => { |
| 97 | if (1.0 - perm).abs() < 1e-12 { |
| 98 | 0.0 |
| 99 | } else { |
| 100 | (base - perm) / (1.0 - perm) |
| 101 | } |
| 102 | } |
| 103 | }; |
| 104 | per_feature[j].push(if imp.is_finite() { imp } else { 0.0 }); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | Ok(pack_stats(feature_names, &per_feature)) |
| 109 | } |
| 110 | |
| 111 | pub fn single_feature_importance<C: SimpleClassifier>( |
| 112 | clf: &mut C, |