Source code for cursus.processing.temporal.sequence_ordering_processor
"""
Sequence Ordering Processor for Temporal Self-Attention Model
This module provides atomic sequence ordering for temporal sequences.
Extracted from TSA preprocess_functions.py logic.
"""
import numpy as np
import pandas as pd
from typing import Dict, List, Optional, Union, Any
import logging
from ..processors import Processor
logger = logging.getLogger(__name__)
[docs]
class SequenceOrderingProcessor(Processor):
"""
Orders sequences by timestamp or other criteria.
Extracted from TSA preprocess_functions.py sequence validation logic.
Args:
sort_field: Field to sort by
sort_order: 'ascending', 'descending'
validate_order: Whether to validate ordering consistency
"""
def __init__(
self,
sort_field: str = "orderDate",
sort_order: str = "ascending",
validate_order: bool = True,
):
super().__init__()
self.sort_field = sort_field
self.sort_order = sort_order
self.validate_order = validate_order
self.is_fitted = False
[docs]
def fit(self, data: Any) -> "SequenceOrderingProcessor":
"""No fitting required for ordering"""
self.is_fitted = True
logger.info(
f"SequenceOrderingProcessor fitted with sort_field: {self.sort_field}"
)
return self
[docs]
def process(
self, input_data: Union[Dict, np.ndarray, pd.DataFrame]
) -> Union[Dict, np.ndarray, pd.DataFrame]:
"""Apply sequence ordering"""
if not self.is_fitted:
raise RuntimeError("Processor must be fitted before processing")
if isinstance(input_data, np.ndarray):
return self._process_numpy_array(input_data)
elif isinstance(input_data, pd.DataFrame):
return self._process_dataframe(input_data)
elif isinstance(input_data, dict):
return self._process_dict(input_data)
else:
raise ValueError(f"Unsupported input type: {type(input_data)}")
def _process_numpy_array(self, input_data: np.ndarray) -> np.ndarray:
"""Process numpy array input"""
# This expects a 2D [rows, features] sequence (last column = timestamp).
# A 1D array would make input_data[:, -1] raise an opaque IndexError, so
# validate the shape up front with a clear message.
if input_data.ndim != 2:
raise ValueError(
f"Expected a 2D array [rows, features], got {input_data.ndim}D "
f"with shape {input_data.shape}."
)
# Assume last column contains timestamps for TSA compatibility
sort_indices = np.argsort(input_data[:, -1])
if self.sort_order == "descending":
sort_indices = sort_indices[::-1]
result = input_data[sort_indices]
# Validate ordering if requested
if self.validate_order:
timestamps = result[:, -1]
if self.sort_order == "ascending":
if not np.all(timestamps[:-1] <= timestamps[1:]):
logger.warning(
"Sequence ordering validation failed for ascending order"
)
else:
if not np.all(timestamps[:-1] >= timestamps[1:]):
logger.warning(
"Sequence ordering validation failed for descending order"
)
return result
def _process_dataframe(self, input_data: pd.DataFrame) -> pd.DataFrame:
"""Process DataFrame input"""
if self.sort_field not in input_data.columns:
raise ValueError(
f"Sort field '{self.sort_field}' not found in DataFrame columns"
)
ascending = self.sort_order == "ascending"
result = input_data.sort_values(
by=self.sort_field, ascending=ascending
).reset_index(drop=True)
# Validate ordering if requested
if self.validate_order:
timestamps = result[self.sort_field].values
if self.sort_order == "ascending":
if not np.all(timestamps[:-1] <= timestamps[1:]):
logger.warning(
f"Sequence ordering validation failed for ascending order on field '{self.sort_field}'"
)
else:
if not np.all(timestamps[:-1] >= timestamps[1:]):
logger.warning(
f"Sequence ordering validation failed for descending order on field '{self.sort_field}'"
)
return result
def _process_dict(self, input_data: Dict) -> Dict:
"""Process dictionary input"""
if self.sort_field not in input_data:
raise ValueError(
f"Sort field '{self.sort_field}' not found in input dictionary"
)
timestamps = input_data[self.sort_field]
if not isinstance(timestamps, list):
# Single value, no sorting needed
return input_data
# Create sort indices
sort_indices = sorted(range(len(timestamps)), key=lambda i: timestamps[i])
if self.sort_order == "descending":
sort_indices = sort_indices[::-1]
# Apply sorting to all list values in the dictionary
result = {}
for key, values in input_data.items():
if isinstance(values, list) and len(values) == len(timestamps):
result[key] = [values[i] for i in sort_indices]
else:
result[key] = values
# Validate ordering if requested
if self.validate_order:
sorted_timestamps = result[self.sort_field]
if self.sort_order == "ascending":
if not all(
sorted_timestamps[i] <= sorted_timestamps[i + 1]
for i in range(len(sorted_timestamps) - 1)
):
logger.warning(
f"Sequence ordering validation failed for ascending order on field '{self.sort_field}'"
)
else:
if not all(
sorted_timestamps[i] >= sorted_timestamps[i + 1]
for i in range(len(sorted_timestamps) - 1)
):
logger.warning(
f"Sequence ordering validation failed for descending order on field '{self.sort_field}'"
)
return result
[docs]
def get_config(self) -> Dict[str, Any]:
"""Return processor configuration"""
return {
"sort_field": self.sort_field,
"sort_order": self.sort_order,
"validate_order": self.validate_order,
}
def __repr__(self) -> str:
return (
f"SequenceOrderingProcessor(sort_field='{self.sort_field}', "
f"sort_order='{self.sort_order}', validate_order={self.validate_order})"
)