Mahmoud-adel25 commited on
Commit
06e950b
Β·
verified Β·
1 Parent(s): 92a7d62

Update src/visualizations.py

Browse files
Files changed (1) hide show
  1. src/visualizations.py +769 -1102
src/visualizations.py CHANGED
@@ -1,1113 +1,780 @@
1
  """
2
- Customer Segmentation Streamlit App
3
- ==================================
4
 
5
- A comprehensive web application for customer segmentation analysis using
6
- K-Means and DBSCAN clustering algorithms.
7
  """
8
 
9
- import streamlit as st
 
 
 
 
 
10
  import pandas as pd
11
  import numpy as np
12
- import sys
13
- import os
14
-
15
- # Add src to path for imports
16
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
17
-
18
- from src.data_loader import DataLoader
19
- from src.clustering import ClusteringAnalyzer
20
- from src.visualizations import Visualizer
21
 
22
- # Page configuration
23
- st.set_page_config(
24
- Β  Β  page_title="Customer Segmentation Analysis",
25
- Β  Β  page_icon="πŸ›οΈ",
26
- Β  Β  layout="wide",
27
- Β  Β  initial_sidebar_state="expanded"
28
- )
29
- import plotly.io as pio
30
  pio.templates.default = "plotly_dark"
31
-
32
- # Modern Dark Mode Compatible CSS
33
- st.markdown("""
34
- <style>
35
- Β  Β  /* Import Google Fonts */
36
- Β  Β  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;500;600;700&display=swap');
37
- Β  Β Β 
38
- Β  Β  /* CSS Variables for Dark Mode Support */
39
- Β  Β  /* :root {
40
- Β  Β  Β  Β  --bg-primary: #0F172A;Β  Β  Β  Β /* slate-900 */
41
- Β  Β  Β  Β  --bg-secondary: #111827;Β  Β  Β /* gray-900 */
42
- Β  Β  Β  Β  --bg-tertiary: #1F2937;Β  Β  Β  /* gray-800 */
43
- Β  Β  Β  Β  --text-primary: #E5E7EB;Β  Β  Β /* gray-200 */
44
- Β  Β  Β  Β  --text-secondary: #CBD5E1;Β  Β /* slate-300 */
45
- Β  Β  Β  Β  --text-tertiary: #94A3B8;Β  Β  /* slate-400 */
46
- Β  Β  Β  Β  --border-color: #374151;Β  Β  Β /* gray-700 */
47
- Β  Β  Β  Β  --accent-primary: #818CF8;Β  Β /* indigo-300 */
48
- Β  Β  Β  Β  --accent-secondary: #A78BFA; /* violet-300 */
49
- Β  Β  Β  Β  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
50
- Β  Β  Β  Β  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
51
- Β  Β  Β  Β  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6);
52
- Β  Β  } */
53
- Β  Β Β 
54
- Β  Β  /* Dark mode support disabled intentionally */
55
- Β  Β Β 
56
- Β  Β  /* Base styling */
57
- Β  Β  .main .block-container {
58
- Β  Β  Β  Β  padding: 2rem 1rem;
59
- Β  Β  Β  Β  max-width: 1200px;
60
- Β  Β  }
61
- Β  Β Β 
62
- Β  Β  /* Apply CSS variables to Streamlit elements */
63
- Β  Β  .stApp { background-color: #0F172A; color: #E5E7EB; }
64
- Β  Β Β 
65
- Β  Β  /* Headers */
66
- Β  Β  .main-header {
67
- Β  Β  Β  Β  font-family: 'Inter', sans-serif;
68
- Β  Β  Β  Β  font-size: clamp(2.5rem, 5vw, 4rem);
69
- Β  Β  Β  Β  font-weight: 800;
70
- Β  Β  Β  Β  text-align: center;
71
- Β  Β  Β  Β  margin-bottom: 3rem;
72
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8 0%, #A78BFA 100%);
73
- Β  Β  Β  Β  -webkit-background-clip: text;
74
- Β  Β  Β  Β  -webkit-text-fill-color: transparent;
75
- Β  Β  Β  Β  background-clip: text;
76
- Β  Β  Β  Β  letter-spacing: -0.02em;
77
- Β  Β  }
78
- Β  Β Β 
79
- Β  Β  .sub-header {
80
- Β  Β  Β  Β  font-family: 'Inter', sans-serif;
81
- Β  Β  Β  Β  font-size: 1.75rem;
82
- Β  Β  Β  Β  font-weight: 600;
83
- Β  Β  Β  Β  color: #E5E7EB;
84
- Β  Β  Β  Β  margin: 2rem 0 1rem 0;
85
- Β  Β  Β  Β  padding-bottom: 0.75rem;
86
- Β  Β  Β  Β  border-bottom: 2px solid #374151;
87
- Β  Β  Β  Β  position: relative;
88
- Β  Β  }
89
- Β  Β Β 
90
- Β  Β  .sub-header::after {
91
- Β  Β  Β  Β  content: '';
92
- Β  Β  Β  Β  bottom: -2px;
93
- Β  Β  Β  Β  left: 0;
94
- Β  Β  Β  Β  width: 60px;
95
- Β  Β  Β  Β  height: 2px;
96
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8, #A78BFA);
97
- Β  Β  }
98
- Β  Β Β 
99
- Β  Β  /* Enhanced Tab Styling */
100
- Β  Β  .stTabs [data-baseweb="tab-list"] {
101
- Β  Β  Β  Β  gap: 4px;
102
- Β  Β  Β  Β  background: #111827;
103
- Β  Β  Β  Β  padding: 8px;
104
- Β  Β  Β  Β  border-radius: 16px;
105
- Β  Β  Β  Β  border: 1px solid #374151;
106
- Β  Β  Β  Β  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
107
- Β  Β  Β  Β  margin-bottom: 2rem;
108
- Β  Β  }
109
- Β  Β Β 
110
- Β  Β  .stTabs [data-baseweb="tab"] {
111
- Β  Β  Β  Β  height: 48px;
112
- Β  Β  Β  Β  padding: 0 20px;
113
- Β  Β  Β  Β  background: transparent;
114
- Β  Β  Β  Β  border-radius: 12px;
115
- Β  Β  Β  Β  color: #CBD5E1;
116
- Β  Β  Β  Β  font-weight: 500;
117
- Β  Β  Β  Β  font-family: 'Inter', sans-serif;
118
- Β  Β  Β  Β  font-size: 0.875rem;
119
- Β  Β  Β  Β  border: none;
120
- Β  Β  Β  Β  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
121
- Β  Β  Β  Β  position: relative;
122
- Β  Β  Β  Β  overflow: hidden;
123
- Β  Β  }
124
- Β  Β Β 
125
- Β  Β  .stTabs [data-baseweb="tab"]:hover {
126
- Β  Β  Β  Β  background: #1F2937;
127
- Β  Β  Β  Β  color: #E5E7EB;
128
- Β  Β  Β  Β  transform: translateY(-1px);
129
- Β  Β  }
130
- Β  Β Β 
131
- Β  Β  .stTabs [aria-selected="true"] {
132
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8 0%, #A78BFA 100%);
133
- Β  Β  Β  Β  color: white !important;
134
- Β  Β  Β  Β  font-weight: 600;
135
- Β  Β  Β  Β  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
136
- Β  Β  Β  Β  transform: translateY(-1px);
137
- Β  Β  }
138
- Β  Β Β 
139
- Β  Β  /* Cards and containers */
140
- Β  Β  .metric-card {
141
- Β  Β  Β  Β  background: #0F172A;
142
- Β  Β  Β  Β  border: 1px solid #374151;
143
- Β  Β  Β  Β  border-radius: 16px;
144
- Β  Β  Β  Β  padding: 1.5rem;
145
- Β  Β  Β  Β  margin: 1rem 0;
146
- Β  Β  Β  Β  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
147
- Β  Β  Β  Β  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
148
- Β  Β  Β  Β  position: relative;
149
- Β  Β  Β  Β  overflow: hidden;
150
- Β  Β  }
151
- Β  Β Β 
152
- Β  Β  .metric-card::before {
153
- Β  Β  Β  Β  content: '';
154
- Β  Β  Β  Β  top: 0;
155
- Β  Β  Β  Β  left: 0;
156
- Β  Β  Β  Β  right: 0;
157
- Β  Β  Β  Β  height: 3px;
158
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8, #A78BFA);
159
- Β  Β  }
160
- Β  Β Β 
161
- Β  Β  .metric-card:hover {
162
- Β  Β  Β  Β  transform: translateY(-4px);
163
- Β  Β  Β  Β  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.6);
164
- Β  Β  Β  Β  border-color: #818CF8;
165
- Β  Β  }
166
- Β  Β Β 
167
- Β  Β  .insight-box {
168
- Β  Β  Β  Β  background: #111827;
169
- Β  Β  Β  Β  border: 1px solid #818CF8;
170
- Β  Β  Β  Β  border-radius: 16px;
171
- Β  Β  Β  Β  padding: 1.5rem;
172
- Β  Β  Β  Β  margin: 1.5rem 0;
173
- Β  Β  Β  Β  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
174
- Β  Β  Β  Β  position: relative;
175
- Β  Β  }
176
- Β  Β Β 
177
- Β  Β  .insight-box::before {
178
- Β  Β  Β  Β  content: '';
179
- Β  Β  Β  Β  top: 0;
180
- Β  Β  Β  Β  left: 0;
181
- Β  Β  Β  Β  right: 0;
182
- Β  Β  Β  Β  height: 3px;
183
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8, #A78BFA);
184
- Β  Β  }
185
- Β  Β Β 
186
- Β  Β  /* Sidebar */
187
- Β  Β  .css-1d391kg {
188
- Β  Β  Β  Β  background: #111827;
189
- Β  Β  Β  Β  border-right: 1px solid #374151;
190
- Β  Β  }
191
- Β  Β Β 
192
- Β  Β  /* Text styling with proper contrast */
193
- Β  Β  .stMarkdown, .stText, p, div, span, label {
194
- Β  Β  Β  Β  color: #E5E7EB !important;
195
- Β  Β  Β  Β  font-family: 'Inter', sans-serif;
196
- Β  Β  }
197
- Β  Β Β 
198
- Β  Β  [data-testid="stMarkdownContainer"] {
199
- Β  Β  Β  Β  color: #E5E7EB !important;
200
- Β  Β  }
201
- Β  Β Β 
202
- Β  Β  /* Enhanced message styling */
203
- Β  Β  .stSuccess {
204
- Β  Β  Β  Β  background: rgba(34, 197, 94, 0.1) !important;
205
- Β  Β  Β  Β  border: 1px solid #22c55e !important;
206
- Β  Β  Β  Β  border-radius: 12px !important;
207
- Β  Β  Β  Β  color: #166534 !important;
208
- Β  Β  }
209
- Β  Β Β 
210
- Β  Β  .stInfo {
211
- Β  Β  Β  Β  background: rgba(59, 130, 246, 0.1) !important;
212
- Β  Β  Β  Β  border: 1px solid #3b82f6 !important;
213
- Β  Β  Β  Β  border-radius: 12px !important;
214
- Β  Β  Β  Β  color: #1e40af !important;
215
- Β  Β  }
216
- Β  Β Β 
217
- Β  Β  .stWarning {
218
- Β  Β  Β  Β  background: rgba(245, 158, 11, 0.1) !important;
219
- Β  Β  Β  Β  border: 1px solid #f59e0b !important;
220
- Β  Β  Β  Β  border-radius: 12px !important;
221
- Β  Β  Β  Β  color: #92400e !important;
222
- Β  Β  }
223
- Β  Β Β 
224
- Β  Β  .stError {
225
- Β  Β  Β  Β  background: rgba(239, 68, 68, 0.1) !important;
226
- Β  Β  Β  Β  border: 1px solid #ef4444 !important;
227
- Β  Β  Β  Β  border-radius: 12px !important;
228
- Β  Β  Β  Β  color: #dc2626 !important;
229
- Β  Β  }
230
- Β  Β Β 
231
- Β  Β  /* Enhanced Modern Button Styling */
232
- Β  Β  .stButton > button {
233
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8 0%, #A78BFA 100%);
234
- Β  Β  Β  Β  color: white !important;
235
- Β  Β  Β  Β  border: none;
236
- Β  Β  Β  Β  border-radius: 16px;
237
- Β  Β  Β  Β  padding: 1rem 2.5rem;
238
- Β  Β  Β  Β  font-weight: 700;
239
- Β  Β  Β  Β  font-family: 'Inter', sans-serif;
240
- Β  Β  Β  Β  font-size: 1rem;
241
- Β  Β  Β  Β  letter-spacing: 0.025em;
242
- Β  Β  Β  Β  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
243
- Β  Β  Β  Β  box-shadow: 0 8px 25px rgba(129, 140, 248, 0.3);
244
- Β  Β  Β  Β  position: relative;
245
- Β  Β  Β  Β  overflow: hidden;
246
- Β  Β  Β  Β  text-transform: uppercase;
247
- Β  Β  Β  Β  min-height: 48px;
248
- Β  Β  }
249
- Β  Β Β 
250
- Β  Β  .stButton > button::before {
251
- Β  Β  Β  Β  content: '';
252
- Β  Β  Β  Β  position: absolute;
253
- Β  Β  Β  Β  top: 0;
254
- Β  Β  Β  Β  left: -100%;
255
- Β  Β  Β  Β  width: 100%;
256
- Β  Β  Β  Β  height: 100%;
257
- Β  Β  Β  Β  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
258
- Β  Β  Β  Β  transition: left 0.5s;
259
- Β  Β  }
260
- Β  Β Β 
261
- Β  Β  .stButton > button:hover {
262
- Β  Β  Β  Β  transform: translateY(-3px) scale(1.02);
263
- Β  Β  Β  Β  box-shadow: 0 15px 35px rgba(129, 140, 248, 0.4);
264
- Β  Β  Β  Β  filter: brightness(1.15);
265
- Β  Β  Β  Β  background: linear-gradient(135deg, #A78BFA 0%, #818CF8 100%);
266
- Β  Β  }
267
- Β  Β Β 
268
- Β  Β  .stButton > button:hover::before {
269
- Β  Β  Β  Β  left: 100%;
270
- Β  Β  }
271
- Β  Β Β 
272
- Β  Β  .stButton > button:active {
273
- Β  Β  Β  Β  transform: translateY(-1px) scale(0.98);
274
- Β  Β  Β  Β  box-shadow: 0 5px 15px rgba(129, 140, 248, 0.3);
275
- Β  Β  }
276
- Β  Β Β 
277
- Β  Β  /* Special styling for primary action buttons */
278
- Β  Β  .stButton > button:contains("Apply") {
279
- Β  Β  Β  Β  background: linear-gradient(135deg, #10B981 0%, #059669 100%);
280
- Β  Β  Β  Β  box-shadow: 0 8px 25px rgba(16, 185, 129, 0.3);
281
- Β  Β  }
282
- Β  Β Β 
283
- Β  Β  .stButton > button:contains("Apply"):hover {
284
- Β  Β  Β  Β  background: linear-gradient(135deg, #059669 0%, #10B981 100%);
285
- Β  Β  Β  Β  box-shadow: 0 15px 35px rgba(16, 185, 129, 0.4);
286
- Β  Β  }
287
- Β  Β Β 
288
- Β  Β  /* Special styling for find/analyze buttons */
289
- Β  Β  .stButton > button:contains("Find") {
290
- Β  Β  Β  Β  background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
291
- Β  Β  Β  Β  box-shadow: 0 8px 25px rgba(245, 158, 11, 0.3);
292
- Β  Β  }
293
- Β  Β Β 
294
- Β  Β  .stButton > button:contains("Find"):hover {
295
- Β  Β  Β  Β  background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%);
296
- Β  Β  Β  Β  box-shadow: 0 15px 35px rgba(245, 158, 11, 0.4);
297
- Β  Β  }
298
- Β  Β Β 
299
- Β  Β  /* Special styling for reload/clear buttons */
300
- Β  Β  .stButton > button:contains("Reload"), .stButton > button:contains("Clear") {
301
- Β  Β  Β  Β  background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
302
- Β  Β  Β  Β  box-shadow: 0 8px 25px rgba(239, 68, 68, 0.3);
303
- Β  Β  }
304
- Β  Β Β 
305
- Β  Β  .stButton > button:contains("Reload"):hover, .stButton > button:contains("Clear"):hover {
306
- Β  Β  Β  Β  background: linear-gradient(135deg, #DC2626 0%, #EF4444 100%);
307
- Β  Β  Β  Β  box-shadow: 0 15px 35px rgba(239, 68, 68, 0.4);
308
- Β  Β  }
309
- Β  Β Β 
310
- Β  Β  /* Form elements */
311
- Β  Β  .stSelectbox > div > div,
312
- Β  Β  .stTextInput > div > div > input,
313
- Β  Β  .stNumberInput > div > div > input {
314
- Β  Β  Β  Β  background: #0F172A !important;
315
- Β  Β  Β  Β  border: 1px solid #374151 !important;
316
- Β  Β  Β  Β  border-radius: 12px !important;
317
- Β  Β  Β  Β  color: #E5E7EB !important;
318
- Β  Β  Β  Β  font-family: 'Inter', sans-serif !important;
319
- Β  Β  Β  Β  transition: all 0.2s ease;
320
- Β  Β  }
321
- Β  Β Β 
322
- Β  Β  .stSelectbox > div > div:focus-within,
323
- Β  Β  .stTextInput > div > div:focus-within,
324
- Β  Β  .stNumberInput > div > div:focus-within {
325
- Β  Β  Β  Β  border-color: #818CF8 !important;
326
- Β  Β  Β  Β  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1) !important;
327
- Β  Β  }
328
- Β  Β Β 
329
- Β  Β  /* Slider styling */
330
- Β  Β  .stSlider > div > div > div > div {
331
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8, #A78BFA) !important;
332
- Β  Β  }
333
- Β  Β Β 
334
- Β  Β  .stSlider > div > div > div > div > div {
335
- Β  Β  Β  Β  background: white !important;
336
- Β  Β  Β  Β  border: 2px solid #818CF8 !important;
337
- Β  Β  Β  Β  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5) !important;
338
- Β  Β  }
339
- Β  Β Β 
340
- fig.update_traces(
341
- Β  Β  fillcolor='rgba(129, 140, 248, 0.3)',Β  # semi-transparent fill
342
- Β  Β  selector=dict(type='box')Β  Β  Β  Β  Β  Β  Β  # only affects box plots
343
- )
344
- Β  Β Β 
345
- Β  Β  .element-container .stPlotlyChart {
346
- Β  Β  Β  Β  background: #0F172A !important;
347
- Β  Β  }
348
- Β  Β  fig.update_traces(
349
- Β  Β  Β  Β  marker=dict(size=8, opacity=0.9, line=dict(width=1, color="white"))
350
- Β  Β  )
351
- import plotly.express as px
352
- color_palette = px.colors.qualitative.Set2
353
- fig = px.scatter(
354
- Β  Β  data_frame,
355
- Β  Β  x='Age',
356
- Β  Β  y='Annual Income (k$)',
357
- Β  Β  color='Cluster',
358
- Β  Β  color_discrete_sequence=color_palette,
359
- Β  Β  title='Age vs. Annual Income',
360
- Β  Β  labels={'Age': 'Age', 'Annual Income (k$)': 'Annual Income (k$)'},
361
- Β  Β  template='plotly_dark'
362
  )
363
 
364
- Β  Β Β 
365
- Β  Β  /* DataFrames */
366
- Β  Β  .stDataFrame {
367
- Β  Β  Β  Β  border: 1px solid #374151;
368
- Β  Β  Β  Β  border-radius: 12px;
369
- Β  Β  Β  Β  overflow: hidden;
370
- Β  Β  Β  Β  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
371
- Β  Β  }
372
- Β  Β Β 
373
- Β  Β  .stDataFrame > div {
374
- Β  Β  Β  Β  background: #0F172A;
375
- Β  Β  }
376
- Β  Β Β 
377
- Β  Β  /* Progress bars */
378
- Β  Β  .stProgress > div > div > div {
379
- Β  Β  Β  Β  background: linear-gradient(135deg, #818CF8, #A78BFA) !important;
380
- Β  Β  Β  Β  border-radius: 8px !important;
381
- Β  Β  }
382
- Β  Β Β 
383
- Β  Β  /* Expanders */
384
- Β  Β  .streamlit-expanderHeader {
385
- Β  Β  Β  Β  background: #111827 !important;
386
- Β  Β  Β  Β  border: 1px solid #374151 !important;
387
- Β  Β  Β  Β  border-radius: 12px !important;
388
- Β  Β  Β  Β  color: #E5E7EB !important;
389
- Β  Β  Β  Β  font-weight: 500 !important;
390
- Β  Β  Β  Β  font-family: 'Inter', sans-serif !important;
391
- Β  Β  Β  Β  transition: all 0.2s ease;
392
- Β  Β  }
393
- Β  Β Β 
394
- Β  Β  .streamlit-expanderHeader:hover {
395
- Β  Β  Β  Β  background: #1F2937 !important;
396
- Β  Β  Β  Β  border-color: #818CF8 !important;
397
- Β  Β  }
398
- Β  Β Β 
399
- Β  Β  .streamlit-expanderContent {
400
- Β  Β  Β  Β  background: #0F172A !important;
401
- Β  Β  Β  Β  border: 1px solid #374151 !important;
402
- Β  Β  Β  Β  border-top: none !important;
403
- Β  Β  Β  Β  color: #E5E7EB !important;
404
- Β  Β  Β  Β  border-radius: 0 0 12px 12px !important;
405
- Β  Β  }
406
- Β  Β Β 
407
- Β  Β  /* Metrics */
408
- Β  Β  [data-testid="metric-container"] {
409
- Β  Β  Β  Β  background: #111827;
410
- Β  Β  Β  Β  border: 1px solid #374151;
411
- Β  Β  Β  Β  border-radius: 12px;
412
- Β  Β  Β  Β  padding: 1rem;
413
- Β  Β  Β  Β  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
414
- Β  Β  Β  Β  transition: all 0.2s ease;
415
- Β  Β  }
416
- Β  Β Β 
417
- Β  Β  [data-testid="metric-container"]:hover {
418
- Β  Β  Β  Β  transform: translateY(-2px);
419
- Β  Β  Β  Β  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
420
- Β  Β  }
421
- Β  Β Β 
422
- Β  Β  [data-testid="metric-container"] > div {
423
- Β  Β  Β  Β  color: #E5E7EB !important;
424
- Β  Β  }
425
- Β  Β Β 
426
- Β  Β  /* Code blocks */
427
- Β  Β  .stCode {
428
- Β  Β  Β  Β  background: #111827 !important;
429
- Β  Β  Β  Β  border: 1px solid #374151 !important;
430
- Β  Β  Β  Β  border-radius: 12px !important;
431
- Β  Β  Β  Β  font-family: 'JetBrains Mono', monospace !important;
432
- Β  Β  }
433
- Β  Β Β 
434
- Β  Β  /* Headings */
435
- Β  Β  h1, h2, h3, h4, h5, h6 {
436
- Β  Β  Β  Β  color: #E5E7EB !important;
437
- Β  Β  Β  Β  font-family: 'Inter', sans-serif !important;
438
- Β  Β  Β  Β  font-weight: 600 !important;
439
- Β  Β  Β  Β  letter-spacing: -0.01em;
440
- Β  Β  }
441
- Β  Β Β 
442
- Β  Β  /* File uploader */
443
- Β  Β  .stFileUploader > div {
444
- Β  Β  Β  Β  background: #111827 !important;
445
- Β  Β  Β  Β  border: 2px dashed #374151 !important;
446
- Β  Β  Β  Β  border-radius: 12px !important;
447
- Β  Β  Β  Β  transition: all 0.2s ease;
448
- Β  Β  }
449
- Β  Β Β 
450
- Β  Β  .stFileUploader > div:hover {
451
- Β  Β  Β  Β  border-color: #818CF8 !important;
452
- Β  Β  Β  Β  background: #1F2937 !important;
453
- Β  Β  }
454
- Β  Β Β 
455
- Β  Β  /* Scrollbars */
456
- Β  Β  ::-webkit-scrollbar {
457
- Β  Β  Β  Β  width: 8px;
458
- Β  Β  Β  Β  height: 8px;
459
- Β  Β  }
460
- Β  Β Β 
461
- Β  Β  ::-webkit-scrollbar-track {
462
- Β  Β  Β  Β  background: #111827;
463
- Β  Β  Β  Β  border-radius: 4px;
464
- Β  Β  }
465
- Β  Β Β 
466
- Β  Β  ::-webkit-scrollbar-thumb {
467
- Β  Β  Β  Β  background: #94A3B8;
468
- Β  Β  Β  Β  border-radius: 4px;
469
- Β  Β  }
470
- Β  Β Β 
471
- Β  Β  ::-webkit-scrollbar-thumb:hover {
472
- Β  Β  Β  Β  background: #CBD5E1;
473
- Β  Β  }
474
- Β  Β Β 
475
- Β  Β  /* Animation keyframes */
476
- Β  Β  @keyframes fadeIn {
477
- Β  Β  Β  Β  from { opacity: 0; transform: translateY(20px); }
478
- Β  Β  Β  Β  to { opacity: 1; transform: translateY(0); }
479
- Β  Β  }
480
- Β  Β Β 
481
- Β  Β  .stTabs [data-baseweb="tabpanel"] {
482
- Β  Β  Β  Β  animation: fadeIn 0.5s ease-out;
483
- Β  Β  }
484
- </style>
485
- """, unsafe_allow_html=True)
486
-
487
- def initialize_session_state():
488
- Β  Β  """Initialize session state variables."""
489
- Β  Β  if 'data_loader' not in st.session_state:
490
- Β  Β  Β  Β  st.session_state.data_loader = DataLoader()
491
- Β  Β  if 'clustering_analyzer' not in st.session_state:
492
- Β  Β  Β  Β  st.session_state.clustering_analyzer = ClusteringAnalyzer()
493
- Β  Β  if 'visualizer' not in st.session_state:
494
- Β  Β  Β  Β  st.session_state.visualizer = Visualizer()
495
- Β  Β  if 'data_loaded' not in st.session_state:
496
- Β  Β  Β  Β  st.session_state.data_loaded = False
497
- Β  Β  if 'data_preprocessed' not in st.session_state:
498
- Β  Β  Β  Β  st.session_state.data_preprocessed = False
499
- Β  Β  if 'clustering_done' not in st.session_state:
500
- Β  Β  Β  Β  st.session_state.clustering_done = {'kmeans': False, 'dbscan': False}
501
-
502
- def main():
503
- Β  Β  """Main application function."""
504
- Β  Β  initialize_session_state()
505
- Β  Β Β 
506
- Β  Β  # Main header
507
- Β  Β  st.markdown('<h1 class="main-header">πŸ›οΈ Customer Segmentation Analysis</h1>', unsafe_allow_html=True)
508
- Β  Β  st.markdown("---")
509
- Β  Β Β 
510
- Β  Β  # Tab navigation
511
- Β  Β  tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([
512
- Β  Β  Β  Β  "🏠 Home", "πŸ“Š Data Overview", "πŸ” Data Exploration", "βš™οΈ Preprocessing",Β 
513
- Β  Β  Β  Β  "🎯 K-Means", "🌟 DBSCAN", "πŸ“ˆ Comparison", "πŸ“‹ Insights"
514
- Β  Β  ])
515
- Β  Β Β 
516
- Β  Β  # Data loading section in sidebar
517
- Β  Β  st.sidebar.markdown("---")
518
- Β  Β  st.sidebar.subheader("πŸ“‚ Data Management")
519
- Β  Β Β 
520
- Β  Β  # Auto-load dataset on first run
521
- Β  Β  if not st.session_state.data_loaded:
522
- Β  Β  Β  Β  st.session_state.data_loader.load_data()
523
- Β  Β  Β  Β  st.session_state.data_loaded = True
524
- Β  Β Β 
525
- Β  Β  # Show current dataset status
526
- Β  Β  if st.session_state.data_loaded and st.session_state.data_loader.data is not None:
527
- Β  Β  Β  Β  data_info = st.session_state.data_loader.get_data_info()
528
- Β  Β  Β  Β  st.sidebar.success(f"πŸ“Š Dataset Loaded")
529
- Β  Β  Β  Β  st.sidebar.info(f"**Rows:** {data_info['shape'][0]}\n**Columns:** {data_info['shape'][1]}")
530
- Β  Β  Β  Β Β 
531
- Β  Β  Β  Β  # Show basic info about the dataset
532
- Β  Β  Β  Β  if 'Annual Income (k$)' in st.session_state.data_loader.data.columns:
533
- Β  Β  Β  Β  Β  Β  st.sidebar.write("**Dataset Type:** Mall Customers")
534
- Β  Β Β 
535
- Β  Β  # File upload option
536
- Β  Β  st.sidebar.markdown("### πŸ“ Upload Different Dataset")
537
- Β  Β  uploaded_file = st.sidebar.file_uploader("Choose a CSV file", type=['csv'])
538
- Β  Β Β 
539
- Β  Β  if uploaded_file is not None:
540
- Β  Β  Β  Β  try:
541
- Β  Β  Β  Β  Β  Β  data = pd.read_csv(uploaded_file)
542
- Β  Β  Β  Β  Β  Β  st.session_state.data_loader.data = data
543
- Β  Β  Β  Β  Β  Β  st.session_state.data_loaded = True
544
- Β  Β  Β  Β  Β  Β  st.session_state.data_preprocessed = FalseΒ  # Reset preprocessing
545
- Β  Β  Β  Β  Β  Β  st.session_state.clustering_done = {'kmeans': False, 'dbscan': False}Β  # Reset clustering
546
- Β  Β  Β  Β  Β  Β  st.sidebar.success("βœ… New file uploaded!")
547
- Β  Β  Β  Β  Β  Β  st.rerun()
548
- Β  Β  Β  Β  except Exception as e:
549
- Β  Β  Β  Β  Β  Β  st.sidebar.error(f"Error loading file: {e}")
550
- Β  Β Β 
551
- Β  Β  # Reload default dataset button
552
- Β  Β  if st.sidebar.button("πŸ”„ Reload Default Dataset"):
553
- Β  Β  Β  Β  st.session_state.data_loader.load_data()
554
- Β  Β  Β  Β  st.session_state.data_loaded = True
555
- Β  Β  Β  Β  st.session_state.data_preprocessed = False
556
- Β  Β  Β  Β  st.session_state.clustering_done = {'kmeans': False, 'dbscan': False}
557
- Β  Β  Β  Β  # Clear any cached clustering results
558
- Β  Β  Β  Β  st.session_state.clustering_analyzer = ClusteringAnalyzer()
559
- Β  Β  Β  Β  st.rerun()
560
- Β  Β Β 
561
- Β  Β  # Debug: Clear session state button (remove this after fixing)
562
- Β  Β  if st.sidebar.button("πŸ§ͺ Clear Session (Debug)"):
563
- Β  Β  Β  Β  for key in list(st.session_state.keys()):
564
- Β  Β  Β  Β  Β  Β  del st.session_state[key]
565
- Β  Β  Β  Β  st.rerun()
566
- Β  Β Β 
567
- Β  Β  # Tab content
568
- Β  Β  with tab1:
569
- Β  Β  Β  Β  show_home_page()
570
- Β  Β  with tab2:
571
- Β  Β  Β  Β  show_data_overview()
572
- Β  Β  with tab3:
573
- Β  Β  Β  Β  show_data_exploration()
574
- Β  Β  with tab4:
575
- Β  Β  Β  Β  show_preprocessing()
576
- Β  Β  with tab5:
577
- Β  Β  Β  Β  show_kmeans_clustering()
578
- Β  Β  with tab6:
579
- Β  Β  Β  Β  show_dbscan_clustering()
580
- Β  Β  with tab7:
581
- Β  Β  Β  Β  show_results_comparison()
582
- Β  Β  with tab8:
583
- Β  Β  Β  Β  show_business_insights()
584
-
585
- def show_home_page():
586
- Β  Β  """Display the home page."""
587
- Β  Β  st.markdown('<h2 class="sub-header">Welcome to Customer Segmentation Analysis</h2>', unsafe_allow_html=True)
588
- Β  Β Β 
589
- Β  Β  col1, col2, col3 = st.columns([1, 2, 1])
590
- Β  Β Β 
591
- Β  Β  with col2:
592
- Β  Β  Β  Β  st.markdown("""
593
- Β  Β  Β  Β  <div class="insight-box">
594
-         <h3>🎯 Project Overview</h3>
595
- Β  Β  Β  Β  <p>This application provides a comprehensive customer segmentation analysis using machine learning clustering algorithms.</p>
596
- Β  Β  Β  Β  </div>
597
- Β  Β  Β  Β  """, unsafe_allow_html=True)
598
- Β  Β Β 
599
- Β  Β  # Feature overview
600
- Β  Β  st.markdown("### πŸš€ Features")
601
- Β  Β Β 
602
- Β  Β  col1, col2, col3 = st.columns(3)
603
- Β  Β Β 
604
- Β  Β  with col1:
605
- Β  Β  Β  Β  st.markdown("""
606
- Β  Β  Β  Β  **πŸ“Š Data Analysis**
607
- Β  Β  Β  Β  - Interactive data exploration
608
- Β  Β  Β  Β  - Statistical summaries
609
- Β  Β  Β  Β  - Correlation analysis
610
- Β  Β  Β  Β  - Missing value detection
611
- Β  Β  Β  Β  """)
612
- Β  Β Β 
613
- Β  Β  with col2:
614
- Β  Β  Β  Β  st.markdown("""
615
-         **🎯 Clustering Algorithms**
616
- Β  Β  Β  Β  - K-Means clustering
617
- Β  Β  Β  Β  - DBSCAN clustering
618
- Β  Β  Β  Β  - Optimal cluster determination
619
- Β  Β  Β  Β  - Performance metrics
620
- Β  Β  Β  Β  """)
621
- Β  Β Β 
622
- Β  Β  with col3:
623
- Β  Β  Β  Β  st.markdown("""
624
- Β  Β  Β  Β  **πŸ“ˆ Visualizations**
625
- Β  Β  Β  Β  - 2D cluster plots
626
- Β  Β  Β  Β  - Distribution analysis
627
- Β  Β  Β  Β  - Comparative visualizations
628
- Β  Β  Β  Β  - Interactive charts
629
- Β  Β  Β  Β  """)
630
- Β  Β Β 
631
- Β  Β  # Getting started
632
-     st.markdown("### 🏁 Getting Started")
633
- Β  Β  st.markdown("""
634
- Β  Β  1. **πŸ“Š Data Overview**: Check your dataset information and statistics (automatically loaded from `data/Mall_Customers.csv`)
635
- Β  Β  2. **πŸ” Data Exploration**: Explore distributions, correlations, and relationships
636
- Β  Β  3. **βš™οΈ Preprocessing**: Select features and scale your data for clustering
637
-     4. **🎯 K-Means**: Apply K-Means clustering with optimal cluster determination
638
-     5. **🌟 DBSCAN**: Try density-based clustering for comparison
639
- Β  Β  6. **πŸ“ˆ Comparison**: Compare results from both algorithms
640
- Β  Β  7. **πŸ“‹ Insights**: Get business recommendations for each customer segment
641
- Β  Β  """)
642
- Β  Β Β 
643
- Β  Β  # Quick start note
644
- Β  Β  st.info("""
645
- Β  Β  πŸ’‘ **Quick Start**: Your dataset is automatically loaded from the `data/` folder.Β 
646
- Β  Β  Just click on the tabs above to start exploring and clustering your customer data!
647
- Β  Β  """)
648
- Β  Β Β 
649
- Β  Β  # Sample data info
650
- Β  Β  st.markdown("### πŸ“‹ Sample Dataset")
651
- Β  Β  st.info("""
652
- Β  Β  The sample dataset simulates mall customer data with the following features:
653
- Β  Β  - **CustomerID**: Unique identifier
654
- Β  Β  - **Gender**: Customer gender (Male/Female)
655
- Β  Β  - **Age**: Customer age (18-70 years)
656
- Β  Β  - **Annual Income (k$)**: Annual income in thousands
657
- Β  Β  - **Spending Score (1-100)**: Mall-assigned spending score
658
- Β  Β  """)
659
-
660
- def show_data_overview():
661
- Β  Β  """Display data overview page."""
662
- Β  Β  st.markdown('<h2 class="sub-header">πŸ“Š Data Overview</h2>', unsafe_allow_html=True)
663
- Β  Β Β 
664
- Β  Β  if not st.session_state.data_loaded:
665
-         st.warning("⚠️ Please load data first using the sidebar.")
666
- Β  Β  Β  Β  return
667
- Β  Β Β 
668
- Β  Β  data = st.session_state.data_loader.data
669
- Β  Β  data_info = st.session_state.data_loader.get_data_info()
670
- Β  Β Β 
671
- Β  Β  # Basic information
672
- Β  Β  col1, col2, col3, col4 = st.columns(4)
673
- Β  Β Β 
674
- Β  Β  with col1:
675
- Β  Β  Β  Β  st.metric("Total Customers", data_info['shape'][0])
676
- Β  Β  with col2:
677
- Β  Β  Β  Β  st.metric("Features", data_info['shape'][1])
678
- Β  Β  with col3:
679
- Β  Β  Β  Β  missing_values = sum(data_info['missing_values'].values())
680
- Β  Β  Β  Β  st.metric("Missing Values", missing_values)
681
- Β  Β  with col4:
682
- Β  Β  Β  Β  numeric_cols = len([col for col, dtype in data_info['dtypes'].items() if dtype in ['int64', 'float64']])
683
- Β  Β  Β  Β  st.metric("Numeric Features", numeric_cols)
684
- Β  Β Β 
685
- Β  Β  # Data preview
686
- Β  Β  st.subheader("πŸ“‹ Data Preview")
687
- Β  Β  st.dataframe(data.head(10), use_container_width=True)
688
- Β  Β Β 
689
- Β  Β  # Data types and missing values
690
- Β  Β  col1, col2 = st.columns(2)
691
- Β  Β Β 
692
- Β  Β  with col1:
693
- Β  Β  Β  Β  st.subheader("πŸ”§ Data Types")
694
- Β  Β  Β  Β  dtypes_df = pd.DataFrame(list(data_info['dtypes'].items()), columns=['Column', 'Data Type'])
695
- Β  Β  Β  Β  st.dataframe(dtypes_df, use_container_width=True)
696
- Β  Β Β 
697
- Β  Β  with col2:
698
- Β  Β  Β  Β  st.subheader("❓ Missing Values")
699
- Β  Β  Β  Β  missing_df = pd.DataFrame(list(data_info['missing_values'].items()), columns=['Column', 'Missing Count'])
700
- Β  Β  Β  Β  missing_df['Missing %'] = (missing_df['Missing Count'] / data_info['shape'][0] * 100).round(2)
701
- Β  Β  Β  Β  st.dataframe(missing_df, use_container_width=True)
702
- Β  Β Β 
703
- Β  Β  # Statistical summary
704
- Β  Β  st.subheader("πŸ“ˆ Statistical Summary")
705
- Β  Β  st.dataframe(data.describe(), use_container_width=True)
706
-
707
- def show_data_exploration():
708
- Β  Β  """Display data exploration page."""
709
- Β  Β  st.markdown('<h2 class="sub-header">πŸ” Data Exploration</h2>', unsafe_allow_html=True)
710
- Β  Β Β 
711
- Β  Β  if not st.session_state.data_loaded:
712
-         st.warning("⚠️ Please load data first using the sidebar.")
713
- Β  Β  Β  Β  return
714
- Β  Β Β 
715
- Β  Β  data = st.session_state.data_loader.data
716
- Β  Β  visualizer = st.session_state.visualizer
717
- Β  Β Β 
718
- Β  Β  # Generate exploration visualizations
719
- Β  Β  visualizer.plot_data_exploration(data)
720
-
721
- def show_preprocessing():
722
- Β  Β  """Display preprocessing page."""
723
- Β  Β  st.markdown('<h2 class="sub-header">βš™οΈ Data Preprocessing</h2>', unsafe_allow_html=True)
724
- Β  Β Β 
725
- Β  Β  if not st.session_state.data_loaded:
726
-         st.warning("⚠️ Please load data first using the sidebar.")
727
- Β  Β  Β  Β  return
728
- Β  Β Β 
729
- Β  Β  data = st.session_state.data_loader.data
730
- Β  Β Β 
731
- Β  Β  # Feature selection
732
-     st.subheader("🎯 Feature Selection")
733
- Β  Β Β 
734
- Β  Β  numeric_columns = data.select_dtypes(include=[np.number]).columns.tolist()
735
- Β  Β  if 'CustomerID' in numeric_columns:
736
- Β  Β  Β  Β  numeric_columns.remove('CustomerID')
737
- Β  Β Β 
738
- Β  Β  selected_features = st.multiselect(
739
- Β  Β  Β  Β  "Select features for clustering:",
740
- Β  Β  Β  Β  numeric_columns,
741
- Β  Β  Β  Β  default=['Annual Income (k$)', 'Spending Score (1-100)'] if all(col in numeric_columns for col in ['Annual Income (k$)', 'Spending Score (1-100)']) else numeric_columns[:2]
742
- Β  Β  )
743
- Β  Β Β 
744
- Β  Β  if len(selected_features) < 2:
745
-         st.error("⚠️ Please select at least 2 features for clustering.")
746
- Β  Β  Β  Β  return
747
- Β  Β Β 
748
- Β  Β  # Preprocessing options
749
- Β  Β  st.subheader("πŸ”§ Preprocessing Options")
750
- Β  Β Β 
751
- Β  Β  col1, col2 = st.columns(2)
752
- Β  Β  with col1:
753
- Β  Β  Β  Β  handle_missing = st.selectbox("Handle missing values:", ["Fill with mean", "Drop rows", "No action"])
754
- Β  Β  with col2:
755
- Β  Β  Β  Β  scaling_method = st.selectbox("Scaling method:", ["StandardScaler", "MinMaxScaler", "No scaling"])
756
- Β  Β Β 
757
- Β  Β  # Apply preprocessing
758
- Β  Β  if st.button("πŸš€ Apply Preprocessing"):
759
- Β  Β  Β  Β  scaled_data = st.session_state.data_loader.preprocess_data(selected_features)
760
- Β  Β  Β  Β Β 
761
- Β  Β  Β  Β  if scaled_data is not None:
762
- Β  Β  Β  Β  Β  Β  st.session_state.data_preprocessed = True
763
- Β  Β  Β  Β  Β  Β Β 
764
- Β  Β  Β  Β  Β  Β  # Show preprocessing results
765
- Β  Β  Β  Β  Β  Β  st.success("βœ… Data preprocessing completed!")
766
- Β  Β  Β  Β  Β  Β Β 
767
- Β  Β  Β  Β  Β  Β  col1, col2 = st.columns(2)
768
- Β  Β  Β  Β  Β  Β Β 
769
- Β  Β  Β  Β  Β  Β  with col1:
770
- Β  Β  Β  Β  Β  Β  Β  Β  st.subheader("πŸ“Š Original Data")
771
- Β  Β  Β  Β  Β  Β  Β  Β  st.dataframe(data[selected_features].head(), use_container_width=True)
772
- Β  Β  Β  Β  Β  Β Β 
773
- Β  Β  Β  Β  Β  Β  with col2:
774
- Β  Β  Β  Β  Β  Β  Β  Β  st.subheader("πŸ”„ Scaled Data")
775
- Β  Β  Β  Β  Β  Β  Β  Β  scaled_df = pd.DataFrame(scaled_data, columns=selected_features)
776
- Β  Β  Β  Β  Β  Β  Β  Β  st.dataframe(scaled_df.head(), use_container_width=True)
777
- Β  Β  Β  Β  Β  Β Β 
778
- Β  Β  Β  Β  Β  Β  # Feature statistics
779
- Β  Β  Β  Β  Β  Β  st.subheader("πŸ“ˆ Feature Statistics")
780
- Β  Β  Β  Β  Β  Β  col1, col2 = st.columns(2)
781
- Β  Β  Β  Β  Β  Β Β 
782
- Β  Β  Β  Β  Β  Β  with col1:
783
- Β  Β  Β  Β  Β  Β  Β  Β  st.write("**Original Data Statistics:**")
784
- Β  Β  Β  Β  Β  Β  Β  Β  st.dataframe(data[selected_features].describe(), use_container_width=True)
785
- Β  Β  Β  Β  Β  Β Β 
786
- Β  Β  Β  Β  Β  Β  with col2:
787
- Β  Β  Β  Β  Β  Β  Β  Β  st.write("**Scaled Data Statistics:**")
788
- Β  Β  Β  Β  Β  Β  Β  Β  st.dataframe(scaled_df.describe(), use_container_width=True)
789
-
790
- def show_kmeans_clustering():
791
- Β  Β  """Display K-Means clustering page."""
792
-     st.markdown('<h2 class="sub-header">🎯 K-Means Clustering</h2>', unsafe_allow_html=True)
793
- Β  Β Β 
794
- Β  Β  if not st.session_state.data_preprocessed:
795
-         st.warning("⚠️ Please preprocess data first.")
796
- Β  Β  Β  Β  return
797
- Β  Β Β 
798
- Β  Β  data_loader = st.session_state.data_loader
799
- Β  Β  clustering_analyzer = st.session_state.clustering_analyzer
800
- Β  Β  visualizer = st.session_state.visualizer
801
- Β  Β Β 
802
- Β  Β  # Optimal cluster determination
803
- Β  Β  st.subheader("πŸ” Optimal Cluster Determination")
804
- Β  Β Β 
805
- Β  Β  col1, col2 = st.columns([1, 1])
806
- Β  Β Β 
807
- Β  Β  with col1:
808
- Β  Β  Β  Β  max_clusters = st.slider("Maximum clusters to test:", 2, 15, 10)
809
- Β  Β Β 
810
- Β  Β  with col2:
811
- Β  Β  Β  Β  if st.button("πŸ” Find Optimal Clusters"):
812
- Β  Β  Β  Β  Β  Β  with st.spinner("Finding optimal number of clusters..."):
813
- Β  Β  Β  Β  Β  Β  Β  Β  optimization_results = clustering_analyzer.find_optimal_clusters(data_loader.scaled_data, max_clusters)
814
- Β  Β  Β  Β  Β  Β  Β  Β  if optimization_results:
815
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  visualizer.plot_optimization_results(optimization_results)
816
- Β  Β Β 
817
- Β  Β  # K-Means clustering
818
-     st.subheader("🎯 K-Means Clustering")
819
- Β  Β Β 
820
- Β  Β  col1, col2 = st.columns([1, 1])
821
- Β  Β Β 
822
- Β  Β  with col1:
823
- Β  Β  Β  Β  n_clusters = st.slider("Number of clusters:", 2, 10, clustering_analyzer.optimal_clusters or 5)
824
- Β  Β Β 
825
- Β  Β  with col2:
826
- Β  Β  Β  Β  if st.button("πŸš€ Apply K-Means"):
827
- Β  Β  Β  Β  Β  Β  # Clear any existing clustering results first to avoid column naming issues
828
- Β  Β  Β  Β  Β  Β  clustering_analyzer.cluster_labels = {}
829
- Β  Β  Β  Β  Β  Β  st.session_state.clustering_done = {'kmeans': False, 'dbscan': False}
830
- Β  Β  Β  Β  Β  Β Β 
831
- Β  Β  Β  Β  Β  Β  # Clear any cached data
832
- Β  Β  Β  Β  Β  Β  if hasattr(st.session_state, 'cluster_analysis_cache'):
833
- Β  Β  Β  Β  Β  Β  Β  Β  del st.session_state.cluster_analysis_cache
834
- Β  Β  Β  Β  Β  Β Β 
835
- Β  Β  Β  Β  Β  Β  with st.spinner("πŸ”„ Applying K-Means clustering..."):
836
- Β  Β  Β  Β  Β  Β  Β  Β  kmeans_results = clustering_analyzer.apply_kmeans(data_loader.scaled_data, n_clusters)
837
- Β  Β  Β  Β  Β  Β Β 
838
- Β  Β  Β  Β  Β  Β  if kmeans_results:
839
- Β  Β  Β  Β  Β  Β  Β  Β  st.session_state.clustering_done['kmeans'] = True
840
- Β  Β  Β  Β  Β  Β  Β  Β Β 
841
- Β  Β  Β  Β  Β  Β  Β  Β  # Display metrics
842
- Β  Β  Β  Β  Β  Β  Β  Β  col1, col2, col3 = st.columns(3)
843
- Β  Β  Β  Β  Β  Β  Β  Β  with col1:
844
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Silhouette Score", f"{kmeans_results['silhouette_score']:.3f}")
845
- Β  Β  Β  Β  Β  Β  Β  Β  with col2:
846
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Calinski-Harabasz Score", f"{kmeans_results['calinski_score']:.1f}")
847
- Β  Β  Β  Β  Β  Β  Β  Β  with col3:
848
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Inertia", f"{kmeans_results['inertia']:.1f}")
849
- Β  Β Β 
850
- Β  Β  # Visualizations
851
- Β  Β  if st.session_state.clustering_done['kmeans']:
852
- Β  Β  Β  Β  feature_data = data_loader.get_feature_data()
853
- Β  Β  Β  Β  kmeans_labels = clustering_analyzer.cluster_labels['kmeans']
854
- Β  Β  Β  Β Β 
855
- Β  Β  Β  Β  visualizer.plot_clusters(
856
- Β  Β  Β  Β  Β  Β  feature_data,Β 
857
- Β  Β  Β  Β  Β  Β  kmeans_labels,Β 
858
- Β  Β  Β  Β  Β  Β  'K-Means',
859
- Β  Β  Β  Β  Β  Β  data_loader.scaler,
860
- Β  Β  Β  Β  Β  Β  clustering_analyzer.kmeans_model.cluster_centers_
861
- Β  Β  Β  Β  )
862
- Β  Β  Β  Β Β 
863
- Β  Β  Β  Β  # Cluster analysis
864
- Β  Β  Β  Β  analysis_results = clustering_analyzer.analyze_clusters(feature_data, 'kmeans')
865
- Β  Β  Β  Β  if analysis_results:
866
- Β  Β  Β  Β  Β  Β  visualizer.plot_cluster_analysis(analysis_results, 'K-Means')
867
-
868
- def show_dbscan_clustering():
869
- Β  Β  """Display DBSCAN clustering page."""
870
-     st.markdown('<h2 class="sub-header">🌟 DBSCAN Clustering</h2>', unsafe_allow_html=True)
871
- Β  Β Β 
872
- Β  Β  if not st.session_state.data_preprocessed:
873
-         st.warning("⚠️ Please preprocess data first.")
874
- Β  Β  Β  Β  return
875
- Β  Β Β 
876
- Β  Β  data_loader = st.session_state.data_loader
877
- Β  Β  clustering_analyzer = st.session_state.clustering_analyzer
878
- Β  Β  visualizer = st.session_state.visualizer
879
- Β  Β Β 
880
- Β  Β  # DBSCAN parameters
881
- Β  Β  st.subheader("βš™οΈ DBSCAN Parameters")
882
- Β  Β Β 
883
- Β  Β  col1, col2 = st.columns(2)
884
- Β  Β Β 
885
- Β  Β  with col1:
886
- Β  Β  Β  Β  eps = st.slider("Epsilon (neighborhood distance):", 0.1, 2.0, 0.5, 0.1)
887
- Β  Β Β 
888
- Β  Β  with col2:
889
- Β  Β  Β  Β  min_samples = st.slider("Minimum samples per cluster:", 2, 20, 5)
890
- Β  Β Β 
891
- Β  Β  # Parameter guidance
892
- Β  Β  st.info("""
893
- Β  Β  **Parameter Guidance:**
894
- Β  Β  - **Epsilon**: Maximum distance between points in the same cluster. Smaller values create more clusters.
895
- Β  Β  - **Min Samples**: Minimum number of points required to form a cluster. Higher values create fewer, denser clusters.
896
- Β  Β  """)
897
- Β  Β Β 
898
- Β  Β  # Apply DBSCAN
899
- Β  Β  if st.button("πŸš€ Apply DBSCAN"):
900
- Β  Β  Β  Β  dbscan_results = clustering_analyzer.apply_dbscan(data_loader.scaled_data, eps, min_samples)
901
- Β  Β  Β  Β Β 
902
- Β  Β  Β  Β  if dbscan_results:
903
- Β  Β  Β  Β  Β  Β  st.session_state.clustering_done['dbscan'] = True
904
- Β  Β  Β  Β  Β  Β Β 
905
- Β  Β  Β  Β  Β  Β  # Display metrics
906
- Β  Β  Β  Β  Β  Β  col1, col2, col3 = st.columns(3)
907
- Β  Β  Β  Β  Β  Β  with col1:
908
- Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Number of Clusters", dbscan_results['n_clusters'])
909
- Β  Β  Β  Β  Β  Β  with col2:
910
- Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Noise Points", dbscan_results['n_noise'])
911
- Β  Β  Β  Β  Β  Β  with col3:
912
- Β  Β  Β  Β  Β  Β  Β  Β  if 'silhouette_score' in dbscan_results:
913
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Silhouette Score", f"{dbscan_results['silhouette_score']:.3f}")
914
- Β  Β  Β  Β  Β  Β  Β  Β  else:
915
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.metric("Silhouette Score", "N/A")
916
- Β  Β Β 
917
- Β  Β  # Visualizations
918
- Β  Β  if st.session_state.clustering_done['dbscan']:
919
- Β  Β  Β  Β  feature_data = data_loader.get_feature_data()
920
- Β  Β  Β  Β  dbscan_labels = clustering_analyzer.cluster_labels['dbscan']
921
- Β  Β  Β  Β Β 
922
- Β  Β  Β  Β  visualizer.plot_clusters(feature_data, dbscan_labels, 'DBSCAN')
923
- Β  Β  Β  Β Β 
924
- Β  Β  Β  Β  # Cluster analysis
925
- Β  Β  Β  Β  analysis_results = clustering_analyzer.analyze_clusters(feature_data, 'dbscan')
926
- Β  Β  Β  Β  if analysis_results:
927
- Β  Β  Β  Β  Β  Β  visualizer.plot_cluster_analysis(analysis_results, 'DBSCAN')
928
-
929
- def show_results_comparison():
930
- Β  Β  """Display results comparison page."""
931
- Β  Β  st.markdown('<h2 class="sub-header">πŸ“ˆ Results Comparison</h2>', unsafe_allow_html=True)
932
- Β  Β Β 
933
- οΏ½οΏ½ Β  if not (st.session_state.clustering_done['kmeans'] and st.session_state.clustering_done['dbscan']):
934
-         st.warning("⚠️ Please complete both K-Means and DBSCAN clustering first.")
935
- Β  Β  Β  Β  return
936
- Β  Β Β 
937
- Β  Β  data_loader = st.session_state.data_loader
938
- Β  Β  clustering_analyzer = st.session_state.clustering_analyzer
939
- Β  Β  visualizer = st.session_state.visualizer
940
- Β  Β Β 
941
- Β  Β  feature_data = data_loader.get_feature_data()
942
- Β  Β  kmeans_labels = clustering_analyzer.cluster_labels['kmeans']
943
- Β  Β  dbscan_labels = clustering_analyzer.cluster_labels['dbscan']
944
- Β  Β Β 
945
- Β  Β  # Comparison visualization
946
- Β  Β  visualizer.plot_comparison(feature_data, kmeans_labels, dbscan_labels)
947
- Β  Β Β 
948
- Β  Β  # Performance comparison
949
- Β  Β  st.subheader("πŸ“Š Performance Metrics Comparison")
950
- Β  Β Β 
951
- Β  Β  # Calculate metrics for both algorithms
952
- Β  Β  kmeans_analysis = clustering_analyzer.analyze_clusters(feature_data, 'kmeans')
953
- Β  Β  dbscan_analysis = clustering_analyzer.analyze_clusters(feature_data, 'dbscan')
954
- Β  Β Β 
955
- Β  Β  comparison_data = {
956
- Β  Β  Β  Β  'Metric': ['Number of Clusters', 'Silhouette Score', 'Noise Points', 'Largest Cluster Size'],
957
- Β  Β  Β  Β  'K-Means': [],Β 
958
- Β  Β  Β  Β  'DBSCAN': []
959
- Β  Β  }
960
- Β  Β Β 
961
- Β  Β  # Number of clusters
962
- Β  Β  comparison_data['K-Means'].append(len(set(kmeans_labels)))
963
- Β  Β  comparison_data['DBSCAN'].append(len(set(dbscan_labels)) - (1 if -1 in dbscan_labels else 0))
964
- Β  Β Β 
965
- Β  Β  # Silhouette scores (if available)
966
- Β  Β  try:
967
- Β  Β  Β  Β  from sklearn.metrics import silhouette_score
968
- Β  Β  Β  Β  kmeans_silhouette = silhouette_score(data_loader.scaled_data, kmeans_labels)
969
- Β  Β  Β  Β  comparison_data['K-Means'].append(f"{kmeans_silhouette:.3f}")
970
- Β  Β  Β  Β Β 
971
- Β  Β  Β  Β  # DBSCAN silhouette (excluding noise)
972
- Β  Β  Β  Β  if -1 in dbscan_labels:
973
- Β  Β  Β  Β  Β  Β  non_noise_mask = dbscan_labels != -1
974
- Β  Β  Β  Β  Β  Β  if np.sum(non_noise_mask) > 1:
975
- Β  Β  Β  Β  Β  Β  Β  Β  dbscan_silhouette = silhouette_score(data_loader.scaled_data[non_noise_mask],Β 
976
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β dbscan_labels[non_noise_mask])
977
- Β  Β  Β  Β  Β  Β  Β  Β  comparison_data['DBSCAN'].append(f"{dbscan_silhouette:.3f}")
978
- Β  Β  Β  Β  Β  Β  else:
979
- Β  Β  Β  Β  Β  Β  Β  Β  comparison_data['DBSCAN'].append("N/A")
980
- Β  Β  Β  Β  else:
981
- Β  Β  Β  Β  Β  Β  dbscan_silhouette = silhouette_score(data_loader.scaled_data, dbscan_labels)
982
- Β  Β  Β  Β  Β  Β  comparison_data['DBSCAN'].append(f"{dbscan_silhouette:.3f}")
983
- Β  Β  except:
984
- Β  Β  Β  Β  comparison_data['K-Means'].append("N/A")
985
- Β  Β  Β  Β  comparison_data['DBSCAN'].append("N/A")
986
- Β  Β Β 
987
- Β  Β  # Noise points
988
- Β  Β  comparison_data['K-Means'].append("0")
989
- Β  Β  comparison_data['DBSCAN'].append(str(list(dbscan_labels).count(-1)))
990
- Β  Β Β 
991
- Β  Β  # Largest cluster size
992
- Β  Β  kmeans_counts = pd.Series(kmeans_labels).value_counts()
993
- Β  Β  dbscan_counts = pd.Series(dbscan_labels).value_counts()
994
- Β  Β Β 
995
- Β  Β  comparison_data['K-Means'].append(str(kmeans_counts.max()))
996
- Β  Β  if -1 in dbscan_counts.index:
997
- Β  Β  Β  Β  dbscan_counts = dbscan_counts.drop(-1)Β  # Exclude noise
998
- Β  Β  comparison_data['DBSCAN'].append(str(dbscan_counts.max()) if len(dbscan_counts) > 0 else "0")
999
- Β  Β Β 
1000
- Β  Β  comparison_df = pd.DataFrame(comparison_data)
1001
- Β  Β  st.dataframe(comparison_df, use_container_width=True)
1002
-
1003
- def show_business_insights():
1004
- Β  Β  """Display business insights page."""
1005
- Β  Β  st.markdown('<h2 class="sub-header">πŸ“‹ Business Insights</h2>', unsafe_allow_html=True)
1006
- Β  Β Β 
1007
- Β  Β  if not st.session_state.clustering_done['kmeans']:
1008
-         st.warning("⚠️ Please complete K-Means clustering first to generate insights.")
1009
- Β  Β  Β  Β  return
1010
- Β  Β Β 
1011
- Β  Β  data_loader = st.session_state.data_loader
1012
- Β  Β  clustering_analyzer = st.session_state.clustering_analyzer
1013
- Β  Β Β 
1014
- Β  Β  feature_data = data_loader.get_feature_data()
1015
- Β  Β Β 
1016
- Β  Β  # Generate customer profiles
1017
- Β  Β  profiles = clustering_analyzer.get_cluster_profiles(feature_data, 'kmeans')
1018
- Β  Β Β 
1019
- Β  Β  if profiles:
1020
- Β  Β  Β  Β  st.subheader("πŸ‘₯ Customer Segment Profiles")
1021
- Β  Β  Β  Β Β 
1022
- Β  Β  Β  Β  for profile in profiles:
1023
-             with st.expander(f"🏷️ Cluster {profile['cluster']} - {profile.get('type', 'Unknown Type')}"):
1024
- Β  Β  Β  Β  Β  Β  Β  Β  col1, col2 = st.columns(2)
1025
- Β  Β  Β  Β  Β  Β  Β  Β Β 
1026
- Β  Β  Β  Β  Β  Β  Β  Β  with col1:
1027
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.markdown(f"**πŸ“Š Segment Overview**")
1028
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Size**: {profile['size']} customers ({profile['percentage']:.1f}%)")
1029
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'description' in profile:
1030
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Profile**: {profile['description']}")
1031
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β Β 
1032
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'avg_age' in profile:
1033
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Average Age**: {profile['avg_age']:.1f} Β± {profile['age_std']:.1f} years")
1034
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β Β 
1035
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'gender_dist' in profile:
1036
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Gender Distribution**: {profile['gender_dist']}")
1037
- Β  Β  Β  Β  Β  Β  Β  Β Β 
1038
- Β  Β  Β  Β  Β  Β  Β  Β  with col2:
1039
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.markdown(f"**πŸ’° Financial Profile**")
1040
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'avg_income' in profile:
1041
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Average Income**: ${profile['avg_income']:.1f}k Β± ${profile['income_std']:.1f}k")
1042
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β Β 
1043
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'avg_spending' in profile:
1044
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write(f"- **Average Spending Score**: {profile['avg_spending']:.1f} Β± {profile['spending_std']:.1f}")
1045
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β Β 
1046
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  # Business recommendations
1047
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.markdown(f"**πŸ“ˆ Recommendations**")
1048
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if 'avg_income' in profile and 'avg_spending' in profile:
1049
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  avg_income = profile['avg_income']
1050
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  avg_spending = profile['avg_spending']
1051
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β Β 
1052
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if avg_income > 70 and avg_spending > 70:
1053
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Focus on premium products and exclusive services")
1054
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Implement VIP loyalty programs")
1055
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Offer personalized shopping experiences")
1056
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  elif avg_income > 70 and avg_spending < 40:
1057
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Develop targeted upselling strategies")
1058
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Showcase value propositions")
1059
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Create incentive programs to increase spending")
1060
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  elif avg_income < 40 and avg_spending > 70:
1061
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Offer value-based products and promotions")
1062
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Focus on customer retention programs")
1063
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Provide flexible payment options")
1064
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  elif avg_income < 40 and avg_spending < 40:
1065
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Implement engagement and retention strategies")
1066
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Offer budget-friendly options")
1067
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Focus on building brand loyalty")
1068
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  else:
1069
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Balanced marketing approach")
1070
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Personalized offers based on preferences")
1071
- Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  st.write("- Regular engagement campaigns")
1072
- Β  Β  Β  Β Β 
1073
- Β  Β  Β  Β  # Overall business strategy
1074
-         st.subheader("🎯 Overall Business Strategy")
1075
- Β  Β  Β  Β Β 
1076
- Β  Β  Β  Β  col1, col2 = st.columns(2)
1077
- Β  Β  Β  Β Β 
1078
- Β  Β  Β  Β  with col1:
1079
- Β  Β  Β  Β  Β  Β  st.markdown("""
1080
-             **🎯 Marketing Strategies**
1081
- Β  Β  Β  Β  Β  Β  - **Segment-specific campaigns**: Tailor marketing messages to each cluster
1082
- Β  Β  Β  Β  Β  Β  - **Product positioning**: Align products with cluster preferences
1083
- Β  Β  Β  Β  Β  Β  - **Channel optimization**: Use preferred communication channels per segment
1084
- Β  Β  Β  Β  Β  Β  - **Pricing strategies**: Implement dynamic pricing based on segment characteristics
1085
- Β  Β  Β  Β  Β  Β  """)
1086
- Β  Β  Β  Β Β 
1087
- Β  Β  Β  Β  with col2:
1088
- Β  Β  Β  Β  Β  Β  st.markdown("""
1089
- Β  Β  Β  Β  Β  Β  **πŸ’‘ Growth Opportunities**
1090
- Β  Β  Β  Β  Β  Β  - **Cross-selling**: Identify products popular in high-spending segments
1091
- Β  Β  Β  Β  Β  Β  - **Retention programs**: Focus on segments with declining engagement
1092
- Β  Β  Β  Β  Β  Β  - **New product development**: Create offerings for underserved segments
1093
- Β  Β  Β  Β  Β  Β  - **Customer lifetime value**: Invest more in high-value segments
1094
- Β  Β  Β  Β  Β  Β  """)
1095
- Β  Β  Β  Β Β 
1096
- Β  Β  Β  Β  # Download results
1097
- Β  Β  Β  Β  st.subheader("πŸ’Ύ Download Results")
1098
- Β  Β  Β  Β Β 
1099
- Β  Β  Β  Β  # Prepare data for download
1100
- Β  Β  Β  Β  result_data = feature_data.copy()
1101
- Β  Β  Β  Β  result_data['KMeans_Cluster'] = clustering_analyzer.cluster_labels['kmeans']
1102
- Β  Β  Β  Β Β 
1103
- Β  Β  Β  Β  csv = result_data.to_csv(index=False)
1104
- Β  Β  Β  Β  st.download_button(
1105
- Β  Β  Β  Β  Β  Β  label="πŸ“₯ Download Customer Segments (CSV)",
1106
- Β  Β  Β  Β  Β  Β  data=csv,
1107
- Β  Β  Β  Β  Β  Β  file_name="customer_segments_results.csv",
1108
- Β  Β  Β  Β  Β  Β  mime="text/csv"
1109
- Β  Β  Β  Β  )
1110
 
