Explorer for CTD Oceanographic Data (Temperature & Salinity)

Explorer for CTD Oceanographic Data (Temperature & Salinity)#

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: Explorer for CTD Oceanographic Data (Temperature & Salinity)

Purpose

This notebook provides an interactive tool for exploring in-situ oceanographic measurements using data served through the ERDDAP service hosted by OCEAN ICE.

Users can:

  • Select a start and end date within the observation range (2014–2023).

  • Choose key measured parameters, such as Temperature and Pratical Salinity.

  • Generate time series scatter plots to visualize patterns, variability, or anomalies in the selected data.

Data sources

This notebook uses data from the IADC (International Antarctic Data Centre) observational network. In particular, the dataset used here (https://er1.s4oceanice.eu/erddap/tabledap/IADC_s1_ctd.html) is a long-term record of Conductivity–Temperature–Depth (CTD) measurements collected by the S1 mooring, a fixed oceanographic monitoring station in the Southern Ocean at approximately 1000 meters depth.

Dataset characteristics:

  • Sensors: CTD package mounted on a deep mooring frame,continuously logging temperature and salinity at fixed depth.

  • Time span: June 9, 2014 – June 22, 2023, providing nearly a decade of uninterrupted measurements.

  • Measured variables: Sea water temperature and Practical salinity (PSU).

  • Sampling location: S1 mooring site is strategically placed to monitor deep inflow and outflow pathways near ice shelf regions, capturing water mass transformation signals.

This mooring is a fixed sentinel station that plays a pivotal role in:

  • Climate change monitoring, detecting long-term shifts in deep ocean temperature and salinity.

  • Ice–ocean interaction studies, observing changes under varying ice shelf conditions.

  • Model validation, providing high-quality reference data for numerical simulations.

  • Event detection, identifying anomalies such as warm water intrusions that could accelerate basal ice melting.

Instructions to use this Notebook

Run each code cell by clicking the Play button (▶️) on the left side of each grey code block. This will execute the code in order and allow all features to work properly.

Explaining the code

1. Import required libraries

This section sets up the for data fetching, visualization and interactivity. It also defines the ERDDAP data access parameters and the focus variables for the notebook.

**Libraries includes: **

# @title
!pip install seaborn
import requests
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt
import seaborn as sns
from io import BytesIO
from ipywidgets import (
    Text,
    HBox,
    Layout,
    Output,
    VBox,
    HTML,
    Label,
    Dropdown,
    DatePicker
)
from IPython.display import display, clear_output

time_url = f'https://er1.s4oceanice.eu/erddap/tabledap/IADC_s1_ctd.csv?time&time%3E=2014-06-09T06%3A30%3A00Z&time%3C=2023-06-22T07%3A00%3A00Z&distinct()'
variables = ['Temperature', 'sea_water_practical_salinity']
Requirement already satisfied: seaborn in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (0.13.2)
Requirement already satisfied: numpy!=1.24.0,>=1.20 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from seaborn) (1.26.3)
Requirement already satisfied: pandas>=1.2 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from seaborn) (2.1.4)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from seaborn) (3.8.2)
Requirement already satisfied: contourpy>=1.0.1 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.59.2)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.9)
Requirement already satisfied: packaging>=20.0 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0)
Requirement already satisfied: pillow>=8 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from pandas>=1.2->seaborn) (2025.2)
Requirement already satisfied: tzdata>=2022.1 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from pandas>=1.2->seaborn) (2025.2)
Requirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)

2. Retrieve available measurement dates

This section fetches the available dates from the dataset and prepares them for use in interactive widgets.

# @title
time_url = f'https://er1.s4oceanice.eu/erddap/tabledap/IADC_s1_ctd.csv?time&time%3E=2014-06-09T06%3A30%3A00Z&time%3C=2023-06-22T07%3A00%3A00Z&distinct()'
time_df = pd.read_csv(time_url)

# Convert the 'time' column to datetime objects, skipping the first row
time_df['time'] = pd.to_datetime(time_df['time'].iloc[1:])

# Extract the date part and store it in a new column
time_df['date_only'] = time_df['time'].dt.date

# Drop duplicate dates, keeping the first occurrence
time_df_unique_dates = time_df.drop_duplicates(subset=['date_only'])

# Set the time to midnight for each unique date and select only the 'time_midnight' column
time_df_unique_dates = time_df_unique_dates.assign(time_midnight=time_df_unique_dates['date_only'].apply(lambda x: pd.to_datetime(x).replace(hour=0, minute=0, second=0)))[['time_midnight']]

3. Create interactive date & variable slection controls

This section sets up:

  • Two DatePicker widgets for start and end date selection.

  • A Dropdown widget to choose a variable (Temperature or Pratical Salinity).

  • An Output widget to display the scatter plot.

The widgets allow runtime updates without rerunning the code.

