Jomaric commited on
Commit
aadff28
·
0 Parent(s):

feat: initial release of machine learning space

Browse files
Files changed (3) hide show
  1. README.md +19 -0
  2. app.py +226 -0
  3. requirements.txt +5 -0
README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Clustering Analyzer
3
+ emoji: 🧩
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ app_file: app.py
8
+ pinned: false
9
+ ---
10
+
11
+ # Document & Artifact Clustering Analyzer
12
+
13
+ An interactive computational tool designed for digital humanities and social science students to automatically group similar documents, articles, or transcripts using unsupervised machine learning.
14
+
15
+ ### Features
16
+ 1. **Multi-Model Clustering**: Choose between K-Means and Agglomerative Hierarchical clustering.
17
+ 2. **Dynamic 2D Projections**: Project high-dimensional text vectors into a 2D scatter plot using PCA (SVD) or t-SNE.
18
+ 3. **Representative Keywords Extraction**: Identify the top TF-IDF words characterizing each cluster subgroup.
19
+ 4. **Data Exports**: Download the full dataset labeled with assigned cluster IDs.
app.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ from sklearn.feature_extraction.text import TfidfVectorizer
5
+ from sklearn.cluster import KMeans, AgglomerativeClustering
6
+ from sklearn.decomposition import TruncatedSVD
7
+ from sklearn.manifold import TSNE
8
+ import plotly.graph_objects as go
9
+ import tempfile
10
+ import os
11
+
12
+ def run_clustering(file_obj, raw_text_area, num_clusters, algorithm, reduction_method, data_type):
13
+ if data_type == "Paste Raw Text (One Document Per Line)":
14
+ if not raw_text_area or len(raw_text_area.strip()) < 10:
15
+ return "Please enter multiple lines of text to cluster.", None, None, None
16
+ documents = [line.strip() for line in raw_text_area.split('\n') if line.strip()]
17
+ if len(documents) < num_clusters:
18
+ return f"Number of documents ({len(documents)}) must be greater than or equal to number of clusters ({num_clusters}).", None, None, None
19
+ df = pd.DataFrame({"Document": documents})
20
+ text_col = "Document"
21
+ else:
22
+ if file_obj is None:
23
+ return "Please upload a CSV or Excel file.", None, None, None
24
+ try:
25
+ if file_obj.name.endswith('.csv'):
26
+ df = pd.read_csv(file_obj.name)
27
+ else:
28
+ df = pd.read_excel(file_obj.name)
29
+ except Exception as e:
30
+ return f"Error reading file: {str(e)}", None, None, None
31
+
32
+ # Find text column
33
+ text_col = None
34
+ for col in df.columns:
35
+ if col.lower() in ['text', 'document', 'content', 'body', 'sentence']:
36
+ text_col = col
37
+ break
38
+ if not text_col:
39
+ # Fallback to first string/object column
40
+ string_cols = df.select_dtypes(include=['object']).columns
41
+ if len(string_cols) > 0:
42
+ text_col = string_cols[0]
43
+ else:
44
+ return "Could not find any text column in the uploaded dataset. Ensure it has a column with textual content.", None, None, None
45
+
46
+ df = df.dropna(subset=[text_col])
47
+ documents = df[text_col].astype(str).tolist()
48
+ if len(documents) < num_clusters:
49
+ return f"Number of documents ({len(documents)}) must be greater than or equal to number of clusters ({num_clusters}).", None, None, None
50
+
51
+ # 1. TF-IDF Embedding
52
+ try:
53
+ vectorizer = TfidfVectorizer(stop_words='english', max_features=1000)
54
+ X = vectorizer.fit_transform(documents).toarray()
55
+ except Exception as e:
56
+ return f"Error building TF-IDF vectors: {str(e)}. Try using longer or more diverse texts.", None, None, None
57
+
58
+ # 2. Cluster Allocation
59
+ if algorithm == "K-Means":
60
+ clusterer = KMeans(n_clusters=num_clusters, random_state=42, n_init=10)
61
+ labels = clusterer.fit_predict(X)
62
+ else: # Hierarchical Agglomerative
63
+ clusterer = AgglomerativeClustering(n_clusters=num_clusters)
64
+ labels = clusterer.fit_predict(X)
65
+
66
+ df['Cluster'] = labels + 1
67
+
68
+ # 3. Dimensions Reduction (2D Projection)
69
+ if reduction_method == "PCA (SVD)":
70
+ reducer = TruncatedSVD(n_components=2, random_state=42)
71
+ X_2d = reducer.fit_transform(X)
72
+ else: # t-SNE
73
+ # t-SNE perplexity must be smaller than the number of samples
74
+ perp = min(30, max(2, len(documents) // 2))
75
+ reducer = TSNE(n_components=2, perplexity=perp, random_state=42, init='random')
76
+ X_2d = reducer.fit_transform(X)
77
+
78
+ df['x'] = X_2d[:, 0]
79
+ df['y'] = X_2d[:, 1]
80
+
81
+ # Shorten texts for hover label
82
+ hover_texts = [d[:75] + "..." if len(d) > 75 else d for d in documents]
83
+ df['HoverText'] = hover_texts
84
+
85
+ # 4. Generate Interactive Plotly Chart
86
+ fig = go.Figure()
87
+
88
+ # Premium color palette for clusters
89
+ colors = ['#ff7043', '#4db6ac', '#9575cd', '#ffd54f', '#64b5f6', '#f06292', '#81c784', '#ffffff', '#a1887f', '#ba68c8']
90
+
91
+ for c_id in sorted(df['Cluster'].unique()):
92
+ sub_df = df[df['Cluster'] == c_id]
93
+ color = colors[(c_id - 1) % len(colors)]
94
+
95
+ fig.add_trace(go.Scatter(
96
+ x=sub_df['x'],
97
+ y=sub_df['y'],
98
+ mode='markers',
99
+ marker=dict(size=12, color=color, line=dict(width=1, color='#16100c')),
100
+ name=f"Cluster {c_id}",
101
+ text=sub_df['HoverText'],
102
+ hoverinfo='text+name'
103
+ ))
104
+
105
+ fig.update_layout(
106
+ title="Document Cluster Projection",
107
+ paper_bgcolor='#16100c',
108
+ plot_bgcolor='#16100c',
109
+ font_color='#f4eee6',
110
+ xaxis=dict(showgrid=True, gridcolor='rgba(255,255,255,0.05)', showticklabels=False),
111
+ yaxis=dict(showgrid=True, gridcolor='rgba(255,255,255,0.05)', showticklabels=False),
112
+ margin=dict(l=40, r=40, t=50, b=40)
113
+ )
114
+
115
+ # 5. Extract Cluster Keywords (Top terms inside each cluster)
116
+ cluster_keywords = []
117
+ feature_names = np.array(vectorizer.get_feature_names_out())
118
+
119
+ for c_id in sorted(df['Cluster'].unique()):
120
+ sub_idx = np.where(labels == (c_id - 1))[0]
121
+ sub_X = X[sub_idx]
122
+ mean_tfidf = sub_X.mean(axis=0)
123
+ top_indices = mean_tfidf.argsort()[::-1][:6]
124
+ top_words = ", ".join(feature_names[top_indices])
125
+
126
+ cluster_keywords.append({
127
+ "Cluster ID": c_id,
128
+ "Document Count": len(sub_idx),
129
+ "Representative Keywords": top_words
130
+ })
131
+
132
+ df_keywords = pd.DataFrame(cluster_keywords)
133
+
134
+ # Output file
135
+ out_csv = tempfile.mktemp(suffix=".csv")
136
+ df.drop(columns=['x', 'y', 'HoverText'], errors='ignore').to_csv(out_csv, index=False)
137
+
138
+ # Build clean previews dataframe
139
+ df_preview = df[[text_col, 'Cluster']].head(100)
140
+
141
+ return "", fig, df_keywords, df_preview, out_csv
142
+
143
+ theme = gr.themes.Default(
144
+ primary_hue="orange",
145
+ neutral_hue="stone"
146
+ ).set(
147
+ body_background_fill="#0d0907",
148
+ body_text_color="#c4bbae",
149
+ block_background_fill="#16100c",
150
+ block_border_width="1px",
151
+ block_label_text_color="#f4eee6"
152
+ )
153
+
154
+ with gr.Blocks(theme=theme, title="Clustering Analyzer") as demo:
155
+ gr.Markdown(
156
+ """
157
+ # 🧩 Document & Artifact Clustering Analyzer
158
+ ### Group similar documents, articles, or textual transcripts automatically using unsupervised machine learning. Visualize groupings in 2D vector space instantly.
159
+ """
160
+ )
161
+
162
+ error_msg = gr.Markdown("", visible=False)
163
+
164
+ with gr.Row():
165
+ with gr.Column(scale=1):
166
+ data_type = gr.Radio(
167
+ choices=["Upload Dataset Sheet", "Paste Raw Text (One Document Per Line)"],
168
+ value="Paste Raw Text (One Document Per Line)",
169
+ label="Data Input Mode"
170
+ )
171
+
172
+ with gr.Group(visible=False) as upload_group:
173
+ file_obj = gr.File(label="Upload CSV or Excel sheet", file_types=[".csv", ".xlsx"])
174
+ gr.Markdown("💡 **Tip**: Make sure your dataset contains a textual column (e.g., **Text**, **Content**).")
175
+
176
+ with gr.Group(visible=True) as text_group:
177
+ raw_text_area = gr.Textbox(
178
+ label="Input Text Documents (one per line)",
179
+ placeholder="Romeo loves Juliet\nShakespeare wrote plays\nVerification is important in coding\nNetworks model nodes and edges\nAI assistants write python scripts",
180
+ lines=10
181
+ )
182
+
183
+ with gr.Row():
184
+ num_clusters = gr.Slider(minimum=2, maximum=10, value=3, step=1, label="Number of Clusters (k)")
185
+ algorithm = gr.Dropdown(choices=["K-Means", "Hierarchical Agglomerative"], value="K-Means", label="Cluster Algorithm")
186
+
187
+ reduction_method = gr.Radio(choices=["PCA (SVD)", "t-SNE"], value="PCA (SVD)", label="Vector Dimensionality Reduction")
188
+
189
+ btn = gr.Button("Calculate and Visualize Clusters", variant="primary")
190
+
191
+ with gr.Column(scale=2):
192
+ with gr.Tabs():
193
+ with gr.TabItem("Interactive 2D Cluster Map"):
194
+ plot_box = gr.Plot()
195
+ with gr.TabItem("Representative Cluster Keywords"):
196
+ table_keywords = gr.Dataframe(headers=["Cluster ID", "Document Count", "Representative Keywords"])
197
+ with gr.TabItem("Labeled Documents Table"):
198
+ table_docs = gr.Dataframe(max_rows=15)
199
+ download_btn = gr.File(label="Download Complete Labeled CSV")
200
+
201
+ def update_visibility(mode):
202
+ if mode == "Upload Dataset Sheet":
203
+ return gr.update(visible=True), gr.update(visible=False)
204
+ else:
205
+ return gr.update(visible=False), gr.update(visible=True)
206
+
207
+ data_type.change(
208
+ update_visibility,
209
+ inputs=[data_type],
210
+ outputs=[upload_group, text_group]
211
+ )
212
+
213
+ def process(file_obj, text_area, clusters, algo, reduction, mode):
214
+ err, plot, keywords, docs, csv_path = run_clustering(file_obj, text_area, clusters, algo, reduction, mode)
215
+ if err:
216
+ return gr.update(value=err, visible=True), None, None, None, gr.update(visible=False)
217
+ return gr.update(visible=False), plot, keywords, docs, gr.update(value=csv_path, visible=True)
218
+
219
+ btn.click(
220
+ process,
221
+ inputs=[file_obj, raw_text_area, num_clusters, algorithm, reduction_method, data_type],
222
+ outputs=[error_msg, plot_box, table_keywords, table_docs, download_btn]
223
+ )
224
+
225
+ if __name__ == "__main__":
226
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ pandas
2
+ numpy
3
+ scikit-learn
4
+ plotly
5
+ openpyxl