Коли українці найчастіше чули тривогу — рахуємо за допомогою Kaggle та Python

💡 Усі статті, обговорення, новини про Python — в одному місці. Приєднуйтесь до Python спільноти!

В який день найчастіше українці чули повітряну тривогу

Одного разу під час обговорення постало питання — у які дні тижня Україна переживала найбільше повітряних тривог? І щоб не дискутувати, вирішено було порахувати. В цьому допомогли набори даних на Kaggle, в якому є дані повітряних тривог в Україні з 24 лютого 2022 року по 1 вересня 2024 року.

Також для здійснення підрахунків було використано python i matplotlib.

Цілі

  • Підрахувати кількість тривог:
    • загалом по регіонах;
    • за роками;
    • за місяцями;
    • за днями тижня.
  • Порахувати загальну тривалість тривог для регіонів.


Спочатку перегляньмо, що цікавого є у наявних даних.



У нас є список файлів JSON, які містять під ключем messages такі поля:

  • id — неважливо;
  • type — неважливо;
  • date — поле з датою та часом;
  • from — неважливо;
  • from_id — неважливо;
  • text — список, що містить інформацію про повітряні тривоги та регіони.

Після обробки дані виглядають ось так (якщо вам цікаві кроки обробки, можете переглянути це посилання):

idtypedatedate_unixtimetext_boldtext_hashtagalert_flagyearmonthdaydayofweek
102087272022-05-04 19:55:16NaN🔴Єланецька_територіальна_громадаair alert2022May4Wednesday
111588222022-05-04 22:06:15NaN🟢Єланецька_територіальна_громадаall clear2022May4Wednesday
9678286102022-10-12 11:07:061665562026🔴Єланецька_територіальна_громадаair alert2022October12Wednesday
9679286112022-10-12 11:10:561665562256🟢Єланецька_територіальна_громадаall clear2022October12Wednesday
9142022-03-15 18:17:28NaN🔴ІваноФранківська_областьair alert2022March15Tuesday
.................................
9803287352022-10-13 16:52:171665669137🟡м_Южноукраїнськ_та_Южноукраїнська_територіальн...all clear2022October13Thursday
13652325842022-11-23 13:54:421669204482🔴м_Южноукраїнськ_та_Южноукраїнська_територіальн...air alert2022November23Wednesday
13669326012022-11-23 15:14:261669209266🟢м_Южноукраїнськ_та_Южноукраїнська_територіальн...all clear2022November23Wednesday
19606385382023-02-18 08:37:151676702235🔴м_Южноукраїнськ_та_Южноукраїнська_територіальн...air alert2023February18Saturday
19634385662023-02-18 09:54:301676706870🟢м_Южноукраїнськ_та_Южноукраїнська_територіальн...all clear2023February18Saturday

Функція для візуалізації, яку я використовував:

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) 


Результат цього можна побачити в анімації нижче.





Резюме

Дуже шкода усвідомлювати, що сигнал повітряної тривоги став невід’ємною частиною нашого життя. Як і слідкування за різними додатками для того, щоб зрозуміти, що саме є причиною цієї тривоги та скільки вона триватиме.

Життя, коли не було чути цих звуків, здається вже якимось дуже далеким і нереальним. І стає страшно, коли усвідомлюєш, що люди прийняли це як частину свого побуту. Після більше ніж двох років війни багато людей не відвідують укриття та залишаються вдома, сподіваючись, що все буде добре.

Але потрібно пам’ятати, що казав Тарас: «Борітеся — поборете! Вам Бог помагає! За вас правда, за вас сила і воля святая».

👍ПодобаєтьсяСподобалось14
До обраногоВ обраному2
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

В Харьковской области, когда бомбят Купянский район, а это 120 км от города, то тревога в Харькове. У меня все.

Было бы интересно исследовать соотношение количества тревог и реальных прилетов (по времени дня и региону). Мне кажется, есть много тревог когда вероятность прилета нулевая.

Heat map по громадах було би цікаво.

якщо зайти на kaggle то там вказано звідки дані були взяті

дякую за статтю, було цікаво почитати
колись памʼятаю гуглив калькулятор шансу чи буде повітряна тривога, щоб зрозуміти коли краще їхати у кінотеатр 😁
подивитися тоді air-alarms.in.ua і зрозумів що такого дня немає

У нас у тревогу кінотеатри працюють

Підписатись на коментарі