How to Read the Smithsonian Volcano Database in Python

On this page

Problem statement

The Smithsonian Institution's Global Volcanism Program keeps the reference list of the world's volcanoes, Volcanoes of the World: every volcano thought to have erupted in the last 11,700 years, the Holocene, with its location, type and tectonic setting, and a second table of each of their known eruptions with dates, explosivity and how each was dated. Newsrooms, hazard agencies and researchers count "active volcanoes" from it. Both tables can be read straight into GeoPandas from the program's WFS service. They join on a six-digit volcano number, and the eruption dates use codes of their own. This page reads both tables and shows what is in them.

Quick answer

Ask the WFS service for GeoJSON and read it with GeoPandas; the setup below reads a copy saved on 19 October 2026:

print(vol[["Volcano_Number", "Volcano_Name", "Country", "Primary_Volcano_Type", "Elevation", "Last_Eruption_Year"]].head(3).to_string(index=False))
print(eru[["Volcano_Name", "Eruption_Number", "Activity_Type", "ExplosivityIndexMax", "StartDateYear", "EndDateYear"]].head(3).to_string(index=False))
 Volcano_Number              Volcano_Name Country Primary_Volcano_Type  Elevation  Last_Eruption_Year
         210010 West Eifel Volcanic Field Germany       Volcanic field      600.0             -8300.0
         210020           Chaine des Puys  France         Lava dome(s)     1464.0             -4040.0
         210030       Olot Volcanic Field   Spain       Volcanic field      893.0                 NaN
 Volcano_Name  Eruption_Number      Activity_Type  ExplosivityIndexMax  StartDateYear  EndDateYear
      Vulsini            10014 Uncertain Eruption                  NaN           -104          NaN
 Colli Albani            10015 Uncertain Eruption                  NaN           -600          NaN
Campi Flegrei            10018 Confirmed Eruption                  4.0          -8480          NaN

The volcano table has one row per volcano with its six-digit Volcano_Number, and the eruption table one row per eruption with the same number. The first volcano is Germany's West Eifel field, last active in 8300 BCE, written -8300; Olot in Spain has no known eruption at all. The first eruptions are Italian and prehistoric: two uncertain ones and a confirmed VEI 4 at Campi Flegrei in 8480 BCE.

World map of 1,214 Holocene volcanoes coloured by last eruption: those active since 2000 in red around the Pacific, Indonesia, Iceland and East Africa, older and unknown ones scattered through the same belts and continental interiors. World map of 1,214 Holocene volcanoes coloured by last eruption: those active since 2000 in red around the Pacific, Indonesia, Iceland and East Africa, older and unknown ones scattered through the same belts and continental interiors.
The Holocene volcanoes by the year of their last eruption.

Two tables and a key

1. The volcano table

print(f"{len(vol):,} volcanoes, {vol.Volcano_Number.nunique():,} numbers, {vol.Country.nunique()} country values, {vol.Region.nunique()} regions")
print(vol.columns.drop(["geometry", "id"]).tolist())
1,214 volcanoes, 1,214 numbers, 88 country values, 19 regions
['Volcano_Number', 'Volcano_Name', 'Volcanic_Landform', 'Primary_Volcano_Type', 'Last_Eruption_Year', 'Country', 'Region', 'Subregion', 'Geological_Summary', 'Latitude', 'Longitude', 'Elevation', 'Tectonic_Setting', 'Geologic_Epoch', 'Evidence_Category', 'Primary_Photo_Link', 'Primary_Photo_Caption', 'Primary_Photo_Credit', 'Major_Rock_Type']

There are 1,214 volcanoes, each with a unique number, in 88 country values and 19 regions. Besides the position and elevation, the table has the volcano's landform, type, tectonic setting, dominant rock type, the category of evidence for Holocene activity, and a paragraph of geological summary. The country count includes shared values such as "Chile-Argentina", which the country page splits.

2. The eruption table

print(eru.Activity_Type.value_counts().to_string())
print(f"eruptions per volcano: median {eru.groupby('Volcano_Number').size().median():.0f}, max {eru.groupby('Volcano_Number').size().max()} ({eru.Volcano_Name.value_counts().index[0]})")
Activity_Type
Confirmed Eruption    9916
Uncertain Eruption    1173
eruptions per volcano: median 4, max 204 (Fournaise, Piton de la)

Of 11,089 eruptions, 9,916 are confirmed and 1,173 uncertain. The median volcano with eruptions has 4, and Piton de la Fournaise on RΓ©union has 204. Names are stored surname-first, "Fournaise, Piton de la", the way the program's index sorts them, so search with str.contains, not equality.

3. The join

orphans = eru[~eru.Volcano_Number.isin(vol.Volcano_Number)]
pleist = gpd.read_file(Z.open("Smithsonian_VOTW_Pleistocene_Volcanoes.json"))
print(f"eruptions whose volcano is not in the Holocene list: {len(orphans)}; of those, in the Pleistocene list: {orphans.Volcano_Number.isin(pleist.Volcano_Number).sum()}")
print(orphans.Volcano_Name.value_counts().head(3).to_string())
eruptions whose volcano is not in the Holocene list: 46; of those, in the Pleistocene list: 46
Volcano_Name
San Pedro-San Pablo                 10
Nevsehir-Acigol Volcanic Complex     5
Itasy Volcanic Field                 3

46 eruptions belong to volcanoes that are not on the Holocene list. All of them are on the Pleistocene list, which the program keeps for volcanoes without confirmed Holocene activity. An inner join on the Holocene table drops them, so if you count eruptions, count them from the eruption table.

Horizontal bars of recorded eruptions per volcano: Piton de la Fournaise 204, Asosan 187, Etna 170, Villarrica 163, Asamayama 145 and Katla 131. Horizontal bars of recorded eruptions per volcano: Piton de la Fournaise 204, Asosan 187, Etna 170, Villarrica 163, Asamayama 145 and Katla 131.
The volcanoes with the most recorded eruptions.

When each volcano last erupted

The data used on this page

The Smithsonian Institution Global Volcanism Program's Volcanoes of the World database (version 5.3 is cited as doi:10.5479/si.GVP.VOTW5-2025.5.3): its Holocene volcano and eruption tables, read from the program's WFS service on 19 October 2026 and saved on this site, because the database is edited as eruptions are reported.

from pathlib import Path
import zipfile

import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# Smithsonian Global Volcanism Program, Volcanoes of the World: Holocene volcanoes, Holocene eruptions and Pleistocene
# volcanoes as GeoJSON from its WFS service, with Natural Earth's populated places and 1:110m countries.
# Saved 19 October 2026, because the database is edited as eruptions are reported.
# Live: https://webservices.volcano.si.edu/geoserver/GVP-VOTW/ows?service=WFS&version=2.0.0&request=GetFeature
#       &outputFormat=json&typeName=GVP-VOTW:Smithsonian_VOTW_Holocene_Volcanoes (and ..._Holocene_Eruptions)
ARCHIVE = "gvp-votw-2026-10-19.zip"
if not Path(f"data/{ARCHIVE}").exists():
    Path(f"data/{ARCHIVE}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{ARCHIVE}", timeout=600).content)
Z = zipfile.ZipFile(f"data/{ARCHIVE}")
vol = gpd.read_file(Z.open("Smithsonian_VOTW_Holocene_Volcanoes.json"))
eru = gpd.read_file(Z.open("Smithsonian_VOTW_Holocene_Eruptions.json"))
print(f"Volcanoes of the World: {len(vol):,} Holocene volcanoes, {len(eru):,} eruptions")
Volcanoes of the World: 1,214 Holocene volcanoes, 11,089 eruptions

Example 1: last eruption year

conf = eru[eru.Activity_Type == "Confirmed Eruption"].groupby("Volcano_Number")
check = vol.set_index("Volcano_Number").Last_Eruption_Year.to_frame().join(conf.StartDateYear.max().rename("start")).join(conf.EndDateYear.max().rename("end"))
print(f"with a year: {check.Last_Eruption_Year.notna().sum()}; equal to the latest start: {(check.Last_Eruption_Year == check.start).sum()}; "
      f"to the latest start or end: {((check.Last_Eruption_Year == check.start) | (check.Last_Eruption_Year == check.end)).sum()}")
bins = [-1e6, 0, 1499, 1899, 1999, 2100]
labels = ["before 1 CE", "1-1499", "1500-1899", "1900-1999", "2000 on"]
print(pd.cut(vol.Last_Eruption_Year, bins, labels=labels).value_counts(sort=False).to_string(), f"\nno known eruption: {vol.Last_Eruption_Year.isna().sum()}")
with a year: 848; equal to the latest start: 703; to the latest start or end: 848
Last_Eruption_Year
before 1 CE    171
1-1499          99
1500-1899      140
1900-1999      207
2000 on        231 
no known eruption: 366

Last_Eruption_Year is not simply the start of the latest eruption: for 145 volcanoes it is the year that eruption ended. Stromboli's current eruption began in 1934, but its last eruption year is 2026, because the eruption was still going at the last report. 171 volcanoes last erupted before the Common Era, 231 have erupted since 2000, and 366 have no known eruption date, though there is evidence they were active in the Holocene.

Bars of volcanoes by last eruption: 171 before 1 CE, 99 from 1 to 1499, 140 from 1500 to 1899, 207 in the 20th century, 231 since 2000 and 366 unknown. Bars of volcanoes by last eruption: 171 before 1 CE, 99 from 1 to 1499, 140 from 1500 to 1899, 207 in the 20th century, 231 since 2000 and 366 unknown.
When each volcano last erupted.

Example 2: a map

vol["last"] = pd.cut(vol.Last_Eruption_Year, bins, labels=labels).cat.add_categories("unknown").fillna("unknown")
ax = vol.to_crs("+proj=robin").plot(column="last", markersize=4, legend=True, figsize=(12, 6))
ax.set_axis_off()
print(vol.groupby("last", observed=True).size().to_string())
last
before 1 CE    171
1-1499          99
1500-1899      140
1900-1999      207
2000 on        231
unknown        366

On the map, the volcanoes active since 2000 trace the Pacific ring of subduction zones, Indonesia and the Mid-Atlantic in Iceland. The 366 with no known eruption lie in the same belts, most of them in East Africa, the Andes, North and Central America and the northwestern Pacific. They are mostly stratovolcanoes and volcanic fields whose young deposits have not been dated.