Seismic events at Mount Vesuvius

Frequency of earthquakes at different times of the day.
plotly
polar
PyDyTuesday
TidyTuesday
Author

Manish Datt

Published

May 13, 2025

TidyTuesday data for 2025-05-13

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
vesuvius = pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-05-13/vesuvius.csv')
vesuvius
event_id time latitude longitude depth_km duration_magnitude_md md_error area type review_level year
0 4251 2011-04-20T00:27:24Z 40.818000 14.430000 0.42 1.2 0.3 Mount Vesuvius earthquake revised 2011
1 4252 2012-06-19T21:29:48Z 40.808833 14.427167 1.31 0.7 0.3 Mount Vesuvius earthquake revised 2012
2 22547 2013-01-01T07:34:46Z 40.822170 14.428000 0.06 2.2 0.3 Mount Vesuvius earthquake preliminary 2013
3 22546 2013-01-03T16:06:48Z NaN NaN NaN 0.2 0.3 Mount Vesuvius earthquake preliminary 2013
4 22545 2013-01-03T16:07:37Z NaN NaN NaN 0.2 0.3 Mount Vesuvius earthquake preliminary 2013
... ... ... ... ... ... ... ... ... ... ... ...
12022 40738 2024-12-29T23:56:51Z 40.823000 14.428333 0.34 -0.1 0.3 Mount Vesuvius earthquake preliminary 2024
12023 40741 2024-12-30T07:52:43Z 40.823333 14.423500 0.56 -0.1 0.3 Mount Vesuvius earthquake preliminary 2024
12024 40743 2024-12-30T12:52:24Z NaN NaN NaN -0.1 0.3 Mount Vesuvius earthquake preliminary 2024
12025 40744 2024-12-30T15:11:28Z 40.819000 14.424500 0.55 -0.4 0.3 Mount Vesuvius earthquake preliminary 2024
12026 40802 2024-12-31T17:02:32Z 40.822000 14.409833 0.41 0.0 0.3 Mount Vesuvius earthquake preliminary 2024

12027 rows × 11 columns

sns.histplot(data=vesuvius,x="depth_km")

vesuvius["time"] = pd.to_datetime(vesuvius["time"])
vesuvius["hour"] = vesuvius["time"].dt.hour
time_bins = [0, 5, 12, 17, 21, 24]  # 0-4 -> Night, 5-11 -> Morning, etc.
time_labels = ["Night", "Morning", "Afternoon", "Evening", "Night"]
vesuvius["time_of_day"] = pd.cut(vesuvius['hour'], bins=time_bins, labels=time_labels, right=False, ordered=False)
vesuvius["time_of_day"]
0            Night
1            Night
2          Morning
3        Afternoon
4        Afternoon
           ...    
12022        Night
12023      Morning
12024    Afternoon
12025    Afternoon
12026      Evening
Name: time_of_day, Length: 12027, dtype: category
Categories (4, object): ['Afternoon', 'Evening', 'Morning', 'Night']
vesuvius["year"].unique()
array([2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021,
       2022, 2023, 2024], dtype=int64)
bin_edges = [-float('inf'), 1, 2, 3, 5, 7, float('inf')]
vesuvius["depth_bins"] = pd.cut(vesuvius['depth_km'], bins=bin_edges)
vesuvius["depth_bins"].value_counts()
depth_bins
(-inf, 1.0]    7777
(1.0, 2.0]      648
(2.0, 3.0]      142
(3.0, 5.0]       25
(5.0, 7.0]        1
(7.0, inf]        1
Name: count, dtype: int64
vesuvius.head()
event_id time latitude longitude depth_km duration_magnitude_md md_error area type review_level year hour time_of_day depth_bins
0 4251 2011-04-20 00:27:24+00:00 40.818000 14.430000 0.42 1.2 0.3 Mount Vesuvius earthquake revised 2011 0 Night (-inf, 1.0]
1 4252 2012-06-19 21:29:48+00:00 40.808833 14.427167 1.31 0.7 0.3 Mount Vesuvius earthquake revised 2012 21 Night (1.0, 2.0]
2 22547 2013-01-01 07:34:46+00:00 40.822170 14.428000 0.06 2.2 0.3 Mount Vesuvius earthquake preliminary 2013 7 Morning (-inf, 1.0]
3 22546 2013-01-03 16:06:48+00:00 NaN NaN NaN 0.2 0.3 Mount Vesuvius earthquake preliminary 2013 16 Afternoon NaN
4 22545 2013-01-03 16:07:37+00:00 NaN NaN NaN 0.2 0.3 Mount Vesuvius earthquake preliminary 2013 16 Afternoon NaN

Annual number of tremors vs duration

fig, ax = plt.subplots()
hue_order = ["Morning", "Afternoon", "Evening", "Night"]
color_map = ["lightblue", "dodgerblue", "lightgrey", "grey"]
colors = {x: color_map[ind] for ind, x in enumerate(hue_order)}
     
sns.stripplot(data=vesuvius, x="year", y="duration_magnitude_md", \
              hue='time_of_day', hue_order=hue_order, palette="plasma")
plt.xticks(rotation=45)
plt.xlabel("")

