from warnings import catch_warnings
from itertools import combinations, product

import datetime as dt
import dateutil
import numpy as np
from numpy.random import randn

from datetime import datetime
from pandas.compat import StringIO, iteritems, PY2
import pandas as pd
from pandas import (DataFrame, concat,
                    read_csv, isna, Series, date_range,
                    Index, Panel, MultiIndex, Timestamp,
                    DatetimeIndex, Categorical)
from pandas.core.dtypes.dtypes import CategoricalDtype
from pandas.util import testing as tm
from pandas.util.testing import (assert_frame_equal,
                                 makeCustomDataframe as mkdf)

import pytest


@pytest.fixture(params=[True, False])
def sort(request):
    """Boolean sort keyword for concat and DataFrame.append."""
    return request.param


@pytest.fixture(params=[True, False, None])
def sort_with_none(request):
    """Boolean sort keyword for concat and DataFrame.append.

    Includes the default of None
    """
    # TODO: Replace with sort once keyword changes.
    return request.param


class ConcatenateBase(object):

    def setup_method(self, method):
        self.frame = DataFrame(tm.getSeriesData())
        self.mixed_frame = self.frame.copy()
        self.mixed_frame['foo'] = 'bar'


class TestConcatAppendCommon(ConcatenateBase):

    """
    Test common dtype coercion rules between concat and append.
    """

    def setup_method(self, method):

        dt_data = [pd.Timestamp('2011-01-01'),
                   pd.Timestamp('2011-01-02'),
                   pd.Timestamp('2011-01-03')]
        tz_data = [pd.Timestamp('2011-01-01', tz='US/Eastern'),
                   pd.Timestamp('2011-01-02', tz='US/Eastern'),
                   pd.Timestamp('2011-01-03', tz='US/Eastern')]

        td_data = [pd.Timedelta('1 days'),
                   pd.Timedelta('2 days'),
                   pd.Timedelta('3 days')]

        period_data = [pd.Period('2011-01', freq='M'),
                       pd.Period('2011-02', freq='M'),
                       pd.Period('2011-03', freq='M')]

        self.data = {'bool': [True, False, True],
                     'int64': [1, 2, 3],
                     'float64': [1.1, np.nan, 3.3],
                     'category': pd.Categorical(['X', 'Y', 'Z']),
                     'object': ['a', 'b', 'c'],
                     'datetime64[ns]': dt_data,
                     'datetime64[ns, US/Eastern]': tz_data,
                     'timedelta64[ns]': td_data,
                     'period[M]': period_data}

    def _check_expected_dtype(self, obj, label):
        """
        Check whether obj has expected dtype depending on label
        considering not-supported dtypes
        """
        if isinstance(obj, pd.Index):
            if label == 'bool':
                assert obj.dtype == 'object'
            else:
                assert obj.dtype == label
        elif isinstance(obj, pd.Series):
            if label.startswith('period'):
                assert obj.dtype == 'object'
            else:
                assert obj.dtype == label
        else:
            raise ValueError

    def test_dtypes(self):
        # to confirm test case covers intended dtypes
        for typ, vals in iteritems(self.data):
            self._check_expected_dtype(pd.Index(vals), typ)
            self._check_expected_dtype(pd.Series(vals), typ)

    def test_concatlike_same_dtypes(self):
        # GH 13660
        for typ1, vals1 in iteritems(self.data):

            vals2 = vals1
            vals3 = vals1

            if typ1 == 'category':
                exp_data = pd.Categorical(list(vals1) + list(vals2))
                exp_data3 = pd.Categorical(list(vals1) + list(vals2) +
                                           list(vals3))
            else:
                exp_data = vals1 + vals2
                exp_data3 = vals1 + vals2 + vals3

            # ----- Index ----- #

            # index.append
            res = pd.Index(vals1).append(pd.Index(vals2))
            exp = pd.Index(exp_data)
            tm.assert_index_equal(res, exp)

            # 3 elements
            res = pd.Index(vals1).append([pd.Index(vals2), pd.Index(vals3)])
            exp = pd.Index(exp_data3)
            tm.assert_index_equal(res, exp)

            # index.append name mismatch
            i1 = pd.Index(vals1, name='x')
            i2 = pd.Index(vals2, name='y')
            res = i1.append(i2)
            exp = pd.Index(exp_data)
            tm.assert_index_equal(res, exp)

            # index.append name match
            i1 = pd.Index(vals1, name='x')
            i2 = pd.Index(vals2, name='x')
            res = i1.append(i2)
            exp = pd.Index(exp_data, name='x')
            tm.assert_index_equal(res, exp)

            # cannot append non-index
            with tm.assert_raises_regex(TypeError,
                                        'all inputs must be Index'):
                pd.Index(vals1).append(vals2)

            with tm.assert_raises_regex(TypeError,
                                        'all inputs must be Index'):
                pd.Index(vals1).append([pd.Index(vals2), vals3])

            # ----- Series ----- #

            # series.append
            res = pd.Series(vals1).append(pd.Series(vals2),
                                          ignore_index=True)
            exp = pd.Series(exp_data)
            tm.assert_series_equal(res, exp, check_index_type=True)

            # concat
            res = pd.concat([pd.Series(vals1), pd.Series(vals2)],
                            ignore_index=True)
            tm.assert_series_equal(res, exp, check_index_type=True)

            # 3 elements
            res = pd.Series(vals1).append([pd.Series(vals2), pd.Series(vals3)],
                                          ignore_index=True)
            exp = pd.Series(exp_data3)
            tm.assert_series_equal(res, exp)

            res = pd.concat([pd.Series(vals1), pd.Series(vals2),
                             pd.Series(vals3)], ignore_index=True)
            tm.assert_series_equal(res, exp)

            # name mismatch
            s1 = pd.Series(vals1, name='x')
            s2 = pd.Series(vals2, name='y')
            res = s1.append(s2, ignore_index=True)
            exp = pd.Series(exp_data)
            tm.assert_series_equal(res, exp, check_index_type=True)

            res = pd.concat([s1, s2], ignore_index=True)
            tm.assert_series_equal(res, exp, check_index_type=True)

            # name match
            s1 = pd.Series(vals1, name='x')
            s2 = pd.Series(vals2, name='x')
            res = s1.append(s2, ignore_index=True)
            exp = pd.Series(exp_data, name='x')
            tm.assert_series_equal(res, exp, check_index_type=True)

            res = pd.concat([s1, s2], ignore_index=True)
            tm.assert_series_equal(res, exp, check_index_type=True)

            # cannot append non-index
            msg = (r'cannot concatenate object of type \"(.+?)\";'
                   ' only pd.Series, pd.DataFrame, and pd.Panel'
                   r' \(deprecated\) objs are valid')
            with tm.assert_raises_regex(TypeError, msg):
                pd.Series(vals1).append(vals2)

            with tm.assert_raises_regex(TypeError, msg):
                pd.Series(vals1).append([pd.Series(vals2), vals3])

            with tm.assert_raises_regex(TypeError, msg):
                pd.concat([pd.Series(vals1), vals2])

            with tm.assert_raises_regex(TypeError, msg):
                pd.concat([pd.Series(vals1), pd.Series(vals2), vals3])

    def test_concatlike_dtypes_coercion(self):
        # GH 13660
        for typ1, vals1 in iteritems(self.data):
            for typ2, vals2 in iteritems(self.data):

                vals3 = vals2

                # basically infer
                exp_index_dtype = None
                exp_series_dtype = None

                if typ1 == typ2:
                    # same dtype is tested in test_concatlike_same_dtypes
                    continue
                elif typ1 == 'category' or typ2 == 'category':
                    # ToDo: suspicious
                    continue

                # specify expected dtype
                if typ1 == 'bool' and typ2 in ('int64', 'float64'):
                    # series coerces to numeric based on numpy rule
                    # index doesn't because bool is object dtype
                    exp_series_dtype = typ2
                elif typ2 == 'bool' and typ1 in ('int64', 'float64'):
                    exp_series_dtype = typ1
                elif (typ1 == 'datetime64[ns, US/Eastern]' or
                      typ2 == 'datetime64[ns, US/Eastern]' or
                      typ1 == 'timedelta64[ns]' or
                      typ2 == 'timedelta64[ns]'):
                    exp_index_dtype = object
                    exp_series_dtype = object

                exp_data = vals1 + vals2
                exp_data3 = vals1 + vals2 + vals3

                # ----- Index ----- #

                # index.append
                res = pd.Index(vals1).append(pd.Index(vals2))
                exp = pd.Index(exp_data, dtype=exp_index_dtype)
                tm.assert_index_equal(res, exp)

                # 3 elements
                res = pd.Index(vals1).append([pd.Index(vals2),
                                              pd.Index(vals3)])
                exp = pd.Index(exp_data3, dtype=exp_index_dtype)
                tm.assert_index_equal(res, exp)

                # ----- Series ----- #

                # series.append
                res = pd.Series(vals1).append(pd.Series(vals2),
                                              ignore_index=True)
                exp = pd.Series(exp_data, dtype=exp_series_dtype)
                tm.assert_series_equal(res, exp, check_index_type=True)

                # concat
                res = pd.concat([pd.Series(vals1), pd.Series(vals2)],
                                ignore_index=True)
                tm.assert_series_equal(res, exp, check_index_type=True)

                # 3 elements
                res = pd.Series(vals1).append([pd.Series(vals2),
                                               pd.Series(vals3)],
                                              ignore_index=True)
                exp = pd.Series(exp_data3, dtype=exp_series_dtype)
                tm.assert_series_equal(res, exp)

                res = pd.concat([pd.Series(vals1), pd.Series(vals2),
                                 pd.Series(vals3)], ignore_index=True)
                tm.assert_series_equal(res, exp)

    def test_concatlike_common_coerce_to_pandas_object(self):
        # GH 13626
        # result must be Timestamp/Timedelta, not datetime.datetime/timedelta
        dti = pd.DatetimeIndex(['2011-01-01', '2011-01-02'])
        tdi = pd.TimedeltaIndex(['1 days', '2 days'])

        exp = pd.Index([pd.Timestamp('2011-01-01'),
                        pd.Timestamp('2011-01-02'),
                        pd.Timedelta('1 days'),
                        pd.Timedelta('2 days')])

        res = dti.append(tdi)
        tm.assert_index_equal(res, exp)
        assert isinstance(res[0], pd.Timestamp)
        assert isinstance(res[-1], pd.Timedelta)

        dts = pd.Series(dti)
        tds = pd.Series(tdi)
        res = dts.append(tds)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))
        assert isinstance(res.iloc[0], pd.Timestamp)
        assert isinstance(res.iloc[-1], pd.Timedelta)

        res = pd.concat([dts, tds])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))
        assert isinstance(res.iloc[0], pd.Timestamp)
        assert isinstance(res.iloc[-1], pd.Timedelta)

    def test_concatlike_datetimetz(self, tz_aware_fixture):
        tz = tz_aware_fixture
        # GH 7795
        dti1 = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], tz=tz)
        dti2 = pd.DatetimeIndex(['2012-01-01', '2012-01-02'], tz=tz)

        exp = pd.DatetimeIndex(['2011-01-01', '2011-01-02',
                                '2012-01-01', '2012-01-02'], tz=tz)

        res = dti1.append(dti2)
        tm.assert_index_equal(res, exp)

        dts1 = pd.Series(dti1)
        dts2 = pd.Series(dti2)
        res = dts1.append(dts2)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([dts1, dts2])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

    @pytest.mark.parametrize('tz',
                             ['UTC', 'US/Eastern', 'Asia/Tokyo', 'EST5EDT'])
    def test_concatlike_datetimetz_short(self, tz):
        # GH 7795
        ix1 = pd.DatetimeIndex(start='2014-07-15', end='2014-07-17',
                               freq='D', tz=tz)
        ix2 = pd.DatetimeIndex(['2014-07-11', '2014-07-21'], tz=tz)
        df1 = pd.DataFrame(0, index=ix1, columns=['A', 'B'])
        df2 = pd.DataFrame(0, index=ix2, columns=['A', 'B'])

        exp_idx = pd.DatetimeIndex(['2014-07-15', '2014-07-16',
                                    '2014-07-17', '2014-07-11',
                                    '2014-07-21'], tz=tz)
        exp = pd.DataFrame(0, index=exp_idx, columns=['A', 'B'])

        tm.assert_frame_equal(df1.append(df2), exp)
        tm.assert_frame_equal(pd.concat([df1, df2]), exp)

    def test_concatlike_datetimetz_to_object(self, tz_aware_fixture):
        tz = tz_aware_fixture
        # GH 13660

        # different tz coerces to object
        dti1 = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], tz=tz)
        dti2 = pd.DatetimeIndex(['2012-01-01', '2012-01-02'])

        exp = pd.Index([pd.Timestamp('2011-01-01', tz=tz),
                        pd.Timestamp('2011-01-02', tz=tz),
                        pd.Timestamp('2012-01-01'),
                        pd.Timestamp('2012-01-02')], dtype=object)

        res = dti1.append(dti2)
        tm.assert_index_equal(res, exp)

        dts1 = pd.Series(dti1)
        dts2 = pd.Series(dti2)
        res = dts1.append(dts2)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([dts1, dts2])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        # different tz
        dti3 = pd.DatetimeIndex(['2012-01-01', '2012-01-02'],
                                tz='US/Pacific')

        exp = pd.Index([pd.Timestamp('2011-01-01', tz=tz),
                        pd.Timestamp('2011-01-02', tz=tz),
                        pd.Timestamp('2012-01-01', tz='US/Pacific'),
                        pd.Timestamp('2012-01-02', tz='US/Pacific')],
                       dtype=object)

        res = dti1.append(dti3)
        # tm.assert_index_equal(res, exp)

        dts1 = pd.Series(dti1)
        dts3 = pd.Series(dti3)
        res = dts1.append(dts3)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([dts1, dts3])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

    def test_concatlike_common_period(self):
        # GH 13660
        pi1 = pd.PeriodIndex(['2011-01', '2011-02'], freq='M')
        pi2 = pd.PeriodIndex(['2012-01', '2012-02'], freq='M')

        exp = pd.PeriodIndex(['2011-01', '2011-02', '2012-01',
                              '2012-02'], freq='M')

        res = pi1.append(pi2)
        tm.assert_index_equal(res, exp)

        ps1 = pd.Series(pi1)
        ps2 = pd.Series(pi2)
        res = ps1.append(ps2)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([ps1, ps2])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

    def test_concatlike_common_period_diff_freq_to_object(self):
        # GH 13221
        pi1 = pd.PeriodIndex(['2011-01', '2011-02'], freq='M')
        pi2 = pd.PeriodIndex(['2012-01-01', '2012-02-01'], freq='D')

        exp = pd.Index([pd.Period('2011-01', freq='M'),
                        pd.Period('2011-02', freq='M'),
                        pd.Period('2012-01-01', freq='D'),
                        pd.Period('2012-02-01', freq='D')], dtype=object)

        res = pi1.append(pi2)
        tm.assert_index_equal(res, exp)

        ps1 = pd.Series(pi1)
        ps2 = pd.Series(pi2)
        res = ps1.append(ps2)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([ps1, ps2])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

    def test_concatlike_common_period_mixed_dt_to_object(self):
        # GH 13221
        # different datetimelike
        pi1 = pd.PeriodIndex(['2011-01', '2011-02'], freq='M')
        tdi = pd.TimedeltaIndex(['1 days', '2 days'])
        exp = pd.Index([pd.Period('2011-01', freq='M'),
                        pd.Period('2011-02', freq='M'),
                        pd.Timedelta('1 days'),
                        pd.Timedelta('2 days')], dtype=object)

        res = pi1.append(tdi)
        tm.assert_index_equal(res, exp)

        ps1 = pd.Series(pi1)
        tds = pd.Series(tdi)
        res = ps1.append(tds)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([ps1, tds])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        # inverse
        exp = pd.Index([pd.Timedelta('1 days'),
                        pd.Timedelta('2 days'),
                        pd.Period('2011-01', freq='M'),
                        pd.Period('2011-02', freq='M')], dtype=object)

        res = tdi.append(pi1)
        tm.assert_index_equal(res, exp)

        ps1 = pd.Series(pi1)
        tds = pd.Series(tdi)
        res = tds.append(ps1)
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

        res = pd.concat([tds, ps1])
        tm.assert_series_equal(res, pd.Series(exp, index=[0, 1, 0, 1]))

    def test_concat_categorical(self):
        # GH 13524

        # same categories -> category
        s1 = pd.Series([1, 2, np.nan], dtype='category')
        s2 = pd.Series([2, 1, 2], dtype='category')

        exp = pd.Series([1, 2, np.nan, 2, 1, 2], dtype='category')
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        # partially different categories => not-category
        s1 = pd.Series([3, 2], dtype='category')
        s2 = pd.Series([2, 1], dtype='category')

        exp = pd.Series([3, 2, 2, 1])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        # completely different categories (same dtype) => not-category
        s1 = pd.Series([10, 11, np.nan], dtype='category')
        s2 = pd.Series([np.nan, 1, 3, 2], dtype='category')

        exp = pd.Series([10, 11, np.nan, np.nan, 1, 3, 2])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

    def test_union_categorical_same_categories_different_order(self):
        # https://github.com/pandas-dev/pandas/issues/19096
        a = pd.Series(Categorical(['a', 'b', 'c'], categories=['a', 'b', 'c']))
        b = pd.Series(Categorical(['a', 'b', 'c'], categories=['b', 'a', 'c']))
        result = pd.concat([a, b], ignore_index=True)
        expected = pd.Series(Categorical(['a', 'b', 'c', 'a', 'b', 'c'],
                                         categories=['a', 'b', 'c']))
        tm.assert_series_equal(result, expected)

    def test_concat_categorical_coercion(self):
        # GH 13524

        # category + not-category => not-category
        s1 = pd.Series([1, 2, np.nan], dtype='category')
        s2 = pd.Series([2, 1, 2])

        exp = pd.Series([1, 2, np.nan, 2, 1, 2])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        # result shouldn't be affected by 1st elem dtype
        exp = pd.Series([2, 1, 2, 1, 2, np.nan])
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

        # all values are not in category => not-category
        s1 = pd.Series([3, 2], dtype='category')
        s2 = pd.Series([2, 1])

        exp = pd.Series([3, 2, 2, 1])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        exp = pd.Series([2, 1, 3, 2])
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

        # completely different categories => not-category
        s1 = pd.Series([10, 11, np.nan], dtype='category')
        s2 = pd.Series([1, 3, 2])

        exp = pd.Series([10, 11, np.nan, 1, 3, 2])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        exp = pd.Series([1, 3, 2, 10, 11, np.nan])
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

        # different dtype => not-category
        s1 = pd.Series([10, 11, np.nan], dtype='category')
        s2 = pd.Series(['a', 'b', 'c'])

        exp = pd.Series([10, 11, np.nan, 'a', 'b', 'c'])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        exp = pd.Series(['a', 'b', 'c', 10, 11, np.nan])
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

        # if normal series only contains NaN-likes => not-category
        s1 = pd.Series([10, 11], dtype='category')
        s2 = pd.Series([np.nan, np.nan, np.nan])

        exp = pd.Series([10, 11, np.nan, np.nan, np.nan])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        exp = pd.Series([np.nan, np.nan, np.nan, 10, 11])
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

    def test_concat_categorical_3elem_coercion(self):
        # GH 13524

        # mixed dtypes => not-category
        s1 = pd.Series([1, 2, np.nan], dtype='category')
        s2 = pd.Series([2, 1, 2], dtype='category')
        s3 = pd.Series([1, 2, 1, 2, np.nan])

        exp = pd.Series([1, 2, np.nan, 2, 1, 2, 1, 2, 1, 2, np.nan])
        tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
        tm.assert_series_equal(s1.append([s2, s3], ignore_index=True), exp)

        exp = pd.Series([1, 2, 1, 2, np.nan, 1, 2, np.nan, 2, 1, 2])
        tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s3.append([s1, s2], ignore_index=True), exp)

        # values are all in either category => not-category
        s1 = pd.Series([4, 5, 6], dtype='category')
        s2 = pd.Series([1, 2, 3], dtype='category')
        s3 = pd.Series([1, 3, 4])

        exp = pd.Series([4, 5, 6, 1, 2, 3, 1, 3, 4])
        tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
        tm.assert_series_equal(s1.append([s2, s3], ignore_index=True), exp)

        exp = pd.Series([1, 3, 4, 4, 5, 6, 1, 2, 3])
        tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s3.append([s1, s2], ignore_index=True), exp)

        # values are all in either category => not-category
        s1 = pd.Series([4, 5, 6], dtype='category')
        s2 = pd.Series([1, 2, 3], dtype='category')
        s3 = pd.Series([10, 11, 12])

        exp = pd.Series([4, 5, 6, 1, 2, 3, 10, 11, 12])
        tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
        tm.assert_series_equal(s1.append([s2, s3], ignore_index=True), exp)

        exp = pd.Series([10, 11, 12, 4, 5, 6, 1, 2, 3])
        tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s3.append([s1, s2], ignore_index=True), exp)

    def test_concat_categorical_multi_coercion(self):
        # GH 13524

        s1 = pd.Series([1, 3], dtype='category')
        s2 = pd.Series([3, 4], dtype='category')
        s3 = pd.Series([2, 3])
        s4 = pd.Series([2, 2], dtype='category')
        s5 = pd.Series([1, np.nan])
        s6 = pd.Series([1, 3, 2], dtype='category')

        # mixed dtype, values are all in categories => not-category
        exp = pd.Series([1, 3, 3, 4, 2, 3, 2, 2, 1, np.nan, 1, 3, 2])
        res = pd.concat([s1, s2, s3, s4, s5, s6], ignore_index=True)
        tm.assert_series_equal(res, exp)
        res = s1.append([s2, s3, s4, s5, s6], ignore_index=True)
        tm.assert_series_equal(res, exp)

        exp = pd.Series([1, 3, 2, 1, np.nan, 2, 2, 2, 3, 3, 4, 1, 3])
        res = pd.concat([s6, s5, s4, s3, s2, s1], ignore_index=True)
        tm.assert_series_equal(res, exp)
        res = s6.append([s5, s4, s3, s2, s1], ignore_index=True)
        tm.assert_series_equal(res, exp)

    def test_concat_categorical_ordered(self):
        # GH 13524

        s1 = pd.Series(pd.Categorical([1, 2, np.nan], ordered=True))
        s2 = pd.Series(pd.Categorical([2, 1, 2], ordered=True))

        exp = pd.Series(pd.Categorical([1, 2, np.nan, 2, 1, 2], ordered=True))
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        exp = pd.Series(pd.Categorical([1, 2, np.nan, 2, 1, 2, 1, 2, np.nan],
                                       ordered=True))
        tm.assert_series_equal(pd.concat([s1, s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s1.append([s2, s1], ignore_index=True), exp)

    def test_concat_categorical_coercion_nan(self):
        # GH 13524

        # some edge cases
        # category + not-category => not category
        s1 = pd.Series(np.array([np.nan, np.nan], dtype=np.float64),
                       dtype='category')
        s2 = pd.Series([np.nan, 1])

        exp = pd.Series([np.nan, np.nan, np.nan, 1])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        s1 = pd.Series([1, np.nan], dtype='category')
        s2 = pd.Series([np.nan, np.nan])

        exp = pd.Series([1, np.nan, np.nan, np.nan])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        # mixed dtype, all nan-likes => not-category
        s1 = pd.Series([np.nan, np.nan], dtype='category')
        s2 = pd.Series([np.nan, np.nan])

        exp = pd.Series([np.nan, np.nan, np.nan, np.nan])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)

        # all category nan-likes => category
        s1 = pd.Series([np.nan, np.nan], dtype='category')
        s2 = pd.Series([np.nan, np.nan], dtype='category')

        exp = pd.Series([np.nan, np.nan, np.nan, np.nan], dtype='category')

        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

    def test_concat_categorical_empty(self):
        # GH 13524

        s1 = pd.Series([], dtype='category')
        s2 = pd.Series([1, 2], dtype='category')

        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), s2)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), s2)

        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), s2)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), s2)

        s1 = pd.Series([], dtype='category')
        s2 = pd.Series([], dtype='category')

        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), s2)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), s2)

        s1 = pd.Series([], dtype='category')
        s2 = pd.Series([], dtype='object')

        # different dtype => not-category
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), s2)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), s2)
        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), s2)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), s2)

        s1 = pd.Series([], dtype='category')
        s2 = pd.Series([np.nan, np.nan])

        # empty Series is ignored
        exp = pd.Series([np.nan, np.nan])
        tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
        tm.assert_series_equal(s1.append(s2, ignore_index=True), exp)

        tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
        tm.assert_series_equal(s2.append(s1, ignore_index=True), exp)


