# Path: formulaic/errors.py
# class DataMismatchWarning(FormulaicWarning):
#     pass
# 
# Path: formulaic/materializers/types/factor_values.py
# class FactorValues(Generic[T], wrapt.ObjectProxy):
#     """
#     A convenience wrapper that surfaces a `FactorValuesMetadata` instance at
#     `<object>.__formulaic_metadata__`. This wrapper can otherwise wrap any
#     object and behaves just like that object.
#     """
# 
#     def __init__(
#         self,
#         values,
#         *,
#         metadata: FactorValuesMetadata = MISSING,
#         kind: Union[str, Factor.Kind] = MISSING,
#         column_names: Tuple[str] = MISSING,
#         spans_intercept: bool = MISSING,
#         drop_field: Optional[str] = MISSING,
#         format: str = MISSING,
#         encoded: bool = MISSING,
#     ):
#         metadata_constructor = FactorValuesMetadata
#         metadata_kwargs = dict(
#             kind=Factor.Kind(kind) if kind is not MISSING else kind,
#             column_names=column_names,
#             spans_intercept=spans_intercept,
#             drop_field=drop_field,
#             format=format,
#             encoded=encoded,
#         )
#         for key in set(metadata_kwargs):
#             if metadata_kwargs[key] is MISSING:
#                 metadata_kwargs.pop(key)
# 
#         if hasattr(values, "__formulaic_metadata__"):
#             metadata_constructor = values.__formulaic_metadata__.replace
#             if isinstance(values, FactorValues):
#                 values = values.__wrapped__
# 
#         if metadata:
#             metadata_constructor = metadata.replace
# 
#         wrapt.ObjectProxy.__init__(self, values)
#         self._self_metadata = metadata_constructor(**metadata_kwargs)
# 
#     @property
#     def __formulaic_metadata__(self) -> FactorValuesMetadata:
#         return self._self_metadata
# 
#     def __repr__(self) -> str:
#         return self.__wrapped__.__repr__()  # pragma: no cover
# 
# Path: formulaic/utils/sparse.py
# def categorical_encode_series_to_sparse_csc_matrix(
#     series: Iterable, reduced_rank: bool = False
# ) -> spsparse.csc_matrix:
#     """
#     Categorically encode (via dummy encoding) a `series` as a sparse matrix.
# 
#     Args:
#         series: The iterable which should be sparse encoded.
#         reduced_rank: Whether to omit the first column in order to avoid
#             structural collinearity.
# 
#     Returns:
#         The sparse (column-major) matrix representation of the series dummy
#         encoding.
#     """
#     df = pandas.DataFrame({"series": pandas.Categorical(series)})
#     results = df.groupby("series").groups
#     categories = list(results)
#     if reduced_rank:
#         del results[sorted(results)[0]]
#     return categories, {
#         value: spsparse.csc_matrix(
#             (
#                 numpy.ones(len(indices), dtype=float),  # data
#                 (indices, numpy.zeros(len(indices), dtype=int)),  # row  # column
#             ),
#             shape=(numpy.array(series).shape[0], 1),
#         )
#         for value, indices in results.items()
#     }
# 
# Path: formulaic/utils/stateful_transforms.py
# def stateful_transform(func: Callable) -> Callable:
#     """
#     Transform a callable object into a stateful transform.
# 
#     This is done by adding special arguments to the callable's signature:
#     - _state: The existing state or an empty dictionary.
#     - _metadata: Any extra metadata passed about the factor being evaluated.
#     - _spec: The `ModelSpec` instance being evaluated (or an empty `ModelSpec`).
#     If the callable has any of these in its signature, these will be passed onto
#     it; otherwise, they will be swallowed by the stateful transform wrapper.
# 
#     Stateful transforms are also transformed into single dispatches, allowing
#     different implementations for incoming data types.
# 
#     Args:
#         func: The function (or other callable) to be made into a stateful
#             transform.
# 
#     Returns:
#         The stateful transform callable.
#     """
#     func = functools.singledispatch(func)
#     params = inspect.signature(func).parameters.keys()
# 
#     @functools.wraps(func)
#     def wrapper(data, *args, _metadata=None, _state=None, _spec=None, **kwargs):
#         from formulaic.model_spec import ModelSpec
# 
#         _state = {} if _state is None else _state
#         extra_params = {}
#         if "_metadata" in params:
#             extra_params["_metadata"] = _metadata
#         if "_spec" in params:
#             extra_params["_spec"] = _spec or ModelSpec([])
# 
#         if isinstance(data, dict):
#             results = {}
#             for key, datum in data.items():
#                 if isinstance(key, str) and key.startswith("__"):
#                     results[key] = datum
#                 else:
#                     statum = _state.get(key, {})
#                     results[key] = wrapper(
#                         datum, *args, _state=statum, **extra_params, **kwargs
#                     )
#                     if statum:
#                         _state[key] = statum
#             return results
#         return func(data, *args, _state=_state, **extra_params, **kwargs)
# 
#     wrapper.__is_stateful_transform__ = True
#     return wrapper


import warnings
import numpy
import pandas
import scipy.sparse as spsparse
from collections import OrderedDict
from formulaic.errors import DataMismatchWarning
from formulaic.materializers.types import FactorValues
from formulaic.utils.sparse import categorical_encode_series_to_sparse_csc_matrix
from formulaic.utils.stateful_transforms import stateful_transform




@stateful_transform
def encode_categorical(
    data, reduced_rank=False, spans_intercept=True, output=None, _state=None, _spec=None
) -> FactorValues[dict]:
    # TODO: Add support for specifying contrast matrix / etc
    output = output or _spec.output or "pandas"

    if output == "sparse":
        data = numpy.array(data)
        data = data.reshape((data.size,))
        categories, encoded = categorical_encode_series_to_sparse_csc_matrix(
            data, reduced_rank=reduced_rank
        )
    else:
        data = pandas.Series(data).astype("category")
        categories = list(data.cat.categories)

# Path: formulaic/errors.py
# class DataMismatchWarning(FormulaicWarning):
#     pass
# 
# Path: formulaic/materializers/types/factor_values.py
# class FactorValues(Generic[T], wrapt.ObjectProxy):
#     """
#     A convenience wrapper that surfaces a `FactorValuesMetadata` instance at
#     `<object>.__formulaic_metadata__`. This wrapper can otherwise wrap any
#     object and behaves just like that object.
#     """
# 
#     def __init__(
#         self,
#         values,
#         *,
#         metadata: FactorValuesMetadata = MISSING,
#         kind: Union[str, Factor.Kind] = MISSING,
#         column_names: Tuple[str] = MISSING,
#         spans_intercept: bool = MISSING,
#         drop_field: Optional[str] = MISSING,
#         format: str = MISSING,
#         encoded: bool = MISSING,
#     ):
#         metadata_constructor = FactorValuesMetadata
#         metadata_kwargs = dict(
#             kind=Factor.Kind(kind) if kind is not MISSING else kind,
#             column_names=column_names,
#             spans_intercept=spans_intercept,
#             drop_field=drop_field,
#             format=format,
#             encoded=encoded,
#         )
#         for key in set(metadata_kwargs):
#             if metadata_kwargs[key] is MISSING:
#                 metadata_kwargs.pop(key)
# 
#         if hasattr(values, "__formulaic_metadata__"):
#             metadata_constructor = values.__formulaic_metadata__.replace
#             if isinstance(values, FactorValues):
#                 values = values.__wrapped__
# 
#         if metadata:
#             metadata_constructor = metadata.replace
# 
#         wrapt.ObjectProxy.__init__(self, values)
#         self._self_metadata = metadata_constructor(**metadata_kwargs)
# 
#     @property
#     def __formulaic_metadata__(self) -> FactorValuesMetadata:
#         return self._self_metadata
# 
#     def __repr__(self) -> str:
#         return self.__wrapped__.__repr__()  # pragma: no cover
# 
# Path: formulaic/utils/sparse.py
# def categorical_encode_series_to_sparse_csc_matrix(
#     series: Iterable, reduced_rank: bool = False
# ) -> spsparse.csc_matrix:
#     """
#     Categorically encode (via dummy encoding) a `series` as a sparse matrix.
# 
#     Args:
#         series: The iterable which should be sparse encoded.
#         reduced_rank: Whether to omit the first column in order to avoid
#             structural collinearity.
# 
#     Returns:
#         The sparse (column-major) matrix representation of the series dummy
#         encoding.
#     """
#     df = pandas.DataFrame({"series": pandas.Categorical(series)})
#     results = df.groupby("series").groups
#     categories = list(results)
#     if reduced_rank:
#         del results[sorted(results)[0]]
#     return categories, {
#         value: spsparse.csc_matrix(
#             (
#                 numpy.ones(len(indices), dtype=float),  # data
#                 (indices, numpy.zeros(len(indices), dtype=int)),  # row  # column
#             ),
#             shape=(numpy.array(series).shape[0], 1),
#         )
#         for value, indices in results.items()
#     }
# 
# Path: formulaic/utils/stateful_transforms.py
# def stateful_transform(func: Callable) -> Callable:
#     """
#     Transform a callable object into a stateful transform.
# 
#     This is done by adding special arguments to the callable's signature:
#     - _state: The existing state or an empty dictionary.
#     - _metadata: Any extra metadata passed about the factor being evaluated.
#     - _spec: The `ModelSpec` instance being evaluated (or an empty `ModelSpec`).
#     If the callable has any of these in its signature, these will be passed onto
#     it; otherwise, they will be swallowed by the stateful transform wrapper.
# 
#     Stateful transforms are also transformed into single dispatches, allowing
#     different implementations for incoming data types.
# 
#     Args:
#         func: The function (or other callable) to be made into a stateful
#             transform.
# 
#     Returns:
#         The stateful transform callable.
#     """
#     func = functools.singledispatch(func)
#     params = inspect.signature(func).parameters.keys()
# 
#     @functools.wraps(func)
#     def wrapper(data, *args, _metadata=None, _state=None, _spec=None, **kwargs):
#         from formulaic.model_spec import ModelSpec
# 
#         _state = {} if _state is None else _state
#         extra_params = {}
#         if "_metadata" in params:
#             extra_params["_metadata"] = _metadata
#         if "_spec" in params:
#             extra_params["_spec"] = _spec or ModelSpec([])
# 
#         if isinstance(data, dict):
#             results = {}
#             for key, datum in data.items():
#                 if isinstance(key, str) and key.startswith("__"):
#                     results[key] = datum
#                 else:
#                     statum = _state.get(key, {})
#                     results[key] = wrapper(
#                         datum, *args, _state=statum, **extra_params, **kwargs
#                     )
#                     if statum:
#                         _state[key] = statum
#             return results
#         return func(data, *args, _state=_state, **extra_params, **kwargs)
# 
#     wrapper.__is_stateful_transform__ = True
#     return wrapper


# Path: formulaic/transforms/encode_categorical.py
import warnings
import numpy
import pandas
import scipy.sparse as spsparse
from collections import OrderedDict
from formulaic.errors import DataMismatchWarning
from formulaic.materializers.types import FactorValues
from formulaic.utils.sparse import categorical_encode_series_to_sparse_csc_matrix
from formulaic.utils.stateful_transforms import stateful_transform





@stateful_transform
def encode_categorical(
    data, reduced_rank=False, spans_intercept=True, output=None, _state=None, _spec=None
) -> FactorValues[dict]:
    # TODO: Add support for specifying contrast matrix / etc
    output = output or _spec.output or "pandas"

    if output == "sparse":
        data = numpy.array(data)
        data = data.reshape((data.size,))
        categories, encoded = categorical_encode_series_to_sparse_csc_matrix(
            data, reduced_rank=reduced_rank
        )
    else:
        data = pandas.Series(data).astype("category")
        categories = list(data.cat.categories)

        encoded = dict(pandas.get_dummies(data, drop_first=reduced_rank))