Main ENSO indices for Pacific Ocean

R
tutorial

Updated visualization and extraction of the four main ENSO-related indices | Visualización actualizada y extracción de 4 principales índices relacionados al ENSO

Published

September 12, 2026

Keywords

enso, environmental, indices, R, scripts, tutorial, PDO, ICEN, MEI, TPI

Image credits: MEI index at NOAA

Required R packages: Paquetes de R necesarios:

require(dplyr)
require(tidyr)
require(plotly)

ICEN (Índice Costero El Niño/EL Niño Coastal Index)

Reference: https://siofen.imarpe.gob.pe/nivel2/indice-costero-el-nino-icen

icen <- read.table(
  file = "http://met.igp.gob.pe/datos/ICEN.txt", 
  skip = 5,
  header = FALSE
) |> 
  
  as_tibble() |> 
  
  rename(año = V1, mes = V2, ICEN = V3) |> 
  
  mutate(
    date = paste(año, mes, 1, sep = "-") |> as.Date(),
    ICEN_c = cut(
      x = ICEN,
      breaks = c(-Inf, -1.3, -1.1, -.7, .5, 1.3, 2.1, 3.5, Inf),
      labels = c(
        "Niña Fuerte", "Niña Moderada", "Niña Débil", 
        "Neutro",
        "Niño Débil", "Niño Moderado", "Niño Fuerte", "Niño Extraordinario"
      )
    )
  )

NOAA indices

Reference: https://psl.noaa.gov/data/timeseries/month/

[EN]: NOAA website keep a catalogue of several environmental indices, most of them up to 2026. Through get_envir_data function, it is possible to read and load any of them as a data table (tibble object).

[ES]: En el sitio web de NOAA se hallan disponibles diversos índices actualizados (la mayoría) al 2026. A través de la función get_envir_data es posible leer los valores directamente a R a manera de una tabla (objeto tibble).

get_envir_data <- \(con, index_name = "index", metadata = TRUE, quiet = FALSE){
  
  if(isFALSE(quiet)) sprintf("%s", con) |> cli::cli_progress_step()
  
  sourceLines <- readLines(con = con)
  
  yrsRange <- strsplit(x = sourceLines[1], split = "[[:blank:]]+") |> 
    
    unlist() |> as.numeric() |> na.omit()
  
  rowIndex <- abs(diff(nchar(sourceLines))/nchar(sourceLines)[-length(sourceLines)])*100
  rowIndex <- which(rowIndex > 80)[1:2] + c(1, 0)
  
  naVals <- sourceLines[rowIndex[2] + 1] |> as.numeric() |> as.character()
  
  # Reading data
  envirIndex <- read.table(
    file = con, 
    header = FALSE, 
    nrows = diff(rowIndex),
    skip = rowIndex[1], 
    colClasses = "character"
  ) |> 
    
    as_tibble() |> 
    
    rename(year = V1) |> 
    
    rename_with(.cols = -year, .fn = gsub, pattern = "V", replacement = "") |> 
    
    pivot_longer(
      cols = -year,
      names_to = "month", 
      names_transform = \(x) as.integer(x) - 1,
      values_to = "index"
    ) |> 
    
    mutate(
      year = as.integer(year),
      index = as.numeric(index) |> as.character(),
      index = replace(
        x = index,
        list = index == naVals,
        values = NA
      ),
      index = as.numeric(index)
    ) |> 
    
    filter(!is.na(index)) |> 
    
    rename(!!index_name := index) 
  
  if(isTRUE(metadata)){
    metadata <- sourceLines[-seq(from = rowIndex[1], to = rowIndex[2])] |> 
        
        gsub(pattern = "(^[[:blank:]]*)|([[:blank:]]*$)", replacement = "")

    envirIndex <- list(data = envirIndex, metadata = metadata)
  }
  
  envirIndex
}

Niño 1+2 (HadISST)

elnino_12_hadisst <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/nino12.long.anom.data", 
  index_name = "elnino_12_hadisst",
  metadata = FALSE,
  quiet = TRUE
)

Niño 1+2 (ERSSTv6)

elnino_12_ersstv6 <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/nina1.anom.data", 
  index_name = "elnino_12_ersstv6",
  metadata = FALSE,
  quiet = TRUE
)

Niño 3 (HadISST)

elnino_3_hadisst11 <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/nino3.long.anom.data", 
  index_name = "elnino_3_hadisst11",
  metadata = FALSE,
  quiet = TRUE
)

Niño 3 (ERSSTv6)

elnino_3_ersstv6 <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/nina3.anom.data", 
  index_name = "elnino_3_ersstv6",
  metadata = FALSE,
  quiet = TRUE
)

