Repository: pydata/xarray
Issue: xray.concat incorrectly converts string arrays to numeric dtype

String DataArrays containing mostly representations of numbers are converted to int or float during concatenation, even when they contain non-convertible values.

a = xray.DataArray(np.arange(6).reshape(3, 2).astype(str), dims=['x','y'])
b = xray.DataArray(np.arange(12).reshape(3, 4).astype(str), dims=['x','y'])

a.dtype, b.dtype
Out:
(dtype('S21'), dtype('S21'))

a[0,0] = 'foo'
a
Out: <xray.DataArray (x: 3, y: 2)>
array([['foo', '1'],
       ['2', '3'],
       ['4', '5']], 
      dtype='|S21')
Coordinates:
- x        (x) int64 0 1 2
- y        (y) int64 0 1

xray.concat([a,b], dim='y')
Out:
<xray.DataArray (x: 3, y: 6)>
array([[ nan,   1.,   0.,   1.,   2.,   3.],
       [  2.,   3.,   4.,   5.,   6.,   7.],
       [  4.,   5.,   8.,   9.,  10.,  11.]])
Coordinates:
- x        (x) int64 0 1 2
- y        (y) int64 0 1 0 1 2 3

Why is this converted to float, with non-convertible strings mapped to nan? Without setting the 'foo' value, the result is an int array.

The same problem happens when directly calling the inner functions:
xray.Dataset._concat([a._dataset, b._dataset], dim='y')[None]
and
xray.Variable.concat([a._dataset._arrays[None], b._dataset._arrays[None]], 'y')


Changed files:
xray/core/common.py, xray/test/test_variable.py

diff --git a/xray/core/common.py b/xray/core/common.py
index 58346ad5d..edb04ab32 100644
--- a/xray/core/common.py
+++ b/xray/core/common.py
@@ -1,3 +1,5 @@
+import functools
+
 import numpy as np
 import pandas as pd
 
@@ -169,8 +171,12 @@ def _maybe_promote(dtype):
 
 
 def _possibly_convert_objects(values):
+    """Convert arrays of datetime.datetime and datetime.timedelta objects into
+    datetime64 and timedelta64
+    """
     try:
-        converter = pd.core.common._possibly_convert_objects
+        converter = functools.partial(pd.core.common._possibly_convert_objects,
+                                      convert_numeric=False)
     except AttributeError:
         # our fault for using a private pandas API that has gone missing
         # this should do the same coercion (though it will be slower)
diff --git a/xray/test/test_variable.py b/xray/test/test_variable.py
index 090d150ba..5f1d491f3 100644
--- a/xray/test/test_variable.py
+++ b/xray/test/test_variable.py
@@ -185,6 +185,11 @@ class VariableSubclassTestCases(object):
             else:
                 self.assertFalse(same_source)
 
+    def test_object_conversion(self):
+        data = np.arange(5).astype(str).astype(object)
+        actual = self.cls('x', data)
+        self.assertEqual(actual.dtype, data.dtype)
+
     def test_pandas_data(self):
         v = self.cls(['x'], pd.Series([0, 1, 2], index=[3, 2, 1]))
         self.assertVariableIdentical(v, v[[0, 1, 2]])
@@ -338,6 +343,16 @@ class VariableSubclassTestCases(object):
                 'animal', np.array(['horse', 'aardvark'], dtype=kind))
             self.assertVariableEqual(expected, actual)
 
+    def test_concat_number_strings(self):
+        # regression test for #305
+        a = self.cls('x', ['0', '1', '2'])
+        b = self.cls('x', ['3', '4'])
+        actual = Variable.concat([a, b], dim='x')
+        expected = Variable('x', np.arange(5).astype(str).astype(object))
+        self.assertVariableIdentical(expected, actual)
+        self.assertEqual(expected.dtype, object)
+        self.assertEqual(type(expected.values[0]), str)
+
     def test_copy(self):
         v = self.cls('x', 0.5 * np.arange(10), {'foo': 'bar'})
         for deep in [True, False]: