fruitpicker01 commited on
Commit
3785bb2
·
verified ·
1 Parent(s): ee90e1b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -42
app.py CHANGED
@@ -1,8 +1,6 @@
1
  import gradio as gr
2
  import pandas as pd
3
  import plotly.express as px
4
-
5
- # Для Prophet
6
  from prophet import Prophet
7
 
8
  # Ссылки на CSV-файлы
@@ -40,6 +38,46 @@ def read_and_process_data(url, user_name):
40
 
41
  return unique_count, df_daily
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def process_data():
44
  # Считываем и обрабатываем все репозитории
45
  dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша")
@@ -57,11 +95,7 @@ def process_data():
57
 
58
  # Генерируем HTML-прогресс-бары
59
  def get_progress_bar(label, abs_val, pct):
60
- # Определяем "ёмкость"
61
- if label in ["Даша", "Лера", "Света"]:
62
- capacity = 234
63
- else:
64
- capacity = 702
65
  return f"""
66
  <div style='margin-bottom: 1em;'>
67
  <div><strong>{label}</strong></div>
@@ -80,107 +114,127 @@ def process_data():
80
  get_progress_bar("Всего", total_count, total_percent)
81
  )
82
 
83
- # Собираем единый DataFrame по дням
84
  daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
85
- # Уберём NaT
86
  daily_all = daily_all.dropna(subset=["date"])
87
 
88
- # Считаем кумулятивное количество SMS отдельно для каждого пользователя
89
  daily_all = daily_all.sort_values(by=["user", "date"])
90
  daily_all["cumulative"] = daily_all.groupby("user")["count"].cumsum()
91
 
92
- # Чтобы отразить "Всего", добавим user="Всего"
93
  total_by_date = daily_all.groupby("date")["count"].sum().reset_index(name="count")
94
  total_by_date = total_by_date.sort_values(by="date")
95
  total_by_date["cumulative"] = total_by_date["count"].cumsum()
96
  total_by_date["user"] = "Всего"
97
 
98
- # Объединим с daily_all
99
  daily_all_final = pd.concat([daily_all, total_by_date], ignore_index=True)
100
 
101
- # Определим порядок легенды, отсортировав по последнему кумулятивному значению
102
- # (у кого больше — тот выше)
103
  last_values = daily_all_final.groupby("user")["cumulative"].last().sort_values(ascending=False)
104
- sorted_users = last_values.index.tolist() # в порядке убывания
105
 
106
- # Явно зададим цвета
107
  color_map = {
108
- "Даша": "#1f77b4", # синий
109
- "Лера": "#2ca02c", # зелёный
110
- "Света": "#d62728", # красный
111
- "Всего": "#9467bd" # фиолетовый
112
  }
113
 
114
- # Строим накопительный (кумулятивный) график (линии)
115
  fig = px.line(
116
  daily_all_final,
117
  x="date",
118
  y="cumulative",
119
  color="user",
120
  title="Накопительное количество SMS",
121
- labels={
122
- "date": "Дата",
123
- "cumulative": "Накопительное количество SMS",
124
- "user": "Редактор"
125
- },
126
- category_orders={"user": sorted_users}, # порядок в легенде
127
  color_discrete_map=color_map
128
  )
129
 
130
- # Прогноз с помощью Prophet (делаем для "Всего" до 28.02.2025)
 
 
 
 
 
131
  forecast_fig = None
132
- if len(total_by_date) > 1:
 
 
133
  df_prophet = total_by_date[["date", "cumulative"]].copy()
134
  df_prophet.columns = ["ds", "y"]
135
  df_prophet["ds"] = pd.to_datetime(df_prophet["ds"])
136
 
137
- model = Prophet()
 
 
138
  model.fit(df_prophet)
139
 
140
- # Рассчитаем, сколько дней до 28.02.2025
141
- future = model.make_future_dataframe(periods=0) # на случай, если уже после
142
- last_date = df_prophet["ds"].max()
143
  end_date = pd.to_datetime("2025-02-28")
 
144
  additional_days = (end_date - last_date).days
 
 
 
145
  if additional_days > 0:
146
- future = model.make_future_dataframe(periods=additional_days)
147
 
148
  forecast = model.predict(future)
149
 
150
- # Сопоставим исторические данные и прогноз
151
  df_plot = pd.merge(
152
  forecast[["ds", "yhat"]],
153
  df_prophet[["ds", "y"]],
154
  on="ds",
155
  how="left"
156
  )
157
- df_history = df_plot.dropna(subset=["y"]) # исторические точки
158
- df_future = df_plot[df_plot["y"].isna()] # будущее
159
 
160
- # Отдельный график
 
 
 
161
  forecast_fig = px.line(
162
  df_history,
163
  x="ds",
164
  y="y",
165
  title="Прогноз до конца февраля 2025 (всего)",
166
- labels={"ds": "Дата", "y": "Накопительное количество SMS"}
167
  )
 
 
168
  forecast_fig.add_scatter(
169
  x=df_future["ds"],
170
  y=df_future["yhat"],
171
  mode="lines",
172
- name="Прогноз",
173
  line=dict(dash="dash", color="red")
174
  )
 
 
 
 
 
 
 
 
 
 
 
175
  forecast_fig.update_layout(showlegend=True)
176
-
177
  return (bars_html, fig, forecast_fig)
178
 
179
  with gr.Blocks() as demo:
180
  gr.Markdown("<h2>Количество сохраненных SMS (Даша, Лера, Света)</h2>")
181
  btn = gr.Button("Обновить данные и показать результат")
182
  html_output = gr.HTML(label="Прогресс-бары: количество SMS и %")
183
- plot_output = gr.Plot(label="Накопительный график по датам (Даша, Лера, Света, Всего)")
184
  forecast_output = gr.Plot(label="Прогноз до конца февраля 2025 (всего)")
185
 
186
  btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output])
 
1
  import gradio as gr
2
  import pandas as pd
3
  import plotly.express as px
 
 
4
  from prophet import Prophet
5
 
6
  # Ссылки на CSV-файлы
 
38
 
39
  return unique_count, df_daily
40
 
41
+ def make_average_forecast(total_by_date, end_date_str="2025-02-28"):
42
+ """
43
+ Строит «прогноз по среднему» до указанной даты (end_date_str),
44
+ считая средний дневной прирост по будням (Mon-Fri).
45
+ В выходные прирост считаем 0.
46
+
47
+ Возвращает DataFrame с колонками ["ds", "yhat"],
48
+ начиная с (last_date+1) по end_date_str (включительно).
49
+ """
50
+ if total_by_date.empty:
51
+ return pd.DataFrame(columns=["ds", "yhat"])
52
+
53
+ df_tmp = total_by_date.copy()
54
+ df_tmp["date"] = pd.to_datetime(df_tmp["date"])
55
+
56
+ # Средний дневной прирост (столбец "count") только по будням (Monday=0 ... Friday=4)
57
+ df_weekdays = df_tmp[df_tmp["date"].dt.weekday < 5]
58
+ avg_inc = df_weekdays["count"].mean() if len(df_weekdays) else 0
59
+
60
+ last_date = df_tmp["date"].max()
61
+ last_cumulative = df_tmp["cumulative"].iloc[-1]
62
+
63
+ end_date = pd.to_datetime(end_date_str)
64
+
65
+ # Движемся по календарю день за днём
66
+ current_date = last_date
67
+ forecast_data = []
68
+ running_total = last_cumulative
69
+
70
+ while current_date < end_date:
71
+ current_date += pd.Timedelta(days=1)
72
+ if current_date > end_date:
73
+ break
74
+ # Если будний день
75
+ if current_date.weekday() < 5:
76
+ running_total += avg_inc
77
+ forecast_data.append({"ds": current_date, "yhat": running_total})
78
+
79
+ return pd.DataFrame(forecast_data)
80
+
81
  def process_data():
82
  # Считываем и обрабатываем все репозитории
83
  dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша")
 
95
 
96
  # Генерируем HTML-прогресс-бары
97
  def get_progress_bar(label, abs_val, pct):
98
+ capacity = 234 if label in ["Даша", "Лера", "Света"] else 702
 
 
 
 
99
  return f"""
100
  <div style='margin-bottom: 1em;'>
101
  <div><strong>{label}</strong></div>
 
114
  get_progress_bar("Всего", total_count, total_percent)
115
  )
116
 
117
+ # Готовим DataFrame с дневными данными
118
  daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
 
119
  daily_all = daily_all.dropna(subset=["date"])
120
 
121
+ # Считаем кумулятивно для каждого пользователя
122
  daily_all = daily_all.sort_values(by=["user", "date"])
123
  daily_all["cumulative"] = daily_all.groupby("user")["count"].cumsum()
124
 
125
+ # "Всего" по датам
126
  total_by_date = daily_all.groupby("date")["count"].sum().reset_index(name="count")
127
  total_by_date = total_by_date.sort_values(by="date")
128
  total_by_date["cumulative"] = total_by_date["count"].cumsum()
129
  total_by_date["user"] = "Всего"
130
 
131
+ # Объединим (для построения общего графика)
132
  daily_all_final = pd.concat([daily_all, total_by_date], ignore_index=True)
133
 
134
+ # Сортировка легенды
 
135
  last_values = daily_all_final.groupby("user")["cumulative"].last().sort_values(ascending=False)
136
+ sorted_users = last_values.index.tolist()
137
 
138
+ # Явно задаём цвета
139
  color_map = {
140
+ "Даша": "#1f77b4",
141
+ "Лера": "#2ca02c",
142
+ "Света": "#d62728",
143
+ "Всего": "#9467bd"
144
  }
