ncardian commited on
Commit
7035add
ยท
verified ยท
1 Parent(s): a49867b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +804 -804
app.py CHANGED
@@ -1,805 +1,805 @@
1
- import io
2
- import streamlit as st
3
- import pandas as pd
4
- import csv
5
- import re
6
- import plotly.express as px
7
- from scipy.stats import skew
8
- from prompt_models import *
9
- from execution_programs import *
10
- from llm_models import text_llm, code_llm
11
-
12
- st.set_page_config(layout="wide")
13
-
14
- # Custom style CSS
15
- st.markdown("""
16
- <style>
17
- .block-container {
18
- padding-top: 1rem;
19
- padding-bottom: 1rem;
20
- padding-left: 0rem;
21
- padding-right: 0rem;
22
- }
23
- .element-container:has(.stColumn) {
24
- gap: 0rem !important;
25
- }
26
- div[data-testid="column"] {
27
- padding-left: 0rem !important;
28
- padding-right: 0rem !important;
29
- }
30
- .stApp {
31
- max-width: 1200px;
32
- margin: 0 auto;
33
- padding: 1rem;
34
- }
35
- .cell {
36
- border: 1px solid #e0e0e0;
37
- border-radius: 4px;
38
- padding: 1rem;
39
- margin-bottom: 1.5rem;
40
- background-color: #f9f9f9;
41
- }
42
- .output {
43
- border-left: 3px solid #4285f4;
44
- padding-left: 1rem;
45
- margin-top: 0.5rem;
46
- }
47
- .input-area {
48
- background-color: #f5f5f5;
49
- padding: 1rem;
50
- border-radius: 4px;
51
- margin-bottom: 1rem;
52
- }
53
- .stButton > button {
54
- padding: 0rem 0.5rem;
55
- border-radius: 5px;
56
- font-size: 16px;
57
- font-weight: bold;
58
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
59
- transition: all 0.3s ease;
60
- }
61
-
62
- .stButton > button:hover {
63
- background-color: #357ae8;
64
- border-color: #357ae8;
65
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
66
- }
67
- .history-item {
68
- margin-bottom: 2rem;
69
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
70
- }
71
- .chat-message {
72
- padding: 1rem;
73
- border-radius: 0.5rem;
74
- margin-bottom: 1rem;
75
- display: flex;
76
- }
77
- .chat-message.user {
78
- background-color: #262626;
79
- align-self: flex-start;
80
- }
81
- .chat-message.assistant {
82
- background-color: #262626;
83
- align-self: flex-end;
84
- }
85
- .chat-message {
86
- font-size: 14px;
87
- }
88
- .chat-message.user {
89
- font-size: 16px;
90
- }
91
- .chat-message.assistant {
92
- font-size: 14px;
93
- }
94
- .sidebar {
95
- width: 300px;
96
- padding-right: 2rem;
97
- }
98
- .delete-btn {
99
- background-color: #ff4b4b !important;
100
- color: white !important;
101
- border: none !important;
102
- padding: 0.25rem 0.5rem !important;
103
- font-size: 0.8rem !important;
104
- margin-top: 0.5rem;
105
- }
106
- .delete-btn:hover {
107
- background-color: #ff0000 !important;
108
- }
109
- </style>
110
- """, unsafe_allow_html=True)
111
-
112
- # Initialize session state
113
- if 'execution_history' not in st.session_state:
114
- st.session_state.execution_history = []
115
- if 'df' not in st.session_state:
116
- st.session_state.df = None
117
-
118
-
119
- # Main app layout
120
- col1a, col2a = st.columns([4, 7], gap="small")
121
- st.markdown("<div style='margin-top: 5px'></div>", unsafe_allow_html=True)
122
- show_col1 = st.toggle("Ask chat bot", value=True)
123
-
124
- if show_col1:
125
- col1a, col2a = st.columns([4,7])
126
- else:
127
- _ , col2a = st.columns([0.0001, 2])
128
-
129
- with col1a:
130
- if show_col1:
131
- with st.container(height=570):
132
- st.header("๐Ÿ’ฌ Chatbot Assistant")
133
-
134
- # Initial state
135
- if 'chatbot' not in st.session_state:
136
- st.session_state.chatbot = {
137
- 'chat_history': [],
138
- 'prev_df': None,
139
- 'prev_raw_dataframes': None
140
- }
141
-
142
- # button clear all chat
143
- if st.button("๐Ÿ—‘๏ธ Clear all chat", key="clear_all_chat"):
144
- st.session_state.chatbot['chat_history'] = []
145
-
146
- # show history chatbot
147
- for i in range(0, len(st.session_state.chatbot['chat_history']), 2):
148
- user_msg = st.session_state.chatbot['chat_history'][i]
149
- assistant_msg = st.session_state.chatbot['chat_history'][i+1] if i+1 < len(st.session_state.chatbot['chat_history']) else None
150
-
151
- with st.expander(f"Chat {i//2 + 1}", expanded=False):
152
- with st.chat_message("user"):
153
- st.markdown(user_msg["content"])
154
- if assistant_msg:
155
- with st.chat_message("assistant"):
156
- st.markdown(assistant_msg["content"])
157
-
158
-
159
- # Input chat
160
- user_query = st.chat_input("Ask coding questions...", key="chatbot_input")
161
-
162
- if user_query:
163
- # save previous state
164
- st.session_state.chatbot['prev_df'] = st.session_state.get('df')
165
- st.session_state.chatbot['prev_raw_dataframes'] = st.session_state.get('raw_dataframes')
166
-
167
- # show chat user
168
- with st.chat_message("user"):
169
- st.markdown(user_query)
170
-
171
- # add to history chat
172
- st.session_state.chatbot['chat_history'].append({"role": "user", "content": user_query})
173
-
174
- with st.spinner("Thinking..."):
175
- if "df" in st.session_state:
176
- try:
177
- df = st.session_state.df
178
- columns = df.columns
179
- shape = df.shape
180
- dtypes = df.dtypes
181
- response = text_llm(prompt_chatbot(user_query,st.session_state.df.columns, st.session_state.df.shape, st.session_state.df.dtypes))
182
- clean_response = re.sub(r'<\s*think\s*>.*?<\s*/\s*think\s*>', '', response, flags=re.DOTALL | re.IGNORECASE)
183
-
184
- # show and save chatbot response
185
- with st.chat_message("assistant"):
186
- st.markdown(clean_response)
187
- st.session_state.chatbot['chat_history'].append({"role": "assistant", "content": clean_response})
188
- except AttributeError as e:
189
- st.error(f"Please confirm dataset before starting the chat: {e}")
190
- else:
191
- st.write('You need to upload the data first')
192
-
193
- with col2a: # Sidebar for chatbot
194
- with st.container(height=570):
195
- st.header("๐Ÿ“Š Chatbot Data Analysis Coder")
196
- st.markdown("Analyze your CSV data with LLM Models")
197
-
198
- def reset_data_state():
199
- st.session_state.df = None
200
- st.session_state.raw_dataframes = None
201
- st.session_state.preview_df = None
202
- st.session_state.file_names = [f.name for f in uploaded_files] if uploaded_files else []
203
-
204
- # Initialize session state if not already present
205
- if 'df' not in st.session_state:
206
- st.session_state.df = None
207
-
208
- uploaded_files = st.file_uploader(
209
- "Upload CSV file(s)",
210
- type=["csv"],
211
- accept_multiple_files=True,
212
- help="Upload one or more CSV files to combine"
213
- )
214
-
215
- if uploaded_files:
216
- # Reset processing if files change
217
- if 'prev_uploaded_files' not in st.session_state or st.session_state.prev_uploaded_files != [f.name for f in uploaded_files]:
218
- st.session_state.df = None
219
- st.session_state.raw_dataframes = None
220
- st.session_state.preview_df = None
221
- st.session_state.prev_uploaded_files = [f.name for f in uploaded_files]
222
-
223
- # Process files but don't merge yet
224
- if st.session_state.raw_dataframes is None:
225
- dataframes = []
226
- processed_files = 0
227
-
228
- for uploaded_file in uploaded_files:
229
- with st.expander(f"๐Ÿ“„ File: {uploaded_file.name}", expanded=False):
230
- try:
231
- # Try to detect delimiter
232
- raw_data = uploaded_file.getvalue().decode("utf-8")
233
- sniffer = csv.Sniffer()
234
-
235
- try:
236
- dialect = sniffer.sniff(raw_data.splitlines()[0])
237
- delimiter = dialect.delimiter
238
- except (csv.Error, IndexError):
239
- # Try common delimiters if sniffing fails
240
- for test_delim in [',', ';', '\t', '|']:
241
- if test_delim in raw_data:
242
- delimiter = test_delim
243
- break
244
- else:
245
- delimiter = ',' # default
246
-
247
- uploaded_file.seek(0) # Reset file pointer
248
-
249
- # Read CSV with error handling
250
- try:
251
- df = pd.read_csv(uploaded_file, delimiter=delimiter, on_bad_lines='warn')
252
- except Exception as e:
253
- st.warning(f"Using Python engine for {uploaded_file.name} due to parsing issues")
254
- df = pd.read_csv(uploaded_file, delimiter=delimiter, engine='python')
255
-
256
- # Display file info
257
- st.write(f"Shape: {df.shape}")
258
- st.write(f"Detected delimiter: '{delimiter}'")
259
- st.dataframe(df.head(5), use_container_width=True)
260
-
261
- dataframes.append(df)
262
- processed_files += 1
263
-
264
- except Exception as e:
265
- st.error(f"Error processing {uploaded_file.name}: {str(e)}")
266
- continue
267
-
268
- if processed_files == 0:
269
- st.error("No files were successfully processed")
270
- st.stop()
271
-
272
- # Store raw dataframes in session state
273
- st.session_state.raw_dataframes = dataframes
274
-
275
- # Only proceed if we have raw dataframes
276
- if st.session_state.raw_dataframes:
277
- dataframes = st.session_state.raw_dataframes
278
-
279
- col1, col2 = st.columns([3, 1])
280
-
281
- with col1:
282
- merge_option = st.radio(
283
- "Merge method:",
284
- ("single data","Horizontal (concat columns)", "Vertical (concat rows)", "Horizontal (merge on common columns)"),
285
- index=0,
286
- horizontal=True,
287
- key="merge_option"
288
- )
289
-
290
- # Additional merge parameters
291
- if merge_option == "Horizontal (merge on common columns)":
292
- if len(dataframes) > 0:
293
- common_cols = set(dataframes[0].columns)
294
- for df in dataframes[1:]:
295
- common_cols.intersection_update(df.columns)
296
-
297
- if common_cols:
298
- selected_cols = st.multiselect(
299
- "Select columns to merge on:",
300
- options=list(common_cols),
301
- default=list(common_cols),
302
- key="merge_columns"
303
- )
304
- how_merge = st.selectbox(
305
- "Merge type:",
306
- ["inner", "outer", "left", "right"],
307
- index=1,
308
- key="how_merge"
309
- )
310
- else:
311
- None
312
-
313
- with col2:
314
- preview_button = st.button("๐Ÿ”„ Preview")
315
- confirm_button = st.button("โœ… Confirm")
316
- reset_btn = st.button("๐Ÿ”„ Reset")
317
-
318
- # Reset logic
319
- if reset_btn:
320
- reset_data_state()
321
- st.rerun()
322
-
323
- # Preview logic
324
- if preview_button:
325
- with st.spinner("Generating preview..."):
326
- try:
327
- if merge_option == "single data":
328
- preview_df = dataframes[0]
329
- elif merge_option == "Horizontal (concat columns)":
330
- # Find common indices across ALL dataframes
331
- common_indices = dataframes[0].index
332
- for df in dataframes[1:]:
333
- common_indices = common_indices.intersection(df.index)
334
-
335
- if len(common_indices) == 0:
336
- st.error("No common indices found across all dataframes")
337
- st.stop()
338
-
339
- # Filter all dataframes to only common indices
340
- filtered_dfs = [df.loc[common_indices] for df in dataframes]
341
-
342
- # Handle duplicate column names
343
- all_columns = []
344
- duplicate_counter = {}
345
-
346
- for i, df in enumerate(filtered_dfs):
347
- new_columns = []
348
- for col in df.columns:
349
- if col in all_columns:
350
- if col not in duplicate_counter:
351
- duplicate_counter[col] = 1
352
- duplicate_counter[col] += 1
353
- new_col = f"{col}_df{duplicate_counter[col]}"
354
- new_columns.append(new_col)
355
- else:
356
- new_columns.append(col)
357
- all_columns.extend(new_columns)
358
- filtered_dfs[i].columns = new_columns
359
-
360
- # Concatenate horizontally
361
- preview_df = pd.concat(filtered_dfs, axis=1)
362
-
363
- elif merge_option == "Vertical (concat rows)":
364
- preview_df = pd.concat(dataframes, axis=0, ignore_index=True)
365
-
366
- elif merge_option == "Horizontal (merge on common columns)":
367
- def auto_merge_many(dfs):
368
- if not dfs:
369
- return None
370
-
371
- merged_df = dfs[0]
372
-
373
- for next_df in dfs[1:]:
374
- common_keys = list(set(merged_df.columns) & set(next_df.columns))
375
- if common_keys:
376
- merged_df = pd.merge(merged_df, next_df, on=common_keys)
377
- else:
378
- print(f"[WARNING] There is no matching key between:\n{merged_df.columns}\n&\n{next_df.columns}")
379
-
380
- return merged_df
381
-
382
- preview_df = auto_merge_many(dataframes)
383
-
384
- st.session_state.preview_df = preview_df
385
- st.success("Preview generated!")
386
-
387
- # Show preview stats
388
- st.subheader("Preview Results")
389
- st.write(f"Shape: {preview_df.shape}")
390
-
391
- # Show sample data with tabs
392
- tab1, tab2 = st.tabs(["First Rows", "Last Rows"])
393
- with tab1:
394
- st.dataframe(preview_df.head(20), use_container_width=True)
395
- with tab2:
396
- st.dataframe(preview_df.tail(20), use_container_width=True)
397
-
398
- # Show quick stats
399
- with st.expander("Preview Statistics"):
400
- st.write("Column types:")
401
- st.dataframe(preview_df.dtypes.astype(str).reset_index().rename(
402
- columns={'index': 'Column', 0: 'DataType'}
403
- ))
404
-
405
- st.write("Missing values:")
406
- missing = preview_df.isnull().sum()
407
- missing = missing[missing > 0].reset_index().rename(
408
- columns={'index': 'Column', 0: 'Missing Count'}
409
- )
410
- if len(missing) > 0:
411
- st.dataframe(missing)
412
- else:
413
- st.success("No missing values found!")
414
-
415
- except Exception as e:
416
- st.error(f"Preview failed: {str(e)}")
417
- st.stop()
418
-
419
- # Confirm merge logic
420
- if confirm_button and st.session_state.preview_df is not None:
421
- st.session_state.df = st.session_state.preview_df
422
- st.session_state.preview_df = None # Clear preview after confirmation
423
- st.success("Data is now available for analysis.")
424
- st.balloons()
425
- st.rerun()
426
-
427
- # Show raw data info
428
- with st.expander("๐Ÿ“ฆ Raw Data Summary", expanded=False):
429
- st.write(f"Total files loaded: {len(dataframes)}")
430
- for i, df in enumerate(dataframes, 1):
431
- st.write(f"### Dataframe {i}")
432
- st.write(f"- Shape: {df.shape}")
433
- st.write("- Columns:")
434
- st.dataframe(pd.DataFrame({
435
- 'Column': df.columns,
436
- 'Type': df.dtypes.values,
437
- 'Missing %': (df.isnull().mean() * 100).round(2)
438
- }), hide_index=True)
439
-
440
- if st.checkbox(f"Show sample data for Dataframe {i}", key=f"show_raw_{i}"):
441
- st.dataframe(df.head(5), use_container_width=True)
442
-
443
- # If merge is already confirmed, show the final data
444
- if st.session_state.df is not None:
445
-
446
- # Data cleaning options (only after merge is confirmed)
447
- with st.expander("**๐Ÿงน Data Cleaning**", expanded=True):
448
- # Inisialisasi df_cleaned di session state jika belum ada
449
-
450
- st.session_state.df_cleaned = st.session_state.df.copy()
451
-
452
- clean_options = st.columns(4)
453
-
454
- with clean_options[0]:
455
- clean_nan = st.selectbox(
456
- "Handle NaN values:",
457
- ["Keep", "Remove rows", "Fill with zero", "Fill with mean"],
458
- index=0,
459
- key="clean_nan"
460
- )
461
-
462
- if st.button("Apply NaN Handling"):
463
- if clean_nan == "Fill with zero":
464
- st.session_state.df_cleaned = st.session_state.df_cleaned.fillna(0)
465
- st.success("Filled NaN with zeros!")
466
- elif clean_nan == "Fill with mean":
467
- st.session_state.df_cleaned = st.session_state.df_cleaned.fillna(st.session_state.df_cleaned.mean())
468
- st.success("Filled NaN with mean values!")
469
- elif clean_nan == "Remove rows":
470
- initial_rows = len(st.session_state.df_cleaned)
471
- st.session_state.df_cleaned = st.session_state.df_cleaned.dropna()
472
- removed_nan = initial_rows - len(st.session_state.df_cleaned)
473
- st.success(f"Removed {removed_nan} rows with NaN values!")
474
-
475
- with clean_options[1]:
476
- clean_duplicates = st.selectbox(
477
- "Handle duplicates:",
478
- ["Keep", "Remove all", "Keep first", "Keep last"],
479
- index=0,
480
- key="clean_duplicates"
481
- )
482
-
483
- if st.button("Apply Deduplication"):
484
- if clean_duplicates != "Keep":
485
- initial_rows = len(st.session_state.df_cleaned)
486
- if clean_duplicates == "Remove all":
487
- st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep=False)
488
- elif clean_duplicates == "Keep first":
489
- st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep='first')
490
- elif clean_duplicates == "Keep last":
491
- st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep='last')
492
-
493
- removed_dups = initial_rows - len(st.session_state.df_cleaned)
494
- st.success(f"Removed {removed_dups} duplicate rows!")
495
-
496
- with clean_options[2]:
497
- col_rename = st.text_input("Rename ('old:new')")
498
- if st.button("Rename Column") and col_rename and ":" in col_rename:
499
- old, new = col_rename.split(":", 1)
500
- if old in st.session_state.df_cleaned.columns:
501
- st.session_state.df_cleaned = st.session_state.df_cleaned.rename(columns={old: new})
502
- st.success(f"Renamed column '{old}' to '{new}'")
503
- else:
504
- st.warning(f"Column '{old}' not found")
505
-
506
- with clean_options[3]:
507
- # Fitur Drop Columns
508
- cols_to_drop = st.text_input(
509
- "Drop columns",
510
- placeholder="column1, column2, ...",
511
- key="drop_cols_input"
512
- )
513
-
514
- if st.button("Drop Columns"):
515
- if cols_to_drop:
516
- # Proses input: hilangkan spasi, split by comma, dan bersihkan
517
- columns_list = [col.strip() for col in cols_to_drop.split(",") if col.strip()]
518
-
519
- # Fungsi untuk mencocokkan nama kolom dengan fleksibilitas
520
- def find_matching_column(col_name, df_columns):
521
- col_name = col_name.lower().strip()
522
- for df_col in df_columns:
523
- if df_col.lower().strip() == col_name:
524
- return df_col
525
- return None
526
-
527
- # Cari kolom yang cocok
528
- matched_cols = []
529
- not_found = []
530
-
531
- for col in columns_list:
532
- matched = find_matching_column(col, st.session_state.df_cleaned.columns)
533
- if matched:
534
- matched_cols.append(matched)
535
- else:
536
- not_found.append(col)
537
-
538
- # Drop kolom yang ditemukan
539
- if matched_cols:
540
- initial_cols = st.session_state.df_cleaned.columns.tolist()
541
- st.session_state.df_cleaned = st.session_state.df_cleaned.drop(columns=matched_cols)
542
- st.success(f"Successfully dropped columns: {', '.join(matched_cols)}")
543
-
544
- # Tampilkan warning untuk kolom yang tidak ditemukan
545
- if not_found:
546
- st.warning(f"Columns not found: {', '.join(not_found)}")
547
-
548
- # Tampilkan perubahan
549
- st.write(f"Remaining columns: {len(st.session_state.df_cleaned.columns)}")
550
- else:
551
- st.error("No matching columns found to drop")
552
- else:
553
- st.warning("Please enter column names to drop")
554
-
555
- # Tampilkan info missing values setelah cleaning
556
- missing_values = st.session_state.df_cleaned.isnull().sum().sum()
557
- duplicate_values = st.session_state.df_cleaned.duplicated().sum().sum()
558
- if missing_values > 0 or duplicate_values > 0:
559
- st.warning(f"โš ๏ธ Warning: There are still {missing_values} missing values and {duplicate_values} ducplicate rows in the data")
560
- else:
561
- st.success("โœ… No missing values and duplicate rows remaining!")
562
-
563
- # Final data display
564
- st.session_state.df = st.session_state.df_cleaned
565
- st.subheader("๐Ÿ“Š Final Data")
566
- st.write(f"Shape: {st.session_state.df.shape}")
567
-
568
- # Interactive data explorer
569
- tab1, tab2, tab3, tab4 = st.tabs(["Data View", "Statistics", "Column Analysis", 'Type Data'])
570
-
571
- with tab1:
572
- num_rows = st.slider(
573
- "Number of rows to display:",
574
- min_value=5,
575
- max_value=100,
576
- value=5,
577
- key="num_rows_view"
578
- )
579
- st.dataframe(st.session_state.df.head(num_rows), use_container_width=True)
580
-
581
- with tab2:
582
- st.write("### Descriptive Statistics")
583
- st.dataframe(st.session_state.df.describe(include='all'), use_container_width=True)
584
-
585
-
586
- with tab3:
587
- selected_col = st.selectbox(
588
- "Select column to analyze:",
589
- st.session_state.df.columns,
590
- key="col_analyze"
591
- )
592
-
593
- col_data = st.session_state.df[selected_col]
594
- st.write(f"**Type:** {col_data.dtype}")
595
- st.write(f"**Unique values:** {len(col_data.unique())}")
596
- st.write(f"**Missing values:** {col_data.isnull().sum()} ({col_data.isnull().mean()*100:.2f}%)")
597
-
598
-
599
- if col_data.nunique() < 20 and not pd.api.types.is_numeric_dtype(col_data):
600
- ascending = st.checkbox("Ascending", value=False)
601
- st.write("**Distribution**")
602
- column_name = col_data.name
603
-
604
- # sum and sort data
605
- counts = col_data.value_counts(ascending=ascending)
606
- ordered_categories = counts.index.tolist()
607
-
608
- # create dataframe and category for data
609
- df_temp = pd.DataFrame({column_name: col_data})
610
- df_temp[column_name] = pd.Categorical(df_temp[column_name], categories=ordered_categories, ordered=True)
611
-
612
- # create histogram
613
- fig = px.histogram(
614
- df_temp,
615
- x=column_name,
616
- opacity=0.7,
617
- color_discrete_sequence=["#d06200"],
618
- )
619
-
620
- fig.update_traces(marker_line_color='gray', marker_line_width=1)
621
-
622
- # Show x axis based on category sorting
623
- fig.update_layout(
624
- template='seaborn',
625
- title=f"Histogram of {column_name}",
626
- xaxis_title=column_name,
627
- yaxis_title="Count",
628
- bargap=0.1,
629
- font=dict(family="Verdana", size=13),
630
- xaxis=dict(
631
- categoryorder="array",
632
- categoryarray=ordered_categories,
633
- )
634
- )
635
-
636
- st.plotly_chart(fig, use_container_width=True)
637
-
638
- elif pd.api.types.is_numeric_dtype(col_data):
639
- st.write("**Distribution**")
640
- column_name = col_data.name
641
-
642
- df_temp = pd.DataFrame({column_name: col_data.dropna()})
643
-
644
- fig = px.histogram(
645
- df_temp,
646
- x=column_name,
647
- nbins=30,
648
- opacity=0.85,
649
- color_discrete_sequence=["#d06200"],
650
- )
651
-
652
- fig.update_traces(marker_line_color='gray', marker_line_width=0.9)
653
-
654
- fig.update_layout(
655
- template='seaborn',
656
- title=f"Histogram of {column_name}",
657
- xaxis_title=column_name,
658
- yaxis_title="Count",
659
- bargap=0.1,
660
- font=dict(family="Verdana", size=13),
661
- )
662
- fig.update_layout(
663
- annotations=[
664
- dict(
665
- text=f"Skewness: {skew(col_data):.2f}",
666
- showarrow=False,
667
- xref="paper", yref="paper",
668
- x=0.95, y=0.95,
669
- bordercolor='black',
670
- borderwidth=1
671
- )
672
- ]
673
- )
674
-
675
- st.plotly_chart(fig, use_container_width=True)
676
-
677
- else:
678
- st.write("**Value counts (top 20):**")
679
- st.dataframe(col_data.value_counts().head(20))
680
-
681
-
682
- with tab4:
683
- buffer = io.StringIO()
684
- st.session_state.df.info(buf=buffer)
685
- st.text(buffer.getvalue())
686
-
687
- st.markdown("**Data Types:**")
688
- st.json(st.session_state.df.dtypes.astype(str).to_dict())
689
-
690
- st.markdown("**Missing Values:**")
691
- st.json(st.session_state.df.isnull().sum().to_dict())
692
-
693
- st.markdown("**Duplicate:**")
694
- duplicate_count = st.session_state.df.duplicated().sum()
695
- st.write(f"Total duplicate row: {duplicate_count}")
696
-
697
- # Menampilkan jumlah duplikat dalam format JSON yang benar
698
- st.json({"duplicate_count": duplicate_count})
699
-
700
-
701
- # Export options
702
- with st.expander("๐Ÿ’พ Export Data", expanded=False):
703
- export_format = st.radio(
704
- "Export format:",
705
- ["CSV", "Excel", "JSON"],
706
- horizontal=True
707
- )
708
-
709
- export_filename = st.text_input("Filename (without extension)", "clean_data")
710
-
711
- if st.button("Export Data"):
712
- try:
713
- if export_format == "CSV":
714
- csv = st.session_state.df.to_csv(index=False).encode('utf-8')
715
- st.download_button(
716
- label="Download CSV",
717
- data=csv,
718
- file_name=f"{export_filename}.csv",
719
- mime='text/csv'
720
- )
721
- elif export_format == "Excel":
722
- excel_buffer = io.BytesIO()
723
- st.session_state.df.to_excel(excel_buffer, index=False)
724
- st.download_button(
725
- label="Download Excel",
726
- data=excel_buffer,
727
- file_name=f"{export_filename}.xlsx",
728
- mime='application/vnd.ms-excel'
729
- )
730
- elif export_format == "JSON":
731
- json_str = st.session_state.df.to_json(orient='records', indent=2)
732
- st.download_button(
733
- label="Download JSON",
734
- data=json_str,
735
- file_name=f"{export_filename}.json",
736
- mime='application/json'
737
- )
738
- st.success("Export ready!")
739
- except Exception as e:
740
- st.error(f"Export failed: {str(e)}")
741
-
742
- # Input area
743
- input_method = st.radio("Input method:", ["Auto run code","Manual run code"])
744
-
745
- user_input = st.text_area("Enter your Python code or question:", height=150,
746
- placeholder="Write code here or ask a question about your data...",
747
- key="user_input")
748
-
749
-
750
-
751
- # Mode handling
752
- if input_method == "Auto run code":
753
- col1, col2, col3 = st.columns(3)
754
- with col1: execute_btn = st.button("โ–ถ๏ธ Execute")
755
- with col2: clear_btn = st.button("๐Ÿ—‘๏ธ Clear History")
756
- with col3: show_vars = st.button("๐Ÿ“Š Show Variables")
757
- # Common button handling
758
- if clear_btn:
759
- st.session_state.execution_history = []
760
-
761
- if show_vars and st.session_state.execution_history:
762
- latest_vars = st.session_state.execution_history[-1].get('variables', {})
763
- st.json({k: str(type(v)) for k, v in latest_vars.items()})
764
- if execute_btn and user_input:
765
- with st.spinner("Execute code..."):
766
- generated_code = code_llm(generate_prompt(user_input, st.session_state.df.columns.tolist()))
767
- st.markdown("**Generated Code:**")
768
- st.code(generated_code, language='python')
769
-
770
- result = execute_code(generated_code, st.session_state.df)
771
- add_to_history(result)
772
- st.rerun()
773
-
774
- elif input_method == "Manual run code":
775
- col1, col2, col3, col4 = st.columns(4)
776
- with col1: code_btn = st.button("โ–ถ๏ธ Code")
777
- with col2: execute_btn = st.button("โ–ถ๏ธ Execute")
778
- with col3: clear_btn = st.button("๐Ÿ—‘๏ธ Clear History")
779
- with col4: show_vars = st.button("๐Ÿ“Š Show Variables")
780
-
781
- if clear_btn:
782
- st.session_state.execution_history = []
783
-
784
- if show_vars and st.session_state.execution_history:
785
- latest_vars = st.session_state.execution_history[-1].get('variables', {})
786
- st.json({k: str(type(v)) for k, v in latest_vars.items()})
787
-
788
- if code_btn and user_input:
789
- with st.spinner("Generate Code"):
790
- generated_code = code_llm(generate_prompt(user_input, st.session_state.df.columns.tolist()))
791
- st.session_state.generated_code = generated_code
792
- st.markdown("**Generated Code:**")
793
- st.code(generated_code, language='python')
794
-
795
- if execute_btn:
796
- if 'generated_code' in st.session_state:
797
- result = execute_code(st.session_state.generated_code, st.session_state.df)
798
- add_to_history(result)
799
- elif user_input:
800
- result = execute_code(user_input, st.session_state.df)
801
- add_to_history(result)
802
- st.rerun()
803
-
804
- # Display execution history
805
  display_history()
 
