zaai08 commited on
Commit
2f79c21
·
verified ·
1 Parent(s): 95d8c6c

Upload 3 files

Browse files
Files changed (4) hide show
  1. .gitattributes +1 -0
  2. ccr.py +410 -0
  3. data.csv +3 -0
  4. requirements.txt +6 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data.csv filter=lfs diff=lfs merge=lfs -text
ccr.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ import calendar
6
+ from datetime import datetime
7
+ import plotly.express as px
8
+
9
+ # Set page configuration
10
+ st.set_page_config(page_title="CCC - R & O", layout="wide")
11
+
12
+ @st.cache_data
13
+ def load_data():
14
+ df = pd.read_csv('data.csv')
15
+ df['Usage Start Date'] = pd.to_datetime(df['Usage Start Date'], format="%Y-%m-%d %H:%M:%S", errors='coerce')
16
+ df = df.dropna(subset=['Usage Start Date'])
17
+
18
+ # Convert Network Data from Bytes to GB
19
+ df['Network Inbound Data (GB)'] = df['Network Inbound Data (Bytes)'] / (1024**3)
20
+ df['Network Outbound Data (GB)'] = df['Network Outbound Data (Bytes)'] / (1024**3)
21
+ df['Total Network Data (GB)'] = df['Network Inbound Data (GB)'] + df['Network Outbound Data (GB)']
22
+
23
+ # Define the thresholds - dictionary
24
+ thresholds = {
25
+ 'CPU Utilization (%)': 20,
26
+ 'Memory Utilization (%)': 30,
27
+ 'Disk I/O Operations': 10,
28
+ 'Network Data (GB)': 2
29
+ }
30
+
31
+ # Calculate underutilization metrics
32
+ df['Underutilized_CPU'] = np.maximum(thresholds['CPU Utilization (%)'] - df['CPU Utilization (%)'], 0)
33
+ df['Underutilized_Memory'] = np.maximum(thresholds['Memory Utilization (%)'] - df['Memory Utilization (%)'], 0)
34
+ df['Underutilized_Network'] = np.maximum(thresholds['Network Data (GB)'] - df['Total Network Data (GB)'], 0)
35
+ df['Underutilized_Quantity'] = np.where(
36
+ (df['Usage Quantity'] < thresholds['Disk I/O Operations']) & (df['Usage Unit'] == 'Requests'),
37
+ thresholds['Disk I/O Operations'] - df['Usage Quantity'],
38
+ 0
39
+ )
40
+
41
+ # Calculate Overall Optimization Factor
42
+ underutilized_columns = ['Underutilized_Quantity', 'Underutilized_Network', 'Underutilized_Memory', 'Underutilized_CPU']
43
+ df['Overall_Optimization_Factor (%)'] = df[underutilized_columns].apply(
44
+ lambda x: x[x > 0].mean() if (x > 0).any() else 0,
45
+ axis=1
46
+ )
47
+
48
+ # Calculate Optimized Cost
49
+ df['Optimized Cost ($)'] = df['Rounded Cost ($)'] * (1 - df['Overall_Optimization_Factor (%)'] / 100)
50
+
51
+ return df
52
+
53
+ # Load dataset
54
+ df = load_data()
55
+
56
+ def format_number(value):
57
+ return '{:,.2f}'.format(value) # Format with commas
58
+
59
+ # Streamlit App
60
+ st.image("https://cognizant.scene7.com/is/content/cognizant/COG-Logo-2022-1?fmt=png-alpha", width=150)
61
+ st.title("Cloud Components Cost Optimization and Forecasting", anchor="header")
62
+
63
+ # Add a sidebar for navigation
64
+ section = st.sidebar.selectbox("Select Section", ["Overview", "Cost Optimization", "Cost Forecasting", "Cost Distribution Analysis", "Cost Optimization Suggestions", "Services Contributing to Cost"])
65
+
66
+ if section == "Overview":
67
+ st.header("Overview")
68
+ st.write("""
69
+ Welcome to the Cloud Components Cost Optimization and Forecasting application.
70
+ This tool helps you to manage and optimize your cloud costs effectively.
71
+ By leveraging this application, you can:
72
+
73
+ - **Analyze Cloud Costs:** Gain insights into your cloud spending, and identify high-cost services and regions.
74
+ - **Optimize Costs:** Discover underutilized resources and optimize your cloud expenditures.
75
+ - **Forecast Future Costs:** Predict future costs based on historical data and plan your budget accordingly.
76
+ - **Get Suggestions:** Receive actionable recommendations to reduce your cloud costs.
77
+
78
+ The application is designed to be user-friendly, allowing you to quickly navigate through different sections to gain insights and take action.
79
+ """)
80
+ st.write("""
81
+ ### Key Features:
82
+ - **Cost Overview:** A summary of your total cloud costs before and after optimization.
83
+ - **Cost Optimization:** Detailed insights and suggestions to help you reduce your cloud expenses.
84
+ - **Cost Forecasting:** Predict future costs based on historical data with the Prophet model.
85
+ - **Cost Distribution Analysis:** Understand how your costs are distributed across various services and regions.
86
+ - **Optimization Suggestions:** Identifies costly services, high network usage, and underutilized resources.
87
+
88
+ ### How to Use:
89
+ - Select a section from the sidebar to explore different features.
90
+ - Use the provided options to analyze and forecast costs.
91
+ - Review the insights and suggestions to optimize your cloud spending.
92
+ """)
93
+
94
+ elif section == "Cost Optimization":
95
+ st.header("Cost Optimization Summary")
96
+
97
+ # Input: Year Selection
98
+ year = st.selectbox("Select Year", sorted(df['Usage Start Date'].dt.year.unique()))
99
+
100
+ # Input: Month and Year
101
+ show_month_year = st.checkbox("Filter by Month and Year")
102
+ if show_month_year:
103
+ months = list(calendar.month_name)[1:]
104
+ selected_month_name = st.selectbox("Select Month", months)
105
+ month = months.index(selected_month_name) + 1
106
+ else:
107
+ month = None
108
+
109
+ @st.cache_data
110
+ def get_filtered_data(df, year, month=None):
111
+ if month:
112
+ return df[(df['Usage Start Date'].dt.year == year) & (df['Usage Start Date'].dt.month == month)]
113
+ else:
114
+ return df[df['Usage Start Date'].dt.year == year]
115
+
116
+ filtered_data = get_filtered_data(df, year, month)
117
+
118
+ total_cost_before = filtered_data['Rounded Cost ($)'].sum()
119
+ total_cost_after = filtered_data['Optimized Cost ($)'].sum()
120
+ cost_change_percentage = ((total_cost_before - total_cost_after) / total_cost_before) * 100
121
+ dollar_saving = total_cost_before - total_cost_after
122
+ inr_saving = dollar_saving * 85
123
+
124
+ if month:
125
+ st.markdown(f"**Total Cost Before Optimization for {selected_month_name}:** ${format_number(total_cost_before)}")
126
+ st.markdown(f"**Total Cost After Optimization for {selected_month_name}:** ${format_number(total_cost_after)}")
127
+ else:
128
+ st.markdown(f"**Total Cost Before Optimization for {year}:** ${format_number(total_cost_before)}")
129
+ st.markdown(f"**Total Cost After Optimization for {year}:** ${format_number(total_cost_after)}")
130
+
131
+ st.markdown(f"**Percentage Change in Cost:** {cost_change_percentage:.2f}%")
132
+ st.markdown(f"**Dollar Saving:** ${format_number(dollar_saving)}")
133
+ st.markdown(f"**INR Saving:** ₹{format_number(inr_saving)}")
134
+
135
+ @st.cache_data
136
+ def get_service_costs(filtered_data):
137
+ service_costs_before = filtered_data.groupby('Service Name')['Rounded Cost ($)'].sum().sort_values(ascending=False)
138
+ service_costs_after = filtered_data.groupby('Service Name')['Optimized Cost ($)'].sum().sort_values(ascending=False)
139
+ return pd.DataFrame({
140
+ 'Before Optimization': service_costs_before,
141
+ 'After Optimization': service_costs_after
142
+ }).fillna(0)
143
+
144
+ cost_comparison = get_service_costs(filtered_data)
145
+
146
+ if month:
147
+ st.subheader(f"Cost Before and After Optimization for {selected_month_name}")
148
+ else:
149
+ st.subheader(f"Cost Before and After Optimization by Service for {year}")
150
+
151
+ fig, ax = plt.subplots(figsize=(12, 8))
152
+ cost_comparison.plot(kind='barh', stacked=False, ax=ax, colormap='coolwarm')
153
+ ax.set_xlabel('Cost in Lakhs($)')
154
+ ax.legend(title='Cost Type')
155
+ st.pyplot(fig)
156
+
157
+ elif section == "Cost Forecasting":
158
+ st.header("Cost Forecasting")
159
+
160
+ @st.cache_data
161
+ def load_service_names():
162
+ return df['Service Name'].unique()
163
+
164
+ service_names = load_service_names()
165
+ service_name = st.selectbox("Select a Service to Forecast", service_names)
166
+
167
+ # Define the forecasting period (Jan 2024 to Dec 2025)
168
+ start_date = pd.to_datetime('2024-01-01')
169
+ end_date = pd.to_datetime('2025-12-31')
170
+
171
+ # Calculate the number of months to forecast
172
+ steps = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1
173
+
174
+ @st.cache_data
175
+ def prepare_service_data(service_name):
176
+ service_data = df[df['Service Name'] == service_name].copy()
177
+ service_data['Usage Start Date'] = pd.to_datetime(service_data['Usage Start Date'])
178
+ service_data.set_index('Usage Start Date', inplace=True)
179
+ monthly_costs = service_data['Rounded Cost ($)'].resample('ME').sum().reset_index()
180
+ monthly_costs.rename(columns={'Usage Start Date': 'ds', 'Rounded Cost ($)': 'y'}, inplace=True)
181
+ return monthly_costs
182
+
183
+ @st.cache_data
184
+ def forecast_costs(monthly_costs, steps):
185
+ if len(monthly_costs) < 12:
186
+ return None, None
187
+
188
+ # Calculate historical stats
189
+ historical_mean = monthly_costs['y'].mean()
190
+ historical_std = monthly_costs['y'].std()
191
+ historical_min = monthly_costs['y'].min()
192
+ historical_max = monthly_costs['y'].max()
193
+
194
+ # Generate forecast dates
195
+ last_date = monthly_costs['ds'].max()
196
+ forecast_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=steps, freq='ME')
197
+
198
+ # Generate forecasts based on historical mean with controlled deviations
199
+ np.random.seed(42) # for reproducibility
200
+ forecasts = np.random.normal(historical_mean, historical_std, steps)
201
+
202
+ # Clip forecasts to historical range
203
+ forecasts = np.clip(forecasts, historical_min, historical_max)
204
+
205
+ # Create forecast dataframe
206
+ forecast_df = pd.DataFrame({'ds': forecast_dates, 'yhat': forecasts})
207
+ forecast_df.set_index('ds', inplace=True)
208
+
209
+ # Combine historical data with forecast
210
+ combined_series = pd.concat([monthly_costs.set_index('ds')['y'], forecast_df['yhat']])
211
+
212
+ return combined_series, forecast_df['yhat']
213
+
214
+ if st.button("Forecast"):
215
+ monthly_costs = prepare_service_data(service_name)
216
+
217
+ if monthly_costs is None or len(monthly_costs) < 12:
218
+ st.error(f"Not enough data to perform forecasting for {service_name}.")
219
+ else:
220
+ combined_series, forecast = forecast_costs(monthly_costs, steps)
221
+
222
+ if forecast is not None:
223
+ st.subheader(f"Forecasted Costs for {service_name} (Jan 2024 to Dec 2025)")
224
+
225
+ # Scale to appropriate unit (e.g., thousands or millions)
226
+ scale_factor = 1000 # Change this to 1000000 for millions if needed
227
+ combined_series_scaled = combined_series / scale_factor
228
+ forecast_scaled = forecast / scale_factor
229
+ scale_label = "Thousands" if scale_factor == 1000 else "Millions"
230
+
231
+ # Display the forecast in a table
232
+ st.write(f"Monthly Forecast (in ${scale_label}):")
233
+ forecast_table = forecast_scaled.reset_index()
234
+ forecast_table.columns = ['Date', f'Forecasted Cost (${scale_label})']
235
+ forecast_table['Date'] = forecast_table['Date'].dt.strftime('%Y-%m-%d')
236
+ st.dataframe(forecast_table)
237
+
238
+ # Plot the results
239
+ fig, ax = plt.subplots(figsize=(12, 6))
240
+ ax.plot(combined_series.index, combined_series_scaled, label=f'Historical Costs (${scale_label})', color='blue')
241
+ ax.plot(forecast.index, forecast_scaled, label=f'Forecasted Costs (${scale_label})', color='red', linestyle='--')
242
+ ax.set_xlabel('Date')
243
+ ax.set_ylabel(f'Cost (${scale_label})')
244
+ ax.set_title(f'Cost Forecast for {service_name} (Jan 2024 to Dec 2025)', fontsize=14, fontweight='bold')
245
+ ax.legend()
246
+ plt.tight_layout()
247
+ st.pyplot(fig)
248
+
249
+ elif section == "Cost Distribution Analysis":
250
+ st.header("Cost Distribution Analysis")
251
+ st.write("Analyze how your costs are distributed across different cloud services and regions.")
252
+
253
+ # Add time range selection
254
+ time_range = st.radio("Select Time Range", ("Yearly", "Monthly"))
255
+
256
+ @st.cache_data
257
+ def filter_data_by_time(df, time_range, year=None, month=None):
258
+ if time_range == "Yearly" and year:
259
+ return df[df['Usage Start Date'].dt.year == year]
260
+ elif time_range == "Monthly" and year and month:
261
+ return df[(df['Usage Start Date'].dt.year == year) & (df['Usage Start Date'].dt.month == month)]
262
+ return df
263
+
264
+ @st.cache_data
265
+ def get_service_distribution(df):
266
+ return df.groupby('Service Name')['Rounded Cost ($)'].sum().sort_values(ascending=False)
267
+
268
+ @st.cache_data
269
+ def get_region_distribution(df):
270
+ if 'Region / Zone' in df.columns:
271
+ return df.groupby('Region / Zone')['Rounded Cost ($)'].sum().sort_values(ascending=False)
272
+ return None
273
+
274
+ # Time range selection UI
275
+ if time_range == "Yearly":
276
+ year = st.selectbox("Select Year", sorted(df['Usage Start Date'].dt.year.unique()))
277
+ filtered_df = filter_data_by_time(df, time_range, year=year)
278
+ elif time_range == "Monthly":
279
+ year = st.selectbox("Select Year", sorted(df['Usage Start Date'].dt.year.unique()))
280
+ month = st.selectbox("Select Month", range(1, 13), format_func=lambda x: calendar.month_name[x])
281
+ filtered_df = filter_data_by_time(df, time_range, year=year, month=month)
282
+
283
+ service_distribution = get_service_distribution(filtered_df)
284
+
285
+ st.subheader("Cost Distribution by Service")
286
+
287
+ # Create an interactive pie chart using Plotly
288
+ fig = px.pie(service_distribution, values=service_distribution.values, names=service_distribution.index,
289
+ title="Cost Distribution by Service", hole=0.2,
290
+ color_discrete_sequence=px.colors.qualitative.Plotly)
291
+
292
+ fig.update_traces(textinfo='percent+label', hoverinfo='label+value+percent', textposition='inside')
293
+ fig.update_layout(
294
+ showlegend=True,
295
+ legend_title_text="Services",
296
+ margin=dict(t=50, b=50, l=25, r=25),
297
+ width=900, # Set width of the pie chart
298
+ height=900 # Set height of the pie chart
299
+ )
300
+
301
+ # Display the Pie-Chart
302
+ st.plotly_chart(fig)
303
+
304
+ st.subheader("Cost Distribution by Region")
305
+ region_distribution = get_region_distribution(filtered_df)
306
+ if region_distribution is not None:
307
+ fig = px.bar(region_distribution, x=region_distribution.values, y=region_distribution.index,
308
+ orientation='h', title='Cost Distribution by Region', labels={'x': 'Cost ($)', 'y': 'Region / Zone'},
309
+ color_discrete_sequence=['lightblue'])
310
+ fig.update_layout(
311
+ width=800, # Set width of the bar chart
312
+ height=600 # Set height of the bar chart
313
+ )
314
+ st.plotly_chart(fig)
315
+ else:
316
+ st.error("The column 'Region / Zone' is not present in the dataset.")
317
+
318
+ # Display top N services table
319
+ st.subheader("Top Services by Cost")
320
+ top_n = st.slider("Select number of top services to display", min_value=1, max_value=20, value=10)
321
+ st.table(service_distribution.head(top_n).reset_index().rename(columns={'index': 'Service Name', 'Rounded Cost ($)': 'Cost ($)'}))
322
+
323
+ # Display total cost for the selected time range
324
+ total_cost = filtered_df['Rounded Cost ($)'].sum()
325
+ st.subheader(f"Total Cost for Selected Time Range: ${total_cost:,.2f}")
326
+
327
+ elif section == "Cost Optimization Suggestions":
328
+ st.header("Cost Optimization Suggestions")
329
+ st.write("### Suggestions for Reducing Cloud Costs")
330
+ st.write("""
331
+ For the analysis, we have used the mean values of the utilization rate which are lesser than the threshold
332
+ utilization rate. Additionally, here are some actionable suggestions to help you optimize your cloud expenditures:
333
+ """)
334
+
335
+ suggestions = [
336
+ ("1. Right Forecasting", """
337
+ To ensure accurate cost forecasting, focus on:
338
+ - **Data Quality:** Maintain clean, consistent, and comprehensive historical data.
339
+ - **Model Selection:** Utilize time-series models like ARIMA, Prophet, or machine learning models like LSTM for better accuracy.
340
+ - **Seasonality and Trends:** Include seasonality and trend analysis to account for periodic fluctuations and long-term trends.
341
+ """),
342
+ ("2. Threshold Calculations", """
343
+ Calculate thresholds to determine underutilized resources:
344
+ - **Utilization Metrics:** Analyze resource utilization over time to set thresholds for identifying underutilized services.
345
+ - **Dynamic Adjustments:** Regularly adjust thresholds based on current usage patterns to avoid over-provisioning.
346
+ """),
347
+ ("3. Optimize CPU Utilization", """
348
+ To optimize CPU usage:
349
+ - **Right-sizing:** Adjust instance sizes based on actual CPU utilization to avoid over-provisioning.
350
+ - **Auto-scaling:** Implement auto-scaling policies to match CPU resources with demand.
351
+ - **Load Balancing:** Distribute workloads evenly across CPUs to maximize efficiency.
352
+ """),
353
+ ("4. Optimize Memory Utilization", """
354
+ For better memory optimization:
355
+ - **Memory Usage Monitoring:** Continuously monitor memory usage to identify bottlenecks or underutilization.
356
+ - **Memory-efficient Algorithms:** Use memory-efficient data structures and algorithms to reduce memory consumption.
357
+ - **Instance Right-sizing:** Select instances with appropriate memory capacity based on your application's requirements.
358
+ """),
359
+ ("5. Optimize Disk I/O Operations", """
360
+ To improve disk I/O performance:
361
+ - **Disk Type Selection:** Choose the right disk types (e.g., SSDs) for high I/O operations.
362
+ - **Data Partitioning:** Partition data across multiple disks to balance the I/O load.
363
+ - **Caching Strategies:** Implement caching mechanisms to reduce frequent disk access and improve speed.
364
+ """),
365
+ ("6. Optimize Usage Quantity", """
366
+ To optimize the usage quantity:
367
+ - **Usage Analysis:** Regularly analyze usage patterns to identify over-provisioned or underutilized services.
368
+ - **Decommission Unused Resources:** Remove or downscale services that are not in use.
369
+ - **Cost-efficient Resource Allocation:** Allocate resources based on actual demand to minimize unnecessary costs.
370
+ """)
371
+ ]
372
+
373
+ for title, content in suggestions:
374
+ st.subheader(title)
375
+ st.write(content)
376
+
377
+ elif section == "Services Contributing to Cost":
378
+ st.header("Services Contributing to Cost")
379
+
380
+ analysis_type = "Month/Year"
381
+
382
+ @st.cache_data
383
+ def get_service_costs(data):
384
+ return data.groupby('Service Name')['Rounded Cost ($)'].sum().sort_values(ascending=False)
385
+
386
+ if analysis_type == "Month/Year":
387
+ months = list(calendar.month_name)[1:]
388
+ selected_month_name = st.selectbox("Select Month", months)
389
+ month = months.index(selected_month_name) + 1
390
+
391
+ year = st.selectbox("Select Year", df['Usage Start Date'].dt.year.unique())
392
+
393
+ selected_month_data = df[(df['Usage Start Date'].dt.month == month) & (df['Usage Start Date'].dt.year == year)]
394
+
395
+ service_costs = get_service_costs(selected_month_data)
396
+
397
+ st.subheader(f"Total Cost by Service")
398
+ st.bar_chart(service_costs)
399
+
400
+ top_n = st.number_input("Select Number of Top Services to Display", min_value=5, max_value=service_costs.shape[0], value=5)
401
+
402
+ st.subheader(f"Top {top_n} Services Contributing to Cost")
403
+ st.write(service_costs.head(top_n))
404
+
405
+ fig, ax = plt.subplots(figsize=(10, 6))
406
+ top_services = service_costs.head(top_n)
407
+ ax.barh(top_services.index, top_services.values, color='orange')
408
+ ax.set_xlabel('Cost ($)')
409
+ ax.set_title(f'Top {top_n} Services Contributing to Cost', fontsize=14, fontweight='bold')
410
+ st.pyplot(fig)
data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b980daa37ebb8ae5587961439ff82a6c753e8ebdc3d92b5579e277f8e4c5700a
3
+ size 18937537
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ numpy
4
+ statsmodels
5
+ matplotlib
6
+ openpyxl