Skip to content

TestResults

questvar._api.TestResults

Container for equivalence test results.

Attributes:

Name Type Description
data DataFrame

Per-feature results with columns: feature_id, n1, n2, log2fc, average, df_p, df_adjp, eq_p, eq_adjp, comb_p, comb_adjp, log10_pval, log10_adj_pval, status. Only features that passed the CV filter are included.

config TestConfig

Configuration used for the analysis.

cond_1, cond_2 list of str

Condition column names.

info DataFrame

Per-feature CV filter status and overall status for all input features, including those excluded by the CV filter.

Source code in src/questvar/_api.py
class TestResults:
    """Container for equivalence test results.

    Attributes
    ----------
    data : pl.DataFrame
        Per-feature results with columns: feature_id, n1, n2, log2fc,
        average, df_p, df_adjp, eq_p, eq_adjp, comb_p, comb_adjp,
        log10_pval, log10_adj_pval, status. Only features that passed
        the CV filter are included.
    config : TestConfig
        Configuration used for the analysis.
    cond_1, cond_2 : list of str
        Condition column names.
    info : pl.DataFrame
        Per-feature CV filter status and overall status for all input
        features, including those excluded by the CV filter.
    """

    __test__ = False

    def __init__(
        self,
        data: pl.DataFrame,
        config: TestConfig,
        cond_1: list[Any],
        cond_2: list[Any],
        info: pl.DataFrame,
    ) -> None:
        self.data = data
        self.config = config
        self.cond_1 = cond_1
        self.cond_2 = cond_2
        self.info = info

    def plot(self, **kwargs: Any) -> Any:
        """Generate the 8-panel summary figure. Delegates to plot_summary().

        Parameters
        ----------
        **kwargs
            Passed to questvar.plot.summary.plot_summary.

        Returns
        -------
        matplotlib.figure.Figure
        """
        from questvar.plot.summary import plot_summary

        return plot_summary(self, **kwargs)

    def save(self, path: str) -> None:
        """Save power analysis results to a file.

        .json output saves the full payload (design_grid, run_metrics,
        search_results, diagnostics). .parquet/.csv/.tsv saves only
        the design_grid with a metadata sidecar.

        Parameters
        ----------
        path : str
            Output path with .parquet, .csv, .tsv, or .json extension.

        Raises
        ------
        ValueError
            If the file extension is not supported.
        """
        """Save results to a file with sidecar metadata.

        Writes the main data table, an info sidecar (CV filter status),
        and a JSON metadata file (config, condition labels).

        Parameters
        ----------
        path : str
            Output path with .parquet, .csv, or .tsv extension.
            Sidecar files use the same stem with .info.* and .meta.json.

        Raises
        ------
        ValueError
            If the file extension is not supported.
        """
        import json

        suffix = Path(path).suffix
        stem = Path(path).with_suffix("")
        if suffix == ".parquet":
            self.data.write_parquet(path)
            self.info.write_parquet(f"{stem}.info.parquet")
        elif suffix == ".csv":
            self.data.write_csv(path)
            self.info.write_csv(f"{stem}.info.csv")
        elif suffix == ".tsv":
            self.data.write_csv(path, separator="\t")
            self.info.write_csv(f"{stem}.info.tsv", separator="\t")
        else:
            raise ValueError(
                f"Parameter 'path' has unsupported output suffix {suffix!r}. "
                "Supported formats: '.parquet', '.csv', '.tsv'."
            )
        meta: dict[str, Any] = {
            "config": self.config.to_dict(),
            "cond_1": self.cond_1,
            "cond_2": self.cond_2,
        }
        with open(f"{stem}.meta.json", "w") as f:
            json.dump(meta, f, indent=2)

    @classmethod
    def load(cls, path: str) -> TestResults:
        """Load saved results from a file with its sidecar files.

        Parameters
        ----------
        path : str
            Path to the saved data file (.parquet, .csv, .tsv).
            Sidecar files must exist alongside.

        Returns
        -------
        TestResults

        Raises
        ------
        FileNotFoundError
            If the sidecar info or metadata file is missing.
        ValueError
            If the metadata JSON is invalid, or columns are missing.
        """
        from questvar._config import TestConfig

        p = Path(path)
        suffix = p.suffix
        stem = p.with_suffix("")
        info_path = f"{stem}.info{suffix}"
        if not Path(info_path).exists():
            raise FileNotFoundError(f"Missing sidecar file: {info_path}")
        if suffix == ".parquet":
            data = pl.read_parquet(path)
            info = pl.read_parquet(info_path)
        elif suffix == ".csv":
            data = pl.read_csv(path)
            info = pl.read_csv(info_path)
        elif suffix == ".tsv":
            data = pl.read_csv(path, separator="\t")
            info = pl.read_csv(info_path, separator="\t")
        else:
            raise ValueError(
                f"Parameter 'path' has unsupported input suffix {suffix!r}. "
                "Supported formats: '.parquet', '.csv', '.tsv'."
            )
        _validate_frame_columns(
            data,
            required_columns=_TEST_RESULTS_DATA_COLUMNS,
            label="TestResults data file",
        )
        _validate_frame_columns(
            info,
            required_columns=_TEST_RESULTS_INFO_COLUMNS,
            label="TestResults sidecar file",
        )
        meta = _load_metadata_json(
            Path(f"{stem}.meta.json"),
            required_keys=("config", "cond_1", "cond_2"),
        )
        assert meta is not None
        config_payload = meta["config"]
        if not isinstance(config_payload, dict):
            raise ValueError(
                f"Metadata key 'config' must be a mapping, got {type(config_payload).__name__}."
            )
        if not isinstance(meta["cond_1"], list) or not isinstance(meta["cond_2"], list):
            raise ValueError(
                "Metadata keys 'cond_1' and 'cond_2' must be lists, "
                f"got cond_1={type(meta['cond_1']).__name__}, cond_2={type(meta['cond_2']).__name__}."
            )
        config = TestConfig.from_dict(config_payload)
        return cls(data, config, meta["cond_1"], meta["cond_2"], info)

    def summary(self) -> str:
        """Return a text summary of the power analysis results.

        Reports design point count, Monte Carlo runs, convergence
        diagnostics, grouped parameter ranges with power/SEI ranges,
        and recommended designs from the search results.

        Returns
        -------
        str
        """
        """Return a text summary of the test results.

        Includes input feature count, CV filter exclusion count,
        tested count, status breakdown (equivalent, differential,
        not significant), thresholds, and correction method.

        Returns
        -------
        str
        """
        counts = self.data.group_by("status").len()

        def _count(val: int) -> int:
            row = counts.filter(pl.col("status") == val)
            return row["len"].item() if len(row) > 0 else 0

        n_input = len(self.info) if self.info is not None else len(self.data)
        tested = len(self.data)
        excluded = n_input - tested
        n_eq = _count(1)
        n_df = _count(-1)
        n_ns = tested - n_eq - n_df
        cfg = self.config
        lines = [
            f"QuEStVar  {self.cond_1} vs {self.cond_2}",
            f"  Input features:      {n_input}",
            f"  Excluded by CV:      {excluded}",
            f"  Tested:              {tested}",
            f"  Equivalent  (+1):    {n_eq:>5}  ({100 * n_eq / max(tested, 1):.1f}%)",
            f"  Differential (-1):   {n_df:>5}  ({100 * n_df / max(tested, 1):.1f}%)",
            f"  Not significant (0): {n_ns:>5}  ({100 * n_ns / max(tested, 1):.1f}%)",
            f"  Thresholds:  eq={cfg.eq_thr}  df={cfg.df_thr}  cv={cfg.cv_thr}  p={cfg.p_thr}",
            f"  Correction:  {cfg.correction}",
        ]
        if tested == 0:
            lines.append("  Note: No features passed the CV filter. No statistical tests were run.")
        return "\n".join(lines)

