Коли українці найчастіше чули тривогу — рахуємо за допомогою Kaggle та Python
В який день найчастіше українці чули повітряну тривогу
Одного разу під час обговорення постало питання — у які дні тижня Україна переживала найбільше повітряних тривог? І щоб не дискутувати, вирішено було порахувати. В цьому допомогли набори даних на Kaggle, в якому є дані повітряних тривог в Україні з 24 лютого 2022 року по 1 вересня 2024 року.
Також для здійснення підрахунків було використано python i matplotlib.
Цілі
- Підрахувати кількість тривог:
- загалом по регіонах;
- за роками;
- за місяцями;
- за днями тижня.
- Порахувати загальну тривалість тривог для регіонів.
Спочатку перегляньмо, що цікавого є у наявних даних.

У нас є список файлів JSON, які містять під ключем messages такі поля:
id— неважливо;type— неважливо;date— поле з датою та часом;from— неважливо;from_id— неважливо;text— список, що містить інформацію про повітряні тривоги та регіони.
Після обробки дані виглядають ось так (якщо вам цікаві кроки обробки, можете переглянути це посилання):
| id | type | date | date_unixtime | text_bold | text_hashtag | alert_flag | year | month | day | dayofweek |
|---|---|---|---|---|---|---|---|---|---|---|
| 1020 | 8727 | NaN | 🔴 | Єланецька_територіальна_громада | air alert | 2022 | May | 4 | Wednesday | |
| 1115 | 8822 | NaN | 🟢 | Єланецька_територіальна_громада | all clear | 2022 | May | 4 | Wednesday | |
| 9678 | 28610 | 1665562026 | 🔴 | Єланецька_територіальна_громада | air alert | 2022 | October | 12 | Wednesday | |
| 9679 | 28611 | 1665562256 | 🟢 | Єланецька_територіальна_громада | all clear | 2022 | October | 12 | Wednesday | |
| 9 | 14 | NaN | 🔴 | ІваноФранківська_область | air alert | 2022 | March | 15 | Tuesday | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9803 | 28735 | 1665669137 | 🟡 | м_Южноукраїнськ_та_Южноукраїнська_територіальн... | all clear | 2022 | October | 13 | Thursday | |
| 13652 | 32584 | 1669204482 | 🔴 | м_Южноукраїнськ_та_Южноукраїнська_територіальн... | air alert | 2022 | November | 23 | Wednesday | |
| 13669 | 32601 | 1669209266 | 🟢 | м_Южноукраїнськ_та_Южноукраїнська_територіальн... | all clear | 2022 | November | 23 | Wednesday | |
| 19606 | 38538 | 1676702235 | 🔴 | м_Южноукраїнськ_та_Южноукраїнська_територіальн... | air alert | 2023 | February | 18 | Saturday | |
| 19634 | 38566 | 1676706870 | 🟢 | м_Южноукраїнськ_та_Южноукраїнська_територіальн... | all clear | 2023 | February | 18 | Saturday |
Функція для візуалізації, яку я використовував:
def plot_counter(data, title, height=12, bg_color="#3c3f41", line_data=None):
plt.style.use('dark_background') # Set dark background
# Plotting with custom figsize
fig, ax = plt.subplots(figsize=(20, height)) # Set width to 10 and height to 6
data.plot(kind='barh', stacked=False, ax=ax, color=['#ff3334', '#769933'])
if line_data is not None:
# Plotting the line chart on top of the bar chart
for _, value in line_data.iterrows():
ax.axvline(x=value["air alert"], color=year_colors[str(int(value["year"]))], linewidth=1.5, label=f'{value["year"]} mean: {value["air alert"]:.2f}')
# Set the figure and axis background colors
fig.patch.set_facecolor(bg_color) # Set the figure background color
ax.set_facecolor(bg_color)
# Move x-axis labels to the top
ax.xaxis.set_label_position('top')
ax.xaxis.tick_top()
# Adding grid (in lighter color for visibility)
ax.grid(True, which='both', axis='x', color='white', linestyle='--', linewidth=0.5)
ax.legend()
# Customizing labels and title
ax.set_xlabel('Count', labelpad=10, color='white')
ax.set_ylabel('Text Hashtag', color='white')
ax.set_title(title, color='white')
plt.tight_layout() # Adjust layout so everything fits without overlap
plt.show()
Ніби все готове для візуалізації. Залишилося спробувати щось «намалювати».
Підрахунок кількості тривог за регіоном
Щоб підрахувати загальну кількість тривог, я згрупував дані за полями text_hashtag (регіони) і alert_flag. Через велику кількість регіонів, я зосередив увагу тільки на тих, де кількість тривог перевищує 300.
Думав згрупувати дані за областями, але вирішив, що, напевне, були причини, чому саме так маркувалися регіони. Тож залишив, як є.
grouped = df.groupby(['text_hashtag', 'alert_flag']).size().unstack(fill_value=0)
grouped = grouped.sort_values(by=['air alert', 'all clear'], ascending=True)
grouped_1 = grouped[grouped['air alert'] > 300]
print("[INFO] the regions with the most of air alerts")
plot_counter(grouped_1, 'Count of Text Hashtags Grouped by Alert Flag')
Результати, які я отримав:

Жителі наступних регіонів чули сигнал повітряної тривоги понад 4000 разів кожен:
- Донецьк
- Запоріжжя
- Харків
- Дніпро
Ми всі живемо в цих реаліях, тому мені важко підібрати слова, щоб зробити якийсь висновок. Як на мене, тут варто подзвонити рідним, близьким, знайомим, які живуть далеко, і просто поспілкуватися.
Підрахувати кількість тривог за роками
Щоб проаналізувати кількість тривог за роками та визначити регіони з найбільшою кількістю тривог, я згрупував дані за роками та alert_flag, а потім візуалізував результати.
Такий код був використаний для групування даних та побудови графіку кількості тривог:
grouped = df.groupby(["year", "alert_flag"]).size().unstack(fill_value=0) grouped = grouped.sort_values(["air alert"]) plot_counter(grouped, 'Count Alert Flag and Group By Year', height=8)
На графіку нижче показано кількість тривог за роками, згрупованих за alert_flag.
З візуалізації видно, що 2024 рік є найбільш активним за кількістю тривог на сьогоднішній день. Потрібно також врахувати, що дані за 2024 не містять інформацію про чотири місяці, що залишились до кінця року на момент написання цієї статті.
Щоб проаналізувати кількість тривог за регіонами і роками, я відфільтрував дані, залишивши лише ті регіони, в яких кількість тривог перевищує 150. Результати були візуалізовані для виявлення трендів та регіонів із значною активністю тривог.
Такий код був використаний для групування даних та побудови графіку результатів:
grouped_1 = grouped[grouped['air alert'] > 150] grouped_1 = grouped_1.sort_values(by=["air alert"]) plot_counter(grouped_1, 'Count of Air Alet by Regions and Year', height=30, line_data=mean_pivot)
На графіку нижче показано кількість повітряних тривог за регіонами та роками, з акцентом на регіони, де кількість тривог перевищує 150.
💔
Підрахунок кількість тривог за місяцями
Давайте проаналізуємо кількість тривог за місяцями:
- загалом;
- за роками;
- за роками та сезонами.
Загалом
Щоб отримати загальну картину кількості тривог за місяцями, я згрупував дані за month та alert_flag, а потім візуалізував результати.
Був використаний такий код:
grouped = df.groupby(["month", "alert_flag"]).size().unstack(fill_value=0) grouped = grouped.sort_values(["air alert"]) plot_counter(grouped, 'Count Alert Flag and Group By Month', height=8)
На графіку нижче показано кількість тривог за місяцями, агреговану за всі роки.

