Daily List of Dry Capes in Dry Dock¶
Run this example in Colab.¶
APIs Used: Voyages API
Description:
The goal of this example is to generate a daily list of all dry docks for Dry Capesizes, after a Date of our preference. We are using the Voyages API and filtering the voyage Purpose to be equal to "Dry Dock"
Output: Daily List of all Dry Docks- Excel File
Install the SDK and set necessary parameters¶
In [2]:
Copied!
!pip install signal-ocean
!pip install signal-ocean
Requirement already satisfied: signal-ocean in /usr/local/lib/python3.11/dist-packages (13.3.0) Requirement already satisfied: requests<3,>=2.23.0 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (2.32.3) Requirement already satisfied: python-dateutil<3,>=2.8.1 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (2.8.2) Requirement already satisfied: pandas<3,>=1.0.3 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (2.2.2) Requirement already satisfied: numpy>=1.18.5 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (1.26.4) Requirement already satisfied: strictly-typed-pandas==0.1.4 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (0.1.4) Requirement already satisfied: typeguard<3.0.0,>=2.13.3 in /usr/local/lib/python3.11/dist-packages (from signal-ocean) (2.13.3) Requirement already satisfied: pandas-stubs in /usr/local/lib/python3.11/dist-packages (from strictly-typed-pandas==0.1.4->signal-ocean) (2.2.2.240909) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.11/dist-packages (from pandas<3,>=1.0.3->signal-ocean) (2025.1) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.11/dist-packages (from pandas<3,>=1.0.3->signal-ocean) (2025.1) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil<3,>=2.8.1->signal-ocean) (1.17.0) Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.11/dist-packages (from requests<3,>=2.23.0->signal-ocean) (3.4.1) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/dist-packages (from requests<3,>=2.23.0->signal-ocean) (3.10) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.11/dist-packages (from requests<3,>=2.23.0->signal-ocean) (2.3.0) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.11/dist-packages (from requests<3,>=2.23.0->signal-ocean) (2025.1.31) Requirement already satisfied: types-pytz>=2022.1.1 in /usr/local/lib/python3.11/dist-packages (from pandas-stubs->strictly-typed-pandas==0.1.4->signal-ocean) (2025.1.0.20250204)
In [3]:
Copied!
signal_ocean_api_key = '' #replace with your subscription key
operations_to_display='Dry dock' #here you can filter for: Load, Discharge, Stop, Start, Dry dock
segment='Dry' #this could be either 'Dry', 'Tanker', 'LPG', 'LNG'
vessels_class_to_filter='Capesize' #Vessel class of our preference
voyages_start_from='2024-01-01' #Voyages Strarting from the Date of our preference
signal_ocean_api_key = '' #replace with your subscription key
operations_to_display='Dry dock' #here you can filter for: Load, Discharge, Stop, Start, Dry dock
segment='Dry' #this could be either 'Dry', 'Tanker', 'LPG', 'LNG'
vessels_class_to_filter='Capesize' #Vessel class of our preference
voyages_start_from='2024-01-01' #Voyages Strarting from the Date of our preference
Call the Voyages API¶
The Voyages API retrieves information about vessel voyages.
In [4]:
Copied!
from signal_ocean import Connection
from signal_ocean.voyages import VoyagesAPI
import pandas as pd
from datetime import datetime
import plotly.express as px
from signal_ocean import Connection
from signal_ocean.voyages import VoyagesAPI
import pandas as pd
from datetime import datetime
import plotly.express as px
In [5]:
Copied!
connection = Connection(signal_ocean_api_key)
api = VoyagesAPI(connection)
connection = Connection(signal_ocean_api_key)
api = VoyagesAPI(connection)
In [6]:
Copied!
vessel_classes = api.get_vessel_classes()
vessel_classes_df = pd.DataFrame(v.__dict__ for v in vessel_classes)
vessel_classes_df=vessel_classes_df[vessel_classes_df['vessel_type']==segment]
vessel_classes_df=vessel_classes_df[vessel_classes_df['vessel_class_name']==vessels_class_to_filter]
vessel_classes_df
vessel_classes = api.get_vessel_classes()
vessel_classes_df = pd.DataFrame(v.__dict__ for v in vessel_classes)
vessel_classes_df=vessel_classes_df[vessel_classes_df['vessel_type']==segment]
vessel_classes_df=vessel_classes_df[vessel_classes_df['vessel_class_name']==vessels_class_to_filter]
vessel_classes_df
Out[6]:
vessel_class_id | vessel_class_name | vessel_type_id | vessel_type | |
---|---|---|---|---|
5 | 70 | Capesize | 3 | Dry |
Get voyages data¶
In [7]:
Copied!
vessel_classes_df = vessel_classes_df.reset_index(drop=True)
vessel_class_id_to_look_for = vessel_classes_df.loc[0, 'vessel_class_id']
vessel_classes_df = vessel_classes_df.reset_index(drop=True)
vessel_class_id_to_look_for = vessel_classes_df.loc[0, 'vessel_class_id']
In [8]:
Copied!
voyages = api.get_voyages_by_advanced_search(vessel_class_id=vessel_class_id_to_look_for, start_date_from=datetime.strptime(voyages_start_from, "%Y-%m-%d"),event_purpose=operations_to_display)
voyages = pd.DataFrame([v.__dict__ for v in voyages])
pd.set_option('display.max_columns', None)
voyages.tail(2)
voyages = api.get_voyages_by_advanced_search(vessel_class_id=vessel_class_id_to_look_for, start_date_from=datetime.strptime(voyages_start_from, "%Y-%m-%d"),event_purpose=operations_to_display)
voyages = pd.DataFrame([v.__dict__ for v in voyages])
pd.set_option('display.max_columns', None)
voyages.tail(2)
Out[8]:
imo | voyage_number | vessel_type_id | vessel_class_id | vessel_status_id | commercial_operator_id | deleted | events | id | horizon_id | horizon | latest_received_ais | vessel_name | pit_vessel_name | vessel_type | vessel_class | trade | trade_id | vessel_status | deadweight | year_built | commercial_operator | start_date | first_load_arrival_date | end_date | charterer_id | charterer | rate | rate_type | ballast_bonus | ballast_bonus_type | cargo_type_id | cargo_type | cargo_type_source_id | cargo_type_source | cargo_sub_type_id | cargo_sub_type | cargo_sub_type_source_id | cargo_sub_type_source | cargo_group_id | cargo_group | cargo_group_source_id | cargo_group_source | cargo_sub_group_id | cargo_sub_group | cargo_sub_group_source_id | cargo_sub_group_source | quantity | quantity_unit_id | quantity_unit | quantity_in_barrels | quantity_source_id | quantity_source | cubic_size | laycan_from | laycan_to | fixture_status_id | fixture_status | fixture_date | fixture_is_coa | fixture_is_hold | is_implied_by_ais | has_manual_entries | ballast_distance | predicted_ballast_distance | laden_distance | predicted_laden_distance | suez_crossing | panama_crossing | canakkale_crossing | bosporus_crossing | torres_strait_crossing | magellan_strait_crossing | great_belt_crossing | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
669 | 1028657 | 1 | 3 | 70 | 1 | NaN | False | (VoyageEvent(id='IFB23146SEDF39E700', voyage_i... | IFB23146VEDF39E700 | 2 | Current | 2025-02-21 05:04:16+00:00 | Jmu Ariake 5303 | None | Dry | Capesize | Bulk | 4 | Voyage | 181000 | 2025 | None | 2025-02-19 01:39:24+00:00 | NaT | 9999-12-31 23:59:59+00:00 | NaN | None | NaN | None | None | None | NaN | None | NaN | None | NaN | None | NaN | None | NaN | None | NaN | None | NaN | None | NaN | None | NaN | NaN | None | None | NaN | None | 151408.0 | NaT | NaT | NaN | None | NaT | None | None | None | None | NaN | NaN | NaN | NaN | None | None | None | None | None | None | None |
670 | 1032268 | 1 | 3 | 70 | 1 | 1109.0 | False | (VoyageEvent(id='IFC04C46SEDEC34000', voyage_i... | IFC04C46VEDEC34000 | 2 | Current | 2025-02-24 14:59:52+00:00 | SG Aquamarine | None | Dry | Capesize | Bulk | 4 | Voyage | 180000 | 2024 | NYK Line | 2024-11-12 03:10:46+00:00 | 2025-01-24 19:55:49+00:00 | 2025-03-08 19:20:17.256000+00:00 | NaN | None | NaN | None | None | None | 5258.0 | Iron Ore Fines | 10.0 | EstimatedHigh | NaN | None | NaN | None | 5644.0 | Ores and Rocks | 10.0 | EstimatedHigh | 28.0 | Iron Ore | 10.0 | EstimatedHigh | 174000.0 | 1.0 | MetricTonnes | None | 1.0 | Estimated | NaN | NaT | NaT | -2.0 | NotSet | NaT | False | False | None | None | 11958.04 | NaN | 7053.49 | 1409.67 | None | None | None | None | None | None | None |
In [9]:
Copied!
voyages = voyages[voyages['voyage_number']!=1][['id','imo', 'vessel_class','vessel_name', 'year_built', 'events']].copy() #here we exclude vessels in their first voyage
voyages.rename(columns={'id': 'v_id'}, inplace = True)
voyages = voyages[voyages['voyage_number']!=1][['id','imo', 'vessel_class','vessel_name', 'year_built', 'events']].copy() #here we exclude vessels in their first voyage
voyages.rename(columns={'id': 'v_id'}, inplace = True)
In [10]:
Copied!
voyages
voyages
Out[10]:
v_id | imo | vessel_class | vessel_name | year_built | events | |
---|---|---|---|---|---|---|
0 | I8CABCCVEDEEACD00 | 9219020 | Capesize | Cape Qingdao | 2002 | (VoyageEvent(id='I8CABCCSEDEEACD00', voyage_id... |
1 | I8CBA4AVEDDD5F200 | 9222730 | Capesize | Smoke | 2001 | (VoyageEvent(id='I8CBA4ASEDDD5F200', voyage_id... |
2 | I8CBA56VEDE742600 | 9222742 | Capesize | Gaia I | 2001 | (VoyageEvent(id='I8CBA56SEDE742600', voyage_id... |
3 | I8CC224VEDD86D800 | 9224740 | Capesize | Cape Zhoushan | 2003 | (VoyageEvent(id='I8CC224SEDD86D800', voyage_id... |
4 | I8CC224VEDEEACD00 | 9224740 | Capesize | Cape Zhoushan | 2003 | (VoyageEvent(id='I8CC224SEDEEACD00', voyage_id... |
... | ... | ... | ... | ... | ... | ... |
631 | I976457VEDE9BB300 | 9921623 | Capesize | Star Shibumi | 2021 | (VoyageEvent(id='I976457SEDE9BB300', voyage_id... |
632 | I976883VEDEC34000 | 9922691 | Capesize | First Penguin | 2021 | (VoyageEvent(id='I976883SEDEC34000', voyage_id... |
633 | I9774A946VEDEEACD00 | 9925801 | Capesize | Yunagi | 2022 | (VoyageEvent(id='I9774A946SEDEEACD00', voyage_... |
634 | I9777DFVEDE742600 | 9926623 | Capesize | GCL Dunkirk | 2022 | (VoyageEvent(id='I9777DFSEDE742600', voyage_id... |
635 | I979F1A46VEDDAE6500 | 9936666 | Capesize | Ubuntu Harmony | 2022 | (VoyageEvent(id='I979F1A46SEDDAE6500', voyage_... |
636 rows × 6 columns
explode dataframe to create 1 row per event and extract event info into columns
In [11]:
Copied!
voyages=voyages.explode('events')
voyages['events'] = voyages['events'].apply(lambda x: x.__dict__)
voyages=pd.concat([voyages.drop(['events'], axis=1), voyages['events'].apply(pd.Series)], axis=1)
voyages=voyages.explode('events')
voyages['events'] = voyages['events'].apply(lambda x: x.__dict__)
voyages=pd.concat([voyages.drop(['events'], axis=1), voyages['events'].apply(pd.Series)], axis=1)
Keep only DryDock events and drop some columns
In [12]:
Copied!
voyages=voyages[(voyages['purpose']==operations_to_display)].sort_values(by='arrival_date')
expanded_rows = []
for _, row in voyages.iterrows():
date_range = pd.date_range(start=row['arrival_date'], end=row['sailing_date'])
for single_date in date_range:
expanded_rows.append([row['imo'], row['vessel_name'], row['vessel_class'], row['year_built'], single_date])
# Create a new DataFrame with expanded rows
expanded_df = pd.DataFrame(expanded_rows, columns=['imo', 'vessel_name', 'vessel_class','year_built', 'date'])
# Sort the DataFrame by date and vessel for better readability
daily_list_df = expanded_df.sort_values(by=['date', 'imo']).reset_index(drop=True)
#Limit the data to today
today = pd.to_datetime(datetime.now().date()).tz_localize('UTC')
# Date to filter on
start_date = pd.Timestamp('2015-01-01').tz_localize('UTC')
# Filter the dataframe for rows where the date is between start_date and today
daily_list_df = daily_list_df[daily_list_df['date'] <= today]
daily_list_df = daily_list_df[daily_list_df['date'] >= start_date]
voyages=voyages[(voyages['purpose']==operations_to_display)].sort_values(by='arrival_date')
expanded_rows = []
for _, row in voyages.iterrows():
date_range = pd.date_range(start=row['arrival_date'], end=row['sailing_date'])
for single_date in date_range:
expanded_rows.append([row['imo'], row['vessel_name'], row['vessel_class'], row['year_built'], single_date])
# Create a new DataFrame with expanded rows
expanded_df = pd.DataFrame(expanded_rows, columns=['imo', 'vessel_name', 'vessel_class','year_built', 'date'])
# Sort the DataFrame by date and vessel for better readability
daily_list_df = expanded_df.sort_values(by=['date', 'imo']).reset_index(drop=True)
#Limit the data to today
today = pd.to_datetime(datetime.now().date()).tz_localize('UTC')
# Date to filter on
start_date = pd.Timestamp('2015-01-01').tz_localize('UTC')
# Filter the dataframe for rows where the date is between start_date and today
daily_list_df = daily_list_df[daily_list_df['date'] <= today]
daily_list_df = daily_list_df[daily_list_df['date'] >= start_date]
Export Data to Excel¶
In [13]:
Copied!
file_path = 'daily_list_of_vessels_in_repair.xlsx'
# Ensure datetimes are timezone-unaware
daily_list_df['date'] = daily_list_df['date'].dt.tz_localize(None)
daily_list_df = daily_list_df.sort_values(by='date',ascending=False)
daily_list_df.to_excel(file_path, index=False)
file_path = 'daily_list_of_vessels_in_repair.xlsx'
# Ensure datetimes are timezone-unaware
daily_list_df['date'] = daily_list_df['date'].dt.tz_localize(None)
daily_list_df = daily_list_df.sort_values(by='date',ascending=False)
daily_list_df.to_excel(file_path, index=False)