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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -96
app.py CHANGED
@@ -1,6 +1,8 @@
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,46 +40,6 @@ def read_and_process_data(url, user_name):
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,7 +57,11 @@ def process_data():
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,127 +80,107 @@ def process_data():
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])
 
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
 
41
  return unique_count, df_daily
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def process_data():
44
  # Считываем и обрабатываем все репозитории
45
  dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша")
 
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
  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])