Update app.py
Browse files
app.py
CHANGED
@@ -2,61 +2,60 @@ import gradio as gr
|
|
2 |
import pandas as pd
|
3 |
import plotly.express as px
|
4 |
|
|
|
|
|
|
|
5 |
# Ссылки на CSV-файлы
|
6 |
URL_DASHA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Dasha_2025/main/messages.csv"
|
7 |
URL_LERA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Lera_2025/main/messages.csv"
|
8 |
URL_SVETA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Sveta_2025/main/messages.csv"
|
9 |
|
10 |
-
# Функция для чтения данных, подсчёта уникальных SMS и подготовки данных для графиков
|
11 |
def read_and_process_data(url, user_name):
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
df = pd.read_csv(url, na_values=["Не выбрано"])
|
13 |
-
|
14 |
-
# Оставляем только нужные столбцы (если какие-то отсутствуют — пропускаем аккуратно)
|
15 |
cols = ["gender", "generation", "industry", "opf", "timestamp"]
|
16 |
-
df = df[[
|
17 |
|
18 |
-
# Убираем дубликаты по
|
19 |
df_unique = df.drop_duplicates(subset=["gender", "generation", "industry", "opf"])
|
20 |
|
21 |
-
#
|
22 |
unique_count = len(df_unique)
|
23 |
|
24 |
-
#
|
25 |
if "timestamp" in df_unique.columns:
|
26 |
-
# Переводим timestamp в datetime
|
27 |
df_unique["timestamp"] = pd.to_numeric(df_unique["timestamp"], errors='coerce')
|
28 |
df_unique["date"] = pd.to_datetime(df_unique["timestamp"], unit="s", origin="unix", errors='coerce').dt.date
|
29 |
else:
|
30 |
df_unique["date"] = pd.NaT
|
31 |
-
|
32 |
-
#
|
33 |
-
# (снова нужно учесть комбинацию gender, generation, industry, opf)
|
34 |
-
# Но т.к. мы уже отфильтровали уникальные, достаточно просто groupby date
|
35 |
df_daily = df_unique.groupby("date").size().reset_index(name="count")
|
36 |
-
df_daily["user"] = user_name
|
37 |
|
38 |
return unique_count, df_daily
|
39 |
|
40 |
def process_data():
|
41 |
-
# Считываем и обрабатываем все
|
42 |
dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша")
|
43 |
lera_count, lera_daily = read_and_process_data(URL_LERA, "Лера")
|
44 |
sveta_count, sveta_daily = read_and_process_data(URL_SVETA, "Света")
|
45 |
|
46 |
-
#
|
47 |
total_count = dasha_count + lera_count + sveta_count
|
48 |
|
49 |
-
#
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
sveta_percent = round((sveta_count / 117) * 100) if 117 else 0
|
54 |
-
|
55 |
-
# Суммарный % от 702
|
56 |
total_percent = round((total_count / 702) * 100) if 702 else 0
|
57 |
|
58 |
-
#
|
59 |
-
# Простейшая полоска: ширина = процент, внутри пишем "X SMS (Y%)"
|
60 |
def get_progress_bar(label, abs_val, pct):
|
61 |
return f"""
|
62 |
<div style='margin-bottom: 1em;'>
|
@@ -76,33 +75,116 @@ def process_data():
|
|
76 |
get_progress_bar("Всего", total_count, total_percent)
|
77 |
)
|
78 |
|
79 |
-
#
|
80 |
daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
|
81 |
-
# Уберём
|
82 |
daily_all = daily_all.dropna(subset=["date"])
|
83 |
|
84 |
-
#
|
85 |
-
#
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
x="date",
|
89 |
-
y="
|
90 |
color="user",
|
91 |
-
title="
|
92 |
-
labels={
|
93 |
-
|
|
|
|
|
|
|
94 |
)
|
95 |
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
-
# Создаём интерфейс Gradio
|
99 |
with gr.Blocks() as demo:
|
100 |
-
gr.Markdown("<h2>Подсчёт уникальных SMS
|
101 |
btn = gr.Button("Обновить данные и показать результат")
|
102 |
-
html_output = gr.HTML(label="
|
103 |
-
plot_output = gr.Plot(label="
|
|
|
104 |
|
105 |
-
btn.click(fn=process_data, outputs=[html_output, plot_output])
|
106 |
|
107 |
if __name__ == "__main__":
|
108 |
demo.launch()
|
|
|
2 |
import pandas as pd
|
3 |
import plotly.express as px
|
4 |
|
5 |
+
# Для Prophet
|
6 |
+
from prophet import Prophet
|
7 |
+
|
8 |
# Ссылки на CSV-файлы
|
9 |
URL_DASHA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Dasha_2025/main/messages.csv"
|
10 |
URL_LERA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Lera_2025/main/messages.csv"
|
11 |
URL_SVETA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Sveta_2025/main/messages.csv"
|
12 |
|
|
|
13 |
def read_and_process_data(url, user_name):
|
14 |
+
"""
|
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"]
|
22 |
+
df = df[[c for c in cols if c in df.columns]].copy()
|
23 |
|
24 |
+
# Убираем дубликаты по 4 ключевым столбцам
|
25 |
df_unique = df.drop_duplicates(subset=["gender", "generation", "industry", "opf"])
|
26 |
|
27 |
+
# Количество уникальных SMS
|
28 |
unique_count = len(df_unique)
|
29 |
|
30 |
+
# Переводим timestamp -> date (UTC)
|
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
|
34 |
else:
|
35 |
df_unique["date"] = pd.NaT
|
36 |
+
|
37 |
+
# Сгруппируем по дате, чтобы получить количество за каждый день
|
|
|
|
|
38 |
df_daily = df_unique.groupby("date").size().reset_index(name="count")
|
39 |
+
df_daily["user"] = 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, "Даша")
|
46 |
lera_count, lera_daily = read_and_process_data(URL_LERA, "Лера")
|
47 |
sveta_count, sveta_daily = read_and_process_data(URL_SVETA, "Света")
|
48 |
|
49 |
+
# Суммарное количество
|
50 |
total_count = dasha_count + lera_count + sveta_count
|
51 |
|
52 |
+
# Проценты (округляем до целого)
|
53 |
+
dasha_percent = round((dasha_count / 234) * 100) if 234 else 0
|
54 |
+
lera_percent = round((lera_count / 234) * 100) if 234 else 0
|
55 |
+
sveta_percent = round((sveta_count / 234) * 100) if 234 else 0
|
|
|
|
|
|
|
56 |
total_percent = round((total_count / 702) * 100) if 702 else 0
|
57 |
|
58 |
+
# Генерируем HTML-прогресс-бары
|
|
|
59 |
def get_progress_bar(label, abs_val, pct):
|
60 |
return f"""
|
61 |
<div style='margin-bottom: 1em;'>
|
|
|
75 |
get_progress_bar("Всего", total_count, total_percent)
|
76 |
)
|
77 |
|
78 |
+
# Собираем единый DataFrame по дням
|
79 |
daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True)
|
80 |
+
# Уберём NaT
|
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()
|
94 |
+
total_by_date["user"] = "Всего"
|
95 |
+
|
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="Кумулятивное количество уникальных SMS по датам (с линией 'Всего')",
|
107 |
+
labels={
|
108 |
+
"date": "Дата",
|
109 |
+
"cumulative": "Накопительное количество SMS",
|
110 |
+
"user": "Пользователь"
|
111 |
+
}
|
112 |
)
|
113 |
|
114 |
+
# Прогноз с помощью Prophet (будем прогнозировать "Всего" до 28.02.2025)
|
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 |
+
# Создаём DataFrame для будущего (до 28.02.2025)
|
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="Прогноз общего кумулятивного количества SMS до 28.02.2025",
|
161 |
+
labels={
|
162 |
+
"ds": "Дата",
|
163 |
+
"value": "Количество SMS"
|
164 |
+
}
|
165 |
+
)
|
166 |
+
forecast_fig.add_scatter(
|
167 |
+
x=df_future["ds"],
|
168 |
+
y=df_future["yhat"],
|
169 |
+
mode="lines",
|
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 |
|
|
|
180 |
with gr.Blocks() as demo:
|
181 |
+
gr.Markdown("<h2>Подсчёт и прогноз уникальных SMS (Даша, Лера, Света)</h2>")
|
182 |
btn = gr.Button("Обновить данные и показать результат")
|
183 |
+
html_output = gr.HTML(label="Прогресс-бары: количество SMS и %")
|
184 |
+
plot_output = gr.Plot(label="Кумулятивный график по датам (Даша, Лера, Света, Всего)")
|
185 |
+
forecast_output = gr.Plot(label="Прогноз до 28.02.2025 (Всего)")
|
186 |
|
187 |
+
btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output])
|
188 |
|
189 |
if __name__ == "__main__":
|
190 |
demo.launch()
|