diff --git a/feature_importance/00_ablation_classification_script.sh b/feature_importance/00_ablation_classification_script.sh
new file mode 100755
index 0000000..42d4285
--- /dev/null
+++ b/feature_importance/00_ablation_classification_script.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_classification_retrain.py --nreps 1 --config mdi_local.real_data_classification_credit_g_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name credit_g_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_classification_script2.sh b/feature_importance/00_ablation_classification_script2.sh
new file mode 100755
index 0000000..9ab0204
--- /dev/null
+++ b/feature_importance/00_ablation_classification_script2.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_classification_retrain.py --nreps 1 --config mdi_local.real_data_classification_csi_pecarn_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name csi_pecarn_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_classification_script3.sh b/feature_importance/00_ablation_classification_script3.sh
new file mode 100755
index 0000000..e474649
--- /dev/null
+++ b/feature_importance/00_ablation_classification_script3.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_classification_retrain.py --nreps 1 --config mdi_local.real_data_classification_juvenile_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name juvenile_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_classification_script4.sh b/feature_importance/00_ablation_classification_script4.sh
new file mode 100755
index 0000000..eeb382c
--- /dev/null
+++ b/feature_importance/00_ablation_classification_script4.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_classification_retrain.py --nreps 1 --config mdi_local.real_data_classification_Ionosphere_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name Ionosphere_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_script.sh b/feature_importance/00_ablation_regression_script.sh
index e62eb02..9d90092 100755
--- a/feature_importance/00_ablation_regression_script.sh
+++ b/feature_importance/00_ablation_regression_script.sh
@@ -4,7 +4,7 @@
#SBATCH --partition=yugroup
source activate mdi
# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
-command="00_run_ablation_regression_retrain.py --nreps 1 --config mdi_local.real_data_regression_retrain --split_seed 0 --rf_seed ${1} --ignore_cache --create_rmd --folder_name linear_retrain --fit_model True --absolute_masking True"
+command="00_run_ablation_regression_retrain.py --nreps 1 --config mdi_local.real_data_regression_parkinsons_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name parkinsons_retrain --fit_model True --absolute_masking True"
# Execute the command
python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_script2.sh b/feature_importance/00_ablation_regression_script2.sh
new file mode 100755
index 0000000..d86a68f
--- /dev/null
+++ b/feature_importance/00_ablation_regression_script2.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_retrain.py --nreps 1 --config mdi_local.real_data_regression_performance_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name performance_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_script3.sh b/feature_importance/00_ablation_regression_script3.sh
new file mode 100755
index 0000000..0178d93
--- /dev/null
+++ b/feature_importance/00_ablation_regression_script3.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_retrain.py --nreps 1 --config mdi_local.real_data_regression_temperature_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name temperature_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_script4.sh b/feature_importance/00_ablation_regression_script4.sh
new file mode 100755
index 0000000..93c70c2
--- /dev/null
+++ b/feature_importance/00_ablation_regression_script4.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_retrain.py --nreps 1 --config mdi_local.real_data_regression_CCLE_PD_0325901_retrain --split_seed ${1} --rf_seed ${2} --ignore_cache --create_rmd --folder_name CCLE_PD_0325901_retrain --fit_model True --absolute_masking True"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_stability_script.sh b/feature_importance/00_ablation_regression_stability_script.sh
new file mode 100755
index 0000000..7aa857d
--- /dev/null
+++ b/feature_importance/00_ablation_regression_stability_script.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_stability.py --nreps 1 --config mdi_local.real_data_regression_parkinsons_retrain --split_seed ${1} --ignore_cache --create_rmd --folder_name parkinsons_stability"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_stability_script2.sh b/feature_importance/00_ablation_regression_stability_script2.sh
new file mode 100755
index 0000000..df168c9
--- /dev/null
+++ b/feature_importance/00_ablation_regression_stability_script2.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_stability.py --nreps 1 --config mdi_local.real_data_regression_performance_retrain --split_seed ${1} --ignore_cache --create_rmd --folder_name performance_stability"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_stability_script3.sh b/feature_importance/00_ablation_regression_stability_script3.sh
new file mode 100755
index 0000000..b94b0da
--- /dev/null
+++ b/feature_importance/00_ablation_regression_stability_script3.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_stability.py --nreps 1 --config mdi_local.real_data_regression_temperature_retrain --split_seed ${1} --ignore_cache --create_rmd --folder_name temperature_stability"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_regression_stability_script4.sh b/feature_importance/00_ablation_regression_stability_script4.sh
new file mode 100755
index 0000000..978614d
--- /dev/null
+++ b/feature_importance/00_ablation_regression_stability_script4.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#SBATCH --mail-user=zhongyuan_liang@berkeley.edu
+#SBATCH --mail-type=ALL
+#SBATCH --partition=yugroup
+source activate mdi
+# Need to specify --result_name --ablate_features(default all features) --fitted(default not fitted)
+command="00_run_ablation_regression_stability.py --nreps 1 --config mdi_local.real_data_regression_CCLE_PD_0325901_retrain --split_seed ${1} --ignore_cache --create_rmd --folder_name CCLE_PD_0325901_stability"
+
+# Execute the command
+python $command
\ No newline at end of file
diff --git a/feature_importance/00_ablation_script_class.sh b/feature_importance/00_ablation_script_class.sh
new file mode 100755
index 0000000..252d70d
--- /dev/null
+++ b/feature_importance/00_ablation_script_class.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+slurm_script="00_ablation_classification_script4.sh"
+
+for split_seed in {1..3}; do
+ for rf_seed in {1..5}; do
+ sbatch $slurm_script $split_seed $rf_seed # Submit SLURM job with both split_seed and rf_seed as arguments
+ sleep 2
+ done
+done
diff --git a/feature_importance/00_ablation_script_regr.sh b/feature_importance/00_ablation_script_regr.sh
index 8c8f0e3..6c670ab 100755
--- a/feature_importance/00_ablation_script_regr.sh
+++ b/feature_importance/00_ablation_script_regr.sh
@@ -1,9 +1,10 @@
#!/bin/bash
-slurm_script="00_ablation_regression_script.sh"
+slurm_script="00_ablation_regression_script4.sh"
-for rep in {1..2}
-do
- sbatch $slurm_script $rep # Submit SLURM job using the specified script
- sleep 2
+for split_seed in {1..3}; do
+ for rf_seed in {1..5}; do
+ sbatch $slurm_script $split_seed $rf_seed # Submit SLURM job with both split_seed and rf_seed as arguments
+ sleep 2
+ done
done
\ No newline at end of file
diff --git a/feature_importance/00_ablation_script_regr_stability.sh b/feature_importance/00_ablation_script_regr_stability.sh
new file mode 100755
index 0000000..a337d7f
--- /dev/null
+++ b/feature_importance/00_ablation_script_regr_stability.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+slurm_script="00_ablation_regression_stability_script4.sh"
+
+for split_seed in {1..3}; do
+ sbatch $slurm_script $split_seed # Submit SLURM job with both split_seed and rf_seed as arguments
+ sleep 2
+done
\ No newline at end of file
diff --git a/feature_importance/00_run_ablation_classification_retrain.py b/feature_importance/00_run_ablation_classification_retrain.py
index 3e7a613..31bb8d5 100644
--- a/feature_importance/00_run_ablation_classification_retrain.py
+++ b/feature_importance/00_run_ablation_classification_retrain.py
@@ -20,6 +20,7 @@
from sklearn import preprocessing
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.linear_model import LogisticRegressionCV
+from sklearn.linear_model import LinearRegression
from sklearn.svm import SVC
import xgboost as xgb
from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
@@ -36,90 +37,13 @@
# python 01_run_ablation_classification.py --nreps 5 --config mdi_local.real_data_classification --split_seed 331 --ignore_cache --create_rmd --result_name diabetes_classification
-# def generate_random_shuffle(data, seed):
-# """
-# Randomly shuffle each column of the data.
-# """
-# np.random.seed(seed)
-# return np.array([np.random.permutation(data[:, i]) for i in range(data.shape[1])]).T
-
-
-# def ablation(data, feature_importance, mode, num_features, seed):
-# """
-# Replace the top num_features max feature importance data with random shuffle for each sample
-# """
-# assert mode in ["max", "min"]
-# fi = feature_importance.to_numpy()
-# shuffle = generate_random_shuffle(data, seed)
-# if mode == "max":
-# indices = np.argsort(-fi)
-# else:
-# indices = np.argsort(fi)
-# data_copy = data.copy()
-# for i in range(data.shape[0]):
-# for j in range(num_features):
-# data_copy[i, indices[i,j]] = shuffle[i, indices[i,j]]
-# return data_copy
-
-# def ablation_removal(train_mean, data, feature_importance_rank, feature_index):
-# """
-# Replace the top num_features max feature importance data with mean value for each sample
-# """
-# data_copy = data.copy()
-# for i in range(data.shape[0]):
-# data_copy[i, feature_importance_rank[i,feature_index]] = train_mean[feature_importance_rank[i,feature_index]]
-# return data_copy
-
-# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
-# """
-# Initialize the data with mean values and add the top num_features max feature importance data for each sample
-# """
-# data_copy = data_ablation.copy()
-# for i in range(data.shape[0]):
-# data_copy[i, feature_importance_rank[i,feature_index]] = data[i, feature_importance_rank[i,feature_index]]
-# return data_copy
-
-def ablation_removal(train_mean, data, feature_importance, feature_importance_rank, feature_index, mode):
- if mode == "absolute":
- return ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index)
- else:
- return ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index)
-
-
-def ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index):
- """
- Replace the top num_features max feature importance data with mean value for each sample
- """
- data_copy = data.copy()
- indices = feature_importance_rank[:, feature_index]
- data_copy[np.arange(data.shape[0]), indices] = train_mean[indices]
- return data_copy
-
-def ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index):
- data_copy = data.copy()
- indices = feature_importance_rank[:, feature_index]
- sum = 0
- for i in range(data.shape[0]):
- if feature_importance[i, indices[i]] != 0 and feature_importance[i, indices[i]] < sys.maxsize - 1:
- sum += 1
- data_copy[i, indices[i]] = train_mean[indices[i]]
- print("Remove sum: ", sum)
- return data_copy
-
-# def delta_mae(y_true, y_pred_1, y_pred_2):
-# mae_before = np.abs(y_true - y_pred_1)
-# mae_after = np.abs(y_true - y_pred_2)
-# absolute_delta_mae = np.mean(np.abs(mae_before - mae_after))
-# return absolute_delta_mae
-
-# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
-# """
-# Initialize the data with mean values and add the top num_features max feature importance data for each sample
-# """
-# data_copy = data_ablation.copy()
-# indices = feature_importance_rank[:, feature_index]
-# data_copy[np.arange(data.shape[0]), indices] = data[np.arange(data.shape[0]), indices]
-# return data_copy
+def select_top_features(array, sorted_indices, percentage):
+ array = copy.deepcopy(array)
+ num_features = array.shape[1]
+ num_selected = int(np.ceil(num_features * percentage))
+ selected_indices = sorted_indices[:num_selected]
+ selected_array = array[:, selected_indices]
+ return num_selected, selected_array
def compare_estimators(estimators: List[ModelConfig],
@@ -141,7 +65,7 @@ def compare_estimators(estimators: List[ModelConfig],
# loop over model estimators
for model in estimators:
- est = model.cls(**model.kwargs)
+ # est = model.cls(**model.kwargs)
# get kwargs for all fi_ests
fi_kwargs = {}
@@ -170,6 +94,7 @@ def compare_estimators(estimators: List[ModelConfig],
print("Fitting Models")
# fit RF model
start_rf = time.time()
+ est = RandomForestClassifier(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed)
est.fit(X_train, y_train)
end_rf = time.time()
@@ -185,13 +110,13 @@ def compare_estimators(estimators: List[ModelConfig],
rf_plus_base_oob.fit(X_train, y_train)
end_rf_plus_oob = time.time()
- # #fit inbag RF_plus model
- # start_rf_plus_inbag = time.time()
- # est_regressor = RandomForestRegressor(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=42)
- # est_regressor.fit(X_train, y_train)
- # rf_plus_base_inbag = RandomForestPlusRegressor(rf_model=est_regressor, include_raw=False, fit_on="inbag", prediction_model=Ridge(alpha=1e-6))
- # rf_plus_base_inbag.fit(X_train, y_train)
- # end_rf_plus_inbag = time.time()
+ #fit inbag RF_plus model
+ start_rf_plus_inbag = time.time()
+ est_regressor = RandomForestRegressor(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed)
+ est_regressor.fit(X_train, y_train)
+ rf_plus_base_inbag = RandomForestPlusRegressor(rf_model=est_regressor, include_raw=False, fit_on="inbag", prediction_model=LinearRegression())
+ rf_plus_base_inbag.fit(X_train, y_train)
+ end_rf_plus_inbag = time.time()
# get test results
test_all_auc_rf = roc_auc_score(y_test, est.predict_proba(X_test)[:, 1])
@@ -203,289 +128,83 @@ def compare_estimators(estimators: List[ModelConfig],
test_all_auc_rf_plus_oob = roc_auc_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1])
test_all_auprc_rf_plus_oob = average_precision_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1])
test_all_f1_rf_plus_oob = f1_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1] > 0.5)
+ # test_all_auc_rf_plus_inbag = roc_auc_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1])
+ # test_all_auprc_rf_plus_inbag = average_precision_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1])
+ # test_all_f1_rf_plus_inbag = f1_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1] > 0.5)
fitted_results = {
- "Model": ["RF", "RF_plus", "RF_plus_oob"],
- "AUC": [test_all_auc_rf, test_all_auc_rf_plus, test_all_auc_rf_plus_oob],
- "AUPRC": [test_all_auprc_rf, test_all_auprc_rf_plus, test_all_auprc_rf_plus_oob],
- "F1": [test_all_f1_rf, test_all_f1_rf_plus, test_all_f1_rf_plus_oob],
- "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob]
+ "Model": ["RF", "RF_plus", "RF_plus_oob"],#, "RF_plus_inbag"],
+ "AUC": [test_all_auc_rf, test_all_auc_rf_plus, test_all_auc_rf_plus_oob],#, test_all_auc_rf_plus_inbag],
+ "AUPRC": [test_all_auprc_rf, test_all_auprc_rf_plus, test_all_auprc_rf_plus_oob],#, test_all_auprc_rf_plus_inbag],
+ "F1": [test_all_f1_rf, test_all_f1_rf_plus, test_all_f1_rf_plus_oob],#, test_all_f1_rf_plus_inbag],
+ "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob]#, end_rf_plus_inbag - start_rf_plus_inbag]
}
os.makedirs(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}", exist_ok=True)
results_df = pd.DataFrame(fitted_results)
- results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_{args.split_seed}.csv", index=False)
+ results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_rf_seed_{args.rf_seed}_split_seed_{args.split_seed}.csv", index=False)
-
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RF_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(est, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_default_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base_oob, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base_inbag, file)
-
- if args.absolute_masking or args.positive_masking or args.negative_masking:
- np.random.seed(42)
- if X_train.shape[0] > 100:
- indices_train = np.random.choice(X_train.shape[0], 100, replace=False)
- X_train_subset = X_train[indices_train]
- y_train_subset = y_train[indices_train]
- else:
- indices_train = np.arange(X_train.shape[0])
- X_train_subset = X_train
- y_train_subset = y_train
-
- if X_test.shape[0] > 100:
- indices_test = np.random.choice(X_test.shape[0], 100, replace=False)
- X_test_subset = X_test[indices_test]
- y_test_subset = y_test[indices_test]
- else:
- indices_test = np.arange(X_test.shape[0])
- X_test_subset = X_test
- y_test_subset = y_test
-
if args.num_features_masked is None:
num_features_masked = X_train.shape[1]
else:
num_features_masked = args.num_features_masked
-
for fi_est in tqdm(fi_ests):
metric_results = {
'model': model.name,
'fi': fi_est.name,
'train_size': X_train.shape[0],
- 'train_subset_size': X_train_subset.shape[0],
'test_size': X_test.shape[0],
- 'test_subset_size': X_test_subset.shape[0],
'num_features': X_train.shape[1],
'data_split_seed': args.split_seed,
+ 'rf_seed': args.rf_seed,
'num_features_masked': num_features_masked
}
- for i in range(X_train_subset.shape[0]):
- metric_results[f'sample_train_{i}'] = indices_train[i]
- for i in range(X_test_subset.shape[0]):
- metric_results[f'sample_test_{i}'] = indices_test[i]
- print("Load Models")
- start = time.time()
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_default_{args.split_seed}.dill", 'rb') as file:
- # rf_plus_base = dill.load(file)
- # if fi_est.base_model == "None":
- # loaded_model = None
- # elif fi_est.base_model == "RF":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RF_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_oob":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_inbag":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_default":
- # loaded_model = rf_plus_base
- rf_plus_base = rf_plus_base
if fi_est.base_model == "None":
loaded_model = None
elif fi_est.base_model == "RF":
loaded_model = est
elif fi_est.base_model == "RFPlus_oob":
loaded_model = rf_plus_base_oob
- # elif fi_est.base_model == "RFPlus_inbag":
- # loaded_model = rf_plus_base_inbag
+ elif fi_est.base_model == "RFPlus_inbag":
+ loaded_model = rf_plus_base_inbag
elif fi_est.base_model == "RFPlus_default":
loaded_model = rf_plus_base
- end = time.time()
- metric_results['load_model_time'] = end - start
- print(f"done with loading models: {end - start}")
-
+ m= "absolute"
start = time.time()
print(f"Compute feature importance")
- # Compute feature importance
- local_fi_score_train, local_fi_score_train_subset, local_fi_score_test, local_fi_score_test_subset = fi_est.cls(X_train=X_train, y_train=y_train, X_train_subset = X_train_subset, y_train_subset=y_train_subset,
- X_test=X_test, y_test=y_test, X_test_subset=X_test_subset, y_test_subset=y_test_subset,
- fit=loaded_model, mode="absolute")
- if fi_est.name.startswith("Local_MDI+"):
- local_fi_score_train_subset = local_fi_score_train[indices_train]
-
- m= "absolute"
- #feature_importance_list[m][fi_est.name] = [local_fi_score_train_subset, local_fi_score_test, local_fi_score_test_subset]
+ local_fi_score_train = fi_est.cls(X_train=X_train, y_train=y_train, fit=loaded_model, mode="absolute")
+ train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ print(f"Train FI Mean: {train_fi_mean}")
+ if fi_est.ascending:
+ sorted_feature = np.argsort(-train_fi_mean)
+ else:
+ sorted_feature = np.argsort(train_fi_mean)
+ print(f"Sorted Feature: {sorted_feature}")
end = time.time()
metric_results[f'fi_time_{m}'] = end - start
- print(f"done with feature importance {m}: {end - start}")
- # prepare ablations
- print("start ablation")
- ablation_models = {"RF_Classifier": RandomForestClassifier(n_estimators=100, min_samples_leaf=1, max_features='sqrt', random_state=42),
- # "LogisticCV": LogisticRegressionCV(random_state=42, max_iter=2000),
- # "SVM": SVC(random_state=42, probability=True),
- # "XGBoost_Classifier": xgb.XGBClassifier(random_state=42),
- #"RF_Plus_Classifier": rf_plus_base
- }
- start = time.time()
- for a_model in ablation_models:
- ablation_models[a_model].fit(X_train_subset, y_train_subset)
- end = time.time()
- metric_results['ablation_model_fit_time'] = end - start
- print(f"done with ablation model fit: {end - start}")
- local_fi_score_train_subset[local_fi_score_train_subset == float("-inf")] = -sys.maxsize - 1
- local_fi_score_train_subset[local_fi_score_train_subset == float("inf")] = sys.maxsize - 1
- if fi_est.ascending:
- local_fi_score_train_subset_rank = np.argsort(-local_fi_score_train_subset)
+ ablation_models = {"RF_Classifier": RandomForestClassifier(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed),
+ "Logistic_Regression": LogisticRegressionCV(cv=5, max_iter=5000),}
+ if X_train.shape[1] > 20:
+ mask_ratio = [0.01, 0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
else:
- local_fi_score_train_subset_rank = np.argsort(local_fi_score_train_subset)
-
- train_mean = np.mean(X_train_subset, axis=0)
-
- for a_model in ablation_models:
- print(f"start ablation removal: {a_model}")
- ablation_est = ablation_models[a_model]
- y_pred_before = ablation_est.predict(X_test)
- metric_results[f'{a_model}_log_loss_after_ablation_0_{m}'] = log_loss(y_test, y_pred_before)
- X_temp = copy.deepcopy(X_train_subset)
- for i in range(num_features_masked):
- ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score_train_subset, local_fi_score_train_subset_rank, i, m)
- ablation_est.fit(ablation_X_data, y_train_subset)
- y_pred = ablation_est.predict(X_test)
- metric_results[f'{a_model}_log_loss_after_ablation_{i+1}_{m}'] = log_loss(y_test, y_pred)
- X_temp = ablation_X_data
-
- # all_fi = [local_fi_score_train_subset, local_fi_score_test_subset, local_fi_score_test]
- # all_fi_rank = [None, None, None]
- # for i in range(len(all_fi)):
- # fi = all_fi[i]
- # if isinstance(fi, np.ndarray):
- # fi[fi == float("-inf")] = -sys.maxsize - 1
- # fi[fi == float("inf")] = sys.maxsize - 1
- # if fi_est.ascending:
- # all_fi_rank[i] = np.argsort(-fi)
- # else:
- # all_fi_rank[i] = np.argsort(fi)
-
- # ablation_datas = {"train_subset": (X_train_subset, y_train_subset, all_fi[0], all_fi_rank[0]),
- # "test_subset": (X_test_subset, y_test_subset, all_fi[1], all_fi_rank[1]),
- # "test": (X_test, y_test, all_fi[2], all_fi_rank[2])}
- # train_mean = np.mean(X_train, axis=0)
-
- # print("start ablation")
- # # Start ablation 1: Feature removal
- # for ablation_data in ablation_datas:
- # start = time.time()
- # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
- # if not isinstance(local_fi_score, np.ndarray):
- # for a_model in ablation_models:
- # for i in range(num_features_masked+1):
- # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i}_{m}'] = None
- # else:
- # for a_model in ablation_models:
- # print(f"start ablation removal: {ablation_data} {a_model}")
- # ablation_est = ablation_models[a_model]
- # y_pred_before = ablation_est.predict_proba(X_data)[:, 1]
- # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_0_{m}'] = 0
- # X_temp = copy.deepcopy(X_data)
- # for i in range(num_features_masked):
- # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
- # y_pred = ablation_est.predict_proba(ablation_X_data)[:, 1]
- # if i == 0:
- # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i+1}_{m}'] = delta_mae(y_data, y_pred_before, y_pred)
- # else:
- # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i+1}_{m}'] = delta_mae(y_data, y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i}_{m}']
- # X_temp = ablation_X_data
- # y_pred_before = y_pred
- # end = time.time()
- # print(f"done with ablation removal {m}: {ablation_data} {end - start}")
- # metric_results[f'{ablation_data}_ablation_removal_{m}_time'] = end - start
-
-
-
- # Start ablation 1: Feature removal
- # for ablation_data in ablation_datas:
- # start = time.time()
- # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
- # if not isinstance(local_fi_score, np.ndarray):
- # for a_model in ablation_models:
- # metric_results[f'{a_model}_{ablation_data}_AUROC_before_ablation_{m}'] = None
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_before_ablation_{m}'] = None
- # metric_results[f'{a_model}_{ablation_data}_F1_before_ablation_{m}'] = None
- # for i in range(num_features_masked):
- # for a_model in ablation_models:
- # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_{m}'] = None
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_{m}'] = None
- # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_{m}'] = None
- # else:
- # for a_model in ablation_models:
- # print(f"start ablation removal: {ablation_data} {a_model}")
- # ablation_est = ablation_models[a_model]
- # y_pred = ablation_est.predict(X_data)
- # metric_results[a_model + f'_{ablation_data}_AUROC_before_ablation_{m}'] = roc_auc_score(y_data, y_pred)
- # metric_results[a_model + f'_{ablation_data}_AUPRC_before_ablation_{m}'] = average_precision_score(y_data, y_pred)
- # metric_results[a_model + f'_{ablation_data}_F1_before_ablation_{m}'] = f1_score(y_data, y_pred > 0.5)
- # ablation_results_auroc_list = [0] * num_features_masked
- # ablation_results_auprc_list = [0] * num_features_masked
- # ablation_results_f1_list = [0] * num_features_masked
- # X_temp = X_data.copy()
- # for i in range(num_features_masked):
- # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
- # ablation_results_auroc_list[i] = roc_auc_score(y_data, ablation_est.predict(ablation_X_data))
- # ablation_results_auprc_list[i] = average_precision_score(y_data, ablation_est.predict(ablation_X_data))
- # ablation_results_f1_list[i] = f1_score(y_data, ablation_est.predict(ablation_X_data) > 0.5)
- # X_temp = ablation_X_data
- # for i in range(num_features_masked):
- # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_{m}'] = ablation_results_auroc_list[i]
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_{m}'] = ablation_results_auprc_list[i]
- # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_{m}'] = ablation_results_f1_list[i]
- # end = time.time()
- # print(f"done with ablation removal: {ablation_data} {end - start}")
- # metric_results[f'{ablation_data}_ablation_removal_time'] = end - start
-
- # # Start ablation 2: Feature addition
- # for ablation_data in ablation_datas:
- # start = time.time()
- # X_data, y_data, local_fi_score_data = ablation_datas[ablation_data]
- # if not isinstance(local_fi_score_data, np.ndarray):
- # for a_model in ablation_models:
- # metric_results[f'{a_model}_{ablation_data}_AUROC_before_ablation_addition'] = None
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_before_ablation_addition'] = None
- # metric_results[f'{a_model}_{ablation_data}_F1_before_ablation_addition'] = None
- # for i in range(num_ablate_features):
- # for a_model in ablation_models:
- # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_addition'] = None
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_addition'] = None
- # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_addition'] = None
- # else:
- # for a_model in ablation_models:
- # print(f"start ablation addtion: {ablation_data} {a_model}")
- # ablation_est = ablation_models[a_model]
- # X_temp = np.array([train_mean_list] * X_data.shape[0]).copy()
- # y_pred = ablation_est.predict(X_temp)
- # metric_results[a_model + f'_{ablation_data}_AUROC_before_ablation_addition'] = roc_auc_score(y_data, y_pred)
- # metric_results[a_model + f'_{ablation_data}_AUPRC_before_ablation_addition'] = average_precision_score(y_data, y_pred)
- # metric_results[a_model + f'_{ablation_data}_F1_before_ablation_addition'] = f1_score(y_data, y_pred > 0.5)
- # imp_vals = copy.deepcopy(local_fi_score_data)
- # ablation_results_auroc_list = [0] * num_ablate_features
- # ablation_results_auprc_list = [0] * num_ablate_features
- # ablation_results_f1_list = [0] * num_ablate_features
- # for i in range(num_ablate_features):
- # ablation_X_data = ablation_addition(X_temp, X_data, imp_vals, i)
- # ablation_results_auroc_list[i] = roc_auc_score(y_data, ablation_est.predict(ablation_X_data))
- # ablation_results_auprc_list[i] = average_precision_score(y_data, ablation_est.predict(ablation_X_data))
- # ablation_results_f1_list[i] = f1_score(y_data, ablation_est.predict(ablation_X_data) > 0.5)
- # X_temp = ablation_X_data
- # for i in range(num_ablate_features):
- # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_addition'] = ablation_results_auroc_list[i]
- # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_addition'] = ablation_results_auprc_list[i]
- # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_addition'] = ablation_results_f1_list[i]
-
- # end = time.time()
- # print(f"done with ablation addtion: {ablation_data} {end - start}")
- # metric_results[f'{ablation_data}_ablation_addition_time'] = end - start
-
+ mask_ratio = [0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
+ print(f"X_train_0: {X_train[0]}")
+ for mask in mask_ratio:
+ print(f"Mask ratio: {mask}")
+ num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ print(f"Train shape: {X_train_masked.shape}")
+ num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ print(f"Test shape: {X_test_masked.shape}")
+ print(f"X_train_masked_0: {X_train_masked[0]}")
+ metric_results[f'num_features_selected_{mask}'] = num_features_selected
+ for a_model in ablation_models:
+ ablation_models[a_model].fit(X_train_masked, y_train)
+ y_pred = ablation_models[a_model].predict_proba(X_test_masked)[:, 1]
+ metric_results[f'{a_model}_LogLoss_top_{mask}'] = log_loss(y_test, y_pred)
+ metric_results[f'{a_model}_AUROC_top_{mask}'] = roc_auc_score(y_test, y_pred)
# initialize results with metadata and metric results
kwargs: dict = model.kwargs # dict
@@ -634,6 +353,7 @@ def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp
parser.add_argument('--positive_masking', type=bool, default=False)
parser.add_argument('--negative_masking', type=bool, default=False)
parser.add_argument('--num_features_masked', type=int, default=None)
+ parser.add_argument('--rf_seed', type=int, default=0)
# for multiple reruns, should support varying split_seed
parser.add_argument('--ignore_cache', action='store_true', default=False)
@@ -687,9 +407,9 @@ def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp
results_dir = oj(args.results_path, args.config, args.folder_name)
if isinstance(vary_param_name, list):
- path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.split_seed))
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "split_seed_" + str(args.split_seed)+"rf_seed_" + str(args.rf_seed))
else:
- path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
+ path = oj(results_dir, "varying_" + vary_param_name, "split_seed_" + str(args.split_seed)+"rf_seed_" + str(args.rf_seed))
os.makedirs(path, exist_ok=True)
eval_out = defaultdict(list)
diff --git a/feature_importance/00_run_ablation_regression_retrain.py b/feature_importance/00_run_ablation_regression_retrain.py
index a14c100..ed3817d 100644
--- a/feature_importance/00_run_ablation_regression_retrain.py
+++ b/feature_importance/00_run_ablation_regression_retrain.py
@@ -22,7 +22,7 @@
from sklearn.linear_model import LinearRegression
import xgboost as xgb
from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
-from sklearn.linear_model import Ridge
+from sklearn.linear_model import RidgeCV
sys.path.append(".")
sys.path.append("..")
sys.path.append("../..")
@@ -36,75 +36,6 @@
#RUN THE FILE
# python 01_run_ablation_regression.py --nreps 5 --config mdi_local.real_data_regression --split_seed 331 --ignore_cache --create_rmd --result_name diabetes_regression
-
-# def generate_random_shuffle(data, seed):
-# """
-# Randomly shuffle each column of the data.
-# """
-# np.random.seed(seed)
-# return np.array([np.random.permutation(data[:, i]) for i in range(data.shape[1])]).T
-
-
-# def ablation(data, feature_importance, mode, num_features, seed):
-# """
-# Replace the top num_features max feature importance data with random shuffle for each sample
-# """
-# assert mode in ["max", "min"]
-# fi = feature_importance.to_numpy()
-# shuffle = generate_random_shuffle(data, seed)
-# if mode == "max":
-# indices = np.argsort(-fi)
-# else:
-# indices = np.argsort(fi)
-# data_copy = data.copy()
-# for i in range(data.shape[0]):
-# for j in range(num_features):
-# data_copy[i, indices[i,j]] = shuffle[i, indices[i,j]]
-# return data_copy
-
-# def ablation_removal(train_mean, data, feature_importance_rank, feature_index):
-# """
-# Replace the top num_features max feature importance data with mean value for each sample
-# """
-# data_copy = data.copy()
-# for i in range(data.shape[0]):
-# data_copy[i, feature_importance_rank[i,feature_index]] = train_mean[feature_importance_rank[i,feature_index]]
-# return data_copy
-
-# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
-# """
-# Initialize the data with mean values and add the top num_features max feature importance data for each sample
-# """
-# data_copy = data_ablation.copy()
-# for i in range(data.shape[0]):
-# data_copy[i, feature_importance_rank[i,feature_index]] = data[i, feature_importance_rank[i,feature_index]]
-# return data_copy
-def ablation_removal(train_mean, data, feature_importance, feature_importance_rank, feature_index, mode):
- if mode == "absolute":
- return ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index)
- # else:
- # return ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index)
-
-def ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index):
- """
- Replace the top num_features max feature importance data with mean value for each sample
- """
- data_copy = data.copy()
- indices = feature_importance_rank[:, feature_index]
- data_copy[np.arange(data.shape[0]), indices] = train_mean[indices]
- return data_copy
-
-# def ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index):
-# data_copy = data.copy()
-# indices = feature_importance_rank[:, feature_index]
-# sum = 0
-# for i in range(data.shape[0]):
-# if feature_importance[i, indices[i]] != 0 and feature_importance[i, indices[i]] < sys.maxsize - 1:
-# sum += 1
-# data_copy[i, indices[i]] = train_mean[indices[i]]
-# print("Remove sum: ", sum)
-# return data_copy
-
def select_top_features(array, sorted_indices, percentage):
array = copy.deepcopy(array)
num_features = array.shape[1]
@@ -113,24 +44,6 @@ def select_top_features(array, sorted_indices, percentage):
selected_array = array[:, selected_indices]
return num_selected, selected_array
-# def delta_mse(y_true, y_pred_1, y_pred_2):
-# mse_before = (y_true - y_pred_1) ** 2
-# mse_after = (y_true - y_pred_2) ** 2
-# absolute_delta_mse = np.mean(np.abs(mse_before - mse_after))
-# return absolute_delta_mse
-
-# def delta_y_pred(y_pred_1, y_pred_2):
-# return np.mean(np.abs(y_pred_1 - y_pred_2))
-
-# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
-# """
-# Initialize the data with mean values and add the top num_features max feature importance data for each sample
-# """
-# data_copy = data_ablation.copy()
-# indices = feature_importance_rank[:, feature_index]
-# data_copy[np.arange(data.shape[0]), indices] = data[np.arange(data.shape[0]), indices]
-# return data_copy
-
def compare_estimators(estimators: List[ModelConfig],
fi_estimators: List[FIModelConfig],
@@ -200,6 +113,17 @@ def compare_estimators(estimators: List[ModelConfig],
rf_plus_base_inbag.fit(X_train, y_train)
end_rf_plus_inbag = time.time()
+
+ # fit default RF_plus model
+ rf_plus_base_ridge = RandomForestPlusRegressor(rf_model=est, prediction_model=RidgeCV(cv=5))
+ rf_plus_base_ridge.fit(X_train, y_train)
+ rf_plus_base_oob_ridge = RandomForestPlusRegressor(rf_model=est, fit_on="oob", prediction_model=RidgeCV(cv=5))
+ rf_plus_base_oob_ridge.fit(X_train, y_train)
+ rf_plus_base_inbag_ridge = RandomForestPlusRegressor(rf_model=est, include_raw=False, fit_on="inbag", prediction_model=RidgeCV(cv=5))
+ rf_plus_base_inbag_ridge.fit(X_train, y_train)
+
+
+
# get test results
test_all_mse_rf = mean_squared_error(y_test, est.predict(X_test))
test_all_r2_rf = r2_score(y_test, est.predict(X_test))
@@ -214,46 +138,12 @@ def compare_estimators(estimators: List[ModelConfig],
"Model": ["RF", "RF_plus", "RF_plus_oob", "RF_plus_inbag"],
"MSE": [test_all_mse_rf, test_all_mse_rf_plus, test_all_mse_rf_plus_oob, test_all_mse_rf_plus_inbag],
"R2": [test_all_r2_rf, test_all_r2_rf_plus, test_all_r2_rf_plus_oob, test_all_r2_rf_plus_inbag],
- "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob]
+ "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob, end_rf_plus_inbag - start_rf_plus_inbag]
}
os.makedirs(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}", exist_ok=True)
results_df = pd.DataFrame(fitted_results)
- results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_rf_seed_{args.rf_seed}.csv", index=False)
-
-
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RF_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(est, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_default_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base_oob, file)
- # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill"
- # with open(pickle_file, 'wb') as file:
- # dill.dump(rf_plus_base_inbag, file)
-
- # if args.absolute_masking or args.positive_masking or args.negative_masking:
- # np.random.seed(42)
- # if X_train.shape[0] > 100:
- # indices_train = np.random.choice(X_train.shape[0], 100, replace=False)
- # X_train_subset = X_train[indices_train]
- # y_train_subset = y_train[indices_train]
- # else:
- # indices_train = np.arange(X_train.shape[0])
- # X_train_subset = X_train
- # y_train_subset = y_train
-
- # if X_test.shape[0] > 100:
- # indices_test = np.random.choice(X_test.shape[0], 100, replace=False)
- # X_test_subset = X_test[indices_test]
- # y_test_subset = y_test[indices_test]
- # else:
- # indices_test = np.arange(X_test.shape[0])
- # X_test_subset = X_test
- # y_test_subset = y_test
+ results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_rf_seed_{args.rf_seed}_split_seed_{args.split_seed}.csv", index=False)
if args.num_features_masked is None:
num_features_masked = X_train.shape[1]
@@ -266,37 +156,12 @@ def compare_estimators(estimators: List[ModelConfig],
'model': model.name,
'fi': fi_est.name,
'train_size': X_train.shape[0],
- # 'train_subset_size': X_train_subset.shape[0],
'test_size': X_test.shape[0],
- # 'test_subset_size': X_test_subset.shape[0],
'num_features': X_train.shape[1],
'data_split_seed': args.split_seed,
'rf_seed': args.rf_seed,
'num_features_masked': num_features_masked
}
- # for i in range(X_train_subset.shape[0]):
- # metric_results[f'sample_train_{i}'] = indices_train[i]
- # for i in range(X_test_subset.shape[0]):
- # metric_results[f'sample_test_{i}'] = indices_test[i]
-
- print("Load Models")
- start = time.time()
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_default_{args.split_seed}.dill", 'rb') as file:
- # rf_plus_base = dill.load(file)
- # if fi_est.base_model == "None":
- # loaded_model = None
- # elif fi_est.base_model == "RF":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RF_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_oob":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_inbag":
- # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill", 'rb') as file:
- # loaded_model = dill.load(file)
- # elif fi_est.base_model == "RFPlus_default":
- # loaded_model = rf_plus_base
- #rf_plus_base = rf_plus_base
if fi_est.base_model == "None":
loaded_model = None
elif fi_est.base_model == "RF":
@@ -307,166 +172,48 @@ def compare_estimators(estimators: List[ModelConfig],
loaded_model = rf_plus_base_inbag
elif fi_est.base_model == "RFPlus_default":
loaded_model = rf_plus_base
- end = time.time()
- metric_results['load_model_time'] = end - start
- print(f"done with loading models: {end - start}")
-
+ elif fi_est.base_model == "RFPlus_ridge":
+ loaded_model = rf_plus_base_ridge
+ elif fi_est.base_model == "RFPlus_oob_ridge":
+ loaded_model = rf_plus_base_oob_ridge
+ elif fi_est.base_model == "RFPlus_inbag_ridge":
+ loaded_model = rf_plus_base_inbag_ridge
- start = time.time()
- print(f"Compute feature importance")
- # Compute feature importance
- local_fi_score_train, _, _, _ = fi_est.cls(X_train=X_train, y_train=y_train, fit=loaded_model, mode="absolute")
- # if fi_est.name.startswith("Local_MDI+"):
- # local_fi_score_train_subset = local_fi_score_train[indices_train]
-
m= "absolute"
- #feature_importance_list[m][fi_est.name] = [local_fi_score_train_subset, local_fi_score_test, local_fi_score_test_subset]
- end = time.time()
- metric_results[f'fi_time_{m}'] = end - start
- print(f"done with feature importance {m}: {end - start}")
- # prepare ablations
- print("prepare ablation")
- ablation_models = {"RF_Regressor": RandomForestRegressor(n_estimators=100,min_samples_leaf=5,max_features=0.33,random_state=args.rf_seed),
- # "Linear": LinearRegression(),
- # "XGB_Regressor": xgb.XGBRegressor(random_state=42),
- # 'Kernel_Ridge': KernelRidge(),
- #"RF_Plus_Regressor": rf_plus_base
- }
start = time.time()
- for a_model in ablation_models:
- ablation_models[a_model].fit(X_train, y_train)
- end = time.time()
- metric_results['ablation_model_fit_time'] = end - start
- print(f"done with ablation model fit: {end - start}")
-
- # all_fi = [local_fi_score_train_subset, local_fi_score_test_subset, local_fi_score_test]
- # all_fi_rank = [None, None, None]
- # for i in range(len(all_fi)):
- # fi = all_fi[i]
- # if isinstance(fi, np.ndarray):
- # fi[fi == float("-inf")] = -sys.maxsize - 1
- # fi[fi == float("inf")] = sys.maxsize - 1
- # if fi_est.ascending:
- # all_fi_rank[i] = np.argsort(-fi)
- # else:
- # all_fi_rank[i] = np.argsort(fi)
- local_fi_score_train[local_fi_score_train == float("-inf")] = -sys.maxsize - 1
- local_fi_score_train[local_fi_score_train == float("inf")] = sys.maxsize - 1
- if fi_est.ascending:
- local_fi_score_train_rank = np.argsort(-local_fi_score_train)
- else:
- local_fi_score_train_rank = np.argsort(local_fi_score_train)
+ print(f"Compute feature importance")
+ local_fi_score_train = fi_est.cls(X_train=X_train, y_train=y_train, fit=loaded_model, mode="absolute")
train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ print(f"Train FI Mean: {train_fi_mean}")
if fi_est.ascending:
sorted_feature = np.argsort(-train_fi_mean)
else:
sorted_feature = np.argsort(train_fi_mean)
- train_mean = np.mean(X_train, axis=0)
-
- for a_model in ablation_models:
- print(f"start ablation removal: {a_model}")
- ablation_est = ablation_models[a_model]
- y_pred_before = ablation_est.predict(X_test)
- metric_results[f'{a_model}_MSE_after_ablation_0_{m}'] = mean_squared_error(y_test, y_pred_before)
- metric_results[f'{a_model}_R2_after_ablation_0_{m}'] = r2_score(y_test, y_pred_before)
- X_temp = copy.deepcopy(X_train)
- print(f"Train 0: X_temp[0]")
- for i in range(num_features_masked):
- print(f"Masking {i}")
- ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score_train, local_fi_score_train_rank, i, m)
- print(f"Train 0: X_temp[0]")
- ablation_est.fit(ablation_X_data, y_train)
- y_pred = ablation_est.predict(X_test)
- metric_results[f'{a_model}_MSE_after_ablation_{i+1}_{m}'] = mean_squared_error(y_test, y_pred)
- metric_results[f'{a_model}_R2_after_ablation_{i+1}_{m}'] = r2_score(y_test, y_pred)
- X_temp = ablation_X_data
-
- mask_ratio = [0.05, 0.1, 0.25, 0.5, 0.9]
+ print(f"Sorted Feature: {sorted_feature}")
+ end = time.time()
+ metric_results[f'fi_time_{m}'] = end - start
+
+ ablation_models = {"RF_Regressor": RandomForestRegressor(n_estimators=100,min_samples_leaf=5,max_features=0.33,random_state=args.rf_seed),
+ "Linear_Regressor": LinearRegression()}
+ if X_train.shape[1] > 20:
+ mask_ratio = [0.01, 0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
+ else:
+ mask_ratio = [0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
+ print(f"X_train_0: {X_train[0]}")
for mask in mask_ratio:
print(f"Mask ratio: {mask}")
- print(f"Train shape: {X_train.shape}")
- num_features_masked, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
print(f"Train shape: {X_train_masked.shape}")
- num_features_masked, X_test_masked = select_top_features(X_test, sorted_feature, mask)
- print(f"Test shape: {X_train_masked.shape}")
- metric_results[f'num_features_masked_{mask}'] = num_features_masked
+ num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ print(f"Test shape: {X_test_masked.shape}")
+ print(f"X_train_masked_0: {X_train_masked[0]}")
+ metric_results[f'num_features_selected_{mask}'] = num_features_selected
for a_model in ablation_models:
ablation_models[a_model].fit(X_train_masked, y_train)
y_pred = ablation_models[a_model].predict(X_test_masked)
- metric_results[f'{a_model}_MSE_after_ablation_top_{mask}'] = mean_squared_error(y_test, y_pred)
- metric_results[f'{a_model}_R2_after_ablation_top_{mask}'] = r2_score(y_test, y_pred)
-
- # ablation_datas = {"train_subset": (X_train_subset, y_train_subset, all_fi[0], all_fi_rank[0]),
- # "test_subset": (X_test_subset, y_test_subset, all_fi[1], all_fi_rank[1]),
- # "test": (X_test, y_test, all_fi[2], all_fi_rank[2])}
- # train_mean = np.mean(X_train, axis=0)
-
- # print("start ablation")
- # # Start ablation 1: Feature removal
- # for ablation_data in ablation_datas:
- # start = time.time()
- # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
- # if not isinstance(local_fi_score, np.ndarray):
- # for a_model in ablation_models:
- # for i in range(num_features_masked+1):
- # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i}_{m}'] = None
- # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i}_{m}'] = None
- # else:
- # for a_model in ablation_models:
- # print(f"start ablation removal: {ablation_data} {a_model}")
- # ablation_est = ablation_models[a_model]
- # y_pred_before = ablation_est.predict(X_data)
- # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_0_{m}'] = 0
- # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_0_{m}'] = 0
- # X_temp = copy.deepcopy(X_data)
- # for i in range(num_features_masked):
- # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
- # y_pred = ablation_est.predict(ablation_X_data)
- # if i == 0:
- # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i+1}_{m}'] = delta_mse(y_data, y_pred_before, y_pred)
- # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i+1}_{m}'] = delta_y_pred(y_pred_before, y_pred)
- # else:
- # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i+1}_{m}'] = delta_mse(y_data, y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i}_{m}']
- # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i+1}_{m}'] = delta_y_pred(y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i}_{m}' ]
- # X_temp = ablation_X_data
- # y_pred_before = y_pred
- # end = time.time()
- # print(f"done with ablation removal {m}: {ablation_data} {end - start}")
- # metric_results[f'{ablation_data}_ablation_removal_{m}_time'] = end - start
- # # Start ablation 2: Feature addition
- # for ablation_data in ablation_datas:
- # start = time.time()
- # X_data, y_data, local_fi_score_data = ablation_datas[ablation_data]
- # if not isinstance(local_fi_score_data, np.ndarray):
- # for a_model in ablation_models:
- # metric_results[a_model + f'_{ablation_data}_MSE_before_ablation_addition'] = None
- # metric_results[a_model + f'_{ablation_data}_R_2_before_ablation_addition'] = None
- # for i in range(num_ablate_features):
- # for a_model in ablation_models:
- # metric_results[f'{a_model}_{ablation_data}_MSE_after_ablation_{i+1}_addition'] = None
- # metric_results[f'{a_model}_{ablation_data}_R_2_after_ablation_{i+1}_addition'] = None
- # else:
- # for a_model in ablation_models:
- # print(f"start ablation addtion: {ablation_data} {a_model}")
- # ablation_est = ablation_models[a_model]
- # X_temp = np.array([train_mean_list] * X_data.shape[0]).copy()
- # y_pred = ablation_est.predict(X_temp)
- # metric_results[a_model + f'_{ablation_data}_MSE_before_ablation_addition'] = mean_squared_error(y_data, y_pred)
- # metric_results[a_model + f'_{ablation_data}_R_2_before_ablation_addition'] = r2_score(y_data, y_pred)
- # imp_vals = copy.deepcopy(local_fi_score_data)
- # ablation_results_list = [0] * num_ablate_features
- # ablation_results_list_r2 = [0] * num_ablate_features
- # for i in range(num_ablate_features):
- # ablation_X_data = ablation_addition(X_temp, X_data, imp_vals, i)
- # ablation_results_list[i] = mean_squared_error(y_data, ablation_est.predict(ablation_X_data))
- # ablation_results_list_r2[i] = r2_score(y_data, ablation_est.predict(ablation_X_data))
- # X_temp = ablation_X_data
- # for i in range(num_ablate_features):
- # metric_results[f'{a_model}_{ablation_data}_MSE_after_ablation_{i+1}_addition'] = ablation_results_list[i]
- # metric_results[f'{a_model}_{ablation_data}_R_2_after_ablation_{i+1}_addition'] = ablation_results_list_r2[i]
- # end = time.time()
- # print(f"done with ablation addtion: {ablation_data} {end - start}")
- # metric_results[f'{ablation_data}_ablation_addition_time'] = end - start
+ metric_results[f'{a_model}_MSE_top_{mask}'] = mean_squared_error(y_test, y_pred)
+ metric_results[f'{a_model}_R2_top_{mask}'] = r2_score(y_test, y_pred)
+
# initialize results with metadata and metric results
kwargs: dict = model.kwargs # dict
@@ -479,6 +226,8 @@ def compare_estimators(estimators: List[ModelConfig],
results[k].append(None)
for met_name, met_val in metric_results.items():
results[met_name].append(met_val)
+ # for key, value in results.items():
+ # print(f"{key}: {len(value)}")
return results, feature_importance_list
@@ -673,9 +422,9 @@ def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp
# else:
# path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
if isinstance(vary_param_name, list):
- path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.rf_seed))
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "split_seed_" + str(args.split_seed)+"rf_seed_" + str(args.rf_seed))
else:
- path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.rf_seed))
+ path = oj(results_dir, "varying_" + vary_param_name, "split_seed_" + str(args.split_seed)+"rf_seed_" + str(args.rf_seed))
os.makedirs(path, exist_ok=True)
eval_out = defaultdict(list)
diff --git a/feature_importance/00_run_ablation_regression_stability.py b/feature_importance/00_run_ablation_regression_stability.py
new file mode 100644
index 0000000..51711a3
--- /dev/null
+++ b/feature_importance/00_run_ablation_regression_stability.py
@@ -0,0 +1,523 @@
+import copy
+import os
+from os.path import join as oj
+import glob
+import argparse
+import pickle as pkl
+import time
+import warnings
+from scipy import stats
+import dask
+from dask.distributed import Client
+import numpy as np
+import pandas as pd
+from tqdm import tqdm
+import sys
+from collections import defaultdict
+from typing import Callable, List, Tuple
+import itertools
+from sklearn.metrics import roc_auc_score, f1_score, recall_score, precision_score, mean_squared_error, r2_score
+from sklearn import preprocessing
+from sklearn.ensemble import RandomForestRegressor
+from sklearn.linear_model import LinearRegression
+import xgboost as xgb
+from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
+from sklearn.linear_model import RidgeCV
+sys.path.append(".")
+sys.path.append("..")
+sys.path.append("../..")
+import fi_config
+from util import ModelConfig, FIModelConfig, tp, fp, neg, pos, specificity_score, auroc_score, auprc_score, compute_nsg_feat_corr_w_sig_subspace, apply_splitting_strategy
+import dill
+from sklearn.kernel_ridge import KernelRidge
+
+warnings.filterwarnings("ignore", message="Bins whose width")
+
+#RUN THE FILE
+# python 01_run_ablation_regression.py --nreps 5 --config mdi_local.real_data_regression --split_seed 331 --ignore_cache --create_rmd --result_name diabetes_regression
+
+
+def compare_estimators(estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ args, ) -> Tuple[dict, dict]:
+ """Calculates results given estimators, feature importance estimators, datasets, and metrics.
+ Called in run_comparison
+ """
+ if type(estimators) != list:
+ raise Exception("First argument needs to be a list of Models")
+ if type(metrics) != list:
+ raise Exception("Argument metrics needs to be a list containing ('name', callable) pairs")
+
+ # initialize results
+ results = defaultdict(lambda: [])
+ feature_importance_list = {"positive": {}, "negative": {}, "absolute": {}}
+
+ # loop over model estimators
+ for model in estimators:
+ # est = model.cls(**model.kwargs)
+
+ # get kwargs for all fi_ests
+ fi_kwargs = {}
+ for fi_est in fi_estimators:
+ fi_kwargs.update(fi_est.kwargs)
+
+ # get groups of estimators for each splitting strategy
+ fi_ests_dict = defaultdict(list)
+ for fi_est in fi_estimators:
+ fi_ests_dict[fi_est.splitting_strategy].append(fi_est)
+
+ # loop over splitting strategies
+ for splitting_strategy, fi_ests in fi_ests_dict.items():
+ # implement provided splitting strategy
+ if splitting_strategy is not None:
+ X_train, X_tune, X_test, y_train, y_tune, y_test = apply_splitting_strategy(X, y, splitting_strategy, args.split_seed)
+ else:
+ X_train = X
+ X_test = X
+ y_train = y
+ y_test = y
+
+ top_features = [3, 5, 10]
+ top_features_dict = {}
+ for fi_est in fi_ests:
+ top_features_dict[fi_est.name] = {}
+ for num_feature in top_features:
+ top_features_dict[fi_est.name][num_feature] = {"train": [], "test": []}
+ for i in range(X_train.shape[0]):
+ top_features_dict[fi_est.name][num_feature]["train"].append([])
+ for i in range(X_test.shape[0]):
+ top_features_dict[fi_est.name][num_feature]["test"].append([])
+
+ for rf_seed in range(5):
+ est = RandomForestRegressor(n_estimators=100, min_samples_leaf=5, max_features=0.33, random_state=rf_seed)
+ est.fit(X_train, y_train)
+
+ rf_plus_base = RandomForestPlusRegressor(rf_model=est)
+ rf_plus_base.fit(X_train, y_train)
+
+ for fi_est in tqdm(fi_ests):
+ if fi_est.base_model == "None":
+ loaded_model = None
+ elif fi_est.base_model == "RF":
+ loaded_model = est
+ elif fi_est.base_model == "RFPlus_default":
+ loaded_model = rf_plus_base
+
+ local_fi_score_train, local_fi_score_test = fi_est.cls(X_train=X_train, y_train=y_train, X_test=X_test, fit=loaded_model, mode="absolute")
+
+ if fi_est.ascending:
+ sorted_feature_train = np.argsort(-local_fi_score_train)
+ sorted_feature_test = np.argsort(-local_fi_score_test)
+ else:
+ sorted_feature_train = np.argsort(local_fi_score_train)
+ sorted_feature_test = np.argsort(local_fi_score_test)
+
+ for i in range(X_train.shape[0]):
+ for num_feature in top_features:
+ top_features_dict[fi_est.name][num_feature]["train"][i].extend(sorted_feature_train[i][:num_feature].tolist())
+
+ for i in range(X_test.shape[0]):
+ for num_feature in top_features:
+ top_features_dict[fi_est.name][num_feature]["test"][i].extend(sorted_feature_test[i][:num_feature].tolist())
+
+
+ for fi_est in tqdm(fi_ests):
+ metric_results = {
+ 'model': model.name,
+ 'fi': fi_est.name,
+ 'train_size': X_train.shape[0],
+ 'test_size': X_test.shape[0],
+ 'num_features': X_train.shape[1],
+ 'data_split_seed': args.split_seed,
+ }
+
+ for num_feature in top_features:
+ total_train = 0
+ for i in range(X_train.shape[0]):
+ total_train += len(set(top_features_dict[fi_est.name][num_feature]["train"][i]))
+ metric_results[f"avg_{num_feature}_features_train"] = total_train / X_train.shape[0]
+
+ total_test = 0
+ for i in range(X_test.shape[0]):
+ total_test += len(set(top_features_dict[fi_est.name][num_feature]["test"][i]))
+ metric_results[f"avg_{num_feature}_features_test"] = total_test / X_test.shape[0]
+
+ # initialize results with metadata and metric results
+ kwargs: dict = model.kwargs # dict
+ for k in kwargs:
+ results[k].append(kwargs[k])
+ for k in fi_kwargs:
+ if k in fi_est.kwargs:
+ results[k].append(str(fi_est.kwargs[k]))
+ else:
+ results[k].append(None)
+ for met_name, met_val in metric_results.items():
+ results[met_name].append(met_val)
+ # for key, value in results.items():
+ # print(f"{key}: {len(value)}")
+ return results, feature_importance_list
+
+
+def run_comparison(path: str,
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ args):
+ estimator_name = estimators[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_estimators) \
+ if fi_estimator.model_type in estimators[0].model_type]
+ model_comparison_files_all = [oj(path, f'{estimator_name}_{fi_estimator.name}_comparisons.pkl') \
+ for fi_estimator in fi_estimators_all]
+
+ feature_importance_all = oj(path, f'feature_importance.pkl')
+
+
+ if args.parallel_id is not None:
+ model_comparison_files_all = [f'_{args.parallel_id[0]}.'.join(model_comparison_file.split('.')) \
+ for model_comparison_file in model_comparison_files_all]
+
+ fi_estimators = []
+ model_comparison_files = []
+ for model_comparison_file, fi_estimator in zip(model_comparison_files_all, fi_estimators_all):
+ if os.path.isfile(model_comparison_file) and not args.ignore_cache:
+ print(
+ f'{estimator_name} with {fi_estimator.name} results already computed and cached. use --ignore_cache to recompute')
+ else:
+ fi_estimators.append(fi_estimator)
+ model_comparison_files.append(model_comparison_file)
+
+ if len(fi_estimators) == 0:
+ return
+
+ results, fi_lst = compare_estimators(estimators=estimators,
+ fi_estimators=fi_estimators,
+ X=X, y=y, support=support,
+ metrics=metrics,
+ args=args)
+
+ estimators_list = [e.name for e in estimators]
+ metrics_list = [m[0] for m in metrics]
+
+ df = pd.DataFrame.from_dict(results)
+ df['split_seed'] = args.split_seed
+ if args.nosave_cols is not None:
+ nosave_cols = np.unique([x.strip() for x in args.nosave_cols.split(",")])
+ else:
+ nosave_cols = []
+ for col in nosave_cols:
+ if col in df.columns:
+ df = df.drop(columns=[col])
+
+ pkl.dump(fi_lst, open(feature_importance_all, 'wb'))
+
+ for model_comparison_file, fi_estimator in zip(model_comparison_files, fi_estimators):
+ output_dict = {
+ # metadata
+ 'sim_name': args.config,
+ 'estimators': estimators_list,
+ 'fi_estimators': fi_estimator.name,
+ 'metrics': metrics_list,
+
+ # actual values
+ 'df': df.loc[df.fi == fi_estimator.name],
+ }
+ pkl.dump(output_dict, open(model_comparison_file, 'wb'))
+ return df
+
+
+def get_metrics():
+ return [('rocauc', auroc_score), ('prauc', auprc_score)]
+
+
+def reformat_results(results):
+ results = results.reset_index().drop(columns=['index'])
+ # fi_scores = pd.concat(results.pop('fi_scores').to_dict()). \
+ # reset_index(level=0).rename(columns={'level_0': 'index'})
+ # results_df = pd.merge(results, fi_scores, left_index=True, right_on="index")
+ # return results_df
+ return results
+
+def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests, metrics, args):
+ os.makedirs(oj(path, val_name, "rep" + str(i)), exist_ok=True)
+ np.random.seed(i)
+ max_iter = 100
+ iter = 0
+ while iter <= max_iter: # regenerate data if y is constant
+ X = X_dgp(**X_params_dict)
+ y, support, beta = y_dgp(X, **y_params_dict, return_support=True)
+ if not all(y == y[0]):
+ break
+ iter += 1
+ if iter > max_iter:
+ raise ValueError("Response y is constant.")
+ if args.omit_vars is not None:
+ omit_vars = np.unique([int(x.strip()) for x in args.omit_vars.split(",")])
+ support = np.delete(support, omit_vars)
+ X = np.delete(X, omit_vars, axis=1)
+ del beta # note: beta is not currently supported when using omit_vars
+
+ for est in ests:
+ results = run_comparison(path=oj(path, val_name, "rep" + str(i)),
+ X=X, y=y, support=support,
+ metrics=metrics,
+ estimators=est,
+ fi_estimators=fi_ests,
+ args=args)
+ return True
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+
+ default_dir = os.getenv("SCRATCH")
+ if default_dir is not None:
+ default_dir = oj(default_dir, "feature_importance", "results")
+ else:
+ default_dir = oj(os.path.dirname(os.path.realpath(__file__)), 'results')
+
+ parser.add_argument('--nreps', type=int, default=2)
+ parser.add_argument('--model', type=str, default=None) # , default='c4')
+ parser.add_argument('--fi_model', type=str, default=None) # , default='c4')
+ parser.add_argument('--config', type=str, default='test')
+ parser.add_argument('--omit_vars', type=str, default=None) # comma-separated string of variables to omit
+ parser.add_argument('--nosave_cols', type=str, default="prediction_model")
+
+ ### Newly added arguments
+ parser.add_argument('--folder_name', type=str, default=None)
+
+ # for multiple reruns, should support varying split_seed
+ parser.add_argument('--ignore_cache', action='store_true', default=False)
+ parser.add_argument('--verbose', action='store_true', default=True)
+ parser.add_argument('--parallel', action='store_true', default=False)
+ parser.add_argument('--parallel_id', nargs='+', type=int, default=None)
+ parser.add_argument('--n_cores', type=int, default=None)
+ parser.add_argument('--split_seed', type=int, default=0)
+ parser.add_argument('--results_path', type=str, default=default_dir)
+
+ # arguments for rmd output of results
+ parser.add_argument('--create_rmd', action='store_true', default=False)
+ parser.add_argument('--show_vars', type=int, default=None)
+
+ args = parser.parse_args()
+
+ if args.parallel:
+ if args.n_cores is None:
+ print(os.getenv("SLURM_CPUS_ON_NODE"))
+ n_cores = int(os.getenv("SLURM_CPUS_ON_NODE"))
+ else:
+ n_cores = args.n_cores
+ client = Client(n_workers=n_cores)
+
+ ests, fi_ests, \
+ X_dgp, X_params_dict, y_dgp, y_params_dict, \
+ vary_param_name, vary_param_vals = fi_config.get_fi_configs(args.config)
+
+ metrics = get_metrics()
+
+ if args.model:
+ ests = list(filter(lambda x: args.model.lower() == x[0].name.lower(), ests))
+ if args.fi_model:
+ fi_ests = list(filter(lambda x: args.fi_model.lower() == x[0].name.lower(), fi_ests))
+
+ if len(ests) == 0:
+ raise ValueError('No valid estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if len(fi_ests) == 0:
+ raise ValueError('No valid FI estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if args.verbose:
+ print('running', args.config,
+ 'ests', ests,
+ 'fi_ests', fi_ests)
+ print('\tsaving to', args.results_path)
+
+ if args.omit_vars is not None:
+ #results_dir = oj(args.results_path, args.config + "_omitted_vars")
+ results_dir = oj(args.results_path, args.config + "_omitted_vars", args.folder_name)
+ else:
+ #results_dir = oj(args.results_path, args.config)
+ results_dir = oj(args.results_path, args.config, args.folder_name)
+
+ # if isinstance(vary_param_name, list):
+ # path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.split_seed))
+ # else:
+ # path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
+ if isinstance(vary_param_name, list):
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "split_seed_" + str(args.split_seed))
+ else:
+ path = oj(results_dir, "varying_" + vary_param_name, "split_seed_" + str(args.split_seed))
+ os.makedirs(path, exist_ok=True)
+
+ eval_out = defaultdict(list)
+
+ vary_type = None
+ if isinstance(vary_param_name, list): # multiple parameters are being varied
+ # get parameters that are being varied over and identify whether it's a DGP/method/fi_method argument
+ keys, values = zip(*vary_param_vals.items())
+ vary_param_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
+ vary_type = {}
+ for vary_param_dict in vary_param_dicts:
+ for param_name, param_val in vary_param_dict.items():
+ if param_name in X_params_dict.keys() and param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif param_name in X_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ X_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ elif param_name in y_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ y_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ else:
+ est_kwargs = list(
+ itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if param_name in est_kwargs:
+ vary_type[param_name] = "est"
+ elif param_name in fi_est_kwargs:
+ vary_type[param_name] = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp,
+ y_params_dict, y_dgp, ests, fi_ests, metrics, args) for i in
+ range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [
+ run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ y_dgp, ests, fi_ests, metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ else: # only on parameter is being varied over
+ # get parameter that is being varied over and identify whether it's a DGP/method/fi_method argument
+ for val_name, val in vary_param_vals.items():
+ if vary_param_name in X_params_dict.keys() and vary_param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif vary_param_name in X_params_dict.keys():
+ vary_type = "dgp"
+ X_params_dict[vary_param_name] = val
+ elif vary_param_name in y_params_dict.keys():
+ vary_type = "dgp"
+ y_params_dict[vary_param_name] = val
+ else:
+ est_kwargs = list(itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if vary_param_name in est_kwargs:
+ vary_type = "est"
+ elif vary_param_name in fi_est_kwargs:
+ vary_type = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests,
+ fi_ests, metrics, args) for i in range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests,
+ metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ print('completed all experiments successfully!')
+
+ # get model file names
+ model_comparison_files_all = []
+ for est in ests:
+ estimator_name = est[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_ests) \
+ if fi_estimator.model_type in est[0].model_type]
+ model_comparison_files = [f'{estimator_name}_{fi_estimator.name}_comparisons.pkl' for fi_estimator in
+ fi_estimators_all]
+ model_comparison_files_all += model_comparison_files
+
+ # aggregate results
+ results_list = []
+ if isinstance(vary_param_name, list):
+ for vary_param_dict in vary_param_dicts:
+ val_name = "_".join(vary_param_dict.values())
+
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+
+ for param_name, param_val in vary_param_dict.items():
+ val = vary_param_vals[param_name][param_val]
+ if vary_type[param_name] == "dgp":
+ if np.isscalar(val):
+ results.insert(0, param_name, val)
+ else:
+ results.insert(0, param_name, [val for i in range(results.shape[0])])
+ results.insert(1, param_name + "_name", param_val)
+ elif vary_type[param_name] == "est" or vary_type[param_name] == "fi_est":
+ results.insert(0, param_name + "_name", copy.deepcopy(results[param_name]))
+ results.insert(0, 'rep', i)
+ results_list.append(results)
+ else:
+ for val_name, val in vary_param_vals.items():
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+ if vary_type == "dgp":
+ if np.isscalar(val):
+ results.insert(0, vary_param_name, val)
+ else:
+ results.insert(0, vary_param_name, [val for i in range(results.shape[0])])
+ results.insert(1, vary_param_name + "_name", val_name)
+ results.insert(2, 'rep', i)
+ elif vary_type == "est" or vary_type == "fi_est":
+ results.insert(0, vary_param_name + "_name", copy.deepcopy(results[vary_param_name]))
+ results.insert(1, 'rep', i)
+ results_list.append(results)
+ results_merged = pd.concat(results_list, axis=0)
+ pkl.dump(results_merged, open(oj(path, 'results.pkl'), 'wb'))
+ results_df = reformat_results(results_merged)
+ results_df.to_csv(oj(path, 'results.csv'), index=False)
+
+ print('merged and saved all experiment results successfully!')
+
+ # create R markdown summary of results
+ if args.create_rmd:
+ if args.show_vars is None:
+ show_vars = 'NULL'
+ else:
+ show_vars = args.show_vars
+
+ if isinstance(vary_param_name, list):
+ vary_param_name = "; ".join(vary_param_name)
+
+ sim_rmd = os.path.basename(results_dir) + '_simulation_results.Rmd'
+ os.system(
+ 'cp {} \'{}\''.format(oj("rmd", "simulation_results.Rmd"), sim_rmd)
+ )
+ os.system(
+ 'Rscript -e "rmarkdown::render(\'{}\', params = list(results_dir = \'{}\', vary_param_name = \'{}\', seed = {}, keep_vars = {}), output_file = \'{}\', quiet = TRUE)"'.format(
+ sim_rmd,
+ results_dir, vary_param_name, str(args.split_seed), str(show_vars),
+ oj(path, "simulation_results.html"))
+ )
+ os.system('rm \'{}\''.format(sim_rmd))
+ print("created rmd of simulation results successfully!")
\ No newline at end of file
diff --git a/feature_importance/00_run_feature_ranking_simulation.py b/feature_importance/00_run_feature_ranking_simulation.py
new file mode 100644
index 0000000..6a4996c
--- /dev/null
+++ b/feature_importance/00_run_feature_ranking_simulation.py
@@ -0,0 +1,548 @@
+import copy
+import os
+from os.path import join as oj
+import glob
+import argparse
+import pickle as pkl
+import time
+import warnings
+from scipy import stats
+import dask
+from dask.distributed import Client
+import numpy as np
+import pandas as pd
+from tqdm import tqdm
+import sys
+from collections import defaultdict
+from typing import Callable, List, Tuple
+import itertools
+from sklearn.metrics import roc_auc_score, f1_score, average_precision_score, recall_score, precision_score, mean_squared_error, r2_score
+from sklearn import preprocessing
+from sklearn.ensemble import RandomForestRegressor
+from sklearn.linear_model import LinearRegression
+import xgboost as xgb
+from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
+from sklearn.linear_model import Ridge
+sys.path.append(".")
+sys.path.append("..")
+sys.path.append("../..")
+import fi_config
+from util import ModelConfig, FIModelConfig, tp, fp, neg, pos, specificity_score, auroc_score, auprc_score, compute_nsg_feat_corr_w_sig_subspace, apply_splitting_strategy
+import dill
+warnings.filterwarnings("ignore", message="Bins whose width")
+
+def compare_estimators(estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ X, y, support,
+ metrics: List[Tuple[str, Callable]],
+ args,
+ vary_setting) -> Tuple[dict, dict]:
+ """Calculates results given estimators, feature importance estimators, datasets, and metrics.
+ Called in run_comparison
+ """
+ if type(estimators) != list:
+ raise Exception("First argument needs to be a list of Models")
+ if type(metrics) != list:
+ raise Exception("Argument metrics needs to be a list containing ('name', callable) pairs")
+
+ # initialize results
+ results = defaultdict(lambda: [])
+ feature_importance_list = {"absolute": {}}
+
+ # loop over model estimators
+ for model in estimators:
+ est = model.cls(**model.kwargs)
+
+ # get kwargs for all fi_ests
+ fi_kwargs = {}
+ for fi_est in fi_estimators:
+ fi_kwargs.update(fi_est.kwargs)
+
+ # get groups of estimators for each splitting strategy
+ fi_ests_dict = defaultdict(list)
+ for fi_est in fi_estimators:
+ fi_ests_dict[fi_est.splitting_strategy].append(fi_est)
+
+ # loop over splitting strategies
+ for splitting_strategy, fi_ests in fi_ests_dict.items():
+ # implement provided splitting strategy
+ if splitting_strategy is not None:
+ X_train, X_tune, X_test, y_train, y_tune, y_test = apply_splitting_strategy(X, y, splitting_strategy, args.split_seed)
+ else:
+ X_train = X
+ X_test = X
+ y_train = y
+ y_test = y
+
+ # check if there are NA values in the data
+ if np.isnan(X_train).any() or np.isnan(y_train).any():
+ raise ValueError("There are NA values in the data")
+ if np.isnan(X_test).any() or np.isnan(y_test).any():
+ raise ValueError("There are NA values in the data")
+
+ # fit RF model
+ start_rf = time.time()
+ est.fit(X_train, y_train)
+ end_rf = time.time()
+
+ # fit default RF_plus model
+ start_rf_plus = time.time()
+ rf_plus_base = RandomForestPlusRegressor(rf_model=est)
+ rf_plus_base.fit(X_train, y_train)
+ end_rf_plus = time.time()
+
+ # get test results
+ test_all_mse_rf = mean_squared_error(y_test, est.predict(X_test))
+ test_all_r2_rf = r2_score(y_test, est.predict(X_test))
+ test_all_mse_rf_plus = mean_squared_error(y_test, rf_plus_base.predict(X_test))
+ test_all_r2_rf_plus = r2_score(y_test, rf_plus_base.predict(X_test))
+
+ fitted_results = {
+ "Model": ["RF", "RF_plus"],
+ "MSE": [test_all_mse_rf, test_all_mse_rf_plus],
+ "R2": [test_all_r2_rf, test_all_r2_rf_plus],
+ "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus],
+ "Y_seed": [args.y_seed, args.y_seed],
+ "Split_seed": [args.split_seed, args.split_seed]
+ }
+ temp = ""
+ for vary_name in vary_setting:
+ fitted_results[vary_name] = [vary_setting[vary_name]] * 3
+ temp += f"{vary_name}_{vary_setting[vary_name]}_"
+
+ print(fitted_results)
+
+ os.makedirs(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}", exist_ok=True)
+ results_df = pd.DataFrame(fitted_results)
+ results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_fitted_summary_{args.y_seed}_{args.split_seed}_{temp}.csv", index=False)
+
+ # loop over fi estimators
+ for fi_est in tqdm(fi_ests):
+ metric_results = {
+ 'model': model.name,
+ 'fi': fi_est.name,
+ 'train_size': X_train.shape[0],
+ 'test_size': X_test.shape[0],
+ 'num_features': X_train.shape[1],
+ 'data_split_seed': args.split_seed,
+ }
+
+ if fi_est.base_model == "None":
+ loaded_model = None
+ elif fi_est.base_model == "RF":
+ loaded_model = est
+ elif fi_est.base_model == "RFPlus_default":
+ loaded_model = rf_plus_base
+
+ local_fi_score_train, local_fi_score_test = fi_est.cls(X_train=X_train, y_train=y_train, X_test=X_test, fit=loaded_model, mode="absolute")
+ feature_importance_list["absolute"][fi_est.name] = [local_fi_score_train, local_fi_score_test]
+ all_fi_data = {"train": local_fi_score_train, "test": local_fi_score_test}
+
+ for d in all_fi_data:
+ fi_data = all_fi_data[d]
+ if not isinstance(fi_data, np.ndarray):
+ metric_results[f'auroc_{d}'] = None
+ metric_results[f'auprc_{d}'] = None
+ else:
+ auroc = []
+ auprc = []
+ for i in range(fi_data.shape[0]):
+ fi_data_i = fi_data[i]
+ if fi_est.ascending:
+ auroc.append(roc_auc_score(support, fi_data_i))
+ auprc.append(average_precision_score(support, fi_data_i))
+ else:
+ auroc.append(roc_auc_score(support, -1*fi_data_i))
+ auprc.append(average_precision_score(support, -1*fi_data_i))
+ metric_results[f'auroc_{d}'] = np.array(auroc).mean()
+ metric_results[f'auprc_{d}'] = np.array(auprc).mean()
+
+ # initialize results with metadata and metric results
+ kwargs: dict = model.kwargs # dict
+ for k in kwargs:
+ results[k].append(kwargs[k])
+ for k in fi_kwargs:
+ if k in fi_est.kwargs:
+ results[k].append(str(fi_est.kwargs[k]))
+ else:
+ results[k].append(None)
+ for met_name, met_val in metric_results.items():
+ results[met_name].append(met_val)
+ return results, feature_importance_list
+
+
+def run_comparison(path: str,
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ args,
+ vary_setting):
+ estimator_name = estimators[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_estimators) \
+ if fi_estimator.model_type in estimators[0].model_type]
+ model_comparison_files_all = [oj(path, f'{estimator_name}_{fi_estimator.name}_comparisons.pkl') \
+ for fi_estimator in fi_estimators_all]
+
+ feature_importance_all = oj(path, f'feature_importance.pkl')
+
+
+ if args.parallel_id is not None:
+ model_comparison_files_all = [f'_{args.parallel_id[0]}.'.join(model_comparison_file.split('.')) \
+ for model_comparison_file in model_comparison_files_all]
+
+ fi_estimators = []
+ model_comparison_files = []
+ for model_comparison_file, fi_estimator in zip(model_comparison_files_all, fi_estimators_all):
+ if os.path.isfile(model_comparison_file) and not args.ignore_cache:
+ print(
+ f'{estimator_name} with {fi_estimator.name} results already computed and cached. use --ignore_cache to recompute')
+ else:
+ fi_estimators.append(fi_estimator)
+ model_comparison_files.append(model_comparison_file)
+
+ if len(fi_estimators) == 0:
+ return
+
+ results, fi_lst = compare_estimators(estimators=estimators,
+ fi_estimators=fi_estimators,
+ X=X, y=y, support=support,
+ metrics=metrics,
+ args=args,
+ vary_setting=vary_setting)
+
+ estimators_list = [e.name for e in estimators]
+ metrics_list = [m[0] for m in metrics]
+
+ df = pd.DataFrame.from_dict(results)
+ df['split_seed'] = args.split_seed
+ if args.nosave_cols is not None:
+ nosave_cols = np.unique([x.strip() for x in args.nosave_cols.split(",")])
+ else:
+ nosave_cols = []
+ for col in nosave_cols:
+ if col in df.columns:
+ df = df.drop(columns=[col])
+
+ pkl.dump(fi_lst, open(feature_importance_all, 'wb'))
+
+ for model_comparison_file, fi_estimator in zip(model_comparison_files, fi_estimators):
+ output_dict = {
+ # metadata
+ 'sim_name': args.config,
+ 'estimators': estimators_list,
+ 'fi_estimators': fi_estimator.name,
+ 'metrics': metrics_list,
+
+ # actual values
+ 'df': df.loc[df.fi == fi_estimator.name],
+ }
+ pkl.dump(output_dict, open(model_comparison_file, 'wb'))
+ return df
+
+
+def get_metrics():
+ return [('rocauc', auroc_score), ('prauc', auprc_score)]
+
+
+def reformat_results(results):
+ results = results.reset_index().drop(columns=['index'])
+ # fi_scores = pd.concat(results.pop('fi_scores').to_dict()). \
+ # reset_index(level=0).rename(columns={'level_0': 'index'})
+ # results_df = pd.merge(results, fi_scores, left_index=True, right_on="index")
+ # return results_df
+ return results
+
+def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests, metrics, args, vary_setting):
+ os.makedirs(oj(path, val_name, "rep" + str(i)), exist_ok=True)
+ np.random.seed(i)
+ max_iter = 100
+ iter = 0
+ while iter <= max_iter: # regenerate data if y is constant
+ X = X_dgp(**X_params_dict)
+ y, support, beta = y_dgp(X, **y_params_dict, seed = args.y_seed, return_support=True)
+ if not all(y == y[0]):
+ break
+ iter += 1
+ if iter > max_iter:
+ raise ValueError("Response y is constant.")
+ if args.omit_vars is not None:
+ omit_vars = np.unique([int(x.strip()) for x in args.omit_vars.split(",")])
+ support = np.delete(support, omit_vars)
+ X = np.delete(X, omit_vars, axis=1)
+ del beta # note: beta is not currently supported when using omit_vars
+
+ for est in ests:
+ results = run_comparison(path=oj(path, val_name, "rep" + str(i)),
+ X=X, y=y, support=support,
+ metrics=metrics,
+ estimators=est,
+ fi_estimators=fi_ests,
+ args=args,
+ vary_setting=vary_setting)
+ return True
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+
+ default_dir = os.getenv("SCRATCH")
+ if default_dir is not None:
+ default_dir = oj(default_dir, "feature_importance", "results")
+ else:
+ default_dir = oj(os.path.dirname(os.path.realpath(__file__)), 'results')
+
+ parser.add_argument('--nreps', type=int, default=2)
+ parser.add_argument('--model', type=str, default=None) # , default='c4')
+ parser.add_argument('--fi_model', type=str, default=None) # , default='c4')
+ parser.add_argument('--config', type=str, default='test')
+ parser.add_argument('--omit_vars', type=str, default=None) # comma-separated string of variables to omit
+ parser.add_argument('--nosave_cols', type=str, default="prediction_model")
+
+ ### Newly added arguments
+ parser.add_argument('--folder_name', type=str, default=None)
+
+ # for multiple reruns, should support varying split_seed
+ parser.add_argument('--ignore_cache', action='store_true', default=False)
+ parser.add_argument('--verbose', action='store_true', default=True)
+ parser.add_argument('--parallel', action='store_true', default=False)
+ parser.add_argument('--parallel_id', nargs='+', type=int, default=None)
+ parser.add_argument('--n_cores', type=int, default=None)
+ parser.add_argument('--split_seed', type=int, default=0)
+ parser.add_argument('--results_path', type=str, default=default_dir)
+ parser.add_argument('--y_seed', type=int, default=0)
+
+ # arguments for rmd output of results
+ parser.add_argument('--create_rmd', action='store_true', default=False)
+ parser.add_argument('--show_vars', type=int, default=None)
+
+ args = parser.parse_args()
+
+ if args.parallel:
+ if args.n_cores is None:
+ print(os.getenv("SLURM_CPUS_ON_NODE"))
+ n_cores = int(os.getenv("SLURM_CPUS_ON_NODE"))
+ else:
+ n_cores = args.n_cores
+ client = Client(n_workers=n_cores)
+
+ ests, fi_ests, \
+ X_dgp, X_params_dict, y_dgp, y_params_dict, \
+ vary_param_name, vary_param_vals = fi_config.get_fi_configs(args.config)
+
+ metrics = get_metrics()
+
+ if args.model:
+ ests = list(filter(lambda x: args.model.lower() == x[0].name.lower(), ests))
+ if args.fi_model:
+ fi_ests = list(filter(lambda x: args.fi_model.lower() == x[0].name.lower(), fi_ests))
+
+ if len(ests) == 0:
+ raise ValueError('No valid estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if len(fi_ests) == 0:
+ raise ValueError('No valid FI estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if args.verbose:
+ print('running', args.config,
+ 'ests', ests,
+ 'fi_ests', fi_ests)
+ print('\tsaving to', args.results_path)
+
+ if args.omit_vars is not None:
+ #results_dir = oj(args.results_path, args.config + "_omitted_vars")
+ results_dir = oj(args.results_path, args.config + "_omitted_vars", args.folder_name)
+ else:
+ #results_dir = oj(args.results_path, args.config)
+ results_dir = oj(args.results_path, args.config, args.folder_name)
+
+ if isinstance(vary_param_name, list):
+ #path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.split_seed))
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.y_seed)+ str(args.split_seed))
+ else:
+ #path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
+ path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.y_seed)+ str(args.split_seed))
+ os.makedirs(path, exist_ok=True)
+
+ eval_out = defaultdict(list)
+
+ vary_type = None
+ if isinstance(vary_param_name, list): # multiple parameters are being varied
+ # get parameters that are being varied over and identify whether it's a DGP/method/fi_method argument
+ keys, values = zip(*vary_param_vals.items())
+ vary_param_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
+ vary_type = {}
+ #### Added
+ vary_setting = {}
+ ####
+ for vary_param_dict in vary_param_dicts:
+ for param_name, param_val in vary_param_dict.items():
+ if param_name in X_params_dict.keys() and param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif param_name in X_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ X_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ elif param_name in y_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ y_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ else:
+ est_kwargs = list(
+ itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if param_name in est_kwargs:
+ vary_type[param_name] = "est"
+ elif param_name in fi_est_kwargs:
+ vary_type[param_name] = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+ #### Added
+ vary_setting[param_name] = param_val
+ ####
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp,
+ y_params_dict, y_dgp, ests, fi_ests, metrics, args, vary_setting) for i in
+ range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ # results = [
+ # run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ # y_dgp, ests, fi_ests, metrics, args) for i in range(args.nreps)]
+ results = [
+ run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ y_dgp, ests, fi_ests, metrics, args, vary_setting) for i in range(args.nreps)]
+ assert all(results)
+
+ else: # only on parameter is being varied over
+ # get parameter that is being varied over and identify whether it's a DGP/method/fi_method argument
+ for val_name, val in vary_param_vals.items():
+ if vary_param_name in X_params_dict.keys() and vary_param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif vary_param_name in X_params_dict.keys():
+ vary_type = "dgp"
+ X_params_dict[vary_param_name] = val
+ elif vary_param_name in y_params_dict.keys():
+ vary_type = "dgp"
+ y_params_dict[vary_param_name] = val
+ else:
+ est_kwargs = list(itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if vary_param_name in est_kwargs:
+ vary_type = "est"
+ elif vary_param_name in fi_est_kwargs:
+ vary_type = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests,
+ fi_ests, metrics, args) for i in range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [
+ run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ y_dgp, ests, fi_ests, metrics, args) for i in range(args.nreps)]
+ # results = [run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests,
+ # metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ print('completed all experiments successfully!')
+
+ # get model file names
+ model_comparison_files_all = []
+ for est in ests:
+ estimator_name = est[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_ests) \
+ if fi_estimator.model_type in est[0].model_type]
+ model_comparison_files = [f'{estimator_name}_{fi_estimator.name}_comparisons.pkl' for fi_estimator in
+ fi_estimators_all]
+ model_comparison_files_all += model_comparison_files
+
+ # aggregate results
+ results_list = []
+ if isinstance(vary_param_name, list):
+ for vary_param_dict in vary_param_dicts:
+ val_name = "_".join(vary_param_dict.values())
+
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+
+ for param_name, param_val in vary_param_dict.items():
+ val = vary_param_vals[param_name][param_val]
+ if vary_type[param_name] == "dgp":
+ if np.isscalar(val):
+ results.insert(0, param_name, val)
+ else:
+ results.insert(0, param_name, [val for i in range(results.shape[0])])
+ results.insert(1, param_name + "_name", param_val)
+ elif vary_type[param_name] == "est" or vary_type[param_name] == "fi_est":
+ results.insert(0, param_name + "_name", copy.deepcopy(results[param_name]))
+ results.insert(0, 'rep', i)
+ results_list.append(results)
+ else:
+ for val_name, val in vary_param_vals.items():
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+ if vary_type == "dgp":
+ if np.isscalar(val):
+ results.insert(0, vary_param_name, val)
+ else:
+ results.insert(0, vary_param_name, [val for i in range(results.shape[0])])
+ results.insert(1, vary_param_name + "_name", val_name)
+ results.insert(2, 'rep', i)
+ elif vary_type == "est" or vary_type == "fi_est":
+ results.insert(0, vary_param_name + "_name", copy.deepcopy(results[vary_param_name]))
+ results.insert(1, 'rep', i)
+ results_list.append(results)
+ results_merged = pd.concat(results_list, axis=0)
+ pkl.dump(results_merged, open(oj(path, 'results.pkl'), 'wb'))
+ results_df = reformat_results(results_merged)
+ results_df.to_csv(oj(path, 'results.csv'), index=False)
+
+ print('merged and saved all experiment results successfully!')
+
+ # create R markdown summary of results
+ if args.create_rmd:
+ if args.show_vars is None:
+ show_vars = 'NULL'
+ else:
+ show_vars = args.show_vars
+
+ if isinstance(vary_param_name, list):
+ vary_param_name = "; ".join(vary_param_name)
+
+ sim_rmd = os.path.basename(results_dir) + '_simulation_results.Rmd'
+ os.system(
+ 'cp {} \'{}\''.format(oj("rmd", "simulation_results.Rmd"), sim_rmd)
+ )
+ os.system(
+ 'Rscript -e "rmarkdown::render(\'{}\', params = list(results_dir = \'{}\', vary_param_name = \'{}\', seed = {}, keep_vars = {}), output_file = \'{}\', quiet = TRUE)"'.format(
+ sim_rmd,
+ results_dir, vary_param_name, str(args.split_seed), str(show_vars),
+ oj(path, "simulation_results.html"))
+ )
+ os.system('rm \'{}\''.format(sim_rmd))
+ print("created rmd of simulation results successfully!")
\ No newline at end of file
diff --git a/feature_importance/OLD_00_run_ablation_classification_retrain.py b/feature_importance/OLD_00_run_ablation_classification_retrain.py
new file mode 100644
index 0000000..8412a8e
--- /dev/null
+++ b/feature_importance/OLD_00_run_ablation_classification_retrain.py
@@ -0,0 +1,936 @@
+import copy
+import os
+from os.path import join as oj
+import glob
+import argparse
+import pickle as pkl
+import time
+import warnings
+from scipy import stats
+import dask
+from dask.distributed import Client
+import numpy as np
+import pandas as pd
+from tqdm import tqdm
+import sys
+from collections import defaultdict
+from typing import Callable, List, Tuple
+import itertools
+from sklearn.metrics import roc_auc_score, f1_score, recall_score, precision_score, mean_squared_error, average_precision_score, log_loss
+from sklearn import preprocessing
+from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
+from sklearn.linear_model import LogisticRegressionCV
+from sklearn.linear_model import LinearRegression
+from sklearn.svm import SVC
+import xgboost as xgb
+from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
+sys.path.append(".")
+sys.path.append("..")
+sys.path.append("../..")
+import fi_config
+from util import ModelConfig, FIModelConfig, tp, fp, neg, pos, specificity_score, auroc_score, auprc_score, compute_nsg_feat_corr_w_sig_subspace, apply_splitting_strategy
+from sklearn.linear_model import Ridge
+warnings.filterwarnings("ignore", message="Bins whose width")
+import dill
+
+#RUN THE FILE
+# python 01_run_ablation_classification.py --nreps 5 --config mdi_local.real_data_classification --split_seed 331 --ignore_cache --create_rmd --result_name diabetes_classification
+
+
+# def generate_random_shuffle(data, seed):
+# """
+# Randomly shuffle each column of the data.
+# """
+# np.random.seed(seed)
+# return np.array([np.random.permutation(data[:, i]) for i in range(data.shape[1])]).T
+
+
+# def ablation(data, feature_importance, mode, num_features, seed):
+# """
+# Replace the top num_features max feature importance data with random shuffle for each sample
+# """
+# assert mode in ["max", "min"]
+# fi = feature_importance.to_numpy()
+# shuffle = generate_random_shuffle(data, seed)
+# if mode == "max":
+# indices = np.argsort(-fi)
+# else:
+# indices = np.argsort(fi)
+# data_copy = data.copy()
+# for i in range(data.shape[0]):
+# for j in range(num_features):
+# data_copy[i, indices[i,j]] = shuffle[i, indices[i,j]]
+# return data_copy
+
+# def ablation_removal(train_mean, data, feature_importance_rank, feature_index):
+# """
+# Replace the top num_features max feature importance data with mean value for each sample
+# """
+# data_copy = data.copy()
+# for i in range(data.shape[0]):
+# data_copy[i, feature_importance_rank[i,feature_index]] = train_mean[feature_importance_rank[i,feature_index]]
+# return data_copy
+
+# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
+# """
+# Initialize the data with mean values and add the top num_features max feature importance data for each sample
+# """
+# data_copy = data_ablation.copy()
+# for i in range(data.shape[0]):
+# data_copy[i, feature_importance_rank[i,feature_index]] = data[i, feature_importance_rank[i,feature_index]]
+# return data_copy
+
+
+# def ablation_removal(train_mean, data, feature_importance, feature_importance_rank, feature_index, mode):
+# if mode == "absolute":
+# return ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index)
+# # else:
+# # return ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index)
+
+
+# def ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index):
+# """
+# Replace the top num_features max feature importance data with mean value for each sample
+# """
+# data_copy = data.copy()
+# indices = feature_importance_rank[:, feature_index]
+# data_copy[np.arange(data.shape[0]), indices] = train_mean[indices]
+# return data_copy
+
+
+def select_top_features(array, sorted_indices, percentage):
+ array = copy.deepcopy(array)
+ num_features = array.shape[1]
+ num_selected = int(np.ceil(num_features * percentage))
+ selected_indices = sorted_indices[:num_selected]
+ selected_array = array[:, selected_indices]
+ return num_selected, selected_array
+
+
+# def ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index):
+# data_copy = data.copy()
+# indices = feature_importance_rank[:, feature_index]
+# sum = 0
+# for i in range(data.shape[0]):
+# if feature_importance[i, indices[i]] != 0 and feature_importance[i, indices[i]] < sys.maxsize - 1:
+# sum += 1
+# data_copy[i, indices[i]] = train_mean[indices[i]]
+# print("Remove sum: ", sum)
+# return data_copy
+
+# def delta_mae(y_true, y_pred_1, y_pred_2):
+# mae_before = np.abs(y_true - y_pred_1)
+# mae_after = np.abs(y_true - y_pred_2)
+# absolute_delta_mae = np.mean(np.abs(mae_before - mae_after))
+# return absolute_delta_mae
+
+# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
+# """
+# Initialize the data with mean values and add the top num_features max feature importance data for each sample
+# """
+# data_copy = data_ablation.copy()
+# indices = feature_importance_rank[:, feature_index]
+# data_copy[np.arange(data.shape[0]), indices] = data[np.arange(data.shape[0]), indices]
+# return data_copy
+
+
+def compare_estimators(estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ args, ) -> Tuple[dict, dict]:
+ """Calculates results given estimators, feature importance estimators, datasets, and metrics.
+ Called in run_comparison
+ """
+ if type(estimators) != list:
+ raise Exception("First argument needs to be a list of Models")
+ if type(metrics) != list:
+ raise Exception("Argument metrics needs to be a list containing ('name', callable) pairs")
+
+ # initialize results
+ results = defaultdict(lambda: [])
+ feature_importance_list = {"positive": {}, "negative": {}, "absolute": {}}
+
+ # loop over model estimators
+ for model in estimators:
+ est = model.cls(**model.kwargs)
+
+ # get kwargs for all fi_ests
+ fi_kwargs = {}
+ for fi_est in fi_estimators:
+ fi_kwargs.update(fi_est.kwargs)
+
+ # get groups of estimators for each splitting strategy
+ fi_ests_dict = defaultdict(list)
+ for fi_est in fi_estimators:
+ fi_ests_dict[fi_est.splitting_strategy].append(fi_est)
+
+ # loop over splitting strategies
+ for splitting_strategy, fi_ests in fi_ests_dict.items():
+ # implement provided splitting strategy
+ if splitting_strategy is not None:
+ X_train, X_tune, X_test, y_train, y_tune, y_test = apply_splitting_strategy(X, y, splitting_strategy, args.split_seed)
+ else:
+ X_train = X
+ X_tune = X
+ X_test = X
+ y_train = y
+ y_tune = y
+ y_test = y
+
+ if args.fit_model:
+ print("Fitting Models")
+ # fit RF model
+ start_rf = time.time()
+ est = RandomForestClassifier(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed)
+ est.fit(X_train, y_train)
+ end_rf = time.time()
+
+ # fit default RF_plus model
+ start_rf_plus = time.time()
+ rf_plus_base = RandomForestPlusClassifier(rf_model=est)
+ rf_plus_base.fit(X_train, y_train)
+ end_rf_plus = time.time()
+
+ # fit oob RF_plus model
+ start_rf_plus_oob = time.time()
+ rf_plus_base_oob = RandomForestPlusClassifier(rf_model=est, fit_on="oob")
+ rf_plus_base_oob.fit(X_train, y_train)
+ end_rf_plus_oob = time.time()
+
+ #fit inbag RF_plus model
+ start_rf_plus_inbag = time.time()
+ est_regressor = RandomForestRegressor(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed)
+ est_regressor.fit(X_train, y_train)
+ rf_plus_base_inbag = RandomForestPlusRegressor(rf_model=est_regressor, include_raw=False, fit_on="inbag", prediction_model=LinearRegression())
+ rf_plus_base_inbag.fit(X_train, y_train)
+ end_rf_plus_inbag = time.time()
+
+ # get test results
+ test_all_auc_rf = roc_auc_score(y_test, est.predict_proba(X_test)[:, 1])
+ test_all_auprc_rf = average_precision_score(y_test, est.predict_proba(X_test)[:, 1])
+ test_all_f1_rf = f1_score(y_test, est.predict_proba(X_test)[:, 1] > 0.5)
+ test_all_auc_rf_plus = roc_auc_score(y_test, rf_plus_base.predict_proba(X_test)[:, 1])
+ test_all_auprc_rf_plus = average_precision_score(y_test, rf_plus_base.predict_proba(X_test)[:, 1])
+ test_all_f1_rf_plus = f1_score(y_test, rf_plus_base.predict_proba(X_test)[:, 1] > 0.5)
+ test_all_auc_rf_plus_oob = roc_auc_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1])
+ test_all_auprc_rf_plus_oob = average_precision_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1])
+ test_all_f1_rf_plus_oob = f1_score(y_test, rf_plus_base_oob.predict_proba(X_test)[:, 1] > 0.5)
+ test_all_auc_rf_plus_inbag = roc_auc_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1])
+ test_all_auprc_rf_plus_inbag = average_precision_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1])
+ test_all_f1_rf_plus_inbag = f1_score(y_test, rf_plus_base_inbag.predict_proba(X_test)[:, 1] > 0.5)
+
+ fitted_results = {
+ "Model": ["RF", "RF_plus", "RF_plus_oob", "RF_plus_inbag"],
+ "AUC": [test_all_auc_rf, test_all_auc_rf_plus, test_all_auc_rf_plus_oob, test_all_auc_rf_plus_inbag],
+ "AUPRC": [test_all_auprc_rf, test_all_auprc_rf_plus, test_all_auprc_rf_plus_oob, test_all_auprc_rf_plus_inbag],
+ "F1": [test_all_f1_rf, test_all_f1_rf_plus, test_all_f1_rf_plus_oob, test_all_f1_rf_plus_inbag],
+ "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob, end_rf_plus_inbag - start_rf_plus_inbag]
+ }
+
+ os.makedirs(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}", exist_ok=True)
+ results_df = pd.DataFrame(fitted_results)
+ results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_rf_seed_{args.rf_seed}_split_seed_{args.split_seed}.csv", index=False)
+
+
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RF_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(est, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_default_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base_oob, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base_inbag, file)
+
+ # if args.absolute_masking or args.positive_masking or args.negative_masking:
+ # np.random.seed(42)
+ # if X_train.shape[0] > 100:
+ # indices_train = np.random.choice(X_train.shape[0], 100, replace=False)
+ # X_train_subset = X_train[indices_train]
+ # y_train_subset = y_train[indices_train]
+ # else:
+ # indices_train = np.arange(X_train.shape[0])
+ # X_train_subset = X_train
+ # y_train_subset = y_train
+
+ # if X_test.shape[0] > 100:
+ # indices_test = np.random.choice(X_test.shape[0], 100, replace=False)
+ # X_test_subset = X_test[indices_test]
+ # y_test_subset = y_test[indices_test]
+ # else:
+ # indices_test = np.arange(X_test.shape[0])
+ # X_test_subset = X_test
+ # y_test_subset = y_test
+
+ if args.num_features_masked is None:
+ num_features_masked = X_train.shape[1]
+ else:
+ num_features_masked = args.num_features_masked
+
+
+ for fi_est in tqdm(fi_ests):
+ metric_results = {
+ 'model': model.name,
+ 'fi': fi_est.name,
+ 'train_size': X_train.shape[0],
+ # 'train_subset_size': X_train_subset.shape[0],
+ 'test_size': X_test.shape[0],
+ # 'test_subset_size': X_test_subset.shape[0],
+ 'num_features': X_train.shape[1],
+ 'data_split_seed': args.split_seed,
+ 'rf_seed': args.rf_seed,
+ 'num_features_masked': num_features_masked
+ }
+ # for i in range(X_train_subset.shape[0]):
+ # metric_results[f'sample_train_{i}'] = indices_train[i]
+ # for i in range(X_test_subset.shape[0]):
+ # metric_results[f'sample_test_{i}'] = indices_test[i]
+ print("Load Models")
+ start = time.time()
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_default_{args.split_seed}.dill", 'rb') as file:
+ # rf_plus_base = dill.load(file)
+ # if fi_est.base_model == "None":
+ # loaded_model = None
+ # elif fi_est.base_model == "RF":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RF_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_oob":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_inbag":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_default":
+ # loaded_model = rf_plus_base
+ rf_plus_base = rf_plus_base
+ if fi_est.base_model == "None":
+ loaded_model = None
+ elif fi_est.base_model == "RF":
+ loaded_model = est
+ elif fi_est.base_model == "RFPlus_oob":
+ loaded_model = rf_plus_base_oob
+ elif fi_est.base_model == "RFPlus_inbag":
+ loaded_model = rf_plus_base_inbag
+ elif fi_est.base_model == "RFPlus_default":
+ loaded_model = rf_plus_base
+ end = time.time()
+ metric_results['load_model_time'] = end - start
+ print(f"done with loading models: {end - start}")
+
+
+ start = time.time()
+ print(f"Compute feature importance")
+ # Compute feature importance
+ local_fi_score_train = fi_est.cls(X_train=X_train, y_train=y_train, fit=loaded_model, mode="absolute")
+ # if fi_est.name.startswith("Local_MDI+"):
+ # local_fi_score_train_subset = local_fi_score_train[indices_train]
+
+ m= "absolute"
+ #feature_importance_list[m][fi_est.name] = [local_fi_score_train_subset, local_fi_score_test, local_fi_score_test_subset]
+ end = time.time()
+ metric_results[f'fi_time_{m}'] = end - start
+ print(f"done with feature importance {m}: {end - start}")
+ # prepare ablations
+ print("start ablation")
+ ablation_models = {"RF_Classifier": RandomForestClassifier(n_estimators=100, min_samples_leaf=3, max_features='sqrt', random_state=args.rf_seed),
+ # "LogisticCV": LogisticRegressionCV(random_state=42, max_iter=2000),
+ # "SVM": SVC(random_state=42, probability=True),
+ # "XGBoost_Classifier": xgb.XGBClassifier(random_state=42),
+ #"RF_Plus_Classifier": rf_plus_base
+ }
+
+ train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ if fi_est.ascending:
+ sorted_feature = np.argsort(-train_fi_mean)
+ else:
+ sorted_feature = np.argsort(train_fi_mean)
+
+ mask_ratio = [0.01, 0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
+ for mask in mask_ratio:
+ print(f"Mask ratio: {mask}")
+ num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ print(f"Train shape: {X_train_masked.shape}")
+ num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ print(f"Test shape: {X_train_masked.shape}")
+ metric_results[f'num_features_selected_{mask}'] = num_features_selected
+ for a_model in ablation_models:
+ ablation_models[a_model].fit(X_train_masked, y_train)
+ y_pred = ablation_models[a_model].predict_proba(X_test_masked)[:, 1]
+ metric_results[f'{a_model}_LogLoss_after_ablation_top_{mask}'] = log_loss(y_test, y_pred)
+ metric_results[f'{a_model}_AUROC_after_ablation_top_{mask}'] = roc_auc_score(y_test, y_pred)
+
+ # start = time.time()
+ # for a_model in ablation_models:
+ # ablation_models[a_model].fit(X_train, y_train)
+ # end = time.time()
+ # metric_results['ablation_model_fit_time'] = end - start
+ # print(f"done with ablation model fit: {end - start}")
+ # all_fi = [local_fi_score_train_subset, local_fi_score_test_subset, local_fi_score_test]
+ # all_fi_rank = [None, None, None]
+ # for i in range(len(all_fi)):
+ # fi = all_fi[i]
+ # if isinstance(fi, np.ndarray):
+ # fi[fi == float("-inf")] = -sys.maxsize - 1
+ # fi[fi == float("inf")] = sys.maxsize - 1
+ # if fi_est.ascending:
+ # all_fi_rank[i] = np.argsort(-fi)
+ # else:
+ # all_fi_rank[i] = np.argsort(fi)
+
+ # local_fi_score_train[local_fi_score_train == float("-inf")] = -sys.maxsize - 1
+ # local_fi_score_train[local_fi_score_train == float("inf")] = sys.maxsize - 1
+ # if fi_est.ascending:
+ # local_fi_score_train_rank = np.argsort(-local_fi_score_train)
+ # else:
+ # local_fi_score_train_rank = np.argsort(local_fi_score_train)
+ # train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ # if fi_est.ascending:
+ # sorted_feature = np.argsort(-train_fi_mean)
+ # else:
+ # sorted_feature = np.argsort(train_fi_mean)
+ # train_mean = np.mean(X_train, axis=0)
+
+ # for a_model in ablation_models:
+ # print(f"start ablation removal: {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # y_pred_before = ablation_est.predict_proba(X_test)[:, 1]
+ # metric_results[f'{a_model}_LogLoss_after_ablation_0_{m}'] = log_loss(y_test, y_pred_before)
+ # metric_results[f'{a_model}_AUROC_after_ablation_0_{m}'] = roc_auc_score(y_test, y_pred_before)
+ # X_temp = copy.deepcopy(X_train)
+ # print(f"Train 0: {X_temp[0]}")
+ # for i in range(num_features_masked):
+ # print(f"Masking {i}")
+ # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score_train, local_fi_score_train_rank, i, m)
+ # print(f"Train 0: {X_temp[0]}")
+ # ablation_est.fit(ablation_X_data, y_train)
+ # y_pred = ablation_est.predict_proba(X_test)[:, 1]
+ # metric_results[f'{a_model}_LogLoss_after_ablation_{i+1}_{m}'] = log_loss(y_test, y_pred)
+ # metric_results[f'{a_model}_AUROC_after_ablation_{i+1}_{m}'] = roc_auc_score(y_test, y_pred)
+ # X_temp = ablation_X_data
+
+ # mask_ratio = [0.01, 0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]
+ # for mask in mask_ratio:
+ # print(f"Mask ratio: {mask}")
+ # num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ # print(f"Train shape: {X_train_masked.shape}")
+ # num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ # print(f"Test shape: {X_train_masked.shape}")
+ # metric_results[f'num_features_selected_{mask}'] = num_features_selected
+ # for a_model in ablation_models:
+ # ablation_models[a_model].fit(X_train_masked, y_train)
+ # y_pred = ablation_models[a_model].predict_proba(X_test_masked)[:, 1]
+ # metric_results[f'{a_model}_LogLoss_after_ablation_top_{mask}'] = log_loss(y_test, y_pred)
+ # metric_results[f'{a_model}_AUROC_after_ablation_top_{mask}'] = roc_auc_score(y_test, y_pred)
+
+
+ # all_fi = [local_fi_score_train_subset, local_fi_score_test_subset, local_fi_score_test]
+ # all_fi_rank = [None, None, None]
+ # for i in range(len(all_fi)):
+ # fi = all_fi[i]
+ # if isinstance(fi, np.ndarray):
+ # fi[fi == float("-inf")] = -sys.maxsize - 1
+ # fi[fi == float("inf")] = sys.maxsize - 1
+ # if fi_est.ascending:
+ # all_fi_rank[i] = np.argsort(-fi)
+ # else:
+ # all_fi_rank[i] = np.argsort(fi)
+
+ # ablation_datas = {"train_subset": (X_train_subset, y_train_subset, all_fi[0], all_fi_rank[0]),
+ # "test_subset": (X_test_subset, y_test_subset, all_fi[1], all_fi_rank[1]),
+ # "test": (X_test, y_test, all_fi[2], all_fi_rank[2])}
+ # train_mean = np.mean(X_train, axis=0)
+
+ # print("start ablation")
+ # # Start ablation 1: Feature removal
+ # for ablation_data in ablation_datas:
+ # start = time.time()
+ # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
+ # if not isinstance(local_fi_score, np.ndarray):
+ # for a_model in ablation_models:
+ # for i in range(num_features_masked+1):
+ # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i}_{m}'] = None
+ # else:
+ # for a_model in ablation_models:
+ # print(f"start ablation removal: {ablation_data} {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # y_pred_before = ablation_est.predict_proba(X_data)[:, 1]
+ # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_0_{m}'] = 0
+ # X_temp = copy.deepcopy(X_data)
+ # for i in range(num_features_masked):
+ # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
+ # y_pred = ablation_est.predict_proba(ablation_X_data)[:, 1]
+ # if i == 0:
+ # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i+1}_{m}'] = delta_mae(y_data, y_pred_before, y_pred)
+ # else:
+ # metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i+1}_{m}'] = delta_mae(y_data, y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_MAE_after_ablation_{i}_{m}']
+ # X_temp = ablation_X_data
+ # y_pred_before = y_pred
+ # end = time.time()
+ # print(f"done with ablation removal {m}: {ablation_data} {end - start}")
+ # metric_results[f'{ablation_data}_ablation_removal_{m}_time'] = end - start
+
+
+
+ # Start ablation 1: Feature removal
+ # for ablation_data in ablation_datas:
+ # start = time.time()
+ # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
+ # if not isinstance(local_fi_score, np.ndarray):
+ # for a_model in ablation_models:
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_before_ablation_{m}'] = None
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_before_ablation_{m}'] = None
+ # metric_results[f'{a_model}_{ablation_data}_F1_before_ablation_{m}'] = None
+ # for i in range(num_features_masked):
+ # for a_model in ablation_models:
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_{m}'] = None
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_{m}'] = None
+ # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_{m}'] = None
+ # else:
+ # for a_model in ablation_models:
+ # print(f"start ablation removal: {ablation_data} {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # y_pred = ablation_est.predict(X_data)
+ # metric_results[a_model + f'_{ablation_data}_AUROC_before_ablation_{m}'] = roc_auc_score(y_data, y_pred)
+ # metric_results[a_model + f'_{ablation_data}_AUPRC_before_ablation_{m}'] = average_precision_score(y_data, y_pred)
+ # metric_results[a_model + f'_{ablation_data}_F1_before_ablation_{m}'] = f1_score(y_data, y_pred > 0.5)
+ # ablation_results_auroc_list = [0] * num_features_masked
+ # ablation_results_auprc_list = [0] * num_features_masked
+ # ablation_results_f1_list = [0] * num_features_masked
+ # X_temp = X_data.copy()
+ # for i in range(num_features_masked):
+ # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
+ # ablation_results_auroc_list[i] = roc_auc_score(y_data, ablation_est.predict(ablation_X_data))
+ # ablation_results_auprc_list[i] = average_precision_score(y_data, ablation_est.predict(ablation_X_data))
+ # ablation_results_f1_list[i] = f1_score(y_data, ablation_est.predict(ablation_X_data) > 0.5)
+ # X_temp = ablation_X_data
+ # for i in range(num_features_masked):
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_{m}'] = ablation_results_auroc_list[i]
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_{m}'] = ablation_results_auprc_list[i]
+ # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_{m}'] = ablation_results_f1_list[i]
+ # end = time.time()
+ # print(f"done with ablation removal: {ablation_data} {end - start}")
+ # metric_results[f'{ablation_data}_ablation_removal_time'] = end - start
+
+ # # Start ablation 2: Feature addition
+ # for ablation_data in ablation_datas:
+ # start = time.time()
+ # X_data, y_data, local_fi_score_data = ablation_datas[ablation_data]
+ # if not isinstance(local_fi_score_data, np.ndarray):
+ # for a_model in ablation_models:
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_before_ablation_addition'] = None
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_before_ablation_addition'] = None
+ # metric_results[f'{a_model}_{ablation_data}_F1_before_ablation_addition'] = None
+ # for i in range(num_ablate_features):
+ # for a_model in ablation_models:
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_addition'] = None
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_addition'] = None
+ # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_addition'] = None
+ # else:
+ # for a_model in ablation_models:
+ # print(f"start ablation addtion: {ablation_data} {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # X_temp = np.array([train_mean_list] * X_data.shape[0]).copy()
+ # y_pred = ablation_est.predict(X_temp)
+ # metric_results[a_model + f'_{ablation_data}_AUROC_before_ablation_addition'] = roc_auc_score(y_data, y_pred)
+ # metric_results[a_model + f'_{ablation_data}_AUPRC_before_ablation_addition'] = average_precision_score(y_data, y_pred)
+ # metric_results[a_model + f'_{ablation_data}_F1_before_ablation_addition'] = f1_score(y_data, y_pred > 0.5)
+ # imp_vals = copy.deepcopy(local_fi_score_data)
+ # ablation_results_auroc_list = [0] * num_ablate_features
+ # ablation_results_auprc_list = [0] * num_ablate_features
+ # ablation_results_f1_list = [0] * num_ablate_features
+ # for i in range(num_ablate_features):
+ # ablation_X_data = ablation_addition(X_temp, X_data, imp_vals, i)
+ # ablation_results_auroc_list[i] = roc_auc_score(y_data, ablation_est.predict(ablation_X_data))
+ # ablation_results_auprc_list[i] = average_precision_score(y_data, ablation_est.predict(ablation_X_data))
+ # ablation_results_f1_list[i] = f1_score(y_data, ablation_est.predict(ablation_X_data) > 0.5)
+ # X_temp = ablation_X_data
+ # for i in range(num_ablate_features):
+ # metric_results[f'{a_model}_{ablation_data}_AUROC_after_ablation_{i+1}_addition'] = ablation_results_auroc_list[i]
+ # metric_results[f'{a_model}_{ablation_data}_AUPRC_after_ablation_{i+1}_addition'] = ablation_results_auprc_list[i]
+ # metric_results[f'{a_model}_{ablation_data}_F1_after_ablation_{i+1}_addition'] = ablation_results_f1_list[i]
+
+ # end = time.time()
+ # print(f"done with ablation addtion: {ablation_data} {end - start}")
+ # metric_results[f'{ablation_data}_ablation_addition_time'] = end - start
+
+
+ # initialize results with metadata and metric results
+ kwargs: dict = model.kwargs # dict
+ for k in kwargs:
+ results[k].append(kwargs[k])
+ for k in fi_kwargs:
+ if k in fi_est.kwargs:
+ results[k].append(str(fi_est.kwargs[k]))
+ else:
+ results[k].append(None)
+ for met_name, met_val in metric_results.items():
+ results[met_name].append(met_val)
+ return results, feature_importance_list
+
+
+def run_comparison(path: str,
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ args):
+ estimator_name = estimators[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_estimators) \
+ if fi_estimator.model_type in estimators[0].model_type]
+ model_comparison_files_all = [oj(path, f'{estimator_name}_{fi_estimator.name}_comparisons.pkl') \
+ for fi_estimator in fi_estimators_all]
+
+ feature_importance_all = oj(path, f'feature_importance.pkl')
+
+
+ if args.parallel_id is not None:
+ model_comparison_files_all = [f'_{args.parallel_id[0]}.'.join(model_comparison_file.split('.')) \
+ for model_comparison_file in model_comparison_files_all]
+
+ fi_estimators = []
+ model_comparison_files = []
+ for model_comparison_file, fi_estimator in zip(model_comparison_files_all, fi_estimators_all):
+ if os.path.isfile(model_comparison_file) and not args.ignore_cache:
+ print(
+ f'{estimator_name} with {fi_estimator.name} results already computed and cached. use --ignore_cache to recompute')
+ else:
+ fi_estimators.append(fi_estimator)
+ model_comparison_files.append(model_comparison_file)
+ if len(fi_estimators) == 0:
+ return
+ results, fi_lst = compare_estimators(estimators=estimators,
+ fi_estimators=fi_estimators,
+ X=X, y=y, support=support,
+ metrics=metrics,
+ args=args)
+
+ estimators_list = [e.name for e in estimators]
+ metrics_list = [m[0] for m in metrics]
+
+ df = pd.DataFrame.from_dict(results)
+ df['split_seed'] = args.split_seed
+ if args.nosave_cols is not None:
+ nosave_cols = np.unique([x.strip() for x in args.nosave_cols.split(",")])
+ else:
+ nosave_cols = []
+ for col in nosave_cols:
+ if col in df.columns:
+ df = df.drop(columns=[col])
+
+ pkl.dump(fi_lst, open(feature_importance_all, 'wb'))
+
+ for model_comparison_file, fi_estimator in zip(model_comparison_files, fi_estimators):
+ output_dict = {
+ # metadata
+ 'sim_name': args.config,
+ 'estimators': estimators_list,
+ 'fi_estimators': fi_estimator.name,
+ 'metrics': metrics_list,
+
+ # actual values
+ 'df': df.loc[df.fi == fi_estimator.name],
+ }
+ pkl.dump(output_dict, open(model_comparison_file, 'wb'))
+ return df
+
+
+def get_metrics():
+ return [('rocauc', auroc_score), ('prauc', auprc_score)]
+
+
+def reformat_results(results):
+ results = results.reset_index().drop(columns=['index'])
+ # fi_scores = pd.concat(results.pop('fi_scores').to_dict()). \
+ # reset_index(level=0).rename(columns={'level_0': 'index'})
+ # results_df = pd.merge(results, fi_scores, left_index=True, right_on="index")
+ # return results_df
+ return results
+
+
+
+def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests, metrics, args):
+ os.makedirs(oj(path, val_name, "rep" + str(i)), exist_ok=True)
+ np.random.seed(i)
+ max_iter = 100
+ iter = 0
+ while iter <= max_iter: # regenerate data if y is constant
+ X = X_dgp(**X_params_dict)
+ y, support, beta = y_dgp(X, **y_params_dict, return_support=True)
+ if not all(y == y[0]):
+ break
+ iter += 1
+ if iter > max_iter:
+ raise ValueError("Response y is constant.")
+ if args.omit_vars is not None:
+ omit_vars = np.unique([int(x.strip()) for x in args.omit_vars.split(",")])
+ support = np.delete(support, omit_vars)
+ X = np.delete(X, omit_vars, axis=1)
+ del beta # note: beta is not currently supported when using omit_vars
+
+ for est in ests:
+ results = run_comparison(path=oj(path, val_name, "rep" + str(i)),
+ X=X, y=y, support=support,
+ metrics=metrics,
+ estimators=est,
+ fi_estimators=fi_ests,
+ args=args)
+ return True
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+
+ default_dir = os.getenv("SCRATCH")
+ if default_dir is not None:
+ default_dir = oj(default_dir, "feature_importance", "results")
+ else:
+ default_dir = oj(os.path.dirname(os.path.realpath(__file__)), 'results')
+
+ parser.add_argument('--nreps', type=int, default=2)
+ parser.add_argument('--model', type=str, default=None) # , default='c4')
+ parser.add_argument('--fi_model', type=str, default=None) # , default='c4')
+ parser.add_argument('--config', type=str, default='test')
+ parser.add_argument('--omit_vars', type=str, default=None) # comma-separated string of variables to omit
+ parser.add_argument('--nosave_cols', type=str, default="prediction_model")
+
+ ### Newly added arguments
+ parser.add_argument('--folder_name', type=str, default=None)
+ parser.add_argument('--fit_model', type=bool, default=False)
+ parser.add_argument('--absolute_masking', type=bool, default=False)
+ parser.add_argument('--positive_masking', type=bool, default=False)
+ parser.add_argument('--negative_masking', type=bool, default=False)
+ parser.add_argument('--num_features_masked', type=int, default=None)
+
+ # for multiple reruns, should support varying split_seed
+ parser.add_argument('--ignore_cache', action='store_true', default=False)
+ parser.add_argument('--verbose', action='store_true', default=True)
+ parser.add_argument('--parallel', action='store_true', default=False)
+ parser.add_argument('--parallel_id', nargs='+', type=int, default=None)
+ parser.add_argument('--n_cores', type=int, default=None)
+ parser.add_argument('--split_seed', type=int, default=0)
+ parser.add_argument('--results_path', type=str, default=default_dir)
+
+ # arguments for rmd output of results
+ parser.add_argument('--create_rmd', action='store_true', default=False)
+ parser.add_argument('--show_vars', type=int, default=None)
+
+ args = parser.parse_args()
+
+ if args.parallel:
+ if args.n_cores is None:
+ print(os.getenv("SLURM_CPUS_ON_NODE"))
+ n_cores = int(os.getenv("SLURM_CPUS_ON_NODE"))
+ else:
+ n_cores = args.n_cores
+ client = Client(n_workers=n_cores)
+
+ ests, fi_ests, \
+ X_dgp, X_params_dict, y_dgp, y_params_dict, \
+ vary_param_name, vary_param_vals = fi_config.get_fi_configs(args.config)
+
+ metrics = get_metrics()
+
+ if args.model:
+ ests = list(filter(lambda x: args.model.lower() == x[0].name.lower(), ests))
+ if args.fi_model:
+ fi_ests = list(filter(lambda x: args.fi_model.lower() == x[0].name.lower(), fi_ests))
+
+ if len(ests) == 0:
+ raise ValueError('No valid estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if len(fi_ests) == 0:
+ raise ValueError('No valid FI estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if args.verbose:
+ print('running', args.config,
+ 'ests', ests,
+ 'fi_ests', fi_ests)
+ print('\tsaving to', args.results_path)
+
+ if args.omit_vars is not None:
+ #results_dir = oj(args.results_path, args.config + "_omitted_vars")
+ results_dir = oj(args.results_path, args.config + "_omitted_vars", args.folder_name)
+ else:
+ #results_dir = oj(args.results_path, args.config)
+ results_dir = oj(args.results_path, args.config, args.folder_name)
+
+ if isinstance(vary_param_name, list):
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.split_seed))
+ else:
+ path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
+ os.makedirs(path, exist_ok=True)
+
+ eval_out = defaultdict(list)
+
+ vary_type = None
+ if isinstance(vary_param_name, list): # multiple parameters are being varied
+ # get parameters that are being varied over and identify whether it's a DGP/method/fi_method argument
+ keys, values = zip(*vary_param_vals.items())
+ vary_param_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
+ vary_type = {}
+ for vary_param_dict in vary_param_dicts:
+ for param_name, param_val in vary_param_dict.items():
+ if param_name in X_params_dict.keys() and param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif param_name in X_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ X_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ elif param_name in y_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ y_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ else:
+ est_kwargs = list(
+ itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if param_name in est_kwargs:
+ vary_type[param_name] = "est"
+ elif param_name in fi_est_kwargs:
+ vary_type[param_name] = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp,
+ y_params_dict, y_dgp, ests, fi_ests, metrics, args) for i in
+ range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [
+ run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ y_dgp, ests, fi_ests, metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ else: # only on parameter is being varied over
+ # get parameter that is being varied over and identify whether it's a DGP/method/fi_method argument
+ for val_name, val in vary_param_vals.items():
+ if vary_param_name in X_params_dict.keys() and vary_param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif vary_param_name in X_params_dict.keys():
+ vary_type = "dgp"
+ X_params_dict[vary_param_name] = val
+ elif vary_param_name in y_params_dict.keys():
+ vary_type = "dgp"
+ y_params_dict[vary_param_name] = val
+ else:
+ est_kwargs = list(itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if vary_param_name in est_kwargs:
+ vary_type = "est"
+ elif vary_param_name in fi_est_kwargs:
+ vary_type = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests,
+ fi_ests, metrics, args) for i in range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests,
+ metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ print('completed all experiments successfully!')
+
+ # get model file names
+ model_comparison_files_all = []
+ for est in ests:
+ estimator_name = est[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_ests) \
+ if fi_estimator.model_type in est[0].model_type]
+ model_comparison_files = [f'{estimator_name}_{fi_estimator.name}_comparisons.pkl' for fi_estimator in
+ fi_estimators_all]
+ model_comparison_files_all += model_comparison_files
+
+ # aggregate results
+ results_list = []
+ if isinstance(vary_param_name, list):
+ for vary_param_dict in vary_param_dicts:
+ val_name = "_".join(vary_param_dict.values())
+
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+
+ for param_name, param_val in vary_param_dict.items():
+ val = vary_param_vals[param_name][param_val]
+ if vary_type[param_name] == "dgp":
+ if np.isscalar(val):
+ results.insert(0, param_name, val)
+ else:
+ results.insert(0, param_name, [val for i in range(results.shape[0])])
+ results.insert(1, param_name + "_name", param_val)
+ elif vary_type[param_name] == "est" or vary_type[param_name] == "fi_est":
+ results.insert(0, param_name + "_name", copy.deepcopy(results[param_name]))
+ results.insert(0, 'rep', i)
+ results_list.append(results)
+ else:
+ for val_name, val in vary_param_vals.items():
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+ if vary_type == "dgp":
+ if np.isscalar(val):
+ results.insert(0, vary_param_name, val)
+ else:
+ results.insert(0, vary_param_name, [val for i in range(results.shape[0])])
+ results.insert(1, vary_param_name + "_name", val_name)
+ results.insert(2, 'rep', i)
+ elif vary_type == "est" or vary_type == "fi_est":
+ results.insert(0, vary_param_name + "_name", copy.deepcopy(results[vary_param_name]))
+ results.insert(1, 'rep', i)
+ results_list.append(results)
+ results_merged = pd.concat(results_list, axis=0)
+ pkl.dump(results_merged, open(oj(path, 'results.pkl'), 'wb'))
+ results_df = reformat_results(results_merged)
+ results_df.to_csv(oj(path, 'results.csv'), index=False)
+
+ print('merged and saved all experiment results successfully!')
+
+ # create R markdown summary of results
+ if args.create_rmd:
+ if args.show_vars is None:
+ show_vars = 'NULL'
+ else:
+ show_vars = args.show_vars
+
+ if isinstance(vary_param_name, list):
+ vary_param_name = "; ".join(vary_param_name)
+
+ sim_rmd = os.path.basename(results_dir) + '_simulation_results.Rmd'
+ os.system(
+ 'cp {} \'{}\''.format(oj("rmd", "simulation_results.Rmd"), sim_rmd)
+ )
+ os.system(
+ 'Rscript -e "rmarkdown::render(\'{}\', params = list(results_dir = \'{}\', vary_param_name = \'{}\', seed = {}, keep_vars = {}), output_file = \'{}\', quiet = TRUE)"'.format(
+ sim_rmd,
+ results_dir, vary_param_name, str(args.split_seed), str(show_vars),
+ oj(path, "simulation_results.html"))
+ )
+ os.system('rm \'{}\''.format(sim_rmd))
+ print("created rmd of simulation results successfully!")
\ No newline at end of file
diff --git a/feature_importance/OLD_00_run_ablation_regression_retrain.py b/feature_importance/OLD_00_run_ablation_regression_retrain.py
new file mode 100644
index 0000000..dad591f
--- /dev/null
+++ b/feature_importance/OLD_00_run_ablation_regression_retrain.py
@@ -0,0 +1,882 @@
+import copy
+import os
+from os.path import join as oj
+import glob
+import argparse
+import pickle as pkl
+import time
+import warnings
+from scipy import stats
+import dask
+from dask.distributed import Client
+import numpy as np
+import pandas as pd
+from tqdm import tqdm
+import sys
+from collections import defaultdict
+from typing import Callable, List, Tuple
+import itertools
+from sklearn.metrics import roc_auc_score, f1_score, recall_score, precision_score, mean_squared_error, r2_score
+from sklearn import preprocessing
+from sklearn.ensemble import RandomForestRegressor
+from sklearn.linear_model import LinearRegression
+import xgboost as xgb
+from imodels.tree.rf_plus.rf_plus.rf_plus_models import RandomForestPlusRegressor, RandomForestPlusClassifier
+from sklearn.linear_model import Ridge
+sys.path.append(".")
+sys.path.append("..")
+sys.path.append("../..")
+import fi_config
+from util import ModelConfig, FIModelConfig, tp, fp, neg, pos, specificity_score, auroc_score, auprc_score, compute_nsg_feat_corr_w_sig_subspace, apply_splitting_strategy
+import dill
+from sklearn.kernel_ridge import KernelRidge
+
+warnings.filterwarnings("ignore", message="Bins whose width")
+
+#RUN THE FILE
+# python 01_run_ablation_regression.py --nreps 5 --config mdi_local.real_data_regression --split_seed 331 --ignore_cache --create_rmd --result_name diabetes_regression
+
+
+# def generate_random_shuffle(data, seed):
+# """
+# Randomly shuffle each column of the data.
+# """
+# np.random.seed(seed)
+# return np.array([np.random.permutation(data[:, i]) for i in range(data.shape[1])]).T
+
+
+# def ablation(data, feature_importance, mode, num_features, seed):
+# """
+# Replace the top num_features max feature importance data with random shuffle for each sample
+# """
+# assert mode in ["max", "min"]
+# fi = feature_importance.to_numpy()
+# shuffle = generate_random_shuffle(data, seed)
+# if mode == "max":
+# indices = np.argsort(-fi)
+# else:
+# indices = np.argsort(fi)
+# data_copy = data.copy()
+# for i in range(data.shape[0]):
+# for j in range(num_features):
+# data_copy[i, indices[i,j]] = shuffle[i, indices[i,j]]
+# return data_copy
+
+# def ablation_removal(train_mean, data, feature_importance_rank, feature_index):
+# """
+# Replace the top num_features max feature importance data with mean value for each sample
+# """
+# data_copy = data.copy()
+# for i in range(data.shape[0]):
+# data_copy[i, feature_importance_rank[i,feature_index]] = train_mean[feature_importance_rank[i,feature_index]]
+# return data_copy
+
+# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
+# """
+# Initialize the data with mean values and add the top num_features max feature importance data for each sample
+# """
+# data_copy = data_ablation.copy()
+# for i in range(data.shape[0]):
+# data_copy[i, feature_importance_rank[i,feature_index]] = data[i, feature_importance_rank[i,feature_index]]
+# return data_copy
+
+
+# def ablation_removal(train_mean, data, feature_importance, feature_importance_rank, feature_index, mode):
+# if mode == "absolute":
+# return ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index)
+# # else:
+# # return ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index)
+
+# def ablation_removal_absolute(train_mean, data, feature_importance_rank, feature_index):
+# """
+# Replace the top num_features max feature importance data with mean value for each sample
+# """
+# data_copy = data.copy()
+# indices = feature_importance_rank[:, feature_index]
+# data_copy[np.arange(data.shape[0]), indices] = train_mean[indices]
+# return data_copy
+
+# def ablation_removal_pos_neg(train_mean, data, feature_importance_rank, feature_importance, feature_index):
+# data_copy = data.copy()
+# indices = feature_importance_rank[:, feature_index]
+# sum = 0
+# for i in range(data.shape[0]):
+# if feature_importance[i, indices[i]] != 0 and feature_importance[i, indices[i]] < sys.maxsize - 1:
+# sum += 1
+# data_copy[i, indices[i]] = train_mean[indices[i]]
+# print("Remove sum: ", sum)
+# return data_copy
+
+def select_top_features(array, sorted_indices, percentage):
+ array = copy.deepcopy(array)
+ num_features = array.shape[1]
+ num_selected = int(np.ceil(num_features * percentage))
+ selected_indices = sorted_indices[:num_selected]
+ selected_array = array[:, selected_indices]
+ return num_selected, selected_array
+
+# def delta_mse(y_true, y_pred_1, y_pred_2):
+# mse_before = (y_true - y_pred_1) ** 2
+# mse_after = (y_true - y_pred_2) ** 2
+# absolute_delta_mse = np.mean(np.abs(mse_before - mse_after))
+# return absolute_delta_mse
+
+# def delta_y_pred(y_pred_1, y_pred_2):
+# return np.mean(np.abs(y_pred_1 - y_pred_2))
+
+# def ablation_addition(data_ablation, data, feature_importance_rank, feature_index):
+# """
+# Initialize the data with mean values and add the top num_features max feature importance data for each sample
+# """
+# data_copy = data_ablation.copy()
+# indices = feature_importance_rank[:, feature_index]
+# data_copy[np.arange(data.shape[0]), indices] = data[np.arange(data.shape[0]), indices]
+# return data_copy
+
+
+def compare_estimators(estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ args, ) -> Tuple[dict, dict]:
+ """Calculates results given estimators, feature importance estimators, datasets, and metrics.
+ Called in run_comparison
+ """
+ if type(estimators) != list:
+ raise Exception("First argument needs to be a list of Models")
+ if type(metrics) != list:
+ raise Exception("Argument metrics needs to be a list containing ('name', callable) pairs")
+
+ # initialize results
+ results = defaultdict(lambda: [])
+ feature_importance_list = {"positive": {}, "negative": {}, "absolute": {}}
+
+ # loop over model estimators
+ for model in estimators:
+ # est = model.cls(**model.kwargs)
+
+ # get kwargs for all fi_ests
+ fi_kwargs = {}
+ for fi_est in fi_estimators:
+ fi_kwargs.update(fi_est.kwargs)
+
+ # get groups of estimators for each splitting strategy
+ fi_ests_dict = defaultdict(list)
+ for fi_est in fi_estimators:
+ fi_ests_dict[fi_est.splitting_strategy].append(fi_est)
+
+ # loop over splitting strategies
+ for splitting_strategy, fi_ests in fi_ests_dict.items():
+ # implement provided splitting strategy
+ if splitting_strategy is not None:
+ X_train, X_tune, X_test, y_train, y_tune, y_test = apply_splitting_strategy(X, y, splitting_strategy, args.split_seed)
+ else:
+ X_train = X
+ X_test = X
+ y_train = y
+ y_test = y
+
+ if args.fit_model:
+ print("Fitting Models")
+ # fit RF model
+ start_rf = time.time()
+ est = RandomForestRegressor(n_estimators=100, min_samples_leaf=5, max_features=0.33, random_state=args.rf_seed)
+ est.fit(X_train, y_train)
+ end_rf = time.time()
+
+ # fit default RF_plus model
+ start_rf_plus = time.time()
+ rf_plus_base = RandomForestPlusRegressor(rf_model=est)
+ rf_plus_base.fit(X_train, y_train)
+ end_rf_plus = time.time()
+
+ # fit oob RF_plus model
+ start_rf_plus_oob = time.time()
+ rf_plus_base_oob = RandomForestPlusRegressor(rf_model=est, fit_on="oob")
+ rf_plus_base_oob.fit(X_train, y_train)
+ end_rf_plus_oob = time.time()
+
+ #fit inbag RF_plus model
+ start_rf_plus_inbag = time.time()
+ rf_plus_base_inbag = RandomForestPlusRegressor(rf_model=est, include_raw=False, fit_on="inbag", prediction_model=LinearRegression())
+ rf_plus_base_inbag.fit(X_train, y_train)
+ end_rf_plus_inbag = time.time()
+
+ # get test results
+ test_all_mse_rf = mean_squared_error(y_test, est.predict(X_test))
+ test_all_r2_rf = r2_score(y_test, est.predict(X_test))
+ test_all_mse_rf_plus = mean_squared_error(y_test, rf_plus_base.predict(X_test))
+ test_all_r2_rf_plus = r2_score(y_test, rf_plus_base.predict(X_test))
+ test_all_mse_rf_plus_oob = mean_squared_error(y_test, rf_plus_base_oob.predict(X_test))
+ test_all_r2_rf_plus_oob = r2_score(y_test, rf_plus_base_oob.predict(X_test))
+ test_all_mse_rf_plus_inbag = mean_squared_error(y_test, rf_plus_base_inbag.predict(X_test))
+ test_all_r2_rf_plus_inbag = r2_score(y_test, rf_plus_base_inbag.predict(X_test))
+
+ fitted_results = {
+ "Model": ["RF", "RF_plus", "RF_plus_oob", "RF_plus_inbag"],
+ "MSE": [test_all_mse_rf, test_all_mse_rf_plus, test_all_mse_rf_plus_oob, test_all_mse_rf_plus_inbag],
+ "R2": [test_all_r2_rf, test_all_r2_rf_plus, test_all_r2_rf_plus_oob, test_all_r2_rf_plus_inbag],
+ "Time": [end_rf - start_rf, end_rf_plus - start_rf_plus, end_rf_plus_oob - start_rf_plus_oob, end_rf_plus_inbag - start_rf_plus_inbag]
+ }
+
+ os.makedirs(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}", exist_ok=True)
+ results_df = pd.DataFrame(fitted_results)
+ results_df.to_csv(f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_fitted_summary_rf_seed_{args.rf_seed}_split_seed_{args.split_seed}.csv", index=False)
+
+
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RF_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(est, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_default_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base_oob, file)
+ # pickle_file = f"/scratch/users/zhongyuan_liang/saved_models/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill"
+ # with open(pickle_file, 'wb') as file:
+ # dill.dump(rf_plus_base_inbag, file)
+
+ # if args.absolute_masking or args.positive_masking or args.negative_masking:
+ # np.random.seed(42)
+ # if X_train.shape[0] > 100:
+ # indices_train = np.random.choice(X_train.shape[0], 100, replace=False)
+ # X_train_subset = X_train[indices_train]
+ # y_train_subset = y_train[indices_train]
+ # else:
+ # indices_train = np.arange(X_train.shape[0])
+ # X_train_subset = X_train
+ # y_train_subset = y_train
+
+ # if X_test.shape[0] > 100:
+ # indices_test = np.random.choice(X_test.shape[0], 100, replace=False)
+ # X_test_subset = X_test[indices_test]
+ # y_test_subset = y_test[indices_test]
+ # else:
+ # indices_test = np.arange(X_test.shape[0])
+ # X_test_subset = X_test
+ # y_test_subset = y_test
+
+ if args.num_features_masked is None:
+ num_features_masked = X_train.shape[1]
+ else:
+ num_features_masked = args.num_features_masked
+
+ # loop over fi estimators
+ for fi_est in tqdm(fi_ests):
+ metric_results = {
+ 'model': model.name,
+ 'fi': fi_est.name,
+ 'train_size': X_train.shape[0],
+ # 'train_subset_size': X_train_subset.shape[0],
+ 'test_size': X_test.shape[0],
+ # 'test_subset_size': X_test_subset.shape[0],
+ 'num_features': X_train.shape[1],
+ 'data_split_seed': args.split_seed,
+ 'rf_seed': args.rf_seed,
+ 'num_features_masked': num_features_masked
+ }
+ # for i in range(X_train_subset.shape[0]):
+ # metric_results[f'sample_train_{i}'] = indices_train[i]
+ # for i in range(X_test_subset.shape[0]):
+ # metric_results[f'sample_test_{i}'] = indices_test[i]
+
+ print("Load Models")
+ start = time.time()
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_default_{args.split_seed}.dill", 'rb') as file:
+ # rf_plus_base = dill.load(file)
+ # if fi_est.base_model == "None":
+ # loaded_model = None
+ # elif fi_est.base_model == "RF":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RF_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_oob":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_oob_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_inbag":
+ # with open(f"/scratch/users/zhongyuan_liang/saved_models/auroc/{args.folder_name}/RFPlus_inbag_{args.split_seed}.dill", 'rb') as file:
+ # loaded_model = dill.load(file)
+ # elif fi_est.base_model == "RFPlus_default":
+ # loaded_model = rf_plus_base
+ #rf_plus_base = rf_plus_base
+ if fi_est.base_model == "None":
+ loaded_model = None
+ elif fi_est.base_model == "RF":
+ loaded_model = est
+ elif fi_est.base_model == "RFPlus_oob":
+ loaded_model = rf_plus_base_oob
+ elif fi_est.base_model == "RFPlus_inbag":
+ loaded_model = rf_plus_base_inbag
+ elif fi_est.base_model == "RFPlus_default":
+ loaded_model = rf_plus_base
+ end = time.time()
+ metric_results['load_model_time'] = end - start
+ print(f"done with loading models: {end - start}")
+
+
+ start = time.time()
+ print(f"Compute feature importance")
+ # Compute feature importance
+ local_fi_score_train = fi_est.cls(X_train=X_train, y_train=y_train, fit=loaded_model, mode="absolute")
+ # if fi_est.name.startswith("Local_MDI+"):
+ # local_fi_score_train_subset = local_fi_score_train[indices_train]
+
+ m= "absolute"
+ #feature_importance_list[m][fi_est.name] = [local_fi_score_train_subset, local_fi_score_test, local_fi_score_test_subset]
+ end = time.time()
+ metric_results[f'fi_time_{m}'] = end - start
+ print(f"done with feature importance {m}: {end - start}")
+ # prepare ablations
+ print("prepare ablation")
+ ablation_models = {"RF_Regressor": RandomForestRegressor(n_estimators=100,min_samples_leaf=5,max_features=0.33,random_state=args.rf_seed),
+ # "Linear": LinearRegression(),
+ # "XGB_Regressor": xgb.XGBRegressor(random_state=42),
+ # 'Kernel_Ridge': KernelRidge(),
+ #"RF_Plus_Regressor": rf_plus_base
+ }
+
+ train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ if fi_est.ascending:
+ sorted_feature = np.argsort(-train_fi_mean)
+ else:
+ sorted_feature = np.argsort(train_fi_mean)
+
+
+ mask_ratio = [0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7,0.9]
+ for mask in mask_ratio:
+ print(f"Mask ratio: {mask}")
+ num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ print(f"Train shape: {X_train_masked.shape}")
+ num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ print(f"Test shape: {X_train_masked.shape}")
+ metric_results[f'num_features_selected_{mask}'] = num_features_selected
+ for a_model in ablation_models:
+ ablation_models[a_model].fit(X_train_masked, y_train)
+ y_pred = ablation_models[a_model].predict(X_test_masked)
+ metric_results[f'{a_model}_MSE_after_ablation_top_{mask}'] = mean_squared_error(y_test, y_pred)
+ metric_results[f'{a_model}_R2_after_ablation_top_{mask}'] = r2_score(y_test, y_pred)
+
+
+ # start = time.time()
+ # for a_model in ablation_models:
+ # ablation_models[a_model].fit(X_train, y_train)
+ # end = time.time()
+ # metric_results['ablation_model_fit_time'] = end - start
+ # print(f"done with ablation model fit: {end - start}")
+
+ # all_fi = [local_fi_score_train_subset, local_fi_score_test_subset, local_fi_score_test]
+ # all_fi_rank = [None, None, None]
+ # for i in range(len(all_fi)):
+ # fi = all_fi[i]
+ # if isinstance(fi, np.ndarray):
+ # fi[fi == float("-inf")] = -sys.maxsize - 1
+ # fi[fi == float("inf")] = sys.maxsize - 1
+ # if fi_est.ascending:
+ # all_fi_rank[i] = np.argsort(-fi)
+ # else:
+ # all_fi_rank[i] = np.argsort(fi)
+ # local_fi_score_train[local_fi_score_train == float("-inf")] = -sys.maxsize - 1
+ # local_fi_score_train[local_fi_score_train == float("inf")] = sys.maxsize - 1
+ # if fi_est.ascending:
+ # local_fi_score_train_rank = np.argsort(-local_fi_score_train)
+ # local_fi_score_test_rank = np.argsort(-local_fi_score_test)
+ # else:
+ # local_fi_score_train_rank = np.argsort(local_fi_score_train)
+ # local_fi_score_test_rank = np.argsort(local_fi_score_test)
+ # train_fi_mean = np.mean(local_fi_score_train, axis=0)
+ # if fi_est.ascending:
+ # sorted_feature = np.argsort(-train_fi_mean)
+ # else:
+ # sorted_feature = np.argsort(train_fi_mean)
+ # train_mean = np.mean(X_train, axis=0)
+
+ # for a_model in ablation_models:
+ # print(f"start ablation removal: {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # y_pred_before = ablation_est.predict(X_test)
+ # metric_results[f'{a_model}_MSE_after_ablation_0_{m}'] = mean_squared_error(y_test, y_pred_before)
+ # metric_results[f'{a_model}_R2_after_ablation_0_{m}'] = r2_score(y_test, y_pred_before)
+ # X_temp = copy.deepcopy(X_train)
+ # X_temp_test = copy.deepcopy(X_test)
+ # print(f"Train 0: {X_temp[0]}")
+ # for i in range(num_features_masked):
+ # print(f"Masking {i}")
+ # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score_train, local_fi_score_train_rank, i, m)
+ # print(f"Train 0: {X_temp[0]}")
+ # ablation_est.fit(ablation_X_data, y_train)
+ # ablation_X_data_test = ablation_removal(train_mean, X_temp_test, local_fi_score_test, local_fi_score_test_rank, i, m)
+ # y_pred = ablation_est.predict(ablation_X_data_test)
+ # metric_results[f'{a_model}_MSE_after_ablation_{i+1}_{m}'] = mean_squared_error(y_test, y_pred)
+ # metric_results[f'{a_model}_R2_after_ablation_{i+1}_{m}'] = r2_score(y_test, y_pred)
+ # X_temp = ablation_X_data
+ # X_temp_test = ablation_X_data_test
+
+ # mask_ratio = [0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7,0.9]
+ # for mask in mask_ratio:
+ # print(f"Mask ratio: {mask}")
+ # num_features_selected, X_train_masked = select_top_features(X_train, sorted_feature, mask)
+ # print(f"Train shape: {X_train_masked.shape}")
+ # num_features_selected, X_test_masked = select_top_features(X_test, sorted_feature, mask)
+ # print(f"Test shape: {X_train_masked.shape}")
+ # metric_results[f'num_features_selected_{mask}'] = num_features_selected
+ # for a_model in ablation_models:
+ # ablation_models[a_model].fit(X_train_masked, y_train)
+ # y_pred = ablation_models[a_model].predict(X_test_masked)
+ # metric_results[f'{a_model}_MSE_after_ablation_top_{mask}'] = mean_squared_error(y_test, y_pred)
+ # metric_results[f'{a_model}_R2_after_ablation_top_{mask}'] = r2_score(y_test, y_pred)
+
+ # ablation_datas = {"train_subset": (X_train_subset, y_train_subset, all_fi[0], all_fi_rank[0]),
+ # "test_subset": (X_test_subset, y_test_subset, all_fi[1], all_fi_rank[1]),
+ # "test": (X_test, y_test, all_fi[2], all_fi_rank[2])}
+ # train_mean = np.mean(X_train, axis=0)
+
+ # print("start ablation")
+ # # Start ablation 1: Feature removal
+ # for ablation_data in ablation_datas:
+ # start = time.time()
+ # X_data, y_data, local_fi_score, local_fi_score_rank = ablation_datas[ablation_data]
+ # if not isinstance(local_fi_score, np.ndarray):
+ # for a_model in ablation_models:
+ # for i in range(num_features_masked+1):
+ # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i}_{m}'] = None
+ # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i}_{m}'] = None
+ # else:
+ # for a_model in ablation_models:
+ # print(f"start ablation removal: {ablation_data} {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # y_pred_before = ablation_est.predict(X_data)
+ # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_0_{m}'] = 0
+ # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_0_{m}'] = 0
+ # X_temp = copy.deepcopy(X_data)
+ # for i in range(num_features_masked):
+ # ablation_X_data = ablation_removal(train_mean, X_temp, local_fi_score, local_fi_score_rank, i, m)
+ # y_pred = ablation_est.predict(ablation_X_data)
+ # if i == 0:
+ # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i+1}_{m}'] = delta_mse(y_data, y_pred_before, y_pred)
+ # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i+1}_{m}'] = delta_y_pred(y_pred_before, y_pred)
+ # else:
+ # metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i+1}_{m}'] = delta_mse(y_data, y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_MSE_after_ablation_{i}_{m}']
+ # metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i+1}_{m}'] = delta_y_pred(y_pred_before, y_pred) + metric_results[f'{a_model}_{ablation_data}_delta_y_hat_after_ablation_{i}_{m}' ]
+ # X_temp = ablation_X_data
+ # y_pred_before = y_pred
+ # end = time.time()
+ # print(f"done with ablation removal {m}: {ablation_data} {end - start}")
+ # metric_results[f'{ablation_data}_ablation_removal_{m}_time'] = end - start
+ # # Start ablation 2: Feature addition
+ # for ablation_data in ablation_datas:
+ # start = time.time()
+ # X_data, y_data, local_fi_score_data = ablation_datas[ablation_data]
+ # if not isinstance(local_fi_score_data, np.ndarray):
+ # for a_model in ablation_models:
+ # metric_results[a_model + f'_{ablation_data}_MSE_before_ablation_addition'] = None
+ # metric_results[a_model + f'_{ablation_data}_R_2_before_ablation_addition'] = None
+ # for i in range(num_ablate_features):
+ # for a_model in ablation_models:
+ # metric_results[f'{a_model}_{ablation_data}_MSE_after_ablation_{i+1}_addition'] = None
+ # metric_results[f'{a_model}_{ablation_data}_R_2_after_ablation_{i+1}_addition'] = None
+ # else:
+ # for a_model in ablation_models:
+ # print(f"start ablation addtion: {ablation_data} {a_model}")
+ # ablation_est = ablation_models[a_model]
+ # X_temp = np.array([train_mean_list] * X_data.shape[0]).copy()
+ # y_pred = ablation_est.predict(X_temp)
+ # metric_results[a_model + f'_{ablation_data}_MSE_before_ablation_addition'] = mean_squared_error(y_data, y_pred)
+ # metric_results[a_model + f'_{ablation_data}_R_2_before_ablation_addition'] = r2_score(y_data, y_pred)
+ # imp_vals = copy.deepcopy(local_fi_score_data)
+ # ablation_results_list = [0] * num_ablate_features
+ # ablation_results_list_r2 = [0] * num_ablate_features
+ # for i in range(num_ablate_features):
+ # ablation_X_data = ablation_addition(X_temp, X_data, imp_vals, i)
+ # ablation_results_list[i] = mean_squared_error(y_data, ablation_est.predict(ablation_X_data))
+ # ablation_results_list_r2[i] = r2_score(y_data, ablation_est.predict(ablation_X_data))
+ # X_temp = ablation_X_data
+ # for i in range(num_ablate_features):
+ # metric_results[f'{a_model}_{ablation_data}_MSE_after_ablation_{i+1}_addition'] = ablation_results_list[i]
+ # metric_results[f'{a_model}_{ablation_data}_R_2_after_ablation_{i+1}_addition'] = ablation_results_list_r2[i]
+ # end = time.time()
+ # print(f"done with ablation addtion: {ablation_data} {end - start}")
+ # metric_results[f'{ablation_data}_ablation_addition_time'] = end - start
+
+ # initialize results with metadata and metric results
+ kwargs: dict = model.kwargs # dict
+ for k in kwargs:
+ results[k].append(kwargs[k])
+ for k in fi_kwargs:
+ if k in fi_est.kwargs:
+ results[k].append(str(fi_est.kwargs[k]))
+ else:
+ results[k].append(None)
+ for met_name, met_val in metric_results.items():
+ results[met_name].append(met_val)
+ # for key, value in results.items():
+ # print(f"{key}: {len(value)}")
+ return results, feature_importance_list
+
+
+def run_comparison(path: str,
+ X, y, support: List,
+ metrics: List[Tuple[str, Callable]],
+ estimators: List[ModelConfig],
+ fi_estimators: List[FIModelConfig],
+ args):
+ estimator_name = estimators[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_estimators) \
+ if fi_estimator.model_type in estimators[0].model_type]
+ model_comparison_files_all = [oj(path, f'{estimator_name}_{fi_estimator.name}_comparisons.pkl') \
+ for fi_estimator in fi_estimators_all]
+
+ feature_importance_all = oj(path, f'feature_importance.pkl')
+
+
+ if args.parallel_id is not None:
+ model_comparison_files_all = [f'_{args.parallel_id[0]}.'.join(model_comparison_file.split('.')) \
+ for model_comparison_file in model_comparison_files_all]
+
+ fi_estimators = []
+ model_comparison_files = []
+ for model_comparison_file, fi_estimator in zip(model_comparison_files_all, fi_estimators_all):
+ if os.path.isfile(model_comparison_file) and not args.ignore_cache:
+ print(
+ f'{estimator_name} with {fi_estimator.name} results already computed and cached. use --ignore_cache to recompute')
+ else:
+ fi_estimators.append(fi_estimator)
+ model_comparison_files.append(model_comparison_file)
+
+ if len(fi_estimators) == 0:
+ return
+
+ results, fi_lst = compare_estimators(estimators=estimators,
+ fi_estimators=fi_estimators,
+ X=X, y=y, support=support,
+ metrics=metrics,
+ args=args)
+
+ estimators_list = [e.name for e in estimators]
+ metrics_list = [m[0] for m in metrics]
+
+ df = pd.DataFrame.from_dict(results)
+ df['split_seed'] = args.split_seed
+ if args.nosave_cols is not None:
+ nosave_cols = np.unique([x.strip() for x in args.nosave_cols.split(",")])
+ else:
+ nosave_cols = []
+ for col in nosave_cols:
+ if col in df.columns:
+ df = df.drop(columns=[col])
+
+ pkl.dump(fi_lst, open(feature_importance_all, 'wb'))
+
+ for model_comparison_file, fi_estimator in zip(model_comparison_files, fi_estimators):
+ output_dict = {
+ # metadata
+ 'sim_name': args.config,
+ 'estimators': estimators_list,
+ 'fi_estimators': fi_estimator.name,
+ 'metrics': metrics_list,
+
+ # actual values
+ 'df': df.loc[df.fi == fi_estimator.name],
+ }
+ pkl.dump(output_dict, open(model_comparison_file, 'wb'))
+ return df
+
+
+def get_metrics():
+ return [('rocauc', auroc_score), ('prauc', auprc_score)]
+
+
+def reformat_results(results):
+ results = results.reset_index().drop(columns=['index'])
+ # fi_scores = pd.concat(results.pop('fi_scores').to_dict()). \
+ # reset_index(level=0).rename(columns={'level_0': 'index'})
+ # results_df = pd.merge(results, fi_scores, left_index=True, right_on="index")
+ # return results_df
+ return results
+
+def run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests, metrics, args):
+ os.makedirs(oj(path, val_name, "rep" + str(i)), exist_ok=True)
+ np.random.seed(i)
+ max_iter = 100
+ iter = 0
+ while iter <= max_iter: # regenerate data if y is constant
+ X = X_dgp(**X_params_dict)
+ y, support, beta = y_dgp(X, **y_params_dict, return_support=True)
+ if not all(y == y[0]):
+ break
+ iter += 1
+ if iter > max_iter:
+ raise ValueError("Response y is constant.")
+ if args.omit_vars is not None:
+ omit_vars = np.unique([int(x.strip()) for x in args.omit_vars.split(",")])
+ support = np.delete(support, omit_vars)
+ X = np.delete(X, omit_vars, axis=1)
+ del beta # note: beta is not currently supported when using omit_vars
+
+ for est in ests:
+ results = run_comparison(path=oj(path, val_name, "rep" + str(i)),
+ X=X, y=y, support=support,
+ metrics=metrics,
+ estimators=est,
+ fi_estimators=fi_ests,
+ args=args)
+ return True
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+
+ default_dir = os.getenv("SCRATCH")
+ if default_dir is not None:
+ default_dir = oj(default_dir, "feature_importance", "results")
+ else:
+ default_dir = oj(os.path.dirname(os.path.realpath(__file__)), 'results')
+
+ parser.add_argument('--nreps', type=int, default=2)
+ parser.add_argument('--model', type=str, default=None) # , default='c4')
+ parser.add_argument('--fi_model', type=str, default=None) # , default='c4')
+ parser.add_argument('--config', type=str, default='test')
+ parser.add_argument('--omit_vars', type=str, default=None) # comma-separated string of variables to omit
+ parser.add_argument('--nosave_cols', type=str, default="prediction_model")
+
+ ### Newly added arguments
+ parser.add_argument('--folder_name', type=str, default=None)
+ parser.add_argument('--fit_model', type=bool, default=False)
+ parser.add_argument('--absolute_masking', type=bool, default=False)
+ parser.add_argument('--positive_masking', type=bool, default=False)
+ parser.add_argument('--negative_masking', type=bool, default=False)
+ parser.add_argument('--num_features_masked', type=int, default=None)
+ parser.add_argument('--rf_seed', type=int, default=0)
+
+ # for multiple reruns, should support varying split_seed
+ parser.add_argument('--ignore_cache', action='store_true', default=False)
+ parser.add_argument('--verbose', action='store_true', default=True)
+ parser.add_argument('--parallel', action='store_true', default=False)
+ parser.add_argument('--parallel_id', nargs='+', type=int, default=None)
+ parser.add_argument('--n_cores', type=int, default=None)
+ parser.add_argument('--split_seed', type=int, default=0)
+ parser.add_argument('--results_path', type=str, default=default_dir)
+
+ # arguments for rmd output of results
+ parser.add_argument('--create_rmd', action='store_true', default=False)
+ parser.add_argument('--show_vars', type=int, default=None)
+
+ args = parser.parse_args()
+
+ if args.parallel:
+ if args.n_cores is None:
+ print(os.getenv("SLURM_CPUS_ON_NODE"))
+ n_cores = int(os.getenv("SLURM_CPUS_ON_NODE"))
+ else:
+ n_cores = args.n_cores
+ client = Client(n_workers=n_cores)
+
+ ests, fi_ests, \
+ X_dgp, X_params_dict, y_dgp, y_params_dict, \
+ vary_param_name, vary_param_vals = fi_config.get_fi_configs(args.config)
+
+ metrics = get_metrics()
+
+ if args.model:
+ ests = list(filter(lambda x: args.model.lower() == x[0].name.lower(), ests))
+ if args.fi_model:
+ fi_ests = list(filter(lambda x: args.fi_model.lower() == x[0].name.lower(), fi_ests))
+
+ if len(ests) == 0:
+ raise ValueError('No valid estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if len(fi_ests) == 0:
+ raise ValueError('No valid FI estimators', 'sim', args.config, 'models', args.model, 'fi', args.fi_model)
+ if args.verbose:
+ print('running', args.config,
+ 'ests', ests,
+ 'fi_ests', fi_ests)
+ print('\tsaving to', args.results_path)
+
+ if args.omit_vars is not None:
+ #results_dir = oj(args.results_path, args.config + "_omitted_vars")
+ results_dir = oj(args.results_path, args.config + "_omitted_vars", args.folder_name)
+ else:
+ #results_dir = oj(args.results_path, args.config)
+ results_dir = oj(args.results_path, args.config, args.folder_name)
+
+ # if isinstance(vary_param_name, list):
+ # path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.split_seed))
+ # else:
+ # path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.split_seed))
+ if isinstance(vary_param_name, list):
+ path = oj(results_dir, "varying_" + "_".join(vary_param_name), "seed" + str(args.rf_seed))
+ else:
+ path = oj(results_dir, "varying_" + vary_param_name, "seed" + str(args.rf_seed))
+ os.makedirs(path, exist_ok=True)
+
+ eval_out = defaultdict(list)
+
+ vary_type = None
+ if isinstance(vary_param_name, list): # multiple parameters are being varied
+ # get parameters that are being varied over and identify whether it's a DGP/method/fi_method argument
+ keys, values = zip(*vary_param_vals.items())
+ vary_param_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
+ vary_type = {}
+ for vary_param_dict in vary_param_dicts:
+ for param_name, param_val in vary_param_dict.items():
+ if param_name in X_params_dict.keys() and param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif param_name in X_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ X_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ elif param_name in y_params_dict.keys():
+ vary_type[param_name] = "dgp"
+ y_params_dict[param_name] = vary_param_vals[param_name][param_val]
+ else:
+ est_kwargs = list(
+ itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if param_name in est_kwargs:
+ vary_type[param_name] = "est"
+ elif param_name in fi_est_kwargs:
+ vary_type[param_name] = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp,
+ y_params_dict, y_dgp, ests, fi_ests, metrics, args) for i in
+ range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [
+ run_simulation(i, path, "_".join(vary_param_dict.values()), X_params_dict, X_dgp, y_params_dict,
+ y_dgp, ests, fi_ests, metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ else: # only on parameter is being varied over
+ # get parameter that is being varied over and identify whether it's a DGP/method/fi_method argument
+ for val_name, val in vary_param_vals.items():
+ if vary_param_name in X_params_dict.keys() and vary_param_name in y_params_dict.keys():
+ raise ValueError('Cannot vary over parameter in both X and y DGPs.')
+ elif vary_param_name in X_params_dict.keys():
+ vary_type = "dgp"
+ X_params_dict[vary_param_name] = val
+ elif vary_param_name in y_params_dict.keys():
+ vary_type = "dgp"
+ y_params_dict[vary_param_name] = val
+ else:
+ est_kwargs = list(itertools.chain(*[list(est.kwargs.keys()) for est in list(itertools.chain(*ests))]))
+ fi_est_kwargs = list(
+ itertools.chain(*[list(fi_est.kwargs.keys()) for fi_est in list(itertools.chain(*fi_ests))]))
+ if vary_param_name in est_kwargs:
+ vary_type = "est"
+ elif vary_param_name in fi_est_kwargs:
+ vary_type = "fi_est"
+ else:
+ raise ValueError('Invalid vary_param_name.')
+
+ if args.parallel:
+ futures = [
+ dask.delayed(run_simulation)(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests,
+ fi_ests, metrics, args) for i in range(args.nreps)]
+ results = dask.compute(*futures)
+ else:
+ results = [run_simulation(i, path, val_name, X_params_dict, X_dgp, y_params_dict, y_dgp, ests, fi_ests,
+ metrics, args) for i in range(args.nreps)]
+ assert all(results)
+
+ print('completed all experiments successfully!')
+
+ # get model file names
+ model_comparison_files_all = []
+ for est in ests:
+ estimator_name = est[0].name.split(' - ')[0]
+ fi_estimators_all = [fi_estimator for fi_estimator in itertools.chain(*fi_ests) \
+ if fi_estimator.model_type in est[0].model_type]
+ model_comparison_files = [f'{estimator_name}_{fi_estimator.name}_comparisons.pkl' for fi_estimator in
+ fi_estimators_all]
+ model_comparison_files_all += model_comparison_files
+
+ # aggregate results
+ results_list = []
+ if isinstance(vary_param_name, list):
+ for vary_param_dict in vary_param_dicts:
+ val_name = "_".join(vary_param_dict.values())
+
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+
+ for param_name, param_val in vary_param_dict.items():
+ val = vary_param_vals[param_name][param_val]
+ if vary_type[param_name] == "dgp":
+ if np.isscalar(val):
+ results.insert(0, param_name, val)
+ else:
+ results.insert(0, param_name, [val for i in range(results.shape[0])])
+ results.insert(1, param_name + "_name", param_val)
+ elif vary_type[param_name] == "est" or vary_type[param_name] == "fi_est":
+ results.insert(0, param_name + "_name", copy.deepcopy(results[param_name]))
+ results.insert(0, 'rep', i)
+ results_list.append(results)
+ else:
+ for val_name, val in vary_param_vals.items():
+ for i in range(args.nreps):
+ all_files = glob.glob(oj(path, val_name, 'rep' + str(i), '*'))
+ model_files = sorted([f for f in all_files if os.path.basename(f) in model_comparison_files_all])
+
+ if len(model_files) == 0:
+ print('No files found at ', oj(path, val_name, 'rep' + str(i)))
+ continue
+
+ results = pd.concat(
+ [pkl.load(open(f, 'rb'))['df'] for f in model_files],
+ axis=0
+ )
+ if vary_type == "dgp":
+ if np.isscalar(val):
+ results.insert(0, vary_param_name, val)
+ else:
+ results.insert(0, vary_param_name, [val for i in range(results.shape[0])])
+ results.insert(1, vary_param_name + "_name", val_name)
+ results.insert(2, 'rep', i)
+ elif vary_type == "est" or vary_type == "fi_est":
+ results.insert(0, vary_param_name + "_name", copy.deepcopy(results[vary_param_name]))
+ results.insert(1, 'rep', i)
+ results_list.append(results)
+ results_merged = pd.concat(results_list, axis=0)
+ pkl.dump(results_merged, open(oj(path, 'results.pkl'), 'wb'))
+ results_df = reformat_results(results_merged)
+ results_df.to_csv(oj(path, 'results.csv'), index=False)
+
+ print('merged and saved all experiment results successfully!')
+
+ # create R markdown summary of results
+ if args.create_rmd:
+ if args.show_vars is None:
+ show_vars = 'NULL'
+ else:
+ show_vars = args.show_vars
+
+ if isinstance(vary_param_name, list):
+ vary_param_name = "; ".join(vary_param_name)
+
+ sim_rmd = os.path.basename(results_dir) + '_simulation_results.Rmd'
+ os.system(
+ 'cp {} \'{}\''.format(oj("rmd", "simulation_results.Rmd"), sim_rmd)
+ )
+ os.system(
+ 'Rscript -e "rmarkdown::render(\'{}\', params = list(results_dir = \'{}\', vary_param_name = \'{}\', seed = {}, keep_vars = {}), output_file = \'{}\', quiet = TRUE)"'.format(
+ sim_rmd,
+ results_dir, vary_param_name, str(args.split_seed), str(show_vars),
+ oj(path, "simulation_results.html"))
+ )
+ os.system('rm \'{}\''.format(sim_rmd))
+ print("created rmd of simulation results successfully!")
\ No newline at end of file
diff --git a/feature_importance/ablation_results_visulization_ranking.ipynb b/feature_importance/ablation_results_visulization_ranking.ipynb
new file mode 100644
index 0000000..5f9231e
--- /dev/null
+++ b/feature_importance/ablation_results_visulization_ranking.ipynb
@@ -0,0 +1,2293 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "import os\n",
+ "import pickle\n",
+ "import seaborn as sns\n",
+ "pd.set_option('display.max_columns', None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# load pickled data\n",
+ "with open('CCLE_rank.pkl', 'rb') as f:\n",
+ " ccle_rank = pickle.load(f)\n",
+ "with open('parkinsons_rank.pkl', 'rb') as f:\n",
+ " parkinsons_rank = pickle.load(f)\n",
+ "with open('performance_rank.pkl', 'rb') as f:\n",
+ " performance_rank = pickle.load(f)\n",
+ "with open('temperature_rank.pkl', 'rb') as f:\n",
+ " temperature_rank = pickle.load(f)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# dictionaries = [ccle_rank, parkinsons_rank, performance_rank, temperature_rank]\n",
+ "\n",
+ "# average_dict = {key: sum(d[key] for d in dictionaries) / len(dictionaries) for key in ccle_rank.keys()}\n",
+ "\n",
+ "# sorted_keys = sorted(average_dict, key=average_dict.get)\n",
+ "\n",
+ "# # Display sorted keys and their corresponding values\n",
+ "# sorted_average_dict = {key: average_dict[key] for key in sorted_keys}\n",
+ "\n",
+ "# for k,v in sorted_average_dict.items():\n",
+ "# print(k, v)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "task = \"regression\" #\"classification\" #\"regression\"\n",
+ "ablation_directory =\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_regression_temperature_retrain/temperature_retrain/varying_sample_row_n\"\n",
+ "#####Regression\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_regression_CCLE_PD_0325901_retrain/CCLE_PD_0325901_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_regression_parkinsons_retrain/parkinsons_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_regression_performance_retrain/performance_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_regression_temperature_retrain/temperature_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#####Classification\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_classification_juvenile_retrain/juvenile_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_classification_csi_pecarn_retrain/csi_pecarn_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_classification_credit_g_retrain/credit_g_retrain/varying_sample_row_n\"\n",
+ "\n",
+ "#\"/accounts/projects/binyu/zhongyuan_liang/local_MDI+/imodels-experiments/feature_importance/results/mdi_local.real_data_classification_Ionosphere_retrain/Ionosphere_retrain/varying_sample_row_n\"\n",
+ "combined_df = pd.DataFrame()\n",
+ "split_seeds = [1,2,3]\n",
+ "rf_seeds = [1,2,3,4,5]\n",
+ "for split_seed in split_seeds:\n",
+ " for rf_seed in rf_seeds:\n",
+ " df = pd.read_csv(os.path.join(ablation_directory, f\"split_seed_{split_seed}rf_seed_{rf_seed}/results.csv\"))\n",
+ " combined_df = pd.concat([combined_df, df], ignore_index=True)\n",
+ "\n",
+ "\n",
+ "# rf_plus_directory = f'/scratch/users/zhongyuan_liang/saved_models/{task_name}'\n",
+ "# combined_df_rf_plus = pd.DataFrame()\n",
+ "# for file in os.listdir(rf_plus_directory):\n",
+ "# if file.endswith(\".csv\"):\n",
+ "# df = pd.read_csv(os.path.join(rf_plus_directory, file))\n",
+ "# combined_df_rf_plus = pd.concat([combined_df_rf_plus, df], ignore_index=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " sample_row_n | \n",
+ " sample_row_n_name | \n",
+ " rep | \n",
+ " n_estimators | \n",
+ " min_samples_leaf | \n",
+ " max_features | \n",
+ " random_state | \n",
+ " model | \n",
+ " fi | \n",
+ " train_size | \n",
+ " test_size | \n",
+ " num_features | \n",
+ " data_split_seed | \n",
+ " rf_seed | \n",
+ " num_features_masked | \n",
+ " fi_time_absolute | \n",
+ " num_features_selected_0.01 | \n",
+ " RF_Regressor_MSE_top_0.01 | \n",
+ " RF_Regressor_R2_top_0.01 | \n",
+ " Linear_Regressor_MSE_top_0.01 | \n",
+ " Linear_Regressor_R2_top_0.01 | \n",
+ " num_features_selected_0.05 | \n",
+ " RF_Regressor_MSE_top_0.05 | \n",
+ " RF_Regressor_R2_top_0.05 | \n",
+ " Linear_Regressor_MSE_top_0.05 | \n",
+ " Linear_Regressor_R2_top_0.05 | \n",
+ " num_features_selected_0.1 | \n",
+ " RF_Regressor_MSE_top_0.1 | \n",
+ " RF_Regressor_R2_top_0.1 | \n",
+ " Linear_Regressor_MSE_top_0.1 | \n",
+ " Linear_Regressor_R2_top_0.1 | \n",
+ " num_features_selected_0.15 | \n",
+ " RF_Regressor_MSE_top_0.15 | \n",
+ " RF_Regressor_R2_top_0.15 | \n",
+ " Linear_Regressor_MSE_top_0.15 | \n",
+ " Linear_Regressor_R2_top_0.15 | \n",
+ " num_features_selected_0.25 | \n",
+ " RF_Regressor_MSE_top_0.25 | \n",
+ " RF_Regressor_R2_top_0.25 | \n",
+ " Linear_Regressor_MSE_top_0.25 | \n",
+ " Linear_Regressor_R2_top_0.25 | \n",
+ " num_features_selected_0.4 | \n",
+ " RF_Regressor_MSE_top_0.4 | \n",
+ " RF_Regressor_R2_top_0.4 | \n",
+ " Linear_Regressor_MSE_top_0.4 | \n",
+ " Linear_Regressor_R2_top_0.4 | \n",
+ " num_features_selected_0.5 | \n",
+ " RF_Regressor_MSE_top_0.5 | \n",
+ " RF_Regressor_R2_top_0.5 | \n",
+ " Linear_Regressor_MSE_top_0.5 | \n",
+ " Linear_Regressor_R2_top_0.5 | \n",
+ " num_features_selected_0.7 | \n",
+ " RF_Regressor_MSE_top_0.7 | \n",
+ " RF_Regressor_R2_top_0.7 | \n",
+ " Linear_Regressor_MSE_top_0.7 | \n",
+ " Linear_Regressor_R2_top_0.7 | \n",
+ " num_features_selected_0.9 | \n",
+ " RF_Regressor_MSE_top_0.9 | \n",
+ " RF_Regressor_R2_top_0.9 | \n",
+ " Linear_Regressor_MSE_top_0.9 | \n",
+ " Linear_Regressor_R2_top_0.9 | \n",
+ " split_seed | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " NaN | \n",
+ " keep_all_rows | \n",
+ " 0 | \n",
+ " 100 | \n",
+ " 5 | \n",
+ " 0.33 | \n",
+ " 42 | \n",
+ " RF | \n",
+ " LIME_RF | \n",
+ " 683 | \n",
+ " 337 | \n",
+ " 46 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 46 | \n",
+ " 104.601947 | \n",
+ " 1 | \n",
+ " 0.065978 | \n",
+ " 0.585859 | \n",
+ " 0.080756 | \n",
+ " 0.493104 | \n",
+ " 3 | \n",
+ " 0.057773 | \n",
+ " 0.637365 | \n",
+ " 0.071752 | \n",
+ " 0.549618 | \n",
+ " 5 | \n",
+ " 0.076612 | \n",
+ " 0.519111 | \n",
+ " 0.073049 | \n",
+ " 0.541476 | \n",
+ " 7 | \n",
+ " 0.059147 | \n",
+ " 0.628740 | \n",
+ " 0.073312 | \n",
+ " 0.539826 | \n",
+ " 12 | \n",
+ " 0.057959 | \n",
+ " 0.636197 | \n",
+ " 0.073617 | \n",
+ " 0.537912 | \n",
+ " 19 | \n",
+ " 0.057859 | \n",
+ " 0.636823 | \n",
+ " 0.071591 | \n",
+ " 0.550629 | \n",
+ " 23 | \n",
+ " 0.055291 | \n",
+ " 0.652944 | \n",
+ " 0.071319 | \n",
+ " 0.552335 | \n",
+ " 33 | \n",
+ " 0.054722 | \n",
+ " 0.656512 | \n",
+ " 0.068056 | \n",
+ " 0.572816 | \n",
+ " 42 | \n",
+ " 0.055254 | \n",
+ " 0.653174 | \n",
+ " 0.067633 | \n",
+ " 0.575476 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " NaN | \n",
+ " keep_all_rows | \n",
+ " 0 | \n",
+ " 100 | \n",
+ " 5 | \n",
+ " 0.33 | \n",
+ " 42 | \n",
+ " RF | \n",
+ " Local_MDI+_fit_on_all_RFPlus | \n",
+ " 683 | \n",
+ " 337 | \n",
+ " 46 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 46 | \n",
+ " 6.051740 | \n",
+ " 1 | \n",
+ " 0.058014 | \n",
+ " 0.635849 | \n",
+ " 0.072183 | \n",
+ " 0.546910 | \n",
+ " 3 | \n",
+ " 0.057286 | \n",
+ " 0.640420 | \n",
+ " 0.071752 | \n",
+ " 0.549618 | \n",
+ " 5 | \n",
+ " 0.055986 | \n",
+ " 0.648579 | \n",
+ " 0.070452 | \n",
+ " 0.557777 | \n",
+ " 7 | \n",
+ " 0.057412 | \n",
+ " 0.639631 | \n",
+ " 0.069463 | \n",
+ " 0.563983 | \n",
+ " 12 | \n",
+ " 0.053869 | \n",
+ " 0.661870 | \n",
+ " 0.067566 | \n",
+ " 0.575892 | \n",
+ " 19 | \n",
+ " 0.054150 | \n",
+ " 0.660104 | \n",
+ " 0.066602 | \n",
+ " 0.581942 | \n",
+ " 23 | \n",
+ " 0.055168 | \n",
+ " 0.653714 | \n",
+ " 0.066852 | \n",
+ " 0.580377 | \n",
+ " 33 | \n",
+ " 0.056201 | \n",
+ " 0.647229 | \n",
+ " 0.067106 | \n",
+ " 0.578780 | \n",
+ " 42 | \n",
+ " 0.055829 | \n",
+ " 0.649568 | \n",
+ " 0.067379 | \n",
+ " 0.577068 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " NaN | \n",
+ " keep_all_rows | \n",
+ " 0 | \n",
+ " 100 | \n",
+ " 5 | \n",
+ " 0.33 | \n",
+ " 42 | \n",
+ " RF | \n",
+ " Local_MDI+_fit_on_all_average_RFPlus | \n",
+ " 683 | \n",
+ " 337 | \n",
+ " 46 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 46 | \n",
+ " 6.443466 | \n",
+ " 1 | \n",
+ " 0.058014 | \n",
+ " 0.635849 | \n",
+ " 0.072183 | \n",
+ " 0.546910 | \n",
+ " 3 | \n",
+ " 0.057286 | \n",
+ " 0.640420 | \n",
+ " 0.071752 | \n",
+ " 0.549618 | \n",
+ " 5 | \n",
+ " 0.055486 | \n",
+ " 0.651717 | \n",
+ " 0.070881 | \n",
+ " 0.555083 | \n",
+ " 7 | \n",
+ " 0.057270 | \n",
+ " 0.640520 | \n",
+ " 0.069463 | \n",
+ " 0.563983 | \n",
+ " 12 | \n",
+ " 0.054145 | \n",
+ " 0.660137 | \n",
+ " 0.067566 | \n",
+ " 0.575892 | \n",
+ " 19 | \n",
+ " 0.054853 | \n",
+ " 0.655689 | \n",
+ " 0.067253 | \n",
+ " 0.577856 | \n",
+ " 23 | \n",
+ " 0.055981 | \n",
+ " 0.648609 | \n",
+ " 0.067493 | \n",
+ " 0.576351 | \n",
+ " 33 | \n",
+ " 0.055092 | \n",
+ " 0.654192 | \n",
+ " 0.067219 | \n",
+ " 0.578070 | \n",
+ " 42 | \n",
+ " 0.055283 | \n",
+ " 0.652991 | \n",
+ " 0.067379 | \n",
+ " 0.577068 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " NaN | \n",
+ " keep_all_rows | \n",
+ " 0 | \n",
+ " 100 | \n",
+ " 5 | \n",
+ " 0.33 | \n",
+ " 42 | \n",
+ " RF | \n",
+ " Local_MDI+_fit_on_all_error_metric_RFPlus | \n",
+ " 683 | \n",
+ " 337 | \n",
+ " 46 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 46 | \n",
+ " 6.669140 | \n",
+ " 1 | \n",
+ " 0.065016 | \n",
+ " 0.591902 | \n",
+ " 0.081005 | \n",
+ " 0.491539 | \n",
+ " 3 | \n",
+ " 0.058308 | \n",
+ " 0.634004 | \n",
+ " 0.071752 | \n",
+ " 0.549618 | \n",
+ " 5 | \n",
+ " 0.056910 | \n",
+ " 0.642778 | \n",
+ " 0.071403 | \n",
+ " 0.551811 | \n",
+ " 7 | \n",
+ " 0.057027 | \n",
+ " 0.642049 | \n",
+ " 0.070910 | \n",
+ " 0.554904 | \n",
+ " 12 | \n",
+ " 0.055956 | \n",
+ " 0.648766 | \n",
+ " 0.067925 | \n",
+ " 0.573638 | \n",
+ " 19 | \n",
+ " 0.056132 | \n",
+ " 0.647663 | \n",
+ " 0.067211 | \n",
+ " 0.578121 | \n",
+ " 23 | \n",
+ " 0.055026 | \n",
+ " 0.654609 | \n",
+ " 0.067716 | \n",
+ " 0.574955 | \n",
+ " 33 | \n",
+ " 0.054757 | \n",
+ " 0.656293 | \n",
+ " 0.066362 | \n",
+ " 0.583450 | \n",
+ " 42 | \n",
+ " 0.055137 | \n",
+ " 0.653908 | \n",
+ " 0.067558 | \n",
+ " 0.575946 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " NaN | \n",
+ " keep_all_rows | \n",
+ " 0 | \n",
+ " 100 | \n",
+ " 5 | \n",
+ " 0.33 | \n",
+ " 42 | \n",
+ " RF | \n",
+ " Local_MDI+_fit_on_all_error_metric_average_RFPlus | \n",
+ " 683 | \n",
+ " 337 | \n",
+ " 46 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 46 | \n",
+ " 7.259469 | \n",
+ " 1 | \n",
+ " 0.065016 | \n",
+ " 0.591902 | \n",
+ " 0.081005 | \n",
+ " 0.491539 | \n",
+ " 3 | \n",
+ " 0.058308 | \n",
+ " 0.634004 | \n",
+ " 0.071752 | \n",
+ " 0.549618 | \n",
+ " 5 | \n",
+ " 0.056910 | \n",
+ " 0.642778 | \n",
+ " 0.071403 | \n",
+ " 0.551811 | \n",
+ " 7 | \n",
+ " 0.057027 | \n",
+ " 0.642049 | \n",
+ " 0.070910 | \n",
+ " 0.554904 | \n",
+ " 12 | \n",
+ " 0.055956 | \n",
+ " 0.648766 | \n",
+ " 0.067925 | \n",
+ " 0.573638 | \n",
+ " 19 | \n",
+ " 0.056132 | \n",
+ " 0.647663 | \n",
+ " 0.067211 | \n",
+ " 0.578121 | \n",
+ " 23 | \n",
+ " 0.055026 | \n",
+ " 0.654609 | \n",
+ " 0.067716 | \n",
+ " 0.574955 | \n",
+ " 33 | \n",
+ " 0.054757 | \n",
+ " 0.656293 | \n",
+ " 0.066362 | \n",
+ " 0.583450 | \n",
+ " 42 | \n",
+ " 0.055137 | \n",
+ " 0.653908 | \n",
+ " 0.067558 | \n",
+ " 0.575946 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " sample_row_n sample_row_n_name rep n_estimators min_samples_leaf \\\n",
+ "0 NaN keep_all_rows 0 100 5 \n",
+ "1 NaN keep_all_rows 0 100 5 \n",
+ "2 NaN keep_all_rows 0 100 5 \n",
+ "3 NaN keep_all_rows 0 100 5 \n",
+ "4 NaN keep_all_rows 0 100 5 \n",
+ "\n",
+ " max_features random_state model \\\n",
+ "0 0.33 42 RF \n",
+ "1 0.33 42 RF \n",
+ "2 0.33 42 RF \n",
+ "3 0.33 42 RF \n",
+ "4 0.33 42 RF \n",
+ "\n",
+ " fi train_size test_size \\\n",
+ "0 LIME_RF 683 337 \n",
+ "1 Local_MDI+_fit_on_all_RFPlus 683 337 \n",
+ "2 Local_MDI+_fit_on_all_average_RFPlus 683 337 \n",
+ "3 Local_MDI+_fit_on_all_error_metric_RFPlus 683 337 \n",
+ "4 Local_MDI+_fit_on_all_error_metric_average_RFPlus 683 337 \n",
+ "\n",
+ " num_features data_split_seed rf_seed num_features_masked \\\n",
+ "0 46 1 1 46 \n",
+ "1 46 1 1 46 \n",
+ "2 46 1 1 46 \n",
+ "3 46 1 1 46 \n",
+ "4 46 1 1 46 \n",
+ "\n",
+ " fi_time_absolute num_features_selected_0.01 RF_Regressor_MSE_top_0.01 \\\n",
+ "0 104.601947 1 0.065978 \n",
+ "1 6.051740 1 0.058014 \n",
+ "2 6.443466 1 0.058014 \n",
+ "3 6.669140 1 0.065016 \n",
+ "4 7.259469 1 0.065016 \n",
+ "\n",
+ " RF_Regressor_R2_top_0.01 Linear_Regressor_MSE_top_0.01 \\\n",
+ "0 0.585859 0.080756 \n",
+ "1 0.635849 0.072183 \n",
+ "2 0.635849 0.072183 \n",
+ "3 0.591902 0.081005 \n",
+ "4 0.591902 0.081005 \n",
+ "\n",
+ " Linear_Regressor_R2_top_0.01 num_features_selected_0.05 \\\n",
+ "0 0.493104 3 \n",
+ "1 0.546910 3 \n",
+ "2 0.546910 3 \n",
+ "3 0.491539 3 \n",
+ "4 0.491539 3 \n",
+ "\n",
+ " RF_Regressor_MSE_top_0.05 RF_Regressor_R2_top_0.05 \\\n",
+ "0 0.057773 0.637365 \n",
+ "1 0.057286 0.640420 \n",
+ "2 0.057286 0.640420 \n",
+ "3 0.058308 0.634004 \n",
+ "4 0.058308 0.634004 \n",
+ "\n",
+ " Linear_Regressor_MSE_top_0.05 Linear_Regressor_R2_top_0.05 \\\n",
+ "0 0.071752 0.549618 \n",
+ "1 0.071752 0.549618 \n",
+ "2 0.071752 0.549618 \n",
+ "3 0.071752 0.549618 \n",
+ "4 0.071752 0.549618 \n",
+ "\n",
+ " num_features_selected_0.1 RF_Regressor_MSE_top_0.1 \\\n",
+ "0 5 0.076612 \n",
+ "1 5 0.055986 \n",
+ "2 5 0.055486 \n",
+ "3 5 0.056910 \n",
+ "4 5 0.056910 \n",
+ "\n",
+ " RF_Regressor_R2_top_0.1 Linear_Regressor_MSE_top_0.1 \\\n",
+ "0 0.519111 0.073049 \n",
+ "1 0.648579 0.070452 \n",
+ "2 0.651717 0.070881 \n",
+ "3 0.642778 0.071403 \n",
+ "4 0.642778 0.071403 \n",
+ "\n",
+ " Linear_Regressor_R2_top_0.1 num_features_selected_0.15 \\\n",
+ "0 0.541476 7 \n",
+ "1 0.557777 7 \n",
+ "2 0.555083 7 \n",
+ "3 0.551811 7 \n",
+ "4 0.551811 7 \n",
+ "\n",
+ " RF_Regressor_MSE_top_0.15 RF_Regressor_R2_top_0.15 \\\n",
+ "0 0.059147 0.628740 \n",
+ "1 0.057412 0.639631 \n",
+ "2 0.057270 0.640520 \n",
+ "3 0.057027 0.642049 \n",
+ "4 0.057027 0.642049 \n",
+ "\n",
+ " Linear_Regressor_MSE_top_0.15 Linear_Regressor_R2_top_0.15 \\\n",
+ "0 0.073312 0.539826 \n",
+ "1 0.069463 0.563983 \n",
+ "2 0.069463 0.563983 \n",
+ "3 0.070910 0.554904 \n",
+ "4 0.070910 0.554904 \n",
+ "\n",
+ " num_features_selected_0.25 RF_Regressor_MSE_top_0.25 \\\n",
+ "0 12 0.057959 \n",
+ "1 12 0.053869 \n",
+ "2 12 0.054145 \n",
+ "3 12 0.055956 \n",
+ "4 12 0.055956 \n",
+ "\n",
+ " RF_Regressor_R2_top_0.25 Linear_Regressor_MSE_top_0.25 \\\n",
+ "0 0.636197 0.073617 \n",
+ "1 0.661870 0.067566 \n",
+ "2 0.660137 0.067566 \n",
+ "3 0.648766 0.067925 \n",
+ "4 0.648766 0.067925 \n",
+ "\n",
+ " Linear_Regressor_R2_top_0.25 num_features_selected_0.4 \\\n",
+ "0 0.537912 19 \n",
+ "1 0.575892 19 \n",
+ "2 0.575892 19 \n",
+ "3 0.573638 19 \n",
+ "4 0.573638 19 \n",
+ "\n",
+ " RF_Regressor_MSE_top_0.4 RF_Regressor_R2_top_0.4 \\\n",
+ "0 0.057859 0.636823 \n",
+ "1 0.054150 0.660104 \n",
+ "2 0.054853 0.655689 \n",
+ "3 0.056132 0.647663 \n",
+ "4 0.056132 0.647663 \n",
+ "\n",
+ " Linear_Regressor_MSE_top_0.4 Linear_Regressor_R2_top_0.4 \\\n",
+ "0 0.071591 0.550629 \n",
+ "1 0.066602 0.581942 \n",
+ "2 0.067253 0.577856 \n",
+ "3 0.067211 0.578121 \n",
+ "4 0.067211 0.578121 \n",
+ "\n",
+ " num_features_selected_0.5 RF_Regressor_MSE_top_0.5 \\\n",
+ "0 23 0.055291 \n",
+ "1 23 0.055168 \n",
+ "2 23 0.055981 \n",
+ "3 23 0.055026 \n",
+ "4 23 0.055026 \n",
+ "\n",
+ " RF_Regressor_R2_top_0.5 Linear_Regressor_MSE_top_0.5 \\\n",
+ "0 0.652944 0.071319 \n",
+ "1 0.653714 0.066852 \n",
+ "2 0.648609 0.067493 \n",
+ "3 0.654609 0.067716 \n",
+ "4 0.654609 0.067716 \n",
+ "\n",
+ " Linear_Regressor_R2_top_0.5 num_features_selected_0.7 \\\n",
+ "0 0.552335 33 \n",
+ "1 0.580377 33 \n",
+ "2 0.576351 33 \n",
+ "3 0.574955 33 \n",
+ "4 0.574955 33 \n",
+ "\n",
+ " RF_Regressor_MSE_top_0.7 RF_Regressor_R2_top_0.7 \\\n",
+ "0 0.054722 0.656512 \n",
+ "1 0.056201 0.647229 \n",
+ "2 0.055092 0.654192 \n",
+ "3 0.054757 0.656293 \n",
+ "4 0.054757 0.656293 \n",
+ "\n",
+ " Linear_Regressor_MSE_top_0.7 Linear_Regressor_R2_top_0.7 \\\n",
+ "0 0.068056 0.572816 \n",
+ "1 0.067106 0.578780 \n",
+ "2 0.067219 0.578070 \n",
+ "3 0.066362 0.583450 \n",
+ "4 0.066362 0.583450 \n",
+ "\n",
+ " num_features_selected_0.9 RF_Regressor_MSE_top_0.9 \\\n",
+ "0 42 0.055254 \n",
+ "1 42 0.055829 \n",
+ "2 42 0.055283 \n",
+ "3 42 0.055137 \n",
+ "4 42 0.055137 \n",
+ "\n",
+ " RF_Regressor_R2_top_0.9 Linear_Regressor_MSE_top_0.9 \\\n",
+ "0 0.653174 0.067633 \n",
+ "1 0.649568 0.067379 \n",
+ "2 0.652991 0.067379 \n",
+ "3 0.653908 0.067558 \n",
+ "4 0.653908 0.067558 \n",
+ "\n",
+ " Linear_Regressor_R2_top_0.9 split_seed \n",
+ "0 0.575476 1 \n",
+ "1 0.577068 1 \n",
+ "2 0.577068 1 \n",
+ "3 0.575946 1 \n",
+ "4 0.575946 1 "
+ ]
+ },
+ "execution_count": 55,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "combined_df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#combined_df = combined_df[(combined_df['heritability'] == 0.8) & (combined_df['n_train'] == 750)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# df = pd.DataFrame(combined_df_rf_plus)\n",
+ "# averages = df.groupby('Model').mean().reset_index()\n",
+ "# pd.DataFrame(averages)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([46])"
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "combined_df[\"num_features\"].unique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Summarise the Ablation Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The training size is 683 and the test size is 337\n"
+ ]
+ }
+ ],
+ "source": [
+ "train_size = combined_df[\"train_size\"].unique()[0]\n",
+ "test_size = combined_df[\"test_size\"].unique()[0]\n",
+ "print(f\"The training size is {train_size} and the test size is {test_size}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array(['LIME_RF', 'Local_MDI+_fit_on_all_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_error_metric_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_error_metric_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_error_metric_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_l2_norm_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_l2_norm_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_l2_norm_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_ranking_ridge_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_error_metric_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_error_metric_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_error_metric_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_l2_norm_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_l2_norm_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_l2_norm_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_inbag_ranking_ridge_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_error_metric_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_error_metric_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_error_metric_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_l2_norm_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_l2_norm_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_l2_norm_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_ranking_ridge_RFPlus', 'Random',\n",
+ " 'TreeSHAP_RF'], dtype=object)"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "combined_df[\"fi\"].unique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Plot the Ablation Data Performance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "methods = ['LIME_RF', \n",
+ "# 'Local_MDI+_fit_on_all_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_l2_norm_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_l2_norm_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_l2_norm_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_all_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_all_ranking_ridge_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_inbag_ranking_ridge_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_average_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_l2_norm_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_l2_norm_average_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_l2_norm_ranking_RFPlus',\n",
+ " 'Local_MDI+_fit_on_oob_ranking_RFPlus',\n",
+ "# 'Local_MDI+_fit_on_oob_ranking_ridge_RFPlus',\n",
+ " # 'Random',\n",
+ " 'TreeSHAP_RF']\n",
+ "\n",
+ "num_features = combined_df['num_features_masked'].drop_duplicates().values[0]\n",
+ "metrics = {\"regression\": [\"MSE\", \"R2\"], \"classification\": [\"AUROC\", \"LogLoss\"]} #MSE\n",
+ "ablation_models = {\"regression\": [\"RF_Regressor\"],#, \"Linear_Regressor\"],\n",
+ " \"classification\": [\"RF_Classifier\", \"Logistic_Regression\"]}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "color_map = {\n",
+ " 'LIME_RF': '#1f77b4', # Bold blue\n",
+ " 'Local_MDI+_fit_on_all_l2_norm_ranking_RFPlus': '#ff7f0e', # Vibrant orange\n",
+ " 'Local_MDI+_fit_on_oob_l2_norm_ranking_RFPlus': '#2ca02c', # Bright green\n",
+ " 'Local_MDI+_fit_on_oob_ranking_RFPlus': '#d62728', # Bright red\n",
+ " 'Local_MDI+_fit_on_all_ranking_RFPlus': '#e377c2', # Pink\n",
+ " 'TreeSHAP_RF': '#9467bd', # Bold purple\n",
+ "}\n",
+ "\n",
+ "# color_map = {\n",
+ "# 'LIME_RF': '#1f77b4', # bold blue\n",
+ "# 'Local_MDI+_fit_on_all_RFPlus': '#ff7f0e', # vibrant orange\n",
+ "# 'Local_MDI+_fit_on_all_average_RFPlus': '#2ca02c', # bright green\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_RFPlus': '#d62728', # bright red\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_average_RFPlus': '#9467bd', # bold purple\n",
+ "# 'Local_MDI+_fit_on_all_error_metric_ranking_RFPlus': '#8c564b', # strong brown\n",
+ "# 'Local_MDI+_fit_on_all_l2_norm_RFPlus': '#e377c2', # pink\n",
+ "# 'Local_MDI+_fit_on_all_l2_norm_average_RFPlus': '#bcbd22', # lime green\n",
+ "# 'Local_MDI+_fit_on_all_l2_norm_ranking_RFPlus': '#17becf', # cyan\n",
+ "# 'Local_MDI+_fit_on_all_ranking_RFPlus': '#7f7f7f', # medium gray\n",
+ "# 'Local_MDI+_fit_on_all_ranking_ridge_RFPlus': '#bc5a34', # burnt orange\n",
+ "# 'Local_MDI+_fit_on_inbag_RFPlus': '#000000', # black\n",
+ "# 'Local_MDI+_fit_on_inbag_average_RFPlus': '#7fbc41', # moss green\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_RFPlus': '#ff9896', # light coral\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_average_RFPlus': '#aec7e8', # light blue\n",
+ "# 'Local_MDI+_fit_on_inbag_error_metric_ranking_RFPlus': '#9edae5', # light cyan\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_RFPlus': '#b29189', # warm taupe\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_average_RFPlus': '#c49c94', # peach\n",
+ "# 'Local_MDI+_fit_on_inbag_l2_norm_ranking_RFPlus': '#dbdb8d', # soft yellow-green\n",
+ "# 'Local_MDI+_fit_on_inbag_ranking_RFPlus': '#393b79', # dark blue\n",
+ "# 'Local_MDI+_fit_on_inbag_ranking_ridge_RFPlus': '#637939', # dark olive green\n",
+ "# 'Local_MDI+_fit_on_oob_RFPlus': '#8c6d31', # earthy brown\n",
+ "# 'Local_MDI+_fit_on_oob_average_RFPlus': '#843c39', # dark brick red\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_RFPlus': '#7b4173', # deep purple\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_average_RFPlus': '#6b6ecf', # muted indigo\n",
+ "# 'Local_MDI+_fit_on_oob_error_metric_ranking_RFPlus': '#5254a3', # steel blue\n",
+ "# 'Local_MDI+_fit_on_oob_l2_norm_RFPlus': '#8ca252', # olive\n",
+ "# 'Local_MDI+_fit_on_oob_l2_norm_average_RFPlus': '#bd9e39', # mustard yellow\n",
+ "# 'Local_MDI+_fit_on_oob_l2_norm_ranking_RFPlus': '#d6616b', # muted pink\n",
+ "# 'Local_MDI+_fit_on_oob_ranking_RFPlus': '#ce6dbd', # bright magenta\n",
+ "# 'Local_MDI+_fit_on_oob_ranking_ridge_RFPlus': '#de9ed6', # soft magenta\n",
+ "# 'Random': '#ad494a', # warm red\n",
+ "# 'TreeSHAP_RF': '#6baed6', # sky blue\n",
+ "# }"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if num_features > 20:\n",
+ " all_ratios = [0.01, 0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]\n",
+ "else:\n",
+ " all_ratios = [0.05, 0.1, 0.15, 0.25, 0.4, 0.5, 0.7, 0.9]\n",
+ "num_features_selected = []\n",
+ "for r in all_ratios:\n",
+ " num_features_selected.append(combined_df[f\"num_features_selected_{r}\"].unique()[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Summary of results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# results = {}\n",
+ "# for a_model in [\"RF_Regressor\"]:\n",
+ "# for metric in [\"MSE\"]:\n",
+ "# for m in methods:\n",
+ "# results[m] = []\n",
+ "# for m in methods:\n",
+ "# for k in all_ratios:\n",
+ "# results[m].append(combined_df[combined_df['fi'] == m][a_model + f\"_{metric}_top_{k}\"].mean())\n",
+ "\n",
+ "# filtered_sums = {\n",
+ "# key: sum(values[:5]) \n",
+ "# for key, values in results.items()\n",
+ "# }\n",
+ "# sorted(filtered_sums, key=filtered_sums.get)\n",
+ "\n",
+ "# import pickle\n",
+ "\n",
+ "# list_dict = {element: index + 1 for index, element in enumerate(sorted(filtered_sums, key=filtered_sums.get))}\n",
+ "\n",
+ "# with open(\"temperature_rank.pkl\", \"wb\") as file:\n",
+ "# pickle.dump(list_dict, file)\n",
+ "\n",
+ "# print(\"Dictionary saved as pickle file:\", list_dict)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABdIAAAHqCAYAAAAAkLx0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3xN9//A8de92TsRMpBhREKMmCGJ2iW2fpWqlqAUNcrPbGt38C1Fq6rVL1HVUjWqtYMooWbtWBFiJBKJ7H3v+f2RunUlISER4/18PO6jzTmf8znvc5Irn7zv57w/KkVRFIQQQgghhBBCCCGEEEIIUSB1WQcghBBCCCGEEEIIIYQQQjzLJJEuhBBCCCGEEEIIIYQQQjyEJNKFEEIIIYQQQgghhBBCiIeQRLoQQgghhBBCCCGEEEII8RCSSBdCCCGEEEIIIYQQQgghHkIS6UIIIYQQQgghhBBCCCHEQ0giXQghhBBCCCGEEEIIIYR4CEmkCyGEEEIIIYQQQgghhBAPIYl0IYQQQgghhBBCCCGEEOIhJJEuhHgmXL16FZVKxdy5cx/Zdvr06ahUqhI9f2hoKCqVitDQ0BLt93nwJPczKCgId3f3kg1ICCGEEEI8NTIOLzsyDhdCiOeLJNKFEE/F4sWLUalU+Pr6lnkcwcHBZRqDeDJBQUGoVCrdy8TEhBo1ajB16lQyMzPztb+/7f0vJyenIp/z3h+Y915qtZpy5coRGBjIwYMHS/LyhBBCCCFKlIzDRUmRcbgQ4mVnWNYBCCFeDqtWrcLd3Z3Dhw9z+fJlqlevXiZxLF68mPLlyxMUFKS3/ZVXXiEjIwNjY+MyiUsUj4mJCd9//z0ASUlJ/Pbbb8yaNYuIiAhWrVqVr327du3o16+f3jYzM7Nin7dPnz507NgRjUbDxYsXWbx4Ma1ateLIkSPUqVPn8S5GCCGEEKIUyThclCQZhwshXmaSSBdClLrIyEgOHDjA+vXreffdd1m1ahXTpk0r67D0qNVqTE1NyzoMUUSGhoa89dZbuq+HDx+On58fP//8M1988QWOjo567WvUqKHX/nE1aNBAr5/mzZsTGBjIN998w+LFi5+4/+JIS0vDwsLiqZ7zSWRmZmJsbIxaLQ/DCSGEEE+LjMNFSZNxuIzDhXiZybtICFHqVq1ahZ2dHZ06daJnz54FzlS43/z583Fzc8PMzIwWLVpw5syZR55j+fLltG7dGgcHB0xMTKhVqxbffPONXht3d3fOnj3L3r17dY8GtmzZEii8NuPatWtp2LAhZmZmlC9fnrfeeoubN2/qtQkKCsLS0pKbN2/SvXt3LC0tqVChAuPGjUOj0Twydnd3dzp37kxoaCiNGjXCzMyMOnXq6GJZv349derUwdTUlIYNG/L333/n62P37t00b94cCwsLbG1t6datG+Hh4fna7d+/n8aNG2Nqakq1atX49ttvC43rxx9/1F17uXLleOONN7h+/fojr6csqFQqAgICUBSFK1euPLXzNm/eHICIiAi97YmJibz//vu4uLhgYmJC9erVmTNnDlqtVq9dfHw8b7/9NtbW1tja2tK/f39OnjyJSqXSe/T53s9YREQEHTt2xMrKir59+wKg1WpZsGAB3t7emJqa4ujoyLvvvsvdu3f1znX06FHat29P+fLlMTMzo0qVKgwcOFCvzerVq2nYsCFWVlZYW1tTp04dFi5cqNfmypUrvP7665QrVw5zc3OaNm3K5s2b9drcez+tXr2ajz76iEqVKmFubk5ycnLxb7IQQgghHpuMwx9OxuFPTsbhMg4X4mUiM9KFEKVu1apVvPbaaxgbG9OnTx+++eYbjhw5QuPGjfO1/eGHH0hJSeG9994jMzOThQsX0rp1a06fPp1vdsP9vvnmG7y9venatSuGhob8/vvvDB8+HK1Wy3vvvQfAggULGDlyJJaWlnz44YcAD+0zODiYAQMG0LhxYz777DNu377NwoULCQsL4++//8bW1lbXVqPR0L59e3x9fZk7dy4hISHMmzePatWqMWzYsEfeo8uXL/Pmm2/y7rvv8tZbbzF37ly6dOnCkiVL+OCDDxg+fDgAn332Gb169eLChQu6GQUhISEEBgZStWpVpk+fTkZGBl999RX+/v4cP35ctwjR6dOnefXVV6lQoQLTp08nNzeXadOmFXgPPvnkE6ZMmUKvXr145513iIuL46uvvuKVV17Jd+1FkZqaWmDdxAcZGRlhY2NTrL7vuXr1KgB2dnb59mVmZnLnzh29bVZWVpiYmDzWuR52zvT0dFq0aMHNmzd59913cXV15cCBA0yePJno6GgWLFgA5A28u3TpwuHDhxk2bBheXl789ttv9O/fv8Bz5ebm0r59ewICApg7dy7m5uYAvPvuu7qf1VGjRhEZ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zIvQ9oeUyCAUBWxOD5WNHb93QgghhBCDicvl4rbbbmPEiBHMnDmTVatW+e0vLS1Frx9cpT8Cren996ncGIJH9S8BaS/bBUDdyASMemOXfVQcakB1a7jQaDL1fvJGy/pEJXX2Xu+7L322v5xfvb6DP32cFeihCCGEEEJ0S0DTk1esWMHzzz/PP//5T/bs2cP1119PY2MjV155JQCXX345d9xxh6/9zTffzLp163jsscfYu3cv9957L99++y033ngjAA0NDdx66618/fXX5Obmsn79en70ox8xYsQIFi9eDHiD8ZGRkVxxxRXs2LGD/fv3c+utt5KTk8OyZcv6/004gu8/XkNkndouiN5CB0TWevj+4zUd7o9JCUUDQjUFW33fBYwLa2z8Z2sRr27JHxTZ3WaDnsRw74cOKe8ihBBCiKHgd7/7HS+++CLXXXcdp59+OitWrODaa6/1azMYEh4GCs3tpubzlutnpc12BzGHygEIC56E5nZ32c/uPZUAVBk1EiIsvT7ORKu38GNx7eAKpLdcg6cPoix6IYQQQhzfehRI37RpE//73//8tr344otkZmYSFxfHz372MxwOR7f7u+CCC3j00Ue5++67mTJlCtu3b2fdunW+BUXz8/MpLi72tZ87dy6vvPIKzz33HJMnT+aNN97gzTffZMKECQDo9Xq+//57zj77bEaNGsXVV1/NtGnT+Pzzz31lWWJiYli3bh0NDQ0sWLCA6dOn88UXX/DWW28xefLknrwd/aK+OP+Y2pksBiLjvRenkX2YeF3SfOGe2AcfDvrK4gkJnDcthVBzwNbcFUIIIYToNS+//DJ///vf+fWvf82DDz7It99+yyeffMKVV17pC6APhoSHgcKxeVNzORf/96ysZgtGD1SEw1j96Tg2b+qyn7zsGgA84cY+ef99GemDLJCeX+WdLZsWHRLgkQghhBBCdE+PIoj3338/p5xyCmeeeSYAO3fu5Oqrr2b58uWMHTuWRx55hKSkJO69995u93njjTf6MsoPt2HDhnbbzjvvPM47r+MFfYKCgvjggw+OeM7p06d3q91AEJbYvZrzXbWLSQ2lptRGRUEDaeOie2tofloyYBLCg47QcuC456zxgR6CEEIIIUSvKSoq8iWYAIwYMYINGzawYMECLrvsMv74xz8GcHSDj1pVC7Sv3V1RuYk0ID8tkkwtpLld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kwCQwsJCERFJT08XtVotAwYMkKNHj0pubq5UVVXJtGnTZMCAAfL1119Lfn6+vP3226LVao39HTVqlAwdOlROnjwpBQUF8sUXX0hWVla9x+7YsWOiUqlk8+bNcvHiRcnOzpZVq1YZ1zfU/9r9yM/PFw8PD1m5cqWcO3dOjh49Kr169ZLExETjPsePHy8hISGydetWKSgokC+//FIyMjLk7t278vnnnwsAycvLk5KSEikvL29UHcXFxaLVauWNN96Q3Nxc2bRpkwQEBNQ5xkRERESWxBzOHM4czhxO1BxxIp2Imq17AV5EpH///jJlyhQRaXqADwsLE71eb1wWEREhAwcOND6/e/eueHh4yMcffywi/w/wy5YtM25z584dCQ4OluXLl4uIyJIlS2TYsGEmbV+6dMkY3kR+C/C9evVqsL9BQUGydOlSk2VPPfWUvPrqq8bnUVFRkpKSYnY/tfv+66+/iru7u3z77bcm202dOlUmTpz4wP0kJSXJ888/b3x+//txT2MDPADJyckxblNUVCQqlUp++eUXk/0NGTJEkpOTRUSkR48ekpqaarav93z++efi7e0tFRUVddY1pv+1+zF16lSZPn26yfbffPONuLi4yO3btyUvL08AyMGDB+utp77j0pg6kpOTpWvXribrFyxYwABPREREVsUczhzOHM4cTtQc2dcFt4iILGT58uV47rnnMG/evCbvo1u3bnBx+f8VsQICAkxuAKRSqdCyZUuUlpaavC46Otr47xYtWqBPnz44e/YsAODEiRPIzMyEp6dnnfYKCgrQqVMnAEDv3r3N1lZRUYHLly/j6aefNln+9NNP48SJE43sYf3y8/Nx69YtDB061GR5TU0NevXqZXy+evVqrF+/HsXFxbh9+zZqamrq/FS3qTQaDSIjI43PT506Bb1ebzw+91RXVxuvOTl79mzMnDkTBw4cQGxsLJ5//nmTfdxv6NChCAsLQ/v27TFixAiMGDECY8eOhbu7e6P7f78TJ07g5MmT+Oijj4zLRAQGgwGFhYU4deoUVCoVYmJiGn0MGlPH2bNn0a9fP5P1948/IiIiImtjDm865nDmcCKyL5xIJyKnMGjQIAwfPhzJycnG69zd4+LiAhExWXbnzp06+1Cr1SbPFUWpd9nD3IDn5s2bGD16NJYvX15nXWBgoPHfHh4ejd7n43bz5k0AwO7d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",
+ "text/plain": [
+ "