Quickstart#

This example loads the IMD best-track record, explores the tidy tables, plots a cyclone track, and shows the xarray view. It runs end-to-end when the docs are built, so every output below is real.

import imdtrack as imd

bt = imd.load()
bt
<BestTracks: 428 storms, 7571 fixes, 1982-2026>

The tidy observations frame#

One row per 3-hourly fix, with a stable storm_id ("<year>-<serial>").

bt.observations.head()
storm_id year serial basin name time lat lon ci_no pressure wind pressure_drop oci oci_diameter grade step pos_suspect date_suspect
0 1982-001 1982 1 BOB NaN 1982-05-01 03:00:00 14.0 82.5 1.5 NaN 25.0 3.0 NaN NaN D 0 False False
1 1982-001 1982 1 BOB NaN 1982-05-01 06:00:00 14.5 82.5 2.0 NaN 30.0 5.0 NaN NaN DD 1 False False
2 1982-001 1982 1 BOB NaN 1982-05-01 12:00:00 15.0 82.0 2.5 NaN 35.0 6.0 NaN NaN CS 2 False False
3 1982-001 1982 1 BOB NaN 1982-05-01 18:00:00 15.5 82.5 2.5 NaN 40.0 8.0 NaN NaN CS 3 False False
4 1982-001 1982 1 BOB NaN 1982-05-02 00:00:00 16.0 83.0 3.0 NaN 45.0 10.0 NaN NaN CS 4 False False

Storm-level summary#

One row per storm: peak grade, max wind, min pressure, start/end time.

bt.storms.tail()
storm_id year serial name basin start_time end_time n_obs max_wind min_pressure peak_grade
423 2025-012 2025 12 NaN ARB 2025-10-22 00:00:00 2025-10-31 18:00:00 50 25.0 1000.0 D
424 2025-013 2025 13 MONTHA BOB 2025-10-25 00:00:00 2025-10-29 18:00:00 32 50.0 990.0 SCS
425 2025-014 2025 14 SENYAR BOB 2025-11-25 03:00:00 2025-11-27 15:00:00 17 40.0 999.0 CS
426 2025-015 2025 15 DITWAH BOB 2025-11-26 18:00:00 2025-12-02 18:00:00 41 40.0 1000.0 CS
427 2026-001 2026 1 NaN BOB 2026-01-07 03:00:00 2026-01-10 12:00:00 18 30.0 1004.0 DD

Plot a cyclone track#

The optional [plot] extra (pip install imdtrack[plot]) draws publication-quality Cartopy maps — the track is coloured by IMD category, with genesis/peak/end marked. Look storms up by id or name:

imd.plot_track(bt.storm("tauktae"), color="grade", annotate=True)
<GeoAxes: >
../_images/9b4c26da2e4b0840a89123999cb5e426e03e4de70a9a827310505b35358d0d8a.png

…or colour by wind speed instead (the title is generated from the storm):

imd.plot_track(bt.storm("2020-001"), color="wind")
<GeoAxes: >
../_images/794ce089bf02db66a36e73d0cbcf920b3254598d0d3b0b18b451158f817daf4e.png

The xarray Dataset#

A ragged track becomes a 2-D (storm, step) grid, IBTrACS-style.

ds = bt.to_xarray()
ds
<xarray.Dataset> Size: 3MB
Dimensions:        (storm: 428, step: 97)
Coordinates:
  * storm          (storm) object 3kB '1982-001' '1982-002' ... '2026-001'
    name           (storm) object 3kB '' '' '' '' ... 'SENYAR' 'DITWAH' ''
    basin          (storm) object 3kB 'BOB' 'BOB' 'BOB' ... 'BOB' 'BOB' 'BOB'
    year           (storm) int64 3kB 1982 1982 1982 1982 ... 2025 2025 2025 2026
    serial         (storm) int64 3kB 1 2 3 4 5 6 7 8 9 ... 9 10 11 12 13 14 15 1
    peak_grade     (storm) object 3kB 'VSCS' 'VSCS' 'DD' 'D' ... 'CS' 'CS' 'DD'
  * step           (step) int64 776B 0 1 2 3 4 5 6 7 ... 89 90 91 92 93 94 95 96
Data variables:
    time           (storm, step) datetime64[ns] 332kB 1982-05-01T03:00:00 ......
    lat            (storm, step) float64 332kB 14.0 14.5 15.0 ... nan nan nan
    lon            (storm, step) float64 332kB 82.5 82.5 82.0 ... nan nan nan
    ci_no          (storm, step) float64 332kB 1.5 2.0 2.5 2.5 ... nan nan nan
    pressure       (storm, step) float64 332kB nan nan nan nan ... nan nan nan
    wind           (storm, step) float64 332kB 25.0 30.0 35.0 ... nan nan nan
    pressure_drop  (storm, step) float64 332kB 3.0 5.0 6.0 8.0 ... nan nan nan
    grade          (storm, step) object 332kB 'D' 'DD' 'CS' 'CS' ... '' '' '' ''
    oci            (storm, step) float64 332kB nan nan nan nan ... nan nan nan
    oci_diameter   (storm, step) float64 332kB nan nan nan nan ... nan nan nan
Attributes:
    title:              IMD RSMC New Delhi best track data
    source:             India Meteorological Department (rsmcnewdelhi.imd.gov...
    featureType:        trajectory
    n_storms:           428
    grade_definitions:  D=Depression; DD=Deep Depression; CS=Cyclonic Storm; ...

Slice one storm and get its peak intensity:

ds.sel(storm="2020-001")["wind"].max().item()
130.0

Data quality#

The dataset mirrors the IMD workbook faithfully, including its occasional data-entry errors, which are flagged non-destructively: pos_suspect marks fixes whose coordinates imply an impossible jump, and date_suspect marks day/month-transposed dates. Inspect them, or clean them on demand:

flagged = bt.observations.query("pos_suspect or date_suspect")
print(
    f"{len(flagged)} flagged fixes "
    f"({int(bt.observations['pos_suspect'].sum())} position, "
    f"{int(bt.observations['date_suspect'].sum())} date)"
)
105 flagged fixes (2 position, 103 date)
# Drop the coordinate spikes and swap day/month-transposed dates back into order.
clean = imd.load().clean(fix_dates=True)
clean
<BestTracks: 428 storms, 7569 fixes, 1982-2026>