Repository: pandas-dev/pandas

Issue:
DatetimeIndex columns cause reset_index() to throw AttributeError
It appears that if a dataframe has column headers of type DatetimeIndex, calling reset_index() throws `AttributeError: 'str' object has no attribute 'view'`.  I see this both in v0.12 and the master branch.

If column headers are strings or integers, reset_index() works fine.

I'm guessing it's treating the new column array as type DatetimeIndex whereas it now has a string in the 0th position ('index').  Maybe the reset_index should first cast the columns as object and convert the DatetimeIndex values to strings?

``` python
In [48]: df = pd.DataFrame(data=np.random.rand(2, 2), columns=pd.date_range('1/1/2013', '1/2/2013'), index=['A', 'B'])

In [49]: df.reset_index()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-49-6983677cc901> in <module>()
----> 1 df.reset_index()

/home/user/environments/python3/src/pandas/pandas/core/frame.py in reset_index(self, level, drop, inplace, col_level, col_fill)
   2447             else:
   2448                 values = _maybe_cast(self.index.values)
-> 2449             new_obj.insert(0, name, values)
   2450 
   2451         new_obj.index = new_index

/home/user/environments/python3/src/pandas/pandas/core/frame.py in insert(self, loc, column, value, allow_duplicates)
   1940         value = self._sanitize_column(column, value)
   1941         self._data.insert(
-> 1942             loc, column, value, allow_duplicates=allow_duplicates)
   1943 
   1944     def _sanitize_column(self, key, value):

/home/user/environments/python3/src/pandas/pandas/core/internals.py in insert(self, loc, item, value, allow_duplicates)
   2899 
   2900         try:
-> 2901             new_items = self.items.insert(loc, item)
   2902             self.set_items_norename(new_items)
   2903 

/home/user/environments/python3/src/pandas/pandas/tseries/index.py in insert(self, loc, item)
   1539 
   1540         new_index = np.concatenate((self[:loc].asi8,
-> 1541                                     [item.view(np.int64)],
   1542                                     self[loc:].asi8))
   1543         return DatetimeIndex(new_index, freq='infer')

AttributeError: 'str' object has no attribute 'view'

In [50]: df = pd.DataFrame(data=np.random.rand(2, 2), columns=[1, 2], index=['A', 'B'])
In [51]: df.reset_index()
Out[51]: 
  index         1         2
0     A  0.947575  0.370406
1     B  0.664856  0.686524

[2 rows x 3 columns]

In [52]: df = pd.DataFrame(data=np.random.rand(2, 2), columns=['C', 'D'], index=['A', 'B'])

In [53]: df.reset_index()
Out[53]: 
  index         C         D
0     A  0.053455  0.599483
1     B  0.776364  0.680425

[2 rows x 3 columns]
```



Discussion:
easy fix - DatetimeIndex's insert method wasn't handling non-datetimes correctly. I'll have a PR up shortly.


Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -1455,7 +1455,6 @@ def _safe_append_to_index(index, key):
 
         # raise here as this is basically an unsafe operation and we want
         # it to be obvious that you are doing something wrong
-
         raise ValueError("unsafe appending to index of type {0} with a key "
                          "{1}".format(index.__class__.__name__, key))
 
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -1533,6 +1533,8 @@ def insert(self, loc, item):
         ----------
         loc : int
         item : object
+            if not either a Python datetime or a numpy integer-like, returned
+            Index dtype will be object rather than datetime.
 
         Returns
         -------
@@ -1540,11 +1542,17 @@ def insert(self, loc, item):
         """
         if isinstance(item, datetime):
             item = _to_m8(item, tz=self.tz)
-
-        new_index = np.concatenate((self[:loc].asi8,
+        try:
+            new_index = np.concatenate((self[:loc].asi8,
                                     [item.view(np.int64)],
                                     self[loc:].asi8))
-        return DatetimeIndex(new_index, freq='infer')
+            return DatetimeIndex(new_index, freq='infer')
+        except (AttributeError, TypeError):
+
+            # fall back to object index
+            if isinstance(item,compat.string_types):
+                return self.asobject.insert(loc, item)
+            raise TypeError("cannot insert DatetimeIndex with incompatible label")
 
     def delete(self, loc):
         """
