Source code for gufo.data.adapter

"""DataAdapter — single resolution point for all input data types."""
import numpy as np

try:
    import pandas as pd
except ImportError:
    pd = None

try:
    import polars as pl
except ImportError:
    pl = None


[docs] class DataAdapter: """ Wraps any supported input data into a common interface. Marks never receive raw DataFrames or dicts. They call DataAdapter.resolve(key) and get back a numpy array. Supported input types: pandas.DataFrame — column access by string key polars.DataFrame — column access by string key (optional dependency) dict — key access None — no bound data; x/y must be arrays/lists directly """
[docs] def __init__(self, data): self._data = data self._type = self._detect_type(data)
@property def raw_data(self): """The original data object passed to the adapter.""" return self._data @property def data_type(self): """String identifying the data backend: 'dataframe', 'polars', 'dict', or 'none'.""" return self._type
[docs] @classmethod def from_any(cls, data): return cls(data)
def _detect_type(self, data): if data is None: return "none" if pd is not None and isinstance(data, pd.DataFrame): return "dataframe" if pl is not None and isinstance(data, pl.DataFrame): return "polars" # Fallback: detect by module name when library import failed cls_module = type(data).__module__ or "" cls_name = type(data).__qualname__ if cls_name == "DataFrame": if cls_module.startswith("pandas"): return "dataframe" if cls_module.startswith("polars"): return "polars" if isinstance(data, dict): return "dict" if pd is not None and isinstance(data, pd.Series): raise TypeError( "gufo.chart() accepts columnar data (DataFrame or dict), " "not a Series. Pass it directly to the mark instead: " "gufo.chart().histogram(series) or gufo.chart().scatter(series, y)." ) if isinstance(data, np.ndarray): if data.ndim >= 2: raise TypeError( f"gufo.chart() accepts columnar data (DataFrame or dict), " f"not a {data.ndim}D array with shape {data.shape}. " f"For multi-column data, use a dict: " f"gufo.chart({{'x': arr[:, 0], 'y': arr[:, 1]}})." ) raise TypeError( "gufo.chart() accepts columnar data (DataFrame or dict), " "not a raw array. Pass arrays directly to the mark instead: " "gufo.chart().histogram(data) or gufo.chart().scatter(x, y)." ) if isinstance(data, (list, tuple)): raise TypeError( "gufo.chart() accepts columnar data (DataFrame or dict), " "not a raw list or tuple. Pass arrays directly to the mark instead: " "gufo.chart().histogram(data) or gufo.chart().scatter(x, y)." ) supported = ["pandas DataFrame", "Polars DataFrame", "dict", "None"] raise TypeError( f"Unsupported data type: {type(data).__name__}. " f"Pass a {', '.join(supported[:-1])}, or {supported[-1]}." )
[docs] def column_names(self): """Return list of column names from bound data.""" if self._type in ("dataframe", "polars"): return list(self._data.columns) if self._type == "dict": return list(self._data.keys()) raise ValueError( "Cannot list columns — no columnar data was provided. " "Pass a DataFrame or dict to gufo.chart(data)." )
[docs] def resolve(self, key): """ Return key as a numpy array. key may be: str — column name looked up in bound data list of str — multiple columns (wide-form); returns list of arrays array-like — returned as numpy array directly """ if isinstance(key, str): return self._resolve_column(key) if isinstance(key, list): if all(isinstance(k, str) for k in key): return [self._resolve_column(k) for k in key] return [np.asarray(k) for k in key] if key is None: return None return np.asarray(key)
[docs] def subset(self, mask): """Return a new DataAdapter containing only rows where mask is True.""" if self._type == "none": raise ValueError( "Cannot subset data — no DataFrame or dict was provided. " "Faceting requires bound columnar data passed to gufo.chart(data)." ) if self._type == "dataframe": return DataAdapter(self._data[mask].reset_index(drop=True)) if self._type == "polars": return DataAdapter(self._data.filter(mask)) if self._type == "dict": filtered = {k: np.asarray(v)[mask] for k, v in self._data.items()} return DataAdapter(filtered)
def _resolve_column(self, name): if self._type in ("dataframe", "dict"): return np.asarray(self._data[name]) if self._type == "polars": return self._data[name].to_numpy() raise ValueError( f"Cannot resolve column '{name}' — no DataFrame or dict was provided. " "Pass data to gufo.chart(data) or pass arrays directly." )