З візуалізації видно, що найбільш активними місяцями є:
- Серпень
- Травень
- Березень
За роками
Щоб отримати загальну картину кількості тривог за місяцями та роками, я згрупував дані за month, year та alert_flag, а потім візуалізував результати.
Був використаний такий код:
grouped = df.groupby(["month", "year", "alert_flag"]).size().unstack(fill_value=0) grouped = grouped.sort_values(["air alert"]) plot_counter(grouped, 'Count Alert Flag and Group By Month and Year', height=12)
На графіку нижче показано кількість тривог за місяцями та роками.
З візуалізації видно, що найбільш активними місяцями є:
- Березень, 2024
- Травень, 2024
- Червень, 2024
За роками та сезонами
Щоб отримати загальну картину кількості тривог за місяцями, роками та сезонами, я згрупував дані за month, year, text_hashtag (регіони) та alert_flag, а потім візуалізував результати.
Був використаний такий код:
grouped = df.groupby(["month", "year", "text_hashtag", "alert_flag"]).size().unstack(fill_value=0) grouped_winter = grouped.loc[["December", "January", "February"]] grouped_winter = grouped_winter.sort_values(["air alert"]) grouped_spring = grouped.loc[["March", "April", "May"]] grouped_spring = grouped_spring.sort_values(["air alert"]) grouped_summer = grouped.loc[["June", "July", "August"]] grouped_summer = grouped_summer.sort_values(["air alert"]) grouped_autumn = grouped.loc[["September", "October", "November"]] grouped_autumn = grouped_autumn.sort_values(["air alert"]) plot_counter(grouped_winter, 'Count Alert Flag and Group By Month, Year and Region (Winter)', height=60) plot_counter(grouped_spring, 'Count Alert Flag and Group By Month, Year and Region (Spring)', height=80) plot_counter(grouped_summer, 'Count Alert Flag and Group By Month, Year and Region (Summer)', height=60) plot_counter(grouped_autumn, 'Count Alert Flag and Group By Month, Year and Region (Autumn)', height=60)На графіку нижче показано кількість тривог за зимовими сезонами:

З цієї візуалізації видно, що найбільш активними зимовими місяцями є:
- Січень 2024: Харківська область
- Лютий 2024: Харківська область
- Лютий 2024: Донецька область

З цієї візуалізації видно, що найбільш активними весняними місяцями є:
- Березень 2024: Харківська область
- Травень 2024: Дніпровська область
- Березень 2024: Сумська область

З цієї візуалізації видно, що найбільш активними літніми місяцями є:
- Серпень 2023: Запорізька область
- Серпень 2023: Донецька область
- Липень 2023: Донецька область

З цієї візуалізації видно, що найбільш активними осінніми місяцями є:
- Вересень 2023: Запорізька область
- Вересень 2023: Донецька область
- Жовтень 2023: Донецька область
Підрахунок кількості тривог за днями тижня
Проаналізуймо кількість тривог за днями тижня:
- загалом;
- за роками.
Щоб отримати загальну картину кількості тривог за днями тижня, я згрупував дані за dayofweek та alert_flag, а потім візуалізував результати.
Був використаний такий код:
grouped = df.groupby(["dayofweek", "alert_flag"]).size().unstack(fill_value=0) grouped = grouped.sort_values(["air alert"]) plot_counter(grouped, 'Count Alert Flag and Group By Day Of Week', height=8)
З цієї візуалізації видно, що найбільш активними днями тижня є:
- П'ятниця
- Четвер
- Середа
Щоб отримати загальну картину кількості тривог за днями тижня по роках, я згрупував дані за dayofweek, year та alert_flag, а потім візуалізував результати.
Був використаний такий код:
grouped = df.groupby(["dayofweek", "year", "alert_flag"]).size().unstack(fill_value=0) grouped = grouped.sort_values(["air alert"]) plot_counter(grouped, 'Count Alert Flag and Group By Day Of Week', height=10)
З цієї візуалізації видно, що найбільш активними днями тижня є:
- П'ятниця, 2024
- П'ятниця, 2023
- Середа, 2024
Аналіз тривалості тривог
Проаналізуймо, скільки часу люди проводили в укриттях через повітряні тривоги.
Підготовка даних
По-перше, потрібно підготувати дані для візуалізації. Такий код обчислює тривалість часу, який люди проводили в укриттях:
# Initialize list to store results
result = []
# Group by text_hashtag
grouped = df.groupby('text_hashtag')
# Iterate over groups
for name, group in grouped:
air_alert_start = None
for _, row in group.iterrows():
if row['alert_flag'] == 'air alert':
air_alert_start = row['date']
elif row['alert_flag'] == 'all clear' and air_alert_start is not None:
# Calculate time difference
time_diff = (row['date'] - air_alert_start).total_seconds() / 60
result.append({
'text_hashtag': name,
'air_alert_start': air_alert_start,
'all_clear_time': row['date'],
'time_in_shelter': time_diff,
"year": row["year"],
"month": row['date'].month,
"day": row["day"],
"dayofweek": row["dayofweek"]
})
air_alert_start = None # Reset for the next alert-clear pair
# Convert result to DataFrame
result_df = pd.DataFrame(result)
result_df
Отриманий DataFrame містить:
text_hashtag: Регіонair_alert_start: Час початку повітряної тривогиall_clear_time: Час закінчення повітряної тривогиtime_in_shelter: Тривалість у хвилинахyear,month,day,dayofweek
Створення зведеного DataFrame
Наступним кроком є створення зведеної таблиці для агрегування загального часу, проведеного в укриттях:
grouped_df = result_df.groupby(['text_hashtag', 'year', 'month', 'day'])['time_in_shelter'].sum().reset_index() grouped_df['date_str'] = pd.to_datetime(grouped_df[['year', 'month', 'day']]).astype(str) grouped_df = grouped_df.sort_values(by=['year', 'month', 'day']) pivot_df = grouped_df.pivot(index="text_hashtag", columns="date_str", values="time_in_shelter").fillna(0) pivot_df = pivot_df.cumsum(axis=1) pivot_dfРезультати:
Зведений DataFrame показує сумарний час, проведений в укриттях за регіонами.

