# Path: sksurv/util.py
# class Surv:
#     """
#     Helper class to construct structured array of event indicator and observed time.
#     """
# 
#     @staticmethod
#     def from_arrays(event, time, name_event=None, name_time=None):
#         """Create structured array.
# 
#         Parameters
#         ----------
#         event : array-like
#             Event indicator. A boolean array or array with values 0/1.
#         time : array-like
#             Observed time.
#         name_event : str|None
#             Name of event, optional, default: 'event'
#         name_time : str|None
#             Name of observed time, optional, default: 'time'
# 
#         Returns
#         -------
#         y : np.array
#             Structured array with two fields.
#         """
#         name_event = name_event or 'event'
#         name_time = name_time or 'time'
#         if name_time == name_event:
#             raise ValueError('name_time must be different from name_event')
# 
#         time = numpy.asanyarray(time, dtype=float)
#         y = numpy.empty(time.shape[0],
#                         dtype=[(name_event, bool), (name_time, float)])
#         y[name_time] = time
# 
#         event = numpy.asanyarray(event)
#         check_consistent_length(time, event)
# 
#         if numpy.issubdtype(event.dtype, numpy.bool_):
#             y[name_event] = event
#         else:
#             events = numpy.unique(event)
#             events.sort()
#             if len(events) != 2:
#                 raise ValueError('event indicator must be binary')
# 
#             if numpy.all(events == numpy.array([0, 1], dtype=events.dtype)):
#                 y[name_event] = event.astype(bool)
#             else:
#                 raise ValueError('non-boolean event indicator must contain 0 and 1 only')
# 
#         return y
# 
#     @staticmethod
#     def from_dataframe(event, time, data):
#         """Create structured array from data frame.
# 
#         Parameters
#         ----------
#         event : object
#             Identifier of column containing event indicator.
#         time : object
#             Identifier of column containing time.
#         data : pandas.DataFrame
#             Dataset.
# 
#         Returns
#         -------
#         y : np.array
#             Structured array with two fields.
#         """
#         if not isinstance(data, pandas.DataFrame):
#             raise TypeError(
#                 "exepected pandas.DataFrame, but got {!r}".format(type(data)))
# 
#         return Surv.from_arrays(
#             data.loc[:, event].values,
#             data.loc[:, time].values,
#             name_event=str(event),
#             name_time=str(time))
# 
# def safe_concat(objs, *args, **kwargs):
#     """Alternative to :func:`pandas.concat` that preserves categorical variables.
# 
#     Parameters
#     ----------
#     objs : a sequence or mapping of Series, DataFrame, or Panel objects
#         If a dict is passed, the sorted keys will be used as the `keys`
#         argument, unless it is passed, in which case the values will be
#         selected (see below). Any None objects will be dropped silently unless
#         they are all None in which case a ValueError will be raised
#     axis : {0, 1, ...}, default 0
#         The axis to concatenate along
#     join : {'inner', 'outer'}, default 'outer'
#         How to handle indexes on other axis(es)
#     join_axes : list of Index objects
#         Specific indexes to use for the other n - 1 axes instead of performing
#         inner/outer set logic
#     verify_integrity : boolean, default False
#         Check whether the new concatenated axis contains duplicates. This can
#         be very expensive relative to the actual data concatenation
#     keys : sequence, default None
#         If multiple levels passed, should contain tuples. Construct
#         hierarchical index using the passed keys as the outermost level
#     levels : list of sequences, default None
#         Specific levels (unique values) to use for constructing a
#         MultiIndex. Otherwise they will be inferred from the keys
#     names : list, default None
#         Names for the levels in the resulting hierarchical index
#     ignore_index : boolean, default False
#         If True, do not use the index values along the concatenation axis. The
#         resulting axis will be labeled 0, ..., n - 1. This is useful if you are
#         concatenating objects where the concatenation axis does not have
#         meaningful indexing information. Note the the index values on the other
#         axes are still respected in the join.
#     copy : boolean, default True
#         If False, do not copy data unnecessarily
# 
#     Notes
#     -----
#     The keys, levels, and names arguments are all optional
# 
#     Returns
#     -------
#     concatenated : type of objects
#     """
#     axis = kwargs.pop("axis", 0)
#     categories = {}
#     for df in objs:
#         if isinstance(df, pandas.Series):
#             if is_categorical_dtype(df.dtype):
#                 categories[df.name] = {"categories": df.cat.categories, "ordered": df.cat.ordered}
#         else:
#             dfc = df.select_dtypes(include=["category"])
#             for name, s in dfc.iteritems():
#                 if name in categories:
#                     if axis == 1:
#                         raise ValueError("duplicate columns %s" % name)
#                     if not categories[name]["categories"].equals(s.cat.categories):
#                         raise ValueError("categories for column %s do not match" % name)
#                 else:
#                     categories[name] = {"categories": s.cat.categories, "ordered": s.cat.ordered}
#                 df[name] = df[name].astype(object)
# 
#     concatenated = pandas.concat(objs, *args, axis=axis, **kwargs)
# 
#     for name, params in categories.items():
#         concatenated[name] = pandas.Categorical(concatenated[name], **params)
# 
#     return concatenated


from collections import OrderedDict
from numpy.testing import assert_array_equal
from sksurv.util import Surv, safe_concat
import numpy
import pandas
import pandas.testing as tm
import pytest




class TestUtil:
    @staticmethod
    def test_concat_numeric():
        rnd = numpy.random.RandomState(14)
        a = pandas.Series(rnd.randn(100), name="col_A")
        b = pandas.Series(rnd.randn(100), name="col_B")

        expected_df = pandas.DataFrame.from_dict(OrderedDict(
            [(a.name, a), (b.name, b)]
        ))


