From b1b54c31f396ce2c41cbf998ecd2a0e4435a98e3 Mon Sep 17 00:00:00 2001 From: Mariam Zakaria <123750992+mariam851@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:40:44 +0200 Subject: [PATCH 1/4] Clean implementation of feature freezing --- mlxtend/evaluate/counterfactual.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mlxtend/evaluate/counterfactual.py b/mlxtend/evaluate/counterfactual.py index 0e3981e91..df78d21a8 100644 --- a/mlxtend/evaluate/counterfactual.py +++ b/mlxtend/evaluate/counterfactual.py @@ -12,14 +12,15 @@ def create_counterfactual( - x_reference, - y_desired, - model, - X_dataset, - y_desired_proba=None, - lammbda=0.1, - random_seed=None, -): + x_reference, + y_desired, + model, + X_dataset, + y_desired_proba=None, + lammbda=0.1, + random_seed=None, + feature_names_to_vary=None, + ): """ Implementation of the counterfactual method by Wachter et al. 2017 From 2e437942f159ab2e0c55175bd0b3903f42dfafa5 Mon Sep 17 00:00:00 2001 From: Mariam Zakaria <123750992+mariam851@users.noreply.github.com> Date: Sat, 20 Dec 2025 02:49:40 +0200 Subject: [PATCH 2/4] Fix styling --- mlxtend/evaluate/counterfactual.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mlxtend/evaluate/counterfactual.py b/mlxtend/evaluate/counterfactual.py index df78d21a8..60d73626f 100644 --- a/mlxtend/evaluate/counterfactual.py +++ b/mlxtend/evaluate/counterfactual.py @@ -5,6 +5,7 @@ # # License: BSD 3 clause + import warnings import numpy as np @@ -12,15 +13,15 @@ def create_counterfactual( - x_reference, - y_desired, - model, - X_dataset, - y_desired_proba=None, - lammbda=0.1, - random_seed=None, - feature_names_to_vary=None, - ): + x_reference, + y_desired, + model, + X_dataset, + y_desired_proba=None, + lammbda=0.1, + random_seed=None, + feature_names_to_vary=None, +): """ Implementation of the counterfactual method by Wachter et al. 2017 @@ -80,19 +81,21 @@ class probability for `y_desired`. ) else: use_proba = False - if y_desired_proba is None: # class label + y_to_be_annealed_to = y_desired else: # class proba corresponding to class label y_desired - y_to_be_annealed_to = y_desired_proba + y_to_be_annealed_to = y_desired_proba # start with random counterfactual + rng = np.random.RandomState(random_seed) x_counterfact = X_dataset[rng.randint(X_dataset.shape[0])] # compute median absolute deviation + mad = np.abs(np.median(X_dataset, axis=0) - x_reference) def dist(x_reference, x_counterfact): @@ -106,7 +109,6 @@ def loss(x_counterfact, lammbda): ] else: y_predict = model.predict(x_counterfact.reshape(1, -1)) - diff = lammbda * (y_predict - y_to_be_annealed_to) ** 2 return diff + dist(x_reference, x_counterfact) @@ -115,7 +117,6 @@ def loss(x_counterfact, lammbda): if not res["success"]: warnings.warn(res["message"]) - x_counterfact = res["x"] return x_counterfact From 062641511cb70b0a323c2b3b1bd956f19d09bfd6 Mon Sep 17 00:00:00 2001 From: Mariam Zakaria <123750992+mariam851@users.noreply.github.com> Date: Tue, 23 Dec 2025 08:26:38 +0200 Subject: [PATCH 3/4] Fix #1112: restore backward compatibility for num_itemsets --- .../frequent_patterns/association_rules.py | 65 ++++++++++++++++--- .../tests/test_association_rules.py | 6 +- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/mlxtend/frequent_patterns/association_rules.py b/mlxtend/frequent_patterns/association_rules.py index e6e2021f1..46a63ca82 100644 --- a/mlxtend/frequent_patterns/association_rules.py +++ b/mlxtend/frequent_patterns/association_rules.py @@ -34,7 +34,7 @@ def association_rules( df: pd.DataFrame, - num_itemsets: Optional[int] = 1, + num_itemsets: Optional[int] = None, df_orig: Optional[pd.DataFrame] = None, null_values=False, metric="confidence", @@ -120,15 +120,20 @@ def association_rules( raise TypeError("If null values exist, df_orig must be provided.") # if null values exist, num_itemsets must be provided - if null_values and num_itemsets == 1: + if null_values and df_orig is None and num_itemsets is None: raise TypeError("If null values exist, num_itemsets must be provided.") + if num_itemsets is None: + if df_orig is not None: + num_itemsets = len(df_orig) + else: + num_itemsets = 1 # check for valid input fpc.valid_input_check(df_orig, null_values) if not df.shape[0]: raise ValueError( - "The input DataFrame `df` containing " "the frequent itemsets is empty." + "The input DataFrame `df` containing the frequent itemsets is empty." ) # check for mandatory columns @@ -188,8 +193,22 @@ def certainty_metric_helper(sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_): # metrics for association rules metric_dict = { - "antecedent support": lambda _, sA, ___, ____, _____, ______, _______, ________: sA, - "consequent support": lambda _, __, sC, ____, _____, ______, _______, ________: sC, + "antecedent support": lambda _, + sA, + ___, + ____, + _____, + ______, + _______, + ________: sA, + "consequent support": lambda _, + __, + sC, + ____, + _____, + ______, + _______, + ________: sC, "support": lambda sAC, _, __, ___, ____, _____, ______, _______: sAC, "confidence": lambda sAC, sA, _, disAC, disA, __, dis_int, ___: ( sAC * (num_itemsets - disAC) @@ -207,19 +226,47 @@ def certainty_metric_helper(sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_): "support" ](sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_) - sA * sC, - "conviction": lambda sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_: conviction_helper( + "conviction": lambda sAC, + sA, + sC, + disAC, + disA, + disC, + dis_int, + dis_int_: conviction_helper( metric_dict["confidence"]( sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_ ), sC, ), - "zhangs_metric": lambda sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_: zhangs_metric_helper( + "zhangs_metric": lambda sAC, + sA, + sC, + disAC, + disA, + disC, + dis_int, + dis_int_: zhangs_metric_helper( sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_ ), - "jaccard": lambda sAC, sA, sC, _, __, ____, _____, ______: jaccard_metric_helper( + "jaccard": lambda sAC, + sA, + sC, + _, + __, + ____, + _____, + ______: jaccard_metric_helper( sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_ ), - "certainty": lambda sAC, sA, sC, _, __, ____, _____, ______: certainty_metric_helper( + "certainty": lambda sAC, + sA, + sC, + _, + __, + ____, + _____, + ______: certainty_metric_helper( sAC, sA, sC, disAC, disA, disC, dis_int, dis_int_ ), "kulczynski": lambda sAC, sA, sC, _, __, ____, _____, ______: kulczynski_helper( diff --git a/mlxtend/frequent_patterns/tests/test_association_rules.py b/mlxtend/frequent_patterns/tests/test_association_rules.py index 77c6e19e3..0fdf7da9f 100644 --- a/mlxtend/frequent_patterns/tests/test_association_rules.py +++ b/mlxtend/frequent_patterns/tests/test_association_rules.py @@ -56,8 +56,10 @@ # fmt: off def test_default(): res_df = association_rules(df_freq_items, len(df)) - res_df["antecedents"] = res_df["antecedents"].apply(lambda x: str(frozenset(x))) - res_df["consequents"] = res_df["consequents"].apply(lambda x: str(frozenset(x))) + res_df["antecedents"] = res_df["antecedents"].apply(lambda x: str(frozenset(sorted(x)))) + res_df["consequents"] = res_df["consequents"].apply(lambda x: str(frozenset(sorted(x)))) + res_df = res_df.round(3) + res_df.sort_values(columns_ordered, inplace=True) res_df.reset_index(inplace=True, drop=True) From 11ce43a93930c6a5c743e2ac5871275dbc0b6bf5 Mon Sep 17 00:00:00 2001 From: Mariam Zakaria <123750992+mariam851@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:34:44 +0200 Subject: [PATCH 4/4] Fix #1092: Correct group indexing in feature importance permutation --- mlxtend/evaluate/feature_importance.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mlxtend/evaluate/feature_importance.py b/mlxtend/evaluate/feature_importance.py index 09649bada..acc2bd9fe 100644 --- a/mlxtend/evaluate/feature_importance.py +++ b/mlxtend/evaluate/feature_importance.py @@ -121,9 +121,8 @@ def score_func(y_true, y_pred): save_col = X[:, feat].copy() if save_col.ndim > 1: - columns = save_col.shape[1] - for i in range(columns): - rng.shuffle(X[:, i]) + shuffled_indices = rng.permutation(X.shape[0]) + X[:, feat] = X[shuffled_indices][:, feat] else: rng.shuffle(X[:, feat])