"""EDA data container."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
import numpy as np
if TYPE_CHECKING:
from collections.abc import Iterable
from matplotlib.axes import Axes
from matplotlib.figure import Figure
[docs]
class EDAData:
"""Validated data container for EDA analysis.
Args:
y: Response variable. Must be 1D and non-empty.
x: Continuous predictor or single categorical factor.
Must match len(y) if provided.
t: Continuous index variable (e.g. time, 1/B, position).
Not restricted to real time. Must match len(y) if provided.
factors: Named factor arrays for multi-factor/DOE/comparative
plots. Each value must match len(y).
"""
def __init__(
self,
y: Iterable[float],
x: Iterable | None = None,
t: Iterable[float] | None = None,
factors: dict[str, Iterable] | None = None,
) -> None:
self._validate_y()
[docs]
self.x = self._validate_and_convert_x(x)
[docs]
self.t = self._validate_and_convert_t(t)
[docs]
self.factors = self._validate_and_convert_factors(factors)
# --- Fluent methods (univariate) ---
[docs]
def run_sequence_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.run_sequence_plot."""
from drippy.univariate import run_sequence_plot as fn
return fn(self, **kwargs)
[docs]
def lag_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.lag_plot."""
from drippy.univariate import lag_plot as fn
return fn(self, **kwargs)
[docs]
def histogram(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.histogram."""
from drippy.univariate import histogram as fn
return fn(self, **kwargs)
[docs]
def normal_probability_plot(
self, **kwargs: Any
) -> tuple[Figure, Axes, float | None]:
"""Delegates to drippy.univariate.normal_probability_plot."""
from drippy.univariate import normal_probability_plot as fn
return fn(self, **kwargs)
[docs]
def four_plot(self, **kwargs: Any) -> tuple[Figure, np.ndarray]:
"""Delegates to drippy.univariate.four_plot."""
from drippy.univariate import four_plot as fn
return fn(self, **kwargs)
[docs]
def ppcc_plot(self, **kwargs: Any) -> tuple[Figure, np.ndarray]:
"""Delegates to drippy.univariate.ppcc_plot."""
from drippy.univariate import ppcc_plot as fn
return fn(self, **kwargs)
[docs]
def weibull_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.weibull_plot."""
from drippy.univariate import weibull_plot as fn
return fn(self, **kwargs)
[docs]
def probability_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.probability_plot."""
from drippy.univariate import probability_plot as fn
return fn(self, **kwargs)
[docs]
def box_cox_linearity_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.box_cox_linearity_plot."""
from drippy.univariate import box_cox_linearity_plot as fn
return fn(self, **kwargs)
[docs]
def box_cox_normality_plot(
self, **kwargs: Any
) -> tuple[Figure, np.ndarray]:
"""Delegates to drippy.univariate.box_cox_normality_plot."""
from drippy.univariate import box_cox_normality_plot as fn
return fn(self, **kwargs)
[docs]
def bootstrap_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.univariate.bootstrap_plot."""
from drippy.univariate import bootstrap_plot as fn
return fn(self, **kwargs)
# --- Fluent methods (timeseries) ---
[docs]
def spectral_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.timeseries.spectral_plot."""
from drippy.timeseries import spectral_plot as fn
return fn(self, **kwargs)
[docs]
def autocorrelation_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.timeseries.autocorrelation_plot."""
from drippy.timeseries import autocorrelation_plot as fn
return fn(self, **kwargs)
[docs]
def complex_demodulation_amplitude_plot(
self, **kwargs: Any
) -> tuple[Figure, Axes]:
"""Delegate to timeseries.complex_demodulation_amplitude_plot."""
from drippy.timeseries import complex_demodulation_amplitude_plot as fn
return fn(self, **kwargs)
[docs]
def complex_demodulation_phase_plot(
self, **kwargs: Any
) -> tuple[Figure, Axes]:
"""Delegate to drippy.timeseries.complex_demodulation_phase_plot."""
from drippy.timeseries import complex_demodulation_phase_plot as fn
return fn(self, **kwargs)
# --- Fluent methods (onefactor) ---
[docs]
def scatter_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.onefactor.scatter_plot."""
from drippy.onefactor import scatter_plot as fn
return fn(self, **kwargs)
[docs]
def box_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.onefactor.box_plot."""
from drippy.onefactor import box_plot as fn
return fn(self, **kwargs)
[docs]
def bihistogram(self, **kwargs: Any) -> tuple[Figure, np.ndarray]:
"""Delegates to drippy.onefactor.bihistogram."""
from drippy.onefactor import bihistogram as fn
return fn(self, **kwargs)
[docs]
def qq_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.onefactor.qq_plot."""
from drippy.onefactor import qq_plot as fn
return fn(self, **kwargs)
[docs]
def mean_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.onefactor.mean_plot."""
from drippy.onefactor import mean_plot as fn
return fn(self, **kwargs)
[docs]
def sd_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.onefactor.sd_plot."""
from drippy.onefactor import sd_plot as fn
return fn(self, **kwargs)
# --- Fluent methods (Phase 2 — multifactor) ---
[docs]
def doe_scatter_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.multifactor.doe_scatter_plot (Phase 2)."""
from drippy.multifactor import doe_scatter_plot as fn
return fn(self, **kwargs)
[docs]
def doe_mean_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.multifactor.doe_mean_plot (Phase 2)."""
from drippy.multifactor import doe_mean_plot as fn
return fn(self, **kwargs)
[docs]
def doe_sd_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.multifactor.doe_sd_plot (Phase 2)."""
from drippy.multifactor import doe_sd_plot as fn
return fn(self, **kwargs)
[docs]
def contour_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.multifactor.contour_plot (Phase 2)."""
from drippy.multifactor import contour_plot as fn
return fn(self, **kwargs)
# --- Fluent methods (Phase 3 — regression) ---
[docs]
def six_plot(
self, **kwargs: Any
) -> tuple[Figure, tuple[Axes, Axes, Axes, Axes, Axes, Axes]]:
"""Delegates to drippy.regression.six_plot (Phase 3)."""
from drippy.regression import six_plot as fn
return fn(self, **kwargs)
[docs]
def linear_correlation_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegate to drippy.regression.linear_correlation_plot (Phase 3)."""
from drippy.regression import linear_correlation_plot as fn
return fn(self, **kwargs)
[docs]
def linear_intercept_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegate to drippy.regression.linear_intercept_plot (Phase 3)."""
from drippy.regression import linear_intercept_plot as fn
return fn(self, **kwargs)
[docs]
def linear_slope_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.regression.linear_slope_plot (Phase 3)."""
from drippy.regression import linear_slope_plot as fn
return fn(self, **kwargs)
[docs]
def linear_residual_sd_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegate to drippy.regression.linear_residual_sd_plot (Phase 3)."""
from drippy.regression import linear_residual_sd_plot as fn
return fn(self, **kwargs)
# --- Fluent methods (Phase 4 — comparative) ---
[docs]
def block_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.comparative.block_plot (Phase 4)."""
from drippy.comparative import block_plot as fn
return fn(self, **kwargs)
[docs]
def youden_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.comparative.youden_plot (Phase 4)."""
from drippy.comparative import youden_plot as fn
return fn(self, **kwargs)
[docs]
def star_plot(self, **kwargs: Any) -> tuple[Figure, Axes]:
"""Delegates to drippy.comparative.star_plot (Phase 4)."""
from drippy.comparative import star_plot as fn
return fn(self, **kwargs)
[docs]
def _validate_y(self) -> None:
"""Validate y array."""
if self.y.size == 0:
msg = "y cannot be empty"
raise ValueError(msg)
if self.y.ndim != 1:
msg = "y must be 1-dimensional"
raise ValueError(msg)
[docs]
def _validate_and_convert_x(self, x: Iterable | None) -> np.ndarray | None:
"""Validate and convert x array."""
if x is None:
return None
x_arr = np.asarray(x)
if x_arr.ndim != 1:
msg = "x must be 1-dimensional"
raise ValueError(msg)
if len(x_arr) != len(self.y):
msg = (
f"x and y must have the same length. "
f"Got len(x)={len(x_arr)}, len(y)={len(self.y)}."
)
raise ValueError(msg)
return x_arr
[docs]
def _validate_and_convert_t(
self, t: Iterable[float] | None
) -> np.ndarray | None:
"""Validate and convert t array."""
if t is None:
return None
t_arr = np.asarray(t, dtype=float)
if t_arr.ndim != 1:
msg = "t must be 1-dimensional"
raise ValueError(msg)
if len(t_arr) != len(self.y):
msg = (
f"t and y must have the same length. "
f"Got len(t)={len(t_arr)}, len(y)={len(self.y)}."
)
raise ValueError(msg)
return t_arr
[docs]
def _validate_and_convert_factors(
self, factors: dict[str, Iterable] | None
) -> dict[str, np.ndarray] | None:
"""Validate and convert factors dict."""
if factors is None:
return None
result = {}
for key, val in factors.items():
arr = np.asarray(val)
if arr.ndim != 1:
msg = f"factors['{key}'] must be 1-dimensional"
raise ValueError(msg)
if len(arr) != len(self.y):
msg = (
f"factors['{key}'] and y must have the same length. "
f"Got len(factors['{key}'])={len(arr)}, "
f"len(y)={len(self.y)}."
)
raise ValueError(msg)
result[key] = arr
return result