Functions

plot

plot(**kwargs)

Generate the 8-panel summary figure. Delegates to plot_summary().

Parameters:

Name Type Description Default
**kwargs Any

Passed to questvar.plot.summary.plot_summary.

{}

Returns:

Type Description
Figure
Source code in src/questvar/_api.py
def plot(self, **kwargs: Any) -> Any:
    """Generate the 8-panel summary figure. Delegates to plot_summary().

    Parameters
    ----------
    **kwargs
        Passed to questvar.plot.summary.plot_summary.

    Returns
    -------
    matplotlib.figure.Figure
    """
    from questvar.plot.summary import plot_summary

    return plot_summary(self, **kwargs)

save

save(path)

Save power analysis results to a file.

.json output saves the full payload (design_grid, run_metrics, search_results, diagnostics). .parquet/.csv/.tsv saves only the design_grid with a metadata sidecar.

Parameters:

Name Type Description Default
path str

Output path with .parquet, .csv, .tsv, or .json extension.

required

Raises:

Type Description
ValueError

If the file extension is not supported.

Source code in src/questvar/_api.py
def save(self, path: str) -> None:
    """Save power analysis results to a file.

    .json output saves the full payload (design_grid, run_metrics,
    search_results, diagnostics). .parquet/.csv/.tsv saves only
    the design_grid with a metadata sidecar.

    Parameters
    ----------
    path : str
        Output path with .parquet, .csv, .tsv, or .json extension.

    Raises
    ------
    ValueError
        If the file extension is not supported.
    """
    """Save results to a file with sidecar metadata.

    Writes the main data table, an info sidecar (CV filter status),
    and a JSON metadata file (config, condition labels).

    Parameters
    ----------
    path : str
        Output path with .parquet, .csv, or .tsv extension.
        Sidecar files use the same stem with .info.* and .meta.json.

    Raises
    ------
    ValueError
        If the file extension is not supported.
    """
    import json

    suffix = Path(path).suffix
    stem = Path(path).with_suffix("")
    if suffix == ".parquet":
        self.data.write_parquet(path)
        self.info.write_parquet(f"{stem}.info.parquet")
    elif suffix == ".csv":
        self.data.write_csv(path)
        self.info.write_csv(f"{stem}.info.csv")
    elif suffix == ".tsv":
        self.data.write_csv(path, separator="\t")
        self.info.write_csv(f"{stem}.info.tsv", separator="\t")
    else:
        raise ValueError(
            f"Parameter 'path' has unsupported output suffix {suffix!r}. "
            "Supported formats: '.parquet', '.csv', '.tsv'."
        )
    meta: dict[str, Any] = {
        "config": self.config.to_dict(),
        "cond_1": self.cond_1,
        "cond_2": self.cond_2,
    }
    with open(f"{stem}.meta.json", "w") as f:
        json.dump(meta, f, indent=2)

