Update app.py
Browse files
app.py
CHANGED
@@ -15,7 +15,7 @@ def read_and_process_data(url, user_name):
|
|
15 |
Возвращает:
|
16 |
1) unique_count: количество уникальных SMS (по gender, generation, industry, opf)
|
17 |
2) df_daily: дата, пользователь, дневное кол-во уникальных SMS (НЕ накопленное),
|
18 |
-
|
19 |
"""
|
20 |
df = pd.read_csv(url, na_values=["Не выбрано"])
|
21 |
cols = ["gender", "generation", "industry", "opf", "timestamp"]
|
@@ -57,12 +57,17 @@ def process_data():
|
|
57 |
|
58 |
# Генерируем HTML-прогресс-бары
|
59 |
def get_progress_bar(label, abs_val, pct):
|
|
|
|
|
|
|
|
|
|
|
60 |
return f"""
|
61 |
<div style='margin-bottom: 1em;'>
|
62 |
<div><strong>{label}</strong></div>
|
63 |
<div style='width: 100%; background-color: #ddd; text-align: left;'>
|
64 |
<div style='width: {pct}%; background-color: #4CAF50; padding: 5px 0;'>
|
65 |
-
{abs_val} SMS ({pct}%)
|
66 |
</div>
|
67 |
</div>
|
68 |
</div>
|
@@ -81,13 +86,10 @@ def process_data():
|
|
81 |
daily_all = daily_all.dropna(subset=["date"])
|
82 |
|
83 |
# Считаем кумулятивное количество SMS отдельно для каждого пользователя
|
84 |
-
# Для этого сначала отсортируем по user + date
|
85 |
daily_all = daily_all.sort_values(by=["user", "date"])
|
86 |
-
# Группируем по user, считаем cumsum
|
87 |
daily_all["cumulative"] = daily_all.groupby("user")["count"].cumsum()
|
88 |
|
89 |
-
# Чтобы отразить "Всего", добавим user="Всего"
|
90 |
-
# суммируя по каждой дате. Потом возьмём кумулятивную сумму этой суммы
|
91 |
total_by_date = daily_all.groupby("date")["count"].sum().reset_index(name="count")
|
92 |
total_by_date = total_by_date.sort_values(by="date")
|
93 |
total_by_date["cumulative"] = total_by_date["count"].cumsum()
|
@@ -96,72 +98,72 @@ def process_data():
|
|
96 |
# Объединим с daily_all
|
97 |
daily_all_final = pd.concat([daily_all, total_by_date], ignore_index=True)
|
98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
# Строим накопительный (кумулятивный) график (линии)
|
100 |
-
# Используем Plotly, делая line chart
|
101 |
fig = px.line(
|
102 |
daily_all_final,
|
103 |
x="date",
|
104 |
y="cumulative",
|
105 |
color="user",
|
106 |
-
title="
|
107 |
labels={
|
108 |
"date": "Дата",
|
109 |
"cumulative": "Накопительное количество SMS",
|
110 |
"user": "Пользователь"
|
111 |
-
}
|
|
|
|
|
112 |
)
|
113 |
|
114 |
-
# Прогноз с помощью Prophet (
|
115 |
-
# Берём total_by_date (уже есть 'date', 'cumulative')
|
116 |
forecast_fig = None
|
117 |
if len(total_by_date) > 1:
|
118 |
-
# Prophet требует колонки ds, y
|
119 |
df_prophet = total_by_date[["date", "cumulative"]].copy()
|
120 |
df_prophet.columns = ["ds", "y"]
|
121 |
df_prophet["ds"] = pd.to_datetime(df_prophet["ds"])
|
122 |
|
123 |
-
# Создаём и обучаем модель
|
124 |
model = Prophet()
|
125 |
model.fit(df_prophet)
|
126 |
|
127 |
-
#
|
128 |
-
future = model.make_future_dataframe(periods=0) #
|
129 |
last_date = df_prophet["ds"].max()
|
130 |
-
# Посчитаем, сколько дней до 28.02.2025
|
131 |
end_date = pd.to_datetime("2025-02-28")
|
132 |
additional_days = (end_date - last_date).days
|
133 |
if additional_days > 0:
|
134 |
future = model.make_future_dataframe(periods=additional_days)
|
135 |
|
136 |
-
# Прогноз
|
137 |
forecast = model.predict(future)
|
138 |
|
139 |
-
#
|
140 |
-
# Можно с plotly, чтобы добавить пунктир.
|
141 |
-
# Для наглядности создадим merge таблицу:
|
142 |
-
# ds, y (история), yhat (прогноз) и т.д.
|
143 |
df_plot = pd.merge(
|
144 |
forecast[["ds", "yhat"]],
|
145 |
df_prophet[["ds", "y"]],
|
146 |
on="ds",
|
147 |
how="left"
|
148 |
)
|
|
|
|
|
149 |
|
150 |
-
#
|
151 |
-
# - где y есть (история)
|
152 |
-
# - где y нет (будущее)
|
153 |
-
df_history = df_plot.dropna(subset=["y"])
|
154 |
-
df_future = df_plot[df_plot["y"].isna()]
|
155 |
-
|
156 |
forecast_fig = px.line(
|
157 |
df_history,
|
158 |
x="ds",
|
159 |
y="y",
|
160 |
title="Прогноз до конца февраля 2025 (всего)",
|
161 |
-
labels={
|
162 |
-
"ds": "Дата",
|
163 |
-
"value": "Количество SMS"
|
164 |
-
}
|
165 |
)
|
166 |
forecast_fig.add_scatter(
|
167 |
x=df_future["ds"],
|
@@ -170,10 +172,7 @@ def process_data():
|
|
170 |
name="Прогноз",
|
171 |
line=dict(dash="dash", color="red")
|
172 |
)
|
173 |
-
forecast_fig.update_layout(
|
174 |
-
showlegend=True,
|
175 |
-
legend=dict(x=0, y=1)
|
176 |
-
)
|
177 |
|
178 |
return (bars_html, fig, forecast_fig)
|
179 |
|
|
|
15 |
Возвращает:
|
16 |
1) unique_count: количество уникальных SMS (по gender, generation, industry, opf)
|
17 |
2) df_daily: дата, пользователь, дневное кол-во уникальных SMS (НЕ накопленное),
|
18 |
+
уже после удаления дубликатов по 4 столбцам.
|
19 |
"""
|
20 |
df = pd.read_csv(url, na_values=["Не выбрано"])
|
21 |
cols = ["gender", "generation", "industry", "opf", "timestamp"]
|
|
|
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>
|
68 |
<div style='width: 100%; background-color: #ddd; text-align: left;'>
|
69 |
<div style='width: {pct}%; background-color: #4CAF50; padding: 5px 0;'>
|
70 |
+
{abs_val} SMS ({pct}% из {capacity})
|
71 |
</div>
|
72 |
</div>
|
73 |
</div>
|
|
|
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()
|
|
|
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"],
|
|
|
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 |
|