class TestAppend(ConcatenateBase):

    def test_append(self, sort):
        begin_index = self.frame.index[:5]
        end_index = self.frame.index[5:]

        begin_frame = self.frame.reindex(begin_index)
        end_frame = self.frame.reindex(end_index)

        appended = begin_frame.append(end_frame)
        tm.assert_almost_equal(appended['A'], self.frame['A'])

        del end_frame['A']
        partial_appended = begin_frame.append(end_frame, sort=sort)
        assert 'A' in partial_appended

        partial_appended = end_frame.append(begin_frame, sort=sort)
        assert 'A' in partial_appended

        # mixed type handling
        appended = self.mixed_frame[:5].append(self.mixed_frame[5:])
        tm.assert_frame_equal(appended, self.mixed_frame)

        # what to test here
        mixed_appended = self.mixed_frame[:5].append(self.frame[5:], sort=sort)
        mixed_appended2 = self.frame[:5].append(self.mixed_frame[5:],
                                                sort=sort)

        # all equal except 'foo' column
        tm.assert_frame_equal(
            mixed_appended.reindex(columns=['A', 'B', 'C', 'D']),
            mixed_appended2.reindex(columns=['A', 'B', 'C', 'D']))

        # append empty
        empty = DataFrame({})

        appended = self.frame.append(empty)
        tm.assert_frame_equal(self.frame, appended)
        assert appended is not self.frame

        appended = empty.append(self.frame)
        tm.assert_frame_equal(self.frame, appended)
        assert appended is not self.frame

        # Overlap
        with pytest.raises(ValueError):
            self.frame.append(self.frame, verify_integrity=True)

        # see gh-6129: new columns
        df = DataFrame({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}})
        row = Series([5, 6, 7], index=['a', 'b', 'c'], name='z')
        expected = DataFrame({'a': {'x': 1, 'y': 2, 'z': 5}, 'b': {
                             'x': 3, 'y': 4, 'z': 6}, 'c': {'z': 7}})
        result = df.append(row)
        tm.assert_frame_equal(result, expected)

    def test_append_length0_frame(self, sort):
        df = DataFrame(columns=['A', 'B', 'C'])
        df3 = DataFrame(index=[0, 1], columns=['A', 'B'])
        df5 = df.append(df3, sort=sort)

        expected = DataFrame(index=[0, 1], columns=['A', 'B', 'C'])
        assert_frame_equal(df5, expected)

    def test_append_records(self):
        arr1 = np.zeros((2,), dtype=('i4,f4,a10'))
        arr1[:] = [(1, 2., 'Hello'), (2, 3., "World")]

        arr2 = np.zeros((3,), dtype=('i4,f4,a10'))
        arr2[:] = [(3, 4., 'foo'),
                   (5, 6., "bar"),
                   (7., 8., 'baz')]

        df1 = DataFrame(arr1)
        df2 = DataFrame(arr2)

        result = df1.append(df2, ignore_index=True)
        expected = DataFrame(np.concatenate((arr1, arr2)))
        assert_frame_equal(result, expected)

    # rewrite sort fixture, since we also want to test default of None
    def test_append_sorts(self, sort_with_none):
        df1 = pd.DataFrame({"a": [1, 2], "b": [1, 2]}, columns=['b', 'a'])
        df2 = pd.DataFrame({"a": [1, 2], 'c': [3, 4]}, index=[2, 3])

        if sort_with_none is None:
            # only warn if not explicitly specified
            # don't check stacklevel since its set for concat, and append
            # has an extra stack.
            ctx = tm.assert_produces_warning(FutureWarning,
                                             check_stacklevel=False)
        else:
            ctx = tm.assert_produces_warning(None)

        with ctx:
            result = df1.append(df2, sort=sort_with_none)

        # for None / True
        expected = pd.DataFrame({"b": [1, 2, None, None],
                                 "a": [1, 2, 1, 2],
                                 "c": [None, None, 3, 4]},
                                columns=['a', 'b', 'c'])
        if sort_with_none is False:
            expected = expected[['b', 'a', 'c']]
        tm.assert_frame_equal(result, expected)

    def test_append_different_columns(self, sort):
        df = DataFrame({'bools': np.random.randn(10) > 0,
                        'ints': np.random.randint(0, 10, 10),
                        'floats': np.random.randn(10),
                        'strings': ['foo', 'bar'] * 5})

        a = df[:5].loc[:, ['bools', 'ints', 'floats']]
        b = df[5:].loc[:, ['strings', 'ints', 'floats']]

        appended = a.append(b, sort=sort)
        assert isna(appended['strings'][0:4]).all()
        assert isna(appended['bools'][5:]).all()

    def test_append_many(self, sort):
        chunks = [self.frame[:5], self.frame[5:10],
                  self.frame[10:15], self.frame[15:]]

        result = chunks[0].append(chunks[1:])
        tm.assert_frame_equal(result, self.frame)

        chunks[-1] = chunks[-1].copy()
        chunks[-1]['foo'] = 'bar'
        result = chunks[0].append(chunks[1:], sort=sort)
        tm.assert_frame_equal(result.loc[:, self.frame.columns], self.frame)
        assert (result['foo'][15:] == 'bar').all()
        assert result['foo'][:15].isna().all()

    def test_append_preserve_index_name(self):
        # #980
        df1 = DataFrame(data=None, columns=['A', 'B', 'C'])
        df1 = df1.set_index(['A'])
        df2 = DataFrame(data=[[1, 4, 7], [2, 5, 8], [3, 6, 9]],
                        columns=['A', 'B', 'C'])
        df2 = df2.set_index(['A'])

        result = df1.append(df2)
        assert result.index.name == 'A'

    indexes_can_append = [
        pd.RangeIndex(3),
        pd.Index([4, 5, 6]),
        pd.Index([4.5, 5.5, 6.5]),
        pd.Index(list('abc')),
        pd.CategoricalIndex('A B C'.split()),
        pd.CategoricalIndex('D E F'.split(), ordered=True),
        pd.DatetimeIndex([dt.datetime(2013, 1, 3, 0, 0),
                          dt.datetime(2013, 1, 3, 6, 10),
                          dt.datetime(2013, 1, 3, 7, 12)]),
    ]

    indexes_cannot_append_with_other = [
        pd.IntervalIndex.from_breaks([0, 1, 2, 3]),
        pd.MultiIndex.from_arrays(['A B C'.split(), 'D E F'.split()]),
    ]

    all_indexes = indexes_can_append + indexes_cannot_append_with_other

    @pytest.mark.parametrize("index",
                             all_indexes,
                             ids=lambda x: x.__class__.__name__)
    def test_append_same_columns_type(self, index):
        # GH18359

        # df wider than ser
        df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=index)
        ser_index = index[:2]
        ser = pd.Series([7, 8], index=ser_index, name=2)
        result = df.append(ser)
        expected = pd.DataFrame([[1., 2., 3.], [4, 5, 6], [7, 8, np.nan]],
                                index=[0, 1, 2],
                                columns=index)
        assert_frame_equal(result, expected)

        # ser wider than df
        ser_index = index
        index = index[:2]
        df = pd.DataFrame([[1, 2], [4, 5]], columns=index)
        ser = pd.Series([7, 8, 9], index=ser_index, name=2)
        result = df.append(ser)
        expected = pd.DataFrame([[1, 2, np.nan], [4, 5, np.nan], [7, 8, 9]],
                                index=[0, 1, 2],
                                columns=ser_index)
        assert_frame_equal(result, expected)

    @pytest.mark.parametrize("df_columns, series_index",
                             combinations(indexes_can_append, r=2),
                             ids=lambda x: x.__class__.__name__)
    def test_append_different_columns_types(self, df_columns, series_index):
        # GH18359
        # See also test 'test_append_different_columns_types_raises' below
        # for errors raised when appending

        df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=df_columns)
        ser = pd.Series([7, 8, 9], index=series_index, name=2)

        result = df.append(ser)
        idx_diff = ser.index.difference(df_columns)
        combined_columns = Index(df_columns.tolist()).append(idx_diff)
        expected = pd.DataFrame([[1., 2., 3., np.nan, np.nan, np.nan],
                                 [4, 5, 6, np.nan, np.nan, np.nan],
                                 [np.nan, np.nan, np.nan, 7, 8, 9]],
                                index=[0, 1, 2],
                                columns=combined_columns)
        assert_frame_equal(result, expected)

    @pytest.mark.parametrize(
        "index_can_append, index_cannot_append_with_other",
        product(indexes_can_append, indexes_cannot_append_with_other),
        ids=lambda x: x.__class__.__name__)
    def test_append_different_columns_types_raises(
            self, index_can_append, index_cannot_append_with_other):
        # GH18359
        # Dataframe.append will raise if IntervalIndex/MultiIndex appends
        # or is appended to a different index type
        #
        # See also test 'test_append_different_columns_types' above for
        # appending without raising.

        df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=index_can_append)
        ser = pd.Series([7, 8, 9], index=index_cannot_append_with_other,
                        name=2)
        with pytest.raises(TypeError):
            df.append(ser)

        df = pd.DataFrame([[1, 2, 3], [4, 5, 6]],
                          columns=index_cannot_append_with_other)
        ser = pd.Series([7, 8, 9], index=index_can_append, name=2)
        with pytest.raises(TypeError):
            df.append(ser)

    def test_append_dtype_coerce(self, sort):

        # GH 4993
        # appending with datetime will incorrectly convert datetime64

        df1 = DataFrame(index=[1, 2], data=[dt.datetime(2013, 1, 1, 0, 0),
                                            dt.datetime(2013, 1, 2, 0, 0)],
                        columns=['start_time'])
        df2 = DataFrame(index=[4, 5], data=[[dt.datetime(2013, 1, 3, 0, 0),
                                             dt.datetime(2013, 1, 3, 6, 10)],
                                            [dt.datetime(2013, 1, 4, 0, 0),
                                             dt.datetime(2013, 1, 4, 7, 10)]],
                        columns=['start_time', 'end_time'])

        expected = concat([Series([pd.NaT,
                                   pd.NaT,
                                   dt.datetime(2013, 1, 3, 6, 10),
                                   dt.datetime(2013, 1, 4, 7, 10)],
                                  name='end_time'),
                           Series([dt.datetime(2013, 1, 1, 0, 0),
                                   dt.datetime(2013, 1, 2, 0, 0),
                                   dt.datetime(2013, 1, 3, 0, 0),
                                   dt.datetime(2013, 1, 4, 0, 0)],
                                  name='start_time')],
                          axis=1, sort=sort)
        result = df1.append(df2, ignore_index=True, sort=sort)
        if sort:
            expected = expected[['end_time', 'start_time']]
        else:
            expected = expected[['start_time', 'end_time']]

        assert_frame_equal(result, expected)

    def test_append_missing_column_proper_upcast(self, sort):
        df1 = DataFrame({'A': np.array([1, 2, 3, 4], dtype='i8')})
        df2 = DataFrame({'B': np.array([True, False, True, False],
                                       dtype=bool)})

        appended = df1.append(df2, ignore_index=True, sort=sort)
        assert appended['A'].dtype == 'f8'
        assert appended['B'].dtype == 'O'


class TestConcatenate(ConcatenateBase):

    def test_concat_copy(self):
        df = DataFrame(np.random.randn(4, 3))
        df2 = DataFrame(np.random.randint(0, 10, size=4).reshape(4, 1))
        df3 = DataFrame({5: 'foo'}, index=range(4))

        # These are actual copies.
        result = concat([df, df2, df3], axis=1, copy=True)

        for b in result._data.blocks:
            assert b.values.base is None

        # These are the same.
        result = concat([df, df2, df3], axis=1, copy=False)

        for b in result._data.blocks:
            if b.is_float:
                assert b.values.base is df._data.blocks[0].values.base
            elif b.is_integer:
                assert b.values.base is df2._data.blocks[0].values.base
            elif b.is_object:
                assert b.values.base is not None

        # Float block was consolidated.
        df4 = DataFrame(np.random.randn(4, 1))
        result = concat([df, df2, df3, df4], axis=1, copy=False)
        for b in result._data.blocks:
            if b.is_float:
                assert b.values.base is None
            elif b.is_integer:
                assert b.values.base is df2._data.blocks[0].values.base
            elif b.is_object:
                assert b.values.base is not None

    def test_concat_with_group_keys(self):
        df = DataFrame(np.random.randn(4, 3))
        df2 = DataFrame(np.random.randn(4, 4))

        # axis=0
        df = DataFrame(np.random.randn(3, 4))
        df2 = DataFrame(np.random.randn(4, 4))

        result = concat([df, df2], keys=[0, 1])
        exp_index = MultiIndex.from_arrays([[0, 0, 0, 1, 1, 1, 1],
                                            [0, 1, 2, 0, 1, 2, 3]])
        expected = DataFrame(np.r_[df.values, df2.values],
                             index=exp_index)
        tm.assert_frame_equal(result, expected)

        result = concat([df, df], keys=[0, 1])
        exp_index2 = MultiIndex.from_arrays([[0, 0, 0, 1, 1, 1],
                                             [0, 1, 2, 0, 1, 2]])
        expected = DataFrame(np.r_[df.values, df.values],
                             index=exp_index2)
        tm.assert_frame_equal(result, expected)

        # axis=1
        df = DataFrame(np.random.randn(4, 3))
        df2 = DataFrame(np.random.randn(4, 4))

        result = concat([df, df2], keys=[0, 1], axis=1)
        expected = DataFrame(np.c_[df.values, df2.values],
                             columns=exp_index)
        tm.assert_frame_equal(result, expected)

        result = concat([df, df], keys=[0, 1], axis=1)
        expected = DataFrame(np.c_[df.values, df.values],
                             columns=exp_index2)
        tm.assert_frame_equal(result, expected)

    def test_concat_keys_specific_levels(self):
        df = DataFrame(np.random.randn(10, 4))
        pieces = [df.iloc[:, [0, 1]], df.iloc[:, [2]], df.iloc[:, [3]]]
        level = ['three', 'two', 'one', 'zero']
        result = concat(pieces, axis=1, keys=['one', 'two', 'three'],
                        levels=[level],
                        names=['group_key'])

        tm.assert_index_equal(result.columns.levels[0],
                              Index(level, name='group_key'))
        assert result.columns.names[0] == 'group_key'

    def test_concat_dataframe_keys_bug(self, sort):
        t1 = DataFrame({
            'value': Series([1, 2, 3], index=Index(['a', 'b', 'c'],
                                                   name='id'))})
        t2 = DataFrame({
            'value': Series([7, 8], index=Index(['a', 'b'], name='id'))})

        # it works
        result = concat([t1, t2], axis=1, keys=['t1', 't2'], sort=sort)
        assert list(result.columns) == [('t1', 'value'), ('t2', 'value')]

    def test_concat_series_partial_columns_names(self):
        # GH10698
        foo = Series([1, 2], name='foo')
        bar = Series([1, 2])
        baz = Series([4, 5])

        result = concat([foo, bar, baz], axis=1)
        expected = DataFrame({'foo': [1, 2], 0: [1, 2], 1: [
                             4, 5]}, columns=['foo', 0, 1])
        tm.assert_frame_equal(result, expected)

        result = concat([foo, bar, baz], axis=1, keys=[
                        'red', 'blue', 'yellow'])
        expected = DataFrame({'red': [1, 2], 'blue': [1, 2], 'yellow': [
                             4, 5]}, columns=['red', 'blue', 'yellow'])
        tm.assert_frame_equal(result, expected)

        result = concat([foo, bar, baz], axis=1, ignore_index=True)
        expected = DataFrame({0: [1, 2], 1: [1, 2], 2: [4, 5]})
        tm.assert_frame_equal(result, expected)

    def test_concat_dict(self):
        frames = {'foo': DataFrame(np.random.randn(4, 3)),
                  'bar': DataFrame(np.random.randn(4, 3)),
                  'baz': DataFrame(np.random.randn(4, 3)),
                  'qux': DataFrame(np.random.randn(4, 3))}

        sorted_keys = sorted(frames)

        result = concat(frames)
        expected = concat([frames[k] for k in sorted_keys], keys=sorted_keys)
        tm.assert_frame_equal(result, expected)

        result = concat(frames, axis=1)
        expected = concat([frames[k] for k in sorted_keys], keys=sorted_keys,
                          axis=1)
        tm.assert_frame_equal(result, expected)

        keys = ['baz', 'foo', 'bar']
        result = concat(frames, keys=keys)
        expected = concat([frames[k] for k in keys], keys=keys)
        tm.assert_frame_equal(result, expected)

    def test_concat_ignore_index(self, sort):
        frame1 = DataFrame({"test1": ["a", "b", "c"],
                            "test2": [1, 2, 3],
                            "test3": [4.5, 3.2, 1.2]})
        frame2 = DataFrame({"test3": [5.2, 2.2, 4.3]})
        frame1.index = Index(["x", "y", "z"])
        frame2.index = Index(["x", "y", "q"])

        v1 = concat([frame1, frame2], axis=1,
                    ignore_index=True, sort=sort)

        nan = np.nan
        expected = DataFrame([[nan, nan, nan, 4.3],
                              ['a', 1, 4.5, 5.2],
                              ['b', 2, 3.2, 2.2],
                              ['c', 3, 1.2, nan]],
                             index=Index(["q", "x", "y", "z"]))
        if not sort:
            expected = expected.loc[['x', 'y', 'z', 'q']]

        tm.assert_frame_equal(v1, expected)

    def test_concat_multiindex_with_keys(self):
        index = MultiIndex(levels=[['foo', 'bar', 'baz', 'qux'],
                                   ['one', 'two', 'three']],
                           labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3],
                                   [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
                           names=['first', 'second'])
        frame = DataFrame(np.random.randn(10, 3), index=index,
                          columns=Index(['A', 'B', 'C'], name='exp'))
        result = concat([frame, frame], keys=[0, 1], names=['iteration'])

        assert result.index.names == ('iteration',) + index.names
        tm.assert_frame_equal(result.loc[0], frame)
        tm.assert_frame_equal(result.loc[1], frame)
        assert result.index.nlevels == 3

    def test_concat_multiindex_with_tz(self):
        # GH 6606
        df = DataFrame({'dt': [datetime(2014, 1, 1),
                               datetime(2014, 1, 2),
                               datetime(2014, 1, 3)],
                        'b': ['A', 'B', 'C'],
                        'c': [1, 2, 3], 'd': [4, 5, 6]})
        df['dt'] = df['dt'].apply(lambda d: Timestamp(d, tz='US/Pacific'))
        df = df.set_index(['dt', 'b'])

        exp_idx1 = DatetimeIndex(['2014-01-01', '2014-01-02',
                                  '2014-01-03'] * 2,
                                 tz='US/Pacific', name='dt')
        exp_idx2 = Index(['A', 'B', 'C'] * 2, name='b')
        exp_idx = MultiIndex.from_arrays([exp_idx1, exp_idx2])
        expected = DataFrame({'c': [1, 2, 3] * 2, 'd': [4, 5, 6] * 2},
                             index=exp_idx, columns=['c', 'd'])

        result = concat([df, df])
        tm.assert_frame_equal(result, expected)

    def test_concat_multiindex_with_none_in_index_names(self):
        # GH 15787
        index = pd.MultiIndex.from_product([[1], range(5)],
                                           names=['level1', None])
        df = pd.DataFrame({'col': range(5)}, index=index, dtype=np.int32)

        result = concat([df, df], keys=[1, 2], names=['level2'])
        index = pd.MultiIndex.from_product([[1, 2], [1], range(5)],
                                           names=['level2', 'level1', None])
        expected = pd.DataFrame({'col': list(range(5)) * 2},
                                index=index, dtype=np.int32)
        assert_frame_equal(result, expected)

        result = concat([df, df[:2]], keys=[1, 2], names=['level2'])
        level2 = [1] * 5 + [2] * 2
        level1 = [1] * 7
        no_name = list(range(5)) + list(range(2))
        tuples = list(zip(level2, level1, no_name))
        index = pd.MultiIndex.from_tuples(tuples,
                                          names=['level2', 'level1', None])
        expected = pd.DataFrame({'col': no_name}, index=index,
                                dtype=np.int32)
        assert_frame_equal(result, expected)

    def test_concat_keys_and_levels(self):
        df = DataFrame(np.random.randn(1, 3))
        df2 = DataFrame(np.random.randn(1, 4))

        levels = [['foo', 'baz'], ['one', 'two']]
        names = ['first', 'second']
        result = concat([df, df2, df, df2],
                        keys=[('foo', 'one'), ('foo', 'two'),
                              ('baz', 'one'), ('baz', 'two')],
                        levels=levels,
                        names=names)
        expected = concat([df, df2, df, df2])
        exp_index = MultiIndex(levels=levels + [[0]],
                               labels=[[0, 0, 1, 1], [0, 1, 0, 1],
                                       [0, 0, 0, 0]],
                               names=names + [None])
        expected.index = exp_index

        tm.assert_frame_equal(result, expected)

        # no names
        result = concat([df, df2, df, df2],
                        keys=[('foo', 'one'), ('foo', 'two'),
                              ('baz', 'one'), ('baz', 'two')],
                        levels=levels)
        assert result.index.names == (None,) * 3

        # no levels
        result = concat([df, df2, df, df2],
                        keys=[('foo', 'one'), ('foo', 'two'),
                              ('baz', 'one'), ('baz', 'two')],
                        names=['first', 'second'])
        assert result.index.names == ('first', 'second') + (None,)
        tm.assert_index_equal(result.index.levels[0],
                              Index(['baz', 'foo'], name='first'))

    def test_concat_keys_levels_no_overlap(self):
        # GH #1406
        df = DataFrame(np.random.randn(1, 3), index=['a'])
        df2 = DataFrame(np.random.randn(1, 4), index=['b'])

        pytest.raises(ValueError, concat, [df, df],
                      keys=['one', 'two'], levels=[['foo', 'bar', 'baz']])

        pytest.raises(ValueError, concat, [df, df2],
                      keys=['one', 'two'], levels=[['foo', 'bar', 'baz']])

    def test_concat_rename_index(self):
        a = DataFrame(np.random.rand(3, 3),
                      columns=list('ABC'),
                      index=Index(list('abc'), name='index_a'))
        b = DataFrame(np.random.rand(3, 3),
                      columns=list('ABC'),
                      index=Index(list('abc'), name='index_b'))

        result = concat([a, b], keys=['key0', 'key1'],
                        names=['lvl0', 'lvl1'])

        exp = concat([a, b], keys=['key0', 'key1'], names=['lvl0'])
        names = list(exp.index.names)
        names[1] = 'lvl1'
        exp.index.set_names(names, inplace=True)

        tm.assert_frame_equal(result, exp)
        assert result.index.names == exp.index.names

    def test_crossed_dtypes_weird_corner(self):
        columns = ['A', 'B', 'C', 'D']
        df1 = DataFrame({'A': np.array([1, 2, 3, 4], dtype='f8'),
                         'B': np.array([1, 2, 3, 4], dtype='i8'),
                         'C': np.array([1, 2, 3, 4], dtype='f8'),
                         'D': np.array([1, 2, 3, 4], dtype='i8')},
                        columns=columns)

        df2 = DataFrame({'A': np.array([1, 2, 3, 4], dtype='i8'),
                         'B': np.array([1, 2, 3, 4], dtype='f8'),
                         'C': np.array([1, 2, 3, 4], dtype='i8'),
                         'D': np.array([1, 2, 3, 4], dtype='f8')},
                        columns=columns)

        appended = df1.append(df2, ignore_index=True)
        expected = DataFrame(np.concatenate([df1.values, df2.values], axis=0),
                             columns=columns)
        tm.assert_frame_equal(appended, expected)

        df = DataFrame(np.random.randn(1, 3), index=['a'])
        df2 = DataFrame(np.random.randn(1, 4), index=['b'])
        result = concat(
            [df, df2], keys=['one', 'two'], names=['first', 'second'])
        assert result.index.names == ('first', 'second')

    def test_dups_index(self):
        # GH 4771

        # single dtypes
        df = DataFrame(np.random.randint(0, 10, size=40).reshape(
            10, 4), columns=['A', 'A', 'C', 'C'])

        result = concat([df, df], axis=1)
        assert_frame_equal(result.iloc[:, :4], df)
        assert_frame_equal(result.iloc[:, 4:], df)

        result = concat([df, df], axis=0)
        assert_frame_equal(result.iloc[:10], df)
        assert_frame_equal(result.iloc[10:], df)

        # multi dtypes
        df = concat([DataFrame(np.random.randn(10, 4),
                               columns=['A', 'A', 'B', 'B']),
                     DataFrame(np.random.randint(0, 10, size=20)
                               .reshape(10, 2),
                               columns=['A', 'C'])],
                    axis=1)

        result = concat([df, df], axis=1)
        assert_frame_equal(result.iloc[:, :6], df)
        assert_frame_equal(result.iloc[:, 6:], df)

        result = concat([df, df], axis=0)
        assert_frame_equal(result.iloc[:10], df)
        assert_frame_equal(result.iloc[10:], df)

        # append
        result = df.iloc[0:8, :].append(df.iloc[8:])
        assert_frame_equal(result, df)

        result = df.iloc[0:8, :].append(df.iloc[8:9]).append(df.iloc[9:10])
        assert_frame_equal(result, df)

        expected = concat([df, df], axis=0)
        result = df.append(df)
        assert_frame_equal(result, expected)

    def test_with_mixed_tuples(self, sort):
        # 10697
        # columns have mixed tuples, so handle properly
        df1 = DataFrame({u'A': 'foo', (u'B', 1): 'bar'}, index=range(2))
        df2 = DataFrame({u'B': 'foo', (u'B', 1): 'bar'}, index=range(2))

        # it works
        concat([df1, df2], sort=sort)

    def test_handle_empty_objects(self, sort):
        df = DataFrame(np.random.randn(10, 4), columns=list('abcd'))

        baz = df[:5].copy()
        baz['foo'] = 'bar'
        empty = df[5:5]

        frames = [baz, empty, empty, df[5:]]
        concatted = concat(frames, axis=0, sort=sort)

        expected = df.reindex(columns=['a', 'b', 'c', 'd', 'foo'])
        expected['foo'] = expected['foo'].astype('O')
        expected.loc[0:4, 'foo'] = 'bar'

        tm.assert_frame_equal(concatted, expected)

        # empty as first element with time series
        # GH3259
        df = DataFrame(dict(A=range(10000)), index=date_range(
            '20130101', periods=10000, freq='s'))
        empty = DataFrame()
        result = concat([df, empty], axis=1)
        assert_frame_equal(result, df)
        result = concat([empty, df], axis=1)
        assert_frame_equal(result, df)

        result = concat([df, empty])
        assert_frame_equal(result, df)
        result = concat([empty, df])
        assert_frame_equal(result, df)

    def test_concat_mixed_objs(self):

        # concat mixed series/frames
        # G2385

        # axis 1
        index = date_range('01-Jan-2013', periods=10, freq='H')
        arr = np.arange(10, dtype='int64')
        s1 = Series(arr, index=index)
        s2 = Series(arr, index=index)
        df = DataFrame(arr.reshape(-1, 1), index=index)

        expected = DataFrame(np.repeat(arr, 2).reshape(-1, 2),
                             index=index, columns=[0, 0])
        result = concat([df, df], axis=1)
        assert_frame_equal(result, expected)

        expected = DataFrame(np.repeat(arr, 2).reshape(-1, 2),
                             index=index, columns=[0, 1])
        result = concat([s1, s2], axis=1)
        assert_frame_equal(result, expected)

        expected = DataFrame(np.repeat(arr, 3).reshape(-1, 3),
                             index=index, columns=[0, 1, 2])
        result = concat([s1, s2, s1], axis=1)
        assert_frame_equal(result, expected)

        expected = DataFrame(np.repeat(arr, 5).reshape(-1, 5),
                             index=index, columns=[0, 0, 1, 2, 3])
        result = concat([s1, df, s2, s2, s1], axis=1)
        assert_frame_equal(result, expected)

        # with names
        s1.name = 'foo'
        expected = DataFrame(np.repeat(arr, 3).reshape(-1, 3),
                             index=index, columns=['foo', 0, 0])
        result = concat([s1, df, s2], axis=1)
        assert_frame_equal(result, expected)

        s2.name = 'bar'
        expected = DataFrame(np.repeat(arr, 3).reshape(-1, 3),
                             index=index, columns=['foo', 0, 'bar'])
        result = concat([s1, df, s2], axis=1)
        assert_frame_equal(result, expected)

        # ignore index
        expected = DataFrame(np.repeat(arr, 3).reshape(-1, 3),
                             index=index, columns=[0, 1, 2])
        result = concat([s1, df, s2], axis=1, ignore_index=True)
        assert_frame_equal(result, expected)

        # axis 0
        expected = DataFrame(np.tile(arr, 3).reshape(-1, 1),
                             index=index.tolist() * 3, columns=[0])
        result = concat([s1, df, s2])
        assert_frame_equal(result, expected)

        expected = DataFrame(np.tile(arr, 3).reshape(-1, 1), columns=[0])
        result = concat([s1, df, s2], ignore_index=True)
        assert_frame_equal(result, expected)

        # invalid concatente of mixed dims
        with catch_warnings(record=True):
            panel = tm.makePanel()
            pytest.raises(ValueError, lambda: concat([panel, s1], axis=1))

    def test_empty_dtype_coerce(self):

        # xref to #12411
        # xref to #12045
        # xref to #11594
        # see below

        # 10571
        df1 = DataFrame(data=[[1, None], [2, None]], columns=['a', 'b'])
        df2 = DataFrame(data=[[3, None], [4, None]], columns=['a', 'b'])
        result = concat([df1, df2])
        expected = df1.dtypes
        tm.assert_series_equal(result.dtypes, expected)

    def test_dtype_coerceion(self):

        # 12411
        df = DataFrame({'date': [pd.Timestamp('20130101').tz_localize('UTC'),
                                 pd.NaT]})

        result = concat([df.iloc[[0]], df.iloc[[1]]])
        tm.assert_series_equal(result.dtypes, df.dtypes)

        # 12045
        import datetime
        df = DataFrame({'date': [datetime.datetime(2012, 1, 1),
                                 datetime.datetime(1012, 1, 2)]})
        result = concat([df.iloc[[0]], df.iloc[[1]]])
        tm.assert_series_equal(result.dtypes, df.dtypes)

        # 11594
        df = DataFrame({'text': ['some words'] + [None] * 9})
        result = concat([df.iloc[[0]], df.iloc[[1]]])
        tm.assert_series_equal(result.dtypes, df.dtypes)

    def test_panel_concat_other_axes(self):
        with catch_warnings(record=True):
            panel = tm.makePanel()

            p1 = panel.iloc[:, :5, :]
            p2 = panel.iloc[:, 5:, :]

            result = concat([p1, p2], axis=1)
            tm.assert_panel_equal(result, panel)

            p1 = panel.iloc[:, :, :2]
            p2 = panel.iloc[:, :, 2:]

            result = concat([p1, p2], axis=2)
            tm.assert_panel_equal(result, panel)

            # if things are a bit misbehaved
            p1 = panel.iloc[:2, :, :2]
            p2 = panel.iloc[:, :, 2:]
            p1['ItemC'] = 'baz'

            result = concat([p1, p2], axis=2)

            expected = panel.copy()
            expected['ItemC'] = expected['ItemC'].astype('O')
            expected.loc['ItemC', :, :2] = 'baz'
            tm.assert_panel_equal(result, expected)

    def test_panel_concat_buglet(self, sort):
        with catch_warnings(record=True):
            # #2257
            def make_panel():
                index = 5
                cols = 3

                def df():
                    return DataFrame(np.random.randn(index, cols),
                                     index=["I%s" % i for i in range(index)],
                                     columns=["C%s" % i for i in range(cols)])
                return Panel(dict(("Item%s" % x, df())
                                  for x in ['A', 'B', 'C']))

            panel1 = make_panel()
            panel2 = make_panel()

            panel2 = panel2.rename_axis(dict((x, "%s_1" % x)
                                             for x in panel2.major_axis),
                                        axis=1)

            panel3 = panel2.rename_axis(lambda x: '%s_1' % x, axis=1)
            panel3 = panel3.rename_axis(lambda x: '%s_1' % x, axis=2)

            # it works!
            concat([panel1, panel3], axis=1, verify_integrity=True, sort=sort)

    def test_concat_series(self):

        ts = tm.makeTimeSeries()
        ts.name = 'foo'

        pieces = [ts[:5], ts[5:15], ts[15:]]

        result = concat(pieces)
        tm.assert_series_equal(result, ts)
        assert result.name == ts.name

        result = concat(pieces, keys=[0, 1, 2])
        expected = ts.copy()

        ts.index = DatetimeIndex(np.array(ts.index.values, dtype='M8[ns]'))

        exp_labels = [np.repeat([0, 1, 2], [len(x) for x in pieces]),
                      np.arange(len(ts))]
        exp_index = MultiIndex(levels=[[0, 1, 2], ts.index],
                               labels=exp_labels)
        expected.index = exp_index
        tm.assert_series_equal(result, expected)

    def test_concat_series_axis1(self, sort=sort):
        ts = tm.makeTimeSeries()

        pieces = [ts[:-2], ts[2:], ts[2:-2]]

        result = concat(pieces, axis=1)
        expected = DataFrame(pieces).T
        assert_frame_equal(result, expected)

        result = concat(pieces, keys=['A', 'B', 'C'], axis=1)
        expected = DataFrame(pieces, index=['A', 'B', 'C']).T
        assert_frame_equal(result, expected)

        # preserve series names, #2489
        s = Series(randn(5), name='A')
        s2 = Series(randn(5), name='B')

        result = concat([s, s2], axis=1)
        expected = DataFrame({'A': s, 'B': s2})
        assert_frame_equal(result, expected)

        s2.name = None
        result = concat([s, s2], axis=1)
        tm.assert_index_equal(result.columns,
                              Index(['A', 0], dtype='object'))

        # must reindex, #2603
        s = Series(randn(3), index=['c', 'a', 'b'], name='A')
        s2 = Series(randn(4), index=['d', 'a', 'b', 'c'], name='B')
        result = concat([s, s2], axis=1, sort=sort)
        expected = DataFrame({'A': s, 'B': s2})
        assert_frame_equal(result, expected)

    def test_concat_single_with_key(self):
        df = DataFrame(np.random.randn(10, 4))

        result = concat([df], keys=['foo'])
        expected = concat([df, df], keys=['foo', 'bar'])
        tm.assert_frame_equal(result, expected[:10])

    def test_concat_exclude_none(self):
        df = DataFrame(np.random.randn(10, 4))

        pieces = [df[:5], None, None, df[5:]]
        result = concat(pieces)
        tm.assert_frame_equal(result, df)
        pytest.raises(ValueError, concat, [None, None])

    def test_concat_datetime64_block(self):
        from pandas.core.indexes.datetimes import date_range

        rng = date_range('1/1/2000', periods=10)

        df = DataFrame({'time': rng})

        result = concat([df, df])
        assert (result.iloc[:10]['time'] == rng).all()
        assert (result.iloc[10:]['time'] == rng).all()

    def test_concat_timedelta64_block(self):
        from pandas import to_timedelta

        rng = to_timedelta(np.arange(10), unit='s')

        df = DataFrame({'time': rng})

        result = concat([df, df])
        assert (result.iloc[:10]['time'] == rng).all()
        assert (result.iloc[10:]['time'] == rng).all()

    def test_concat_keys_with_none(self):
        # #1649
        df0 = DataFrame([[10, 20, 30], [10, 20, 30], [10, 20, 30]])

        result = concat(dict(a=None, b=df0, c=df0[:2], d=df0[:1], e=df0))
        expected = concat(dict(b=df0, c=df0[:2], d=df0[:1], e=df0))
        tm.assert_frame_equal(result, expected)

        result = concat([None, df0, df0[:2], df0[:1], df0],
                        keys=['a', 'b', 'c', 'd', 'e'])
        expected = concat([df0, df0[:2], df0[:1], df0],
                          keys=['b', 'c', 'd', 'e'])
        tm.assert_frame_equal(result, expected)

    def test_concat_bug_1719(self):
        ts1 = tm.makeTimeSeries()
        ts2 = tm.makeTimeSeries()[::2]

        # to join with union
        # these two are of different length!
        left = concat([ts1, ts2], join='outer', axis=1)
        right = concat([ts2, ts1], join='outer', axis=1)

        assert len(left) == len(right)

    def test_concat_bug_2972(self):
        ts0 = Series(np.zeros(5))
        ts1 = Series(np.ones(5))
        ts0.name = ts1.name = 'same name'
        result = concat([ts0, ts1], axis=1)

        expected = DataFrame({0: ts0, 1: ts1})
        expected.columns = ['same name', 'same name']
        assert_frame_equal(result, expected)

    def test_concat_bug_3602(self):

        # GH 3602, duplicate columns
        df1 = DataFrame({'firmNo': [0, 0, 0, 0], 'prc': [6, 6, 6, 6],
                         'stringvar': ['rrr', 'rrr', 'rrr', 'rrr']})
        df2 = DataFrame({'C': [9, 10, 11, 12], 'misc': [1, 2, 3, 4],
                         'prc': [6, 6, 6, 6]})
        expected = DataFrame([[0, 6, 'rrr', 9, 1, 6],
                              [0, 6, 'rrr', 10, 2, 6],
                              [0, 6, 'rrr', 11, 3, 6],
                              [0, 6, 'rrr', 12, 4, 6]])
        expected.columns = ['firmNo', 'prc', 'stringvar', 'C', 'misc', 'prc']

        result = concat([df1, df2], axis=1)
        assert_frame_equal(result, expected)

    def test_concat_inner_join_empty(self):
        # GH 15328
        df_empty = pd.DataFrame()
        df_a = pd.DataFrame({'a': [1, 2]}, index=[0, 1], dtype='int64')
        df_expected = pd.DataFrame({'a': []}, index=[], dtype='int64')

        for how, expected in [('inner', df_expected), ('outer', df_a)]:
            result = pd.concat([df_a, df_empty], axis=1, join=how)
            assert_frame_equal(result, expected)

    def test_concat_series_axis1_same_names_ignore_index(self):
        dates = date_range('01-Jan-2013', '01-Jan-2014', freq='MS')[0:-1]
        s1 = Series(randn(len(dates)), index=dates, name='value')
        s2 = Series(randn(len(dates)), index=dates, name='value')

        result = concat([s1, s2], axis=1, ignore_index=True)
        expected = Index([0, 1])

        tm.assert_index_equal(result.columns, expected)

    def test_concat_iterables(self):
        from collections import deque, Iterable

        # GH8645 check concat works with tuples, list, generators, and weird
        # stuff like deque and custom iterables
        df1 = DataFrame([1, 2, 3])
        df2 = DataFrame([4, 5, 6])
        expected = DataFrame([1, 2, 3, 4, 5, 6])
        assert_frame_equal(concat((df1, df2), ignore_index=True), expected)
        assert_frame_equal(concat([df1, df2], ignore_index=True), expected)
        assert_frame_equal(concat((df for df in (df1, df2)),
                                  ignore_index=True), expected)
        assert_frame_equal(
            concat(deque((df1, df2)), ignore_index=True), expected)

        class CustomIterator1(object):

            def __len__(self):
                return 2

            def __getitem__(self, index):
                try:
                    return {0: df1, 1: df2}[index]
                except KeyError:
                    raise IndexError
        assert_frame_equal(pd.concat(CustomIterator1(),
                                     ignore_index=True), expected)

        class CustomIterator2(Iterable):

            def __iter__(self):
                yield df1
                yield df2
        assert_frame_equal(pd.concat(CustomIterator2(),
                                     ignore_index=True), expected)

    def test_concat_invalid(self):

        # trying to concat a ndframe with a non-ndframe
        df1 = mkdf(10, 2)
        for obj in [1, dict(), [1, 2], (1, 2)]:
            pytest.raises(TypeError, lambda x: concat([df1, obj]))

    def test_concat_invalid_first_argument(self):
        df1 = mkdf(10, 2)
        df2 = mkdf(10, 2)
        pytest.raises(TypeError, concat, df1, df2)

        # generator ok though
        concat(DataFrame(np.random.rand(5, 5)) for _ in range(3))

        # text reader ok
        # GH6583
        data = """index,A,B,C,D
foo,2,3,4,5
bar,7,8,9,10
baz,12,13,14,15
qux,12,13,14,15
foo2,12,13,14,15
bar2,12,13,14,15
"""

        reader = read_csv(StringIO(data), chunksize=1)
        result = concat(reader, ignore_index=True)
        expected = read_csv(StringIO(data))
        assert_frame_equal(result, expected)

    def test_concat_NaT_series(self):
        # GH 11693
        # test for merging NaT series with datetime series.
        x = Series(date_range('20151124 08:00', '20151124 09:00',
                              freq='1h', tz='US/Eastern'))
        y = Series(pd.NaT, index=[0, 1], dtype='datetime64[ns, US/Eastern]')
        expected = Series([x[0], x[1], pd.NaT, pd.NaT])

        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

        # all NaT with tz
        expected = Series(pd.NaT, index=range(4),
                          dtype='datetime64[ns, US/Eastern]')
        result = pd.concat([y, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

        # without tz
        x = pd.Series(pd.date_range('20151124 08:00',
                                    '20151124 09:00', freq='1h'))
        y = pd.Series(pd.date_range('20151124 10:00',
                                    '20151124 11:00', freq='1h'))
        y[:] = pd.NaT
        expected = pd.Series([x[0], x[1], pd.NaT, pd.NaT])
        result = pd.concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

        # all NaT without tz
        x[:] = pd.NaT
        expected = pd.Series(pd.NaT, index=range(4),
                             dtype='datetime64[ns]')
        result = pd.concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

    def test_concat_tz_frame(self):
        df2 = DataFrame(dict(A=pd.Timestamp('20130102', tz='US/Eastern'),
                             B=pd.Timestamp('20130603', tz='CET')),
                        index=range(5))

        # concat
        df3 = pd.concat([df2.A.to_frame(), df2.B.to_frame()], axis=1)
        assert_frame_equal(df2, df3)

    def test_concat_tz_series(self):
        # gh-11755: tz and no tz
        x = Series(date_range('20151124 08:00',
                              '20151124 09:00',
                              freq='1h', tz='UTC'))
        y = Series(date_range('2012-01-01', '2012-01-02'))
        expected = Series([x[0], x[1], y[0], y[1]],
                          dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

        # gh-11887: concat tz and object
        x = Series(date_range('20151124 08:00',
                              '20151124 09:00',
                              freq='1h', tz='UTC'))
        y = Series(['a', 'b'])
        expected = Series([x[0], x[1], y[0], y[1]],
                          dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)

        # see gh-12217 and gh-12306
        # Concatenating two UTC times
        first = pd.DataFrame([[datetime(2016, 1, 1)]])
        first[0] = first[0].dt.tz_localize('UTC')

        second = pd.DataFrame([[datetime(2016, 1, 2)]])
        second[0] = second[0].dt.tz_localize('UTC')

        result = pd.concat([first, second])
        assert result[0].dtype == 'datetime64[ns, UTC]'

        # Concatenating two London times
        first = pd.DataFrame([[datetime(2016, 1, 1)]])
        first[0] = first[0].dt.tz_localize('Europe/London')

        second = pd.DataFrame([[datetime(2016, 1, 2)]])
        second[0] = second[0].dt.tz_localize('Europe/London')

        result = pd.concat([first, second])
        assert result[0].dtype == 'datetime64[ns, Europe/London]'

        # Concatenating 2+1 London times
        first = pd.DataFrame([[datetime(2016, 1, 1)], [datetime(2016, 1, 2)]])
        first[0] = first[0].dt.tz_localize('Europe/London')

        second = pd.DataFrame([[datetime(2016, 1, 3)]])
        second[0] = second[0].dt.tz_localize('Europe/London')

        result = pd.concat([first, second])
        assert result[0].dtype == 'datetime64[ns, Europe/London]'

        # Concat'ing 1+2 London times
        first = pd.DataFrame([[datetime(2016, 1, 1)]])
        first[0] = first[0].dt.tz_localize('Europe/London')

        second = pd.DataFrame([[datetime(2016, 1, 2)], [datetime(2016, 1, 3)]])
        second[0] = second[0].dt.tz_localize('Europe/London')

        result = pd.concat([first, second])
        assert result[0].dtype == 'datetime64[ns, Europe/London]'

    def test_concat_tz_series_with_datetimelike(self):
        # see gh-12620: tz and timedelta
        x = [pd.Timestamp('2011-01-01', tz='US/Eastern'),
             pd.Timestamp('2011-02-01', tz='US/Eastern')]
        y = [pd.Timedelta('1 day'), pd.Timedelta('2 day')]
        result = concat([pd.Series(x), pd.Series(y)], ignore_index=True)
        tm.assert_series_equal(result, pd.Series(x + y, dtype='object'))

        # tz and period
        y = [pd.Period('2011-03', freq='M'), pd.Period('2011-04', freq='M')]
        result = concat([pd.Series(x), pd.Series(y)], ignore_index=True)
        tm.assert_series_equal(result, pd.Series(x + y, dtype='object'))

    def test_concat_tz_series_tzlocal(self):
        # see gh-13583
        x = [pd.Timestamp('2011-01-01', tz=dateutil.tz.tzlocal()),
             pd.Timestamp('2011-02-01', tz=dateutil.tz.tzlocal())]
        y = [pd.Timestamp('2012-01-01', tz=dateutil.tz.tzlocal()),
             pd.Timestamp('2012-02-01', tz=dateutil.tz.tzlocal())]

        result = concat([pd.Series(x), pd.Series(y)], ignore_index=True)
        tm.assert_series_equal(result, pd.Series(x + y))
        assert result.dtype == 'datetime64[ns, tzlocal()]'

    def test_concat_period_series(self):
        x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
        y = Series(pd.PeriodIndex(['2015-10-01', '2016-01-01'], freq='D'))
        expected = Series([x[0], x[1], y[0], y[1]], dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)
        assert result.dtype == 'object'

        # different freq
        x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
        y = Series(pd.PeriodIndex(['2015-10-01', '2016-01-01'], freq='M'))
        expected = Series([x[0], x[1], y[0], y[1]], dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)
        assert result.dtype == 'object'

        x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
        y = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='M'))
        expected = Series([x[0], x[1], y[0], y[1]], dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)
        assert result.dtype == 'object'

        # non-period
        x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
        y = Series(pd.DatetimeIndex(['2015-11-01', '2015-12-01']))
        expected = Series([x[0], x[1], y[0], y[1]], dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)
        assert result.dtype == 'object'

        x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
        y = Series(['A', 'B'])
        expected = Series([x[0], x[1], y[0], y[1]], dtype='object')
        result = concat([x, y], ignore_index=True)
        tm.assert_series_equal(result, expected)
        assert result.dtype == 'object'

    def test_concat_empty_series(self):
        # GH 11082
        s1 = pd.Series([1, 2, 3], name='x')
        s2 = pd.Series(name='y')
        res = pd.concat([s1, s2], axis=1)
        exp = pd.DataFrame({'x': [1, 2, 3], 'y': [np.nan, np.nan, np.nan]})
        tm.assert_frame_equal(res, exp)

        s1 = pd.Series([1, 2, 3], name='x')
        s2 = pd.Series(name='y')
        res = pd.concat([s1, s2], axis=0)
        # name will be reset
        exp = pd.Series([1, 2, 3])
        tm.assert_series_equal(res, exp)

        # empty Series with no name
        s1 = pd.Series([1, 2, 3], name='x')
        s2 = pd.Series(name=None)
        res = pd.concat([s1, s2], axis=1)
        exp = pd.DataFrame({'x': [1, 2, 3], 0: [np.nan, np.nan, np.nan]},
                           columns=['x', 0])
        tm.assert_frame_equal(res, exp)

    def test_default_index(self):
        # is_series and ignore_index
        s1 = pd.Series([1, 2, 3], name='x')
        s2 = pd.Series([4, 5, 6], name='y')
        res = pd.concat([s1, s2], axis=1, ignore_index=True)
        assert isinstance(res.columns, pd.RangeIndex)
        exp = pd.DataFrame([[1, 4], [2, 5], [3, 6]])
        # use check_index_type=True to check the result have
        # RangeIndex (default index)
        tm.assert_frame_equal(res, exp, check_index_type=True,
                              check_column_type=True)

        # is_series and all inputs have no names
        s1 = pd.Series([1, 2, 3])
        s2 = pd.Series([4, 5, 6])
        res = pd.concat([s1, s2], axis=1, ignore_index=False)
        assert isinstance(res.columns, pd.RangeIndex)
        exp = pd.DataFrame([[1, 4], [2, 5], [3, 6]])
        exp.columns = pd.RangeIndex(2)
        tm.assert_frame_equal(res, exp, check_index_type=True,
                              check_column_type=True)

        # is_dataframe and ignore_index
        df1 = pd.DataFrame({'A': [1, 2], 'B': [5, 6]})
        df2 = pd.DataFrame({'A': [3, 4], 'B': [7, 8]})

        res = pd.concat([df1, df2], axis=0, ignore_index=True)
        exp = pd.DataFrame([[1, 5], [2, 6], [3, 7], [4, 8]],
                           columns=['A', 'B'])
        tm.assert_frame_equal(res, exp, check_index_type=True,
                              check_column_type=True)

        res = pd.concat([df1, df2], axis=1, ignore_index=True)
        exp = pd.DataFrame([[1, 5, 3, 7], [2, 6, 4, 8]])
        tm.assert_frame_equal(res, exp, check_index_type=True,
                              check_column_type=True)

    def test_concat_multiindex_rangeindex(self):
        # GH13542
        # when multi-index levels are RangeIndex objects
        # there is a bug in concat with objects of len 1

        df = DataFrame(np.random.randn(9, 2))
        df.index = MultiIndex(levels=[pd.RangeIndex(3), pd.RangeIndex(3)],
                              labels=[np.repeat(np.arange(3), 3),
                                      np.tile(np.arange(3), 3)])

        res = concat([df.iloc[[2, 3, 4], :], df.iloc[[5], :]])
        exp = df.iloc[[2, 3, 4, 5], :]
        tm.assert_frame_equal(res, exp)

    def test_concat_multiindex_dfs_with_deepcopy(self):
        # GH 9967
        from copy import deepcopy
        example_multiindex1 = pd.MultiIndex.from_product([['a'], ['b']])
        example_dataframe1 = pd.DataFrame([0], index=example_multiindex1)

        example_multiindex2 = pd.MultiIndex.from_product([['a'], ['c']])
        example_dataframe2 = pd.DataFrame([1], index=example_multiindex2)

        example_dict = {'s1': example_dataframe1, 's2': example_dataframe2}
        expected_index = pd.MultiIndex(levels=[['s1', 's2'],
                                               ['a'],
                                               ['b', 'c']],
                                       labels=[[0, 1], [0, 0], [0, 1]],
                                       names=['testname', None, None])
        expected = pd.DataFrame([[0], [1]], index=expected_index)
        result_copy = pd.concat(deepcopy(example_dict), names=['testname'])
        tm.assert_frame_equal(result_copy, expected)
        result_no_copy = pd.concat(example_dict, names=['testname'])
        tm.assert_frame_equal(result_no_copy, expected)

    def test_categorical_concat_append(self):
        cat = Categorical(["a", "b"], categories=["a", "b"])
        vals = [1, 2]
        df = DataFrame({"cats": cat, "vals": vals})
        cat2 = Categorical(["a", "b", "a", "b"], categories=["a", "b"])
        vals2 = [1, 2, 1, 2]
        exp = DataFrame({"cats": cat2, "vals": vals2},
                        index=Index([0, 1, 0, 1]))

        tm.assert_frame_equal(pd.concat([df, df]), exp)
        tm.assert_frame_equal(df.append(df), exp)

        # GH 13524 can concat different categories
        cat3 = Categorical(["a", "b"], categories=["a", "b", "c"])
        vals3 = [1, 2]
        df_different_categories = DataFrame({"cats": cat3, "vals": vals3})

        res = pd.concat([df, df_different_categories], ignore_index=True)
        exp = DataFrame({"cats": list('abab'), "vals": [1, 2, 1, 2]})
        tm.assert_frame_equal(res, exp)

        res = df.append(df_different_categories, ignore_index=True)
        tm.assert_frame_equal(res, exp)

    def test_categorical_concat_dtypes(self):

        # GH8143
        index = ['cat', 'obj', 'num']
        cat = Categorical(['a', 'b', 'c'])
        obj = Series(['a', 'b', 'c'])
        num = Series([1, 2, 3])
        df = pd.concat([Series(cat), obj, num], axis=1, keys=index)

        result = df.dtypes == 'object'
        expected = Series([False, True, False], index=index)
        tm.assert_series_equal(result, expected)

        result = df.dtypes == 'int64'
        expected = Series([False, False, True], index=index)
        tm.assert_series_equal(result, expected)

        result = df.dtypes == 'category'
        expected = Series([True, False, False], index=index)
        tm.assert_series_equal(result, expected)

    def test_categorical_concat(self, sort):
        # See GH 10177
        df1 = DataFrame(np.arange(18, dtype='int64').reshape(6, 3),
                        columns=["a", "b", "c"])

        df2 = DataFrame(np.arange(14, dtype='int64').reshape(7, 2),
                        columns=["a", "c"])

        cat_values = ["one", "one", "two", "one", "two", "two", "one"]
        df2['h'] = Series(Categorical(cat_values))

        res = pd.concat((df1, df2), axis=0, ignore_index=True, sort=sort)
        exp = DataFrame({'a': [0, 3, 6, 9, 12, 15, 0, 2, 4, 6, 8, 10, 12],
                         'b': [1, 4, 7, 10, 13, 16, np.nan, np.nan, np.nan,
                               np.nan, np.nan, np.nan, np.nan],
                         'c': [2, 5, 8, 11, 14, 17, 1, 3, 5, 7, 9, 11, 13],
                         'h': [None] * 6 + cat_values})
        tm.assert_frame_equal(res, exp)

    def test_categorical_concat_gh7864(self):
        # GH 7864
        # make sure ordering is preserverd
        df = DataFrame({"id": [1, 2, 3, 4, 5, 6], "raw_grade": list('abbaae')})
        df["grade"] = Categorical(df["raw_grade"])
        df['grade'].cat.set_categories(['e', 'a', 'b'])

        df1 = df[0:3]
        df2 = df[3:]

        tm.assert_index_equal(df['grade'].cat.categories,
                              df1['grade'].cat.categories)
        tm.assert_index_equal(df['grade'].cat.categories,
                              df2['grade'].cat.categories)

        dfx = pd.concat([df1, df2])
        tm.assert_index_equal(df['grade'].cat.categories,
                              dfx['grade'].cat.categories)

        dfa = df1.append(df2)
        tm.assert_index_equal(df['grade'].cat.categories,
                              dfa['grade'].cat.categories)

    def test_categorical_concat_preserve(self):

        # GH 8641  series concat not preserving category dtype
        # GH 13524 can concat different categories
        s = Series(list('abc'), dtype='category')
        s2 = Series(list('abd'), dtype='category')

        exp = Series(list('abcabd'))
        res = pd.concat([s, s2], ignore_index=True)
        tm.assert_series_equal(res, exp)

        exp = Series(list('abcabc'), dtype='category')
        res = pd.concat([s, s], ignore_index=True)
        tm.assert_series_equal(res, exp)

        exp = Series(list('abcabc'), index=[0, 1, 2, 0, 1, 2],
                     dtype='category')
        res = pd.concat([s, s])
        tm.assert_series_equal(res, exp)

        a = Series(np.arange(6, dtype='int64'))
        b = Series(list('aabbca'))

        df2 = DataFrame({'A': a,
                         'B': b.astype(CategoricalDtype(list('cab')))})
        res = pd.concat([df2, df2])
        exp = DataFrame(
            {'A': pd.concat([a, a]),
             'B': pd.concat([b, b]).astype(CategoricalDtype(list('cab')))})
        tm.assert_frame_equal(res, exp)

    def test_categorical_index_preserver(self):

        a = Series(np.arange(6, dtype='int64'))
        b = Series(list('aabbca'))

        df2 = DataFrame({'A': a,
                         'B': b.astype(CategoricalDtype(list('cab')))
                         }).set_index('B')
        result = pd.concat([df2, df2])
        expected = DataFrame(
            {'A': pd.concat([a, a]),
             'B': pd.concat([b, b]).astype(CategoricalDtype(list('cab')))
             }).set_index('B')
        tm.assert_frame_equal(result, expected)

        # wrong catgories
        df3 = DataFrame({'A': a, 'B': Categorical(b, categories=list('abe'))
                         }).set_index('B')
        pytest.raises(TypeError, lambda: pd.concat([df2, df3]))

    def test_concat_categoricalindex(self):
        # GH 16111, categories that aren't lexsorted
        categories = [9, 0, 1, 2, 3]

        a = pd.Series(1, index=pd.CategoricalIndex([9, 0],
                                                   categories=categories))
        b = pd.Series(2, index=pd.CategoricalIndex([0, 1],
                                                   categories=categories))
        c = pd.Series(3, index=pd.CategoricalIndex([1, 2],
                                                   categories=categories))

        result = pd.concat([a, b, c], axis=1)

        exp_idx = pd.CategoricalIndex([0, 1, 2, 9])
        exp = pd.DataFrame({0: [1, np.nan, np.nan, 1],
                            1: [2, 2, np.nan, np.nan],
                            2: [np.nan, 3, 3, np.nan]},
                           columns=[0, 1, 2],
                           index=exp_idx)
        tm.assert_frame_equal(result, exp)

    def test_concat_order(self):
        # GH 17344
        dfs = [pd.DataFrame(index=range(3), columns=['a', 1, None])]
        dfs += [pd.DataFrame(index=range(3), columns=[None, 1, 'a'])
                for i in range(100)]

        result = pd.concat(dfs, sort=True).columns

        if PY2:
            # Different sort order between incomparable objects between
            # python 2 and python3 via Index.union.
            expected = dfs[1].columns
        else:
            expected = dfs[0].columns
        tm.assert_index_equal(result, expected)

    def test_concat_datetime_timezone(self):
        # GH 18523
        idx1 = pd.date_range('2011-01-01', periods=3, freq='H',
                             tz='Europe/Paris')
        idx2 = pd.date_range(start=idx1[0], end=idx1[-1], freq='H')
        df1 = pd.DataFrame({'a': [1, 2, 3]}, index=idx1)
        df2 = pd.DataFrame({'b': [1, 2, 3]}, index=idx2)
        result = pd.concat([df1, df2], axis=1)

        exp_idx = DatetimeIndex(['2011-01-01 00:00:00+01:00',
                                 '2011-01-01 01:00:00+01:00',
                                 '2011-01-01 02:00:00+01:00'],
                                freq='H'
                                ).tz_localize('UTC').tz_convert('Europe/Paris')

        expected = pd.DataFrame([[1, 1], [2, 2], [3, 3]],
                                index=exp_idx, columns=['a', 'b'])

        tm.assert_frame_equal(result, expected)

        idx3 = pd.date_range('2011-01-01', periods=3,
                             freq='H', tz='Asia/Tokyo')
        df3 = pd.DataFrame({'b': [1, 2, 3]}, index=idx3)
        result = pd.concat([df1, df3], axis=1)

        exp_idx = DatetimeIndex(['2010-12-31 15:00:00+00:00',
                                 '2010-12-31 16:00:00+00:00',
                                 '2010-12-31 17:00:00+00:00',
                                 '2010-12-31 23:00:00+00:00',
                                 '2011-01-01 00:00:00+00:00',
                                 '2011-01-01 01:00:00+00:00']
                                ).tz_localize('UTC')

        expected = pd.DataFrame([[np.nan, 1], [np.nan, 2], [np.nan, 3],
                                 [1, np.nan], [2, np.nan], [3, np.nan]],
                                index=exp_idx, columns=['a', 'b'])

        tm.assert_frame_equal(result, expected)


@pytest.mark.parametrize('pdt', [pd.Series, pd.DataFrame, pd.Panel])
@pytest.mark.parametrize('dt', np.sctypes['float'])
def test_concat_no_unnecessary_upcast(dt, pdt):
    with catch_warnings(record=True):
        # GH 13247
        dims = pdt().ndim
        dfs = [pdt(np.array([1], dtype=dt, ndmin=dims)),
               pdt(np.array([np.nan], dtype=dt, ndmin=dims)),
               pdt(np.array([5], dtype=dt, ndmin=dims))]
        x = pd.concat(dfs)
        assert x.values.dtype == dt


@pytest.mark.parametrize('pdt', [pd.Series, pd.DataFrame, pd.Panel])
@pytest.mark.parametrize('dt', np.sctypes['int'])
def test_concat_will_upcast(dt, pdt):
    with catch_warnings(record=True):
        dims = pdt().ndim
        dfs = [pdt(np.array([1], dtype=dt, ndmin=dims)),
               pdt(np.array([np.nan], ndmin=dims)),
               pdt(np.array([5], dtype=dt, ndmin=dims))]
        x = pd.concat(dfs)
        assert x.values.dtype == 'float64'


def test_concat_empty_and_non_empty_frame_regression():
    # GH 18178 regression test
    df1 = pd.DataFrame({'foo': [1]})
    df2 = pd.DataFrame({'foo': []})
    expected = pd.DataFrame({'foo': [1.0]})
    result = pd.concat([df1, df2])
    assert_frame_equal(result, expected)


def test_concat_empty_and_non_empty_series_regression():
    # GH 18187 regression test
    s1 = pd.Series([1])
    s2 = pd.Series([])
    expected = s1
    result = pd.concat([s1, s2])
    tm.assert_series_equal(result, expected)


def test_concat_sorts_columns(sort_with_none):
    # GH-4588
    df1 = pd.DataFrame({"a": [1, 2], "b": [1, 2]}, columns=['b', 'a'])
    df2 = pd.DataFrame({"a": [3, 4], "c": [5, 6]})

    # for sort=True/None
    expected = pd.DataFrame({"a": [1, 2, 3, 4],
                             "b": [1, 2, None, None],
                             "c": [None, None, 5, 6]},
                            columns=['a', 'b', 'c'])

    if sort_with_none is False:
        expected = expected[['b', 'a', 'c']]

    if sort_with_none is None:
        # only warn if not explicitly specified
        ctx = tm.assert_produces_warning(FutureWarning)
    else:
        ctx = tm.assert_produces_warning(None)

    # default
    with ctx:
        result = pd.concat([df1, df2], ignore_index=True, sort=sort_with_none)
    tm.assert_frame_equal(result, expected)


def test_concat_sorts_index(sort_with_none):
    df1 = pd.DataFrame({"a": [1, 2, 3]}, index=['c', 'a', 'b'])
    df2 = pd.DataFrame({"b": [1, 2]}, index=['a', 'b'])

    # For True/None
    expected = pd.DataFrame({"a": [2, 3, 1], "b": [1, 2, None]},
                            index=['a', 'b', 'c'],
                            columns=['a', 'b'])
    if sort_with_none is False:
        expected = expected.loc[['c', 'a', 'b']]

    if sort_with_none is None:
        # only warn if not explicitly specified
        ctx = tm.assert_produces_warning(FutureWarning)
    else:
        ctx = tm.assert_produces_warning(None)

    # Warn and sort by default
    with ctx:
        result = pd.concat([df1, df2], axis=1, sort=sort_with_none)
    tm.assert_frame_equal(result, expected)


def test_concat_inner_sort(sort_with_none):
    # https://github.com/pandas-dev/pandas/pull/20613
    df1 = pd.DataFrame({"a": [1, 2], "b": [1, 2], "c": [1, 2]},
                       columns=['b', 'a', 'c'])
    df2 = pd.DataFrame({"a": [1, 2], 'b': [3, 4]}, index=[3, 4])

    with tm.assert_produces_warning(None):
        # unset sort should *not* warn for inner join
        # since that never sorted
        result = pd.concat([df1, df2], sort=sort_with_none,
                           join='inner',
                           ignore_index=True)

    expected = pd.DataFrame({"b": [1, 2, 3, 4], "a": [1, 2, 1, 2]},
                            columns=['b', 'a'])
    if sort_with_none is True:
        expected = expected[['a', 'b']]
    tm.assert_frame_equal(result, expected)


def test_concat_aligned_sort():
    # GH-4588
    df = pd.DataFrame({"c": [1, 2], "b": [3, 4], 'a': [5, 6]},
                      columns=['c', 'b', 'a'])
    result = pd.concat([df, df], sort=True, ignore_index=True)
    expected = pd.DataFrame({'a': [5, 6, 5, 6], 'b': [3, 4, 3, 4],
                             'c': [1, 2, 1, 2]},
                            columns=['a', 'b', 'c'])
    tm.assert_frame_equal(result, expected)

    result = pd.concat([df, df[['c', 'b']]], join='inner', sort=True,
                       ignore_index=True)
    expected = expected[['b', 'c']]
    tm.assert_frame_equal(result, expected)


def test_concat_aligned_sort_does_not_raise():
    # GH-4588
    # We catch TypeErrors from sorting internally and do not re-raise.
    df = pd.DataFrame({1: [1, 2], "a": [3, 4]}, columns=[1, 'a'])
    expected = pd.DataFrame({1: [1, 2, 1, 2], 'a': [3, 4, 3, 4]},
                            columns=[1, 'a'])
    result = pd.concat([df, df], ignore_index=True, sort=True)
    tm.assert_frame_equal(result, expected)
