streamlit-launcher 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit_launcher/.env +5 -0
- streamlit_launcher/.pypirc +8 -0
- streamlit_launcher/LICENSE +31 -0
- streamlit_launcher/README.md +281 -0
- streamlit_launcher/Screenshot 2025-09-12 185242.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185705.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185725.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185806.png +0 -0
- streamlit_launcher/__init__.py +2 -0
- streamlit_launcher/anime.png +0 -0
- streamlit_launcher/cli.py +8 -0
- streamlit_launcher/dashboard.py +848 -0
- streamlit_launcher/gui.py +368 -0
- streamlit_launcher/img.ico +0 -0
- streamlit_launcher/kemas.png +0 -0
- streamlit_launcher/mas.py +1938 -0
- streamlit_launcher/requirements.txt +3 -0
- streamlit_launcher/setup.py +45 -0
- streamlit_launcher/treamlit.jpg +0 -0
- streamlit_launcher/ui.png +0 -0
- streamlit_launcher/upload_to_pypi.bat +18 -0
- streamlit_launcher-2.0.0.dist-info/METADATA +313 -0
- streamlit_launcher-2.0.0.dist-info/RECORD +27 -0
- streamlit_launcher-2.0.0.dist-info/WHEEL +5 -0
- streamlit_launcher-2.0.0.dist-info/entry_points.txt +2 -0
- streamlit_launcher-2.0.0.dist-info/licenses/LICENSE +31 -0
- streamlit_launcher-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import streamlit as st
|
|
4
|
+
import plotly.graph_objects as go
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
from scipy import stats
|
|
8
|
+
import matplotlib.dates as mdates
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
import warnings
|
|
11
|
+
warnings.filterwarnings('ignore')
|
|
12
|
+
|
|
13
|
+
# Konfigurasi halaman
|
|
14
|
+
st.set_page_config(
|
|
15
|
+
page_title="Dashboard Statistik Profesional",
|
|
16
|
+
page_icon="📊",
|
|
17
|
+
layout="wide",
|
|
18
|
+
initial_sidebar_state="expanded"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# CSS kustom untuk styling
|
|
22
|
+
st.markdown("""
|
|
23
|
+
<style>
|
|
24
|
+
.main-header {
|
|
25
|
+
font-size: 3rem;
|
|
26
|
+
color: #1f77b4;
|
|
27
|
+
text-align: center;
|
|
28
|
+
margin-bottom: 2rem;
|
|
29
|
+
}
|
|
30
|
+
.metric-card {
|
|
31
|
+
background-color: #f0f2f6;
|
|
32
|
+
padding: 1.5rem;
|
|
33
|
+
border-radius: 0.5rem;
|
|
34
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
35
|
+
}
|
|
36
|
+
.stButton>button {
|
|
37
|
+
width: 100%;
|
|
38
|
+
background-color: #1f77b4;
|
|
39
|
+
color: white;
|
|
40
|
+
}
|
|
41
|
+
.data-table {
|
|
42
|
+
width: 100%;
|
|
43
|
+
border-collapse: collapse;
|
|
44
|
+
margin: 1rem 0;
|
|
45
|
+
}
|
|
46
|
+
.data-table th, .data-table td {
|
|
47
|
+
border: 1px solid #ddd;
|
|
48
|
+
padding: 8px;
|
|
49
|
+
text-align: left;
|
|
50
|
+
}
|
|
51
|
+
.data-table th {
|
|
52
|
+
background-color: #f2f2f2;
|
|
53
|
+
}
|
|
54
|
+
.data-table tr:nth-child(even) {
|
|
55
|
+
background-color: #f9f9f9;
|
|
56
|
+
}
|
|
57
|
+
.up-trend {
|
|
58
|
+
color: green;
|
|
59
|
+
font-weight: bold;
|
|
60
|
+
}
|
|
61
|
+
.down-trend {
|
|
62
|
+
color: red;
|
|
63
|
+
font-weight: bold;
|
|
64
|
+
}
|
|
65
|
+
.stock-summary {
|
|
66
|
+
background-color: #f8f9fa;
|
|
67
|
+
padding: 15px;
|
|
68
|
+
border-radius: 5px;
|
|
69
|
+
margin-bottom: 20px;
|
|
70
|
+
border-left: 5px solid #1f77b4;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
""", unsafe_allow_html=True)
|
|
74
|
+
|
|
75
|
+
# Fungsi untuk menampilkan dataframe sebagai HTML (menggantikan st.dataframe)
|
|
76
|
+
def display_dataframe(df, num_rows=5):
|
|
77
|
+
st.markdown(df.head(num_rows).to_html(classes='data-table', escape=False), unsafe_allow_html=True)
|
|
78
|
+
|
|
79
|
+
# Fungsi untuk memproses file yang diupload
|
|
80
|
+
def process_uploaded_file(uploaded_file):
|
|
81
|
+
try:
|
|
82
|
+
if uploaded_file.name.endswith('.csv'):
|
|
83
|
+
df = pd.read_csv(uploaded_file)
|
|
84
|
+
elif uploaded_file.name.endswith('.xlsx') or uploaded_file.name.endswith('.xls'):
|
|
85
|
+
df = pd.read_excel(uploaded_file)
|
|
86
|
+
else:
|
|
87
|
+
st.error("Format file tidak didukung. Harap unggah file CSV atau Excel.")
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
return df
|
|
91
|
+
except Exception as e:
|
|
92
|
+
st.error(f"Terjadi kesalahan saat memproses file: {str(e)}")
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
# Fungsi untuk menggabungkan beberapa dataset
|
|
96
|
+
def merge_datasets(datasets, merge_method='concat'):
|
|
97
|
+
if not datasets:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
if merge_method == 'concat':
|
|
101
|
+
# Gabungkan dataset secara vertikal (menambah baris)
|
|
102
|
+
return pd.concat(datasets, ignore_index=True)
|
|
103
|
+
else:
|
|
104
|
+
# Untuk penggabungan berdasarkan kolom (join)
|
|
105
|
+
merged_df = datasets[0]
|
|
106
|
+
for i in range(1, len(datasets)):
|
|
107
|
+
merged_df = pd.merge(merged_df, datasets[i], how=merge_method)
|
|
108
|
+
return merged_df
|
|
109
|
+
|
|
110
|
+
# Fungsi untuk membuat visualisasi data
|
|
111
|
+
def create_visualizations(df):
|
|
112
|
+
# Menentukan kolom numerik dan non-numerik
|
|
113
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
114
|
+
non_numeric_cols = df.select_dtypes(exclude=[np.number]).columns.tolist()
|
|
115
|
+
|
|
116
|
+
# Jika tidak ada kolom numerik, beri pesan error
|
|
117
|
+
if not numeric_cols:
|
|
118
|
+
st.error("Tidak ditemukan kolom numerik dalam dataset.")
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Default kolom untuk sumbu X dan Y
|
|
122
|
+
default_x = non_numeric_cols[0] if non_numeric_cols else df.index.name if df.index.name else "Index"
|
|
123
|
+
default_y = numeric_cols[0]
|
|
124
|
+
|
|
125
|
+
# Sidebar untuk konfigurasi chart
|
|
126
|
+
st.sidebar.header("Konfigurasi Visualisasi")
|
|
127
|
+
|
|
128
|
+
# Pilihan jenis chart
|
|
129
|
+
chart_type = st.sidebar.selectbox(
|
|
130
|
+
"Pilih Jenis Chart",
|
|
131
|
+
["Line Chart", "Bar Chart", "Histogram", "Scatter Plot", "Pie Chart", "Box Plot", "Heatmap", "Candlestick", "Area Chart", "Histogram dengan Multiple Variables"]
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Pilihan kolom berdasarkan jenis chart
|
|
135
|
+
if chart_type in ["Line Chart", "Bar Chart", "Scatter Plot", "Area Chart"]:
|
|
136
|
+
x_col = st.sidebar.selectbox("Pilih kolom untuk sumbu X", [default_x] + non_numeric_cols + numeric_cols)
|
|
137
|
+
y_col = st.sidebar.selectbox("Pilih kolom untuk sumbu Y", numeric_cols, index=0)
|
|
138
|
+
|
|
139
|
+
if chart_type == "Line Chart":
|
|
140
|
+
fig = px.line(df, x=x_col, y=y_col, title=f"{y_col} over {x_col}")
|
|
141
|
+
elif chart_type == "Bar Chart":
|
|
142
|
+
fig = px.bar(df, x=x_col, y=y_col, title=f"{y_col} by {x_col}")
|
|
143
|
+
elif chart_type == "Scatter Plot":
|
|
144
|
+
color_col = st.sidebar.selectbox("Pilih kolom untuk warna (opsional)", [None] + non_numeric_cols + numeric_cols)
|
|
145
|
+
size_col = st.sidebar.selectbox("Pilih kolom untuk ukuran (opsional)", [None] + numeric_cols)
|
|
146
|
+
fig = px.scatter(df, x=x_col, y=y_col, color=color_col, size=size_col,
|
|
147
|
+
title=f"Scatter Plot: {y_col} vs {x_col}")
|
|
148
|
+
elif chart_type == "Area Chart":
|
|
149
|
+
fig = px.area(df, x=x_col, y=y_col, title=f"{y_col} over {x_col} (Area Chart)")
|
|
150
|
+
|
|
151
|
+
elif chart_type == "Histogram":
|
|
152
|
+
col = st.sidebar.selectbox("Pilih kolom", numeric_cols)
|
|
153
|
+
fig = px.histogram(df, x=col, title=f"Distribusi {col}")
|
|
154
|
+
|
|
155
|
+
elif chart_type == "Histogram dengan Multiple Variables":
|
|
156
|
+
selected_cols = st.sidebar.multiselect("Pilih kolom untuk histogram", numeric_cols, default=numeric_cols[:min(3, len(numeric_cols))])
|
|
157
|
+
|
|
158
|
+
if len(selected_cols) > 0:
|
|
159
|
+
# Buat histogram dengan multiple variables
|
|
160
|
+
fig = go.Figure()
|
|
161
|
+
|
|
162
|
+
for col in selected_cols:
|
|
163
|
+
# Pastikan tidak ada nilai NaN atau infinite
|
|
164
|
+
clean_data = df[col].replace([np.inf, -np.inf], np.nan).dropna()
|
|
165
|
+
|
|
166
|
+
if len(clean_data) > 0:
|
|
167
|
+
fig.add_trace(go.Histogram(
|
|
168
|
+
x=clean_data,
|
|
169
|
+
name=col,
|
|
170
|
+
opacity=0.75
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
fig.update_layout(
|
|
174
|
+
barmode='overlay',
|
|
175
|
+
title_text='Distribusi Beberapa Variabel',
|
|
176
|
+
xaxis_title_text='Nilai',
|
|
177
|
+
yaxis_title_text='Frekuensi'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Atur opacity untuk melihat overlap
|
|
181
|
+
fig.update_traces(opacity=0.75)
|
|
182
|
+
else:
|
|
183
|
+
st.error("Pilih setidaknya satu kolom untuk histogram")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
elif chart_type == "Pie Chart":
|
|
187
|
+
# Untuk pie chart, kita butuh kolom kategori dan nilai
|
|
188
|
+
cat_col = st.sidebar.selectbox("Pilih kolom kategori", non_numeric_cols)
|
|
189
|
+
value_col = st.sidebar.selectbox("Pilih kolom nilai", numeric_cols)
|
|
190
|
+
|
|
191
|
+
# Opsi untuk menggabungkan kategori kecil
|
|
192
|
+
combine_threshold = st.sidebar.slider(
|
|
193
|
+
"Gabungkan kategori dengan persentase di bawah (%)",
|
|
194
|
+
min_value=0, max_value=20, value=5, step=1
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Hitung total untuk setiap kategori
|
|
198
|
+
pie_data = df.groupby(cat_col)[value_col].sum().reset_index()
|
|
199
|
+
total = pie_data[value_col].sum()
|
|
200
|
+
pie_data['percentage'] = (pie_data[value_col] / total) * 100
|
|
201
|
+
|
|
202
|
+
# Gabungkan kategori kecil
|
|
203
|
+
if combine_threshold > 0:
|
|
204
|
+
other_data = pie_data[pie_data['percentage'] < combine_threshold]
|
|
205
|
+
main_data = pie_data[pie_data['percentage'] >= combine_threshold]
|
|
206
|
+
|
|
207
|
+
if len(other_data) > 0:
|
|
208
|
+
other_sum = other_data[value_col].sum()
|
|
209
|
+
other_percentage = other_data['percentage'].sum()
|
|
210
|
+
|
|
211
|
+
# Buat dataframe baru dengan kategori "Lainnya"
|
|
212
|
+
main_data = pd.concat([
|
|
213
|
+
main_data,
|
|
214
|
+
pd.DataFrame({cat_col: ['Lainnya'], value_col: [other_sum], 'percentage': [other_percentage]})
|
|
215
|
+
], ignore_index=True)
|
|
216
|
+
|
|
217
|
+
pie_data = main_data
|
|
218
|
+
|
|
219
|
+
fig = px.pie(pie_data, names=cat_col, values=value_col, title=f"Proporsi {value_col} oleh {cat_col}")
|
|
220
|
+
|
|
221
|
+
elif chart_type == "Box Plot":
|
|
222
|
+
col = st.sidebar.selectbox("Pilih kolom", numeric_cols)
|
|
223
|
+
fig = px.box(df, y=col, title=f"Box Plot {col}")
|
|
224
|
+
|
|
225
|
+
elif chart_type == "Heatmap":
|
|
226
|
+
if len(numeric_cols) < 2:
|
|
227
|
+
st.error("Heatmap memerlukan setidaknya 2 kolom numerik")
|
|
228
|
+
return
|
|
229
|
+
selected_cols = st.sidebar.multiselect("Pilih kolom untuk heatmap", numeric_cols, default=numeric_cols[:min(5, len(numeric_cols))])
|
|
230
|
+
if len(selected_cols) < 2:
|
|
231
|
+
st.error("Pilih setidaknya 2 kolom untuk heatmap")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
# Pastikan tidak ada nilai NaN atau infinite
|
|
235
|
+
clean_df = df[selected_cols].replace([np.inf, -np.inf], np.nan).dropna()
|
|
236
|
+
|
|
237
|
+
if len(clean_df) < 2:
|
|
238
|
+
st.error("Tidak cukup data setelah pembersihan nilai NaN/infinite")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
corr_matrix = clean_df.corr()
|
|
242
|
+
fig = px.imshow(corr_matrix, text_auto=True, aspect="auto", title="Heatmap Korelasi")
|
|
243
|
+
|
|
244
|
+
elif chart_type == "Candlestick":
|
|
245
|
+
# Untuk candlestick, kita butuh kolom OHLC
|
|
246
|
+
st.sidebar.info("Candlestick memerlukan kolom Open, High, Low, Close")
|
|
247
|
+
date_col = st.sidebar.selectbox("Pilih kolom tanggal", non_numeric_cols)
|
|
248
|
+
open_col = st.sidebar.selectbox("Pilih kolom Open", numeric_cols)
|
|
249
|
+
high_col = st.sidebar.selectbox("Pilih kolom High", numeric_cols)
|
|
250
|
+
low_col = st.sidebar.selectbox("Pilih kolom Low", numeric_cols)
|
|
251
|
+
close_col = st.sidebar.selectbox("Pilih kolom Close", numeric_cols)
|
|
252
|
+
|
|
253
|
+
# Pastikan kolom tanggal dalam format datetime
|
|
254
|
+
try:
|
|
255
|
+
df[date_col] = pd.to_datetime(df[date_col])
|
|
256
|
+
df_sorted = df.sort_values(by=date_col).copy()
|
|
257
|
+
|
|
258
|
+
fig = go.Figure(data=[go.Candlestick(
|
|
259
|
+
x=df_sorted[date_col],
|
|
260
|
+
open=df_sorted[open_col],
|
|
261
|
+
high=df_sorted[high_col],
|
|
262
|
+
low=df_sorted[low_col],
|
|
263
|
+
close=df_sorted[close_col],
|
|
264
|
+
name='Harga Saham'
|
|
265
|
+
)])
|
|
266
|
+
|
|
267
|
+
fig.update_layout(
|
|
268
|
+
title='Chart Candlestick',
|
|
269
|
+
yaxis_title='Harga',
|
|
270
|
+
xaxis_title='Tanggal'
|
|
271
|
+
)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
st.error(f"Error membuat candlestick chart: {str(e)}")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Tampilkan chart
|
|
277
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
278
|
+
|
|
279
|
+
# Tampilkan beberapa chart otomatis berdasarkan data
|
|
280
|
+
st.subheader("Visualisasi Otomatis")
|
|
281
|
+
|
|
282
|
+
# Pilih beberapa kolom numerik untuk ditampilkan
|
|
283
|
+
if len(numeric_cols) > 0:
|
|
284
|
+
selected_cols = st.multiselect(
|
|
285
|
+
"Pilih kolom untuk divisualisasikan",
|
|
286
|
+
numeric_cols,
|
|
287
|
+
default=numeric_cols[:min(3, len(numeric_cols))]
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if selected_cols:
|
|
291
|
+
col1, col2 = st.columns(2)
|
|
292
|
+
|
|
293
|
+
with col1:
|
|
294
|
+
# Line chart untuk kolom terpilih
|
|
295
|
+
# Pastikan tidak ada nilai NaN atau infinite
|
|
296
|
+
clean_df = df[selected_cols].replace([np.inf, -np.inf], np.nan).dropna()
|
|
297
|
+
fig_multi = px.line(clean_df, y=selected_cols, title="Trend Data")
|
|
298
|
+
st.plotly_chart(fig_multi, use_container_width=True)
|
|
299
|
+
|
|
300
|
+
with col2:
|
|
301
|
+
# Correlation heatmap
|
|
302
|
+
if len(selected_cols) > 1:
|
|
303
|
+
# Pastikan tidak ada nilai NaN atau infinite
|
|
304
|
+
clean_df = df[selected_cols].replace([np.inf, -np.inf], np.nan).dropna()
|
|
305
|
+
|
|
306
|
+
if len(clean_df) > 1:
|
|
307
|
+
corr_matrix = clean_df.corr()
|
|
308
|
+
fig_corr = px.imshow(corr_matrix, text_auto=True, aspect="auto",
|
|
309
|
+
title="Korelasi antar Variabel")
|
|
310
|
+
st.plotly_chart(fig_corr, use_container_width=True)
|
|
311
|
+
else:
|
|
312
|
+
st.error("Tidak cukup data untuk menghitung korelasi setelah pembersihan")
|
|
313
|
+
else:
|
|
314
|
+
# Jika hanya satu kolom, tampilkan box plot
|
|
315
|
+
clean_data = df[selected_cols[0]].replace([np.inf, -np.inf], np.nan).dropna()
|
|
316
|
+
if len(clean_data) > 0:
|
|
317
|
+
fig_box = px.box(y=clean_data, title=f"Distribusi {selected_cols[0]}")
|
|
318
|
+
st.plotly_chart(fig_box, use_container_width=True)
|
|
319
|
+
else:
|
|
320
|
+
st.error("Tidak ada data yang valid untuk ditampilkan")
|
|
321
|
+
|
|
322
|
+
# Fungsi untuk menampilkan statistik deskriptif lengkap
|
|
323
|
+
def show_statistics(df):
|
|
324
|
+
st.header("Statistik Deskriptif")
|
|
325
|
+
|
|
326
|
+
# Tampilkan statistik dasar
|
|
327
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
328
|
+
|
|
329
|
+
with col1:
|
|
330
|
+
st.metric("Jumlah Baris", df.shape[0])
|
|
331
|
+
|
|
332
|
+
with col2:
|
|
333
|
+
st.metric("Jumlah Kolom", df.shape[1])
|
|
334
|
+
|
|
335
|
+
with col3:
|
|
336
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
337
|
+
st.metric("Kolom Numerik", len(numeric_cols))
|
|
338
|
+
|
|
339
|
+
with col4:
|
|
340
|
+
non_numeric_cols = df.select_dtypes(exclude=[np.number]).columns.tolist()
|
|
341
|
+
st.metric("Kolom Non-Numerik", len(non_numeric_cols))
|
|
342
|
+
|
|
343
|
+
# Tampilkan preview data menggunakan HTML
|
|
344
|
+
st.subheader("Preview Data")
|
|
345
|
+
display_dataframe(df)
|
|
346
|
+
|
|
347
|
+
# Tampilkan statistik deskriptif untuk kolom numerik
|
|
348
|
+
if numeric_cols:
|
|
349
|
+
st.subheader("Statistik Numerik Lengkap")
|
|
350
|
+
|
|
351
|
+
# Bersihkan data dari nilai infinite
|
|
352
|
+
clean_df = df[numeric_cols].replace([np.inf, -np.inf], np.nan)
|
|
353
|
+
|
|
354
|
+
# Hitung berbagai statistik
|
|
355
|
+
desc_stats = clean_df.describe()
|
|
356
|
+
|
|
357
|
+
# Tambahkan statistik tambahan
|
|
358
|
+
additional_stats = pd.DataFrame({
|
|
359
|
+
'median': clean_df.median(),
|
|
360
|
+
'variance': clean_df.var(),
|
|
361
|
+
'skewness': clean_df.skew(),
|
|
362
|
+
'kurtosis': clean_df.kurtosis(),
|
|
363
|
+
'range': clean_df.max() - clean_df.min(),
|
|
364
|
+
'Q1': clean_df.quantile(0.25),
|
|
365
|
+
'Q3': clean_df.quantile(0.75),
|
|
366
|
+
'IQR': clean_df.quantile(0.75) - clean_df.quantile(0.25)
|
|
367
|
+
}).T
|
|
368
|
+
|
|
369
|
+
# Gabungkan dengan statistik deskriptif standar
|
|
370
|
+
full_stats = pd.concat([desc_stats, additional_stats])
|
|
371
|
+
st.markdown(full_stats.to_html(classes='data-table', escape=False), unsafe_allow_html=True)
|
|
372
|
+
|
|
373
|
+
# Uji normalitas untuk setiap kolom numerik
|
|
374
|
+
st.subheader("Uji Normalitas (Shapiro-Wilk)")
|
|
375
|
+
normality_results = []
|
|
376
|
+
for col in numeric_cols:
|
|
377
|
+
# Bersihkan data dari nilai infinite dan NaN
|
|
378
|
+
data = df[col].replace([np.inf, -np.inf], np.nan).dropna()
|
|
379
|
+
if len(data) > 3 and len(data) < 5000: # Shapiro-Wilk bekerja untuk 3 < n < 5000
|
|
380
|
+
try:
|
|
381
|
+
stat, p_value = stats.shapiro(data)
|
|
382
|
+
normality_results.append({
|
|
383
|
+
'Kolom': col,
|
|
384
|
+
'Statistik': f"{stat:.4f}",
|
|
385
|
+
'p-value': f"{p_value:.4f}",
|
|
386
|
+
'Normal': "Ya" if p_value > 0.05 else "Tidak"
|
|
387
|
+
})
|
|
388
|
+
except Exception as e:
|
|
389
|
+
normality_results.append({
|
|
390
|
+
'Kolom': col,
|
|
391
|
+
'Statistik': f"Error: {str(e)}",
|
|
392
|
+
'p-value': "Error",
|
|
393
|
+
'Normal': "Error"
|
|
394
|
+
})
|
|
395
|
+
else:
|
|
396
|
+
normality_results.append({
|
|
397
|
+
'Kolom': col,
|
|
398
|
+
'Statistik': "N/A",
|
|
399
|
+
'p-value': "N/A",
|
|
400
|
+
'Normal': "Sample size tidak sesuai"
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
normality_df = pd.DataFrame(normality_results)
|
|
404
|
+
st.markdown(normality_df.to_html(classes='data-table', escape=False, index=False), unsafe_allow_html=True)
|
|
405
|
+
|
|
406
|
+
# Tampilkan informasi tentang missing values
|
|
407
|
+
st.subheader("Informasi Missing Values")
|
|
408
|
+
missing_data = df.isnull().sum()
|
|
409
|
+
missing_df = pd.DataFrame({
|
|
410
|
+
'Kolom': missing_data.index,
|
|
411
|
+
'Jumlah Missing': missing_data.values,
|
|
412
|
+
'Persentase Missing': (missing_data.values / len(df)) * 100
|
|
413
|
+
})
|
|
414
|
+
st.markdown(missing_df.to_html(classes='data-table', escape=False, index=False), unsafe_allow_html=True)
|
|
415
|
+
|
|
416
|
+
# Tampilkan informasi tentang infinite values
|
|
417
|
+
st.subheader("Informasi Infinite Values")
|
|
418
|
+
if numeric_cols:
|
|
419
|
+
inf_data = {}
|
|
420
|
+
for col in numeric_cols:
|
|
421
|
+
inf_count = np.isinf(df[col]).sum()
|
|
422
|
+
inf_data[col] = inf_count
|
|
423
|
+
|
|
424
|
+
inf_df = pd.DataFrame({
|
|
425
|
+
'Kolom': list(inf_data.keys()),
|
|
426
|
+
'Jumlah Infinite': list(inf_data.values()),
|
|
427
|
+
'Persentase Infinite': [f"{(v / len(df)) * 100:.2f}%" for v in inf_data.values()]
|
|
428
|
+
})
|
|
429
|
+
st.markdown(inf_df.to_html(classes='data-table', escape=False, index=False), unsafe_allow_html=True)
|
|
430
|
+
|
|
431
|
+
# Tampilkan informasi tipe data
|
|
432
|
+
st.subheader("Informasi Tipe Data")
|
|
433
|
+
dtype_info = pd.DataFrame({
|
|
434
|
+
'Kolom': df.columns,
|
|
435
|
+
'Tipe Data': df.dtypes.values,
|
|
436
|
+
'Nilai Unik': [df[col].nunique() for col in df.columns]
|
|
437
|
+
})
|
|
438
|
+
st.markdown(dtype_info.to_html(classes='data-table', escape=False, index=False), unsafe_allow_html=True)
|
|
439
|
+
|
|
440
|
+
# Analisis trend saham jika ada kolom yang sesuai
|
|
441
|
+
analyze_stock_trends(df)
|
|
442
|
+
|
|
443
|
+
# Fungsi untuk menganalisis trend saham naik/turun
|
|
444
|
+
def analyze_stock_trends(df):
|
|
445
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
446
|
+
date_cols = df.select_dtypes(include=['datetime64']).columns.tolist()
|
|
447
|
+
|
|
448
|
+
if not date_cols:
|
|
449
|
+
# Coba konversi kolom string ke datetime
|
|
450
|
+
for col in df.columns:
|
|
451
|
+
if df[col].dtype == 'object':
|
|
452
|
+
try:
|
|
453
|
+
df[col] = pd.to_datetime(df[col])
|
|
454
|
+
date_cols.append(col)
|
|
455
|
+
break
|
|
456
|
+
except:
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
if date_cols and numeric_cols:
|
|
460
|
+
st.subheader("Analisis Trend Saham")
|
|
461
|
+
|
|
462
|
+
date_col = st.selectbox("Pilih kolom tanggal", date_cols)
|
|
463
|
+
price_col = st.selectbox("Pilih kolom harga", numeric_cols)
|
|
464
|
+
|
|
465
|
+
try:
|
|
466
|
+
# Pastikan data diurutkan berdasarkan tanggal
|
|
467
|
+
df_sorted = df.sort_values(by=date_col).copy()
|
|
468
|
+
|
|
469
|
+
# Bersihkan data dari nilai infinite
|
|
470
|
+
df_sorted[price_col] = df_sorted[price_col].replace([np.inf, -np.inf], np.nan)
|
|
471
|
+
df_sorted = df_sorted.dropna(subset=[price_col])
|
|
472
|
+
|
|
473
|
+
# Hitung perubahan harga
|
|
474
|
+
df_sorted['Perubahan'] = df_sorted[price_col].pct_change() * 100
|
|
475
|
+
df_sorted['Perubahan_Abs'] = df_sorted[price_col].diff()
|
|
476
|
+
|
|
477
|
+
# Hitung statistik trend
|
|
478
|
+
total_days = len(df_sorted)
|
|
479
|
+
up_days = len(df_sorted[df_sorted['Perubahan_Abs'] > 0])
|
|
480
|
+
down_days = len(df_sorted[df_sorted['Perubahan_Abs'] < 0])
|
|
481
|
+
flat_days = len(df_sorted[df_sorted['Perubahan_Abs'] == 0])
|
|
482
|
+
|
|
483
|
+
avg_change = df_sorted['Perubahan'].mean()
|
|
484
|
+
max_gain = df_sorted['Perubahan'].max()
|
|
485
|
+
max_loss = df_sorted['Perubahan'].min()
|
|
486
|
+
|
|
487
|
+
# Tampilkan metrik trend
|
|
488
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
489
|
+
|
|
490
|
+
with col1:
|
|
491
|
+
trend_icon = "📈" if avg_change > 0 else "📉"
|
|
492
|
+
st.metric(f"Rata-rata Perubahan {trend_icon}", f"{avg_change:.2f}%")
|
|
493
|
+
|
|
494
|
+
with col2:
|
|
495
|
+
st.metric("Hari Naik", f"{up_days} ({up_days/total_days*100:.1f}%)")
|
|
496
|
+
|
|
497
|
+
with col3:
|
|
498
|
+
st.metric("Hari Turun", f"{down_days} ({down_days/total_days*100:.1f}%)")
|
|
499
|
+
|
|
500
|
+
with col4:
|
|
501
|
+
st.metric("Hari Datar", f"{flat_days} ({flat_days/total_days*100:.1f}%)")
|
|
502
|
+
|
|
503
|
+
# Analisis volatilitas
|
|
504
|
+
st.subheader("Analisis Volatilitas")
|
|
505
|
+
|
|
506
|
+
# Hitung volatilitas (standar deviasi dari perubahan harga)
|
|
507
|
+
volatility = df_sorted['Perubahan'].std()
|
|
508
|
+
|
|
509
|
+
# Hitung moving averages
|
|
510
|
+
df_sorted['MA_7'] = df_sorted[price_col].rolling(window=7).mean()
|
|
511
|
+
df_sorted['MA_20'] = df_sorted[price_col].rolling(window=20).mean()
|
|
512
|
+
|
|
513
|
+
# Hitung RSI (Relative Strength Index)
|
|
514
|
+
delta = df_sorted[price_col].diff()
|
|
515
|
+
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
|
516
|
+
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
|
517
|
+
rs = gain / loss
|
|
518
|
+
df_sorted['RSI'] = 100 - (100 / (1 + rs))
|
|
519
|
+
|
|
520
|
+
col1, col2, col3 = st.columns(3)
|
|
521
|
+
|
|
522
|
+
with col1:
|
|
523
|
+
st.metric("Volatilitas (Std Dev)", f"{volatility:.2f}%")
|
|
524
|
+
|
|
525
|
+
with col2:
|
|
526
|
+
# Tentukan apakah dalam kondisi overbought atau oversold berdasarkan RSI
|
|
527
|
+
if 'RSI' in df_sorted.columns and not df_sorted['RSI'].isna().all():
|
|
528
|
+
latest_rsi = df_sorted['RSI'].iloc[-1]
|
|
529
|
+
rsi_status = "Overbought (>70)" if latest_rsi > 70 else "Oversold (<30)" if latest_rsi < 30 else "Netral"
|
|
530
|
+
st.metric("RSI Terakhir", f"{latest_rsi:.2f}", rsi_status)
|
|
531
|
+
|
|
532
|
+
with col3:
|
|
533
|
+
# Tentukan trend berdasarkan moving averages
|
|
534
|
+
if 'MA_7' in df_sorted.columns and 'MA_20' in df_sorted.columns:
|
|
535
|
+
latest_ma7 = df_sorted['MA_7'].iloc[-1]
|
|
536
|
+
latest_ma20 = df_sorted['MA_20'].iloc[-1]
|
|
537
|
+
ma_trend = "Uptrend" if latest_ma7 > latest_ma20 else "Downtrend"
|
|
538
|
+
st.metric("Trend MA", ma_trend)
|
|
539
|
+
|
|
540
|
+
# Tampilkan grafik harga dengan matplotlib
|
|
541
|
+
st.subheader("Grafik Harga dengan Analisis Teknikal")
|
|
542
|
+
|
|
543
|
+
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 10))
|
|
544
|
+
|
|
545
|
+
# Plot harga dan moving averages
|
|
546
|
+
ax1.plot(df_sorted[date_col], df_sorted[price_col], label='Harga', color='blue', linewidth=1)
|
|
547
|
+
if 'MA_7' in df_sorted.columns:
|
|
548
|
+
ax1.plot(df_sorted[date_col], df_sorted['MA_7'], label='MA 7', color='orange', linewidth=1)
|
|
549
|
+
if 'MA_20' in df_sorted.columns:
|
|
550
|
+
ax1.plot(df_sorted[date_col], df_sorted['MA_20'], label='MA 20', color='red', linewidth=1)
|
|
551
|
+
ax1.set_ylabel('Harga')
|
|
552
|
+
ax1.set_title('Harga dan Moving Averages')
|
|
553
|
+
ax1.grid(True)
|
|
554
|
+
ax1.legend()
|
|
555
|
+
|
|
556
|
+
# Format tanggal pada sumbu x
|
|
557
|
+
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
|
558
|
+
ax1.xaxis.set_major_locator(mdates.MonthLocator())
|
|
559
|
+
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45)
|
|
560
|
+
|
|
561
|
+
# Plot perubahan harga
|
|
562
|
+
colors = ['green' if x >= 0 else 'red' for x in df_sorted['Perubahan_Abs']]
|
|
563
|
+
ax2.bar(df_sorted[date_col], df_sorted['Perubahan_Abs'], color=colors, alpha=0.7)
|
|
564
|
+
ax2.set_ylabel('Perubahan')
|
|
565
|
+
ax2.set_title('Perubahan Harga Harian')
|
|
566
|
+
ax2.grid(True)
|
|
567
|
+
|
|
568
|
+
# Format tanggal pada sumbu x
|
|
569
|
+
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
|
570
|
+
ax2.xaxis.set_major_locator(mdates.MonthLocator())
|
|
571
|
+
plt.setp(ax2.xaxis.get_majorticklabels(), rotation=45)
|
|
572
|
+
|
|
573
|
+
# Plot RSI jika tersedia
|
|
574
|
+
if 'RSI' in df_sorted.columns:
|
|
575
|
+
ax3.plot(df_sorted[date_col], df_sorted['RSI'], label='RSI', color='purple', linewidth=1)
|
|
576
|
+
ax3.axhline(y=70, color='r', linestyle='--', alpha=0.7, label='Overbought (70)')
|
|
577
|
+
ax3.axhline(y=30, color='g', linestyle='--', alpha=0.7, label='Oversold (30)')
|
|
578
|
+
ax3.set_ylabel('RSI')
|
|
579
|
+
ax3.set_title('Relative Strength Index (RSI)')
|
|
580
|
+
ax3.grid(True)
|
|
581
|
+
ax3.legend()
|
|
582
|
+
|
|
583
|
+
# Format tanggal pada sumbu x
|
|
584
|
+
ax3.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
|
585
|
+
ax3.xaxis.set_major_locator(mdates.MonthLocator())
|
|
586
|
+
plt.setp(ax3.xaxis.get_majorticklabels(), rotation=45)
|
|
587
|
+
|
|
588
|
+
plt.tight_layout()
|
|
589
|
+
st.pyplot(fig)
|
|
590
|
+
|
|
591
|
+
# Tampilkan sinyal trading sederhana
|
|
592
|
+
st.subheader("Sinyal Trading Sederhana")
|
|
593
|
+
|
|
594
|
+
if 'RSI' in df_sorted.columns and 'MA_7' in df_sorted.columns and 'MA_20' in df_sorted.columns:
|
|
595
|
+
# Ambil data terbaru
|
|
596
|
+
latest_data = df_sorted.iloc[-1]
|
|
597
|
+
|
|
598
|
+
# Generate sinyal berdasarkan RSI dan MA
|
|
599
|
+
signals = []
|
|
600
|
+
|
|
601
|
+
if latest_data['RSI'] < 30 and latest_data['MA_7'] > latest_data['MA_20']:
|
|
602
|
+
signals.append(("BUY", "RSI oversold dan MA7 > MA20"))
|
|
603
|
+
elif latest_data['RSI'] > 70 and latest_data['MA_7'] < latest_data['MA_20']:
|
|
604
|
+
signals.append(("SELL", "RSI overbought dan MA7 < MA20"))
|
|
605
|
+
elif latest_data['RSI'] < 30:
|
|
606
|
+
signals.append(("POTENTIAL BUY", "RSI oversold"))
|
|
607
|
+
elif latest_data['RSI'] > 70:
|
|
608
|
+
signals.append(("POTENTIAL SELL", "RSI overbought"))
|
|
609
|
+
elif latest_data['MA_7'] > latest_data['MA_20']:
|
|
610
|
+
signals.append(("BULLISH", "MA7 > MA20 (Uptrend)"))
|
|
611
|
+
elif latest_data['MA_7'] < latest_data['MA_20']:
|
|
612
|
+
signals.append(("BEARISH", "MA7 < MA20 (Downtrend)"))
|
|
613
|
+
else:
|
|
614
|
+
signals.append(("NEUTRAL", "Tidak ada sinyal kuat"))
|
|
615
|
+
|
|
616
|
+
# Tampilkan sinyal
|
|
617
|
+
for signal, reason in signals:
|
|
618
|
+
if signal == "BUY":
|
|
619
|
+
st.success(f"🚀 {signal}: {reason}")
|
|
620
|
+
elif signal == "SELL":
|
|
621
|
+
st.error(f"🔻 {signal}: {reason}")
|
|
622
|
+
elif signal in ["POTENTIAL BUY", "BULLISH"]:
|
|
623
|
+
st.info(f"📈 {signal}: {reason}")
|
|
624
|
+
elif signal in ["POTENTIAL SELL", "BEARISH"]:
|
|
625
|
+
st.warning(f"📉 {signal}: {reason}")
|
|
626
|
+
else:
|
|
627
|
+
st.info(f"📊 {signal}: {reason}")
|
|
628
|
+
|
|
629
|
+
except Exception as e:
|
|
630
|
+
st.error(f"Error dalam analisis trend: {str(e)}")
|
|
631
|
+
|
|
632
|
+
# Fungsi untuk membuat file contoh dengan data saham
|
|
633
|
+
def create_sample_file():
|
|
634
|
+
dates = pd.date_range(start='2023-01-01', periods=100, freq='D')
|
|
635
|
+
|
|
636
|
+
# Generate harga saham dengan random walk
|
|
637
|
+
np.random.seed(42)
|
|
638
|
+
price_changes = np.random.normal(0.001, 0.02, len(dates))
|
|
639
|
+
prices = 100 * (1 + price_changes).cumprod()
|
|
640
|
+
|
|
641
|
+
# Generate volume
|
|
642
|
+
volumes = np.random.randint(1000, 10000, len(dates))
|
|
643
|
+
|
|
644
|
+
example_data = pd.DataFrame({
|
|
645
|
+
'Tanggal': dates,
|
|
646
|
+
'Open': prices * (1 + np.random.normal(0, 0.01, len(dates))),
|
|
647
|
+
'High': prices * (1 + np.abs(np.random.normal(0.005, 0.01, len(dates)))),
|
|
648
|
+
'Low': prices * (1 - np.abs(np.random.normal(0.005, 0.01, len(dates)))),
|
|
649
|
+
'Close': prices,
|
|
650
|
+
'Volume': volumes,
|
|
651
|
+
'Perusahaan': np.random.choice(['AAPL', 'GOOGL', 'MSFT', 'AMZN'], len(dates)),
|
|
652
|
+
'Sektor': np.random.choice(['Teknologi', 'Kesehatan', 'Finansial', 'Konsumsi'], len(dates))
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
# Adjust OHLC untuk konsistensi
|
|
656
|
+
for i in range(len(example_data)):
|
|
657
|
+
example_data.loc[i, 'High'] = max(example_data.loc[i, 'Open'], example_data.loc[i, 'Close'], example_data.loc[i, 'High'])
|
|
658
|
+
example_data.loc[i, 'Low'] = min(example_data.loc[i, 'Open'], example_data.loc[i, 'Close'], example_data.loc[i, 'Low'])
|
|
659
|
+
|
|
660
|
+
return example_data
|
|
661
|
+
|
|
662
|
+
# Fungsi untuk analisis korelasi antar saham
|
|
663
|
+
def analyze_stock_correlation(df):
|
|
664
|
+
st.header("Analisis Korelasi Antar Saham")
|
|
665
|
+
|
|
666
|
+
# Identifikasi kolom yang mungkin berisi data harga saham
|
|
667
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
668
|
+
date_cols = df.select_dtypes(include=['datetime64']).columns.tolist()
|
|
669
|
+
|
|
670
|
+
if not date_cols:
|
|
671
|
+
for col in df.columns:
|
|
672
|
+
if df[col].dtype == 'object':
|
|
673
|
+
try:
|
|
674
|
+
df[col] = pd.to_datetime(df[col])
|
|
675
|
+
date_cols.append(col)
|
|
676
|
+
break
|
|
677
|
+
except:
|
|
678
|
+
pass
|
|
679
|
+
|
|
680
|
+
if date_cols and len(numeric_cols) > 1:
|
|
681
|
+
date_col = st.selectbox("Pilih kolom tanggal untuk analisis korelasi", date_cols)
|
|
682
|
+
price_cols = st.multiselect("Pilih kolom harga untuk analisis korelasi", numeric_cols, default=numeric_cols[:min(5, len(numeric_cols))])
|
|
683
|
+
|
|
684
|
+
if len(price_cols) > 1:
|
|
685
|
+
try:
|
|
686
|
+
# Pastikan data diurutkan berdasarkan tanggal
|
|
687
|
+
df_sorted = df.sort_values(by=date_col).copy()
|
|
688
|
+
|
|
689
|
+
# Bersihkan data dari nilai infinite dan NaN
|
|
690
|
+
clean_data = df_sorted[price_cols].replace([np.inf, -np.inf], np.nan).dropna()
|
|
691
|
+
|
|
692
|
+
if len(clean_data) > 1:
|
|
693
|
+
# Hitung korelasi
|
|
694
|
+
correlation_matrix = clean_data.corr()
|
|
695
|
+
|
|
696
|
+
# Tampilkan heatmap korelasi
|
|
697
|
+
fig = px.imshow(correlation_matrix,
|
|
698
|
+
text_auto=True,
|
|
699
|
+
aspect="auto",
|
|
700
|
+
title="Korelasi Antar Saham",
|
|
701
|
+
color_continuous_scale='RdBu_r',
|
|
702
|
+
zmin=-1, zmax=1)
|
|
703
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
704
|
+
|
|
705
|
+
# Tampilkan matriks korelasi sebagai tabel
|
|
706
|
+
st.subheader("Matriks Korelasi")
|
|
707
|
+
st.dataframe(correlation_matrix.style.background_gradient(cmap='RdBu_r', vmin=-1, vmax=1).format("{:.2f}"))
|
|
708
|
+
|
|
709
|
+
# Analisis pasangan dengan korelasi tertinggi dan terendah
|
|
710
|
+
st.subheader("Pasangan dengan Korelasi Ekstrem")
|
|
711
|
+
|
|
712
|
+
# Dapatkan pasangan dengan korelasi tertinggi dan terendah
|
|
713
|
+
corr_pairs = []
|
|
714
|
+
for i in range(len(correlation_matrix.columns)):
|
|
715
|
+
for j in range(i+1, len(correlation_matrix.columns)):
|
|
716
|
+
corr_pairs.append({
|
|
717
|
+
'Pair': f"{correlation_matrix.columns[i]} - {correlation_matrix.columns[j]}",
|
|
718
|
+
'Correlation': correlation_matrix.iloc[i, j]
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
corr_df = pd.DataFrame(corr_pairs)
|
|
722
|
+
top_corr = corr_df.nlargest(3, 'Correlation')
|
|
723
|
+
bottom_corr = corr_df.nsmallest(3, 'Correlation')
|
|
724
|
+
|
|
725
|
+
col1, col2 = st.columns(2)
|
|
726
|
+
|
|
727
|
+
with col1:
|
|
728
|
+
st.markdown("**Korelasi Tertinggi**")
|
|
729
|
+
for _, row in top_corr.iterrows():
|
|
730
|
+
st.write(f"{row['Pair']}: {row['Correlation']:.3f}")
|
|
731
|
+
|
|
732
|
+
with col2:
|
|
733
|
+
st.markdown("**Korelasi Terendah**")
|
|
734
|
+
for _, row in bottom_corr.iterrows():
|
|
735
|
+
st.write(f"{row['Pair']}: {row['Correlation']:.3f}")
|
|
736
|
+
|
|
737
|
+
else:
|
|
738
|
+
st.error("Tidak cukup data untuk analisis korelasi setelah pembersihan")
|
|
739
|
+
except Exception as e:
|
|
740
|
+
st.error(f"Error dalam analisis korelasi: {str(e)}")
|
|
741
|
+
else:
|
|
742
|
+
st.error("Pilih setidaknya 2 kolom harga untuk analisis korelasi")
|
|
743
|
+
else:
|
|
744
|
+
st.error("Data tidak memiliki cukup kolom tanggal atau numerik untuk analisis korelasi")
|
|
745
|
+
|
|
746
|
+
# UI utama
|
|
747
|
+
st.markdown('<h1 class="main-header">📊 Dashboard Statistik Profesional</h1>', unsafe_allow_html=True)
|
|
748
|
+
st.markdown("Unggah file CSV atau Excel untuk melihat visualisasi dan statistik data.")
|
|
749
|
+
|
|
750
|
+
# Sidebar untuk upload file dan kontrol
|
|
751
|
+
st.sidebar.header("Kontrol Aplikasi")
|
|
752
|
+
|
|
753
|
+
# Tombol untuk membuat dan mengunduh file contoh
|
|
754
|
+
if st.sidebar.button("Buat File Contoh"):
|
|
755
|
+
example_data = create_sample_file()
|
|
756
|
+
csv = example_data.to_csv(index=False)
|
|
757
|
+
st.sidebar.download_button(
|
|
758
|
+
label="Unduh Contoh CSV",
|
|
759
|
+
data=csv,
|
|
760
|
+
file_name="contoh_data_saham.csv",
|
|
761
|
+
mime="text/csv"
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
# Fitur untuk mengunggah dan menggabungkan beberapa file
|
|
765
|
+
st.sidebar.header("Unggah & Gabungkan Beberapa File")
|
|
766
|
+
uploaded_files = st.sidebar.file_uploader(
|
|
767
|
+
"Pilih file CSV atau Excel (bisa multiple)",
|
|
768
|
+
type=['csv', 'xlsx', 'xls'],
|
|
769
|
+
help="Format yang didukung: CSV, XLSX, XLS",
|
|
770
|
+
accept_multiple_files=True
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
# Pilihan metode penggabungan jika ada beberapa file
|
|
774
|
+
merge_method = "concat"
|
|
775
|
+
if uploaded_files and len(uploaded_files) > 1:
|
|
776
|
+
merge_method = st.sidebar.selectbox(
|
|
777
|
+
"Metode Penggabungan Data",
|
|
778
|
+
["concat", "inner", "outer", "left", "right"],
|
|
779
|
+
help="Concat: menggabungkan baris, lainnya: menggabungkan berdasarkan kolom"
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
# Proses file yang diupload
|
|
783
|
+
df = None
|
|
784
|
+
if uploaded_files:
|
|
785
|
+
datasets = []
|
|
786
|
+
for uploaded_file in uploaded_files:
|
|
787
|
+
dataset = process_uploaded_file(uploaded_file)
|
|
788
|
+
if dataset is not None:
|
|
789
|
+
datasets.append(dataset)
|
|
790
|
+
st.sidebar.success(f"File berhasil diunggah: {uploaded_file.name}")
|
|
791
|
+
|
|
792
|
+
if datasets:
|
|
793
|
+
if len(datasets) == 1:
|
|
794
|
+
df = datasets[0]
|
|
795
|
+
else:
|
|
796
|
+
df = merge_datasets(datasets, merge_method)
|
|
797
|
+
st.sidebar.success(f"Berhasil menggabungkan {len(datasets)} dataset")
|
|
798
|
+
|
|
799
|
+
# Jika file sudah diupload
|
|
800
|
+
if df is not None:
|
|
801
|
+
# Tampilkan statistik
|
|
802
|
+
show_statistics(df)
|
|
803
|
+
|
|
804
|
+
# Tampilkan visualisasi
|
|
805
|
+
st.header("Visualisasi Data")
|
|
806
|
+
create_visualizations(df)
|
|
807
|
+
|
|
808
|
+
# Analisis korelasi antar saham
|
|
809
|
+
analyze_stock_correlation(df)
|
|
810
|
+
|
|
811
|
+
# Tambahkan opsi untuk mengunduh data yang telah diproses
|
|
812
|
+
st.sidebar.header("Unduh Data")
|
|
813
|
+
if st.sidebar.button("Unduh Data yang Telah Diproses"):
|
|
814
|
+
csv = df.to_csv(index=False)
|
|
815
|
+
st.sidebar.download_button(
|
|
816
|
+
label="Unduh sebagai CSV",
|
|
817
|
+
data=csv,
|
|
818
|
+
file_name="processed_data.csv",
|
|
819
|
+
mime="text/csv"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
else:
|
|
823
|
+
# Tampilkan contoh data jika belum ada file yang diupload
|
|
824
|
+
st.info("Silakan unggah file CSV atau Excel melalui sidebar di sebelah kiri.")
|
|
825
|
+
|
|
826
|
+
# Buat contoh data
|
|
827
|
+
st.subheader("Contoh Data")
|
|
828
|
+
example_data = create_sample_file()
|
|
829
|
+
display_dataframe(example_data)
|
|
830
|
+
|
|
831
|
+
# Tampilkan contoh visualisasi
|
|
832
|
+
st.subheader("Contoh Visualisasi")
|
|
833
|
+
|
|
834
|
+
col1, col2 = st.columns(2)
|
|
835
|
+
|
|
836
|
+
with col1:
|
|
837
|
+
fig_example = px.line(example_data, x='Tanggal', y='Close', title='Contoh Trend Harga Saham')
|
|
838
|
+
st.plotly_chart(fig_example, use_container_width=True)
|
|
839
|
+
|
|
840
|
+
with col2:
|
|
841
|
+
# Pie chart contoh
|
|
842
|
+
sector_data = example_data.groupby('Sektor')['Volume'].sum().reset_index()
|
|
843
|
+
fig_pie = px.pie(sector_data, names='Sektor', values='Volume', title='Volume Perdagangan per Sektor')
|
|
844
|
+
st.plotly_chart(fig_pie, use_container_width=True)
|
|
845
|
+
|
|
846
|
+
# Footer
|
|
847
|
+
st.markdown("---")
|
|
848
|
+
st.markdown("Dashboard Statistik © 2025 - Dibuat oleh Dwi Bakti N Dev")
|