Source code for cdm_reader_mapper.metmetpy.datetime.model_datetimes
"""
Internal metmetpy modelo datetime package.
Created on Wed Jul 10 09:18:41 2019
Defines the datetime field extraction or generation for data models.
Reference names of different metadata fields used in the metmetpy modules
and its location column|(section,column) in a data model are
registered in ../properties.py in metadata_datamodels.
@author: iregon
"""
from __future__ import annotations
import math
import numpy as np
import pandas as pd
from .. import properties
[docs]
def datetime_decimalhour_to_hm(decimal_hours: float) -> tuple[int, int]:
"""
Convert a decimal-hour value (e.g., 12.5) to (hours, minutes).
Parameters
----------
decimal_hours : float
Decimal hour value.
Returns
-------
tuple[int, int]
Integer hours and minutes.
"""
decimal_hours = float(decimal_hours)
hours = int(math.floor(decimal_hours))
minutes = int(math.floor(60.0 * math.fmod(decimal_hours, 1)))
return hours, minutes
[docs]
def icoads(data: pd.DataFrame | pd.Series, conversion: str) -> pd.DataFrame | pd.Series:
"""
Convert ICOADS date/time fields between DataFrame representation and pandas datetime Series.
Parameters
----------
data : DataFrame or Series
For conversion="to_datetime": a DataFrame containing the ICOADS
date fields YR, MO, DY, HR (can be strings or tuple column names).
For conversion="from_datetime": a Series of datetime64 values.
conversion : {"to_datetime", "from_datetime"}
Direction of conversion.
Returns
-------
Series or DataFrame
Converted date values.
"""
yr_col = properties.metadata_datamodels["year"]["icoads"]
mo_col = properties.metadata_datamodels["month"]["icoads"]
dd_col = properties.metadata_datamodels["day"]["icoads"]
hr_col = properties.metadata_datamodels["hour"]["icoads"]
datetime_cols = [yr_col, mo_col, dd_col, hr_col]
if isinstance(data, pd.DataFrame):
datetime_cols = [c for c in datetime_cols if c in data.columns]
def to_datetime(df: pd.DataFrame) -> pd.Series:
"""
Convert datetime information to datetime object.
Parameters
----------
df : pd.DataFrame
Input data to be converted.
Returns
-------
pd.Series
Series as datetime object.
"""
if not datetime_cols:
return pd.Series(dtype="datetime64[ns]")
dt_data = df[[col for col in datetime_cols if col in df.columns]].copy()
valid = dt_data.notna().all(axis=1)
out = pd.Series(pd.NaT, index=df.index, dtype="datetime64[ns]")
if not valid.any():
return out
hour_values = dt_data.iloc[valid.values, -1].values
hours, minutes = np.vectorize(datetime_decimalhour_to_hm)(hour_values)
dt_data = dt_data.drop(dt_data.columns[-1], axis=1)
dt_data.loc[valid, "H"] = hours
dt_data.loc[valid, "M"] = minutes
strings = dt_data.loc[valid].astype(int).astype(str).apply("-".join, axis=1)
out.loc[valid] = pd.to_datetime(strings, format="%Y-%m-%d-%H-%M", errors="coerce")
return out
def from_datetime(ds: pd.Series) -> pd.DataFrame:
"""
Convert datetime object to str.
Parameters
----------
ds : pd.Series
Input series to be converted.
Returns
-------
pd.DataFrame
DataFrame with year, month, day and hour columns.
"""
df = pd.DataFrame(index=ds.index, columns=datetime_cols)
df.columns = pd.MultiIndex.from_tuples(datetime_cols)
valid = ds.notna()
df.loc[valid, yr_col] = ds.dt.year[valid].astype(int)
df.loc[valid, mo_col] = ds.dt.month[valid].astype(int)
df.loc[valid, dd_col] = ds.dt.day[valid].astype(int)
df.loc[valid, hr_col] = ds.dt.hour[valid] + ds.dt.minute[valid] / 60
return df
if conversion == "to_datetime":
if not isinstance(data, pd.DataFrame):
raise TypeError("to_datetime requires a DataFrame")
return to_datetime(data)
if conversion == "from_datetime":
if not isinstance(data, pd.Series):
raise TypeError("from_datetime requires a Series")
return from_datetime(data)
raise ValueError("conversion must be one of {'to_datetime','from_datetime'}")
[docs]
def to_datetime(data: pd.DataFrame, model: str = "icoads") -> pd.Series:
"""
Dispatch conversion to datetime according to model.
Parameters
----------
data : pd.DataFrame
Data to be converted.
model : str, default: icoads
If 'icoads' convert data in ICOADS-style.
Else skip conversion.
Returns
-------
pd.Series
Converted series as datetime object.
"""
if model == "icoads":
return icoads(data, "to_datetime")
return data
[docs]
def from_datetime(data: pd.Series, model: str = "icoads") -> pd.DataFrame:
"""
Dispatch conversion from datetime according to model.
Parameters
----------
data : pd.Series
Series to split into multiple datetime columns.
model : str, default: icoads
If 'icoads' convert data in ICOADS-style.
Else skip conversion.
Returns
-------
pd.DataFrame
DataFrame with multiple datetime columns: year, month, day and hour.
"""
if model == "icoads":
return icoads(data, "from_datetime")
return data