Interactive visualization of CCHDO Bottle Data

Interactive visualization of CCHDO Bottle Data#

For an interactive version of this page please visit the Google Colab:
Open in Google Colab
(To open link in new tab press Ctrl + click)

Alternatively this notebook can be opened with Binder by following the link: Interactive visualization of CCHDO Bottle Data

Purpose

This notebook provides an interactively explore Bottle/CTD hydrographic measurements delivered via ERDDAP.

Bottle/CTD hydrography is the gold standard for detecting changes in water-mass properties, ventilation, deoxygenation, nutrient shifts and the ocean carbon system. It also provides essential calibration and validation for autonomous platforms and numerical models. By combining sensor profiles with discrete chemistry, these data enable robust assessments of variability and trends on seasonal to decadal scales, directly supporting climate and biogeochemical research.

In this notebook, the user can load a specific time window and visualize or compare physical variables (temperature, salinity, pressure) alongside biogeochemical tracers (oxygen, nutrients, CFCs, SF₆, DIC/alkalinity, pH).

Data sources

The data used in this notebook comes from the CCHDO Bottle collection served on the OCEAN ICE ERDDAP: https://er1.s4oceanice.eu/erddap/tabledap/CCHDO_Bottle.html

CCHDO Bottle data are discrete water samples collected at specific depths using a CTD/rosette system . This instrument profiles conductivity–temperature–depth while triggering Niskin bottles to capture water for laboratory analyses. The combination of high-frequency CTD sensor records with lab-quality measurements of oxygen, nutrients (nitrate, nitrite, phosphate, silicate, ammonium), carbon system variables (DIC, total alkalinity, pH) and transient tracers (CFCs, SF₆) makes this dataset the benchmark for water-mass characterization and carbon-cycle diagnostics.

These data are curated by the CLIVAR & Carbon Hydrographic Data Office (CCHDO) at Scripps, which serves as the data assembly center for global repeat hydrography programs (WOCE, CLIVAR, GO-SHIP. CCHDO provides standardized, high.quality cruise datasets enabilng long-term, climate-grade analyses of ocean change. The office currently stewards data from thousands of cruises across dozen of countries, underpinning much of the ship-based hydrographic scence community.

Instructions for using this Notebook

To use this interactive notebook, simply run each code cell by clicking the Play button (▶️) on the left side of each grey code block. This ensure all features function correctly.

Explaining the code

1. Install required libraries

This first block loads the necessary Python packages to access and visualize chemical and physical oceanography data from the CCHDO Bottle database.

Libraries include:

# @title
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import (
    FloatSlider,
    Text,
    HBox,
    Layout,
    Output,
    VBox,
    HBox,
    HTML,
    Label,
    Dropdown
)

api_url = 'https://er1.s4oceanice.eu/erddap/tabledap/CCHDO_Bottle.csv?time%2Cdepth%2Cpressure%2Cctd_temperature%2Cctd_salinity%2Cbottle_salinity%2Cctd_oxygen%2Coxygen%2Csilicate%2Cammonium%2Cnitrate%2Cnitrite%2Cphosphate%2Ccfc_11%2Ccfc_12%2Ccfc_113%2Csulfur_hexifluoride%2Ctotal_carbon%2Ctotal_alkalinity%2Cph_total_h_scale%2Cph_temperature%2Cctd_transmissometer_raw%2Cnitrous_oxide&time%3E=2024-03-21T00%3A00%3A00Z&time%3C=2024-03-28T05%3A31%3A00Z'

try:
  # Specify dtype for 'time' as string to ensure it's read correctly
  df_api = pd.read_csv(api_url, dtype={'time': str})
except Exception as e:
  print('ERROR: ', e)

units = {
    'pressure': 'dbar',
    'ctd_temperature': '°C',
    'ctd_salinity': 'PSU',
    'bottle_salinity': 'PSU',
    'ctd_oxygen': 'micromol/kg',
    'oxygen': 'micromol/kg',
    'silicate': 'micromol/kg',
    'ammonium': 'micromol/kg',
    'nitrate': 'micromol/kg',
    'nitrite': 'micromol/kg',
    'phosphate': 'micromol/kg',
    'cfc_11': 'mol/kg',
    'cfc_12': 'mol/kg',
    'cfc_113': 'mol/kg',
    'sulfur_hexifluoride': 'fmol/kg',
    'total_carbon': 'micromol/kg',
    'total_alkalinity': 'micromol/kg',
    'ph_total_h_scale': 'pH',
    'ph_temperature': '°C',
    'nitrous_oxide': 'nmol/kg'
}

2. Create variable selection dropdown

This code block generates a dropdown widget listing all available variables (excluding time and depth) to allow the user to choose which parameter to visualize.

# @title
column_dropdown = Dropdown(
    options=df_api.columns.tolist()[2:],
    disabled=False,
)

3. Plot update function and interactive visualization

This section builds the interactive plotting engine. Based on the selected variables, it formats the dataset and generates a scatter plot of the variable versus time and depth, with a color scale representing parameter values and units.

# @title
# Create an Output widget to display the plot
output_widget = Output()

def update_plot(change):
    # Replace 'Z' with '+00:00' in the 'time' column for consistent timezone format
    df_api['time'] = df_api['time'].astype(str).str.replace('Z', '+00:00')

    # Convert 'time' column to datetime objects, coercing errors
    df_api['time'] = pd.to_datetime(df_api['time'], errors='coerce')

    # Drop the first row which contains units
    df_plot = df_api.drop(0).copy()

    # Convert 'depth' column to numeric, coercing errors
    df_plot['depth'] = pd.to_numeric(df_plot['depth'], errors='coerce')

    # Remove rows with NaN values in 'depth' after coercion
    df_plot.dropna(subset=['depth'], inplace=True)

    # Get the selected column value from the dropdown
    selected_column = column_dropdown.value

    # Convert the selected column to numeric, coercing errors
    df_plot[selected_column] = pd.to_numeric(df_plot[selected_column], errors='coerce')

    # Remove rows with NaN values in 'time' or the selected column after coercion
    df_plot.dropna(subset=['time', selected_column], inplace=True)

    # Get the unit from the units dictionary
    unit = units.get(selected_column, '') # Use .get() to avoid errors if key not found

    # Clear the previous output and display the new plot within the Output widget
    with output_widget:
        output_widget.clear_output(wait=True)
        plt.figure(figsize=(12, 8))
        # Use 'time' for x-axis, 'depth' for y-axis, and selected_column for color, set marker to 's' for squares
        plt.scatter(df_plot['time'], df_plot['depth'], c=df_plot[selected_column], cmap='viridis', s=50, edgecolors='none', marker='s')

        # Set labels for the axes
        plt.xlabel('Time')
        plt.ylabel('Depth (m)')
        plt.title(f'Scatter plot of {selected_column} vs. Time and Depth')

        # Add colorbar with the correct label and unit
        cbar = plt.colorbar(label=f'{selected_column} ({unit})')

        # Explicitly set font properties for the colorbar label
        cbar.set_label(f'{selected_column} ({unit})', fontfamily='DejaVu Sans')

        plt.grid(True)
        plt.tight_layout()
        plt.show()

# Observe the dropdown for changes and update the plot
column_dropdown.observe(update_plot, names='value')

# Display the dropdown and the output widget
display(column_dropdown, output_widget)

# Initial plot display
update_plot(None)
/tmp/ipykernel_2811/175304960.py:10: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  df_api['time'] = pd.to_datetime(df_api['time'], errors='coerce')