@@ -1585,7 +1593,7 @@ def tz_convert(self, tz):
     def tz_localize(self, tz, infer_dst=False):
         """
         Localize tz-naive DatetimeIndex to given time zone (using pytz)
-       
+
         Parameters
         ----------
         tz : string or pytz.timezone


Test patch:
diff --git a/pandas/sparse/tests/test_sparse.py b/pandas/sparse/tests/test_sparse.py
--- a/pandas/sparse/tests/test_sparse.py
+++ b/pandas/sparse/tests/test_sparse.py
@@ -1084,8 +1084,10 @@ def test_icol(self):
 
     def test_set_value(self):
 
-        # this is invalid because it is not a valid type for this index
-        self.assertRaises(ValueError, self.frame.set_value, 'foobar', 'B', 1.5)
+        # ok as the index gets conver to object
+        frame = self.frame.copy()
+        res = frame.set_value('foobar', 'B', 1.5)
+        self.assert_(res.index.dtype == 'object')
 
         res = self.frame
         res.index = res.index.astype(object)
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -10897,6 +10897,19 @@ def test_reset_index_multiindex_col(self):
                                 ['a', 'mean', 'median', 'mean']])
         assert_frame_equal(rs, xp)
 
+    def test_reset_index_with_datetimeindex_cols(self):
+        # GH5818
+        #
+        df = pd.DataFrame([[1, 2], [3, 4]],
+                          columns=pd.date_range('1/1/2013', '1/2/2013'),
+                          index=['A', 'B'])
+
+        result = df.reset_index()
+        expected = pd.DataFrame([['A', 1, 2], ['B', 3, 4]],
+                          columns=['index', datetime(2013, 1, 1),
+                                   datetime(2013, 1, 2)])
+        assert_frame_equal(result, expected)
+
     #----------------------------------------------------------------------
     # Tests to cope with refactored internals
     def test_as_matrix_numeric_cols(self):
diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py
--- a/pandas/tests/test_indexing.py
+++ b/pandas/tests/test_indexing.py
@@ -1665,15 +1665,13 @@ def test_partial_set_invalid(self):
 
         df = tm.makeTimeDataFrame()
 
+        # don't allow not string inserts
         def f():
             df.loc[100.0, :] = df.ix[0]
         self.assertRaises(ValueError, f)
         def f():
             df.loc[100,:] = df.ix[0]
         self.assertRaises(ValueError, f)
-        def f():
-            df.loc['a',:] = df.ix[0]
-        self.assertRaises(ValueError, f)
 
         def f():
             df.ix[100.0, :] = df.ix[0]
@@ -1682,6 +1680,9 @@ def f():
             df.ix[100,:] = df.ix[0]
         self.assertRaises(ValueError, f)
 
+        # allow object conversion here
+        df.loc['a',:] = df.ix[0]
+
     def test_partial_set_empty(self):
 
         # GH5226
diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py
--- a/pandas/tseries/tests/test_timeseries.py
+++ b/pandas/tseries/tests/test_timeseries.py
@@ -2099,6 +2099,13 @@ def test_insert(self):
                              '2000-01-02'])
         self.assert_(result.equals(exp))
 
+        # insertion of non-datetime should coerce to object index
+        result = idx.insert(1, 'inserted')
+        expected = Index([datetime(2000, 1, 4), 'inserted', datetime(2000, 1, 1),
+                          datetime(2000, 1, 2)])
+        self.assert_(not isinstance(result, DatetimeIndex))
+        tm.assert_index_equal(result, expected)
+
         idx = date_range('1/1/2000', periods=3, freq='M')
         result = idx.insert(3, datetime(2000, 4, 30))
         self.assert_(result.freqstr == 'M')