# pylint: disable=E1101,E1103
# pylint: disable=W0212,W0703,W0231,W0622

from cStringIO import StringIO
import sys

from numpy import NaN
import numpy as np

from pandas.core.common import (_pickle_array, _unpickle_array)
from pandas.core.frame import (DataFrame, _try_sort, _extract_index,
                               _default_index)
from pandas.core.index import Index, NULL_INDEX
from pandas.core.series import Series
import pandas.core.common as common
import pandas.core.datetools as datetools
import pandas.lib.tseries as tseries

#-------------------------------------------------------------------------------
# DataMatrix class

class DataMatrix(DataFrame):
    """
    Matrix version of DataFrame, optimized for cross-section operations,
    numerical computation, and other operations that do not require the frame to
    change size.

    Parameters
    ----------
    data : numpy ndarray or dict of sequence-like objects
        Dict can contain Series, arrays, or list-like objects
        Constructor can understand various kinds of inputs
    index : Index or array-like
        Index to use for resulting frame (optional if provided dict of Series)
    columns : Index or array-like
        Required if data is ndarray
    dtype : dtype, default None (infer)
        Data type to force

    Notes
    -----
    Most operations are faster with DataMatrix. You should use it primarily
    unless you are doing a lot of column insertion / deletion (which causes the
    underlying ndarray to have to be reallocated!).
    """
    objects = None
    def __init__(self, data=None, index=None, columns=None, dtype=None,
                 objects=None):

        if isinstance(data, dict) and len(data) > 0:
            (index, columns,
             values, objects) = self._init_dict(data, index, columns, objects,
                                                dtype)
        elif isinstance(data, (np.ndarray, list)):
            (index, columns, values) = self._init_matrix(data, index,
                                                         columns, dtype)

            if objects is not None:
                if isinstance(objects, DataMatrix):
                    if not objects.index.equals(index):
                        objects = objects.reindex(index)
                else:
                    objects = DataMatrix(objects, index=index)
        elif isinstance(data, DataFrame):
            if not isinstance(data, DataMatrix):
                data = data.toDataMatrix()
            values = data.values

            if dtype is not None:
                values = values.astype(dtype)

            index = data.index
            columns = data.columns
            objects = data.objects
        elif data is None or len(data) == 0:
            # this is a touch convoluted...
            if objects is not None:
                if isinstance(objects, DataMatrix):
                    if index is not None and objects.index is not index:
                        objects = objects.reindex(index)
                else:
                    objects = DataMatrix(objects, index=index)

                index = objects.index

            if index is None:
                N = 0
                index = NULL_INDEX
            else:
                N = len(index)

            if columns is None:
                K = 0
                columns = NULL_INDEX
            else:
                K = len(columns)

            values = np.empty((N, K), dtype=dtype)
            values[:] = NaN
        else:
            raise Exception('DataMatrix constructor not properly called!')

        self.values = values
        self.index = index
        self.columns = columns
        self.objects = objects

    def _init_dict(self, data, index, columns, objects, dtype):
        """
        Segregate Series based on type and coerce into matrices.

        Needs to handle a lot of exceptional cases.

        Somehow this got outrageously complicated
        """
        # pre-filter out columns if we passed it
        if columns is not None:
            colset = set(columns)
            data = dict((k, v) for k, v in data.iteritems() if k in colset)

        index = _extract_index(data, index)

        objectDict = {}
        if objects is not None and isinstance(objects, dict):
            objectDict.update(objects)

        valueDict = {}
        for k, v in data.iteritems():
            if isinstance(v, Series):
                if v.index is not index:
                    # Forces alignment. No need to copy data since we
                    # are putting it into an ndarray later
                    v = v.reindex(index)
            else:
                if isinstance(v, dict):
                    v = [v.get(i, NaN) for i in index]
                else:
                    assert(len(v) == len(index))

                try:
                    v = Series(v, dtype=dtype, index=index)
                except Exception:
                    v = Series(v, index=index)

            if issubclass(v.dtype.type, (np.bool_, float, int)):
                valueDict[k] = v
            else:
                objectDict[k] = v

        if columns is None:
            columns = Index(_try_sort(valueDict))
            objectColumns = Index(_try_sort(objectDict))
        else:
            objectColumns = Index([c for c in columns if c in objectDict])
            columns = Index([c for c in columns if c not in objectDict])

        if len(valueDict) == 0:
            dtype = np.object_
            valueDict = objectDict
            columns = objectColumns
        else:
            dtypes = set(v.dtype for v in valueDict.values())

            if len(dtypes) > 1:
                dtype = np.float_
            else:
                dtype = list(dtypes)[0]

            if len(objectDict) > 0:
                new_objects = DataMatrix(objectDict,
                                         dtype=np.object_,
                                         index=index,
                                         columns=objectColumns)
                if isinstance(objects, DataMatrix):
                    objects = objects.join(new_objects, how='left')
                else:
                    objects = new_objects

        values = np.empty((len(index), len(columns)), dtype=dtype)

        for i, col in enumerate(columns):
            if col in valueDict:
                values[:, i] = valueDict[col]
            else:
                values[:, i] = np.NaN

        return index, columns, values, objects

    def _init_matrix(self, values, index, columns, dtype):
        if not isinstance(values, np.ndarray):
            arr = np.array(values)
            if issubclass(arr.dtype.type, basestring):
                arr = np.array(values, dtype=object, copy=True)

            values = arr

        if values.ndim == 1:
            N = values.shape[0]
            if N == 0:
                values = values.reshape((values.shape[0], 0))
            else:
                values = values.reshape((values.shape[0], 1))

        if dtype is not None:
            try:
                values = values.astype(dtype)
            except Exception:
                pass

        N, K = values.shape

        if index is None:
            index = _default_index(N)

        if columns is None:
            columns = _default_index(K)

        return index, columns, values

    @property
    def _constructor(self):
        return DataMatrix

    # Because of DataFrame property
    values = None

    def __array__(self):
        return self.values

    def __array_wrap__(self, result):
        return DataMatrix(result, index=self.index, columns=self.columns)

#-------------------------------------------------------------------------------
# DataMatrix-specific implementation of private API

    def _join_on(self, other, on):
        if len(other.index) == 0:
            return self

        if on not in self:
            raise Exception('%s column not contained in this frame!' % on)

        fillVec, mask = tseries.getMergeVec(self[on],
                                            other.index.indexMap)
        notmask = -mask

        tmpMatrix = other.values.take(fillVec, axis=0)
        tmpMatrix[notmask] = NaN

        seriesDict = dict((col, tmpMatrix[:, j])
                           for j, col in enumerate(other.columns))

        if getattr(other, 'objects'):
            objects = other.objects

            tmpMat = objects.values.take(fillVec, axis=0)
            tmpMat[notmask] = NaN
            objDict = dict((col, tmpMat[:, j])
                           for j, col in enumerate(objects.columns))

            seriesDict.update(objDict)

        filledFrame = DataFrame(data=seriesDict, index=self.index)

        return self.join(filledFrame, how='left')

    def _reindex_index(self, index, method):
        if index is self.index:
            return self.copy()

        if not isinstance(index, Index):
            index = Index(index)

        if len(self.index) == 0:
            return DataMatrix(index=index, columns=self.columns)

        indexer, mask = common.get_indexer(self.index, index, method)
        mat = self.values.take(indexer, axis=0)

        notmask = -mask
        if len(index) > 0:
            if notmask.any():
                if issubclass(mat.dtype.type, np.int_):
                    mat = mat.astype(float)
                elif issubclass(mat.dtype.type, np.bool_):
                    mat = mat.astype(float)

                common.null_out_axis(mat, notmask, 0)

        if self.objects is not None and len(self.objects.columns) > 0:
            newObjects = self.objects.reindex(index)
        else:
            newObjects = None

        return DataMatrix(mat, index=index, columns=self.columns,
                          objects=newObjects)

    def _reindex_columns(self, columns):
        if len(columns) == 0:
            return DataMatrix(index=self.index)

        if not isinstance(columns, Index):
            columns = Index(columns)

        if self.objects is not None:
            object_columns = columns.intersection(self.objects.columns)
            columns = columns - object_columns

            objects = self.objects._reindex_columns(object_columns)
        else:
            objects = None

        if len(columns) > 0 and len(self.columns) == 0:
            return DataMatrix(index=self.index, columns=columns,
                              objects=objects)

        indexer, mask = common.get_indexer(self.columns, columns, None)
        mat = self.values.take(indexer, axis=1)

        notmask = -mask
        if len(mask) > 0:
            if notmask.any():
                if issubclass(mat.dtype.type, np.int_):
                    mat = mat.astype(float)
                elif issubclass(mat.dtype.type, np.bool_):
                    mat = mat.astype(float)

                common.null_out_axis(mat, notmask, 1)

        return DataMatrix(mat, index=self.index, columns=columns,
                          objects=objects)

    def _rename_columns_inplace(self, mapper):
        self.columns = [mapper(x) for x in self.columns]

        if self.objects is not None:
            self.objects._rename_columns_inplace(mapper)

    def _combine_frame(self, other, func):
        """
        Methodology, briefly
        - Really concerned here about speed, space

        - Get new index
        - Reindex to new index
        - Determine new_columns and commonColumns
        - Add common columns over all (new) indices
        - Fill to new set of columns

        Could probably deal with some Cython action in here at some point
        """
        new_index = self._union_index(other)

        if not self and not other:
            return DataMatrix(index=new_index)
        elif not self:
            return other * NaN
        elif not other:
            return self * NaN

        need_reindex = False
        new_columns = self._union_columns(other)
        need_reindex = (need_reindex or new_index is not self.index
                        or new_index is not other.index)
        need_reindex = (need_reindex or new_columns is not self.columns
                        or new_columns is not other.columns)

        this = self
        if need_reindex:
            this = self.reindex(index=new_index, columns=new_columns)
            other = other.reindex(index=new_index, columns=new_columns)

        return DataMatrix(func(this.values, other.values),
                          index=new_index, columns=new_columns)

    def _combine_match_index(self, other, func):
        new_index = self._union_index(other)
        values = self.values
        other_vals = other.values

        # Operate row-wise
        if not other.index.equals(new_index):
            other_vals = other.reindex(new_index).values

        if not self.index.equals(new_index):
            values = self.reindex(new_index).values

        return DataMatrix(func(values.T, other_vals).T,
                          index=new_index, columns=self.columns)

    def _combine_match_columns(self, other, func):
        newCols = self.columns.union(other.index)

        # Operate column-wise
        this = self.reindex(columns=newCols)
        other = other.reindex(newCols).values

        return DataMatrix(func(this.values, other),
                          index=self.index, columns=newCols)

    def _combine_const(self, other, func):
        if not self:
            return self

        # TODO: deal with objects
        return DataMatrix(func(self.values, other), index=self.index,
                          columns=self.columns)


#-------------------------------------------------------------------------------
# Properties for index and columns

    def _set_columns(self, cols):
        if len(cols) != self.values.shape[1]:
            raise Exception('Columns length %d did not match values %d!' %
                            (len(cols), self.values.shape[1]))

        if not isinstance(cols, Index):
            cols = Index(cols)

        self._columns = cols

    def _set_index(self, index):
        if len(index) > 0:
            if len(index) != self.values.shape[0]:
                raise Exception('Index length %d did not match values %d!' %
                                (len(index), self.values.shape[0]))

        if not isinstance(index, Index):
            index = Index(index)

        self._index = index

        if self.objects is not None:
            self.objects._index = index

#-------------------------------------------------------------------------------
# "Magic methods"

    def __getstate__(self):
        if self.objects is not None:
            objects = self.objects._matrix_state(pickle_index=False)
        else:
            objects = None

        state = self._matrix_state()

        return (state, objects)

    def _matrix_state(self, pickle_index=True):
        columns = _pickle_array(self.columns)

        if pickle_index:
            index = _pickle_array(self.index)
        else:
            index = None

        return self.values, index, columns

    def __setstate__(self, state):
        (vals, idx, cols), object_state = state

        self.values = vals
        self.index = _unpickle_array(idx)
        self.columns = _unpickle_array(cols)

        if object_state:
            ovals, _, ocols = object_state
            self.objects = DataMatrix(ovals,
                                      index=self.index,
                                      columns=_unpickle_array(ocols))
        else:
            self.objects = None

    def __nonzero__(self):
        N, K = self.values.shape
        if N == 0 or K == 0:
            if self.objects is None:
                return False
            else:
                return self.objects.__nonzero__()
        else:
            return True

    def __repr__(self):
        """Return a string representation for a particular DataMatrix"""
        buffer = StringIO()

        if len(self.cols()) == 0:
            buffer.write('Empty DataMatrix\nIndex: %s' % repr(self.index))
        elif 0 < len(self.index) < 500 and self.values.shape[1] < 10:
            self.toString(buffer=buffer)
        else:
            print >> buffer, str(self.__class__)
            self.info(buffer=buffer)

        return buffer.getvalue()

    def __getitem__(self, item):
        """
        Retrieve column, slice, or subset from DataMatrix.

        Possible inputs
        ---------------
        single value : retrieve a column as a Series
        slice : reindex to indices specified by slice
        boolean vector : like slice but more general, reindex to indices
          where the input vector is True

        Examples
        --------
        column = dm['A']

        dmSlice = dm[:20] # First 20 rows

        dmSelect = dm[dm.count(axis=1) > 10]

        Notes
        -----
        This is a magic method. Do NOT call explicity.
        """
        if isinstance(item, slice):
            indexRange = self.index[item]
            return self.reindex(indexRange)

        elif isinstance(item, np.ndarray):
            if len(item) != len(self.index):
                raise Exception('Item wrong length %d instead of %d!' %
                                (len(item), len(self.index)))
            newIndex = self.index[item]
            return self.reindex(newIndex)
        else:
            if self.objects is not None and item in self.objects:
                return self.objects[item]
            else:
                return self._getSeries(item)

    # __setitem__ logic

    def _boolean_set(self, key, value):
        mask = key.values
        if mask.dtype != np.bool_:
            raise Exception('Must pass DataFrame with boolean values only')

        self.values[mask] = value

    def _insert_item(self, key, value):
        """
        Add series to DataMatrix in specified column.

        If series is a numpy-array (not a Series/TimeSeries), it must be the
        same length as the DataMatrix's index or an error will be thrown.

        Series/TimeSeries will be conformed to the DataMatrix's index to
        ensure homogeneity.
        """
        if hasattr(value, '__iter__'):
            if isinstance(value, Series):
                if value.index.equals(self.index):
                    # no need to copy
                    value = value.values
                else:
                    value = value.reindex(self.index).values
            else:
                assert(len(value) == len(self.index))

                if not isinstance(value, np.ndarray):
                    value = np.array(value)
                    if value.dtype.type == np.str_:
                        value = np.array(value, dtype=object)
        else:
            value = np.repeat(value, len(self.index))

        if self.values.dtype == np.object_:
            self._insert_object_dtype(key, value)
        else:
            self._insert_float_dtype(key, value)

    _dataTypes = [np.float_, np.bool_, np.int_]
    def _insert_float_dtype(self, key, value):
        isObject = value.dtype not in self._dataTypes

        # sanity check
        if len(value) != len(self.index): # pragma: no cover
            raise Exception('Column is wrong length')

        def _put_object(value):
            if self.objects is None:
                self.objects = DataMatrix({key : value},
                                          index=self.index)
            else:
                self.objects[key] = value

        if key in self.columns:
            loc = self.columns.indexMap[key]
            try:
                # attempt coercion
                self.values[:, loc] = value
            except ValueError:
                self._delete_column(loc)
                self._delete_column_index(loc)
                _put_object(value)
        elif isObject:
            _put_object(value)
        else:
            loc = self._get_insert_loc(key)
            self._insert_column(value.astype(float), loc)
            self._insert_column_index(key, loc)

    def _insert_object_dtype(self, key, value):
        if key in self.columns:
            loc = self.columns.indexMap[key]
            self.values[:, loc] = value
        else:
            loc = self._get_insert_loc(key)
            self._insert_column(value, loc)
            self._insert_column_index(key, loc)

    def __delitem__(self, key):
        """
        Delete column from DataMatrix
        """
        if key in self.columns:
            loc = self.columns.indexMap[key]
            self._delete_column(loc)
            self._delete_column_index(loc)
        else:
            if self.objects is not None and key in self.objects:
                del self.objects[key]
            else:
                raise KeyError('%s' % key)

    def _insert_column(self, column, loc):
        mat = self.values

        if column.ndim == 1:
            column = column.reshape((len(column), 1))

        if loc == mat.shape[1]:
            values = np.hstack((mat, column))
        elif loc == 0:
            values = np.hstack((column, mat))
        else:
            values = np.hstack((mat[:, :loc], column, mat[:, loc:]))

        self.values = values

    def _delete_column(self, loc):
        if loc == self.values.shape[1] - 1:
            newValues = self.values[:, :loc]
        else:
            newValues = np.c_[self.values[:, :loc], self.values[:, loc+1:]]
        self.values = newValues

    def __iter__(self):
        """Iterate over columns of the frame."""
        return iter(self.columns)

    def __contains__(self, key):
        """True if DataMatrix has this column"""
        hasCol = key in self.columns
        if hasCol:
            return True
        else:
            if self.objects is not None and key in self.objects:
                return True
            return False

    def iteritems(self):
        return self._series.iteritems()

#-------------------------------------------------------------------------------
# Helper methods

    # For DataFrame compatibility
    def _getSeries(self, item=None, loc=None):
        if loc is None:
            try:
                loc = self.columns.indexMap[item]
            except KeyError:
                raise Exception('%s not here!' % item)
        return Series(self.values[:, loc], index=self.index)

    def _getSeriesDict(self):
        series = {}
        for i, col in enumerate(self.columns):
            series[col] = self._getSeries(loc=i)
        if self.objects is not None:
            for i, col in enumerate(self.objects.columns):
                series[col] = self.objects._getSeries(loc=i)

        return series
    _series = property(_getSeriesDict)

#-------------------------------------------------------------------------------
# Outputting

    def toString(self, buffer=sys.stdout, columns=None, colSpace=15,
                 nanRep='NaN', formatters=None, float_format=None):
        """
        Output a string version of this DataMatrix
        """
        _pf = common._pfixed
        formatters = formatters or {}

        if columns is None:
            columns = self.columns
            values = self.values
            if self.objects:
                columns = list(columns) + list(self.objects.columns)
                values = np.column_stack((values.astype(object),
                                          self.objects.values))
        else:
            columns = [c for c in columns if c in self]
            values = self.asMatrix(columns)

        ident = lambda x: x

        idxSpace = max([len(str(idx)) for idx in self.index]) + 4

        if len(self.cols()) == 0:
            buffer.write('DataMatrix is empty!\n')
            buffer.write(repr(self.index))
        else:
            buffer.write(_pf('', idxSpace))
            for h in columns:
                buffer.write(_pf(h, colSpace))
            buffer.write('\n')

            for i, idx in enumerate(self.index):
                buffer.write(_pf(idx, idxSpace))
                for j, col in enumerate(columns):
                    formatter = formatters.get(col, ident)
                    buffer.write(_pf(formatter(values[i, j]), colSpace,
                                     float_format=float_format,
                                     nanRep=nanRep))
                buffer.write('\n')

    def info(self, buffer=sys.stdout):
        """
        Concise summary of a DataMatrix, used in __repr__ when very large.
        """
        print >> buffer, 'Index: %s entries' % len(self.index),
        if len(self.index) > 0:
            print >> buffer, ', %s to %s' % (self.index[0], self.index[-1])
        else:
            print >> buffer, ''

        if len(self.cols()) == 0:
            print >> buffer, 'DataMatrix is empty!'
            print >> buffer, repr(self.index)
            return

        print >> buffer, 'Data columns:'
        space = max([len(str(k)) for k in self.cols()]) + 4

        counts = self.count()

        cols = self.cols()
        assert(len(cols) == len(counts))

        columns = []
        for col, count in counts.iteritems():
            columns.append('%s%d  non-null values' %
                           (common._pfixed(col, space), count))

        dtypeLine = ''

        nf = len(self.columns)
        df = self.values.dtype

        if self.objects is not None:
            no = len(self.objects.columns)
            do = self.objects.values.dtype
            dtypeLine = '\ndtypes: %s(%d), %s(%d)' % (df, nf, do, no)
        else:
            dtypeLine = '\ndtype: %s(%d)' % (df, nf)

        buffer.write('\n'.join(columns) + dtypeLine)