plt.legend(
    title='',
    loc='upper center',     
    bbox_to_anchor=(0.5, 1.1), 
    ncol=4,
    frameon=False
)
sns.despine()

Earthquakes at different times of the day

vesuvius["hour12"] = vesuvius["time"].dt.strftime("%I").astype(int)    
vesuvius["AMPM"] = vesuvius["time"].dt.strftime("%p")              
vesuvius_grp = vesuvius.groupby(["hour12", "AMPM"]).size().unstack(fill_value=0)
vesuvius_grp
AMPM AM PM
hour12
1 691 339
2 694 384
3 618 335
4 591 440
5 510 732
6 472 555
7 352 610
8 287 592
9 284 624
10 380 598
11 334 627
12 646 332
vesuvius_grp.plot(kind="bar", stacked=True, color=['dodgerblue', 'salmon'])

Polar coordinates

import plotly.graph_objects as go
import numpy as np
import roman
vesuvius_grp['angle'] = vesuvius_grp.index * 30

# Create polar plot with two bar traces: AM and PM
fig = go.Figure()

# AM bars
fig.add_trace(go.Barpolar(
    r=vesuvius_grp['AM'],
    theta=vesuvius_grp['angle'],
    name='AM',
    marker_color='dodgerblue',
    hovertemplate='count = %{r}<br>time = %{theta} AM<extra></extra>'
))

# PM bars
fig.add_trace(go.Barpolar(
    r=vesuvius_grp['PM'],
    theta=vesuvius_grp['angle'],
    name='PM',
    marker_color='salmon',
    hovertemplate='count = %{r}<br>time = %{theta} PM<extra></extra>'
))

# Layout
fig.update_layout(
    title={
        'text': f'Distribution of <b>{vesuvius_grp["AM"].sum() + vesuvius_grp["PM"].sum():,.0f}</b> earthquakes during <br><span style="color:dodgerblue;">AM</span> and <span style="color:red;">PM</span> at Mount Vesuvius (2011-24)',
        'font': {
            'size': 16  
        },
        "x" : 0.5,
    },
    polar=dict(
        angularaxis=dict(direction='clockwise', rotation=90, tickmode='array',
                         tickvals=np.arange(30, 361, 30),
                         ticktext=[f"<b>{roman.toRoman(h)}</b>" for h in range(1, 13)], 
                         showline=False, showgrid=False),
        radialaxis=dict(showticklabels=False, ticks='', showline=False, showgrid=False)
    ),
    showlegend=False,
    template='plotly_white',
    width=500,
    height=500,
    margin=dict(l=0, r=0, t=100, b=20)
)
#fig.write_image("Vesuvius.png")
fig.show()

There is no direct way to add labels to bars in radial axis. As a workaround, add scatterplot and show only text.

vesuvius_grp['angle'] = vesuvius_grp.index * 30

# Create polar plot with two bar traces: AM and PM
fig = go.Figure()

# AM bars
fig.add_trace(go.Barpolar(
    r=vesuvius_grp['AM'],
    theta=vesuvius_grp['angle'],
    name='AM',
    marker_color='dodgerblue',
    hovertemplate='count = %{r}<br>time = %{theta} AM<extra></extra>'
))

# PM bars
fig.add_trace(go.Barpolar(
    r=vesuvius_grp['PM'],
    theta=vesuvius_grp['angle'],
    name='PM',
    marker_color='salmon',
    hovertemplate='count = %{r}<br>time = %{theta} PM<extra></extra>'
))

# Layout
fig.update_layout(
    title={
        'text': f'Distribution of <b>{vesuvius_grp["AM"].sum() + vesuvius_grp["PM"].sum():,.0f}</b> earthquakes during <br><span style="color:dodgerblue;">AM</span> and <span style="color:red;">PM</span> at Mount Vesuvius (2011-24)',
        'font': {
            'size': 16  
        },
        "x" : 0.5,
    },
    polar=dict(
        angularaxis=dict(direction='clockwise', rotation=90, tickmode='array',
                         tickvals=np.arange(30, 361, 30),
                         ticktext=[f"<b>{roman.toRoman(h)}</b>" for h in range(1, 13)], 
                         showline=False, showgrid=False),
        radialaxis=dict(showticklabels=False, ticks='', showline=False, showgrid=False)
    ),
    showlegend=False,
    template='plotly_white',
    width=500,
    height=500,
    margin=dict(l=0, r=0, t=100, b=20)
)
fig.add_trace(go.Scatterpolar(
    r=vesuvius_grp['AM']+50,  
    theta=vesuvius_grp['angle'],  
    mode="text",  
    text=vesuvius_grp['AM'],  
    textposition="middle center",  
    textfont=dict(size=14, color="white"),
    name="AM Labels",
    hoverinfo="skip",  
    showlegend=False
))

fig.add_trace(go.Scatterpolar(
    r=vesuvius_grp['AM']+vesuvius_grp['PM']+50,  
    theta=vesuvius_grp['angle'],  
    mode="text",
    text=vesuvius_grp['PM'],
    textposition="middle center",
    name="PM Labels",
    hoverinfo="skip",
    showlegend=False
))
#fig.write_image("Vesuvius_labels.png")
fig.show()