Niño 3.4 (HadISST)

elnino_34_hadisst11 <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/nino34.long.anom.data", 
  index_name = "elnino_34_hadisst11",
  metadata = FALSE,
  quiet = TRUE
)

Niño 3.4 (ERSSTv6)

elnino_34_ersstv6 <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/nina34.anom.data", 
  index_name = "elnino_34_ersstv6",
  metadata = FALSE,
  quiet = TRUE
)

Niño 4 (HadISST)

elnino_4_hadisst11 <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/nino4.long.anom.data", 
  index_name = "elnino_4_hadisst11",
  metadata = FALSE,
  quiet = TRUE
)

Niño 4 (ERSSTv6)

elnino_4_ersstv6 <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/nina4.anom.data", 
  index_name = "elnino_4_ersstv6",
  metadata = FALSE,
  quiet = TRUE
)

Multivariate ENSO Index (MEI Extended)

mei_ext <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/mei.ext.long.data", 
  index_name = "mei_ext",
  metadata = FALSE,
  quiet = TRUE
)

Oceanic Niño Index (ONI)

oni <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/oni.data", 
  index_name = "oni",
  metadata = FALSE,
  quiet = TRUE
)

Relative Oceanic Niño Index (RONI)

roni <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/roni.data", 
  index_name = "roni",
  metadata = FALSE,
  quiet = TRUE
)

Central Tropical Pacific OLR Index

olr <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/olr.data", 
  index_name = "olr",
  metadata = FALSE,
  quiet = TRUE
)

Pacific Warm Pool

pac_warmpool <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/pacwarmpool.ersst.data", 
  index_name = "pac_warmpool",
  metadata = FALSE,
  quiet = TRUE
)

Trans-Niño Index (TNI)

tni <- get_envir_data(
  con = "https://psl.noaa.gov/data/timeseries/month/data/tni.long.data", 
  index_name = "tni",
  metadata = FALSE,
  quiet = TRUE
)

Equatorial Central Pacific Heat Content: 160E-80W

central_pac_heat_cont <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/heatcentra.data", 
  index_name = "central_pac_heat_cont",
  metadata = FALSE,
  quiet = TRUE
)

Bivariate ENSO Time-series (BEST)

best <- get_envir_data(
  con = "https://psl.noaa.gov/data/correlation/censo.data", 
  index_name = "best",
  metadata = FALSE,
  quiet = TRUE
)
Warning in readLines(con = con): incomplete final line found on
'https://psl.noaa.gov/data/correlation/censo.data'

PDO (Pacific Decadal Oscillation)

Reference: https://www.ncei.noaa.gov/access/monitoring/pdo/

pdo <- read.table(
  file = "https://www.ncei.noaa.gov/pub/data/cmb/ersst/v5/v6/index/ersst.v6.pdo.dat",
  skip = 1, header = TRUE
) |> 
  
  as_tibble() |> 
  
  pivot_longer(
    cols = -Year,
    names_to = "mes",
    values_to = "PDO"
  ) |> 
  
  rename(año = Year) |> 
  
  mutate(
    mes = seq(12)[match(x = mes, month.abb)],
    date = paste(año, mes, 1, sep = "-") |> as.Date(),
    PDO_c = cut(
      x = PDO,
      breaks = c(-Inf, 0, Inf),
      labels = c("Frío", "Cálido")
    )
  ) |> 
  
  filter(PDO < 99)

TPI (Tripole Index for the Interdecadal Pacific Oscillation)

Reference: https://psl.noaa.gov/data/timeseries/IPOTPI/

tpiURL <- "https://psl.noaa.gov/data/timeseries/IPOTPI/tpi.timeseries.ersstv5.data"

yrsRange <- read.table(
  file = tpiURL,
  header = FALSE, nrow = 1
) |> as.numeric()

tpi <- read.table(
  file = tpiURL, 
  skip = 1, 
  header = FALSE, 
  nrow = diff(yrsRange) + 1
) |> 
  
  as_tibble() |> 
  
  pivot_longer(
    cols = -V1,
    names_to = "mes",
    values_to = "TPI"
  ) |> 
  
  rename(año = V1) |> 
  
  mutate(
    mes = gsub(x = mes, pattern = "V", replacement = "") |> as.numeric(),
    mes = mes - 1,
    date = paste(año, mes, 1, sep = "-") |> as.Date(),
    TPI_c = cut(
      x = TPI,
      breaks = c(-Inf, 0, Inf),
      labels = c("Frío", "Cálido")
    )
  ) |> 
  
  filter(TPI > -99)