Update app.py
Browse files
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-файлы
|
@@ -27,7 +25,7 @@ def read_and_process_data(url, user_name):
|
|
27 |
# Количество уникальных SMS
|
28 |
unique_count = len(df_unique)
|
29 |
|
30 |
-
# Переводим timestamp -> date
|
31 |
if "timestamp" in df_unique.columns:
|
32 |
df_unique["timestamp"] = pd.to_numeric(df_unique["timestamp"], errors='coerce')
|
33 |
df_unique["date"] = pd.to_datetime(df_unique["timestamp"], unit="s", origin="unix", errors='coerce').dt.date
|
@@ -40,6 +38,54 @@ 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,7 +103,6 @@ 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:
|
@@ -82,14 +127,13 @@ def process_data():
|
|
82 |
|
83 |
# Собираем единый DataFrame по дням
|
84 |
daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
|
85 |
-
#
|
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 |
-
#
|
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()
|
@@ -98,10 +142,9 @@ def process_data():
|
|
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 = {
|
@@ -111,69 +154,88 @@ def process_data():
|
|
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 |
-
|
123 |
-
"cumulative": "Накопительное количество SMS",
|
124 |
-
"user": "Редактор"
|
125 |
-
},
|
126 |
-
category_orders={"user": sorted_users}, # порядок в легенде
|
127 |
color_discrete_map=color_map
|
128 |
)
|
129 |
|
130 |
-
#
|
|
|
|
|
|
|
131 |
forecast_fig = None
|
132 |
-
|
|
|
|
|
|
|
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="
|
166 |
-
labels={"ds": "Дата", "y": "
|
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:
|
@@ -181,7 +243,7 @@ with gr.Blocks() as demo:
|
|
181 |
btn = gr.Button("Обновить данные и показать результат")
|
182 |
html_output = gr.HTML(label="Прогресс-бары: количество SMS и %")
|
183 |
plot_output = gr.Plot(label="Накопительный график по датам (Даша, Лера, Света, Всего)")
|
184 |
-
forecast_output = gr.Plot(label="
|
185 |
|
186 |
btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output])
|
187 |
|
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
import plotly.express as px
|
|
|
|
|
4 |
from prophet import Prophet
|
5 |
|
6 |
# Ссылки на CSV-файлы
|
|
|
25 |
# Количество уникальных SMS
|
26 |
unique_count = len(df_unique)
|
27 |
|
28 |
+
# Переводим timestamp -> date
|
29 |
if "timestamp" in df_unique.columns:
|
30 |
df_unique["timestamp"] = pd.to_numeric(df_unique["timestamp"], errors='coerce')
|
31 |
df_unique["date"] = pd.to_datetime(df_unique["timestamp"], unit="s", origin="unix", errors='coerce').dt.date
|
|
|
38 |
|
39 |
return unique_count, df_daily
|
40 |
|
41 |
+
def make_naive_forecast(total_by_date, end_date_str="2025-02-28"):
|
42 |
+
"""
|
43 |
+
Строит «наивный» прогноз до указанной даты (end_date_str),
|
44 |
+
считая среднее дневное приращение только по будням (Mon-Fri).
|
45 |
+
В выходные (Sat/Sun) прирост = 0.
|
46 |
+
|
47 |
+
Возвращает DataFrame с колонками ["ds", "yhat"], начиная с (last_date+1) по end_date_str.
|
48 |
+
"""
|
49 |
+
if total_by_date.empty:
|
50 |
+
return pd.DataFrame(columns=["ds", "yhat"])
|
51 |
+
|
52 |
+
# Превращаем date -> datetime, чтобы работать с днем недели
|
53 |
+
df_tmp = total_by_date.copy()
|
54 |
+
df_tmp["date"] = pd.to_datetime(df_tmp["date"])
|
55 |
+
|
56 |
+
# Средний дневной прирост по будням
|
57 |
+
# Прирост за день хранится в столбце "count"
|
58 |
+
# Фильтруем по будням (понедельник=0 ... пятница=4)
|
59 |
+
df_weekdays = df_tmp[df_tmp["date"].dt.weekday < 5]
|
60 |
+
if len(df_weekdays) == 0:
|
61 |
+
avg_inc = 0
|
62 |
+
else:
|
63 |
+
avg_inc = df_weekdays["count"].mean()
|
64 |
+
|
65 |
+
last_date = df_tmp["date"].max()
|
66 |
+
last_cumulative = df_tmp["cumulative"].iloc[-1] # последнее накопленное значение
|
67 |
+
|
68 |
+
end_date = pd.to_datetime(end_date_str)
|
69 |
+
|
70 |
+
# Составляем прогноз, двигаясь по календарю день за днём
|
71 |
+
current_date = last_date
|
72 |
+
naive_data = []
|
73 |
+
running_total = last_cumulative
|
74 |
+
|
75 |
+
while current_date < end_date:
|
76 |
+
current_date = current_date + pd.Timedelta(days=1)
|
77 |
+
if current_date > end_date:
|
78 |
+
break
|
79 |
+
# Проверяем, будний ли это день (0..4)
|
80 |
+
if current_date.weekday() < 5:
|
81 |
+
running_total += avg_inc # добавляем среднее приращение
|
82 |
+
# Записываем точку прогноза
|
83 |
+
naive_data.append({"ds": current_date, "yhat": running_total})
|
84 |
+
|
85 |
+
# Превращаем в DataFrame
|
86 |
+
df_naive = pd.DataFrame(naive_data)
|
87 |
+
return df_naive
|
88 |
+
|
89 |
def process_data():
|
90 |
# Считываем и обрабатываем все репозитории
|
91 |
dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша")
|
|
|
103 |
|
104 |
# Генерируем HTML-прогресс-бары
|
105 |
def get_progress_bar(label, abs_val, pct):
|
|
|
106 |
if label in ["Даша", "Лера", "Света"]:
|
107 |
capacity = 234
|
108 |
else:
|
|
|
127 |
|
128 |
# Собираем единый DataFrame по дням
|
129 |
daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
|
130 |
+
daily_all = daily_all.dropna(subset=["date"]) # убираем NaT
|
|
|
131 |
|
132 |
# Считаем кумулятивное количество SMS отдельно для каждого пользователя
|
133 |
daily_all = daily_all.sort_values(by=["user", "date"])
|
134 |
daily_all["cumulative"] = daily_all.groupby("user")["count"].cumsum()
|
135 |
|
136 |
+
# Складываем в "Всего" по датам
|
137 |
total_by_date = daily_all.groupby("date")["count"].sum().reset_index(name="count")
|
138 |
total_by_date = total_by_date.sort_values(by="date")
|
139 |
total_by_date["cumulative"] = total_by_date["count"].cumsum()
|
|
|
142 |
# Объединим с daily_all
|
143 |
daily_all_final = pd.concat([daily_all, total_by_date], ignore_index=True)
|
144 |
|
145 |
+
# Определим порядок легенды по последнему кумулятивному значению (убывание)
|
|
|
146 |
last_values = daily_all_final.groupby("user")["cumulative"].last().sort_values(ascending=False)
|
147 |
+
sorted_users = last_values.index.tolist()
|
148 |
|
149 |
# Явно зададим цвета
|
150 |
color_map = {
|
|
|
154 |
"Всего": "#9467bd" # фиолетовый
|
155 |
}
|
156 |
|
157 |
+
# Строим накопительный (кумулятивный) график (линии) по факту
|
158 |
fig = px.line(
|
159 |
daily_all_final,
|
160 |
x="date",
|
161 |
y="cumulative",
|
162 |
color="user",
|
163 |
title="Накопительное количество SMS",
|
164 |
+
labels={"date": "Дата", "cumulative": "Накопленное количество SMS", "user": "Редактор"},
|
165 |
+
category_orders={"user": sorted_users},
|
|
|
|
|
|
|
|
|
166 |
color_discrete_map=color_map
|
167 |
)
|
168 |
|
169 |
+
# -----------------------
|
170 |
+
# Прогнозы (Prophet + Наивный)
|
171 |
+
# -----------------------
|
172 |
+
|
173 |
forecast_fig = None
|
174 |
+
|
175 |
+
# Сформируем DF для Prophet из "Всего"
|
176 |
+
# (Если данных мало, проверим, чтобы не было пусто)
|
177 |
+
if not total_by_date.empty:
|
178 |
df_prophet = total_by_date[["date", "cumulative"]].copy()
|
179 |
df_prophet.columns = ["ds", "y"]
|
180 |
df_prophet["ds"] = pd.to_datetime(df_prophet["ds"])
|
181 |
|
182 |
+
# 1) Prophet
|
183 |
model = Prophet()
|
184 |
model.fit(df_prophet)
|
185 |
|
|
|
|
|
|
|
186 |
end_date = pd.to_datetime("2025-02-28")
|
187 |
+
last_date = df_prophet["ds"].max()
|
188 |
additional_days = (end_date - last_date).days
|
189 |
+
|
190 |
+
future = model.make_future_dataframe(periods=0)
|
191 |
if additional_days > 0:
|
192 |
future = model.make_future_dataframe(periods=additional_days)
|
193 |
|
194 |
forecast = model.predict(future)
|
195 |
|
196 |
+
# Сопоставим исторические точки (y) и прогноз (yhat)
|
197 |
df_plot = pd.merge(
|
198 |
forecast[["ds", "yhat"]],
|
199 |
df_prophet[["ds", "y"]],
|
200 |
on="ds",
|
201 |
how="left"
|
202 |
)
|
203 |
+
df_history = df_plot.dropna(subset=["y"]) # исторические данные
|
204 |
+
df_future = df_plot[df_plot["y"].isna()] # будущее (прогноз prophet)
|
205 |
+
|
206 |
+
# 2) Наивный прогноз (будни)
|
207 |
+
# Используем total_by_date (уже есть date, count, cumulative)
|
208 |
+
# Функция вернёт df_naive: ds, yhat
|
209 |
+
df_naive = make_naive_forecast(total_by_date, "2025-02-28")
|
210 |
|
211 |
+
# Формируем общий график для сравнения
|
212 |
forecast_fig = px.line(
|
213 |
df_history,
|
214 |
x="ds",
|
215 |
y="y",
|
216 |
+
title="Два прогноза до конца февраля 2025 (всего)",
|
217 |
+
labels={"ds": "Дата", "y": "Накопленное число SMS"}
|
218 |
)
|
219 |
forecast_fig.add_scatter(
|
220 |
x=df_future["ds"],
|
221 |
y=df_future["yhat"],
|
222 |
mode="lines",
|
223 |
+
name="Прогноз (Prophet)",
|
224 |
line=dict(dash="dash", color="red")
|
225 |
)
|
226 |
+
|
227 |
+
# Добавим наивный прогноз
|
228 |
+
if not df_naive.empty:
|
229 |
+
forecast_fig.add_scatter(
|
230 |
+
x=df_naive["ds"],
|
231 |
+
y=df_naive["yhat"],
|
232 |
+
mode="lines",
|
233 |
+
name="Прогноз (Наивн., будни)",
|
234 |
+
line=dict(dash="dash", color="green")
|
235 |
+
)
|
236 |
+
|
237 |
forecast_fig.update_layout(showlegend=True)
|
238 |
+
|
239 |
return (bars_html, fig, forecast_fig)
|
240 |
|
241 |
with gr.Blocks() as demo:
|
|
|
243 |
btn = gr.Button("Обновить данные и показать результат")
|
244 |
html_output = gr.HTML(label="Прогресс-бары: количество SMS и %")
|
245 |
plot_output = gr.Plot(label="Накопительный график по датам (Даша, Лера, Света, Всего)")
|
246 |
+
forecast_output = gr.Plot(label="Сравнение прогнозов до конца февраля 2025 (всего)")
|
247 |
|
248 |
btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output])
|
249 |
|