Repository: databricks/koalas

Issue:
koalas.concat
Similar to https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html#pandas.concat

This is basically a unionAll in Spark.



Discussion:
This implementation is very different than unionAll of spark. unionAll in spark requires columns to be the same number in both dataframes. pandas.concat doesn't have that requirement. Here some reference: https://stackoverflow.com/questions/37332434/concatenate-two-pyspark-dataframes
@pyMixin would you be interested in working on this?
Is someone working on this? This is a super helpful function. 

Example:
`test["target"] = train["target"].mean()`
`all_df = pd.concat([train[["ID", "target"] + cols], test[["ID", "target"]+ cols]]).reset_index(drop=True)`



I'm gonna take a look

Resolving patch:
diff --git a/databricks/koalas/__init__.py b/databricks/koalas/__init__.py
--- a/databricks/koalas/__init__.py
+++ b/databricks/koalas/__init__.py
@@ -41,7 +41,8 @@ def assert_pyspark_version():
 from databricks.koalas.typedef import Col, pandas_wraps
 
 __all__ = ['read_csv', 'read_parquet', 'to_datetime', 'from_pandas',
-           'get_dummies', 'DataFrame', 'Series', 'Col', 'pandas_wraps']
+           'get_dummies', 'DataFrame', 'Series', 'Col', 'pandas_wraps',
+           'sql', 'range', 'concat']
 
 
 def _auto_patch():
