Repository: pandas-dev/pandas

Issue:
recursion limit reached when joining period and datetimeindexes
one should probably _not_ be trying to join these types of indices. even so, a stack overflow is unacceptable. there should at least be an error message...

code:

```
In [9]: df = mkdf(10, 10, data_gen_f=lambda *args: randint(2), c_idx_type='p', r_idx_type='dt')

In [10]: s = df.iloc[:5, 0]

In [11]: s.index.join(df.columns, how='outer')
```



Discussion:
cc #3900 

@jreback should joins be allowed here (resulting in an object index)? alternative is to raise a type error disallowing joins between period and datetime indices. more generally, to revisit the issue about datetimeindex joins, should anything besides "date-able" strings and integers be allowed to join, the result being an object index if they were, otherwise raise a typeerror...i think this particular bug might be easy to fix if we choose to raise, but legacy support will most likely be broken since the latter would be more strict with allowed joins...

u can convert period to datetime (and vice versa)
so I think u should do that

though still fuzzy when/if this is useful

i don't think it is that useful and maybe this bug is even a bit academic, lmk if u think this not worth fixing. these kinds of bugs are notoriously difficult because the code path to the cycle has the potential to be _enormous_. thankfully, i just discovered the cycle, and it's not that long. i believe the problem is that object datetime indices are not being converted to datetime indices and thus `self` and `other` dtype keep testing as not equal ad infinitum. 

another way to see it is that periodindexes should be kept as object here and thus the dtypes will test equal on the next recursive call and the code will terminate.

either way if the dtypes are not equal the code attempts conversion to object but the `__new__` method intercepts and periodindexes always get constructed as periodindexes then join is called recursively and never finishes

that's why it might make sense to only allow certain types of joins (and conversions)
raising otherwise

okay...also this bug hidden by the "fake" unicode error that i submitted a pr for last night. later today i'll put up a table with what i think makes sense for conversions and would be nice if u could suggest additions/deletions...then will fix it according to that. thanks

i think implementing this `_assert_can_do_setop` method in the index base class is probably a good idea. and then have a `_join` method (overloaded by subclasses) that is called by `join`

@jreback so one solution that i have working is to convert period/datetime indices to the other depending on which is doing the join. the obvs prb here is all period can convert to datetime where as not all datetime can cvt to period. possible solutions:

1) let periodindex raise when it cannot convert the rhs datetimeindex to period

or

2) convert everything to datetime indexes

i think 1) is the best option since it seems weird to have the periodindex join method return a datetimeindex even if the other is a datetimeindex. i think it should raise if the datetimeindex can't be converted to a periodindex and of course convert if it can

let push to 0.12

what are we doing with this? punt/push?

i'll fix by 0.13

just need to clarify what the exact issue is

as in write it down here so that we can discuss it

why don't we just raise for now? and then let's figure out in 0.14 (if it should even be allowed)

pr in the works

entire problem was that `PeriodIndex.astype(object)` was returning an `ndarray` which would then get converted back to period iindex then reconverted back to raw ndarray and so on until the recursion limit was reeached


Resolving patch:
diff --git a/pandas/tseries/period.py b/pandas/tseries/period.py
--- a/pandas/tseries/period.py
+++ b/pandas/tseries/period.py
@@ -12,8 +12,8 @@
 import pandas.tseries.frequencies as _freq_mod
 
 import pandas.core.common as com
-from pandas.core.common import (isnull, _NS_DTYPE, _INT64_DTYPE,
-                                _maybe_box, _values_from_object)
+from pandas.core.common import (isnull, _INT64_DTYPE, _maybe_box,
+                                _values_from_object)
 from pandas import compat
 from pandas.lib import Timestamp
 import pandas.lib as lib
@@ -712,13 +712,10 @@ def _array_values(self):
     def astype(self, dtype):
         dtype = np.dtype(dtype)
         if dtype == np.object_:
-            result = np.empty(len(self), dtype=dtype)
-            result[:] = [x for x in self]
-            return result
+            return Index(np.array(list(self), dtype), dtype)
         elif dtype == _INT64_DTYPE:
-            return self.values.copy()
-        else:  # pragma: no cover
-            raise ValueError('Cannot cast PeriodIndex to dtype %s' % dtype)
+            return Index(self.values, dtype)
+        raise ValueError('Cannot cast PeriodIndex to dtype %s' % dtype)
 
     def __iter__(self):
         for val in self.values:


Test patch:
diff --git a/pandas/tseries/tests/test_period.py b/pandas/tseries/tests/test_period.py
--- a/pandas/tseries/tests/test_period.py
+++ b/pandas/tseries/tests/test_period.py
@@ -22,8 +22,8 @@
 import pandas.core.datetools as datetools
 import pandas as pd
 import numpy as np
-from pandas.compat import range, lrange, lmap, map, zip
-randn = np.random.randn
+from numpy.random import randn
+from pandas.compat import range, lrange, lmap, zip
 
 from pandas import Series, TimeSeries, DataFrame
 from pandas.util.testing import(assert_series_equal, assert_almost_equal,
@@ -1207,7 +1207,6 @@ def test_is_(self):
         self.assertFalse(index.is_(index - 2))
         self.assertFalse(index.is_(index - 0))
 
-
     def test_comp_period(self):
         idx = period_range('2007-01', periods=20, freq='M')
 
@@ -1913,6 +1912,17 @@ def test_join_self(self):
             res = index.join(index, how=kind)
             self.assert_(index is res)
 
+    def test_join_does_not_recur(self):
+        df = tm.makeCustomDataframe(3, 2, data_gen_f=lambda *args:
+                                    np.random.randint(2), c_idx_type='p',
+                                    r_idx_type='dt')
+        s = df.iloc[:2, 0]
+
+        res = s.index.join(df.columns, how='outer')
+        expected = Index([s.index[0], s.index[1],
+                          df.columns[0], df.columns[1]], object)
+        tm.assert_index_equal(res, expected)
+
     def test_align_series(self):
         rng = period_range('1/1/2000', '1/1/2010', freq='A')
         ts = Series(np.random.randn(len(rng)), index=rng)
@@ -2185,15 +2195,15 @@ def test_minutely(self):
 
     def test_secondly(self):
         self._check_freq('S', '1970-01-01')
-        
+
     def test_millisecondly(self):
         self._check_freq('L', '1970-01-01')
 
     def test_microsecondly(self):
         self._check_freq('U', '1970-01-01')
-        
+
     def test_nanosecondly(self):
-        self._check_freq('N', '1970-01-01')        
+        self._check_freq('N', '1970-01-01')
 
     def _check_freq(self, freq, base_date):
         rng = PeriodIndex(start=base_date, periods=10, freq=freq)
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
@@ -31,16 +31,12 @@
 
 import pandas.index as _index
 
-from pandas.compat import(
-    range, long, StringIO, lrange, lmap, map, zip, cPickle as pickle, product
-)
-from pandas import read_pickle
+from pandas.compat import range, long, StringIO, lrange, lmap, zip, product
 import pandas.core.datetools as dt
 from numpy.random import rand
 from numpy.testing import assert_array_equal
 from pandas.util.testing import assert_frame_equal
 import pandas.compat as compat
-from pandas.core.datetools import BDay
 import pandas.core.common as com
 from pandas import concat
 from pandas import _np_version_under1p7
@@ -2064,6 +2060,18 @@ def test_ns_index(self):
         new_index = pd.DatetimeIndex(start=index[0], end=index[-1], freq=index.freq)
         self.assert_index_parameters(new_index)
 
+    def test_join_with_period_index(self):
+        df = tm.makeCustomDataframe(10, 10, data_gen_f=lambda *args:
+                                    np.random.randint(2), c_idx_type='p',
+                                    r_idx_type='dt')
+        s = df.iloc[:5, 0]
+        joins = 'left', 'right', 'inner', 'outer'
+
+        for join in joins:
+            with tm.assertRaisesRegexp(ValueError, 'can only call with other '
+                                       'PeriodIndex-ed objects'):
+                df.columns.join(s.index, how=join)
+
 
 class TestDatetime64(unittest.TestCase):
     """