load classmethod

load(path)

Load saved results from a file with its sidecar files.

Parameters:

Name Type Description Default
path str

Path to the saved data file (.parquet, .csv, .tsv). Sidecar files must exist alongside.

required

Returns:

Type Description
TestResults

Raises:

Type Description
FileNotFoundError

If the sidecar info or metadata file is missing.

ValueError

If the metadata JSON is invalid, or columns are missing.

Source code in src/questvar/_api.py
@classmethod
def load(cls, path: str) -> TestResults:
    """Load saved results from a file with its sidecar files.

    Parameters
    ----------
    path : str
        Path to the saved data file (.parquet, .csv, .tsv).
        Sidecar files must exist alongside.

    Returns
    -------
    TestResults

    Raises
    ------
    FileNotFoundError
        If the sidecar info or metadata file is missing.
    ValueError
        If the metadata JSON is invalid, or columns are missing.
    """
    from questvar._config import TestConfig

    p = Path(path)
    suffix = p.suffix
    stem = p.with_suffix("")
    info_path = f"{stem}.info{suffix}"
    if not Path(info_path).exists():
        raise FileNotFoundError(f"Missing sidecar file: {info_path}")
    if suffix == ".parquet":
        data = pl.read_parquet(path)
        info = pl.read_parquet(info_path)
    elif suffix == ".csv":
        data = pl.read_csv(path)
        info = pl.read_csv(info_path)
    elif suffix == ".tsv":
        data = pl.read_csv(path, separator="\t")
        info = pl.read_csv(info_path, separator="\t")
    else:
        raise ValueError(
            f"Parameter 'path' has unsupported input suffix {suffix!r}. "
            "Supported formats: '.parquet', '.csv', '.tsv'."
        )
    _validate_frame_columns(
        data,
        required_columns=_TEST_RESULTS_DATA_COLUMNS,
        label="TestResults data file",
    )
    _validate_frame_columns(
        info,
        required_columns=_TEST_RESULTS_INFO_COLUMNS,
        label="TestResults sidecar file",
    )
    meta = _load_metadata_json(
        Path(f"{stem}.meta.json"),
        required_keys=("config", "cond_1", "cond_2"),
    )
    assert meta is not None
    config_payload = meta["config"]
    if not isinstance(config_payload, dict):
        raise ValueError(
            f"Metadata key 'config' must be a mapping, got {type(config_payload).__name__}."
        )
    if not isinstance(meta["cond_1"], list) or not isinstance(meta["cond_2"], list):
        raise ValueError(
            "Metadata keys 'cond_1' and 'cond_2' must be lists, "
            f"got cond_1={type(meta['cond_1']).__name__}, cond_2={type(meta['cond_2']).__name__}."
        )
    config = TestConfig.from_dict(config_payload)
    return cls(data, config, meta["cond_1"], meta["cond_2"], info)