145
 
146
+ # Основной график (накопитель��ый)
147
  fig = px.line(
148
  daily_all_final,
149
  x="date",
150
  y="cumulative",
151
  color="user",
152
  title="Накопительное количество SMS",
153
+ labels={"date": "Дата", "cumulative": "Накопленное количество SMS", "user": "Редактор"},
154
+ category_orders={"user": sorted_users},
 
 
 
 
155
  color_discrete_map=color_map
156
  )
157
 
158
+ # --------------------------------------------------------------------------------
159
+ # Прогноз (Prophet) с учётом, что по выходным не должно быть прироста
160
+ # делается через freq='B' в make_future_dataframe (пропускает выходные).
161
+ # Второй — «Прогноз по среднему», также без прироста в выходные.
162
+ # --------------------------------------------------------------------------------
163
+
164
  forecast_fig = None
165
+
166
+ if not total_by_date.empty:
167
+ # Подготовка данных для Prophet (только "Всего")
168
  df_prophet = total_by_date[["date", "cumulative"]].copy()
169
  df_prophet.columns = ["ds", "y"]
170
  df_prophet["ds"] = pd.to_datetime(df_prophet["ds"])
171
 
172
+ model = Prophet(weekly_seasonality=True, daily_seasonality=False)
173
+ # Часто Prophet сам распознаёт weekly_seasonality, но укажем явно
174
+
175
  model.fit(df_prophet)
176
 
 
 
 
177
  end_date = pd.to_datetime("2025-02-28")
178
+ last_date = df_prophet["ds"].max()
179
  additional_days = (end_date - last_date).days
180
+
181
+ # Создаём будущее ТОЛЬКО по рабочим дням (freq='B')
182
+ future = model.make_future_dataframe(periods=0, freq='B')
183
  if additional_days > 0:
184
+ future = model.make_future_dataframe(periods=additional_days, freq='B')
185
 
186
  forecast = model.predict(future)
187
 
188
+ # История + прогноз
189
  df_plot = pd.merge(
190
  forecast[["ds", "yhat"]],
191
  df_prophet[["ds", "y"]],
192
  on="ds",
193
  how="left"
194
  )
195
+ df_history = df_plot.dropna(subset=["y"]) # фактические точки
196
+ df_future = df_plot[df_plot["y"].isna()] # будущее (Prophet)
197
 
198
+ # Второй прогноз "по среднему"
199
+ df_avg_forecast = make_average_forecast(total_by_date, "2025-02-28")
200
+
201
+ # Один общий график
202
  forecast_fig = px.line(
203
  df_history,
204
  x="ds",
205
  y="y",
206
  title="Прогноз до конца февраля 2025 (всего)",
207
+ labels={"ds": "Дата", "y": "Накопленное число SMS"}
208
  )
209
+
210
+ # Prophet (пунктир красный)
211
  forecast_fig.add_scatter(
212
  x=df_future["ds"],
213
  y=df_future["yhat"],
214
  mode="lines",
215
+ name="Прогноз (Prophet)",
216
  line=dict(dash="dash", color="red")
217
  )
218
+
219
+ # Прогноз «по среднему» (пунктир зелёный)
220
+ if not df_avg_forecast.empty:
221
+ forecast_fig.add_scatter(
222
+ x=df_avg_forecast["ds"],
223
+ y=df_avg_forecast["yhat"],
224
+ mode="lines",
225
+ name="Прогноз (по среднему)",
226
+ line=dict(dash="dash", color="green")
227
+ )
228
+
229
  forecast_fig.update_layout(showlegend=True)
230
+
231
  return (bars_html, fig, forecast_fig)
232
 
233
  with gr.Blocks() as demo:
234
  gr.Markdown("<h2>Количество сохраненных SMS (Даша, Лера, Света)</h2>")
235
  btn = gr.Button("Обновить данные и показать результат")
236
  html_output = gr.HTML(label="Прогресс-бары: количество SMS и %")
237
+ plot_output = gr.Plot(label="Накопительный график (Даша, Лера, Света, Всего)")
238
  forecast_output = gr.Plot(label="Прогноз до конца февраля 2025 (всего)")
239
 
240
  btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output])