# @title
# Convert the 'time_midnight' column to a list of datetime objects
valid_dates = time_df_unique_dates['time_midnight'].tolist()
# Create a DatePicker widget
date_picker = DatePicker(
    disabled=False
)

# Create another DatePicker widget
second_date_picker = DatePicker(
    disabled=False
)


# You can optionally set a default date if desired
if valid_dates and pd.notna(valid_dates[1]):
    date_picker.value = valid_dates[1].date()
    if len(valid_dates) > 2: # Set a different default for the second picker if possible
        second_date_picker.value = valid_dates[-1].date()


# Create a Dropdown widget using the 'variables' list
variable_dropdown = Dropdown(
    options=variables,
    disabled=False,
)

# Create an Output widget
output_widget = Output()

# Add a line break widget
space_widget = HTML("<br>")


# Arrange the widgets using VBox and display them
display(VBox([Label('Select starting date (09/06/2014 to 22/06/2023): '), date_picker, Label('Select ending date (09/06/2014 to 22/06/2023): '), second_date_picker, Label('Select a variable: '), variable_dropdown, space_widget, output_widget]))

4. Generate and display scatter plot of selected variable

This section fetches the selected varibale and date range, then creates a scatter plot.

Note: the bigger the gap between starting date and ending date the longer it may take to load the data. As a result, updates on the graph might take a few seconds to appear.

# @title
# Assuming date_picker, variable_dropdown, and output_widget are already defined and populated

def generate_scatterplot():
    selected_start_date = date_picker.value
    selected_end_date = second_date_picker.value
    selected_variable = variable_dropdown.value

    # Check if both dates and a variable are selected
    if selected_start_date is None or selected_end_date is None or selected_variable is None:
         with output_widget:
            clear_output(wait=True)
            print("Please select both a start date, an end date, and a variable.")
         return

    # Validate start date
    if selected_start_date not in [date.date() for date in valid_dates if pd.notna(date)]:
         with output_widget:
            clear_output(wait=True)
            print(f"Invalid start date selected: {selected_start_date}, please select a date between {valid_dates[1].date()} and {valid_dates[-1].date()}")


    # Validate end date
    if selected_end_date not in [date.date() for date in valid_dates if pd.notna(date)]:
         with output_widget:
            clear_output(wait=True)
            print(f"Invalid end date selected: {selected_end_date}, please select a date between {valid_dates[1].date()} and {valid_dates[-1].date()}")


    if selected_start_date > selected_end_date:
        with output_widget:
            clear_output(wait=True)
            print("End date cannot be before start date")

    # Format the dates for the URL (YYYY-MM-DDTHH:MM:SSZ) and replace : with %3A
    formatted_start_date = selected_start_date.strftime('%Y-%m-%dT%H:%M:%SZ').replace(':', '%3A')
    formatted_end_date = selected_end_date.strftime('%Y-%m-%dT%H:%M:%SZ').replace(':', '%3A')


    # Construct the graph URL to include both selected_variable and Temperature for coloring
    graph_url = f'https://er1.s4oceanice.eu/erddap/tabledap/IADC_s1_ctd.csv?time%2C{selected_variable}&time%3E={formatted_start_date}&time%3C={formatted_end_date}&distinct()'


    try:
        # Fetch the data from the URL
        graph_df = pd.read_csv(graph_url, skiprows=[1]) # Skip the units row

        # Convert the 'time' column to datetime
        graph_df['time'] = pd.to_datetime(graph_df['time'])

        # Clear previous output and use the output widget
        with output_widget:
            clear_output(wait=True)
            # Create the scatterplot, coloring by Temperature
            plt.figure(figsize=(12, 6))

            sns.scatterplot(data=graph_df, x='time', y=selected_variable, hue=selected_variable, palette='coolwarm', marker="x", legend=False)

            plt.title(f'{selected_variable} over time between {selected_start_date} and {selected_end_date}')

            # Add units to the y-axis label
            if selected_variable == 'Temperature':
                plt.ylabel(f'{selected_variable} (degree_C)')
            elif selected_variable == 'sea_water_practical_salinity':
                plt.ylabel(f'{selected_variable} (PSS-78)')
            else:
                plt.ylabel(selected_variable)

            plt.xlabel('Time')
            plt.xticks(rotation=45)
            plt.tight_layout()
            plt.show()
            plt.close() # Close the figure to prevent the extra output

    except Exception as e:
        with output_widget:
            clear_output(wait=True)
            print(f"Error fetching data or generating plot: {e}")
            print(f"Attempted URL: {graph_url}")

# Link this function to the observe events of the widgets
date_picker.observe(lambda change: generate_scatterplot(), names='value')
second_date_picker.observe(lambda change: generate_scatterplot(), names='value')
variable_dropdown.observe(lambda change: generate_scatterplot(), names='value')

# Generate the initial plot when the cell is executed
generate_scatterplot()