DavMelchi commited on
Commit
06e3227
Β·
1 Parent(s): 4664a1e

Add anaomalies detector

Browse files
Files changed (3) hide show
  1. app.py +4 -0
  2. apps/kpi_analysis/anomalie.py +168 -0
  3. requirements.txt +0 -0
app.py CHANGED
@@ -162,6 +162,10 @@ if check_password():
162
  "apps/kpi_analysis/lte_drop_trafic.py",
163
  title=" πŸ“Š LTE Drop Traffic Analysis",
164
  ),
 
 
 
 
165
  ],
166
  "Paging Analysis": [
167
  st.Page(
 
162
  "apps/kpi_analysis/lte_drop_trafic.py",
163
  title=" πŸ“Š LTE Drop Traffic Analysis",
164
  ),
165
+ st.Page(
166
+ "apps/kpi_analysis/anomalie.py",
167
+ title=" πŸ“Š KPIs Anomaly Detection",
168
+ ),
169
  ],
170
  "Paging Analysis": [
171
  st.Page(
apps/kpi_analysis/anomalie.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import plotly.graph_objs as go
6
+ import ruptures as rpt
7
+ import streamlit as st
8
+
9
+ st.set_page_config(layout="wide")
10
+ st.title("KPIsAnomaly Detection")
11
+
12
+ uploaded_file = st.file_uploader("Upload KPI CSV file", type=["csv"])
13
+ penalty = st.number_input("Penalty", min_value=1.0, max_value=100.0, value=2.5)
14
+
15
+
16
+ @st.cache_data(show_spinner="Analyzing anomalies...")
17
+ def detect_anomalies(df: pd.DataFrame, penalty: int):
18
+ # Cleaning
19
+ df = df.rename(
20
+ columns={
21
+ df.columns[0]: "date",
22
+ df.columns[1]: "ctrl",
23
+ df.columns[2]: "bts",
24
+ df.columns[3]: "cell",
25
+ df.columns[4]: "DN",
26
+ }
27
+ )
28
+ df["date"] = pd.to_datetime(df["date"], errors="coerce")
29
+ df = df.dropna(subset=["date", "cell"])
30
+
31
+ non_kpi_columns = ["date", "cell", "bts", "ctrl", "DN"]
32
+ kpi_columns = [col for col in df.columns if col not in non_kpi_columns]
33
+
34
+ anomaly_dict = {}
35
+ anomaly_data = {}
36
+
37
+ def detect_change_points(series, model="rbf", penalty=penalty):
38
+ algo = rpt.Pelt(model=model).fit(series)
39
+ result = algo.predict(pen=penalty)
40
+ return result[:-1]
41
+
42
+ def process_kpi_cell(df_cell, kpi, cell):
43
+ df_kpi = (
44
+ df_cell[["date", kpi]].dropna().sort_values("date").reset_index(drop=True)
45
+ )
46
+ if len(df_kpi) < 30:
47
+ return None
48
+
49
+ series = df_kpi[kpi].values
50
+ try:
51
+ change_indices = detect_change_points(series)
52
+ if not change_indices:
53
+ return None
54
+
55
+ df_kpi["change_point"] = False
56
+ for idx in change_indices:
57
+ if 0 <= idx < len(df_kpi):
58
+ df_kpi.loc[idx, "change_point"] = True
59
+
60
+ df_kpi["cell"] = cell
61
+
62
+ change_indices = [0] + change_indices + [len(df_kpi)]
63
+ segments = [
64
+ series[change_indices[i] : change_indices[i + 1]]
65
+ for i in range(len(change_indices) - 1)
66
+ ]
67
+
68
+ if len(segments) < 2:
69
+ return None
70
+
71
+ initial_mean = np.mean(segments[0])
72
+ final_mean = np.mean(segments[-1])
73
+
74
+ if abs(final_mean - initial_mean) > 0.1 * abs(initial_mean):
75
+ # Attach full history, not just final segment
76
+ df_kpi["initial_mean"] = initial_mean
77
+ df_kpi["final_mean"] = final_mean
78
+ return df_kpi
79
+ else:
80
+ return None
81
+ except Exception as e:
82
+ print(f"Error {cell}-{kpi}: {e}")
83
+ return None
84
+
85
+ for kpi in kpi_columns:
86
+ anomalies = []
87
+ for cell, group in df.groupby("cell"):
88
+ result = process_kpi_cell(group, kpi, cell)
89
+ if result is not None:
90
+ anomalies.append(cell)
91
+ anomaly_data[(kpi, cell)] = result
92
+ if anomalies:
93
+ anomaly_dict[kpi] = anomalies
94
+
95
+ return anomaly_dict, anomaly_data, kpi_columns
96
+
97
+
98
+ if uploaded_file:
99
+ df = pd.read_csv(uploaded_file)
100
+ anomaly_dict, anomaly_data, all_kpis = detect_anomalies(df, penalty)
101
+
102
+ if not anomaly_dict:
103
+ st.info("No anomalies detected.")
104
+ else:
105
+ st.success(f"{len(anomaly_dict)} KPI(s) have un-recovered anomalies detected.")
106
+
107
+ @st.fragment
108
+ def selection_and_plot():
109
+ selected_kpi = st.selectbox("KPI with anomalies", list(anomaly_dict.keys()))
110
+ selected_cell = st.selectbox("Affected cell", anomaly_dict[selected_kpi])
111
+ df_plot = anomaly_data[(selected_kpi, selected_cell)]
112
+
113
+ fig = go.Figure()
114
+ fig.add_trace(
115
+ go.Scatter(
116
+ x=df_plot["date"],
117
+ y=df_plot[selected_kpi],
118
+ mode="lines+markers",
119
+ name="KPI",
120
+ )
121
+ )
122
+ fig.add_trace(
123
+ go.Scatter(
124
+ x=df_plot[df_plot["change_point"]]["date"],
125
+ y=df_plot[df_plot["change_point"]][selected_kpi],
126
+ mode="markers",
127
+ name="Change Point",
128
+ marker=dict(color="red", size=10),
129
+ )
130
+ )
131
+ fig.add_hline(
132
+ y=df_plot["initial_mean"].iloc[0],
133
+ line_dash="dot",
134
+ line_color="gray",
135
+ annotation_text="Initial Mean",
136
+ )
137
+ fig.add_hline(
138
+ y=df_plot["final_mean"].iloc[0],
139
+ line_dash="dash",
140
+ line_color="black",
141
+ annotation_text="Final Mean",
142
+ )
143
+ fig.update_layout(
144
+ title=f"{selected_kpi} - {selected_cell}",
145
+ xaxis_title="Date",
146
+ yaxis_title=selected_kpi,
147
+ )
148
+ st.plotly_chart(fig, use_container_width=True)
149
+
150
+ @st.fragment
151
+ def export_button():
152
+ if st.button("Generate Excel file with anomalies"):
153
+ buffer = BytesIO()
154
+ with pd.ExcelWriter(buffer, engine="openpyxl") as writer:
155
+ for kpi, cells in anomaly_dict.items():
156
+ results = [anomaly_data[(kpi, c)] for c in cells]
157
+ df_final = pd.concat(results)
158
+ df_final.to_excel(writer, sheet_name=kpi[:31], index=False)
159
+ st.download_button(
160
+ label="Download Excel file",
161
+ data=buffer.getvalue(),
162
+ file_name="anomalies_kpi_2G.xlsx",
163
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
164
+ type="primary",
165
+ )
166
+
167
+ selection_and_plot()
168
+ export_button()
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