Let's import some data hereΒΆ
Let's start by getting our hands on a dataset! We have downloaded a 7-day .csv dataset from the USGS website, filtered for earthquakes with a magnitude stronger than 5, and and put it under:ΒΆ
'datasets/earthquakes.csv'
We shall read the .csv with pandas and print it to verify it is parsed correctly.ΒΆ
InΒ [143]:
import pandas as pd
# Load the earthquake data
data = pd.read_csv('datasets/earthquakes.csv')
# Print to check
print(data)
time latitude longitude depth mag magType nst \
0 2025-11-24T02:21:02.756Z 7.4819 -37.0268 10.000 5.4 mww 82
1 2025-11-23T08:21:46.286Z 51.4112 -177.0684 54.820 5.5 mww 109
2 2025-11-23T04:39:22.232Z 13.3402 95.8348 10.000 5.3 mww 48
3 2025-11-23T03:19:55.726Z 1.2749 128.5695 10.000 5.4 mww 61
4 2025-11-22T18:41:19.405Z 8.7615 -84.1029 8.031 5.1 mww 158
5 2025-11-21T22:38:09.124Z 49.6155 158.6388 10.000 5.7 mww 95
6 2025-11-21T12:37:59.558Z -24.2360 179.9218 519.151 5.6 mww 111
7 2025-11-21T12:22:03.648Z 39.6282 143.5446 10.000 5.5 mww 98
8 2025-11-21T04:38:26.465Z 23.8942 90.5788 10.000 5.5 mww 101
9 2025-11-20T23:36:07.248Z 25.6353 124.8355 120.250 5.1 mww 91
10 2025-11-20T15:55:45.358Z -11.5975 164.5887 44.810 5.0 mb 28
11 2025-11-20T12:50:02.013Z -25.3510 -176.5576 96.408 5.4 mww 70
12 2025-11-20T06:59:44.153Z -3.7031 128.3705 128.344 5.8 mww 84
13 2025-11-20T04:19:54.753Z 1.5789 127.1375 136.935 5.4 mb 52
14 2025-11-20T01:06:49.304Z -31.5946 -178.7643 114.275 5.1 mb 33
15 2025-11-19T21:45:22.218Z -57.9631 -25.1924 39.527 5.0 mb 43
16 2025-11-19T06:39:01.963Z -37.8809 -75.1065 10.000 5.0 mww 50
17 2025-11-19T02:23:19.189Z 39.2352 143.4678 10.000 5.0 mb 38
18 2025-11-19T01:38:53.994Z 10.8709 -62.7566 122.975 5.0 mww 65
19 2025-11-18T22:36:32.878Z 8.7898 -84.0158 10.000 5.4 mww 82
20 2025-11-18T15:35:29.949Z -11.9799 65.7397 10.000 5.3 mww 240
21 2025-11-17T19:06:35.519Z 39.7568 143.1957 33.331 5.2 mww 98
22 2025-11-17T17:23:03.682Z 12.1277 -86.4514 128.024 5.3 mww 66
23 2025-11-17T12:12:37.800Z 0.1060 124.0046 119.483 5.3 mww 116
24 2025-11-17T09:48:31.919Z -61.1895 159.6597 10.000 5.0 mb 16
25 2025-11-17T00:46:17.538Z -11.0885 -106.3645 10.000 5.0 mb 72
gap dmin rms ... updated \
0 51 13.268 1.53 ... 2025-11-24T04:43:00.104Z
1 92 0.366 0.92 ... 2025-11-24T08:47:46.499Z
2 93 3.517 1.15 ... 2025-11-24T13:12:55.040Z
3 84 3.427 1.26 ... 2025-11-24T03:45:38.429Z
4 136 0.978 0.84 ... 2025-11-22T23:02:19.040Z
5 74 3.547 0.96 ... 2025-11-22T23:01:23.571Z
6 22 6.690 0.91 ... 2025-11-22T13:03:52.062Z
7 99 2.225 0.72 ... 2025-11-22T12:50:13.363Z
8 49 4.820 0.89 ... 2025-11-24T18:31:48.787Z
9 37 1.364 0.68 ... 2025-11-21T21:49:35.040Z
10 111 5.043 0.78 ... 2025-11-20T16:12:37.040Z
11 126 8.757 1.17 ... 2025-11-21T13:31:14.724Z
12 42 1.733 0.93 ... 2025-11-21T09:06:50.940Z
13 79 3.544 0.83 ... 2025-11-21T04:40:10.118Z
14 93 6.424 0.79 ... 2025-11-20T01:25:28.040Z
15 69 7.309 0.68 ... 2025-11-19T22:05:04.040Z
16 154 2.388 1.16 ... 2025-11-19T16:55:17.821Z
17 138 2.411 1.06 ... 2025-11-19T21:39:47.136Z
18 92 4.144 1.20 ... 2025-11-19T02:13:52.916Z
19 84 0.946 0.76 ... 2025-11-20T02:24:04.357Z
20 31 8.023 0.54 ... 2025-11-19T23:00:55.040Z
21 85 1.929 0.55 ... 2025-11-18T19:35:38.249Z
22 126 0.793 1.18 ... 2025-11-18T13:48:50.776Z
23 25 3.374 1.02 ... 2025-11-17T12:31:48.040Z
24 142 6.721 0.47 ... 2025-11-17T10:13:34.040Z
25 86 16.197 0.56 ... 2025-11-17T02:48:46.040Z
place type horizontalError \
0 central Mid-Atlantic Ridge earthquake 10.61
1 59 km SSW of Adak, Alaska earthquake 4.40
2 267 km WSW of Dawei, Burma (Myanmar) earthquake 9.93
3 70 km NNE of Maba, Indonesia earthquake 7.27
4 67 km WSW of Puerto CortΓ©s, Costa Rica earthquake 7.62
5 214 km SE of Severo-Kurilβsk, Russia earthquake 8.31
6 south of the Fiji Islands earthquake 11.76
7 137 km E of Miyako, Japan earthquake 3.25
8 14 km WSW of Narsingdi, Bangladesh earthquake 8.62
9 103 km NNW of Hirara, Japan earthquake 7.05
10 163 km SW of Lata, Solomon Islands earthquake 11.62
11 south of the Fiji Islands earthquake 10.53
12 20 km E of Ambon, Indonesia earthquake 7.63
13 91 km NNW of Ternate, Indonesia earthquake 8.16
14 Kermadec Islands region earthquake 12.84
15 South Sandwich Islands region earthquake 11.36
16 131 km WSW of Lebu, Chile earthquake 4.44
17 133 km ESE of Yamada, Japan earthquake 8.85
18 34 km NNE of Yaguaraparo, Venezuela earthquake 11.10
19 57 km WSW of Puerto CortΓ©s, Costa Rica earthquake 5.34
20 Mid-Indian Ridge earthquake 2.83
21 108 km E of Miyako, Japan earthquake 7.19
22 12 km WSW of Ciudad Sandino, Nicaragua earthquake 7.91
23 61 km SW of Modisi, Indonesia earthquake 7.12
24 Balleny Islands region earthquake 13.36
25 central East Pacific Rise earthquake 13.58
depthError magError magNst status locationSource magSource
0 1.871 0.098 10 reviewed us us
1 5.555 0.053 34 reviewed us us
2 1.888 0.083 14 reviewed us us
3 1.874 0.103 9 reviewed us us
4 4.734 0.048 41 reviewed us us
5 1.840 0.093 11 reviewed us us
6 5.489 0.071 19 reviewed us us
7 1.842 0.065 23 reviewed us us
8 1.837 0.098 10 reviewed us us
9 5.671 0.065 23 reviewed us us
10 7.857 0.078 53 reviewed us us
11 5.150 0.069 20 reviewed us us
12 6.586 0.048 41 reviewed us us
13 7.646 0.067 75 reviewed us us
14 7.928 0.108 28 reviewed us us
15 6.488 0.080 50 reviewed us us
16 1.881 0.052 35 reviewed us us
17 1.905 0.086 43 reviewed us us
18 6.011 0.056 31 reviewed us us
19 1.727 0.050 39 reviewed us us
20 1.723 0.073 18 reviewed us us
21 4.886 0.073 18 reviewed us us
22 5.336 0.056 31 reviewed us us
23 6.309 0.062 25 reviewed us us
24 1.958 0.146 15 reviewed us us
25 1.844 0.036 242 reviewed us us
[26 rows x 22 columns]
Ok, this looks cute, there is actually data in here :) We can also output the columns of the data for our reference, to know what information is available in the dataset.ΒΆ
InΒ [33]:
data.columns
Out[33]:
Index(['time', 'latitude', 'longitude', 'depth', 'mag', 'magType', 'nst',
'gap', 'dmin', 'rms', 'net', 'id', 'updated', 'place', 'type',
'horizontalError', 'depthError', 'magError', 'magNst', 'status',
'locationSource', 'magSource'],
dtype='object')
VisualizationΒΆ
Let's first plot a simple 1D graph.ΒΆ
I have no idea how to use pandas to plot, so let's start with something super simple. The data is a time series of earthquakes, so maybe plot the magnitudes of the earthquakes over time? We could use the 'time' column and the 'mag' column.ΒΆ
I'm not sure how the code will look like in the end, but it must be something of the form:ΒΆ
plot(x, y)
or better say:ΒΆ
plot(time, mag)
Let's try to create the variables for time and data_values first, we'll figure out the plotting shenanigans later. Seems like pandas allows to reference the column directly by its name and not an index, which is actually quite easy.ΒΆ
InΒ [34]:
data['time']
Out[34]:
0 2025-11-24T02:21:02.756Z 1 2025-11-23T08:21:46.286Z 2 2025-11-23T04:39:22.232Z 3 2025-11-23T03:19:55.726Z 4 2025-11-22T18:41:19.405Z 5 2025-11-21T22:38:09.124Z 6 2025-11-21T12:37:59.558Z 7 2025-11-21T12:22:03.648Z 8 2025-11-21T04:38:26.465Z 9 2025-11-20T23:36:07.248Z 10 2025-11-20T15:55:45.358Z 11 2025-11-20T12:50:02.013Z 12 2025-11-20T06:59:44.153Z 13 2025-11-20T04:19:54.753Z 14 2025-11-20T01:06:49.304Z 15 2025-11-19T21:45:22.218Z 16 2025-11-19T06:39:01.963Z 17 2025-11-19T02:23:19.189Z 18 2025-11-19T01:38:53.994Z 19 2025-11-18T22:36:32.878Z 20 2025-11-18T15:35:29.949Z 21 2025-11-17T19:06:35.519Z 22 2025-11-17T17:23:03.682Z 23 2025-11-17T12:12:37.800Z 24 2025-11-17T09:48:31.919Z 25 2025-11-17T00:46:17.538Z Name: time, dtype: object
Boom! There you go. Ok, so the format I'm seeing here has numbers and letters, which tells me it is a string. Let's check it.ΒΆ
InΒ [35]:
type(data['time'])
Out[35]:
pandas.core.series.Series
Aha, according to the pandas reference, you can use the apply method, and pass whatever function you want to use... Let's see if this works...ΒΆ
InΒ [36]:
data['time'].apply(type)
Out[36]:
0 <class 'str'> 1 <class 'str'> 2 <class 'str'> 3 <class 'str'> 4 <class 'str'> 5 <class 'str'> 6 <class 'str'> 7 <class 'str'> 8 <class 'str'> 9 <class 'str'> 10 <class 'str'> 11 <class 'str'> 12 <class 'str'> 13 <class 'str'> 14 <class 'str'> 15 <class 'str'> 16 <class 'str'> 17 <class 'str'> 18 <class 'str'> 19 <class 'str'> 20 <class 'str'> 21 <class 'str'> 22 <class 'str'> 23 <class 'str'> 24 <class 'str'> 25 <class 'str'> Name: time, dtype: object
Alright! So these are actually strings... Well, string is not a good variable to work with, at least not for time series. Let's convert it to something more useful - one would usually use the datetime type. We'll conver the time columnt using the to_datetime() method. We probably should be able to write the converted column to the table, using the same "indexing" with the square brackets and column name. Let's call the new column cute_datetime.ΒΆ
InΒ [45]:
data['cute_datetime'] = pd.to_datetime(data['time'])
data
Out[45]:
| time | latitude | longitude | depth | mag | magType | nst | gap | dmin | rms | ... | place | type | horizontalError | depthError | magError | magNst | status | locationSource | magSource | cute_datetime | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2025-11-24T02:21:02.756Z | 7.4819 | -37.0268 | 10.000 | 5.4 | mww | 82 | 51 | 13.268 | 1.53 | ... | central Mid-Atlantic Ridge | earthquake | 10.61 | 1.871 | 0.098 | 10 | reviewed | us | us | 2025-11-24 02:21:02.756000+00:00 |
| 1 | 2025-11-23T08:21:46.286Z | 51.4112 | -177.0684 | 54.820 | 5.5 | mww | 109 | 92 | 0.366 | 0.92 | ... | 59 km SSW of Adak, Alaska | earthquake | 4.40 | 5.555 | 0.053 | 34 | reviewed | us | us | 2025-11-23 08:21:46.286000+00:00 |
| 2 | 2025-11-23T04:39:22.232Z | 13.3402 | 95.8348 | 10.000 | 5.3 | mww | 48 | 93 | 3.517 | 1.15 | ... | 267 km WSW of Dawei, Burma (Myanmar) | earthquake | 9.93 | 1.888 | 0.083 | 14 | reviewed | us | us | 2025-11-23 04:39:22.232000+00:00 |
| 3 | 2025-11-23T03:19:55.726Z | 1.2749 | 128.5695 | 10.000 | 5.4 | mww | 61 | 84 | 3.427 | 1.26 | ... | 70 km NNE of Maba, Indonesia | earthquake | 7.27 | 1.874 | 0.103 | 9 | reviewed | us | us | 2025-11-23 03:19:55.726000+00:00 |
| 4 | 2025-11-22T18:41:19.405Z | 8.7615 | -84.1029 | 8.031 | 5.1 | mww | 158 | 136 | 0.978 | 0.84 | ... | 67 km WSW of Puerto CortΓ©s, Costa Rica | earthquake | 7.62 | 4.734 | 0.048 | 41 | reviewed | us | us | 2025-11-22 18:41:19.405000+00:00 |
| 5 | 2025-11-21T22:38:09.124Z | 49.6155 | 158.6388 | 10.000 | 5.7 | mww | 95 | 74 | 3.547 | 0.96 | ... | 214 km SE of Severo-Kurilβsk, Russia | earthquake | 8.31 | 1.840 | 0.093 | 11 | reviewed | us | us | 2025-11-21 22:38:09.124000+00:00 |
| 6 | 2025-11-21T12:37:59.558Z | -24.2360 | 179.9218 | 519.151 | 5.6 | mww | 111 | 22 | 6.690 | 0.91 | ... | south of the Fiji Islands | earthquake | 11.76 | 5.489 | 0.071 | 19 | reviewed | us | us | 2025-11-21 12:37:59.558000+00:00 |
| 7 | 2025-11-21T12:22:03.648Z | 39.6282 | 143.5446 | 10.000 | 5.5 | mww | 98 | 99 | 2.225 | 0.72 | ... | 137 km E of Miyako, Japan | earthquake | 3.25 | 1.842 | 0.065 | 23 | reviewed | us | us | 2025-11-21 12:22:03.648000+00:00 |
| 8 | 2025-11-21T04:38:26.465Z | 23.8942 | 90.5788 | 10.000 | 5.5 | mww | 101 | 49 | 4.820 | 0.89 | ... | 14 km WSW of Narsingdi, Bangladesh | earthquake | 8.62 | 1.837 | 0.098 | 10 | reviewed | us | us | 2025-11-21 04:38:26.465000+00:00 |
| 9 | 2025-11-20T23:36:07.248Z | 25.6353 | 124.8355 | 120.250 | 5.1 | mww | 91 | 37 | 1.364 | 0.68 | ... | 103 km NNW of Hirara, Japan | earthquake | 7.05 | 5.671 | 0.065 | 23 | reviewed | us | us | 2025-11-20 23:36:07.248000+00:00 |
| 10 | 2025-11-20T15:55:45.358Z | -11.5975 | 164.5887 | 44.810 | 5.0 | mb | 28 | 111 | 5.043 | 0.78 | ... | 163 km SW of Lata, Solomon Islands | earthquake | 11.62 | 7.857 | 0.078 | 53 | reviewed | us | us | 2025-11-20 15:55:45.358000+00:00 |
| 11 | 2025-11-20T12:50:02.013Z | -25.3510 | -176.5576 | 96.408 | 5.4 | mww | 70 | 126 | 8.757 | 1.17 | ... | south of the Fiji Islands | earthquake | 10.53 | 5.150 | 0.069 | 20 | reviewed | us | us | 2025-11-20 12:50:02.013000+00:00 |
| 12 | 2025-11-20T06:59:44.153Z | -3.7031 | 128.3705 | 128.344 | 5.8 | mww | 84 | 42 | 1.733 | 0.93 | ... | 20 km E of Ambon, Indonesia | earthquake | 7.63 | 6.586 | 0.048 | 41 | reviewed | us | us | 2025-11-20 06:59:44.153000+00:00 |
| 13 | 2025-11-20T04:19:54.753Z | 1.5789 | 127.1375 | 136.935 | 5.4 | mb | 52 | 79 | 3.544 | 0.83 | ... | 91 km NNW of Ternate, Indonesia | earthquake | 8.16 | 7.646 | 0.067 | 75 | reviewed | us | us | 2025-11-20 04:19:54.753000+00:00 |
| 14 | 2025-11-20T01:06:49.304Z | -31.5946 | -178.7643 | 114.275 | 5.1 | mb | 33 | 93 | 6.424 | 0.79 | ... | Kermadec Islands region | earthquake | 12.84 | 7.928 | 0.108 | 28 | reviewed | us | us | 2025-11-20 01:06:49.304000+00:00 |
| 15 | 2025-11-19T21:45:22.218Z | -57.9631 | -25.1924 | 39.527 | 5.0 | mb | 43 | 69 | 7.309 | 0.68 | ... | South Sandwich Islands region | earthquake | 11.36 | 6.488 | 0.080 | 50 | reviewed | us | us | 2025-11-19 21:45:22.218000+00:00 |
| 16 | 2025-11-19T06:39:01.963Z | -37.8809 | -75.1065 | 10.000 | 5.0 | mww | 50 | 154 | 2.388 | 1.16 | ... | 131 km WSW of Lebu, Chile | earthquake | 4.44 | 1.881 | 0.052 | 35 | reviewed | us | us | 2025-11-19 06:39:01.963000+00:00 |
| 17 | 2025-11-19T02:23:19.189Z | 39.2352 | 143.4678 | 10.000 | 5.0 | mb | 38 | 138 | 2.411 | 1.06 | ... | 133 km ESE of Yamada, Japan | earthquake | 8.85 | 1.905 | 0.086 | 43 | reviewed | us | us | 2025-11-19 02:23:19.189000+00:00 |
| 18 | 2025-11-19T01:38:53.994Z | 10.8709 | -62.7566 | 122.975 | 5.0 | mww | 65 | 92 | 4.144 | 1.20 | ... | 34 km NNE of Yaguaraparo, Venezuela | earthquake | 11.10 | 6.011 | 0.056 | 31 | reviewed | us | us | 2025-11-19 01:38:53.994000+00:00 |
| 19 | 2025-11-18T22:36:32.878Z | 8.7898 | -84.0158 | 10.000 | 5.4 | mww | 82 | 84 | 0.946 | 0.76 | ... | 57 km WSW of Puerto CortΓ©s, Costa Rica | earthquake | 5.34 | 1.727 | 0.050 | 39 | reviewed | us | us | 2025-11-18 22:36:32.878000+00:00 |
| 20 | 2025-11-18T15:35:29.949Z | -11.9799 | 65.7397 | 10.000 | 5.3 | mww | 240 | 31 | 8.023 | 0.54 | ... | Mid-Indian Ridge | earthquake | 2.83 | 1.723 | 0.073 | 18 | reviewed | us | us | 2025-11-18 15:35:29.949000+00:00 |
| 21 | 2025-11-17T19:06:35.519Z | 39.7568 | 143.1957 | 33.331 | 5.2 | mww | 98 | 85 | 1.929 | 0.55 | ... | 108 km E of Miyako, Japan | earthquake | 7.19 | 4.886 | 0.073 | 18 | reviewed | us | us | 2025-11-17 19:06:35.519000+00:00 |
| 22 | 2025-11-17T17:23:03.682Z | 12.1277 | -86.4514 | 128.024 | 5.3 | mww | 66 | 126 | 0.793 | 1.18 | ... | 12 km WSW of Ciudad Sandino, Nicaragua | earthquake | 7.91 | 5.336 | 0.056 | 31 | reviewed | us | us | 2025-11-17 17:23:03.682000+00:00 |
| 23 | 2025-11-17T12:12:37.800Z | 0.1060 | 124.0046 | 119.483 | 5.3 | mww | 116 | 25 | 3.374 | 1.02 | ... | 61 km SW of Modisi, Indonesia | earthquake | 7.12 | 6.309 | 0.062 | 25 | reviewed | us | us | 2025-11-17 12:12:37.800000+00:00 |
| 24 | 2025-11-17T09:48:31.919Z | -61.1895 | 159.6597 | 10.000 | 5.0 | mb | 16 | 142 | 6.721 | 0.47 | ... | Balleny Islands region | earthquake | 13.36 | 1.958 | 0.146 | 15 | reviewed | us | us | 2025-11-17 09:48:31.919000+00:00 |
| 25 | 2025-11-17T00:46:17.538Z | -11.0885 | -106.3645 | 10.000 | 5.0 | mb | 72 | 86 | 16.197 | 0.56 | ... | central East Pacific Rise | earthquake | 13.58 | 1.844 | 0.036 | 242 | reviewed | us | us | 2025-11-17 00:46:17.538000+00:00 |
26 rows Γ 23 columns
Okay! This works. These look more like timestamps, with date and time. Also, I just accidentally discovered that if you don't useΒΆ
print(data)
But actually run justΒΆ
data
Then Jupyter shows you a much more user-friendly version of the table. If we scroll to the very right we see our newly added column there.ΒΆ
Also, can I use square brackets to index a single value from the series?ΒΆ
InΒ [38]:
data['datetime'][1]
Out[38]:
Timestamp('2025-11-23 08:21:46.286000+0000', tz='UTC')
Beautiful! Ok, let's look at the magnitudes.ΒΆ
InΒ [39]:
data['mag'][:5]
Out[39]:
0 5.4 1 5.5 2 5.3 3 5.4 4 5.1 Name: mag, dtype: float64
Nice! We can slice the indices just like we have learned before. Also, these are floats, as the type says. I think we can try to plot this now.ΒΆ
InΒ [52]:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(data['cute_datetime'], data['mag'])
plt.show()
Alrighty! Looks like the data we wanted to see - earthquakes stronger than 5, as they happened over the last 7 days.ΒΆ
The data does make sense, but the plot looks ugly, let's look at the reference of matplotlib to see how we can make it nicer. Let's try adding labels to the x- and y- axis, and a title to the overall plot.ΒΆ
InΒ [87]:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(data['cute_datetime'], data['mag'])
plt.xlabel('Time') # Add x-label
plt.ylabel('Magnitude') # Add y-label
plt.title('Earthquake magnitudes over the last 7 days') # Add title
plt.show()
Yep, better now. But that ticks on the time axis are still horrible, they all overlap. Can we rotate them?ΒΆ
InΒ [89]:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(data['cute_datetime'], data['mag'])
plt.xlabel('Time')
plt.ylabel('Magnitude')
plt.title('Earthquake magnitudes over the last 7 days')
plt.xticks(rotation=45) # los rotaΓ§iones
plt.show()
Ok, much better now! One last touch - need a better color here.ΒΆ
InΒ [58]:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(data['cute_datetime'], data['mag'], color='m') # mmmmmmmagenta!
plt.xlabel('Time')
plt.ylabel('Magnitude')
plt.title('Earthquake magnitudes over the last 7 days')
plt.xticks(rotation=45)
plt.show()
Boom! There you have it. M for magenta. I'm happy with this so far. Now let's attempt.. SOMETHING BIGGER π muaahahahaΒΆ
InΒ [60]:
pip install folium
Requirement already satisfied: folium in /opt/conda/lib/python3.13/site-packages (0.20.0) Requirement already satisfied: branca>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from folium) (0.8.2) Requirement already satisfied: jinja2>=2.9 in /opt/conda/lib/python3.13/site-packages (from folium) (3.1.6) Requirement already satisfied: numpy in /opt/conda/lib/python3.13/site-packages (from folium) (2.3.3) Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from folium) (2.32.5) Requirement already satisfied: xyzservices in /opt/conda/lib/python3.13/site-packages (from folium) (2025.4.0) Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2>=2.9->folium) (3.0.3) Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->folium) (3.4.4) Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->folium) (3.11) Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->folium) (2.5.0) Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->folium) (2025.10.5) Note: you may need to restart the kernel to use updated packages.
Okie-dokie. Whenever making something difficult, we always build up from something simple, adding complexity step-by-step. Otherwise if we write a ton of code and it doesn't work in the end - we have no idea why. So let's start by displaying an empty map.ΒΆ
InΒ [64]:
import folium
cute_map = folium.Map() # Create an empty map
cute_map.save('earthquakes_cute_basic_map.html') # Save it
cute_map # Show it
Out[64]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Aha, mhm! Let's understand how to add markers here.ΒΆ
It seems that we can use the .CircleMarker() method to create a marker cluster with our required properties (location, radius, etc.) and add it to the map using the .add_to() method:ΒΆ
folium.CircleMarker(*some properties).add_to(cute_map)
Let's try adding one marker first.ΒΆ
InΒ [86]:
import folium
cute_map = folium.Map()
folium.CircleMarker(location = [0, 0], radius = 5).add_to(cute_map) # Adding a marker at location 0,0, with radius 5
cute_map.save('earthquakes_cute_basic_map.html')
cute_map
Out[86]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Would you look at that! A cute little circle. How about we iterate through the whole table and repeat this command and get a bunch of actual markers! We can replace the [0, 0] in the location property with entries from the table, but then the code gets a bit cluttered. Instead, let's create variables right before that and put variables into the CircleMarker to keep the code clean. Pure aesthetics, but aesthetics nevertheless π€ΒΆ
InΒ [84]:
import folium
cute_map = folium.Map()
for i in range(len(data)):
lat = data['latitude'][i]
lon = data['longitude'][i]
folium.CircleMarker(location = [lat, lon], radius = 5).add_to(cute_map)
cute_map.save('earthquakes_cute_basic_map.html')
cute_map
Out[84]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Cuteness! Now let's get creative. How about we scale the radius with the magnitude? Also I want the circles to be filled.ΒΆ
InΒ [180]:
import folium
cute_map = folium.Map()
for i in range(len(data)):
lat = data['latitude'][i]
lon = data['longitude'][i]
mag = 10*(data['mag'][i]-4) # 10* and -4 looks like random voodoo magic, because it is.
folium.CircleMarker(location = [lat, lon],
radius = mag,
fill = True
).add_to(cute_map)
cute_map.save('earthquakes_cute_basic_map.html')
cute_map
Out[180]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Speaking of aesthetics... Let's remove the circles and only leave the insides for a flatter look, and adjust the opacity a bit... We can also use an if-statement to change the color based on how strong the earthquake was - this sould make the visualization much more telling πΒΆ
InΒ [184]:
import folium
cute_map = folium.Map()
for i in range(len(data)):
lat = data['latitude'][i]
lon = data['longitude'][i]
mag = 10*(data['mag'][i]-4)
# Conditional structure to change the color based on magnitude
if data['mag'][i] < 5.3:
color = 'green'
elif data['mag'][i] < 5.6:
color = 'yellow'
else:
color = 'red'
folium.CircleMarker(location = [lat, lon],
radius = mag,
fill = True,
color = None,
fill_color = color,
fill_opacity = 0.4
).add_to(cute_map)
cute_map.save('earthquakes_cute_basic_map.html')
cute_map
Out[184]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Oooha, this starts to look like something now! How about we import the data with no filters (we filtered above magnitude 5 before, let's get the full data with all earthquakes now). Also, let's do some GPT magic to give us a continuous color map based on magnitude, instead of 3 discrete colors we used in the if-statement.ΒΆ
InΒ [185]:
import folium
import matplotlib.colors as mcolors
data_full = pd.read_csv('datasets/earthquakes_no_filter.csv')
# GPT magic for a continuous colormap from green to yellow to red - no idea what's happening here.
cmap = mcolors.LinearSegmentedColormap.from_list("green_yellow_red", ['green', 'yellow', 'red'])
norm = mcolors.Normalize(vmin=0, vmax=8)
cute_map = folium.Map()
for i in range(len(data_full)):
lat = data_full['latitude'][i]
lon = data_full['longitude'][i]
mag = data_full['mag'][i]
color = mcolors.to_hex(cmap(norm(mag))) # Continuation of the color magic
folium.CircleMarker(location = [lat, lon],
radius = 3*mag,
fill = True,
color = None,
fill_color = color,
fill_opacity = 0.8
).add_to(cute_map)
cute_map.save('earthquakes_cute_basic_map.html')
cute_map
Out[185]:
Make this Notebook Trusted to load map: File -> Trust Notebook