Cosmographer commited on
Commit
428f6f1
Β·
verified Β·
1 Parent(s): a98d6df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +335 -89
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
- if transaction_col and transaction_col in df.columns:
997
- # If transaction column is specified
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  transactions = []
999
- for items in df[transaction_col]:
1000
- if isinstance(items, str):
1001
- transactions.append([item.strip() for item in items.split(',')])
1002
  else:
1003
- transactions.append([str(items)])
 
 
 
 
 
1004
  else:
1005
- # Convert entire dataframe to transactions (binary/one-hot expected)
1006
  transactions = []
1007
- for _, row in df.iterrows():
1008
  transaction = []
1009
- for col in df.columns:
1010
- if row[col] == 1 or row[col] == True:
1011
- transaction.append(col)
 
 
 
1012
  if transaction:
1013
  transactions.append(transaction)
1014
 
1015
- # Encode transactions
1016
- te = TransactionEncoder()
1017
- te_ary = te.fit(transactions).transform(transactions)
1018
- df_encoded = pd.DataFrame(te_ary, columns=te.columns_)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
 
1020
- return df_encoded, transactions
 
 
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", "FP-Growth"],
1550
- value="Apriori",
1551
- label="Association Rule Algorithm"
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
- # Prepare transaction data
2003
- df_encoded, transactions = prepare_transaction_data(df, trans_col if trans_col else None)
2004
-
2005
- if rule_algo == "Apriori":
2006
- frequent_itemsets, rules = run_apriori(
2007
- df_encoded,
2008
- min_support=min_sup,
2009
- min_confidence=min_conf,
2010
- min_lift=min_lft,
2011
- max_length=max_len
2012
- )
2013
- else: # FP-Growth
2014
- frequent_itemsets, rules = run_fp_growth(
2015
- df_encoded,
2016
- min_support=min_sup,
2017
- min_confidence=min_conf,
2018
- min_lift=min_lft,
2019
- max_length=max_len
2020
- )
2021
-
2022
- if rules is None or rules.empty:
2023
- return "No association rules found with the given parameters.", None, None, None
2024
-
2025
- results_summary = f"## πŸ”— {rule_algo} Association Rules\n\n"
2026
- results_summary += f"- **Total rules found:** {len(rules)}\n"
2027
- results_summary += f"- **Minimum support:** {min_sup}\n"
2028
- results_summary += f"- **Minimum confidence:** {min_conf}\n"
2029
- results_summary += f"- **Minimum lift:** {min_lft}\n\n"
2030
-
2031
- # Top 10 rules by confidence
2032
- top_rules = rules.head(10)
2033
- results_summary += "### πŸ† Top 10 Rules by Confidence\n\n"
2034
- for idx, rule in top_rules.iterrows():
2035
- antecedents = ', '.join(list(rule['antecedents']))
2036
- consequents = ', '.join(list(rule['consequents']))
2037
- results_summary += f"{idx+1}. **IF** {antecedents} **THEN** {consequents} \n"
2038
- results_summary += f" Support: {rule['support']:.3f}, Confidence: {rule['confidence']:.3f}, Lift: {rule['lift']:.3f}\n\n"
2039
-
2040
- # Rules visualization
2041
- if not rules.empty:
2042
- plot_rules = rules.head(20).copy()
2043
- plot_rules['rule'] = plot_rules.apply(
2044
- lambda x: f"{', '.join(list(x['antecedents']))} β†’ {', '.join(list(x['consequents']))}", axis=1)
2045
-
2046
- fig = px.scatter(plot_rules, x='support', y='confidence',
2047
- size='lift', color='lift',
2048
- hover_name='rule',
2049
- title=f'{rule_algo} Association Rules',
2050
- labels={'support': 'Support', 'confidence': 'Confidence'})
2051
- plot_fig = fig
2052
-
2053
- detailed_df = rules[['antecedents', 'consequents', 'support', 'confidence', 'lift']].head(20)
2054
-
2055
- return results_summary, plot_fig, detailed_df
2056
-
2057
- except Exception as e:
2058
- traceback.print_exc()
2059
- return f"Error in model training: {str(e)}", None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,