summary

summary()

Return a text summary of the power analysis results.

Reports design point count, Monte Carlo runs, convergence diagnostics, grouped parameter ranges with power/SEI ranges, and recommended designs from the search results.

Returns:

Type Description
str
Source code in src/questvar/_api.py
def summary(self) -> str:
    """Return a text summary of the power analysis results.

    Reports design point count, Monte Carlo runs, convergence
    diagnostics, grouped parameter ranges with power/SEI ranges,
    and recommended designs from the search results.

    Returns
    -------
    str
    """
    """Return a text summary of the test results.

    Includes input feature count, CV filter exclusion count,
    tested count, status breakdown (equivalent, differential,
    not significant), thresholds, and correction method.

    Returns
    -------
    str
    """
    counts = self.data.group_by("status").len()

    def _count(val: int) -> int:
        row = counts.filter(pl.col("status") == val)
        return row["len"].item() if len(row) > 0 else 0

    n_input = len(self.info) if self.info is not None else len(self.data)
    tested = len(self.data)
    excluded = n_input - tested
    n_eq = _count(1)
    n_df = _count(-1)
    n_ns = tested - n_eq - n_df
    cfg = self.config
    lines = [
        f"QuEStVar  {self.cond_1} vs {self.cond_2}",
        f"  Input features:      {n_input}",
        f"  Excluded by CV:      {excluded}",
        f"  Tested:              {tested}",
        f"  Equivalent  (+1):    {n_eq:>5}  ({100 * n_eq / max(tested, 1):.1f}%)",
        f"  Differential (-1):   {n_df:>5}  ({100 * n_df / max(tested, 1):.1f}%)",
        f"  Not significant (0): {n_ns:>5}  ({100 * n_ns / max(tested, 1):.1f}%)",
        f"  Thresholds:  eq={cfg.eq_thr}  df={cfg.df_thr}  cv={cfg.cv_thr}  p={cfg.p_thr}",
        f"  Correction:  {cfg.correction}",
    ]
    if tested == 0:
        lines.append("  Note: No features passed the CV filter. No statistical tests were run.")
    return "\n".join(lines)