Spaces:
Sleeping
Sleeping
File size: 9,113 Bytes
6e3c3d6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | import os
import gradio as gr
import pandas as pd
import numpy as np
import re
import plotly.express as px
from collections import Counter
def load_data(file_obj):
"""Safely loads CSV, Excel, or TXT file into a Pandas DataFrame."""
if file_obj is None:
return None, gr.update(choices=[], visible=False), "Please upload a file."
file_path = file_obj.name
ext = os.path.splitext(file_path)[1].lower()
try:
if ext == '.csv':
df = pd.read_csv(file_path)
elif ext in ['.xls', '.xlsx']:
df = pd.read_excel(file_path)
elif ext == '.txt':
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
df = pd.DataFrame({'text': [content]})
else:
return None, gr.update(choices=[], visible=False), "Unsupported file format. Please upload .csv, .xlsx, or .txt."
string_cols = [col for col in df.columns if df[col].dtype == 'object' or df[col].astype(str).str.len().mean() > 5]
if not string_cols:
string_cols = list(df.columns)
return df, gr.update(choices=string_cols, value=string_cols[0], visible=True), f"Successfully loaded dataset with {len(df)} rows."
except Exception as e:
return None, gr.update(choices=[], visible=False), f"Error loading file: {str(e)}"
def calculate_collocations(docs, n_gram_type, metric, min_freq, top_n):
"""Calculates n-gram collocations using Raw Frequency or PMI."""
# Combine texts and tokenize
words = []
stopwords = {'the', 'and', 'for', 'that', 'with', 'this', 'have', 'from', 'your', 'will', 'not', 'are', 'was', 'were', 'but', 'how', 'they', 'our', 'them', 'their', 'she', 'him', 'her', 'his', 'has', 'had', 'been', 'would', 'could', 'should'}
for doc in docs:
cleaned = re.sub(r'[^\w\s]', ' ', doc.lower())
doc_words = [w.strip() for w in cleaned.split() if w.strip() and w.strip() not in stopwords and len(w.strip()) > 2]
words.extend(doc_words)
if len(words) < 5:
return pd.DataFrame()
n = 2 if n_gram_type == "Bigrams (2-word pairs)" else 3
# Generate n-grams
ngrams_list = []
for i in range(len(words) - n + 1):
ngram = tuple(words[i:i+n])
ngrams_list.append(ngram)
ngram_counts = Counter(ngrams_list)
# Filter by minimum frequency
filtered_ngrams = {k: v for k, v in ngram_counts.items() if v >= min_freq}
if not filtered_ngrams:
return pd.DataFrame()
results = []
if metric == "Raw Joint Frequency":
for ngram, count in Counter(filtered_ngrams).most_common(top_n):
results.append({
"Word Phrase": " ".join(ngram),
"Score": float(count),
"Measure": "Frequency"
})
else:
# Pointwise Mutual Information (PMI)
# PMI(x, y) = log2( P(x,y) / (P(x)*P(y)) )
word_counts = Counter(words)
total_words = len(words)
total_ngrams = len(ngrams_list)
pmi_scores = {}
for ngram, count in filtered_ngrams.items():
# Joint probability
p_joint = count / total_ngrams
# Marginal probabilities product
p_marginals = 1.0
for word in ngram:
p_marginals *= (word_counts[word] / total_words)
pmi = np.log2(p_joint / p_marginals)
pmi_scores[ngram] = pmi
sorted_pmi = sorted(pmi_scores.items(), key=lambda x: x[1], reverse=True)[:top_n]
for ngram, score in sorted_pmi:
results.append({
"Word Phrase": " ".join(ngram),
"Score": round(float(score), 4),
"Measure": "PMI Score"
})
return pd.DataFrame(results)
def run_analysis(file_obj, text_col, n_gram_type, metric, min_freq, top_n):
if file_obj is None:
return None, None, None, "Please upload a dataset first."
# Re-load data
df, _, _ = load_data(file_obj)
if df is None:
return None, None, None, "Failed to parse the file."
docs = df[text_col].astype(str).fillna("").tolist()
if not docs:
return None, None, None, "No text documents found in the selected column."
try:
df_res = calculate_collocations(docs, n_gram_type, metric, min_freq, top_n)
if df_res.empty:
return None, None, None, "No collocations met the minimum frequency filter. Try lowering 'Min Word Co-occurrences'."
# Plotly Bar Chart
fig = px.bar(
df_res,
x="Score",
y="Word Phrase",
orientation="h",
color="Score",
title=f"Top Collocations via {metric}",
template="plotly_dark",
color_continuous_scale="Cividis"
)
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, height=450, margin=dict(l=20, r=20, t=40, b=20))
# Export CSV
csv_path = "collocations_report.csv"
df_res.to_csv(csv_path, index=False)
status_md = f"Successfully calculated top **{len(df_res)}** collocations using {metric}."
return df_res, fig, csv_path, status_md
except Exception as e:
return None, None, None, f"Analysis failed: {str(e)}"
custom_css = """
body {
background-color: #0b0f19;
color: #f3f4f6;
}
.gradio-container {
font-family: 'Inter', sans-serif !important;
}
h1, h2 {
color: #6366f1 !important;
}
"""
with gr.Blocks(theme=gr.themes.Default(primary_hue="indigo", secondary_hue="slate"), css=custom_css) as demo:
df_state = gr.State()
gr.HTML("""
<div style="text-align: center; margin-bottom: 2rem;">
<h1 style="font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem; background: linear-gradient(to right, #6366f1, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Interactive Collocation Analyzer</h1>
<p style="font-size: 1.1rem; color: #94a3b8; max-width: 800px; margin: 0 auto;">
Extract and analyze recurring word pairs (Bigrams) or triplets (Trigrams) that frequently co-occur in your text.
Toggle between raw joint counts and Pointwise Mutual Information (PMI) to reveal locked idioms and idioms.
</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1. Upload Dataset")
file_input = gr.File(label="Upload Dataset (.csv, .xlsx, .txt)", file_types=[".csv", ".xlsx", ".txt"])
text_column_selector = gr.Dropdown(
label="Target Text Column",
choices=[],
visible=False,
interactive=True
)
status_text = gr.Markdown("No file uploaded yet.")
gr.Markdown("### 2. Configure Collocations")
n_gram_type = gr.Radio(
choices=["Bigrams (2-word pairs)", "Trigrams (3-word triplets)"],
value="Bigrams (2-word pairs)",
label="Co-occurrence Type"
)
metric_selector = gr.Radio(
choices=["Raw Joint Frequency", "Pointwise Mutual Information (PMI)"],
value="Raw Joint Frequency",
label="Association Metric"
)
with gr.Row():
min_freq = gr.Slider(minimum=1, maximum=50, value=3, step=1, label="Min Word Co-occurrences")
top_n = gr.Slider(minimum=5, maximum=40, value=15, step=1, label="Phrases to Display")
run_btn = gr.Button("Analyze Collocations", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### 3. Collocations Results")
status_markdown = gr.Markdown("Configure settings and click 'Analyze Collocations' to run.")
with gr.Tabs():
with gr.TabItem("Collocations Plot"):
chart_output = gr.Plot(label="Collocation Strength Plot")
with gr.TabItem("Collocations Table"):
table_output = gr.Dataframe(
headers=["Word Phrase", "Score", "Measure"],
datatype=["str", "number", "str"],
interactive=False,
wrap=True
)
gr.Markdown("### 4. Export")
download_csv = gr.File(label="Download Collocations Report (CSV)")
file_input.change(
fn=load_data,
inputs=file_input,
outputs=[df_state, text_column_selector, status_text]
)
run_btn.click(
fn=run_analysis,
inputs=[file_input, text_column_selector, n_gram_type, metric_selector, min_freq, top_n],
outputs=[table_output, chart_output, download_csv, status_markdown]
)
if __name__ == "__main__":
demo.launch()
|