1
+ import io
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import csv
5
+ import re
6
+ import plotly.express as px
7
+ from scipy.stats import skew
8
+ from prompt_models import *
9
+ from execution_programs import *
10
+ from llm_models import text_llm, code_llm
11
+
12
+ st.set_page_config(layout="wide")
13
+
14
+ # Custom style CSS
15
+ st.markdown("""
16
+ <style>
17
+ .block-container {
18
+ padding-top: 1rem;
19
+ padding-bottom: 1rem;
20
+ padding-left: 0rem;
21
+ padding-right: 0rem;
22
+ }
23
+ .element-container:has(.stColumn) {
24
+ gap: 0rem !important;
25
+ }
26
+ div[data-testid="column"] {
27
+ padding-left: 0rem !important;
28
+ padding-right: 0rem !important;
29
+ }
30
+ .stApp {
31
+ max-width: 1200px;
32
+ margin: 0 auto;
33
+ padding: 1rem;
34
+ }
35
+ .cell {
36
+ border: 1px solid #e0e0e0;
37
+ border-radius: 4px;
38
+ padding: 1rem;
39
+ margin-bottom: 1.5rem;
40
+ background-color: #f9f9f9;
41
+ }
42
+ .output {
43
+ border-left: 3px solid #4285f4;
44
+ padding-left: 1rem;
45
+ margin-top: 0.5rem;
46
+ }
47
+ .input-area {
48
+ background-color: #f5f5f5;
49
+ padding: 1rem;
50
+ border-radius: 4px;
51
+ margin-bottom: 1rem;
52
+ }
53
+ .stButton > button {
54
+ padding: 0rem 0.5rem;
55
+ border-radius: 5px;
56
+ font-size: 16px;
57
+ font-weight: bold;
58
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .stButton > button:hover {
63
+ background-color: #357ae8;
64
+ border-color: #357ae8;
65
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
66
+ }
67
+ .history-item {
68
+ margin-bottom: 2rem;
69
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
70
+ }
71
+ .chat-message {
72
+ padding: 1rem;
73
+ border-radius: 0.5rem;
74
+ margin-bottom: 1rem;
75
+ display: flex;
76
+ }
77
+ .chat-message.user {
78
+ background-color: #262626;
79
+ align-self: flex-start;
80
+ }
81
+ .chat-message.assistant {
82
+ background-color: #262626;
83
+ align-self: flex-end;
84
+ }
85
+ .chat-message {
86
+ font-size: 14px;
87
+ }
88
+ .chat-message.user {
89
+ font-size: 16px;
90
+ }
91
+ .chat-message.assistant {
92
+ font-size: 14px;
93
+ }
94
+ .sidebar {
95
+ width: 300px;
96
+ padding-right: 2rem;
97
+ }
98
+ .delete-btn {
99
+ background-color: #ff4b4b !important;
100
+ color: white !important;
101
+ border: none !important;
102
+ padding: 0.25rem 0.5rem !important;
103
+ font-size: 0.8rem !important;
104
+ margin-top: 0.5rem;
105
+ }
106
+ .delete-btn:hover {
107
+ background-color: #ff0000 !important;
108
+ }
109
+ </style>
110
+ """, unsafe_allow_html=True)
111
+
112
+ # Initialize session state
113
+ if 'execution_history' not in st.session_state:
114
+ st.session_state.execution_history = []
115
+ if 'df' not in st.session_state:
116
+ st.session_state.df = None
117
+
118
+
119
+ # Main app layout
120
+ col1a, col2a = st.columns([4, 7], gap="small")
121
+ st.markdown("<div style='margin-top: 5px'></div>", unsafe_allow_html=True)
122
+ show_col1 = st.toggle("Ask chat bot", value=True)
123
+
124
+ if show_col1:
125
+ col1a, col2a = st.columns([4,7])
126
+ else:
127
+ _ , col2a = st.columns([0.0001, 2])
128
+
129
+ with col1a:
130
+ if show_col1:
131
+ with st.container(height=570):
132
+ st.header("๐Ÿ’ฌ Chatbot Assistant")
133
+
134
+ # Initial state
135
+ if 'chatbot' not in st.session_state:
136
+ st.session_state.chatbot = {
137
+ 'chat_history': [],
138
+ 'prev_df': None,
139
+ 'prev_raw_dataframes': None
140
+ }
141
+
142
+ # button clear all chat
143
+ if st.button("๐Ÿ—‘๏ธ Clear all chat", key="clear_all_chat"):
144
+ st.session_state.chatbot['chat_history'] = []
145
+
146
+ # show history chatbot
147
+ for i in range(0, len(st.session_state.chatbot['chat_history']), 2):
148
+ user_msg = st.session_state.chatbot['chat_history'][i]
149
+ assistant_msg = st.session_state.chatbot['chat_history'][i+1] if i+1 < len(st.session_state.chatbot['chat_history']) else None
150
+
151
+ with st.expander(f"Chat {i//2 + 1}", expanded=False):
152
+ with st.chat_message("user"):
153
+ st.markdown(user_msg["content"])
154
+ if assistant_msg:
155
+ with st.chat_message("assistant"):
156
+ st.markdown(assistant_msg["content"])
157
+
158
+
159
+ # Input chat
160
+ user_query = st.chat_input("Ask coding questions...", key="chatbot_input")
161
+
162
+ if user_query:
163
+ # save previous state
164
+ st.session_state.chatbot['prev_df'] = st.session_state.get('df')
165
+ st.session_state.chatbot['prev_raw_dataframes'] = st.session_state.get('raw_dataframes')
166
+
167
+ # show chat user
168
+ with st.chat_message("user"):
169
+ st.markdown(user_query)
170
+
171
+ # add to history chat
172
+ st.session_state.chatbot['chat_history'].append({"role": "user", "content": user_query})
173
+
174
+ with st.spinner("Thinking..."):
175
+ if "df" in st.session_state:
176
+ try:
177
+ df = st.session_state.df
178
+ columns = df.columns
179
+ shape = df.shape
180
+ dtypes = df.dtypes
181
+ response = text_llm(prompt_chatbot(user_query,st.session_state.df.columns, st.session_state.df.shape, st.session_state.df.dtypes))
182
+ clean_response = re.sub(r'<\s*think\s*>.*?<\s*/\s*think\s*>', '', response, flags=re.DOTALL | re.IGNORECASE)
183
+
184
+ # show and save chatbot response
185
+ with st.chat_message("assistant"):
186
+ st.markdown(clean_response)
187
+ st.session_state.chatbot['chat_history'].append({"role": "assistant", "content": clean_response})
188
+ except AttributeError as e:
189
+ st.error(f"Please confirm dataset before starting the chat: {e}")
190
+ else:
191
+ st.write('You need to upload the data first')
192
+
193
+ with col2a: # Sidebar for chatbot
194
+ with st.container(height=570):
195
+ st.header("๐Ÿ“Š DataCoder AI")
196
+ st.markdown("Analyze your CSV data with LLM Models")
197
+
198
+ def reset_data_state():
199
+ st.session_state.df = None
200
+ st.session_state.raw_dataframes = None
201
+ st.session_state.preview_df = None
202
+ st.session_state.file_names = [f.name for f in uploaded_files] if uploaded_files else []
203
+
204
+ # Initialize session state if not already present
205
+ if 'df' not in st.session_state:
206
+ st.session_state.df = None
207
+
208
+ uploaded_files = st.file_uploader(
209
+ "Upload CSV file(s)",
210
+ type=["csv"],
211
+ accept_multiple_files=True,
212
+ help="Upload one or more CSV files to combine"
213
+ )
214
+
215
+ if uploaded_files:
216
+ # Reset processing if files change
217
+ if 'prev_uploaded_files' not in st.session_state or st.session_state.prev_uploaded_files != [f.name for f in uploaded_files]:
218
+ st.session_state.df = None
219
+ st.session_state.raw_dataframes = None
220
+ st.session_state.preview_df = None
221
+ st.session_state.prev_uploaded_files = [f.name for f in uploaded_files]
222
+
223
+ # Process files but don't merge yet
224
+ if st.session_state.raw_dataframes is None:
225
+ dataframes = []
226
+ processed_files = 0
227
+
228
+ for uploaded_file in uploaded_files:
229
+ with st.expander(f"๐Ÿ“„ File: {uploaded_file.name}", expanded=False):
230
+ try:
231
+ # Try to detect delimiter
232
+ raw_data = uploaded_file.getvalue().decode("utf-8")
233
+ sniffer = csv.Sniffer()
234
+
235
+ try:
236
+ dialect = sniffer.sniff(raw_data.splitlines()[0])
237
+ delimiter = dialect.delimiter
238
+ except (csv.Error, IndexError):
239
+ # Try common delimiters if sniffing fails
240
+ for test_delim in [',', ';', '\t', '|']:
241
+ if test_delim in raw_data:
242
+ delimiter = test_delim
243
+ break
244
+ else:
245
+ delimiter = ',' # default
246
+
247
+ uploaded_file.seek(0) # Reset file pointer
248
+
249
+ # Read CSV with error handling
250
+ try:
251
+ df = pd.read_csv(uploaded_file, delimiter=delimiter, on_bad_lines='warn')
252
+ except Exception as e:
253
+ st.warning(f"Using Python engine for {uploaded_file.name} due to parsing issues")
254
+ df = pd.read_csv(uploaded_file, delimiter=delimiter, engine='python')
255
+
256
+ # Display file info
257
+ st.write(f"Shape: {df.shape}")
258
+ st.write(f"Detected delimiter: '{delimiter}'")
259
+ st.dataframe(df.head(5), use_container_width=True)
260
+
261
+ dataframes.append(df)
262
+ processed_files += 1
263
+
264
+ except Exception as e:
265
+ st.error(f"Error processing {uploaded_file.name}: {str(e)}")
266
+ continue
267
+
268
+ if processed_files == 0:
269
+ st.error("No files were successfully processed")
270
+ st.stop()
271
+
272
+ # Store raw dataframes in session state
273
+ st.session_state.raw_dataframes = dataframes
274
+
275
+ # Only proceed if we have raw dataframes
276
+ if st.session_state.raw_dataframes:
277
+ dataframes = st.session_state.raw_dataframes
278
+
279
+ col1, col2 = st.columns([3, 1])
280
+
281
+ with col1:
282
+ merge_option = st.radio(
283
+ "Merge method:",
284
+ ("single data","Horizontal (concat columns)", "Vertical (concat rows)", "Horizontal (merge on common columns)"),
285
+ index=0,
286
+ horizontal=True,
287
+ key="merge_option"
288
+ )
289
+
290
+ # Additional merge parameters
291
+ if merge_option == "Horizontal (merge on common columns)":
292
+ if len(dataframes) > 0:
293
+ common_cols = set(dataframes[0].columns)
294
+ for df in dataframes[1:]:
295
+ common_cols.intersection_update(df.columns)
296
+
297
+ if common_cols:
298
+ selected_cols = st.multiselect(
299
+ "Select columns to merge on:",
300
+ options=list(common_cols),
301
+ default=list(common_cols),
302
+ key="merge_columns"
303
+ )
304
+ how_merge = st.selectbox(
305
+ "Merge type:",
306
+ ["inner", "outer", "left", "right"],
307
+ index=1,
308
+ key="how_merge"
309
+ )
310
+ else:
311
+ None
312
+
313
+ with col2:
314
+ preview_button = st.button("๐Ÿ”„ Preview")
315
+ confirm_button = st.button("โœ… Confirm")
316
+ reset_btn = st.button("๐Ÿ”„ Reset")
317
+
318
+ # Reset logic
319
+ if reset_btn:
320
+ reset_data_state()
321
+ st.rerun()
322
+
323
+ # Preview logic
324
+ if preview_button:
325
+ with st.spinner("Generating preview..."):
326
+ try:
327
+ if merge_option == "single data":
328
+ preview_df = dataframes[0]
329
+ elif merge_option == "Horizontal (concat columns)":
330
+ # Find common indices across ALL dataframes
331
+ common_indices = dataframes[0].index
332
+ for df in dataframes[1:]:
333
+ common_indices = common_indices.intersection(df.index)
334
+
335
+ if len(common_indices) == 0:
336
+ st.error("No common indices found across all dataframes")
337
+ st.stop()
338
+
339
+ # Filter all dataframes to only common indices
340
+ filtered_dfs = [df.loc[common_indices] for df in dataframes]
341
+
342
+ # Handle duplicate column names
343
+ all_columns = []
344
+ duplicate_counter = {}
345
+
346
+ for i, df in enumerate(filtered_dfs):
347
+ new_columns = []
348
+ for col in df.columns:
349
+ if col in all_columns:
350
+ if col not in duplicate_counter:
351
+ duplicate_counter[col] = 1
352
+ duplicate_counter[col] += 1
353
+ new_col = f"{col}_df{duplicate_counter[col]}"
354
+ new_columns.append(new_col)
355
+ else:
356
+ new_columns.append(col)
357
+ all_columns.extend(new_columns)
358
+ filtered_dfs[i].columns = new_columns
359
+
360
+ # Concatenate horizontally
361
+ preview_df = pd.concat(filtered_dfs, axis=1)
362
+
363
+ elif merge_option == "Vertical (concat rows)":
364
+ preview_df = pd.concat(dataframes, axis=0, ignore_index=True)
365
+
366
+ elif merge_option == "Horizontal (merge on common columns)":
367
+ def auto_merge_many(dfs):
368
+ if not dfs:
369
+ return None
370
+
371
+ merged_df = dfs[0]
372
+
373
+ for next_df in dfs[1:]:
374
+ common_keys = list(set(merged_df.columns) & set(next_df.columns))
375
+ if common_keys:
376
+ merged_df = pd.merge(merged_df, next_df, on=common_keys)
377
+ else:
378
+ print(f"[WARNING] There is no matching key between:\n{merged_df.columns}\n&\n{next_df.columns}")
379
+
380
+ return merged_df
381
+
382
+ preview_df = auto_merge_many(dataframes)
383
+
384
+ st.session_state.preview_df = preview_df
385
+ st.success("Preview generated!")
386
+
387
+ # Show preview stats
388
+ st.subheader("Preview Results")
389
+ st.write(f"Shape: {preview_df.shape}")
390
+
391
+ # Show sample data with tabs
392
+ tab1, tab2 = st.tabs(["First Rows", "Last Rows"])
393
+ with tab1:
394
+ st.dataframe(preview_df.head(20), use_container_width=True)
395
+ with tab2:
396
+ st.dataframe(preview_df.tail(20), use_container_width=True)
397
+
398
+ # Show quick stats
399
+ with st.expander("Preview Statistics"):
400
+ st.write("Column types:")
401
+ st.dataframe(preview_df.dtypes.astype(str).reset_index().rename(
402
+ columns={'index': 'Column', 0: 'DataType'}
403
+ ))
404
+
405
+ st.write("Missing values:")
406
+ missing = preview_df.isnull().sum()
407
+ missing = missing[missing > 0].reset_index().rename(
408
+ columns={'index': 'Column', 0: 'Missing Count'}
409
+ )
410
+ if len(missing) > 0:
411
+ st.dataframe(missing)
412
+ else:
413
+ st.success("No missing values found!")
414
+
415
+ except Exception as e:
416
+ st.error(f"Preview failed: {str(e)}")
417
+ st.stop()
418
+
419
+ # Confirm merge logic
420
+ if confirm_button and st.session_state.preview_df is not None:
421
+ st.session_state.df = st.session_state.preview_df
422
+ st.session_state.preview_df = None # Clear preview after confirmation
423
+ st.success("Data is now available for analysis.")
424
+ st.balloons()
425
+ st.rerun()
426
+
427
+ # Show raw data info
428
+ with st.expander("๐Ÿ“ฆ Raw Data Summary", expanded=False):
429
+ st.write(f"Total files loaded: {len(dataframes)}")
430
+ for i, df in enumerate(dataframes, 1):
431
+ st.write(f"### Dataframe {i}")
432
+ st.write(f"- Shape: {df.shape}")
433
+ st.write("- Columns:")
434
+ st.dataframe(pd.DataFrame({
435
+ 'Column': df.columns,
436
+ 'Type': df.dtypes.values,
437
+ 'Missing %': (df.isnull().mean() * 100).round(2)
438
+ }), hide_index=True)
439
+
440
+ if st.checkbox(f"Show sample data for Dataframe {i}", key=f"show_raw_{i}"):
441
+ st.dataframe(df.head(5), use_container_width=True)
442
+
443
+ # If merge is already confirmed, show the final data
444
+ if st.session_state.df is not None:
445
+
446
+ # Data cleaning options (only after merge is confirmed)
447
+ with st.expander("**๐Ÿงน Data Cleaning**", expanded=True):
448
+ # Inisialisasi df_cleaned di session state jika belum ada
449
+
450
+ st.session_state.df_cleaned = st.session_state.df.copy()
451
+
452
+ clean_options = st.columns(4)
453
+
454
+ with clean_options[0]:
455
+ clean_nan = st.selectbox(
456
+ "Handle NaN values:",
457
+ ["Keep", "Remove rows", "Fill with zero", "Fill with mean"],
458
+ index=0,
459
+ key="clean_nan"
460
+ )
461
+
462
+ if st.button("Apply NaN Handling"):
463
+ if clean_nan == "Fill with zero":
464
+ st.session_state.df_cleaned = st.session_state.df_cleaned.fillna(0)
465
+ st.success("Filled NaN with zeros!")
466
+ elif clean_nan == "Fill with mean":
467
+ st.session_state.df_cleaned = st.session_state.df_cleaned.fillna(st.session_state.df_cleaned.mean())
468
+ st.success("Filled NaN with mean values!")
469
+ elif clean_nan == "Remove rows":
470
+ initial_rows = len(st.session_state.df_cleaned)
471
+ st.session_state.df_cleaned = st.session_state.df_cleaned.dropna()
472
+ removed_nan = initial_rows - len(st.session_state.df_cleaned)
473
+ st.success(f"Removed {removed_nan} rows with NaN values!")
474
+
475
+ with clean_options[1]:
476
+ clean_duplicates = st.selectbox(
477
+ "Handle duplicates:",
478
+ ["Keep", "Remove all", "Keep first", "Keep last"],
479
+ index=0,
480
+ key="clean_duplicates"
481
+ )
482
+
483
+ if st.button("Apply Deduplication"):
484
+ if clean_duplicates != "Keep":
485
+ initial_rows = len(st.session_state.df_cleaned)
486
+ if clean_duplicates == "Remove all":
487
+ st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep=False)
488
+ elif clean_duplicates == "Keep first":
489
+ st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep='first')
490
+ elif clean_duplicates == "Keep last":
491
+ st.session_state.df_cleaned = st.session_state.df_cleaned.drop_duplicates(keep='last')
492
+
493
+ removed_dups = initial_rows - len(st.session_state.df_cleaned)
494
+ st.success(f"Removed {removed_dups} duplicate rows!")
495
+
496
+ with clean_options[2]:
497
+ col_rename = st.text_input("Rename ('old:new')")
498
+ if st.button("Rename Column") and col_rename and ":" in col_rename:
499
+ old, new = col_rename.split(":", 1)
500
+ if old in st.session_state.df_cleaned.columns:
501
+ st.session_state.df_cleaned = st.session_state.df_cleaned.rename(columns={old: new})
502
+ st.success(f"Renamed column '{old}' to '{new}'")
503
+ else:
504
+ st.warning(f"Column '{old}' not found")
505
+
506
+ with clean_options[3]:
507
+ # Fitur Drop Columns
508
+ cols_to_drop = st.text_input(
509
+ "Drop columns",
510
+ placeholder="column1, column2, ...",
511
+ key="drop_cols_input"
512
+ )
513
+
514
+ if st.button("Drop Columns"):
515
+ if cols_to_drop:
516
+ # Proses input: hilangkan spasi, split by comma, dan bersihkan
517
+ columns_list = [col.strip() for col in cols_to_drop.split(",") if col.strip()]
518
+
519
+ # Fungsi untuk mencocokkan nama kolom dengan fleksibilitas
520
+ def find_matching_column(col_name, df_columns):
521
+ col_name = col_name.lower().strip()
522
+ for df_col in df_columns:
523
+ if df_col.lower().strip() == col_name:
524
+ return df_col
525
+ return None
526
+
527
+ # Cari kolom yang cocok
528
+ matched_cols = []
529
+ not_found = []
530
+
531
+ for col in columns_list:
532
+ matched = find_matching_column(col, st.session_state.df_cleaned.columns)
533
+ if matched:
534
+ matched_cols.append(matched)
535
+ else:
536
+ not_found.append(col)
537
+
538
+ # Drop kolom yang ditemukan
539
+ if matched_cols:
540
+ initial_cols = st.session_state.df_cleaned.columns.tolist()
541
+ st.session_state.df_cleaned = st.session_state.df_cleaned.drop(columns=matched_cols)
542
+ st.success(f"Successfully dropped columns: {', '.join(matched_cols)}")
543
+
544
+ # Tampilkan warning untuk kolom yang tidak ditemukan
545
+ if not_found:
546
+ st.warning(f"Columns not found: {', '.join(not_found)}")
547
+
548
+ # Tampilkan perubahan
549
+ st.write(f"Remaining columns: {len(st.session_state.df_cleaned.columns)}")
550
+ else:
551
+ st.error("No matching columns found to drop")
552
+ else:
553
+ st.warning("Please enter column names to drop")
554
+
555
+ # Tampilkan info missing values setelah cleaning
556
+ missing_values = st.session_state.df_cleaned.isnull().sum().sum()
557
+ duplicate_values = st.session_state.df_cleaned.duplicated().sum().sum()
558
+ if missing_values > 0 or duplicate_values > 0:
559
+ st.warning(f"โš ๏ธ Warning: There are still {missing_values} missing values and {duplicate_values} ducplicate rows in the data")
560
+ else:
561
+ st.success("โœ… No missing values and duplicate rows remaining!")
562
+
563
+ # Final data display
564
+ st.session_state.df = st.session_state.df_cleaned
565
+ st.subheader("๐Ÿ“Š Final Data")
566
+ st.write(f"Shape: {st.session_state.df.shape}")
567
+
568
+ # Interactive data explorer
569
+ tab1, tab2, tab3, tab4 = st.tabs(["Data View", "Statistics", "Column Analysis", 'Type Data'])
570
+
571
+ with tab1:
572
+ num_rows = st.slider(
573
+ "Number of rows to display:",
574
+ min_value=5,
575
+ max_value=100,
576
+ value=5,
577
+ key="num_rows_view"
578
+ )
579
+ st.dataframe(st.session_state.df.head(num_rows), use_container_width=True)
580
+
581
+ with tab2:
582
+ st.write("### Descriptive Statistics")
583
+ st.dataframe(st.session_state.df.describe(include='all'), use_container_width=True)
584
+
585
+
586
+ with tab3:
587
+ selected_col = st.selectbox(
588
+ "Select column to analyze:",
589
+ st.session_state.df.columns,
590
+ key="col_analyze"
591
+ )
592
+
593
+ col_data = st.session_state.df[selected_col]
594
+ st.write(f"**Type:** {col_data.dtype}")
595
+ st.write(f"**Unique values:** {len(col_data.unique())}")
596
+ st.write(f"**Missing values:** {col_data.isnull().sum()} ({col_data.isnull().mean()*100:.2f}%)")
597
+
598
+
599
+ if col_data.nunique() < 20 and not pd.api.types.is_numeric_dtype(col_data):
600
+ ascending = st.checkbox("Ascending", value=False)
601
+ st.write("**Distribution**")
602
+ column_name = col_data.name
603
+
604
+ # sum and sort data
605
+ counts = col_data.value_counts(ascending=ascending)
606
+ ordered_categories = counts.index.tolist()
607
+
608
+ # create dataframe and category for data
609
+ df_temp = pd.DataFrame({column_name: col_data})
610
+ df_temp[column_name] = pd.Categorical(df_temp[column_name], categories=ordered_categories, ordered=True)
611
+
612
+ # create histogram
613
+ fig = px.histogram(
614
+ df_temp,
615
+ x=column_name,
616
+ opacity=0.7,
617
+ color_discrete_sequence=["#d06200"],
618
+ )
619
+
620
+ fig.update_traces(marker_line_color='gray', marker_line_width=1)
621
+
622
+ # Show x axis based on category sorting
623
+ fig.update_layout(
624
+ template='seaborn',
625
+ title=f"Histogram of {column_name}",
626
+ xaxis_title=column_name,
627
+ yaxis_title="Count",
628
+ bargap=0.1,
629
+ font=dict(family="Verdana", size=13),
630
+ xaxis=dict(
631
+ categoryorder="array",
632
+ categoryarray=ordered_categories,
633
+ )
634
+ )
635
+
636
+ st.plotly_chart(fig, use_container_width=True)
637
+
638
+ elif pd.api.types.is_numeric_dtype(col_data):
639
+ st.write("**Distribution**")
640
+ column_name = col_data.name
641
+
642
+ df_temp = pd.DataFrame({column_name: col_data.dropna()})
643
+
644
+ fig = px.histogram(
645
+ df_temp,
646
+ x=column_name,
647
+ nbins=30,
648
+ opacity=0.85,
649
+ color_discrete_sequence=["#d06200"],
650
+ )
651
+
652
+ fig.update_traces(marker_line_color='gray', marker_line_width=0.9)
653
+
654
+ fig.update_layout(
655
+ template='seaborn',
656
+ title=f"Histogram of {column_name}",
657
+ xaxis_title=column_name,
658
+ yaxis_title="Count",
659
+ bargap=0.1,
660
+ font=dict(family="Verdana", size=13),
661
+ )
662
+ fig.update_layout(
663
+ annotations=[
664
+ dict(
665
+ text=f"Skewness: {skew(col_data):.2f}",
666
+ showarrow=False,
667
+ xref="paper", yref="paper",
668
+ x=0.95, y=0.95,
669
+ bordercolor='black',
670
+ borderwidth=1
671
+ )
672
+ ]
673
+ )
674
+
675
+ st.plotly_chart(fig, use_container_width=True)
676
+
677
+ else:
678
+ st.write("**Value counts (top 20):**")
679
+ st.dataframe(col_data.value_counts().head(20))
680
+
681
+
682
+ with tab4:
683
+ buffer = io.StringIO()
684
+ st.session_state.df.info(buf=buffer)
685
+ st.text(buffer.getvalue())
686
+
687
+ st.markdown("**Data Types:**")
688
+ st.json(st.session_state.df.dtypes.astype(str).to_dict())
689
+
690
+ st.markdown("**Missing Values:**")
691
+ st.json(st.session_state.df.isnull().sum().to_dict())
692
+
693
+ st.markdown("**Duplicate:**")
694
+ duplicate_count = st.session_state.df.duplicated().sum()
695
+ st.write(f"Total duplicate row: {duplicate_count}")
696
+
697
+ # Menampilkan jumlah duplikat dalam format JSON yang benar
698
+ st.json({"duplicate_count": duplicate_count})
699
+
700
+
701
+ # Export options
702
+ with st.expander("๐Ÿ’พ Export Data", expanded=False):
703
+ export_format = st.radio(
704
+ "Export format:",
705
+ ["CSV", "Excel", "JSON"],
706
+ horizontal=True
707
+ )
708
+
709
+ export_filename = st.text_input("Filename (without extension)", "clean_data")
710
+
711
+ if st.button("Export Data"):
712
+ try:
713
+ if export_format == "CSV":
714
+ csv = st.session_state.df.to_csv(index=False).encode('utf-8')
715
+ st.download_button(
716
+ label="Download CSV",
717
+ data=csv,
718
+ file_name=f"{export_filename}.csv",
719
+ mime='text/csv'
720
+ )
721
+ elif export_format == "Excel":
722
+ excel_buffer = io.BytesIO()
723
+ st.session_state.df.to_excel(excel_buffer, index=False)
724
+ st.download_button(
725
+ label="Download Excel",
726
+ data=excel_buffer,
727
+ file_name=f"{export_filename}.xlsx",
728
+ mime='application/vnd.ms-excel'
729
+ )
730
+ elif export_format == "JSON":
731
+ json_str = st.session_state.df.to_json(orient='records', indent=2)
732
+ st.download_button(
733
+ label="Download JSON",
734
+ data=json_str,
735
+ file_name=f"{export_filename}.json",
736
+ mime='application/json'
737
+ )
738
+ st.success("Export ready!")
739
+ except Exception as e:
740
+ st.error(f"Export failed: {str(e)}")
741
+
742
+ # Input area
743
+ input_method = st.radio("Input method:", ["Auto run code","Manual run code"])
744
+
745
+ user_input = st.text_area("Enter your Python code or question:", height=150,
746
+ placeholder="Write code here or ask a question about your data...",
747
+ key="user_input")
748
+
749
+
750
+
751
+ # Mode handling
752
+ if input_method == "Auto run code":
753
+ col1, col2, col3 = st.columns(3)
754
+ with col1: execute_btn = st.button("โ–ถ๏ธ Execute")
755
+ with col2: clear_btn = st.button("๐Ÿ—‘๏ธ Clear History")
756
+ with col3: show_vars = st.button("๐Ÿ“Š Show Variables")
757
+ # Common button handling
758
+ if clear_btn:
759
+ st.session_state.execution_history = []
760
+
761
+ if show_vars and st.session_state.execution_history:
762
+ latest_vars = st.session_state.execution_history[-1].get('variables', {})
763
+ st.json({k: str(type(v)) for k, v in latest_vars.items()})
764
+ if execute_btn and user_input:
765
+ with st.spinner("Execute code..."):
766
+ generated_code = code_llm(generate_prompt(user_input, st.session_state.df.columns.tolist()))
767
+ st.markdown("**Generated Code:**")
768
+ st.code(generated_code, language='python')
769
+
770
+ result = execute_code(generated_code, st.session_state.df)
771
+ add_to_history(result)
772
+ st.rerun()
773
+
774
+ elif input_method == "Manual run code":
775
+ col1, col2, col3, col4 = st.columns(4)
776
+ with col1: code_btn = st.button("โ–ถ๏ธ Code")
777
+ with col2: execute_btn = st.button("โ–ถ๏ธ Execute")
778
+ with col3: clear_btn = st.button("๐Ÿ—‘๏ธ Clear History")
779
+ with col4: show_vars = st.button("๐Ÿ“Š Show Variables")
780
+
781
+ if clear_btn:
782
+ st.session_state.execution_history = []
783
+
784
+ if show_vars and st.session_state.execution_history:
785
+ latest_vars = st.session_state.execution_history[-1].get('variables', {})
786
+ st.json({k: str(type(v)) for k, v in latest_vars.items()})
787
+
788
+ if code_btn and user_input:
789
+ with st.spinner("Generate Code"):
790
+ generated_code = code_llm(generate_prompt(user_input, st.session_state.df.columns.tolist()))
791
+ st.session_state.generated_code = generated_code
792
+ st.markdown("**Generated Code:**")
793
+ st.code(generated_code, language='python')
794
+
795
+ if execute_btn:
796
+ if 'generated_code' in st.session_state:
797
+ result = execute_code(st.session_state.generated_code, st.session_state.df)
798
+ add_to_history(result)
799
+ elif user_input:
800
+ result = execute_code(user_input, st.session_state.df)
801
+ add_to_history(result)
802
+ st.rerun()
803
+
804
+ # Display execution history
805
  display_history()