Тепер ми готові створити анімацію, яка ілюструє топ-10 регіонів, показуючи, скільки часу люди проводять в укриттях через повітряні тривоги.
fig, ax = plt.subplots(figsize=(15, 10))
date_str = pivot_df.columns
# Function to update the animation frame by frame
def update_animation(date_value):
current_data = pivot_df[date_value].sort_values(ascending=False).head(10)
ax.clear()
ax.invert_yaxis()
upper_limit_xaxis = current_data.max() + 10000
ax.set_xlim(0, upper_limit_xaxis)
bars = ax.barh(current_data.index[:3], current_data[:3], color=["#a8323c", "#f56991", "#944e4e"], label='Top 3')
bars += ax.barh(current_data.index[3:], current_data[3:], color='#c7b75f', label='Remaining')
ax.set_xlabel('Cumulative Time in Shelter (minutes)')
ax.set_title(f'Time (minutes) spend in shelter by region (top 10) - {date_value}')
# Return the artists to be animated
return bars
# Create animation object
ani = animation.FuncAnimation(
fig, update_animation, frames=date_str, interval=100, repeat=True
)
fig.subplots_adjust(left=.35)
# Save the animation as a GIF
ani.save('animation.gif', writer='pillow', fps=5)
plt.close(fig)
Результат цього можна побачити в анімації нижче.

Резюме
Дуже шкода усвідомлювати, що сигнал повітряної тривоги став невід’ємною частиною нашого життя. Як і слідкування за різними додатками для того, щоб зрозуміти, що саме є причиною цієї тривоги та скільки вона триватиме.
Життя, коли не було чути цих звуків, здається вже якимось дуже далеким і нереальним. І стає страшно, коли усвідомлюєш, що люди прийняли це як частину свого побуту. Після більше ніж двох років війни багато людей не відвідують укриття та залишаються вдома, сподіваючись, що все буде добре.
Але потрібно пам’ятати, що казав Тарас: «Борітеся — поборете! Вам Бог помагає! За вас правда, за вас сила і воля святая».
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівВ Харьковской области, когда бомбят Купянский район, а это 120 км от города, то тревога в Харькове. У меня все.
Было бы интересно исследовать соотношение количества тревог и реальных прилетов (по времени дня и региону). Мне кажется, есть много тревог когда вероятность прилета нулевая.
Heat map по громадах було би цікаво.
А першоджерело даних яке?
якщо зайти на kaggle то там вказано звідки дані були взяті
дякую за статтю, було цікаво почитати
колись памʼятаю гуглив калькулятор шансу чи буде повітряна тривога, щоб зрозуміти коли краще їхати у кінотеатр 😁
подивитися тоді air-alarms.in.ua і зрозумів що такого дня немає
У нас у тревогу кінотеатри працюють