diff --git a/databricks/koalas/namespace.py b/databricks/koalas/namespace.py
--- a/databricks/koalas/namespace.py
+++ b/databricks/koalas/namespace.py
@@ -18,6 +18,8 @@
 Wrappers around spark that correspond to common pandas functions.
 """
 from typing import Optional, Union
+from collections.abc import Iterable
+import itertools
 
 import numpy as np
 import pandas as pd
@@ -30,7 +32,7 @@
 from databricks.koalas.utils import default_session
 from databricks.koalas.frame import DataFrame, _reduce_spark_multi
 from databricks.koalas.typedef import Col, pandas_wraps
-from databricks.koalas.series import Series
+from databricks.koalas.series import Series, _col
 
 
 def from_pandas(pobj: Union['pd.DataFrame', 'pd.Series']) -> Union['Series', 'DataFrame']:
@@ -538,6 +540,238 @@ def column_name(value):
     return kdf[remaining_columns]
 
 
+# TODO: there are many parameters to implement and support. See Pandas's pd.concat.
+def concat(objs, axis=0, join='outer', ignore_index=False):
+    """
+    Concatenate pandas objects along a particular axis with optional set logic
+    along the other axes.
+
+    Parameters
+    ----------
+    objs : a sequence of Series or DataFrame
+        Any None objects will be dropped silently unless
+        they are all None in which case a ValueError will be raised
+    axis : {0/'index'}, default 0
+        The axis to concatenate along.
+    join : {'inner', 'outer'}, default 'outer'
+        How to handle indexes on other axis(es)
+    ignore_index : boolean, default False
+        If True, do not use the index values along the concatenation axis. The
+        resulting axis will be labeled 0, ..., n - 1. This is useful if you are
+        concatenating objects where the concatenation axis does not have
+        meaningful indexing information. Note the index values on the other
+        axes are still respected in the join.
+
+    Returns
+    -------
+    concatenated : object, type of objs
+        When concatenating all ``Series`` along the index (axis=0), a
+        ``Series`` is returned. When ``objs`` contains at least one
+        ``DataFrame``, a ``DataFrame`` is returned.
+
+    See Also
+    --------
+    DataFrame.merge
+
+    Examples
+    --------
+    Combine two ``Series``.
+
+    >>> s1 = ks.Series(['a', 'b'])
+    >>> s2 = ks.Series(['c', 'd'])
+    >>> ks.concat([s1, s2])
+    0    a
+    1    b
+    0    c
+    1    d
+    Name: 0, dtype: object
+
+    Clear the existing index and reset it in the result
+    by setting the ``ignore_index`` option to ``True``.
+
+    >>> ks.concat([s1, s2], ignore_index=True)
+    0    a
+    1    b
+    2    c
+    3    d
+    Name: 0, dtype: object
+
+    Combine two ``DataFrame`` objects with identical columns.
+
+    >>> df1 = ks.DataFrame([['a', 1], ['b', 2]],
+    ...                    columns=['letter', 'number'])
+    >>> df1
+      letter  number
+    0      a       1
+    1      b       2
+    >>> df2 = ks.DataFrame([['c', 3], ['d', 4]],
+    ...                    columns=['letter', 'number'])
+    >>> df2
+      letter  number
+    0      c       3
+    1      d       4
+    >>> ks.concat([df1, df2])
+      letter  number
+    0      a       1
+    1      b       2
+    0      c       3
+    1      d       4
+
+    Combine ``DataFrame`` and ``Series`` objects with different columns.
+
+    >>> ks.concat([df2, s1, s2])
+          0 letter  number
+    0  None      c     3.0
+    1  None      d     4.0
+    0     a   None     NaN
+    1     b   None     NaN
+    0     c   None     NaN
+    1     d   None     NaN
+
+    Combine ``DataFrame`` objects with overlapping columns
+    and return everything. Columns outside the intersection will
+    be filled with ``None`` values.
+
+    >>> df3 = ks.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']],
+    ...                    columns=['letter', 'number', 'animal'])
+    >>> df3
+      letter  number animal
+    0      c       3    cat
+    1      d       4    dog
+    >>> ks.concat([df1, df3])
+      animal letter  number
+    0   None      a       1
+    1   None      b       2
+    0    cat      c       3
+    1    dog      d       4
+
+    Combine ``DataFrame`` objects with overlapping columns
+    and return only those that are shared by passing ``inner`` to
+    the ``join`` keyword argument.
+
+    >>> ks.concat([df1, df3], join="inner")
+      letter  number
+    0      a       1
+    1      b       2
+    0      c       3
+    1      d       4
+    """
+    if not isinstance(objs, (dict, Iterable)):
+        raise TypeError('first argument must be an iterable of koalas '
+                        'objects, you passed an object of type '
+                        '"{name}"'.format(name=type(objs).__name__))
+
+    if axis not in [0, 'index']:
+        raise ValueError('axis should be either 0 or "index" currently.')
+
+    if all(map(lambda obj: obj is None, objs)):
+        raise ValueError("All objects passed were None")
+    objs = list(filter(lambda obj: obj is not None, objs))
+
+    for obj in objs:
+        if not isinstance(obj, (Series, DataFrame)):
+            raise TypeError('cannot concatenate object of type '"'{name}"'; only ks.Series '
+                            'and ks.DataFrame are valid'.format(name=type(objs).__name__))
+
+    # Series, Series ...
+    # We should return Series if objects are all Series.
+    should_return_series = all(map(lambda obj: isinstance(obj, Series), objs))
+
+    # DataFrame, Series ... & Series, Series ...
+    # In this case, we should return DataFrame.
+    new_objs = []
+    for obj in objs:
+        if isinstance(obj, Series):
+            obj = obj.to_dataframe()
+        new_objs.append(obj)
+    objs = new_objs
+
+    # DataFrame, DataFrame, ...
+    # All Series are converted into DataFrame and then compute concat.
+    if not ignore_index:
+        indices_of_kdfs = [kdf._metadata.index_map for kdf in objs]
+        index_of_first_kdf = indices_of_kdfs[0]
+        for index_of_kdf in indices_of_kdfs:
+            if index_of_first_kdf != index_of_kdf:
+                raise ValueError(
+                    'Index type and names should be same in the objects to concatenate. '
+                    'You passed different indices '
+                    '{index_of_first_kdf} and {index_of_kdf}'.format(
+                        index_of_first_kdf=index_of_first_kdf, index_of_kdf=index_of_kdf))
+
+    columns_of_kdfs = [kdf._metadata.columns for kdf in objs]
+    first_kdf = objs[0]
+    if ignore_index:
+        columns_of_first_kdf = first_kdf._metadata.data_columns
+    else:
+        columns_of_first_kdf = first_kdf._metadata.columns
+    if all(current_kdf == columns_of_first_kdf for current_kdf in columns_of_kdfs):
+        # If all columns are in the same order and values, use it.
+        kdfs = objs
+    else:
+        if ignore_index:
+            columns_to_apply = [kdf._metadata.data_columns for kdf in objs]
+        else:
+            columns_to_apply = [kdf._metadata.columns for kdf in objs]
+
+        if join == "inner":
+            interested_columns = set.intersection(*map(set, columns_to_apply))
+            # Keep the column order with its firsts DataFrame.
+            interested_columns = list(map(
+                lambda c: columns_of_first_kdf[columns_of_first_kdf.index(c)],
+                interested_columns))
+
+            kdfs = []
+            for kdf in objs:
+                sdf = kdf._sdf.select(interested_columns)
+                if ignore_index:
+                    kdfs.append(DataFrame(sdf))
+                else:
+                    kdfs.append(DataFrame(sdf, first_kdf._metadata.copy()))
+        elif join == "outer":
+            # If there are columns unmatched, just sort the column names.
+            merged_columns = set(
+                itertools.chain.from_iterable(columns_to_apply))
+
+            kdfs = []
+            for kdf in objs:
+                if ignore_index:
+                    columns_to_add = merged_columns - set(kdf._metadata.data_columns)
+                else:
+                    columns_to_add = merged_columns - set(kdf._metadata.columns)
+
+                # TODO: NaN and None difference for missing values. pandas seems filling NaN.
+                kdf = kdf.assign(**dict(zip(columns_to_add, [None] * len(columns_to_add))))
+
+                if ignore_index:
+                    sdf = kdf._sdf.select(sorted(kdf._metadata.data_columns))
+                else:
+                    sdf = kdf._sdf.select(
+                        kdf._metadata.index_columns + sorted(kdf._metadata.data_columns))
+
+                kdf = DataFrame(sdf, kdf._metadata.copy(
+                    data_columns=sorted(kdf._metadata.data_columns)))
+                kdfs.append(kdf)
+        else:
+            raise ValueError(
+                "Only can inner (intersect) or outer (union) join the other axis.")
+
+    concatenated = kdfs[0]._sdf
+    for kdf in kdfs[1:]:
+        concatenated = concatenated.unionByName(kdf._sdf)
+
+    if ignore_index:
+        result_kdf = DataFrame(concatenated.select(kdfs[0]._metadata.data_columns))
+    else:
+        result_kdf = DataFrame(concatenated, kdfs[0]._metadata.copy())
+
+    if should_return_series:
+        # If all input were Series, we should return Series.
+        return _col(result_kdf)
+    else:
+        return result_kdf
+
+
 # @pandas_wraps(return_col=np.datetime64)
 @pandas_wraps
 def _to_datetime1(arg, errors, format, infer_datetime_format) -> Col[np.datetime64]:


Test patch:
diff --git a/databricks/koalas/tests/test_dataframe.py b/databricks/koalas/tests/test_dataframe.py
--- a/databricks/koalas/tests/test_dataframe.py
+++ b/databricks/koalas/tests/test_dataframe.py
@@ -19,8 +19,7 @@
 import numpy as np
 import pandas as pd
 
-from databricks import koalas
-from databricks.koalas.generic import max_display_count
+from databricks import koalas as ks
 from databricks.koalas.testing.utils import ReusedSQLTestCase, SQLTestUtils
 from databricks.koalas.exceptions import PandasNotImplementedError
 from databricks.koalas.missing.frame import _MissingPandasLikeDataFrame
@@ -37,7 +36,7 @@ def pdf(self):
 
     @property
     def kdf(self):
-        return koalas.from_pandas(self.pdf)
+        return ks.from_pandas(self.pdf)
 
     def test_dataframe(self):
         kdf = self.kdf
@@ -64,21 +63,21 @@ def test_dataframe(self):
             'a': [1, 2, 3, 4, 5, 6, 7, 8, 9],
             'b': [4, 5, 6, 3, 2, 1, 0, 0, 0],
         })
-        ddf = koalas.from_pandas(df)
+        ddf = ks.from_pandas(df)
         self.assert_eq(df[['a', 'b']], ddf[['a', 'b']])
 
         self.assertEqual(ddf.a.notnull().alias("x").name, "x")
 
     def test_repr_cache_invalidation(self):
         # If there is any cache, inplace operations should invalidate it.
-        df = koalas.range(10)
+        df = ks.range(10)
         df.__repr__()
         df['a'] = df['id']
         self.assertEqual(df.__repr__(), df.to_pandas().__repr__())
 
     def test_repr_html_cache_invalidation(self):
         # If there is any cache, inplace operations should invalidate it.
-        df = koalas.range(10)
+        df = ks.range(10)
         df._repr_html_()
         df['a'] = df['id']
         self.assertEqual(df._repr_html_(), df.to_pandas()._repr_html_())
@@ -87,20 +86,20 @@ def test_empty_dataframe(self):
         pdf = pd.DataFrame({'a': pd.Series([], dtype='i1'),
                             'b': pd.Series([], dtype='str')})
 
-        self.assertRaises(ValueError, lambda: koalas.from_pandas(pdf))
+        self.assertRaises(ValueError, lambda: ks.from_pandas(pdf))
 
         with self.sql_conf({'spark.sql.execution.arrow.enabled': False}):
-            self.assertRaises(ValueError, lambda: koalas.from_pandas(pdf))
+            self.assertRaises(ValueError, lambda: ks.from_pandas(pdf))
 
     def test_all_null_dataframe(self):
 
         pdf = pd.DataFrame({'a': pd.Series([None, None, None], dtype='float64'),
                             'b': pd.Series([None, None, None], dtype='str')})
 
-        self.assertRaises(ValueError, lambda: koalas.from_pandas(pdf))
+        self.assertRaises(ValueError, lambda: ks.from_pandas(pdf))
 
         with self.sql_conf({'spark.sql.execution.arrow.enabled': False}):
-            self.assertRaises(ValueError, lambda: koalas.from_pandas(pdf))
+            self.assertRaises(ValueError, lambda: ks.from_pandas(pdf))
 
     def test_nullable_object(self):
         pdf = pd.DataFrame({'a': list('abc') + [np.nan],
@@ -110,11 +109,11 @@ def test_nullable_object(self):
                             'e': [True, False, True, np.nan],
                             'f': list(pd.date_range('20130101', periods=3)) + [np.nan]})
 
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
         self.assert_eq(kdf, pdf)
 
         with self.sql_conf({'spark.sql.execution.arrow.enabled': False}):
-            kdf = koalas.from_pandas(pdf)
+            kdf = ks.from_pandas(pdf)
             self.assert_eq(kdf, pdf)
 
     def test_assign(self):
@@ -150,7 +149,7 @@ def test_index(self):
                      pd.DataFrame(np.random.randn(10, 5),
                                   index=pd.date_range('2011-01-01', freq='D',
                                                       periods=10))]:
-            ddf = koalas.from_pandas(case)
+            ddf = ks.from_pandas(case)
             self.assert_eq(list(ddf.index.toPandas()), list(case.index))
 
     def test_attributes(self):
@@ -160,9 +159,9 @@ def test_attributes(self):
         self.assertNotIn('foo', dir(kdf))
         self.assertRaises(AttributeError, lambda: kdf.foo)
 
-        kdf = koalas.DataFrame({'a b c': [1, 2, 3]})
+        kdf = ks.DataFrame({'a b c': [1, 2, 3]})
         self.assertNotIn('a b c', dir(kdf))
-        kdf = koalas.DataFrame({'a': [1, 2], 5: [1, 2]})
+        kdf = ks.DataFrame({'a': [1, 2], 5: [1, 2]})
         self.assertIn('a', dir(kdf))
         self.assertNotIn(5, dir(kdf))
 
@@ -181,13 +180,13 @@ def test_index_names(self):
 
         idx = pd.Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], name='x')
         df = pd.DataFrame(np.random.randn(10, 5), idx)
-        ddf = koalas.from_pandas(df)
+        ddf = ks.from_pandas(df)
         self.assertEqual(ddf.index.name, 'x')
 
     def test_rename_columns(self):
         pdf = pd.DataFrame({'a': [1, 2, 3, 4, 5, 6, 7],
                             'b': [7, 6, 5, 4, 3, 2, 1]})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         kdf.columns = ['x', 'y']
         pdf.columns = ['x', 'y']
@@ -200,7 +199,7 @@ def test_rename_columns(self):
 
         # Multi-index columns
         pdf = pd.DataFrame({('A', '0'): [1, 2, 2, 3], ('B', 1): [1, 2, 3, 4]})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         pdf.columns = ['x', 'y']
         kdf.columns = ['x', 'y']
@@ -208,7 +207,7 @@ def test_rename_columns(self):
         self.assert_eq(kdf, pdf)
 
     def test_drop(self):
-        kdf = koalas.DataFrame({'x': [1, 2], 'y': [3, 4], 'z': [5, 6]})
+        kdf = ks.DataFrame({'x': [1, 2], 'y': [3, 4], 'z': [5, 6]})
 
         # Assert 'labels' or 'columns' parameter is set
         expected_error_message = "Need to specify at least one of 'labels' or 'columns'"
@@ -235,7 +234,7 @@ def test_dropna(self):
                             'y': [1, 2, np.nan, 4, np.nan, np.nan],
                             'z': [1, 2, 3, 4, np.nan, np.nan]},
                            index=[10, 20, 30, 40, 50, 60])
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         self.assert_eq(kdf.dropna(), pdf.dropna())
         self.assert_eq(kdf.dropna(how='all'), pdf.dropna(how='all'))
@@ -267,7 +266,7 @@ def test_dtype(self):
                             'd': np.arange(4.0, 7.0, dtype='float64'),
                             'e': [True, False, True],
                             'f': pd.date_range('20130101', periods=3)})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
         self.assert_eq(kdf, pdf)
         self.assertTrue((kdf.dtypes == pdf.dtypes).all())
 
@@ -277,7 +276,7 @@ def test_fillna(self):
                             'z': [1, 2, 3, 4, np.nan, np.nan]},
                            index=[10, 20, 30, 40, 50, 60])
 
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         self.assert_eq(kdf, pdf)
         self.assert_eq(kdf.fillna(-1), pdf.fillna(-1))
@@ -306,23 +305,15 @@ def test_fillna(self):
     def test_isnull(self):
         pdf = pd.DataFrame({'x': [1, 2, 3, 4, None, 6], 'y': list('abdabd')},
                            index=[10, 20, 30, 40, 50, 60])
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         self.assert_eq(kdf.notnull(), pdf.notnull())
         self.assert_eq(kdf.isnull(), pdf.isnull())
 
-    def test_to_datetime(self):
-        pdf = pd.DataFrame({'year': [2015, 2016],
-                            'month': [2, 3],
-                            'day': [4, 5]})
-        kdf = koalas.from_pandas(pdf)
-
-        self.assert_eq(pd.to_datetime(pdf), koalas.to_datetime(kdf))
-
     def test_sort_values(self):
         pdf = pd.DataFrame({'a': [1, 2, 3, 4, 5, None, 7],
                             'b': [7, 6, 5, 4, 3, 2, 1]})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
         self.assert_eq(repr(kdf.sort_values('b')), repr(pdf.sort_values('b')))
         self.assert_eq(repr(kdf.sort_values(['b', 'a'])), repr(pdf.sort_values(['b', 'a'])))
         self.assert_eq(
@@ -379,7 +370,7 @@ def test_to_numpy(self):
                             'c': ["one", "three", "six", "seven", "one", "5"]},
                            index=[10, 20, 30, 40, 50, 60])
 
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         np.testing.assert_equal(kdf.to_numpy(), pdf.values)
 
@@ -395,7 +386,7 @@ def test_isin(self):
                            'c': ["one", "three", "six", "seven", "one", "5"]},
                           index=[10, 20, 30, 40, 50, 60])
 
-        kdf = koalas.from_pandas(df)
+        kdf = ks.from_pandas(df)
         self.assert_eq(kdf.isin([4, 'six']), df.isin([4, 'six']))
         self.assert_eq(kdf.isin({"a": [2, 8], "c": ['three', "one"]}),
                        df.isin({"a": [2, 8], "c": ['three', "one"]}))
@@ -413,8 +404,8 @@ def test_isin(self):
             kdf.isin(1)
 
     def test_merge(self):
-        left_kdf = koalas.DataFrame({'A': [1, 2]})
-        right_kdf = koalas.DataFrame({'B': ['x', 'y']}, index=[1, 2])
+        left_kdf = ks.DataFrame({'A': [1, 2]})
+        right_kdf = ks.DataFrame({'B': ['x', 'y']}, index=[1, 2])
 
         # Assert only 'on' or 'left_index' and 'right_index' parameters are set
         msg = "At least 'on' or 'left_index' and 'right_index' have to be set"
@@ -435,8 +426,8 @@ def test_merge(self):
         self.assert_eq(res, pd.DataFrame({'A': [2], 'B': ['x']}))
 
         # Assert inner join on non-default column
-        left_kdf_with_id = koalas.DataFrame({'A': [1, 2], 'id': [0, 1]})
-        right_kdf_with_id = koalas.DataFrame({'B': ['x', 'y'], 'id': [0, 1]}, index=[1, 2])
+        left_kdf_with_id = ks.DataFrame({'A': [1, 2], 'id': [0, 1]})
+        right_kdf_with_id = ks.DataFrame({'B': ['x', 'y'], 'id': [0, 1]}, index=[1, 2])
         res = left_kdf_with_id.merge(right_kdf_with_id, on='id')
         # Explicitly set columns to also assure their correct order with Python 3.5
         self.assert_eq(res, pd.DataFrame({'A': [1, 2], 'id': [0, 1], 'B': ['x', 'y']},
@@ -462,13 +453,13 @@ def test_merge(self):
         self.assert_eq(res, pd.DataFrame({'A': [1, 2, np.nan], 'B': [None, 'x', 'y']}))
 
         # Assert suffixes create the expected column names
-        res = left_kdf.merge(koalas.DataFrame({'A': [3, 4]}), left_index=True, right_index=True,
+        res = left_kdf.merge(ks.DataFrame({'A': [3, 4]}), left_index=True, right_index=True,
                              suffixes=('_left', '_right'))
         self.assert_eq(res, pd.DataFrame({'A_left': [1, 2], 'A_right': [3, 4]}))
 
     def test_clip(self):
         pdf = pd.DataFrame({'A': [0, 2, 4]})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         # Assert list-like values are not accepted for 'lower' and 'upper'
         msg = "List-like value are not supported for 'lower' and 'upper' at the moment"
@@ -487,12 +478,12 @@ def test_clip(self):
         self.assert_eq(kdf.clip(1, 3), pdf.clip(1, 3))
 
         # Assert behavior on string values
-        str_kdf = koalas.DataFrame({'A': ['a', 'b', 'c']})
+        str_kdf = ks.DataFrame({'A': ['a', 'b', 'c']})
         self.assert_eq(str_kdf.clip(1, 3), str_kdf)
 
     def test_sample(self):
         pdf = pd.DataFrame({'A': [0, 2, 4]})
-        kdf = koalas.from_pandas(pdf)
+        kdf = ks.from_pandas(pdf)
 
         # Make sure the tests run, but we can't check the result because they are non-deterministic.
         kdf.sample(frac=0.1)
diff --git a/databricks/koalas/tests/test_namespace.py b/databricks/koalas/tests/test_namespace.py
new file mode 100644
--- /dev/null
+++ b/databricks/koalas/tests/test_namespace.py
@@ -0,0 +1,52 @@
+#
+# Copyright (C) 2019 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import pandas as pd
+
+from databricks import koalas as ks
+from databricks.koalas.testing.utils import ReusedSQLTestCase, SQLTestUtils
+
+
+class NamespaceTest(ReusedSQLTestCase, SQLTestUtils):
+
+    def test_to_datetime(self):
+        pdf = pd.DataFrame({'year': [2015, 2016],
+                            'month': [2, 3],
+                            'day': [4, 5]})
+        kdf = ks.from_pandas(pdf)
+
+        self.assert_eq(pd.to_datetime(pdf), ks.to_datetime(kdf))
+
+    def test_concat(self):
+        pdf = pd.DataFrame({'A': [0, 2, 4], 'B': [1, 3, 5]})
+        kdf = ks.from_pandas(pdf)
+
+        self.assertRaisesRegex(TypeError, "first argument must be", lambda: ks.concat(kdf))
+        self.assertRaisesRegex(
+            TypeError, "cannot concatenate object", lambda: ks.concat([kdf, 1]))
+
+        kdf2 = kdf.set_index('B', append=True)
+        self.assertRaisesRegex(
+            ValueError, "Index type and names should be same", lambda: ks.concat([kdf, kdf2]))
+        kdf2 = kdf.reset_index()
+        self.assertRaisesRegex(
+            ValueError, "Index type and names should be same", lambda: ks.concat([kdf, kdf2]))
+
+        self.assertRaisesRegex(
+            ValueError, "All objects passed", lambda: ks.concat([None, None]))
+
+        self.assertRaisesRegex(
+            ValueError, 'axis should be either 0 or', lambda: ks.concat([kdf, kdf], axis=1))