#-------------------------------------------------------------------------------
# Public methods

    def apply(self, func, axis=0):
        """
        Applies func to columns (Series) of this DataMatrix and returns either
        a DataMatrix (if the function produces another series) or a Series
        indexed on the column names of the DataFrame if the function produces
        a value.

        Parameters
        ----------
        func : function
            Function to apply to each column

        Examples
        --------

            >>> df.apply(numpy.sqrt) --> DataMatrix
            >>> df.apply(numpy.sum) --> Series

        N.B.: Do NOT use functions that might toy with the index.
        """
        if not len(self.cols()):
            return self

        if isinstance(func, np.ufunc):
            results = func(self.values)
            return DataMatrix(data=results, index=self.index,
                              columns=self.columns, objects=self.objects)
        else:
            return DataFrame.apply(self, func, axis=axis)

    def applymap(self, func):
        """
        Apply a function to a DataMatrix that is intended to operate
        elementwise, i.e. like doing
            map(func, series) for each series in the DataMatrix

        Parameters
        ----------
        func : function
            Python function, returns a single value from a single value

        Note : try to avoid using this function if you can, very slow.
        """
        npfunc = np.frompyfunc(func, 1, 1)
        results = npfunc(self.values)
        try:
            results = results.astype(self.values.dtype)
        except Exception:
            pass

        return DataMatrix(results, index=self.index, columns=self.columns)

    def append(self, other):
        """
        Glue together DataFrame objects having non-overlapping indices

        Parameters
        ----------
        other : DataFrame
        """
        if not other:
            return self.copy()

        if not self:
            return other.copy()

        if (isinstance(other, DataMatrix) and
            self.columns.equals(other.columns)):

            idx = Index(np.concatenate([self.index, other.index]))
            mat = np.vstack((self.values, other.values))

            if other.objects is None:
                objects = self.objects
            elif self.objects is None:
                objects = other.objects
            else:
                objects = self.objects.append(other.objects)

            if objects:
                objects = objects.reindex(idx)

            dm = DataMatrix(mat, idx, self.columns, objects=objects)
            return dm
        else:
            return super(DataMatrix, self).append(other)

    def asMatrix(self, columns=None):
        """
        Convert the DataMatrix to its Numpy-array matrix representation

        Columns are presented in sorted order unless a specific list
        of columns is provided.

        Parameters
        ----------
        columns : list-like
            columns to use in producing matrix, must all be contained

        Returns
        -------
        ndarray
        """
        if columns is None:
            values = self.values.copy()

            if self.objects:
                values = np.column_stack((values, self.objects.values))
                order = Index(np.concatenate((self.columns,
                                                self.objects.columns)))
            else:
                order = self.columns

            columns = Index(self.cols())
        else:
            if not isinstance(columns, Index):
                columns = Index(columns)

            values = self.values
            order = self.columns

            if self.objects:
                idxMap = self.objects.columns.indexMap
                indexer = [idxMap[col] for col in columns if col in idxMap]

                obj_values = self.objects.values.take(indexer, axis=1)

                values = np.column_stack((values, obj_values))

                order = Index(np.concatenate((order, self.objects.columns)))

        # now put in the right order
        return _reorder_columns(values, order, columns)

    def cols(self):
        """Return sorted list of frame's columns"""
        if self.objects is not None and len(self.objects.columns) > 0:
            return list(self.columns.union(self.objects.columns))
        else:
            return list(self.columns)

    def copy(self):
        """
        Make a copy of this DataMatrix
        """
        if self.objects:
            objects = self.objects.copy()
        else:
            objects = None

        return DataMatrix(self.values.copy(), index=self.index,
                          columns=self.columns, objects=objects)

    def cumsum(self, axis=0):
        """
        Return DataMatrix of cumulative sums over requested axis.

        Parameters
        ----------
        axis : {0, 1}
            0 for row-wise, 1 for column-wise

        Returns
        -------
        y : DataMatrix
        """
        y = np.array(self.values, subok=True)
        if not issubclass(y.dtype.type, np.int_):
            mask = np.isnan(self.values)
            y[mask] = 0
            result = y.cumsum(axis)
            has_obs = (-mask).astype(int).cumsum(axis) > 0
            result[-has_obs] = np.NaN
        else:
            result = y.cumsum(axis)

        return DataMatrix(result, index=self.index,
                          columns=self.columns, objects=self.objects)

    def min(self, axis=0):
        """
        Return array or Series of minimums over requested axis.

        Parameters
        ----------
        axis : {0, 1}
            0 for row-wise, 1 for column-wise

        Returns
        -------
        Series or TimeSeries
        """
        values = self.values.copy()
        np.putmask(values, -np.isfinite(values), np.inf)
        return Series(values.min(axis), index=self._get_agg_axis(axis))

    def max(self, axis=0):
        """
        Return array or Series of maximums over requested axis.

        Parameters
        ----------
        axis : {0, 1}
            0 for row-wise, 1 for column-wise

        Returns
        -------
        Series or TimeSeries
        """
        values = self.values.copy()
        np.putmask(values, -np.isfinite(values), -np.inf)
        return Series(values.max(axis), index=self._get_agg_axis(axis))

    def fillna(self, value=None, method='pad'):
        """
        Fill NaN values using the specified method.

        Member Series / TimeSeries are filled separately.

        Parameters
        ----------
        value : any kind (should be same type as array)
            Value to use to fill holes (e.g. 0)

        method : {'backfill', 'pad', None}
            Method to use for filling holes in new inde

        Returns
        -------
        y : DataMatrix

        See also
        --------
        DataMatrix.reindex, DataMatrix.asfreq
        """
        if value is None:
            result = {}
            series = self._series
            for col, s in series.iteritems():
                result[col] = s.fillna(method=method, value=value)

            return DataMatrix(result, index=self.index, objects=self.objects)
        else:
            # Float type values
            if len(self.columns) == 0:
                return self

            vals = self.values.copy()
            vals.flat[common.isnull(vals.ravel())] = value

            objects = None

            if self.objects is not None:
                objects = self.objects.copy()

            return DataMatrix(vals, index=self.index, columns=self.columns,
                              objects=objects)

    def xs(self, key):
        """
        Returns a row from the DataMatrix as a Series object.

        Parameters
        ----------
        key : some index contained in the index

        Returns
        -------
        Series
        """
        if key not in self.index:
            raise Exception('No cross-section for %s' % key)

        loc = self.index.indexMap[key]
        theSlice = self.values[loc, :].copy()
        xsIndex = self.columns

        result = Series(theSlice, index=xsIndex)

        if self.objects is not None and len(self.objects.columns) > 0:
            result = result.append(self.objects.xs(key))

        return result

    @property
    def T(self):
        """
        Returns a DataMatrix with the rows/columns switched.
        """
        if self.objects is not None:
            objectsT = self.objects.values.T
            valuesT = self.values.T
            newValues = np.concatenate((valuesT, objectsT), axis=0)
            newIndex = Index(np.concatenate((self.columns,
                                             self.objects.columns)))

            return DataMatrix(newValues, index=newIndex, columns=self.index)
        else:
            return DataMatrix(data=self.values.T, index=self.columns,
                              columns=self.index)

    def shift(self, periods, offset=None, timeRule=None):
        """
        Shift the underlying series of the DataMatrix and Series objects within
        by given number (positive or negative) of periods.

        Parameters
        ----------
        periods : int (+ or -)
            Number of periods to move
        offset : DateOffset, optional
            Increment to use from datetools module
        timeRule : string
            Time rule to use by name

        Returns
        -------
        DataMatrix
        """
        if periods == 0:
            return self

        if timeRule is not None and offset is None:
            offset = datetools.getOffset(timeRule)

        if offset is None:
            indexer = self._shift_indexer(periods)
            new_values = self.values.take(indexer, axis=0)
            new_index = self.index

            new_values = common.ensure_float(new_values)

            if periods > 0:
                new_values[:periods] = NaN
            else:
                new_values[periods:] = NaN
        else:
            new_index = self.index.shift(periods, offset)
            new_values = self.values.copy()

        if self.objects is not None:
            shifted_objects = self.objects.shift(periods, offset=offset,
                                                 timeRule=timeRule)

            shifted_objects.index = new_index
        else:
            shifted_objects = None

        return DataMatrix(data=new_values, index=new_index,
                          columns=self.columns, objects=shifted_objects)

def _reorder_columns(mat, current, desired):
    indexer, mask = common.get_indexer(current, desired, None)
    return mat.take(indexer[mask], axis=1)
