Skip to content

Commit cb748b3

Browse files
committed
Add Hawkes earthquake aftershocks study example
We need to install basemap to make it work
1 parent 0ca995b commit cb748b3

File tree

6 files changed

+6385
-2
lines changed

6 files changed

+6385
-2
lines changed

doc/_static/example_data/earthquakes.csv

Lines changed: 6253 additions & 0 deletions
Large diffs are not rendered by default.

examples/earthquakes.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../doc/_static/example_data/earthquakes.csv
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
==============================================================
3+
Study earthquake aftershocks propagation with Hawkes processes
4+
==============================================================
5+
6+
This experiments aims to detect how aftershocks propagate near Japan. A Hawkes
7+
process has been fit on data from
8+
9+
Ogata, Y., 1988.
10+
Statistical models for earthquake occurrences and residual analysis
11+
for point processes.
12+
Journal of the American Statistical association, 83(401), pp.9-27.
13+
14+
On the left we can see where earthquakes have occurred and on the right
15+
the propagation matrix, i.e. how likely a earthquake in a given zone will
16+
trigger an aftershock in another zone.
17+
We can observe than zone 1, 2 and 3 are tightly linked while zone 4 and
18+
5 are more self-excited.
19+
Note that this Hawkes process does not take the location of an earthquake
20+
to recover this result.
21+
22+
Dataset `'earthquakes.csv'` is available
23+
`here <../_static/example_data/earthquakes.csv>`_.
24+
"""
25+
26+
import matplotlib.pyplot as plt
27+
import numpy as np
28+
import pandas as pd
29+
from mpl_toolkits.basemap import Basemap
30+
31+
from tick.hawkes import HawkesEM
32+
from tick.plot import plot_hawkes_kernel_norms
33+
from tick.plot.plot_utilities import get_plot_color
34+
35+
df = pd.read_csv('earthquakes.csv')
36+
37+
lats = df.Latitude.values
38+
lons = df.Longitude.values
39+
40+
# put timestamps in minutes and remove minimum
41+
timestamps = pd.to_datetime(df.DateTime).values.astype(np.float64)
42+
timestamps *= 1e-9 / 60
43+
timestamps -= min(timestamps)
44+
45+
fig, ax_list = plt.subplots(1, 2, figsize=(8, 3.4))
46+
ax_list[0].set_title('Earthquakes near Japan')
47+
48+
# Draw map
49+
lon0, lat0 = (139, 34)
50+
lon1, lat1 = (147, 44)
51+
m = Basemap(projection='merc', llcrnrlon=lon0, llcrnrlat=lat0, urcrnrlon=lon1,
52+
urcrnrlat=lat1, resolution='l', ax=ax_list[0])
53+
m.drawmapboundary(fill_color='#99ffff')
54+
m.fillcontinents(color='#cc9966', lake_color='#99ffff')
55+
56+
# Number of splits in the map
57+
n_groups_lats = 5
58+
59+
group_lats = []
60+
group_lons = []
61+
group_timestamps = []
62+
group_names = []
63+
64+
delta_lats = (lats.max() - lats.min()) / n_groups_lats
65+
delta_lons = lons.max() - lons.min()
66+
67+
for group in range(n_groups_lats):
68+
# Split events into groups
69+
min_lat_group = lats.min() + group * delta_lats
70+
max_lat_group = lats.min() + (group + 1) * delta_lats
71+
72+
mask = (min_lat_group <= lats) & (lats < max_lat_group)
73+
74+
group_lats += [lats[mask]]
75+
group_lons += [lons[mask]]
76+
group_timestamps += [timestamps[mask]]
77+
78+
# Draw earthquakes on map
79+
x, y = m(group_lons[group], group_lats[group])
80+
m.scatter(x, y, 0.3, marker='o', color=get_plot_color(group))
81+
82+
# Draw zone labels on map
83+
group_name = 'Z{}'.format(group)
84+
group_names += [group_name]
85+
86+
center_lat = 0.5 * (min_lat_group + max_lat_group)
87+
x_zone, y_zone = m(lons.max() + 0.1 * delta_lons, center_lat)
88+
89+
zone_bbox_style = dict(boxstyle="round", ec=(0, 0, 0, 0.9), fc=(1., 1, 1,
90+
0.6))
91+
ax_list[0].text(x_zone, y_zone, group_name, fontsize=12, fontweight='bold',
92+
ha='left', va='center', color='k', withdash=True,
93+
bbox=zone_bbox_style)
94+
95+
# Fit Hawkes process on events
96+
events = []
97+
for g in range(n_groups_lats):
98+
events += [group_timestamps[g]]
99+
events[g].sort()
100+
101+
kernel_discretization = np.linspace(0, 10000 / 60, 6)
102+
em = HawkesEM(kernel_discretization=kernel_discretization, tol=1e-9,
103+
max_iter=1000, print_every=200)
104+
em.fit(events)
105+
106+
plot_hawkes_kernel_norms(em, ax=ax_list[1], node_names=group_names)
107+
ax_list[1].set_xticklabels(ax_list[1].get_xticklabels(), fontsize=11)
108+
ax_list[1].set_yticklabels(ax_list[1].get_yticklabels(), fontsize=11)
109+
110+
fig.tight_layout()
111+
plt.show()

tick/plot/plot_hawkes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def plot_hawkes_kernel_norms(kernel_object, show=True, pcolor_kwargs=None,
11-
node_names=None, rotate_x_labels=0.):
11+
node_names=None, rotate_x_labels=0., ax=None):
1212
"""Generic function to plot Hawkes kernel norms.
1313
1414
Parameters
@@ -37,6 +37,10 @@ def plot_hawkes_kernel_norms(kernel_object, show=True, pcolor_kwargs=None,
3737
Number of degrees to rotate the x-labels clockwise, to prevent
3838
overlapping.
3939
40+
ax : `matplotlib.axes`, default=None
41+
If not None, the figure will be plot on this axis and show will be
42+
set to False. Used only with matplotlib
43+
4044
Notes
4145
-----
4246
Kernels are displayed such that it shows norm of column influence's
@@ -54,7 +58,6 @@ def plot_hawkes_kernel_norms(kernel_object, show=True, pcolor_kwargs=None,
5458
column_labels = ['$\\rightarrow {}$'.format(i) for i in node_names]
5559

5660
norms = kernel_object.get_kernel_norms()
57-
fig, ax = plt.subplots()
5861

5962
if rotate_x_labels != 0.:
6063
# we want clockwise rotation because x-axis is on top
@@ -63,6 +66,12 @@ def plot_hawkes_kernel_norms(kernel_object, show=True, pcolor_kwargs=None,
6366
else:
6467
x_label_alignment = 'center'
6568

69+
if ax is None:
70+
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
71+
else:
72+
fig = ax.figure
73+
show = False
74+
6675
if pcolor_kwargs is None:
6776
pcolor_kwargs = {}
6877

tools/travis/docker_run.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ set -e -x
66
cp -R /io src
77
cd src
88

9+
apt-get update;
10+
apt-get install -y libgeos*;
11+
912
eval "$(pyenv init -)"
1013

1114
pyenv global ${PYVER}
1215
pyenv local ${PYVER}
1316

17+
# needed for basemap see https://github.com/matplotlib/basemap/issues/414#issuecomment-436792915
18+
python -m pip install https://github.com/jswhit/pyproj/archive/v1.9.5.1rel.zip
19+
python -m pip install https://github.com/matplotlib/basemap/archive/v1.1.0.tar.gz
1420
python -m pip install yapf --upgrade
1521
python -m yapf --style tools/code_style/yapf.conf -i tick examples --recursive
1622

tools/travis/osx_install.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@ python -m pip install -r requirements.txt
4545
python -m pip install sphinx pillow
4646
python -m pip install cpplint
4747
[[ "${PYVER}" != "3.7.0" ]] && python -m pip install tensorflow # does not yet exist on python 3.7
48+
brew install geos
49+
python -m pip install https://github.com/jswhit/pyproj/archive/v1.9.5.1rel.zip
50+
python -m pip install https://github.com/matplotlib/basemap/archive/v1.1.0.tar.gz
4851
pyenv rehash
4952

0 commit comments

Comments
 (0)