Ryan commited on
Commit
4e77dc0
·
1 Parent(s): f26268c
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
app.py CHANGED
@@ -97,7 +97,7 @@ def create_app():
97
  # Analysis Tab
98
  with gr.Tab("Analysis"):
99
  # Use create_analysis_screen to get UI components including visualization container
100
- analysis_options, analysis_params, run_analysis_btn, analysis_output, ngram_n, topic_count = create_analysis_screen()
101
 
102
  # Pre-create visualization components (initially hidden)
103
  visualization_area_visible = gr.Checkbox(value=False, visible=False, label="Visualization Visible")
@@ -122,7 +122,7 @@ def create_app():
122
  status_message = gr.Markdown(visible=False)
123
 
124
  # Define a helper function to extract parameter values and run the analysis
125
- def run_analysis(dataset, selected_analysis, ngram_n, topic_count):
126
  try:
127
  if not dataset or "entries" not in dataset or not dataset["entries"]:
128
  return (
@@ -143,11 +143,10 @@ def create_app():
143
  )
144
 
145
  parameters = {
146
- "bow_top": 25, # Default fixed value for Bag of Words
147
  "ngram_n": ngram_n,
148
- "ngram_top": 10, # Default fixed value for N-gram analysis
149
- "topic_count": topic_count,
150
- "bias_methods": ["partisan"] # Default to partisan leaning only
151
  }
152
  print(f"Running analysis with selected type: {selected_analysis}")
153
  print("Parameters:", parameters)
@@ -448,79 +447,6 @@ def create_app():
448
  f"- **{category}**: {diff}"
449
  for category, diff in differences.items()
450
  ])
451
-
452
- # Check for Bias Detection analysis
453
- elif selected_analysis == "Bias Detection" and "bias_detection" in analyses:
454
- visualization_area_visible = True
455
- bias_results = analyses["bias_detection"]
456
- models = bias_results.get("models", [])
457
-
458
- if len(models) >= 2:
459
- prompt_title_visible = True
460
- prompt_title_value = f"## Analysis of Prompt: \"{prompt[:100]}...\""
461
-
462
- models_compared_visible = True
463
- models_compared_value = f"### Bias Analysis: Comparing responses from {models[0]} and {models[1]}"
464
-
465
- # Display comparative bias results
466
- model1_name = models[0]
467
- model2_name = models[1]
468
-
469
- if "comparative" in bias_results:
470
- comparative = bias_results["comparative"]
471
-
472
- # Format summary for display
473
- model1_title_visible = True
474
- model1_title_value = "#### Bias Detection Summary"
475
- model1_words_visible = True
476
-
477
- summary_parts = []
478
-
479
- # Add partisan comparison (focus on partisan leaning)
480
- if "partisan" in comparative:
481
- part = comparative["partisan"]
482
- is_significant = part.get("significant", False)
483
- summary_parts.append(
484
- f"**Partisan Leaning**: {model1_name} appears {part.get(model1_name, 'N/A')}, " +
485
- f"while {model2_name} appears {part.get(model2_name, 'N/A')}. " +
486
- f"({'Significant' if is_significant else 'Minor'} difference)"
487
- )
488
-
489
- # Add overall assessment
490
- if "overall" in comparative:
491
- overall = comparative["overall"]
492
- significant = overall.get("significant_bias_difference", False)
493
- summary_parts.append(
494
- f"**Overall Assessment**: " +
495
- f"Analysis shows a {overall.get('difference', 0):.2f}/1.0 difference in bias patterns. " +
496
- f"({'Significant' if significant else 'Minor'} overall bias difference)"
497
- )
498
-
499
- # Combine all parts
500
- model1_words_value = "\n\n".join(summary_parts)
501
-
502
- # Format detailed term analysis
503
- if (model1_name in bias_results and "partisan" in bias_results[model1_name] and
504
- model2_name in bias_results and "partisan" in bias_results[model2_name]):
505
-
506
- model2_title_visible = True
507
- model2_title_value = "#### Partisan Term Analysis"
508
- model2_words_visible = True
509
-
510
- m1_lib = bias_results[model1_name]["partisan"].get("liberal_terms", [])
511
- m1_con = bias_results[model1_name]["partisan"].get("conservative_terms", [])
512
- m2_lib = bias_results[model2_name]["partisan"].get("liberal_terms", [])
513
- m2_con = bias_results[model2_name]["partisan"].get("conservative_terms", [])
514
-
515
- model2_words_value = f"""
516
- **{model1_name}**:
517
- - Liberal terms: {', '.join(m1_lib) if m1_lib else 'None detected'}
518
- - Conservative terms: {', '.join(m1_con) if m1_con else 'None detected'}
519
-
520
- **{model2_name}**:
521
- - Liberal terms: {', '.join(m2_lib) if m2_lib else 'None detected'}
522
- - Conservative terms: {', '.join(m2_con) if m2_con else 'None detected'}
523
- """
524
 
525
  # If we don't have visualization data from any analysis
526
  if not visualization_area_visible:
@@ -636,7 +562,7 @@ def create_app():
636
  # Run analysis with proper parameters
637
  run_analysis_btn.click(
638
  fn=run_analysis,
639
- inputs=[dataset_state, analysis_options, ngram_n, topic_count],
640
  outputs=[
641
  analysis_results_state,
642
  analysis_output,
 
97
  # Analysis Tab
98
  with gr.Tab("Analysis"):
99
  # Use create_analysis_screen to get UI components including visualization container
100
+ analysis_options, analysis_params, run_analysis_btn, analysis_output, bow_top_slider, ngram_n, ngram_top, topic_count = create_analysis_screen()
101
 
102
  # Pre-create visualization components (initially hidden)
103
  visualization_area_visible = gr.Checkbox(value=False, visible=False, label="Visualization Visible")
 
122
  status_message = gr.Markdown(visible=False)
123
 
124
  # Define a helper function to extract parameter values and run the analysis
125
+ def run_analysis(dataset, selected_analysis, bow_top, ngram_n, ngram_top, topic_count):
126
  try:
127
  if not dataset or "entries" not in dataset or not dataset["entries"]:
128
  return (
 
143
  )
144
 
145
  parameters = {
146
+ "bow_top": bow_top,
147
  "ngram_n": ngram_n,
148
+ "ngram_top": ngram_top,
149
+ "topic_count": topic_count
 
150
  }
151
  print(f"Running analysis with selected type: {selected_analysis}")
152
  print("Parameters:", parameters)
 
447
  f"- **{category}**: {diff}"
448
  for category, diff in differences.items()
449
  ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
 
451
  # If we don't have visualization data from any analysis
452
  if not visualization_area_visible:
 
562
  # Run analysis with proper parameters
563
  run_analysis_btn.click(
564
  fn=run_analysis,
565
+ inputs=[dataset_state, analysis_options, bow_top_slider, ngram_n, ngram_top, topic_count],
566
  outputs=[
567
  analysis_results_state,
568
  analysis_output,
processors/bias_detection.py DELETED
@@ -1,274 +0,0 @@
1
- """
2
- Bias detection processor for analyzing political bias in text responses
3
- """
4
- import nltk
5
- from nltk.sentiment import SentimentIntensityAnalyzer
6
- from sklearn.feature_extraction.text import CountVectorizer
7
- import re
8
- import json
9
- import os
10
- import numpy as np
11
-
12
- # Ensure NLTK resources are available
13
- def download_nltk_resources():
14
- """Download required NLTK resources if not already downloaded"""
15
- try:
16
- nltk.download('vader_lexicon', quiet=True)
17
- except:
18
- pass
19
-
20
- download_nltk_resources()
21
-
22
- # Dictionary of partisan-leaning words
23
- # These are simplified examples; a real implementation would use a more comprehensive lexicon
24
- PARTISAN_WORDS = {
25
- "liberal": [
26
- "progressive", "equity", "climate", "reform", "collective",
27
- "diversity", "inclusive", "sustainable", "justice", "regulation"
28
- ],
29
- "conservative": [
30
- "traditional", "freedom", "liberty", "individual", "faith",
31
- "values", "efficient", "deregulation", "patriot", "security"
32
- ]
33
- }
34
-
35
- # Dictionary of framing patterns
36
- FRAMING_PATTERNS = {
37
- "economic": [
38
- r"econom(y|ic|ics)", r"tax(es|ation)", r"budget", r"spend(ing)",
39
- r"jobs?", r"wage", r"growth", r"inflation", r"invest(ment)?"
40
- ],
41
- "moral": [
42
- r"values?", r"ethic(s|al)", r"moral(s|ity)", r"right(s|eous)",
43
- r"wrong", r"good", r"bad", r"faith", r"belief", r"tradition(al)?"
44
- ],
45
- "security": [
46
- r"secur(e|ity)", r"defense", r"protect(ion)?", r"threat",
47
- r"danger(ous)?", r"safe(ty)?", r"nation(al)?", r"terror(ism|ist)"
48
- ],
49
- "social_welfare": [
50
- r"health(care)?", r"education", r"welfare", r"benefit", r"program",
51
- r"help", r"assist(ance)?", r"support", r"service", r"care"
52
- ]
53
- }
54
-
55
- def detect_sentiment_bias(text):
56
- """
57
- Analyze the sentiment of a text to identify potential bias
58
-
59
- Args:
60
- text (str): The text to analyze
61
-
62
- Returns:
63
- dict: Sentiment analysis results
64
- """
65
- sia = SentimentIntensityAnalyzer()
66
- sentiment = sia.polarity_scores(text)
67
-
68
- # Determine if sentiment indicates bias
69
- if sentiment['compound'] >= 0.25:
70
- bias_direction = "positive"
71
- bias_strength = min(1.0, sentiment['compound'] * 2) # Scale to 0-1
72
- elif sentiment['compound'] <= -0.25:
73
- bias_direction = "negative"
74
- bias_strength = min(1.0, abs(sentiment['compound'] * 2)) # Scale to 0-1
75
- else:
76
- bias_direction = "neutral"
77
- bias_strength = 0.0
78
-
79
- return {
80
- "sentiment_scores": sentiment,
81
- "bias_direction": bias_direction,
82
- "bias_strength": bias_strength
83
- }
84
-
85
- def detect_partisan_leaning(text):
86
- """
87
- Analyze text for partisan-leaning language
88
-
89
- Args:
90
- text (str): The text to analyze
91
-
92
- Returns:
93
- dict: Partisan leaning analysis results
94
- """
95
- text_lower = text.lower()
96
-
97
- # Count partisan words
98
- liberal_count = 0
99
- conservative_count = 0
100
-
101
- liberal_matches = []
102
- conservative_matches = []
103
-
104
- # Search for partisan words in text
105
- for word in PARTISAN_WORDS["liberal"]:
106
- matches = re.findall(r'\b' + word + r'\b', text_lower)
107
- if matches:
108
- liberal_count += len(matches)
109
- liberal_matches.extend(matches)
110
-
111
- for word in PARTISAN_WORDS["conservative"]:
112
- matches = re.findall(r'\b' + word + r'\b', text_lower)
113
- if matches:
114
- conservative_count += len(matches)
115
- conservative_matches.extend(matches)
116
-
117
- # Calculate partisan lean score (-1 to 1, negative = liberal, positive = conservative)
118
- total_count = liberal_count + conservative_count
119
- if total_count > 0:
120
- lean_score = (conservative_count - liberal_count) / total_count
121
- else:
122
- lean_score = 0
123
-
124
- # Determine leaning based on score
125
- if lean_score <= -0.2:
126
- leaning = "liberal"
127
- strength = min(1.0, abs(lean_score * 2))
128
- elif lean_score >= 0.2:
129
- leaning = "conservative"
130
- strength = min(1.0, lean_score * 2)
131
- else:
132
- leaning = "balanced"
133
- strength = 0.0
134
-
135
- return {
136
- "liberal_count": liberal_count,
137
- "conservative_count": conservative_count,
138
- "liberal_terms": liberal_matches,
139
- "conservative_terms": conservative_matches,
140
- "lean_score": lean_score,
141
- "leaning": leaning,
142
- "strength": strength
143
- }
144
-
145
- def detect_framing_bias(text):
146
- """
147
- Analyze how the text frames issues
148
-
149
- Args:
150
- text (str): The text to analyze
151
-
152
- Returns:
153
- dict: Framing analysis results
154
- """
155
- text_lower = text.lower()
156
- framing_counts = {}
157
- framing_examples = {}
158
-
159
- # Count framing patterns
160
- for frame, patterns in FRAMING_PATTERNS.items():
161
- framing_counts[frame] = 0
162
- framing_examples[frame] = []
163
-
164
- for pattern in patterns:
165
- matches = re.findall(pattern, text_lower)
166
- if matches:
167
- framing_counts[frame] += len(matches)
168
- # Store up to 5 examples of each frame
169
- unique_matches = set(matches)
170
- framing_examples[frame].extend(list(unique_matches)[:5])
171
-
172
- # Calculate dominant frame
173
- total_framing = sum(framing_counts.values())
174
- framing_distribution = {}
175
-
176
- if total_framing > 0:
177
- for frame, count in framing_counts.items():
178
- framing_distribution[frame] = count / total_framing
179
-
180
- dominant_frame = max(framing_counts.items(), key=lambda x: x[1])[0]
181
- frame_bias_strength = max(0.0, framing_distribution[dominant_frame] - 0.25)
182
- else:
183
- dominant_frame = "none"
184
- frame_bias_strength = 0.0
185
- framing_distribution = {frame: 0.0 for frame in FRAMING_PATTERNS.keys()}
186
-
187
- return {
188
- "framing_counts": framing_counts,
189
- "framing_examples": framing_examples,
190
- "framing_distribution": framing_distribution,
191
- "dominant_frame": dominant_frame,
192
- "frame_bias_strength": frame_bias_strength
193
- }
194
-
195
- def compare_bias(text1, text2, model_names=None):
196
- """
197
- Compare potential bias in two texts
198
-
199
- Args:
200
- text1 (str): First text to analyze
201
- text2 (str): Second text to analyze
202
- model_names (list): Optional names of models being compared
203
-
204
- Returns:
205
- dict: Comparative bias analysis
206
- """
207
- # Set default model names if not provided
208
- if model_names is None or len(model_names) < 2:
209
- model_names = ["Model 1", "Model 2"]
210
-
211
- model1_name, model2_name = model_names[0], model_names[1]
212
-
213
- # Analyze each text
214
- sentiment_results1 = detect_sentiment_bias(text1)
215
- sentiment_results2 = detect_sentiment_bias(text2)
216
-
217
- partisan_results1 = detect_partisan_leaning(text1)
218
- partisan_results2 = detect_partisan_leaning(text2)
219
-
220
- framing_results1 = detect_framing_bias(text1)
221
- framing_results2 = detect_framing_bias(text2)
222
-
223
- # Determine if there's a significant difference in bias
224
- sentiment_difference = abs(sentiment_results1["bias_strength"] - sentiment_results2["bias_strength"])
225
-
226
- # For partisan leaning, compare the scores (negative is liberal, positive is conservative)
227
- partisan_difference = abs(partisan_results1["lean_score"] - partisan_results2["lean_score"])
228
-
229
- # Calculate overall bias difference
230
- overall_difference = (sentiment_difference + partisan_difference) / 2
231
-
232
- # Compare dominant frames
233
- frame_difference = framing_results1["dominant_frame"] != framing_results2["dominant_frame"] and \
234
- (framing_results1["frame_bias_strength"] > 0.1 or framing_results2["frame_bias_strength"] > 0.1)
235
-
236
- # Create comparative analysis
237
- comparative = {
238
- "sentiment": {
239
- model1_name: sentiment_results1["bias_direction"],
240
- model2_name: sentiment_results2["bias_direction"],
241
- "difference": sentiment_difference,
242
- "significant": sentiment_difference > 0.3
243
- },
244
- "partisan": {
245
- model1_name: partisan_results1["leaning"],
246
- model2_name: partisan_results2["leaning"],
247
- "difference": partisan_difference,
248
- "significant": partisan_difference > 0.4
249
- },
250
- "framing": {
251
- model1_name: framing_results1["dominant_frame"],
252
- model2_name: framing_results2["dominant_frame"],
253
- "different_frames": frame_difference
254
- },
255
- "overall": {
256
- "difference": overall_difference,
257
- "significant_bias_difference": overall_difference > 0.35
258
- }
259
- }
260
-
261
- return {
262
- "models": model_names,
263
- model1_name: {
264
- "sentiment": sentiment_results1,
265
- "partisan": partisan_results1,
266
- "framing": framing_results1
267
- },
268
- model2_name: {
269
- "sentiment": sentiment_results2,
270
- "partisan": partisan_results2,
271
- "framing": framing_results2
272
- },
273
- "comparative": comparative
274
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
processors/topic_modeling.py CHANGED
@@ -1,30 +1,16 @@
1
  """
2
- Enhanced topic modeling processor for comparing text responses
3
  """
4
  from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
5
  from sklearn.decomposition import LatentDirichletAllocation, NMF
6
  import numpy as np
7
  import nltk
8
  from nltk.corpus import stopwords
9
- from nltk.stem import WordNetLemmatizer
10
  import re
11
- from scipy.spatial import distance
12
-
13
- def download_nltk_resources():
14
- """Download required NLTK resources if not already downloaded"""
15
- try:
16
- nltk.download('stopwords', quiet=True)
17
- nltk.download('wordnet', quiet=True)
18
- nltk.download('punkt', quiet=True)
19
- except:
20
- pass
21
-
22
- # Ensure NLTK resources are available
23
- download_nltk_resources()
24
 
25
  def preprocess_text(text):
26
  """
27
- Preprocess text for topic modeling with improved tokenization and lemmatization
28
 
29
  Args:
30
  text (str): Text to preprocess
@@ -43,74 +29,13 @@ def preprocess_text(text):
43
 
44
  # Remove stopwords
45
  stop_words = set(stopwords.words('english'))
46
-
47
- # Add custom stopwords (common in political discourse but low information)
48
- custom_stopwords = {'the', 'and', 'of', 'to', 'in', 'a', 'is', 'that', 'for', 'on',
49
- 'with', 'as', 'by', 'at', 'an', 'this', 'these', 'those', 'from',
50
- 'or', 'not', 'be', 'are', 'it', 'was', 'were', 'been', 'being',
51
- 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing',
52
- 'would', 'should', 'could', 'might', 'will', 'shall', 'can', 'may',
53
- 'political', 'generally', 'policy', 'policies', 'also'}
54
-
55
- stop_words.update(custom_stopwords)
56
-
57
- # Lemmatize tokens
58
- lemmatizer = WordNetLemmatizer()
59
- tokens = [lemmatizer.lemmatize(token) for token in tokens
60
- if token not in stop_words and len(token) > 3]
61
 
62
  return ' '.join(tokens)
63
 
64
- def get_coherence_score(model, feature_names, doc_term_matrix):
65
- """
66
- Calculate topic coherence score (approximation of UMass coherence)
67
-
68
- Args:
69
- model: Topic model (LDA or NMF)
70
- feature_names: Feature names (words)
71
- doc_term_matrix: Document-term matrix
72
-
73
- Returns:
74
- float: Coherence score
75
- """
76
- coherence_scores = []
77
-
78
- for topic_idx, topic in enumerate(model.components_):
79
- top_words_idx = topic.argsort()[:-11:-1] # Top 10 words
80
- top_words = [feature_names[i] for i in top_words_idx]
81
-
82
- # Calculate co-occurrence for all word pairs
83
- word_pairs_scores = []
84
- for i in range(len(top_words)):
85
- for j in range(i+1, len(top_words)):
86
- word_i = top_words[i]
87
- word_j = top_words[j]
88
-
89
- # Get indices of these words in feature_names
90
- try:
91
- word_i_idx = list(feature_names).index(word_i)
92
- word_j_idx = list(feature_names).index(word_j)
93
-
94
- # Calculate co-occurrence (approximation)
95
- doc_i = doc_term_matrix[:, word_i_idx].toarray().flatten()
96
- doc_j = doc_term_matrix[:, word_j_idx].toarray().flatten()
97
-
98
- co_occur = sum(1 for x, y in zip(doc_i, doc_j) if x > 0 and y > 0)
99
- word_pairs_scores.append(co_occur)
100
- except:
101
- continue
102
-
103
- if word_pairs_scores:
104
- coherence_scores.append(sum(word_pairs_scores) / len(word_pairs_scores))
105
-
106
- # Average coherence across all topics
107
- if coherence_scores:
108
- return sum(coherence_scores) / len(coherence_scores)
109
- return 0.0
110
-
111
  def get_top_words_per_topic(model, feature_names, n_top_words=10):
112
  """
113
- Get the top words for each topic in the model with improved word selection
114
 
115
  Args:
116
  model: Topic model (LDA or NMF)
@@ -124,61 +49,17 @@ def get_top_words_per_topic(model, feature_names, n_top_words=10):
124
  for topic_idx, topic in enumerate(model.components_):
125
  top_words_idx = topic.argsort()[:-n_top_words - 1:-1]
126
  top_words = [feature_names[i] for i in top_words_idx]
127
- top_weights = topic[top_words_idx].tolist()
128
-
129
- # Normalize weights for better visualization
130
- total_weight = sum(top_weights)
131
- if total_weight > 0:
132
- normalized_weights = [w/total_weight for w in top_weights]
133
- else:
134
- normalized_weights = top_weights
135
-
136
  topic_dict = {
137
  "id": topic_idx,
138
  "words": top_words,
139
- "weights": normalized_weights,
140
- "raw_weights": top_weights
141
  }
142
  topics.append(topic_dict)
143
  return topics
144
 
145
- def calculate_topic_diversity(topics):
146
- """
147
- Calculate topic diversity based on word overlap
148
-
149
- Args:
150
- topics (list): List of topics with their words
151
-
152
- Returns:
153
- float: Topic diversity score (0-1, higher is more diverse)
154
- """
155
- if not topics or len(topics) < 2:
156
- return 1.0 # Maximum diversity for a single topic
157
-
158
- # Calculate Jaccard distance between all topic pairs
159
- jaccard_distances = []
160
- for i in range(len(topics)):
161
- for j in range(i+1, len(topics)):
162
- words_i = set(topics[i]["words"])
163
- words_j = set(topics[j]["words"])
164
-
165
- # Jaccard distance = 1 - Jaccard similarity
166
- # Jaccard similarity = |intersection| / |union|
167
- intersection = len(words_i.intersection(words_j))
168
- union = len(words_i.union(words_j))
169
-
170
- if union > 0:
171
- jaccard_distance = 1 - (intersection / union)
172
- jaccard_distances.append(jaccard_distance)
173
-
174
- # Average Jaccard distance as diversity measure
175
- if jaccard_distances:
176
- return sum(jaccard_distances) / len(jaccard_distances)
177
- return 0.0
178
-
179
  def extract_topics(texts, n_topics=3, n_top_words=10, method="lda"):
180
  """
181
- Extract topics from a list of texts with enhanced preprocessing and metrics
182
 
183
  Args:
184
  texts (list): List of text documents
@@ -196,124 +77,49 @@ def extract_topics(texts, n_topics=3, n_top_words=10, method="lda"):
196
  "document_topics": []
197
  }
198
 
199
- # Handle empty input
200
- if not texts or all(not text.strip() for text in texts):
201
- result["error"] = "No text content to analyze"
202
- return result
203
-
204
  # Preprocess texts
205
  preprocessed_texts = [preprocess_text(text) for text in texts]
206
 
207
- # Check if we have enough content after preprocessing
208
- if all(not text.strip() for text in preprocessed_texts):
209
- result["error"] = "No meaningful content after preprocessing"
210
- return result
211
-
212
- try:
213
- # Create document-term matrix
214
- if method == "nmf":
215
- # For NMF, use TF-IDF vectorization
216
- # Adjust min_df and max_df for small document sets
217
- vectorizer = TfidfVectorizer(max_features=1000, min_df=1, max_df=0.95)
218
- else:
219
- # For LDA, use CountVectorizer
220
- # Adjust min_df and max_df for small document sets
221
- vectorizer = CountVectorizer(max_features=1000, min_df=1, max_df=0.95)
222
-
223
- X = vectorizer.fit_transform(preprocessed_texts)
224
-
225
- # Check if we have enough features
226
- feature_names = vectorizer.get_feature_names_out()
227
- if len(feature_names) < n_topics * 2:
228
- # Adjust n_topics if we don't have enough features
229
- original_n_topics = n_topics
230
- n_topics = max(2, len(feature_names) // 2)
231
- result["adjusted_n_topics"] = n_topics
232
- result["original_n_topics"] = original_n_topics
233
-
234
- # Apply topic modeling
235
- if method == "nmf":
236
- # Non-negative Matrix Factorization
237
- model = NMF(n_components=n_topics, random_state=42, max_iter=500,
238
- alpha=0.1, l1_ratio=0.5)
239
- else:
240
- # Latent Dirichlet Allocation with better hyperparameters
241
- model = LatentDirichletAllocation(
242
- n_components=n_topics,
243
- random_state=42,
244
- max_iter=30,
245
- learning_method='online',
246
- learning_offset=50.0,
247
- doc_topic_prior=0.1,
248
- topic_word_prior=0.01
249
- )
250
-
251
- topic_distribution = model.fit_transform(X)
252
-
253
- # Get top words for each topic
254
- result["topics"] = get_top_words_per_topic(model, feature_names, n_top_words)
255
-
256
- # Get topic distribution for each document
257
- for i, dist in enumerate(topic_distribution):
258
- # Normalize for easier comparison
259
- normalized_dist = dist / np.sum(dist) if np.sum(dist) > 0 else dist
260
- result["document_topics"].append({
261
- "document_id": i,
262
- "distribution": normalized_dist.tolist()
263
- })
264
-
265
- # Calculate coherence score
266
- result["coherence_score"] = get_coherence_score(model, feature_names, X)
267
-
268
- # Calculate topic diversity
269
- result["diversity_score"] = calculate_topic_diversity(result["topics"])
270
-
271
- return result
272
- except Exception as e:
273
- import traceback
274
- result["error"] = str(e)
275
- result["traceback"] = traceback.format_exc()
276
- return result
277
-
278
- def calculate_js_divergence(p, q):
279
- """
280
- Calculate Jensen-Shannon divergence between two distributions
281
-
282
- Args:
283
- p (list): First probability distribution
284
- q (list): Second probability distribution
285
-
286
- Returns:
287
- float: JS divergence (0-1, lower means more similar)
288
- """
289
- # Convert to numpy arrays
290
- p = np.array(p)
291
- q = np.array(q)
292
-
293
- # Convert to proper probability distributions
294
- p = p / np.sum(p) if np.sum(p) > 0 else p
295
- q = q / np.sum(q) if np.sum(q) > 0 else q
296
-
297
- # Calculate JS divergence
298
- m = (p + q) / 2
299
-
300
- # Handle potential errors
301
- kl_pm = 0
302
- for pi, mi in zip(p, m):
303
- if pi > 0 and mi > 0:
304
- kl_pm += pi * np.log2(pi / mi)
305
-
306
- kl_qm = 0
307
- for qi, mi in zip(q, m):
308
- if qi > 0 and mi > 0:
309
- kl_qm += qi * np.log2(qi / mi)
310
 
311
- js_divergence = (kl_pm + kl_qm) / 2
312
- return js_divergence
313
 
314
  def compare_topics(texts_set_1, texts_set_2, n_topics=3, n_top_words=10, method="lda", model_names=None):
315
  """
316
- Compare topics between two sets of texts with enhanced metrics
317
 
318
  Args:
319
  texts_set_1 (list): First list of text documents
@@ -330,35 +136,10 @@ def compare_topics(texts_set_1, texts_set_2, n_topics=3, n_top_words=10, method=
330
  if model_names is None:
331
  model_names = ["Model 1", "Model 2"]
332
 
333
- # Handle case where both sets are the same (e.g., comparing same document against itself)
334
- if texts_set_1 == texts_set_2:
335
- texts_set_2 = texts_set_2.copy() # Create a copy to avoid reference issues
336
-
337
- # Extract topics for each set individually
338
  topics_set_1 = extract_topics(texts_set_1, n_topics, n_top_words, method)
339
  topics_set_2 = extract_topics(texts_set_2, n_topics, n_top_words, method)
340
 
341
- # Extract topics for combined set (for a common topic space)
342
- combined_texts = texts_set_1 + texts_set_2
343
- combined_topics = extract_topics(combined_texts, n_topics, n_top_words, method)
344
-
345
- # Check for errors
346
- if "error" in topics_set_1 or "error" in topics_set_2 or "error" in combined_topics:
347
- errors = []
348
- if "error" in topics_set_1:
349
- errors.append(f"Error in set 1: {topics_set_1['error']}")
350
- if "error" in topics_set_2:
351
- errors.append(f"Error in set 2: {topics_set_2['error']}")
352
- if "error" in combined_topics:
353
- errors.append(f"Error in combined set: {combined_topics['error']}")
354
-
355
- return {
356
- "error": " | ".join(errors),
357
- "method": method,
358
- "n_topics": n_topics,
359
- "models": model_names
360
- }
361
-
362
  # Calculate similarity between topics
363
  similarity_matrix = []
364
  for topic1 in topics_set_1["topics"]:
@@ -385,72 +166,16 @@ def compare_topics(texts_set_1, texts_set_2, n_topics=3, n_top_words=10, method=
385
  "similarity": similarities[best_match_idx]
386
  })
387
 
388
- # Calculate topic distribution differences
389
- topic_differences = []
390
- if (len(topics_set_1["document_topics"]) > 0 and
391
- len(topics_set_2["document_topics"]) > 0):
392
-
393
- # Get average topic distribution for each set
394
- dist1 = np.mean([doc["distribution"] for doc in topics_set_1["document_topics"]], axis=0)
395
- dist2 = np.mean([doc["distribution"] for doc in topics_set_2["document_topics"]], axis=0)
396
-
397
- for i in range(min(len(dist1), len(dist2))):
398
- topic_differences.append({
399
- "topic_id": i,
400
- "model1_weight": float(dist1[i]),
401
- "model2_weight": float(dist2[i]),
402
- "difference": float(abs(dist1[i] - dist2[i]))
403
- })
404
-
405
- # Calculate Jensen-Shannon Divergence
406
- js_divergence = 0
407
- if (len(topics_set_1["document_topics"]) > 0 and
408
- len(topics_set_2["document_topics"]) > 0):
409
-
410
- # Get topic distributions
411
- dist1 = topics_set_1["document_topics"][0]["distribution"]
412
- dist2 = topics_set_2["document_topics"][0]["distribution"]
413
-
414
- # Calculate JS divergence
415
- js_divergence = calculate_js_divergence(dist1, dist2)
416
-
417
  # Construct result
418
  result = {
419
  "method": method,
420
  "n_topics": n_topics,
421
- "models": model_names,
422
  "set1_topics": topics_set_1["topics"],
423
  "set2_topics": topics_set_2["topics"],
424
- "combined_topics": combined_topics["topics"],
425
  "similarity_matrix": similarity_matrix,
426
  "matched_topics": matched_topics,
427
  "average_similarity": np.mean([match["similarity"] for match in matched_topics]),
428
- "topic_differences": topic_differences,
429
- "js_divergence": js_divergence,
430
- "model_topics": {
431
- model_names[0]: topics_set_1["document_topics"][0]["distribution"] if topics_set_1["document_topics"] else [],
432
- model_names[1]: topics_set_2["document_topics"][0]["distribution"] if topics_set_2["document_topics"] else []
433
- },
434
- "comparisons": {
435
- f"{model_names[0]} vs {model_names[1]}": {
436
- "js_divergence": js_divergence,
437
- "topic_differences": topic_differences,
438
- "average_topic_similarity": np.mean([match["similarity"] for match in matched_topics])
439
- }
440
- }
441
- }
442
-
443
- # Add coherence and diversity scores
444
- result["coherence_scores"] = {
445
- model_names[0]: topics_set_1.get("coherence_score", 0),
446
- model_names[1]: topics_set_2.get("coherence_score", 0),
447
- "combined": combined_topics.get("coherence_score", 0)
448
- }
449
-
450
- result["diversity_scores"] = {
451
- model_names[0]: topics_set_1.get("diversity_score", 0),
452
- model_names[1]: topics_set_2.get("diversity_score", 0),
453
- "combined": combined_topics.get("diversity_score", 0)
454
  }
455
 
456
  return result
 
1
  """
2
+ Topic modeling processor for comparing text responses
3
  """
4
  from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
5
  from sklearn.decomposition import LatentDirichletAllocation, NMF
6
  import numpy as np
7
  import nltk
8
  from nltk.corpus import stopwords
 
9
  import re
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def preprocess_text(text):
12
  """
13
+ Preprocess text for topic modeling
14
 
15
  Args:
16
  text (str): Text to preprocess
 
29
 
30
  # Remove stopwords
31
  stop_words = set(stopwords.words('english'))
32
+ tokens = [token for token in tokens if token not in stop_words and len(token) > 3]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  return ' '.join(tokens)
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  def get_top_words_per_topic(model, feature_names, n_top_words=10):
37
  """
38
+ Get the top words for each topic in the model
39
 
40
  Args:
41
  model: Topic model (LDA or NMF)
 
49
  for topic_idx, topic in enumerate(model.components_):
50
  top_words_idx = topic.argsort()[:-n_top_words - 1:-1]
51
  top_words = [feature_names[i] for i in top_words_idx]
 
 
 
 
 
 
 
 
 
52
  topic_dict = {
53
  "id": topic_idx,
54
  "words": top_words,
55
+ "weights": topic[top_words_idx].tolist()
 
56
  }
57
  topics.append(topic_dict)
58
  return topics
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  def extract_topics(texts, n_topics=3, n_top_words=10, method="lda"):
61
  """
62
+ Extract topics from a list of texts
63
 
64
  Args:
65
  texts (list): List of text documents
 
77
  "document_topics": []
78
  }
79
 
 
 
 
 
 
80
  # Preprocess texts
81
  preprocessed_texts = [preprocess_text(text) for text in texts]
82
 
83
+ # Create document-term matrix
84
+ if method == "nmf":
85
+ # For NMF, use TF-IDF vectorization
86
+ # Adjust min_df and max_df for small document sets
87
+ vectorizer = TfidfVectorizer(max_features=1000, min_df=1, max_df=1.0)
88
+ else:
89
+ # For LDA, use CountVectorizer
90
+ # Adjust min_df and max_df for small document sets
91
+ vectorizer = CountVectorizer(max_features=1000, min_df=1, max_df=1.0)
92
+
93
+ X = vectorizer.fit_transform(preprocessed_texts)
94
+ feature_names = vectorizer.get_feature_names_out()
95
+
96
+ # Apply topic modeling
97
+ if method == "nmf":
98
+ # Non-negative Matrix Factorization
99
+ model = NMF(n_components=n_topics, random_state=42, max_iter=1000)
100
+ else:
101
+ # Latent Dirichlet Allocation
102
+ model = LatentDirichletAllocation(n_components=n_topics, random_state=42, max_iter=20)
103
+
104
+ topic_distribution = model.fit_transform(X)
105
+
106
+ # Get top words for each topic
107
+ result["topics"] = get_top_words_per_topic(model, feature_names, n_top_words)
108
+
109
+ # Get topic distribution for each document
110
+ for i, dist in enumerate(topic_distribution):
111
+ # Normalize for easier comparison
112
+ normalized_dist = dist / np.sum(dist) if np.sum(dist) > 0 else dist
113
+ result["document_topics"].append({
114
+ "document_id": i,
115
+ "distribution": normalized_dist.tolist()
116
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ return result
 
119
 
120
  def compare_topics(texts_set_1, texts_set_2, n_topics=3, n_top_words=10, method="lda", model_names=None):
121
  """
122
+ Compare topics between two sets of texts
123
 
124
  Args:
125
  texts_set_1 (list): First list of text documents
 
136
  if model_names is None:
137
  model_names = ["Model 1", "Model 2"]
138
 
139
+ # Extract topics for each set
 
 
 
 
140
  topics_set_1 = extract_topics(texts_set_1, n_topics, n_top_words, method)
141
  topics_set_2 = extract_topics(texts_set_2, n_topics, n_top_words, method)
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  # Calculate similarity between topics
144
  similarity_matrix = []
145
  for topic1 in topics_set_1["topics"]:
 
166
  "similarity": similarities[best_match_idx]
167
  })
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  # Construct result
170
  result = {
171
  "method": method,
172
  "n_topics": n_topics,
 
173
  "set1_topics": topics_set_1["topics"],
174
  "set2_topics": topics_set_2["topics"],
 
175
  "similarity_matrix": similarity_matrix,
176
  "matched_topics": matched_topics,
177
  "average_similarity": np.mean([match["similarity"] for match in matched_topics]),
178
+ "models": model_names # Add model names to result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
180
 
181
  return result
ui/analysis_screen.py CHANGED
@@ -7,7 +7,123 @@ from processors.topic_modeling import compare_topics
7
  from processors.ngram_analysis import compare_ngrams
8
  from processors.bow_analysis import compare_bow
9
  from processors.text_classifiers import classify_formality, classify_sentiment, classify_complexity, compare_classifications
10
- from processors.bias_detection import compare_bias
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # Add the implementation of these helper functions
13
  def extract_important_words(text, top_n=20):
@@ -207,6 +323,7 @@ def perform_topic_modeling(texts, model_names, n_topics=3):
207
 
208
  return result
209
 
 
210
  def process_analysis_request(dataset, selected_analysis, parameters):
211
  """
212
  Process the analysis request based on the selected options.
@@ -250,7 +367,6 @@ def process_analysis_request(dataset, selected_analysis, parameters):
250
  print(f"Using top_n value: {top_n}") # Debug print
251
 
252
  # Perform Bag of Words analysis using the processor
253
- from processors.bow_analysis import compare_bow
254
  bow_results = compare_bow(
255
  [model1_response, model2_response],
256
  [model1_name, model2_name],
@@ -264,7 +380,7 @@ def process_analysis_request(dataset, selected_analysis, parameters):
264
  if isinstance(ngram_size, str):
265
  ngram_size = int(ngram_size)
266
 
267
- top_n = parameters.get("ngram_top", 10) # Using default 10
268
  if isinstance(top_n, str):
269
  top_n = int(top_n)
270
 
@@ -285,9 +401,6 @@ def process_analysis_request(dataset, selected_analysis, parameters):
285
  topic_count = int(topic_count)
286
 
287
  try:
288
- # Import the enhanced topic modeling function
289
- from processors.topic_modeling import compare_topics
290
-
291
  topic_results = compare_topics(
292
  texts_set_1=[model1_response],
293
  texts_set_2=[model2_response],
@@ -295,13 +408,6 @@ def process_analysis_request(dataset, selected_analysis, parameters):
295
  model_names=[model1_name, model2_name])
296
 
297
  results["analyses"][prompt_text]["topic_modeling"] = topic_results
298
-
299
- # Add helpful message if text is very short
300
- if (len(model1_response.split()) < 50 or len(model2_response.split()) < 50):
301
- if "error" not in topic_results:
302
- # Add a warning message about short text
303
- results["analyses"][prompt_text]["topic_modeling"]["warning"] = "One or both texts are relatively short. Topic modeling works best with longer texts."
304
-
305
  except Exception as e:
306
  import traceback
307
  print(f"Topic modeling error: {str(e)}\n{traceback.format_exc()}")
@@ -313,8 +419,6 @@ def process_analysis_request(dataset, selected_analysis, parameters):
313
 
314
  elif selected_analysis == "Classifier":
315
  # Perform classifier analysis
316
- from processors.text_classifiers import classify_formality, classify_sentiment, classify_complexity, compare_classifications
317
-
318
  results["analyses"][prompt_text]["classifier"] = {
319
  "models": [model1_name, model2_name],
320
  "classifications": {
@@ -331,28 +435,6 @@ def process_analysis_request(dataset, selected_analysis, parameters):
331
  },
332
  "differences": compare_classifications(model1_response, model2_response)
333
  }
334
-
335
- elif selected_analysis == "Bias Detection":
336
- try:
337
- # Perform bias detection analysis, always focusing on partisan leaning
338
- from processors.bias_detection import compare_bias
339
-
340
- bias_results = compare_bias(
341
- model1_response,
342
- model2_response,
343
- model_names=[model1_name, model2_name]
344
- )
345
-
346
- results["analyses"][prompt_text]["bias_detection"] = bias_results
347
-
348
- except Exception as e:
349
- import traceback
350
- print(f"Bias detection error: {str(e)}\n{traceback.format_exc()}")
351
- results["analyses"][prompt_text]["bias_detection"] = {
352
- "models": [model1_name, model2_name],
353
- "error": str(e),
354
- "message": "Bias detection failed. Try with different parameters."
355
- }
356
 
357
  else:
358
  # Unknown analysis type
@@ -360,110 +442,3 @@ def process_analysis_request(dataset, selected_analysis, parameters):
360
 
361
  # Return both the analysis results and a placeholder for visualization data
362
  return results, None
363
-
364
-
365
- def create_analysis_screen():
366
- """
367
- Create the analysis options screen with enhanced topic modeling options
368
-
369
- Returns:
370
- tuple: (analysis_options, analysis_params, run_analysis_btn, analysis_output, ngram_n, topic_count)
371
- """
372
- import gradio as gr
373
-
374
- with gr.Column() as analysis_screen:
375
- gr.Markdown("## Analysis Options")
376
- gr.Markdown("Select which analysis you want to run on the LLM responses.")
377
-
378
- # Change from CheckboxGroup to Radio for analysis selection
379
- with gr.Group():
380
- analysis_options = gr.Radio(
381
- choices=[
382
- "Bag of Words",
383
- "N-gram Analysis",
384
- "Topic Modeling",
385
- "Bias Detection",
386
- "Classifier"
387
- ],
388
- value="Bag of Words", # Default selection
389
- label="Select Analysis Type"
390
- )
391
-
392
- # Create N-gram parameters accessible at top level
393
- ngram_n = gr.Radio(
394
- choices=["1", "2", "3"], value="2",
395
- label="N-gram Size",
396
- visible=False
397
- )
398
-
399
- # Create enhanced topic modeling parameter accessible at top level
400
- topic_count = gr.Slider(
401
- minimum=2, maximum=10, value=3, step=1,
402
- label="Number of Topics",
403
- info="Choose fewer topics for shorter texts, more topics for longer texts",
404
- visible=False
405
- )
406
-
407
- # Parameters for each analysis type
408
- with gr.Group() as analysis_params:
409
- # Topic modeling parameters with enhanced options
410
- with gr.Group(visible=False) as topic_params:
411
- gr.Markdown("### Topic Modeling Parameters")
412
- gr.Markdown("""
413
- Topic modeling extracts thematic patterns from text.
414
-
415
- For best results:
416
- - Use longer text samples (100+ words)
417
- - Adjust topic count based on text length
418
- - For political content, 3-5 topics usually works well
419
- """)
420
- # We're already using topic_count defined above
421
-
422
- # N-gram parameters group (using external ngram_n)
423
- with gr.Group(visible=False) as ngram_params:
424
- gr.Markdown("### N-gram Parameters")
425
- # We're already using ngram_n defined above
426
-
427
- # Bias detection parameters
428
- with gr.Group(visible=False) as bias_params:
429
- gr.Markdown("### Bias Detection Parameters")
430
- gr.Markdown("Analysis will focus on detecting partisan leaning.")
431
-
432
- # Classifier parameters
433
- with gr.Group(visible=False) as classifier_params:
434
- gr.Markdown("### Classifier Parameters")
435
- gr.Markdown("Classifies responses based on formality, sentiment, and complexity")
436
-
437
- # Function to update parameter visibility based on selected analysis
438
- def update_params_visibility(selected):
439
- return {
440
- topic_params: gr.update(visible=selected == "Topic Modeling"),
441
- ngram_params: gr.update(visible=selected == "N-gram Analysis"),
442
- bias_params: gr.update(visible=selected == "Bias Detection"),
443
- classifier_params: gr.update(visible=selected == "Classifier"),
444
- ngram_n: gr.update(visible=selected == "N-gram Analysis"),
445
- topic_count: gr.update(visible=selected == "Topic Modeling")
446
- }
447
-
448
- # Set up event handler for analysis selection
449
- analysis_options.change(
450
- fn=update_params_visibility,
451
- inputs=[analysis_options],
452
- outputs=[
453
- topic_params,
454
- ngram_params,
455
- bias_params,
456
- classifier_params,
457
- ngram_n,
458
- topic_count
459
- ]
460
- )
461
-
462
- # Run analysis button
463
- run_analysis_btn = gr.Button("Run Analysis", variant="primary", size="large")
464
-
465
- # Analysis output area - hidden JSON component to store raw results
466
- analysis_output = gr.JSON(label="Analysis Results", visible=False)
467
-
468
- # Return the components needed by app.py
469
- return analysis_options, analysis_params, run_analysis_btn, analysis_output, ngram_n, topic_count
 
7
  from processors.ngram_analysis import compare_ngrams
8
  from processors.bow_analysis import compare_bow
9
  from processors.text_classifiers import classify_formality, classify_sentiment, classify_complexity, compare_classifications
10
+
11
+ def create_analysis_screen():
12
+ """
13
+ Create the analysis options screen
14
+
15
+ Returns:
16
+ tuple: (analysis_options, analysis_params, run_analysis_btn, analysis_output, bow_top_slider, ngram_n, ngram_top, topic_count)
17
+ """
18
+ with gr.Column() as analysis_screen:
19
+ gr.Markdown("## Analysis Options")
20
+ gr.Markdown("Select which analysis you want to run on the LLM responses.")
21
+
22
+ # Change from CheckboxGroup to Radio for analysis selection
23
+ with gr.Group():
24
+ analysis_options = gr.Radio(
25
+ choices=[
26
+ "Bag of Words",
27
+ "N-gram Analysis",
28
+ "Topic Modeling",
29
+ "Bias Detection",
30
+ "Classifier"
31
+ # Removed "LLM Analysis" as requested
32
+ ],
33
+ value="Bag of Words", # Default selection
34
+ label="Select Analysis Type"
35
+ )
36
+
37
+ # Create slider directly here for easier access
38
+ gr.Markdown("### Bag of Words Parameters")
39
+ bow_top_slider = gr.Slider(
40
+ minimum=10, maximum=100, value=25, step=5,
41
+ label="Top Words to Compare",
42
+ elem_id="bow_top_slider"
43
+ )
44
+
45
+ # Create N-gram parameters accessible at top level
46
+ ngram_n = gr.Radio(
47
+ choices=["1", "2", "3"], value="2",
48
+ label="N-gram Size",
49
+ visible=False
50
+ )
51
+ ngram_top = gr.Slider(
52
+ minimum=5, maximum=30, value=10, step=1,
53
+ label="Top N-grams to Display",
54
+ visible=False
55
+ )
56
+
57
+ # Create topic modeling parameter accessible at top level
58
+ topic_count = gr.Slider(
59
+ minimum=2, maximum=10, value=3, step=1,
60
+ label="Number of Topics",
61
+ visible=False
62
+ )
63
+
64
+ # Parameters for each analysis type
65
+ with gr.Group() as analysis_params:
66
+ # Topic modeling parameters
67
+ with gr.Group(visible=False) as topic_params:
68
+ gr.Markdown("### Topic Modeling Parameters")
69
+ # We'll use the topic_count defined above
70
+
71
+ # N-gram parameters group (using external ngram_n and ngram_top)
72
+ with gr.Group(visible=False) as ngram_params:
73
+ gr.Markdown("### N-gram Parameters")
74
+ # We're already using ngram_n and ngram_top defined above
75
+
76
+ # Bias detection parameters
77
+ with gr.Group(visible=False) as bias_params:
78
+ gr.Markdown("### Bias Detection Parameters")
79
+ bias_methods = gr.CheckboxGroup(
80
+ choices=["Sentiment Analysis", "Partisan Leaning", "Framing Analysis"],
81
+ value=["Sentiment Analysis", "Partisan Leaning"],
82
+ label="Bias Detection Methods"
83
+ )
84
+
85
+ # Classifier parameters
86
+ with gr.Group(visible=False) as classifier_params:
87
+ gr.Markdown("### Classifier Parameters")
88
+ gr.Markdown("Classifies responses based on formality, sentiment, and complexity")
89
+
90
+ # Function to update parameter visibility based on selected analysis
91
+ def update_params_visibility(selected):
92
+ return {
93
+ topic_params: gr.update(visible=selected == "Topic Modeling"),
94
+ ngram_params: gr.update(visible=selected == "N-gram Analysis"),
95
+ bias_params: gr.update(visible=selected == "Bias Detection"),
96
+ classifier_params: gr.update(visible=selected == "Classifier"),
97
+ ngram_n: gr.update(visible=selected == "N-gram Analysis"),
98
+ ngram_top: gr.update(visible=selected == "N-gram Analysis"),
99
+ topic_count: gr.update(visible=selected == "Topic Modeling"),
100
+ bow_top_slider: gr.update(visible=selected == "Bag of Words")
101
+ }
102
+
103
+ # Set up event handler for analysis selection
104
+ analysis_options.change(
105
+ fn=update_params_visibility,
106
+ inputs=[analysis_options],
107
+ outputs=[
108
+ topic_params,
109
+ ngram_params,
110
+ bias_params,
111
+ classifier_params,
112
+ ngram_n,
113
+ ngram_top,
114
+ topic_count,
115
+ bow_top_slider
116
+ ]
117
+ )
118
+
119
+ # Run analysis button
120
+ run_analysis_btn = gr.Button("Run Analysis", variant="primary", size="large")
121
+
122
+ # Analysis output area - hidden JSON component to store raw results
123
+ analysis_output = gr.JSON(label="Analysis Results", visible=False)
124
+
125
+ # Return the components needed by app.py
126
+ return analysis_options, analysis_params, run_analysis_btn, analysis_output, bow_top_slider, ngram_n, ngram_top, topic_count
127
 
128
  # Add the implementation of these helper functions
129
  def extract_important_words(text, top_n=20):
 
323
 
324
  return result
325
 
326
+ # Process analysis request function
327
  def process_analysis_request(dataset, selected_analysis, parameters):
328
  """
329
  Process the analysis request based on the selected options.
 
367
  print(f"Using top_n value: {top_n}") # Debug print
368
 
369
  # Perform Bag of Words analysis using the processor
 
370
  bow_results = compare_bow(
371
  [model1_response, model2_response],
372
  [model1_name, model2_name],
 
380
  if isinstance(ngram_size, str):
381
  ngram_size = int(ngram_size)
382
 
383
+ top_n = parameters.get("ngram_top", 15)
384
  if isinstance(top_n, str):
385
  top_n = int(top_n)
386
 
 
401
  topic_count = int(topic_count)
402
 
403
  try:
 
 
 
404
  topic_results = compare_topics(
405
  texts_set_1=[model1_response],
406
  texts_set_2=[model2_response],
 
408
  model_names=[model1_name, model2_name])
409
 
410
  results["analyses"][prompt_text]["topic_modeling"] = topic_results
 
 
 
 
 
 
 
411
  except Exception as e:
412
  import traceback
413
  print(f"Topic modeling error: {str(e)}\n{traceback.format_exc()}")
 
419
 
420
  elif selected_analysis == "Classifier":
421
  # Perform classifier analysis
 
 
422
  results["analyses"][prompt_text]["classifier"] = {
423
  "models": [model1_name, model2_name],
424
  "classifications": {
 
435
  },
436
  "differences": compare_classifications(model1_response, model2_response)
437
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
  else:
440
  # Unknown analysis type
 
442
 
443
  # Return both the analysis results and a placeholder for visualization data
444
  return results, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
visualization/__init__.py CHANGED
@@ -5,11 +5,9 @@ Visualization components for LLM Response Comparator
5
  from .bow_visualizer import process_and_visualize_analysis
6
  from .topic_visualizer import process_and_visualize_topic_analysis
7
  from .ngram_visualizer import process_and_visualize_ngram_analysis
8
- from .bias_visualizer import process_and_visualize_bias_analysis
9
 
10
  __all__ = [
11
  'process_and_visualize_analysis',
12
  'process_and_visualize_topic_analysis',
13
- 'process_and_visualize_ngram_analysis',
14
- 'process_and_visualize_bias_analysis'
15
- ]
 
5
  from .bow_visualizer import process_and_visualize_analysis
6
  from .topic_visualizer import process_and_visualize_topic_analysis
7
  from .ngram_visualizer import process_and_visualize_ngram_analysis
 
8
 
9
  __all__ = [
10
  'process_and_visualize_analysis',
11
  'process_and_visualize_topic_analysis',
12
+ 'process_and_visualize_ngram_analysis'
13
+ ]
 
visualization/bias_visualizer.py DELETED
@@ -1,233 +0,0 @@
1
- import gradio as gr
2
- import plotly.graph_objects as go
3
- import plotly.express as px
4
- import pandas as pd
5
-
6
- def create_bias_visualization(analysis_results):
7
- """
8
- Create visualizations for bias detection analysis results
9
-
10
- Args:
11
- analysis_results (dict): Analysis results from the bias detection
12
-
13
- Returns:
14
- list: List of gradio components with visualizations
15
- """
16
- output_components = []
17
-
18
- # Check if we have valid results
19
- if not analysis_results or "analyses" not in analysis_results:
20
- return [gr.Markdown("No analysis results found.")]
21
-
22
- # Process each prompt
23
- for prompt, analyses in analysis_results["analyses"].items():
24
- # Process Bias Detection analysis if available
25
- if "bias_detection" in analyses:
26
- bias_results = analyses["bias_detection"]
27
-
28
- # Show models being compared
29
- models = bias_results.get("models", [])
30
- if len(models) >= 2:
31
- output_components.append(gr.Markdown(f"### Bias Analysis: Comparing responses from {models[0]} and {models[1]}"))
32
-
33
- # Check if there's an error
34
- if "error" in bias_results:
35
- output_components.append(gr.Markdown(f"**Error in bias detection:** {bias_results['error']}"))
36
- continue
37
-
38
- model1_name, model2_name = models[0], models[1]
39
-
40
- # Comparative results
41
- if "comparative" in bias_results:
42
- comparative = bias_results["comparative"]
43
-
44
- output_components.append(gr.Markdown("#### Comparative Bias Analysis"))
45
-
46
- # Create summary table
47
- summary_html = f"""
48
- <table style="width:100%; border-collapse: collapse; margin-bottom: 20px;">
49
- <tr>
50
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #f2f2f2;">Bias Category</th>
51
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #f2f2f2;">{model1_name}</th>
52
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #f2f2f2;">{model2_name}</th>
53
- <th style="border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #f2f2f2;">Significant Difference?</th>
54
- </tr>
55
- """
56
-
57
- # Sentiment row
58
- if "sentiment" in comparative:
59
- sent_sig = comparative["sentiment"].get("significant", False)
60
- summary_html += f"""
61
- <tr>
62
- <td style="border: 1px solid #ddd; padding: 8px;">Sentiment Bias</td>
63
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["sentiment"].get(model1_name, "N/A").title()}</td>
64
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["sentiment"].get(model2_name, "N/A").title()}</td>
65
- <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold; color: {'red' if sent_sig else 'green'}">{"Yes" if sent_sig else "No"}</td>
66
- </tr>
67
- """
68
-
69
- # Partisan row
70
- if "partisan" in comparative:
71
- part_sig = comparative["partisan"].get("significant", False)
72
- summary_html += f"""
73
- <tr>
74
- <td style="border: 1px solid #ddd; padding: 8px;">Partisan Leaning</td>
75
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["partisan"].get(model1_name, "N/A").title()}</td>
76
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["partisan"].get(model2_name, "N/A").title()}</td>
77
- <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold; color: {'red' if part_sig else 'green'}">{"Yes" if part_sig else "No"}</td>
78
- </tr>
79
- """
80
-
81
- # Framing row
82
- if "framing" in comparative:
83
- frame_diff = comparative["framing"].get("different_frames", False)
84
- summary_html += f"""
85
- <tr>
86
- <td style="border: 1px solid #ddd; padding: 8px;">Dominant Frame</td>
87
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["framing"].get(model1_name, "N/A").title().replace('_', ' ')}</td>
88
- <td style="border: 1px solid #ddd; padding: 8px;">{comparative["framing"].get(model2_name, "N/A").title().replace('_', ' ')}</td>
89
- <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold; color: {'red' if frame_diff else 'green'}">{"Yes" if frame_diff else "No"}</td>
90
- </tr>
91
- """
92
-
93
- # Overall row
94
- if "overall" in comparative:
95
- overall_sig = comparative["overall"].get("significant_bias_difference", False)
96
- summary_html += f"""
97
- <tr>
98
- <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold;">Overall Bias Difference</td>
99
- <td colspan="2" style="border: 1px solid #ddd; padding: 8px; text-align: center;">{comparative["overall"].get("difference", 0):.2f} / 1.0</td>
100
- <td style="border: 1px solid #ddd; padding: 8px; font-weight: bold; color: {'red' if overall_sig else 'green'}">{"Yes" if overall_sig else "No"}</td>
101
- </tr>
102
- """
103
-
104
- summary_html += "</table>"
105
-
106
- # Add the HTML table to the components
107
- output_components.append(gr.HTML(summary_html))
108
-
109
- # Create detailed visualizations for each model if available
110
- for model_name in [model1_name, model2_name]:
111
- if model_name in bias_results:
112
- model_data = bias_results[model_name]
113
-
114
- # Sentiment visualization
115
- if "sentiment" in model_data:
116
- sentiment = model_data["sentiment"]
117
- if "sentiment_scores" in sentiment:
118
- # Create sentiment score chart
119
- sentiment_df = pd.DataFrame({
120
- 'Score': [
121
- sentiment["sentiment_scores"]["pos"],
122
- sentiment["sentiment_scores"]["neg"],
123
- sentiment["sentiment_scores"]["neu"]
124
- ],
125
- 'Category': ['Positive', 'Negative', 'Neutral']
126
- })
127
-
128
- fig = px.bar(
129
- sentiment_df,
130
- x='Category',
131
- y='Score',
132
- title=f"Sentiment Analysis for {model_name}",
133
- height=300
134
- )
135
-
136
- output_components.append(gr.Plot(value=fig))
137
-
138
- # Partisan leaning visualization
139
- if "partisan" in model_data:
140
- partisan = model_data["partisan"]
141
- if "liberal_count" in partisan and "conservative_count" in partisan:
142
- # Create partisan terms chart
143
- partisan_df = pd.DataFrame({
144
- 'Count': [partisan["liberal_count"], partisan["conservative_count"]],
145
- 'Category': ['Liberal Terms', 'Conservative Terms']
146
- })
147
-
148
- fig = px.bar(
149
- partisan_df,
150
- x='Category',
151
- y='Count',
152
- title=f"Partisan Term Usage for {model_name}",
153
- color='Category',
154
- color_discrete_map={
155
- 'Liberal Terms': 'blue',
156
- 'Conservative Terms': 'red'
157
- },
158
- height=300
159
- )
160
-
161
- output_components.append(gr.Plot(value=fig))
162
-
163
- # Show example partisan terms
164
- if "liberal_terms" in partisan or "conservative_terms" in partisan:
165
- lib_terms = ", ".join(partisan.get("liberal_terms", []))
166
- con_terms = ", ".join(partisan.get("conservative_terms", []))
167
-
168
- if lib_terms or con_terms:
169
- terms_md = f"**Partisan Terms Used by {model_name}**\n\n"
170
- if lib_terms:
171
- terms_md += f"- Liberal terms: {lib_terms}\n"
172
- if con_terms:
173
- terms_md += f"- Conservative terms: {con_terms}\n"
174
-
175
- output_components.append(gr.Markdown(terms_md))
176
-
177
- # Framing visualization
178
- if "framing" in model_data:
179
- framing = model_data["framing"]
180
- if "framing_distribution" in framing:
181
- # Create framing distribution chart
182
- frame_items = []
183
- for frame, value in framing["framing_distribution"].items():
184
- frame_items.append({
185
- 'Frame': frame.replace('_', ' ').title(),
186
- 'Proportion': value
187
- })
188
-
189
- frame_df = pd.DataFrame(frame_items)
190
-
191
- fig = px.pie(
192
- frame_df,
193
- values='Proportion',
194
- names='Frame',
195
- title=f"Issue Framing Distribution for {model_name}",
196
- height=400
197
- )
198
-
199
- output_components.append(gr.Plot(value=fig))
200
-
201
- # Show example framing terms
202
- if "framing_examples" in framing:
203
- examples_md = f"**Example Framing Terms Used by {model_name}**\n\n"
204
- for frame, examples in framing["framing_examples"].items():
205
- if examples:
206
- examples_md += f"- {frame.replace('_', ' ').title()}: {', '.join(examples)}\n"
207
-
208
- output_components.append(gr.Markdown(examples_md))
209
-
210
- # If no components were added, show a message
211
- if len(output_components) <= 1:
212
- output_components.append(gr.Markdown("No detailed bias detection analysis found in results."))
213
-
214
- return output_components
215
-
216
- def process_and_visualize_bias_analysis(analysis_results):
217
- """
218
- Process the bias detection analysis results and create visualization components
219
-
220
- Args:
221
- analysis_results (dict): The analysis results
222
-
223
- Returns:
224
- list: List of gradio components for visualization
225
- """
226
- try:
227
- print(f"Starting visualization of bias detection analysis results")
228
- return create_bias_visualization(analysis_results)
229
- except Exception as e:
230
- import traceback
231
- error_msg = f"Bias detection visualization error: {str(e)}\n{traceback.format_exc()}"
232
- print(error_msg)
233
- return [gr.Markdown(f"**Error during bias detection visualization:**\n\n```\n{error_msg}\n```")]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
visualization/topic_visualizer.py CHANGED
@@ -1,16 +1,18 @@
1
  """
2
- Enhanced visualization for topic modeling analysis results
3
  """
 
4
  import gradio as gr
 
 
5
  import pandas as pd
6
  import plotly.express as px
7
  import plotly.graph_objects as go
8
  from plotly.subplots import make_subplots
9
- import numpy as np
10
 
11
  def create_topic_visualization(analysis_results):
12
  """
13
- Create enhanced visualizations for topic modeling analysis results
14
 
15
  Args:
16
  analysis_results (dict): Analysis results from the topic modeling analysis
@@ -31,127 +33,27 @@ def create_topic_visualization(analysis_results):
31
  if "topic_modeling" in analyses:
32
  topic_results = analyses["topic_modeling"]
33
 
34
- # Check for errors first
35
- if "error" in topic_results:
36
- output_components.append(gr.Markdown(f"## ⚠️ Topic Modeling Error"))
37
- output_components.append(gr.Markdown(f"Error: {topic_results['error']}"))
38
- output_components.append(gr.Markdown("Try adjusting the number of topics or using longer text samples."))
39
- continue
40
-
41
  # Show method and number of topics
42
  method = topic_results.get("method", "lda").upper()
43
  n_topics = topic_results.get("n_topics", 3)
44
-
45
- # Check if n_topics was adjusted
46
- if "adjusted_n_topics" in topic_results and topic_results["adjusted_n_topics"] != topic_results.get("original_n_topics", n_topics):
47
- output_components.append(gr.Markdown(
48
- f"## Topic Modeling Analysis ({method}, {topic_results['adjusted_n_topics']} topics) " +
49
- f"*Adjusted from {topic_results['original_n_topics']} due to limited text content*"
50
- ))
51
- n_topics = topic_results["adjusted_n_topics"]
52
- else:
53
- output_components.append(gr.Markdown(f"## Topic Modeling Analysis ({method}, {n_topics} topics)"))
54
 
55
  # Show models being compared
56
  models = topic_results.get("models", [])
57
  if len(models) >= 2:
58
  output_components.append(gr.Markdown(f"### Comparing responses from {models[0]} and {models[1]}"))
59
 
60
- # Show topic quality metrics if available
61
- if "coherence_scores" in topic_results:
62
- coherence_html = f"""
63
- <div style="margin: 20px 0; padding: 15px; background-color: #f8f9fa; border-radius: 5px;">
64
- <h4 style="margin-top: 0;">Topic Quality Metrics</h4>
65
- <table style="width: 100%; border-collapse: collapse;">
66
- <tr>
67
- <th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Metric</th>
68
- <th style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">{models[0]}</th>
69
- <th style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">{models[1]}</th>
70
- <th style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">Combined</th>
71
- </tr>
72
- <tr>
73
- <td style="padding: 8px; border-bottom: 1px solid #ddd;">Topic Coherence</td>
74
- <td style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">
75
- {topic_results["coherence_scores"].get(models[0], 0):.2f}
76
- </td>
77
- <td style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">
78
- {topic_results["coherence_scores"].get(models[1], 0):.2f}
79
- </td>
80
- <td style="text-align: center; padding: 8px; border-bottom: 1px solid #ddd;">
81
- {topic_results["coherence_scores"].get("combined", 0):.2f}
82
- </td>
83
- </tr>
84
- <tr>
85
- <td style="padding: 8px;">Topic Diversity</td>
86
- <td style="text-align: center; padding: 8px;">
87
- {topic_results["diversity_scores"].get(models[0], 0):.2f}
88
- </td>
89
- <td style="text-align: center; padding: 8px;">
90
- {topic_results["diversity_scores"].get(models[1], 0):.2f}
91
- </td>
92
- <td style="text-align: center; padding: 8px;">
93
- {topic_results["diversity_scores"].get("combined", 0):.2f}
94
- </td>
95
- </tr>
96
- </table>
97
- <p style="margin-bottom: 0; font-size: 0.9em; color: #666;">
98
- Higher coherence scores indicate more semantically coherent topics.<br>
99
- Higher diversity scores indicate less overlap between topics.
100
- </p>
101
- </div>
102
- """
103
- output_components.append(gr.HTML(coherence_html))
104
-
105
  # Visualize topics
106
  topics = topic_results.get("topics", [])
107
  if topics:
108
  output_components.append(gr.Markdown("### Discovered Topics"))
109
 
110
- # Create a topic word cloud using HTML/CSS for better visibility
111
  for topic in topics:
112
  topic_id = topic.get("id", 0)
113
  words = topic.get("words", [])
114
  weights = topic.get("weights", [])
115
 
116
- if words and weights and len(words) == len(weights):
117
- # Generate a word cloud-like div using HTML/CSS
118
- word_cloud_html = f"""
119
- <div style="margin-bottom: 25px;">
120
- <h4 style="margin-bottom: 10px;">Topic {topic_id+1}</h4>
121
- <div style="display: flex; flex-wrap: wrap; gap: 10px; background: #f9f9f9; padding: 15px; border-radius: 5px;">
122
- """
123
-
124
- # Sort words by weight for better visualization
125
- word_weight_pairs = sorted(zip(words, weights), key=lambda x: x[1], reverse=True)
126
-
127
- # Add each word with size based on weight
128
- for word, weight in word_weight_pairs:
129
- # Scale weight to a reasonable font size (min 14px, max 28px)
130
- font_size = 14 + min(14, round(weight * 30))
131
- # Color based on weight (darker = higher weight)
132
- color_intensity = max(0, min(90, int(100 - weight * 100)))
133
- color = f"hsl(210, 70%, {color_intensity}%)"
134
-
135
- word_cloud_html += f"""
136
- <span style="font-size: {font_size}px; color: {color}; margin: 3px;
137
- padding: 5px; border-radius: 3px; background: rgba(0,0,0,0.03);">
138
- {word}
139
- </span>
140
- """
141
-
142
- word_cloud_html += """
143
- </div>
144
- </div>
145
- """
146
-
147
- output_components.append(gr.HTML(word_cloud_html))
148
-
149
- # Add a proper bar chart visualization for topic words
150
- for topic in topics[:min(3, len(topics))]: # Show charts for max 3 topics to avoid clutter
151
- topic_id = topic.get("id", 0)
152
- words = topic.get("words", [])
153
- weights = topic.get("weights", [])
154
-
155
  if words and weights and len(words) == len(weights):
156
  # Create dataframe for plotting
157
  df = pd.DataFrame({
@@ -162,22 +64,12 @@ def create_topic_visualization(analysis_results):
162
  # Sort by weight
163
  df = df.sort_values('weight', ascending=False)
164
 
165
- # Limit to top N words for clarity
166
- df = df.head(10)
167
-
168
  # Create bar chart
169
  fig = px.bar(
170
- df, x='weight', y='word',
171
  title=f"Topic {topic_id+1} Top Words",
172
  labels={'word': 'Word', 'weight': 'Weight'},
173
- height=300,
174
- orientation='h' # Horizontal bars
175
- )
176
-
177
- # Improve layout
178
- fig.update_layout(
179
- margin=dict(l=10, r=10, t=40, b=10),
180
- yaxis={'categoryorder': 'total ascending'}
181
  )
182
 
183
  output_components.append(gr.Plot(value=fig))
@@ -188,135 +80,66 @@ def create_topic_visualization(analysis_results):
188
  output_components.append(gr.Markdown("### Topic Distribution by Model"))
189
 
190
  # Create multi-model topic distribution comparison
191
- distribution_data = []
192
  for model in models:
193
  if model in model_topics:
194
  distribution = model_topics[model]
195
- for i, weight in enumerate(distribution):
196
- distribution_data.append({
197
- 'Model': model,
198
- 'Topic': f"Topic {i+1}",
199
- 'Weight': weight
200
- })
201
 
202
- if distribution_data:
203
- df = pd.DataFrame(distribution_data)
204
-
205
- # Create grouped bar chart
206
- fig = px.bar(
207
- df, x='Topic', y='Weight', color='Model',
208
- barmode='group',
209
- title="Topic Distribution Comparison",
210
- height=400
211
- )
212
-
213
- output_components.append(gr.Plot(value=fig))
214
 
215
- # Visualize topic differences as a heatmap
216
  comparisons = topic_results.get("comparisons", {})
217
  if comparisons:
218
- comparison_key = f"{models[0]} vs {models[1]}"
219
- if comparison_key in comparisons:
220
- output_components.append(gr.Markdown("### Topic Similarity Analysis"))
221
-
222
- # Get JS divergence
223
- js_divergence = comparisons[comparison_key].get("js_divergence", 0)
224
-
225
- # Create a divergence meter
226
- divergence_html = f"""
227
- <div style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border-radius: 5px; text-align: center;">
228
- <h4 style="margin-top: 0;">Topic Distribution Divergence</h4>
229
- <div style="display: flex; align-items: center; justify-content: center;">
230
- <div style="width: 300px; height: 40px; background: linear-gradient(to right, #1a9850, #ffffbf, #d73027); border-radius: 5px; position: relative; margin: 10px 0;">
231
- <div style="position: absolute; height: 40px; width: 2px; background-color: #000; left: {min(300, max(0, js_divergence * 300))}px;"></div>
232
- </div>
233
- </div>
234
- <div style="display: flex; justify-content: space-between; width: 300px; margin: 0 auto;">
235
- <span>Similar (0.0)</span>
236
- <span>Different (1.0)</span>
237
- </div>
238
- <p style="margin-top: 10px; font-weight: bold;">Score: {js_divergence:.3f}</p>
239
- <p style="margin-bottom: 0; font-size: 0.9em; color: #666;">
240
- Jensen-Shannon Divergence measures the similarity between topic distributions.<br>
241
- Lower values indicate more similar topic distributions between models.
242
- </p>
243
- </div>
244
- """
245
 
246
- output_components.append(gr.HTML(divergence_html))
 
 
247
 
248
- # Create similarity matrix heatmap if available
249
- similarity_matrix = topic_results.get("similarity_matrix", [])
250
- if similarity_matrix and len(similarity_matrix) > 0:
251
- # Convert to format for heatmap
252
- z_data = similarity_matrix
253
 
254
- # Create heatmap
255
- fig = go.Figure(data=go.Heatmap(
256
- z=z_data,
257
- x=[f"{models[1]} Topic {i+1}" for i in range(len(similarity_matrix[0]))],
258
- y=[f"{models[0]} Topic {i+1}" for i in range(len(similarity_matrix))],
259
- colorscale='Viridis',
260
- showscale=True,
261
- colorbar=dict(title="Similarity")
 
 
 
262
  ))
263
 
264
  fig.update_layout(
265
- title="Topic Similarity Matrix",
266
- height=400,
267
- margin=dict(l=50, r=50, t=50, b=50)
 
 
268
  )
269
 
270
  output_components.append(gr.Plot(value=fig))
271
-
272
- # Show best matching topics
273
- matched_topics = topic_results.get("matched_topics", [])
274
- if matched_topics:
275
- output_components.append(gr.Markdown("### Most Similar Topic Pairs"))
276
-
277
- # Create HTML table for matched topics
278
- matched_topics_html = """
279
- <div style="margin: 20px 0;">
280
- <table style="width: 100%; border-collapse: collapse;">
281
- <tr>
282
- <th style="padding: 8px; border-bottom: 2px solid #ddd; text-align: left;">Topic Pair</th>
283
- <th style="padding: 8px; border-bottom: 2px solid #ddd; text-align: left;">Top Words in Model 1</th>
284
- <th style="padding: 8px; border-bottom: 2px solid #ddd; text-align: left;">Top Words in Model 2</th>
285
- <th style="padding: 8px; border-bottom: 2px solid #ddd; text-align: center;">Similarity</th>
286
- </tr>
287
- """
288
-
289
- # Sort by similarity, highest first
290
- sorted_matches = sorted(matched_topics, key=lambda x: x['similarity'], reverse=True)
291
-
292
- for match in sorted_matches:
293
- # Format words with commas
294
- words1 = ", ".join(match["set1_topic_words"][:5]) # Show top 5 words
295
- words2 = ", ".join(match["set2_topic_words"][:5]) # Show top 5 words
296
-
297
- # Calculate color based on similarity (green for high, red for low)
298
- similarity = match["similarity"]
299
- color = f"hsl({int(120 * similarity)}, 70%, 50%)"
300
-
301
- matched_topics_html += f"""
302
- <tr>
303
- <td style="padding: 8px; border-bottom: 1px solid #ddd;">
304
- {models[0]} Topic {match['set1_topic_id']+1} ↔ {models[1]} Topic {match['set2_topic_id']+1}
305
- </td>
306
- <td style="padding: 8px; border-bottom: 1px solid #ddd;">{words1}</td>
307
- <td style="padding: 8px; border-bottom: 1px solid #ddd;">{words2}</td>
308
- <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: center; font-weight: bold; color: {color};">
309
- {similarity:.2f}
310
- </td>
311
- </tr>
312
- """
313
-
314
- matched_topics_html += """
315
- </table>
316
- </div>
317
- """
318
-
319
- output_components.append(gr.HTML(matched_topics_html))
320
 
321
  # If no components were added, show a message
322
  if len(output_components) <= 1:
@@ -337,15 +160,9 @@ def process_and_visualize_topic_analysis(analysis_results):
337
  """
338
  try:
339
  print(f"Starting visualization of topic modeling analysis results")
340
- components = create_topic_visualization(analysis_results)
341
- print(f"Completed topic modeling visualization with {len(components)} components")
342
- return components
343
  except Exception as e:
344
  import traceback
345
  error_msg = f"Topic modeling visualization error: {str(e)}\n{traceback.format_exc()}"
346
  print(error_msg)
347
- return [
348
- gr.Markdown(f"**Error during topic modeling visualization:**"),
349
- gr.Markdown(f"```\n{str(e)}\n```"),
350
- gr.Markdown("Try adjusting the number of topics or using longer text inputs.")
351
- ]
 
1
  """
2
+ Visualization for topic modeling analysis results
3
  """
4
+ from visualization.ngram_visualizer import create_ngram_visualization
5
  import gradio as gr
6
+ import json
7
+ import numpy as np
8
  import pandas as pd
9
  import plotly.express as px
10
  import plotly.graph_objects as go
11
  from plotly.subplots import make_subplots
 
12
 
13
  def create_topic_visualization(analysis_results):
14
  """
15
+ Create visualizations for topic modeling analysis results
16
 
17
  Args:
18
  analysis_results (dict): Analysis results from the topic modeling analysis
 
33
  if "topic_modeling" in analyses:
34
  topic_results = analyses["topic_modeling"]
35
 
 
 
 
 
 
 
 
36
  # Show method and number of topics
37
  method = topic_results.get("method", "lda").upper()
38
  n_topics = topic_results.get("n_topics", 3)
39
+ output_components.append(gr.Markdown(f"## Topic Modeling Analysis ({method}, {n_topics} topics)"))
 
 
 
 
 
 
 
 
 
40
 
41
  # Show models being compared
42
  models = topic_results.get("models", [])
43
  if len(models) >= 2:
44
  output_components.append(gr.Markdown(f"### Comparing responses from {models[0]} and {models[1]}"))
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  # Visualize topics
47
  topics = topic_results.get("topics", [])
48
  if topics:
49
  output_components.append(gr.Markdown("### Discovered Topics"))
50
 
 
51
  for topic in topics:
52
  topic_id = topic.get("id", 0)
53
  words = topic.get("words", [])
54
  weights = topic.get("weights", [])
55
 
56
+ # Create topic word bar chart
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  if words and weights and len(words) == len(weights):
58
  # Create dataframe for plotting
59
  df = pd.DataFrame({
 
64
  # Sort by weight
65
  df = df.sort_values('weight', ascending=False)
66
 
 
 
 
67
  # Create bar chart
68
  fig = px.bar(
69
+ df, x='word', y='weight',
70
  title=f"Topic {topic_id+1} Top Words",
71
  labels={'word': 'Word', 'weight': 'Weight'},
72
+ height=300
 
 
 
 
 
 
 
73
  )
74
 
75
  output_components.append(gr.Plot(value=fig))
 
80
  output_components.append(gr.Markdown("### Topic Distribution by Model"))
81
 
82
  # Create multi-model topic distribution comparison
83
+ fig = go.Figure()
84
  for model in models:
85
  if model in model_topics:
86
  distribution = model_topics[model]
87
+ fig.add_trace(go.Bar(
88
+ x=[f"Topic {i+1}" for i in range(len(distribution))],
89
+ y=distribution,
90
+ name=model
91
+ ))
 
92
 
93
+ fig.update_layout(
94
+ title="Topic Distributions Comparison",
95
+ xaxis_title="Topic",
96
+ yaxis_title="Weight",
97
+ barmode='group',
98
+ height=400
99
+ )
100
+
101
+ output_components.append(gr.Plot(value=fig))
 
 
 
102
 
103
+ # Visualize topic differences
104
  comparisons = topic_results.get("comparisons", {})
105
  if comparisons:
106
+ output_components.append(gr.Markdown("### Topic Distribution Differences"))
107
+
108
+ for comparison_key, comparison_data in comparisons.items():
109
+ js_divergence = comparison_data.get("js_divergence", 0)
110
+ topic_differences = comparison_data.get("topic_differences", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ output_components.append(gr.Markdown(
113
+ f"**{comparison_key}** - Jensen-Shannon Divergence: {js_divergence:.4f}"
114
+ ))
115
 
116
+ if topic_differences:
117
+ # Create DataFrame for plotting
118
+ model1, model2 = comparison_key.split(" vs ")
119
+ df_diff = pd.DataFrame(topic_differences)
 
120
 
121
+ # Create bar chart for topic differences
122
+ fig = go.Figure()
123
+ fig.add_trace(go.Bar(
124
+ x=[f"Topic {d['topic_id']+1}" for d in topic_differences],
125
+ y=[d["model1_weight"] for d in topic_differences],
126
+ name=model1
127
+ ))
128
+ fig.add_trace(go.Bar(
129
+ x=[f"Topic {d['topic_id']+1}" for d in topic_differences],
130
+ y=[d["model2_weight"] for d in topic_differences],
131
+ name=model2
132
  ))
133
 
134
  fig.update_layout(
135
+ title="Topic Weight Comparison",
136
+ xaxis_title="Topic",
137
+ yaxis_title="Weight",
138
+ barmode='group',
139
+ height=400
140
  )
141
 
142
  output_components.append(gr.Plot(value=fig))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  # If no components were added, show a message
145
  if len(output_components) <= 1:
 
160
  """
161
  try:
162
  print(f"Starting visualization of topic modeling analysis results")
163
+ return create_topic_visualization(analysis_results)
 
 
164
  except Exception as e:
165
  import traceback
166
  error_msg = f"Topic modeling visualization error: {str(e)}\n{traceback.format_exc()}"
167
  print(error_msg)
168
+ return [gr.Markdown(f"**Error during topic modeling visualization:**\n\n```\n{error_msg}\n```")]