1111
- if __name__ == "__main__":
1112
- Β  Β  main()
1113
- where are the insights
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Visualization Module
3
+ ===================
4
 
5
+ This module handles all visualization components for the customer segmentation analysis.
 
6
  """
7
 
8
+ # Matplotlib and Seaborn removed to avoid extra dependency
9
+ # All charts use Plotly for interactive visualization
10
+ import plotly.express as px
11
+ import plotly.graph_objects as go
12
+ from plotly.subplots import make_subplots
13
+ import plotly.io as pio
14
  import pandas as pd
15
  import numpy as np
16
+ import streamlit as st
 
 
 
 
 
 
 
 
17
 
18
+ # Global Plotly template: dark backgrounds to match app theme
 
 
 
 
 
 
 
19
  pio.templates.default = "plotly_dark"
20
+ pio.templates["plotly_dark"].layout.update(
21
+ paper_bgcolor="#0F172A",
22
+ plot_bgcolor="#0F172A",
23
+ font=dict(color="#E5E7EB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  )
25
 
26
+ # Plot styling handled via Plotly theme settings per figure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ class Visualizer:
29
+ """
30
+ Handles all visualizations for customer segmentation analysis.
31
+ """
32
+
33
+ def __init__(self):
34
+ # Enhanced color palettes for better visual appeal
35
+ self.colors = px.colors.qualitative.Set1 # More vibrant colors
36
+ self.gradient_colors = [
37
+ '#FF6B6B', # Coral Red
38
+ '#4ECDC4', # Turquoise
39
+ '#45B7D1', # Sky Blue
40
+ '#96CEB4', # Mint Green
41
+ '#FFEAA7', # Warm Yellow
42
+ '#DDA0DD', # Plum
43
+ '#98D8C8', # Seafoam
44
+ '#F7DC6F', # Golden Yellow
45
+ '#BB8FCE', # Lavender
46
+ '#85C1E9' # Light Blue
47
+ ]
48
+ self.modern_colors = [
49
+ '#6C5CE7', # Purple
50
+ '#00B894', # Green
51
+ '#E17055', # Orange
52
+ '#0984E3', # Blue
53
+ '#FDCB6E', # Yellow
54
+ '#E84393', # Pink
55
+ '#00CEC9', # Cyan
56
+ '#A29BFE', # Light Purple
57
+ '#FD79A8', # Light Pink
58
+ '#81ECEC' # Light Cyan
59
+ ]
60
+
61
+ def plot_data_exploration(self, data):
62
+ """Create comprehensive data exploration plots with enhanced styling."""
63
+ if data is None:
64
+ st.error("❌ No data available for visualization.")
65
+ return
66
+
67
+ # Debug: Show data info
68
+ st.info(f"πŸ” **Data shape:** {data.shape}")
69
+ st.info(f"πŸ” **Data columns:** {list(data.columns)}")
70
+
71
+ st.subheader("πŸ“Š Data Distribution Analysis")
72
+
73
+ # Create subplots for different visualizations
74
+ col1, col2 = st.columns(2)
75
+
76
+ with col1:
77
+ # Age distribution with enhanced styling
78
+ if 'Age' in data.columns:
79
+ st.write("πŸ“Š Creating Age distribution plot...")
80
+ fig_age = px.histogram(
81
+ data, x='Age', nbins=20,
82
+ title='πŸ‘₯ Age Distribution',
83
+ color_discrete_sequence=[self.gradient_colors[0]]
84
+ )
85
+ fig_age.update_layout(
86
+ height=450,
87
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
88
+ plot_bgcolor='#0F172A',
89
+ paper_bgcolor='#0F172A',
90
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
91
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
92
+ )
93
+ fig_age.update_traces(marker=dict(line=dict(width=1, color='white')))
94
+ st.plotly_chart(fig_age, use_container_width=True, theme=None)
95
+ st.success("βœ… Age distribution plot created!")
96
+
97
+ # Income distribution with enhanced styling
98
+ if 'Annual Income (k$)' in data.columns:
99
+ st.write("πŸ’° Creating Income distribution plot...")
100
+ fig_income = px.histogram(
101
+ data, x='Annual Income (k$)', nbins=20,
102
+ title='πŸ’° Annual Income Distribution',
103
+ color_discrete_sequence=[self.gradient_colors[1]]
104
+ )
105
+ fig_income.update_layout(
106
+ height=450,
107
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
108
+ plot_bgcolor='#0F172A',
109
+ paper_bgcolor='#0F172A',
110
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
111
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
112
+ )
113
+ fig_income.update_traces(marker=dict(line=dict(width=1, color='white')))
114
+ st.plotly_chart(fig_income, use_container_width=True, theme=None)
115
+ st.success("βœ… Income distribution plot created!")
116
+
117
+ with col2:
118
+ # Spending Score distribution with enhanced styling
119
+ if 'Spending Score (1-100)' in data.columns:
120
+ st.write("πŸ›οΈ Creating Spending Score distribution plot...")
121
+ fig_spending = px.histogram(
122
+ data, x='Spending Score (1-100)', nbins=20,
123
+ title='πŸ›οΈ Spending Score Distribution',
124
+ color_discrete_sequence=[self.gradient_colors[2]]
125
+ )
126
+ fig_spending.update_layout(
127
+ height=450,
128
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
129
+ plot_bgcolor='#0F172A',
130
+ paper_bgcolor='#0F172A',
131
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
132
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
133
+ )
134
+ fig_spending.update_traces(marker=dict(line=dict(width=1, color='white')))
135
+ st.plotly_chart(fig_spending, use_container_width=True, theme=None)
136
+ st.success("βœ… Spending Score distribution plot created!")
137
+
138
+ # Gender distribution with enhanced styling
139
+ if 'Gender' in data.columns:
140
+ gender_counts = data['Gender'].value_counts()
141
+ fig_gender = px.pie(
142
+ values=gender_counts.values,
143
+ names=gender_counts.index,
144
+ title='πŸ‘« Gender Distribution',
145
+ color_discrete_sequence=self.modern_colors[:len(gender_counts)]
146
+ )
147
+ fig_gender.update_layout(
148
+ height=450,
149
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
150
+ plot_bgcolor='#0F172A',
151
+ paper_bgcolor='#0F172A'
152
+ )
153
+ fig_gender.update_traces(
154
+ textposition='inside',
155
+ textinfo='percent+label',
156
+ textfont_size=14,
157
+ marker=dict(line=dict(color='white', width=2))
158
+ )
159
+ st.plotly_chart(fig_gender, use_container_width=True)
160
+
161
+ # Enhanced correlation analysis
162
+ st.subheader("πŸ”— Feature Correlations")
163
+ numeric_cols = data.select_dtypes(include=[np.number]).columns
164
+ if len(numeric_cols) > 1:
165
+ corr_matrix = data[numeric_cols].corr()
166
+ fig_corr = px.imshow(
167
+ corr_matrix,
168
+ text_auto=True,
169
+ title='πŸ”— Feature Correlation Matrix',
170
+ color_continuous_scale='RdYlBu',
171
+ aspect='auto'
172
+ )
173
+ fig_corr.update_layout(
174
+ height=500,
175
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
176
+ plot_bgcolor='#0F172A',
177
+ paper_bgcolor='#0F172A',
178
+ font=dict(size=12, color='#E5E7EB')
179
+ )
180
+ fig_corr.update_traces(
181
+ textfont=dict(size=12, color='#E5E7EB'),
182
+ hoverongaps=False
183
+ )
184
+ st.plotly_chart(fig_corr, theme=None, use_container_width=True)
185
+
186
+ # Enhanced scatter plots
187
+ st.subheader("πŸ” Feature Relationships")
188
+ col1, col2 = st.columns(2)
189
+
190
+ with col1:
191
+ if 'Annual Income (k$)' in data.columns and 'Spending Score (1-100)' in data.columns:
192
+ fig_scatter1 = px.scatter(
193
+ data, x='Annual Income (k$)', y='Spending Score (1-100)',
194
+ title='πŸ’° Income vs Spending Score',
195
+ hover_data=['Age'] if 'Age' in data.columns else None,
196
+ color_discrete_sequence=[self.modern_colors[3]]
197
+ )
198
+ fig_scatter1.update_layout(
199
+ height=450,
200
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
201
+ plot_bgcolor='#0F172A',
202
+ paper_bgcolor='#0F172A',
203
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
204
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
205
+ )
206
+ fig_scatter1.update_traces(
207
+ marker=dict(size=8, opacity=0.7, line=dict(width=1, color='white'))
208
+ )
209
+ st.plotly_chart(fig_scatter1, use_container_width=True)
210
+
211
+ with col2:
212
+ if 'Age' in data.columns and 'Spending Score (1-100)' in data.columns:
213
+ fig_scatter2 = px.scatter(
214
+ data, x='Age', y='Spending Score (1-100)',
215
+ title='πŸ‘₯ Age vs Spending Score',
216
+ hover_data=['Annual Income (k$)'] if 'Annual Income (k$)' in data.columns else None,
217
+ color_discrete_sequence=[self.modern_colors[4]]
218
+ )
219
+ fig_scatter2.update_layout(
220
+ height=450,
221
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
222
+ plot_bgcolor='#0F172A',
223
+ paper_bgcolor='#0F172A',
224
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
225
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
226
+ )
227
+ fig_scatter2.update_traces(
228
+ marker=dict(size=8, opacity=0.7, line=dict(width=1, color='white'))
229
+ )
230
+ st.plotly_chart(fig_scatter2, use_container_width=True)
231
+
232
+ def plot_optimization_results(self, results):
233
+ """Plot cluster optimization results."""
234
+ if results is None:
235
+ st.error("No optimization results available.")
236
+ return
237
+
238
+ # Create subplots
239
+ fig = make_subplots(
240
+ rows=1, cols=3,
241
+ subplot_titles=('Elbow Method', 'Silhouette Score', 'Calinski-Harabasz Score'),
242
+ specs=[[{"secondary_y": False}, {"secondary_y": False}, {"secondary_y": False}]]
243
+ )
244
+
245
+ cluster_range = results['cluster_range']
246
+
247
+ # Elbow method
248
+ fig.add_trace(
249
+ go.Scatter(x=cluster_range, y=results['inertias'],
250
+ mode='lines+markers', name='Inertia',
251
+ line=dict(color='blue')),
252
+ row=1, col=1
253
+ )
254
+
255
+ # Silhouette score
256
+ fig.add_trace(
257
+ go.Scatter(x=cluster_range, y=results['silhouette_scores'],
258
+ mode='lines+markers', name='Silhouette Score',
259
+ line=dict(color='red')),
260
+ row=1, col=2
261
+ )
262
+
263
+ # Calinski-Harabasz score
264
+ fig.add_trace(
265
+ go.Scatter(x=cluster_range, y=results['calinski_scores'],
266
+ mode='lines+markers', name='Calinski-Harabasz Score',
267
+ line=dict(color='green')),
268
+ row=1, col=3
269
+ )
270
+
271
+ # Update layout
272
+ fig.update_layout(
273
+ title_text="Cluster Optimization Results",
274
+ height=400,
275
+ showlegend=False,
276
+ paper_bgcolor="#0F172A",
277
+ plot_bgcolor="#0F172A",
278
+ font=dict(color="#E5E7EB")
279
+ )
280
+
281
+ fig.update_xaxes(title_text="Number of Clusters")
282
+ fig.update_yaxes(title_text="Inertia", row=1, col=1)
283
+ fig.update_yaxes(title_text="Silhouette Score", row=1, col=2)
284
+ fig.update_yaxes(title_text="Calinski-Harabasz Score", row=1, col=3)
285
+
286
+ st.plotly_chart(fig, theme=None, use_container_width=True)
287
+
288
+ # Display optimal results
289
+ col1, col2, col3 = st.columns(3)
290
+ with col1:
291
+ st.metric("Optimal Clusters (Silhouette)", results['optimal_silhouette'])
292
+ with col2:
293
+ st.metric("Optimal Clusters (Calinski-Harabasz)", results['optimal_calinski'])
294
+ with col3:
295
+ st.metric("Recommended", results['optimal_silhouette'])
296
+
297
+ def plot_clusters(self, data, cluster_labels, algorithm='K-Means', scaler=None, centers=None):
298
+ """Plot cluster visualizations."""
299
+ if data is None or cluster_labels is None:
300
+ st.error("No data or cluster labels available for visualization.")
301
+ return
302
+
303
+ # Prepare data with clusters
304
+ plot_data = data.copy()
305
+ plot_data['Cluster'] = cluster_labels
306
+
307
+ # Main clustering visualization
308
+ st.subheader(f"🎯 {algorithm} Clustering Results")
309
+
310
+ col1, col2 = st.columns(2)
311
+
312
+ with col1:
313
+ if 'Annual Income (k$)' in data.columns and 'Spending Score (1-100)' in data.columns:
314
+ fig_main = px.scatter(plot_data,
315
+ x='Annual Income (k$)',
316
+ y='Spending Score (1-100)',
317
+ color='Cluster',
318
+ title=f'{algorithm}: Income vs Spending Score',
319
+ hover_data=['Age'] if 'Age' in data.columns else None,
320
+ color_discrete_sequence=self.colors)
321
+
322
+ # Add cluster centers if available
323
+ if centers is not None and scaler is not None:
324
+ centers_original = scaler.inverse_transform(centers)
325
+ centers_df = pd.DataFrame(centers_original,
326
+ columns=['Annual Income (k$)', 'Spending Score (1-100)'])
327
+ centers_df['Cluster'] = range(len(centers_df))
328
+
329
+ fig_main.add_scatter(x=centers_df['Annual Income (k$)'],
330
+ y=centers_df['Spending Score (1-100)'],
331
+ mode='markers',
332
+ marker=dict(symbol='x', size=15, color='red', line=dict(width=2)),
333
+ name='Centers',
334
+ showlegend=True)
335
+
336
+ fig_main.update_layout(
337
+ height=500,
338
+ paper_bgcolor="#0F172A",
339
+ plot_bgcolor="#0F172A",
340
+ font=dict(color="#E5E7EB"),
341
+ xaxis=dict(gridcolor="rgba(229,231,235,0.12)"),
342
+ yaxis=dict(gridcolor="rgba(229,231,235,0.12)")
343
+ )
344
+ st.plotly_chart(fig_main, theme=None, use_container_width=True)
345
+
346
+ with col2:
347
+ if 'Age' in data.columns and 'Spending Score (1-100)' in data.columns:
348
+ fig_age = px.scatter(plot_data,
349
+ x='Age',
350
+ y='Spending Score (1-100)',
351
+ color='Cluster',
352
+ title=f'{algorithm}: Age vs Spending Score',
353
+ color_discrete_sequence=self.colors)
354
+ fig_age.update_layout(
355
+ height=500,
356
+ paper_bgcolor="#0F172A",
357
+ plot_bgcolor="#0F172A",
358
+ font=dict(color="#E5E7EB"),
359
+ xaxis=dict(gridcolor="rgba(229,231,235,0.12)"),
360
+ yaxis=dict(gridcolor="rgba(229,231,235,0.12)")
361
+ )
362
+ st.plotly_chart(fig_age, theme=None, use_container_width=True)
363
+
364
+ # Enhanced cluster distribution
365
+ st.subheader("πŸ“Š Cluster Distribution")
366
+ cluster_counts = pd.Series(cluster_labels).value_counts().sort_index()
367
+
368
+ fig_dist = px.bar(
369
+ x=cluster_counts.index, y=cluster_counts.values,
370
+ title='πŸ“Š Number of Customers per Cluster',
371
+ labels={'x': 'Cluster', 'y': 'Number of Customers'},
372
+ color=cluster_counts.values,
373
+ color_continuous_scale='Turbo'
374
+ )
375
+ fig_dist.update_layout(
376
+ height=450,
377
+ title=dict(font=dict(size=18, color='#E5E7EB'), x=0.5),
378
+ plot_bgcolor='#0F172A',
379
+ paper_bgcolor='#0F172A',
380
+ xaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB')),
381
+ yaxis=dict(gridcolor='rgba(229,231,235,0.12)', title_font=dict(size=14, color='#E5E7EB'))
382
+ )
383
+ fig_dist.update_traces(
384
+ marker=dict(line=dict(width=1, color='white'))
385
+ )
386
+ st.plotly_chart(fig_dist, theme=None, use_container_width=True)
387
+
388
+ def plot_cluster_analysis(self, analysis_results, algorithm='K-Means'):
389
+ """Plot detailed cluster analysis with enhanced visualizations."""
390
+ if analysis_results is None:
391
+ st.error("❌ No analysis results available.")
392
+ return
393
+
394
+ try:
395
+ data_with_clusters = analysis_results['data_with_clusters']
396
+ spending_analysis = analysis_results['spending_analysis']
397
+
398
+ # COMPLETELY REWRITTEN: Find cluster column with bulletproof detection
399
+ available_columns = list(data_with_clusters.columns)
400
+ st.info(f"πŸ” **Available columns in data:** {available_columns}")
401
+
402
+ # Find ANY column that contains 'cluster' (case insensitive)
403
+ cluster_columns = [col for col in available_columns if 'cluster' in col.lower()]
404
+ st.info(f"🎯 **Found cluster columns:** {cluster_columns}")
405
+
406
+ if not cluster_columns:
407
+ st.error("❌ No cluster column found in the data!")
408
+ st.write("Available columns:", available_columns)
409
+ st.write("Please ensure clustering has been performed first.")
410
+ return
411
+
412
+ # Use the first cluster column found
413
+ cluster_col = cluster_columns[0]
414
+ st.success(f"βœ… **Using cluster column:** `{cluster_col}`")
415
+
416
+ # EXTRA SAFETY: Ensure the column actually exists before proceeding
417
+ if cluster_col not in data_with_clusters.columns:
418
+ st.error(f"❌ Column `{cluster_col}` not found in data!")
419
+ st.write("This should not happen. Please report this bug.")
420
+ return
421
+
422
+ # Create a beautiful header with metrics
423
+ st.markdown(f"""
424
+ <div style="
425
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
426
+ padding: 2rem;
427
+ border-radius: 15px;
428
+ color: white;
429
+ text-align: center;
430
+ margin: 2rem 0;
431
+ box-shadow: 0 10px 25px rgba(0,0,0,0.1);
432
+ ">
433
+ <h2 style="margin: 0; font-size: 2.5rem; font-weight: 700;">πŸ“ˆ {algorithm} Cluster Analysis</h2>
434
+ <p style="margin: 0.5rem 0 0 0; font-size: 1.2rem; opacity: 0.9;">Interactive Cluster Visualization & Analysis</p>
435
+ </div>
436
+ """, unsafe_allow_html=True)
437
+
438
+ # Quick stats
439
+ num_clusters = len(data_with_clusters[cluster_col].unique())
440
+ total_customers = len(data_with_clusters)
441
+
442
+ metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
443
+ with metric_col1:
444
+ st.metric("🎯 Total Clusters", num_clusters)
445
+ with metric_col2:
446
+ st.metric("πŸ‘₯ Total Customers", total_customers)
447
+ with metric_col3:
448
+ avg_cluster_size = total_customers / num_clusters
449
+ st.metric("πŸ“Š Avg Cluster Size", f"{avg_cluster_size:.0f}")
450
+ with metric_col4:
451
+ if 'Spending Score (1-100)' in data_with_clusters.columns:
452
+ avg_spending = data_with_clusters['Spending Score (1-100)'].mean()
453
+ st.metric("πŸ’° Avg Spending", f"{avg_spending:.1f}")
454
+
455
+ st.markdown("---")
456
+
457
+ # Enhanced Box plots with better styling
458
+ st.subheader("πŸ“Š Distribution Analysis")
459
+ col1, col2 = st.columns(2)
460
+
461
+ with col1:
462
+ if 'Spending Score (1-100)' in data_with_clusters.columns:
463
+ # Convert cluster column to string to ensure proper categorical handling
464
+ plot_data = data_with_clusters.copy()
465
+ plot_data[cluster_col] = plot_data[cluster_col].astype(str)
466
+
467
+ # DEBUG: Show exactly what we're passing to plotly
468
+ st.write(f"πŸ” **DEBUG - About to create box plot with:**")
469
+ st.write(f"- x column: `{cluster_col}`")
470
+ st.write(f"- Columns in plot_data: {list(plot_data.columns)}")
471
+ st.write(f"- First few rows of plot_data:")
472
+ st.dataframe(plot_data.head(3))
473
+
474
+ fig_spending_box = px.box(
475
+ plot_data,
476
+ x=cluster_col,
477
+ y='Spending Score (1-100)',
478
+ title='πŸ’° Spending Score Distribution by Cluster',
479
+ color=cluster_col,
480
+ color_discrete_sequence=self.modern_colors
481
+ )
482
+
483
+ # Enhanced styling for maximum visibility
484
+ fig_spending_box.update_layout(
485
+ height=600,
486
+ title=dict(
487
+ text='πŸ’° Spending Score Distribution by Cluster',
488
+ font=dict(size=20, color='#E5E7EB'),
489
+ x=0.5,
490
+ y=0.95
491
+ ),
492
+ plot_bgcolor='#0F172A',
493
+ paper_bgcolor='#0F172A',
494
+ font=dict(size=14, family="Arial, sans-serif", color='#E5E7EB'),
495
+ xaxis=dict(
496
+ title=dict(text='Cluster', font=dict(size=16, color='#E5E7EB')),
497
+ tickfont=dict(size=14, color='#E5E7EB'),
498
+ gridcolor='rgba(229,231,235,0.12)',
499
+ gridwidth=1,
500
+ showgrid=True
501
+ ),
502
+ yaxis=dict(
503
+ title=dict(text='Spending Score', font=dict(size=16, color='#E5E7EB')),
504
+ tickfont=dict(size=14, color='#E5E7EB'),
505
+ gridcolor='rgba(229,231,235,0.12)',
506
+ gridwidth=1,
507
+ showgrid=True
508
+ ),
509
+ showlegend=False,
510
+ margin=dict(t=80, b=60, l=60, r=40)
511
+ )
512
+
513
+ fig_spending_box.update_traces(
514
+ marker=dict(size=6, opacity=0.8),
515
+ line=dict(width=3),
516
+ fillcolor='rgba(0,0,0,0)',
517
+ boxpoints='outliers'
518
+ )
519
+
520
+ st.plotly_chart(fig_spending_box, theme=None, use_container_width=True)
521
+
522
+ with col2:
523
+ if 'Annual Income (k$)' in data_with_clusters.columns:
524
+ # Convert cluster column to string to ensure proper categorical handling
525
+ plot_data = data_with_clusters.copy()
526
+ plot_data[cluster_col] = plot_data[cluster_col].astype(str)
527
+
528
+ fig_income_box = px.box(
529
+ plot_data,
530
+ x=cluster_col,
531
+ y='Annual Income (k$)',
532
+ title='πŸ’΅ Income Distribution by Cluster',
533
+ color=cluster_col,
534
+ color_discrete_sequence=self.modern_colors
535
+ )
536
+
537
+ # Enhanced styling for maximum visibility
538
+ fig_income_box.update_layout(
539
+ height=600,
540
+ title=dict(
541
+ text='πŸ’΅ Annual Income Distribution by Cluster',
542
+ font=dict(size=20, color='#E5E7EB'),
543
+ x=0.5,
544
+ y=0.95
545
+ ),
546
+ plot_bgcolor='#0F172A',
547
+ paper_bgcolor='#0F172A',
548
+ font=dict(size=14, family="Arial, sans-serif", color='#E5E7EB'),
549
+ xaxis=dict(
550
+ title=dict(text='Cluster', font=dict(size=16, color='#E5E7EB')),
551
+ tickfont=dict(size=14, color='#E5E7EB'),
552
+ gridcolor='rgba(229,231,235,0.12)',
553
+ gridwidth=1,
554
+ showgrid=True
555
+ ),
556
+ yaxis=dict(
557
+ title=dict(text='Annual Income (k$)', font=dict(size=16, color='#E5E7EB')),
558
+ tickfont=dict(size=14, color='#E5E7EB'),
559
+ gridcolor='rgba(229,231,235,0.12)',
560
+ gridwidth=1,
561
+ showgrid=True
562
+ ),
563
+ showlegend=False,
564
+ margin=dict(t=80, b=60, l=60, r=40)
565
+ )
566
+
567
+ fig_income_box.update_traces(
568
+ marker=dict(size=6, opacity=0.8),
569
+ line=dict(width=3),
570
+ fillcolor='rgba(0,0,0,0)',
571
+ boxpoints='outliers'
572
+ )
573
+
574
+ st.plotly_chart(fig_income_box, theme=None, use_container_width=True)
575
+
576
+ # Average spending per cluster with stunning visualization
577
+ if spending_analysis is not None:
578
+ st.markdown("---")
579
+
580
+ # Beautiful section header
581
+ st.markdown(f"""
582
+ <div style="
583
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
584
+ padding: 1.5rem;
585
+ border-radius: 15px;
586
+ color: white;
587
+ text-align: center;
588
+ margin: 2rem 0 1rem 0;
589
+ box-shadow: 0 8px 20px rgba(240, 147, 251, 0.3);
590
+ ">
591
+ <h3 style="margin: 0; font-size: 1.8rem; font-weight: 600;">πŸ’° Average Spending Analysis</h3>
592
+ </div>
593
+ """, unsafe_allow_html=True)
594
+
595
+ # Create stunning bar chart with enhanced colors
596
+ fig_avg_spending = px.bar(
597
+ x=spending_analysis.index.astype(str),
598
+ y=spending_analysis['mean'],
599
+ title='πŸ“Š Average Spending Score by Cluster',
600
+ labels={'x': 'Cluster', 'y': 'Average Spending Score'},
601
+ error_y=spending_analysis['std'],
602
+ color=spending_analysis['mean'],
603
+ color_continuous_scale='Viridis'
604
+ )
605
+
606
+ # Ultra-enhanced styling
607
+ fig_avg_spending.update_layout(
608
+ height=650,
609
+ title=dict(
610
+ text='πŸ“Š Average Spending Score by Cluster',
611
+ font=dict(size=24, color='#E5E7EB', family="Arial Black"),
612
+ x=0.5,
613
+ y=0.95
614
+ ),
615
+ plot_bgcolor='#0F172A',
616
+ paper_bgcolor='#0F172A',
617
+ font=dict(size=16, family="Arial, sans-serif", color='#E5E7EB'),
618
+ xaxis=dict(
619
+ title=dict(text='Cluster', font=dict(size=18, color='#E5E7EB')),
620
+ tickfont=dict(size=16, color='#E5E7EB'),
621
+ gridcolor='rgba(229,231,235,0.12)',
622
+ gridwidth=1,
623
+ showgrid=True,
624
+ zeroline=False
625
+ ),
626
+ yaxis=dict(
627
+ title=dict(text='Average Spending Score', font=dict(size=18, color='#E5E7EB')),
628
+ tickfont=dict(size=16, color='#E5E7EB'),
629
+ gridcolor='rgba(229,231,235,0.12)',
630
+ gridwidth=1,
631
+ showgrid=True,
632
+ zeroline=False
633
+ ),
634
+ showlegend=False,
635
+ margin=dict(t=100, b=80, l=80, r=80)
636
+ )
637
+
638
+ # Add stylish value labels on bars
639
+ for i, (cluster, value) in enumerate(zip(spending_analysis.index, spending_analysis['mean'])):
640
+ fig_avg_spending.add_annotation(
641
+ x=str(cluster),
642
+ y=value + spending_analysis.loc[cluster, 'std'] + 5,
643
+ text=f'<b>{value:.1f}</b>',
644
+ showarrow=False,
645
+ font=dict(size=16, color='white', family="Arial Black"),
646
+ bgcolor='rgba(44, 62, 80, 0.9)',
647
+ bordercolor='rgba(44, 62, 80, 1)',
648
+ borderwidth=2,
649
+ borderpad=8
650
+ )
651
+
652
+ # Enhance the bars themselves
653
+ fig_avg_spending.update_traces(
654
+ marker=dict(
655
+ line=dict(width=2, color='rgba(44, 62, 80, 0.8)'),
656
+ opacity=0.9
657
+ ),
658
+ width=0.6
659
+ )
660
+
661
+ st.plotly_chart(fig_avg_spending, theme=None, use_container_width=True)
662
+
663
+ # Beautiful cluster insights table
664
+ st.markdown("""
665
+ <div style="
666
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
667
+ padding: 1.5rem;
668
+ border-radius: 15px;
669
+ color: white;
670
+ text-align: center;
671
+ margin: 2rem 0 1rem 0;
672
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
673
+ ">
674
+ <h3 style="margin: 0; font-size: 1.8rem; font-weight: 600;">πŸ“‹ Detailed Cluster Statistics</h3>
675
+ </div>
676
+ """, unsafe_allow_html=True)
677
+
678
+ summary_df = spending_analysis.round(2)
679
+ summary_df.columns = ['🎯 Avg Spending', 'πŸ“Š Std Dev', 'πŸ“‰ Min', 'πŸ“ˆ Max', 'πŸ‘₯ Count']
680
+
681
+ # Create a Plotly table instead of using background_gradient
682
+ fig_table = go.Figure(data=[go.Table(
683
+ header=dict(
684
+ values=list(summary_df.columns),
685
+ fill_color='#1F2937',
686
+ font=dict(color='#E5E7EB', size=14, family='Inter'),
687
+ align='center',
688
+ height=40
689
+ ),
690
+ cells=dict(
691
+ values=[summary_df[col] for col in summary_df.columns],
692
+ fill_color='#0F172A',
693
+ font=dict(color='#E5E7EB', size=12, family='Inter'),
694
+ align='center',
695
+ height=35,
696
+ format=[None, '.2f', '.2f', '.2f', '.2f', '.0f']
697
+ )
698
+ )])
699
+
700
+ fig_table.update_layout(
701
+ height=300,
702
+ title=dict(
703
+ text='πŸ“Š Cluster Spending Analysis',
704
+ font=dict(size=18, color='#E5E7EB', family='Inter'),
705
+ x=0.5
706
+ ),
707
+ plot_bgcolor='#0F172A',
708
+ paper_bgcolor='#0F172A',
709
+ margin=dict(t=60, b=20, l=20, r=20)
710
+ )
711
+ st.plotly_chart(fig_table, use_container_width=True, theme=None)
712
+
713
+ except Exception as e:
714
+ st.error(f"❌ Error in cluster analysis visualization: {str(e)}")
715
+ st.write("Please try the 'Clear Session' button in the sidebar and run clustering again.")
716
+
717
+ def plot_comparison(self, data, kmeans_labels, dbscan_labels):
718
+ """Plot comparison between K-Means and DBSCAN."""
719
+ st.subheader("πŸ”„ Algorithm Comparison")
720
+
721
+ col1, col2 = st.columns(2)
722
+
723
+ with col1:
724
+ # K-Means
725
+ plot_data_kmeans = data.copy()
726
+ plot_data_kmeans['Cluster'] = kmeans_labels
727
+
728
+ fig_kmeans = px.scatter(plot_data_kmeans,
729
+ x='Annual Income (k$)',
730
+ y='Spending Score (1-100)',
731
+ color='Cluster',
732
+ title='K-Means Clustering',
733
+ color_discrete_sequence=self.colors)
734
+ fig_kmeans.update_layout(
735
+ height=400,
736
+ paper_bgcolor="#0F172A",
737
+ plot_bgcolor="#0F172A",
738
+ font=dict(color="#E5E7EB")
739
+ )
740
+ st.plotly_chart(fig_kmeans, theme=None, use_container_width=True)
741
+
742
+ with col2:
743
+ # DBSCAN
744
+ plot_data_dbscan = data.copy()
745
+ plot_data_dbscan['Cluster'] = dbscan_labels
746
+ plot_data_dbscan['Cluster'] = plot_data_dbscan['Cluster'].astype(str)
747
+ plot_data_dbscan.loc[plot_data_dbscan['Cluster'] == '-1', 'Cluster'] = 'Noise'
748
+
749
+ fig_dbscan = px.scatter(plot_data_dbscan,
750
+ x='Annual Income (k$)',
751
+ y='Spending Score (1-100)',
752
+ color='Cluster',
753
+ title='DBSCAN Clustering',
754
+ color_discrete_sequence=self.colors)
755
+ fig_dbscan.update_layout(
756
+ height=400,
757
+ paper_bgcolor="#0F172A",
758
+ plot_bgcolor="#0F172A",
759
+ font=dict(color="#E5E7EB")
760
+ )
761
+ st.plotly_chart(fig_dbscan, theme=None, use_container_width=True)
762
+
763
+ # Comparison metrics
764
+ col1, col2, col3, col4 = st.columns(4)
765
+
766
+ with col1:
767
+ kmeans_clusters = len(set(kmeans_labels))
768
+ st.metric("K-Means Clusters", kmeans_clusters)
769
+
770
+ with col2:
771
+ dbscan_clusters = len(set(dbscan_labels)) - (1 if -1 in dbscan_labels else 0)
772
+ st.metric("DBSCAN Clusters", dbscan_clusters)
773
+
774
+ with col3:
775
+ noise_points = list(dbscan_labels).count(-1)
776
+ st.metric("DBSCAN Noise Points", noise_points)
777
+
778
+ with col4:
779
+ noise_percentage = (noise_points / len(dbscan_labels)) * 100
780
+ st.metric("Noise Percentage", f"{noise_percentage:.1f}%")