Коли українці найчастіше чули тривогу — рахуємо за допомогою 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 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів