Download src/tools/analytics/relationship.py from DataEyond/Agentic-Service-Data-Eyond-Catalog: direct link, hf CLI and curl.
- Browser
- Download file 4.97 kB
-
https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/717452f69ec32394dea2a8fc11aba47eebe5bc53/src/tools/analytics/relationship.py
- Command line
-
hf download hf://spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog@717452f69ec32394dea2a8fc11aba47eebe5bc53/src/tools/analytics/relationship.py
-
curl -L -o relationship.py https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/717452f69ec32394dea2a8fc11aba47eebe5bc53/src/tools/analytics/relationship.py
4.97 kB
| """analyze_correlation — correlation among numeric columns (KM-608). | |
| An analytical "family" tool: in ONE call it measures how strongly numeric | |
| columns move together. Returns the full correlation matrix plus a list of | |
| column pairs ranked by strength. Answers questions like "does price relate to | |
| units sold?". | |
| STATUS: compute layer only — the function takes an already-materialized | |
| DataFrame. The wrapper layer (fetching data from the catalog via source_id, | |
| the ToolOutput envelope, ToolSpec registration) is added once the Planner | |
| seam (KM-418) is settled. Keeping compute separate from data-fetching makes | |
| this function easy to unit-test in isolation and stable when wrapped. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import pandas as pd | |
| from src.tools.analytics.descriptive import ColumnNotFoundError | |
| # Correlation methods supported by pandas .corr(). | |
| SUPPORTED_METHODS = ("pearson", "spearman", "kendall") | |
| class InvalidMethodError(ValueError): | |
| """The requested method is not supported (maps to error_code INVALID_METHOD).""" | |
| class NonNumericColumnError(ValueError): | |
| """A requested column is not numeric (maps to error_code NON_NUMERIC_COLUMN).""" | |
| class NotEnoughColumnsError(ValueError): | |
| """Correlation needs at least two numeric columns (maps to NOT_ENOUGH_COLUMNS).""" | |
| def _clean(value: object) -> float | None: | |
| """Cast to plain float; NaN (e.g. a zero-variance column) -> None.""" | |
| if value is None: | |
| return None | |
| f = float(value) # type: ignore[arg-type] | |
| return None if math.isnan(f) else f | |
| # Prompt-style description read by the Planner to decide WHEN to pick this tool. | |
| # Final destination is ToolSpec.description once the wrapper layer is built. | |
| DESCRIPTION = """\ | |
| Summary: Pairwise correlation across numeric columns (pearson, spearman, or \ | |
| kendall). Returns a correlation matrix plus the strongest pairs ranked by \ | |
| absolute strength. | |
| USE WHEN the question is about relationship or association between numeric \ | |
| variables. Trigger words: "correlation" (korelasi), "related/relationship" \ | |
| (hubungan/keterkaitan), "does X affect Y", "move together". | |
| DON'T USE WHEN: | |
| - it implies causation — correlation is not causality; stay descriptive | |
| - it compares two groups of one metric -> analyze_comparison | |
| - it summarizes a single column -> analyze_descriptive | |
| Example questions: | |
| - "is there a correlation between price and quantity sold?" | |
| - "which variables are most related to revenue?" | |
| - "do age and spending move together?" | |
| - "show the correlation matrix for the numeric columns" | |
| """ | |
| def analyze_correlation( | |
| df: pd.DataFrame, | |
| column_ids: list[str] | None = None, | |
| method: str = "pearson", | |
| ) -> dict[str, object]: | |
| """Pairwise correlation across numeric columns. | |
| Args: | |
| df: already-materialized data (in the real system the wrapper fetches | |
| this from a source_id). | |
| column_ids: numeric columns to correlate. If None, every numeric | |
| column in df is used. | |
| method: "pearson" (linear), "spearman" (rank), or "kendall". | |
| Returns: | |
| dict with: | |
| method — echo of the chosen method | |
| columns — the numeric columns actually correlated | |
| matrix — { col: { col: corr|None } } full square matrix | |
| pairs — [{"a", "b", "corr"}] unique pairs, strongest |corr| first | |
| Raises: | |
| InvalidMethodError: if method is unknown. | |
| ColumnNotFoundError: if an explicit column is absent. | |
| NonNumericColumnError: if an explicit column is not numeric. | |
| NotEnoughColumnsError: if fewer than two numeric columns remain. | |
| """ | |
| if method not in SUPPORTED_METHODS: | |
| raise InvalidMethodError( | |
| f"unknown method '{method}'; supported: {list(SUPPORTED_METHODS)}" | |
| ) | |
| if column_ids is None: | |
| cols = [c for c in df.columns if pd.api.types.is_numeric_dtype(df[c])] | |
| else: | |
| missing = [c for c in column_ids if c not in df.columns] | |
| if missing: | |
| raise ColumnNotFoundError(f"columns not found: {missing}") | |
| non_numeric = [ | |
| c for c in column_ids if not pd.api.types.is_numeric_dtype(df[c]) | |
| ] | |
| if non_numeric: | |
| raise NonNumericColumnError(f"columns are not numeric: {non_numeric}") | |
| cols = list(column_ids) | |
| if len(cols) < 2: | |
| raise NotEnoughColumnsError( | |
| f"need >= 2 numeric columns, got {len(cols)}: {cols}" | |
| ) | |
| corr = df[cols].corr(method=method) | |
| matrix = {a: {b: _clean(corr.loc[a, b]) for b in cols} for a in cols} | |
| pairs = [] | |
| for i in range(len(cols)): | |
| for j in range(i + 1, len(cols)): | |
| val = _clean(corr.iloc[i, j]) | |
| if val is not None: | |
| pairs.append({"a": cols[i], "b": cols[j], "corr": val}) | |
| pairs.sort(key=lambda p: abs(p["corr"]), reverse=True) | |
| return {"method": method, "columns": cols, "matrix": matrix, "pairs": pairs} | |