ncardian commited on
Commit
3fcec00
·
verified ·
1 Parent(s): 022fbfa

Upload 4 files

Browse files
Files changed (3) hide show
  1. app.py +805 -0
  2. execution_programs.py +224 -0
  3. llm_models.py +78 -0
app.py ADDED
@@ -0,0 +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
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()
execution_programs.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import contextlib
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ import seaborn as sns
7
+ from datetime import datetime
8
+ import re
9
+ from io import StringIO
10
+ import plotly.graph_objects as go
11
+ from llm_models import text_llm
12
+ from prompt_models import prompt_analyze
13
+
14
+
15
+ def analyze_code_execution(code: str, execution_result: dict, df: pd.DataFrame) -> str:
16
+ """
17
+ Analyze only the execution results (output and variables) to provide focused insights.
18
+
19
+ Args:
20
+ code: The Python code that was executed
21
+ execution_result: The dictionary containing execution results
22
+ df: The DataFrame being analyzed (for context only)
23
+
24
+ Returns:
25
+ str: Natural language analysis focused on the execution results
26
+ """
27
+ # Extract relevant information from execution_result
28
+ output = execution_result.get('stdout', 'Tidak ada output teks')
29
+ variables = execution_result.get('variables', {})
30
+ error = execution_result.get('error', None)
31
+
32
+ # Prepare variable summaries
33
+ var_summaries = []
34
+ for var_name, var_value in variables.items():
35
+ if isinstance(var_value, (pd.DataFrame, pd.Series)):
36
+ var_summaries.append(f"- {var_name}: {type(var_value).__name__} dengan bentuk {var_value.shape}")
37
+ elif isinstance(var_value, (plt.Figure, sns.axisgrid.Grid)):
38
+ var_summaries.append(f"- {var_name}: Visualisasi plot")
39
+ else:
40
+ var_summaries.append(f"- {var_name}: {type(var_value).__name__}")
41
+
42
+
43
+ prompt = prompt_analyze(output, var_summaries, df, error)
44
+ # Get analysis from the text model
45
+ response = text_llm(prompt)
46
+
47
+ # Clear thinking output
48
+ clean_response = re.sub(r'<\s*think\s*>.*?<\s*/\s*think\s*>', '', response, flags=re.DOTALL | re.IGNORECASE)
49
+
50
+ return clean_response
51
+
52
+ # =======================
53
+ def execute_code(code: str, df: pd.DataFrame):
54
+ """Execute Python code safely and return outputs"""
55
+ # Extract Python code from markdown blocks
56
+ if "```python" in code:
57
+ clean_code = code.split("```python")[1].split("```")[0].strip()
58
+ else:
59
+ clean_code = code.strip()
60
+
61
+ # Add required imports if missing
62
+ required_imports = [
63
+ "import pandas as pd",
64
+ "import matplotlib.pyplot as plt",
65
+ "import seaborn as sns",
66
+ "import plotly.express as px",
67
+ "import plotly.graph_objects as go"
68
+ ]
69
+
70
+ for imp in required_imports:
71
+ if imp not in clean_code:
72
+ clean_code = imp + "\n" + clean_code
73
+
74
+ # Prepare execution environment
75
+ local_vars = {
76
+ 'df': df,
77
+ 'plt': plt,
78
+ 'sns': sns,
79
+ 'pd': pd
80
+ }
81
+
82
+ # Capture outputs
83
+ stdout = io.StringIO()
84
+ stderr = io.StringIO()
85
+
86
+ try:
87
+ with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
88
+ exec(clean_code, local_vars)
89
+
90
+ # Deteksi plot Plotly
91
+ plotly_figs = []
92
+ for var_name, var_value in local_vars.items():
93
+ if isinstance(var_value, go.Figure):
94
+ plotly_figs.append(var_value)
95
+
96
+ return {
97
+ 'code': clean_code,
98
+ 'stdout': stdout.getvalue(),
99
+ 'stderr': stderr.getvalue(),
100
+ 'figure': plotly_figs,
101
+ 'success': True,
102
+ 'variables': {k: v for k, v in local_vars.items()
103
+ if not k.startswith('_') and k not in ['df', 'plt', 'sns', 'pd']}
104
+ }
105
+
106
+ except Exception as e:
107
+ return {
108
+ 'code': clean_code,
109
+ 'error': str(e),
110
+ 'stderr': stderr.getvalue(),
111
+ 'success': False
112
+ }
113
+
114
+ def add_to_history(execution_result):
115
+ """Add execution result to history with timestamp"""
116
+ history_item = {
117
+ **execution_result,
118
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
119
+ }
120
+ st.session_state.execution_history.append(history_item)
121
+
122
+ # Generate explanation for successful executions
123
+ if execution_result['success']:
124
+ with st.spinner("Analyzing results..."):
125
+ df_to_analyze = st.session_state.df_cleaned if 'df_cleaned' in st.session_state else st.session_state.df
126
+
127
+ analysis = analyze_code_execution(
128
+ code=execution_result['code'],
129
+ execution_result=execution_result,
130
+ df=df_to_analyze # Only used for context, not analyzed
131
+ )
132
+
133
+ history_item['explanation'] = f"""
134
+ **Analisis Hasil Eksekusi:**
135
+ {analysis}
136
+ """
137
+
138
+
139
+
140
+ # =======================
141
+ # Display Functions
142
+ # =======================
143
+
144
+ def display_code_with_highlight(code: str):
145
+ """Display formatted Python code using Streamlit's built-in code block"""
146
+ st.code(code, language='python')
147
+
148
+ def display_history():
149
+ """Display all execution history items"""
150
+ st.markdown("### Execution History")
151
+
152
+ if not st.session_state.execution_history:
153
+ st.info("No executions yet. Run some code to see results here.")
154
+ return
155
+
156
+ for i, item in enumerate(reversed(st.session_state.execution_history)):
157
+ with st.container():
158
+ st.markdown(f"### Execution #{len(st.session_state.execution_history)-i}")
159
+ st.caption(f"Executed at {item['timestamp']}")
160
+
161
+ # Display code
162
+ with st.expander("View Code", expanded=False):
163
+ display_code_with_highlight(item['code'])
164
+
165
+ # Display outputs
166
+ if item['success']:
167
+ if 'explanation' in item:
168
+ with st.expander("See Explanation"):
169
+ st.markdown("**Explanation:**")
170
+ st.info(item['explanation'])
171
+
172
+ if item['stdout']:
173
+ with st.expander("View Output", expanded=False):
174
+ st.markdown("**Output:**")
175
+ lines = item['stdout'].split('\n')
176
+ text_content = []
177
+ table_data = []
178
+ header_detected = False
179
+
180
+ for idx, line in enumerate(lines):
181
+ cleaned_line = line.strip()
182
+ if not cleaned_line:
183
+ continue
184
+
185
+ if not header_detected:
186
+ if re.match(r"^[\w\s_]+$", cleaned_line) and len(cleaned_line.split()) > 1:
187
+ if (idx + 1 < len(lines)) and re.match(r"^\d+\s+[\d\.e+-]+", lines[idx+1].strip()):
188
+ header_detected = True
189
+ table_data = lines[idx:]
190
+ break
191
+ else:
192
+ text_content.append(cleaned_line)
193
+ else:
194
+ text_content.append(cleaned_line)
195
+
196
+ if text_content:
197
+ st.write("\n".join(text_content))
198
+
199
+ if table_data:
200
+ try:
201
+ clean_table = [line.strip() for line in table_data if line.strip()]
202
+ df = pd.read_csv(StringIO("\n".join(clean_table)), sep=r"\s+", engine="python")
203
+ st.dataframe(df)
204
+ except Exception as e:
205
+ st.write("\n".join(table_data))
206
+
207
+
208
+ if item['figure']:
209
+ st.markdown("**Visualisasi Interaktif:**")
210
+ for fig in item['figure']:
211
+ st.plotly_chart(fig, use_container_width=True)
212
+
213
+
214
+ if item['variables']:
215
+ with st.expander("Created Variables"):
216
+ st.json({k: str(type(v)) for k, v in item['variables'].items()})
217
+ else:
218
+ st.error("Execution failed")
219
+ st.error(item['error'])
220
+ if item['stderr']:
221
+ st.text("Error details:")
222
+ st.text(item['stderr'])
223
+
224
+ st.markdown("---")
llm_models.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ from langchain.llms.base import LLM
4
+ from typing import Optional, List
5
+ import os
6
+ from execution_programs import *
7
+
8
+ # API key for llm model
9
+ GROQ_API_KEY = os.environ.get("GROQ_TOKENS")
10
+ class GroqLLM(LLM):
11
+ model: str = "llama-4-maverick-17b-128e-instruct"
12
+ temperature: float = 0.3
13
+ api_key: str = GROQ_API_KEY
14
+
15
+ @property
16
+ def _llm_type(self) -> str:
17
+ return "groq-llm"
18
+
19
+ def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
20
+ headers = {
21
+ "Authorization": f"Bearer {self.api_key}",
22
+ "Content-Type": "application/json",
23
+ }
24
+
25
+ payload = {
26
+ "messages": [{"role": "user", "content": prompt}],
27
+ "model": self.model,
28
+ "temperature": self.temperature,
29
+ }
30
+
31
+ try:
32
+ response = requests.post(
33
+ "https://api.groq.com/openai/v1/chat/completions",
34
+ headers=headers,
35
+ json=payload
36
+ )
37
+ response.raise_for_status()
38
+ return response.json()["choices"][0]["message"]["content"]
39
+ except Exception as e:
40
+ st.error(f"Error calling Groq API: {str(e)}")
41
+ return "Sorry, there was an error processing your request."
42
+
43
+ class GroqTextLLM(LLM):
44
+ model: str = "deepseek-r1-distill-llama-70b"
45
+ temperature: float = 0.7
46
+ api_key: str = GROQ_API_KEY
47
+
48
+ @property
49
+ def _llm_type(self) -> str:
50
+ return "groq-text-llm"
51
+
52
+ def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
53
+ headers = {
54
+ "Authorization": f"Bearer {self.api_key}",
55
+ "Content-Type": "application/json",
56
+ }
57
+
58
+ payload = {
59
+ "messages": [{"role": "user", "content": prompt}],
60
+ "model": self.model,
61
+ "temperature": self.temperature,
62
+ }
63
+
64
+ try:
65
+ response = requests.post(
66
+ "https://api.groq.com/openai/v1/chat/completions",
67
+ headers=headers,
68
+ json=payload
69
+ )
70
+ response.raise_for_status()
71
+ return response.json()["choices"][0]["message"]["content"]
72
+ except Exception as e:
73
+ st.error(f"Error calling Groq API: {str(e)}")
74
+ return "Sorry, there was an error processing your request."
75
+
76
+ # Initialize LLMs
77
+ code_llm = GroqLLM()
78
+ text_llm = GroqTextLLM()