# Path: sksurv/util.py
# class Surv:
#     """
#     Helper class to construct structured array of event indicator and observed time.
#     """
# 
#     @staticmethod
#     def from_arrays(event, time, name_event=None, name_time=None):
#         """Create structured array.
# 
#         Parameters
#         ----------
#         event : array-like
#             Event indicator. A boolean array or array with values 0/1.
#         time : array-like
#             Observed time.
#         name_event : str|None
#             Name of event, optional, default: 'event'
#         name_time : str|None
#             Name of observed time, optional, default: 'time'
# 
#         Returns
#         -------
#         y : np.array
#             Structured array with two fields.
#         """
#         name_event = name_event or 'event'
#         name_time = name_time or 'time'
#         if name_time == name_event:
#             raise ValueError('name_time must be different from name_event')
# 
#         time = numpy.asanyarray(time, dtype=float)
#         y = numpy.empty(time.shape[0],
#                         dtype=[(name_event, bool), (name_time, float)])
#         y[name_time] = time
# 
#         event = numpy.asanyarray(event)
#         check_consistent_length(time, event)
# 
#         if numpy.issubdtype(event.dtype, numpy.bool_):
#             y[name_event] = event
#         else:
#             events = numpy.unique(event)
#             events.sort()
#             if len(events) != 2:
#                 raise ValueError('event indicator must be binary')
# 
#             if numpy.all(events == numpy.array([0, 1], dtype=events.dtype)):
#                 y[name_event] = event.astype(bool)
#             else:
#                 raise ValueError('non-boolean event indicator must contain 0 and 1 only')
# 
#         return y
# 
#     @staticmethod
#     def from_dataframe(event, time, data):
#         """Create structured array from data frame.
# 
#         Parameters
#         ----------
#         event : object
#             Identifier of column containing event indicator.
#         time : object
#             Identifier of column containing time.
#         data : pandas.DataFrame
#             Dataset.
# 
#         Returns
#         -------
#         y : np.array
#             Structured array with two fields.
#         """
#         if not isinstance(data, pandas.DataFrame):
#             raise TypeError(
#                 "exepected pandas.DataFrame, but got {!r}".format(type(data)))
# 
#         return Surv.from_arrays(
#             data.loc[:, event].values,
#             data.loc[:, time].values,
#             name_event=str(event),
#             name_time=str(time))
# 
# def safe_concat(objs, *args, **kwargs):
#     """Alternative to :func:`pandas.concat` that preserves categorical variables.
# 
#     Parameters
#     ----------
#     objs : a sequence or mapping of Series, DataFrame, or Panel objects
#         If a dict is passed, the sorted keys will be used as the `keys`
#         argument, unless it is passed, in which case the values will be
#         selected (see below). Any None objects will be dropped silently unless
#         they are all None in which case a ValueError will be raised
#     axis : {0, 1, ...}, default 0
#         The axis to concatenate along
#     join : {'inner', 'outer'}, default 'outer'
#         How to handle indexes on other axis(es)
#     join_axes : list of Index objects
#         Specific indexes to use for the other n - 1 axes instead of performing
#         inner/outer set logic
#     verify_integrity : boolean, default False
#         Check whether the new concatenated axis contains duplicates. This can
#         be very expensive relative to the actual data concatenation
#     keys : sequence, default None
#         If multiple levels passed, should contain tuples. Construct
#         hierarchical index using the passed keys as the outermost level
#     levels : list of sequences, default None
#         Specific levels (unique values) to use for constructing a
#         MultiIndex. Otherwise they will be inferred from the keys
#     names : list, default None
#         Names for the levels in the resulting hierarchical index
#     ignore_index : boolean, default False
#         If True, do not use the index values along the concatenation axis. The
#         resulting axis will be labeled 0, ..., n - 1. This is useful if you are
#         concatenating objects where the concatenation axis does not have
#         meaningful indexing information. Note the the index values on the other
#         axes are still respected in the join.
#     copy : boolean, default True
#         If False, do not copy data unnecessarily
# 
#     Notes
#     -----
#     The keys, levels, and names arguments are all optional
# 
#     Returns
#     -------
#     concatenated : type of objects
#     """
#     axis = kwargs.pop("axis", 0)
#     categories = {}
#     for df in objs:
#         if isinstance(df, pandas.Series):
#             if is_categorical_dtype(df.dtype):
#                 categories[df.name] = {"categories": df.cat.categories, "ordered": df.cat.ordered}
#         else:
#             dfc = df.select_dtypes(include=["category"])
#             for name, s in dfc.iteritems():
#                 if name in categories:
#                     if axis == 1:
#                         raise ValueError("duplicate columns %s" % name)
#                     if not categories[name]["categories"].equals(s.cat.categories):
#                         raise ValueError("categories for column %s do not match" % name)
#                 else:
#                     categories[name] = {"categories": s.cat.categories, "ordered": s.cat.ordered}
#                 df[name] = df[name].astype(object)
# 
#     concatenated = pandas.concat(objs, *args, axis=axis, **kwargs)
# 
#     for name, params in categories.items():
#         concatenated[name] = pandas.Categorical(concatenated[name], **params)
# 
#     return concatenated


# Path: tests/test_util.py
from collections import OrderedDict
from numpy.testing import assert_array_equal
from sksurv.util import Surv, safe_concat
import numpy
import pandas
import pandas.testing as tm
import pytest





class TestUtil:
    @staticmethod
    def test_concat_numeric():
        rnd = numpy.random.RandomState(14)
        a = pandas.Series(rnd.randn(100), name="col_A")
        b = pandas.Series(rnd.randn(100), name="col_B")

        expected_df = pandas.DataFrame.from_dict(OrderedDict(
            [(a.name, a), (b.name, b)]
        ))


        actual_df = safe_concat((a, b), axis=1)