Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# pip install seaborn mlxtend
|
| 2 |
# app.py
|
|
|
|
| 3 |
import os
|
| 4 |
import io
|
| 5 |
import tempfile
|
|
@@ -36,8 +37,6 @@ from sklearn.exceptions import NotFittedError
|
|
| 36 |
import scipy.stats as stats
|
| 37 |
import matplotlib
|
| 38 |
matplotlib.use("Agg") # for headless plotting
|
| 39 |
-
from mlxtend.frequent_patterns import apriori, association_rules, fpgrowth
|
| 40 |
-
from mlxtend.preprocessing import TransactionEncoder
|
| 41 |
import matplotlib.pyplot as plt
|
| 42 |
|
| 43 |
# --- lightweight password hashing (stdlib) ---
|
|
@@ -992,32 +991,229 @@ def run_clustering(clustering_method: str, X, n_clusters=3, eps=0.5, min_samples
|
|
| 992 |
# -----------------------------
|
| 993 |
# Rule-Based (Apriori) Functions
|
| 994 |
# -----------------------------
|
| 995 |
-
def prepare_transaction_data(df, transaction_col=None):
|
| 996 |
-
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
transactions = []
|
| 999 |
-
for items in
|
| 1000 |
-
if
|
| 1001 |
-
transactions.append([
|
| 1002 |
else:
|
| 1003 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1004 |
else:
|
| 1005 |
-
#
|
| 1006 |
transactions = []
|
| 1007 |
-
for _, row in
|
| 1008 |
transaction = []
|
| 1009 |
-
for col in
|
| 1010 |
-
if
|
| 1011 |
-
|
|
|
|
|
|
|
|
|
|
| 1012 |
if transaction:
|
| 1013 |
transactions.append(transaction)
|
| 1014 |
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1019 |
|
| 1020 |
-
|
|
|
|
|
|
|
| 1021 |
|
| 1022 |
def run_apriori(df_encoded, min_support=0.1, min_confidence=0.5, min_lift=1.0, max_length=4):
|
| 1023 |
# Find frequent itemsets
|
|
@@ -1469,7 +1665,7 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 1469 |
with gr.Column(scale=1):
|
| 1470 |
gr.Markdown("### π― Select Task Type")
|
| 1471 |
task_select = gr.Radio(
|
| 1472 |
-
choices=["Regression", "Classification", "Clustering", "Rule-Based"],
|
| 1473 |
value="Regression",
|
| 1474 |
label="ML Task Type"
|
| 1475 |
)
|
|
@@ -1546,14 +1742,14 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 1546 |
with gr.Column(visible=False) as rule_based_params:
|
| 1547 |
gr.Markdown("### π Rule-Based Mining Settings")
|
| 1548 |
rule_method = gr.Radio(
|
| 1549 |
-
choices=["Apriori", "
|
| 1550 |
-
value="Apriori",
|
| 1551 |
-
label="
|
| 1552 |
)
|
| 1553 |
|
| 1554 |
transaction_col = gr.Dropdown(
|
| 1555 |
choices=[],
|
| 1556 |
-
label="Transaction Column (optional)",
|
| 1557 |
interactive=True
|
| 1558 |
)
|
| 1559 |
|
|
@@ -1581,13 +1777,6 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 1581 |
step=0.1,
|
| 1582 |
label="Minimum Lift"
|
| 1583 |
)
|
| 1584 |
-
max_rule_length = gr.Slider(
|
| 1585 |
-
minimum=2,
|
| 1586 |
-
maximum=10,
|
| 1587 |
-
value=4,
|
| 1588 |
-
step=1,
|
| 1589 |
-
label="Maximum Rule Length"
|
| 1590 |
-
)
|
| 1591 |
|
| 1592 |
with gr.Column(scale=2):
|
| 1593 |
gr.Markdown("### π Model Training & Evaluation")
|
|
@@ -1999,64 +2188,121 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 1999 |
detailed_df = cluster_stats
|
| 2000 |
|
| 2001 |
else: # Rule-Based
|
| 2002 |
-
|
| 2003 |
-
|
| 2004 |
-
|
| 2005 |
-
|
| 2006 |
-
|
| 2007 |
-
|
| 2008 |
-
|
| 2009 |
-
|
| 2010 |
-
|
| 2011 |
-
|
| 2012 |
-
|
| 2013 |
-
|
| 2014 |
-
|
| 2015 |
-
|
| 2016 |
-
|
| 2017 |
-
|
| 2018 |
-
|
| 2019 |
-
|
| 2020 |
-
|
| 2021 |
-
|
| 2022 |
-
|
| 2023 |
-
|
| 2024 |
-
|
| 2025 |
-
|
| 2026 |
-
|
| 2027 |
-
|
| 2028 |
-
|
| 2029 |
-
|
| 2030 |
-
|
| 2031 |
-
|
| 2032 |
-
|
| 2033 |
-
|
| 2034 |
-
|
| 2035 |
-
|
| 2036 |
-
|
| 2037 |
-
|
| 2038 |
-
|
| 2039 |
-
|
| 2040 |
-
|
| 2041 |
-
|
| 2042 |
-
|
| 2043 |
-
|
| 2044 |
-
|
| 2045 |
-
|
| 2046 |
-
|
| 2047 |
-
|
| 2048 |
-
|
| 2049 |
-
|
| 2050 |
-
|
| 2051 |
-
|
| 2052 |
-
|
| 2053 |
-
|
| 2054 |
-
|
| 2055 |
-
|
| 2056 |
-
|
| 2057 |
-
|
| 2058 |
-
|
| 2059 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2060 |
|
| 2061 |
train_btn.click(
|
| 2062 |
fn=train_model_wrapper,
|
|
|
|
| 1 |
# pip install seaborn mlxtend
|
| 2 |
# app.py
|
| 3 |
+
# app.py
|
| 4 |
import os
|
| 5 |
import io
|
| 6 |
import tempfile
|
|
|
|
| 37 |
import scipy.stats as stats
|
| 38 |
import matplotlib
|
| 39 |
matplotlib.use("Agg") # for headless plotting
|
|
|
|
|
|
|
| 40 |
import matplotlib.pyplot as plt
|
| 41 |
|
| 42 |
# --- lightweight password hashing (stdlib) ---
|
|
|
|
| 991 |
# -----------------------------
|
| 992 |
# Rule-Based (Apriori) Functions
|
| 993 |
# -----------------------------
|
| 994 |
+
def prepare_transaction_data(df, transaction_col=None, threshold=0.5):
|
| 995 |
+
"""
|
| 996 |
+
Prepare data for association rule mining.
|
| 997 |
+
For categorical columns, create binary indicators.
|
| 998 |
+
For numeric columns, bin them into categories.
|
| 999 |
+
"""
|
| 1000 |
+
df_prep = df.copy()
|
| 1001 |
+
|
| 1002 |
+
# Convert numeric columns to categorical by binning
|
| 1003 |
+
for col in df_prep.select_dtypes(include=[np.number]).columns:
|
| 1004 |
+
if len(df_prep[col].unique()) > 10: # Only bin if many unique values
|
| 1005 |
+
# Create 5 equal-width bins
|
| 1006 |
+
df_prep[col] = pd.qcut(df_prep[col], q=5, duplicates='drop')
|
| 1007 |
+
df_prep[col] = df_prep[col].astype(str)
|
| 1008 |
+
|
| 1009 |
+
# Convert all to string for consistency
|
| 1010 |
+
for col in df_prep.columns:
|
| 1011 |
+
df_prep[col] = df_prep[col].astype(str)
|
| 1012 |
+
|
| 1013 |
+
# If transaction column is specified, use it
|
| 1014 |
+
if transaction_col and transaction_col in df_prep.columns:
|
| 1015 |
transactions = []
|
| 1016 |
+
for items in df_prep[transaction_col]:
|
| 1017 |
+
if pd.isna(items):
|
| 1018 |
+
transactions.append([])
|
| 1019 |
else:
|
| 1020 |
+
# Split by common delimiters
|
| 1021 |
+
if isinstance(items, str):
|
| 1022 |
+
split_items = [item.strip() for item in str(items).replace(';', ',').split(',')]
|
| 1023 |
+
transactions.append([f"{transaction_col}={item}" for item in split_items if item])
|
| 1024 |
+
else:
|
| 1025 |
+
transactions.append([f"{transaction_col}={items}"])
|
| 1026 |
else:
|
| 1027 |
+
# Create transactions from entire dataframe (one-hot like)
|
| 1028 |
transactions = []
|
| 1029 |
+
for _, row in df_prep.iterrows():
|
| 1030 |
transaction = []
|
| 1031 |
+
for col in df_prep.columns:
|
| 1032 |
+
if col == transaction_col:
|
| 1033 |
+
continue
|
| 1034 |
+
value = str(row[col])
|
| 1035 |
+
if value and value.lower() not in ['nan', 'null', 'none', '']:
|
| 1036 |
+
transaction.append(f"{col}={value}")
|
| 1037 |
if transaction:
|
| 1038 |
transactions.append(transaction)
|
| 1039 |
|
| 1040 |
+
return transactions
|
| 1041 |
+
|
| 1042 |
+
|
| 1043 |
+
def simple_apriori(transactions, min_support=0.1):
|
| 1044 |
+
"""
|
| 1045 |
+
A simplified Apriori algorithm implementation.
|
| 1046 |
+
Returns frequent itemsets and their support.
|
| 1047 |
+
"""
|
| 1048 |
+
from collections import defaultdict
|
| 1049 |
+
import itertools
|
| 1050 |
+
|
| 1051 |
+
# Count item frequencies
|
| 1052 |
+
item_counts = defaultdict(int)
|
| 1053 |
+
for transaction in transactions:
|
| 1054 |
+
for item in set(transaction): # Use set to avoid duplicate items in same transaction
|
| 1055 |
+
item_counts[item] += 1
|
| 1056 |
+
|
| 1057 |
+
total_transactions = len(transactions)
|
| 1058 |
+
|
| 1059 |
+
# Get frequent 1-itemsets
|
| 1060 |
+
frequent_1_itemsets = {}
|
| 1061 |
+
for item, count in item_counts.items():
|
| 1062 |
+
support = count / total_transactions
|
| 1063 |
+
if support >= min_support:
|
| 1064 |
+
frequent_1_itemsets[frozenset([item])] = support
|
| 1065 |
+
|
| 1066 |
+
frequent_itemsets = {1: frequent_1_itemsets}
|
| 1067 |
+
|
| 1068 |
+
# Generate larger itemsets (up to 3-itemsets for simplicity)
|
| 1069 |
+
k = 2
|
| 1070 |
+
while True:
|
| 1071 |
+
candidate_itemsets = set()
|
| 1072 |
+
prev_itemsets = list(frequent_itemsets[k-1].keys())
|
| 1073 |
+
|
| 1074 |
+
# Generate candidates
|
| 1075 |
+
for i in range(len(prev_itemsets)):
|
| 1076 |
+
for j in range(i+1, len(prev_itemsets)):
|
| 1077 |
+
itemset1 = prev_itemsets[i]
|
| 1078 |
+
itemset2 = prev_itemsets[j]
|
| 1079 |
+
union_set = itemset1.union(itemset2)
|
| 1080 |
+
if len(union_set) == k:
|
| 1081 |
+
candidate_itemsets.add(union_set)
|
| 1082 |
+
|
| 1083 |
+
if not candidate_itemsets:
|
| 1084 |
+
break
|
| 1085 |
+
|
| 1086 |
+
# Count support for candidates
|
| 1087 |
+
candidate_counts = defaultdict(int)
|
| 1088 |
+
for transaction in transactions:
|
| 1089 |
+
trans_set = set(transaction)
|
| 1090 |
+
for candidate in candidate_itemsets:
|
| 1091 |
+
if candidate.issubset(trans_set):
|
| 1092 |
+
candidate_counts[candidate] += 1
|
| 1093 |
+
|
| 1094 |
+
# Filter by minimum support
|
| 1095 |
+
frequent_k_itemsets = {}
|
| 1096 |
+
for itemset, count in candidate_counts.items():
|
| 1097 |
+
support = count / total_transactions
|
| 1098 |
+
if support >= min_support:
|
| 1099 |
+
frequent_k_itemsets[itemset] = support
|
| 1100 |
+
|
| 1101 |
+
if not frequent_k_itemsets:
|
| 1102 |
+
break
|
| 1103 |
+
|
| 1104 |
+
frequent_itemsets[k] = frequent_k_itemsets
|
| 1105 |
+
k += 1
|
| 1106 |
+
if k > 3: # Limit to 3-itemsets for performance
|
| 1107 |
+
break
|
| 1108 |
+
|
| 1109 |
+
return frequent_itemsets
|
| 1110 |
+
|
| 1111 |
+
def generate_association_rules(frequent_itemsets, min_confidence=0.5, min_lift=1.0):
|
| 1112 |
+
"""Generate association rules from frequent itemsets."""
|
| 1113 |
+
rules = []
|
| 1114 |
+
|
| 1115 |
+
for k, itemsets in frequent_itemsets.items():
|
| 1116 |
+
if k < 2:
|
| 1117 |
+
continue
|
| 1118 |
+
|
| 1119 |
+
for itemset, support_itemset in itemsets.items():
|
| 1120 |
+
itemset_list = list(itemset)
|
| 1121 |
+
|
| 1122 |
+
# Generate all non-empty proper subsets
|
| 1123 |
+
for i in range(1, k):
|
| 1124 |
+
for antecedent in itertools.combinations(itemset_list, i):
|
| 1125 |
+
antecedent_set = frozenset(antecedent)
|
| 1126 |
+
consequent_set = itemset - antecedent_set
|
| 1127 |
+
|
| 1128 |
+
if not consequent_set:
|
| 1129 |
+
continue
|
| 1130 |
+
|
| 1131 |
+
# Find support of antecedent
|
| 1132 |
+
antecedent_support = None
|
| 1133 |
+
for size in range(1, k):
|
| 1134 |
+
if size in frequent_itemsets and antecedent_set in frequent_itemsets[size]:
|
| 1135 |
+
antecedent_support = frequent_itemsets[size][antecedent_set]
|
| 1136 |
+
break
|
| 1137 |
+
|
| 1138 |
+
if antecedent_support is None or antecedent_support == 0:
|
| 1139 |
+
continue
|
| 1140 |
+
|
| 1141 |
+
# Calculate confidence and lift
|
| 1142 |
+
confidence = support_itemset / antecedent_support
|
| 1143 |
+
consequent_support = None
|
| 1144 |
+
|
| 1145 |
+
# Find support of consequent
|
| 1146 |
+
for size in range(1, k):
|
| 1147 |
+
if size in frequent_itemsets and consequent_set in frequent_itemsets[size]:
|
| 1148 |
+
consequent_support = frequent_itemsets[size][consequent_set]
|
| 1149 |
+
break
|
| 1150 |
+
|
| 1151 |
+
if consequent_support is None or consequent_support == 0:
|
| 1152 |
+
continue
|
| 1153 |
+
|
| 1154 |
+
lift = confidence / consequent_support
|
| 1155 |
+
|
| 1156 |
+
if confidence >= min_confidence and lift >= min_lift:
|
| 1157 |
+
rules.append({
|
| 1158 |
+
'antecedents': set(antecedent),
|
| 1159 |
+
'consequents': set(consequent_set),
|
| 1160 |
+
'support': support_itemset,
|
| 1161 |
+
'confidence': confidence,
|
| 1162 |
+
'lift': lift
|
| 1163 |
+
})
|
| 1164 |
+
|
| 1165 |
+
# Sort by confidence, then lift
|
| 1166 |
+
rules.sort(key=lambda x: (x['confidence'], x['lift']), reverse=True)
|
| 1167 |
+
return rules
|
| 1168 |
+
|
| 1169 |
+
def run_association_mining(transactions, min_support=0.1, min_confidence=0.5, min_lift=1.0):
|
| 1170 |
+
"""Run association rule mining using our simple implementation."""
|
| 1171 |
+
frequent_itemsets = simple_apriori(transactions, min_support)
|
| 1172 |
+
rules = generate_association_rules(frequent_itemsets, min_confidence, min_lift)
|
| 1173 |
+
return frequent_itemsets, rules
|
| 1174 |
+
|
| 1175 |
+
def run_frequency_based_rules(df, categorical_cols=None, min_frequency=0.1, min_cooccurrence=0.5):
|
| 1176 |
+
"""
|
| 1177 |
+
An alternative rule discovery method based on frequency and co-occurrence.
|
| 1178 |
+
Simpler but effective for many use cases.
|
| 1179 |
+
"""
|
| 1180 |
+
if categorical_cols is None:
|
| 1181 |
+
categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
|
| 1182 |
+
|
| 1183 |
+
rules = []
|
| 1184 |
+
|
| 1185 |
+
for col1 in categorical_cols:
|
| 1186 |
+
for col2 in categorical_cols:
|
| 1187 |
+
if col1 == col2:
|
| 1188 |
+
continue
|
| 1189 |
+
|
| 1190 |
+
# Calculate contingency table
|
| 1191 |
+
contingency = pd.crosstab(df[col1], df[col2], normalize='all')
|
| 1192 |
+
|
| 1193 |
+
# Find strong associations
|
| 1194 |
+
for val1 in contingency.index:
|
| 1195 |
+
for val2 in contingency.columns:
|
| 1196 |
+
p_val1_val2 = contingency.loc[val1, val2]
|
| 1197 |
+
p_val1 = df[col1].value_counts(normalize=True).get(val1, 0)
|
| 1198 |
+
p_val2 = df[col2].value_counts(normalize=True).get(val2, 0)
|
| 1199 |
+
|
| 1200 |
+
if p_val1 > 0 and p_val2 > 0 and p_val1_val2 > 0:
|
| 1201 |
+
confidence = p_val1_val2 / p_val1
|
| 1202 |
+
lift = p_val1_val2 / (p_val1 * p_val2)
|
| 1203 |
+
|
| 1204 |
+
if p_val1_val2 >= min_frequency and confidence >= min_cooccurrence:
|
| 1205 |
+
rules.append({
|
| 1206 |
+
'rule': f"If {col1} = {val1} then {col2} = {val2}",
|
| 1207 |
+
'support': p_val1_val2,
|
| 1208 |
+
'confidence': confidence,
|
| 1209 |
+
'lift': lift,
|
| 1210 |
+
'antecedent': f"{col1}={val1}",
|
| 1211 |
+
'consequent': f"{col2}={val2}"
|
| 1212 |
+
})
|
| 1213 |
|
| 1214 |
+
# Sort by confidence and lift
|
| 1215 |
+
rules.sort(key=lambda x: (x['confidence'], x['lift']), reverse=True)
|
| 1216 |
+
return rules
|
| 1217 |
|
| 1218 |
def run_apriori(df_encoded, min_support=0.1, min_confidence=0.5, min_lift=1.0, max_length=4):
|
| 1219 |
# Find frequent itemsets
|
|
|
|
| 1665 |
with gr.Column(scale=1):
|
| 1666 |
gr.Markdown("### π― Select Task Type")
|
| 1667 |
task_select = gr.Radio(
|
| 1668 |
+
choices=["Regression", "Classification", "Clustering", "Rule-Based (Association)"],
|
| 1669 |
value="Regression",
|
| 1670 |
label="ML Task Type"
|
| 1671 |
)
|
|
|
|
| 1742 |
with gr.Column(visible=False) as rule_based_params:
|
| 1743 |
gr.Markdown("### π Rule-Based Mining Settings")
|
| 1744 |
rule_method = gr.Radio(
|
| 1745 |
+
choices=["Apriori (Simple)", "Frequency-Based Rules"],
|
| 1746 |
+
value="Apriori (Simple)",
|
| 1747 |
+
label="Rule Discovery Method"
|
| 1748 |
)
|
| 1749 |
|
| 1750 |
transaction_col = gr.Dropdown(
|
| 1751 |
choices=[],
|
| 1752 |
+
label="Transaction Column (optional - for Apriori)",
|
| 1753 |
interactive=True
|
| 1754 |
)
|
| 1755 |
|
|
|
|
| 1777 |
step=0.1,
|
| 1778 |
label="Minimum Lift"
|
| 1779 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1780 |
|
| 1781 |
with gr.Column(scale=2):
|
| 1782 |
gr.Markdown("### π Model Training & Evaluation")
|
|
|
|
| 2188 |
detailed_df = cluster_stats
|
| 2189 |
|
| 2190 |
else: # Rule-Based
|
| 2191 |
+
try:
|
| 2192 |
+
if rule_algo == "Apriori (Simple)":
|
| 2193 |
+
# Prepare transaction data
|
| 2194 |
+
transactions = prepare_transaction_data(df, trans_col if trans_col else None)
|
| 2195 |
+
|
| 2196 |
+
if not transactions:
|
| 2197 |
+
return "No valid transactions found in the data.", None, None
|
| 2198 |
+
|
| 2199 |
+
# Run Apriori
|
| 2200 |
+
frequent_itemsets, rules = run_association_mining(
|
| 2201 |
+
transactions,
|
| 2202 |
+
min_support=min_sup,
|
| 2203 |
+
min_confidence=min_conf,
|
| 2204 |
+
min_lift=min_lft
|
| 2205 |
+
)
|
| 2206 |
+
|
| 2207 |
+
results_summary = f"## π Simple Apriori Association Rules\n\n"
|
| 2208 |
+
|
| 2209 |
+
else: # Frequency-Based Rules
|
| 2210 |
+
# Identify categorical columns
|
| 2211 |
+
cat_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
|
| 2212 |
+
if not cat_cols:
|
| 2213 |
+
# Try to use all columns as categorical
|
| 2214 |
+
cat_cols = df.columns.tolist()
|
| 2215 |
+
|
| 2216 |
+
rules_list = run_frequency_based_rules(
|
| 2217 |
+
df,
|
| 2218 |
+
categorical_cols=cat_cols,
|
| 2219 |
+
min_frequency=min_sup,
|
| 2220 |
+
min_cooccurrence=min_conf
|
| 2221 |
+
)
|
| 2222 |
+
|
| 2223 |
+
# Convert to rules format similar to Apriori
|
| 2224 |
+
rules = []
|
| 2225 |
+
for rule in rules_list:
|
| 2226 |
+
rules.append({
|
| 2227 |
+
'antecedents': {rule['antecedent']},
|
| 2228 |
+
'consequents': {rule['consequent']},
|
| 2229 |
+
'support': rule['support'],
|
| 2230 |
+
'confidence': rule['confidence'],
|
| 2231 |
+
'lift': rule['lift'],
|
| 2232 |
+
'rule_str': rule['rule']
|
| 2233 |
+
})
|
| 2234 |
+
|
| 2235 |
+
results_summary = f"## π Frequency-Based Association Rules\n\n"
|
| 2236 |
+
|
| 2237 |
+
if not rules:
|
| 2238 |
+
return "No association rules found with the given parameters.", None, None
|
| 2239 |
+
|
| 2240 |
+
results_summary += f"- **Total rules found:** {len(rules)}\n"
|
| 2241 |
+
results_summary += f"- **Minimum support:** {min_sup}\n"
|
| 2242 |
+
results_summary += f"- **Minimum confidence:** {min_conf}\n"
|
| 2243 |
+
results_summary += f"- **Minimum lift:** {min_lft}\n\n"
|
| 2244 |
+
|
| 2245 |
+
# Top 10 rules by confidence
|
| 2246 |
+
top_rules = rules[:10]
|
| 2247 |
+
results_summary += "### π Top 10 Rules by Confidence\n\n"
|
| 2248 |
+
for idx, rule in enumerate(top_rules):
|
| 2249 |
+
if 'rule_str' in rule:
|
| 2250 |
+
rule_text = rule['rule_str']
|
| 2251 |
+
else:
|
| 2252 |
+
antecedents = ', '.join(list(rule['antecedents']))
|
| 2253 |
+
consequents = ', '.join(list(rule['consequents']))
|
| 2254 |
+
rule_text = f"IF {antecedents} THEN {consequents}"
|
| 2255 |
+
|
| 2256 |
+
results_summary += f"{idx+1}. **{rule_text}** \n"
|
| 2257 |
+
results_summary += f" Support: {rule['support']:.3f}, Confidence: {rule['confidence']:.3f}, Lift: {rule['lift']:.3f}\n\n"
|
| 2258 |
+
|
| 2259 |
+
# Prepare rules for visualization
|
| 2260 |
+
plot_data = []
|
| 2261 |
+
for rule in rules[:20]: # Limit to 20 for plotting
|
| 2262 |
+
if 'rule_str' in rule:
|
| 2263 |
+
rule_name = rule['rule_str'][:50] + "..." if len(rule['rule_str']) > 50 else rule['rule_str']
|
| 2264 |
+
else:
|
| 2265 |
+
antecedents = ', '.join(list(rule['antecedents']))[:20]
|
| 2266 |
+
consequents = ', '.join(list(rule['consequents']))[:20]
|
| 2267 |
+
rule_name = f"{antecedents}β{consequents}"
|
| 2268 |
+
|
| 2269 |
+
plot_data.append({
|
| 2270 |
+
'rule': rule_name,
|
| 2271 |
+
'support': rule['support'],
|
| 2272 |
+
'confidence': rule['confidence'],
|
| 2273 |
+
'lift': rule['lift']
|
| 2274 |
+
})
|
| 2275 |
+
|
| 2276 |
+
if plot_data:
|
| 2277 |
+
plot_df = pd.DataFrame(plot_data)
|
| 2278 |
+
fig = px.scatter(plot_df, x='support', y='confidence',
|
| 2279 |
+
size='lift', color='lift',
|
| 2280 |
+
hover_name='rule',
|
| 2281 |
+
title=f'{rule_algo} Association Rules',
|
| 2282 |
+
labels={'support': 'Support', 'confidence': 'Confidence'})
|
| 2283 |
+
plot_fig = fig
|
| 2284 |
+
|
| 2285 |
+
# Prepare detailed results
|
| 2286 |
+
detailed_data = []
|
| 2287 |
+
for rule in rules[:20]:
|
| 2288 |
+
if 'rule_str' in rule:
|
| 2289 |
+
rule_str = rule['rule_str']
|
| 2290 |
+
else:
|
| 2291 |
+
antecedents = ', '.join(list(rule['antecedents']))
|
| 2292 |
+
consequents = ', '.join(list(rule['consequents']))
|
| 2293 |
+
rule_str = f"{antecedents} β {consequents}"
|
| 2294 |
+
|
| 2295 |
+
detailed_data.append({
|
| 2296 |
+
'Rule': rule_str,
|
| 2297 |
+
'Support': f"{rule['support']:.3f}",
|
| 2298 |
+
'Confidence': f"{rule['confidence']:.3f}",
|
| 2299 |
+
'Lift': f"{rule['lift']:.3f}"
|
| 2300 |
+
})
|
| 2301 |
+
|
| 2302 |
+
detailed_df = pd.DataFrame(detailed_data)
|
| 2303 |
+
|
| 2304 |
+
except Exception as e:
|
| 2305 |
+
return f"Error in rule-based mining: {str(e)}", None, None
|
| 2306 |
|
| 2307 |
train_btn.click(
|
| 2308 |
fn=train_model_wrapper,
|