Skip to content

Commit 0f3a7b8

Browse files
sinhrksjreback
authored andcommitted
BUG: Mixed period cannot be displayed with ValueError
we can create Series contains Periods with mixed freq s = pd.Series([pd.Period('2011-01', freq='M'), pd.Period('2011-02-01', freq='D')]) ValueError: Input has different freq=D Author: sinhrks <[email protected]> Closes pandas-dev#12615 from sinhrks/period_format and squashes the following commits: a107294 [sinhrks] BUG: Mixed period cannot be displayed
1 parent bf89220 commit 0f3a7b8

File tree

8 files changed

+108
-53
lines changed

8 files changed

+108
-53
lines changed

doc/source/whatsnew/v0.18.1.txt

+2-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Enhancements
4444
API changes
4545
~~~~~~~~~~~
4646

47-
47+
- ``Period`` and ``PeriodIndex`` now raises ``IncompatibleFrequency`` error which inherits ``ValueError`` rather than raw ``ValueError`` (:issue:`12615`)
4848

4949

5050

@@ -91,6 +91,7 @@ Bug Fixes
9191
~~~~~~~~~
9292

9393
- Bug in ``Period`` and ``PeriodIndex`` creation raises ``KeyError`` if ``freq="Minute"`` is specified. Note that "Minute" freq is deprecated in v0.17.0, and recommended to use ``freq="T"`` instead (:issue:`11854`)
94+
- Bug in printing data which contains ``Period`` with different ``freq`` raises ``ValueError`` (:issue:`12615`)
9495
- Bug in ``Series`` construction with ``Categorical`` and ``dtype='category'`` is specified (:issue:`12574`)
9596

9697

@@ -101,7 +102,6 @@ Bug Fixes
101102

102103

103104

104-
105105
- Bug in ``value_counts`` when ``normalize=True`` and ``dropna=True`` where nulls still contributed to the normalized count (:issue:`12558`)
106106
- Bug in ``Panel.fillna()`` ignoring ``inplace=True`` (:issue:`12633`)
107107
- Bug in ``Series.rename``, ``DataFrame.rename`` and ``DataFrame.rename_axis`` not treating ``Series`` as mappings to relabel (:issue:`12623`).
@@ -135,5 +135,4 @@ Bug Fixes
135135

136136

137137

138-
139138
- Bug in ``pivot_table`` when ``margins=True`` and ``dropna=True`` where nulls still contributed to margin count (:issue:`12577`)

pandas/core/format.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -2235,7 +2235,13 @@ def _format_strings(self):
22352235

22362236
class PeriodArrayFormatter(IntArrayFormatter):
22372237
def _format_strings(self):
2238-
values = PeriodIndex(self.values).to_native_types()
2238+
from pandas.tseries.period import IncompatibleFrequency
2239+
try:
2240+
values = PeriodIndex(self.values).to_native_types()
2241+
except IncompatibleFrequency:
2242+
# periods may contains different freq
2243+
values = Index(self.values, dtype='object').to_native_types()
2244+
22392245
formatter = self.formatter or (lambda x: '%s' % x)
22402246
fmt_values = [formatter(x) for x in values]
22412247
return fmt_values

pandas/src/period.pyx

+9-3
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,8 @@ def extract_ordinals(ndarray[object] values, freq):
452452
p = values[i]
453453
ordinals[i] = p.ordinal
454454
if p.freqstr != freqstr:
455-
raise ValueError(_DIFFERENT_FREQ_INDEX.format(freqstr, p.freqstr))
455+
msg = _DIFFERENT_FREQ_INDEX.format(freqstr, p.freqstr)
456+
raise IncompatibleFrequency(msg)
456457

457458
return ordinals
458459

@@ -627,6 +628,11 @@ cdef ndarray[int64_t] localize_dt64arr_to_period(ndarray[int64_t] stamps,
627628
_DIFFERENT_FREQ = "Input has different freq={1} from Period(freq={0})"
628629
_DIFFERENT_FREQ_INDEX = "Input has different freq={1} from PeriodIndex(freq={0})"
629630

631+
632+
class IncompatibleFrequency(ValueError):
633+
pass
634+
635+
630636
cdef class Period(object):
631637
"""
632638
Represents an period of time
@@ -768,7 +774,7 @@ cdef class Period(object):
768774
from pandas.tseries.frequencies import get_freq_code as _gfc
769775
if other.freq != self.freq:
770776
msg = _DIFFERENT_FREQ.format(self.freqstr, other.freqstr)
771-
raise ValueError(msg)
777+
raise IncompatibleFrequency(msg)
772778
if self.ordinal == tslib.iNaT or other.ordinal == tslib.iNaT:
773779
return _nat_scalar_rules[op]
774780
return PyObject_RichCompareBool(self.ordinal, other.ordinal, op)
@@ -809,7 +815,7 @@ cdef class Period(object):
809815
ordinal = self.ordinal + other.n
810816
return Period(ordinal=ordinal, freq=self.freq)
811817
msg = _DIFFERENT_FREQ.format(self.freqstr, other.freqstr)
812-
raise ValueError(msg)
818+
raise IncompatibleFrequency(msg)
813819
else: # pragma no cover
814820
return NotImplemented
815821

pandas/tests/test_format.py

+35
Original file line numberDiff line numberDiff line change
@@ -3151,6 +3151,20 @@ def test_to_csv_engine_kw_deprecation(self):
31513151
df = DataFrame({'col1': [1], 'col2': ['a'], 'col3': [10.1]})
31523152
df.to_csv(engine='python')
31533153

3154+
def test_period(self):
3155+
# GH 12615
3156+
df = pd.DataFrame({'A': pd.period_range('2013-01',
3157+
periods=4, freq='M'),
3158+
'B': [pd.Period('2011-01', freq='M'),
3159+
pd.Period('2011-02-01', freq='D'),
3160+
pd.Period('2011-03-01 09:00', freq='H'),
3161+
pd.Period('2011-04', freq='M')],
3162+
'C': list('abcd')})
3163+
exp = (" A B C\n0 2013-01 2011-01 a\n"
3164+
"1 2013-02 2011-02-01 b\n2 2013-03 2011-03-01 09:00 c\n"
3165+
"3 2013-04 2011-04 d")
3166+
self.assertEqual(str(df), exp)
3167+
31543168

31553169
class TestSeriesFormatting(tm.TestCase):
31563170
_multiprocess_can_split_ = True
@@ -3481,6 +3495,27 @@ def test_mixed_datetime64(self):
34813495
result = repr(df.ix[0])
34823496
self.assertTrue('2012-01-01' in result)
34833497

3498+
def test_period(self):
3499+
# GH 12615
3500+
index = pd.period_range('2013-01', periods=6, freq='M')
3501+
s = Series(np.arange(6), index=index)
3502+
exp = ("2013-01 0\n2013-02 1\n2013-03 2\n2013-04 3\n"
3503+
"2013-05 4\n2013-06 5\nFreq: M, dtype: int64")
3504+
self.assertEqual(str(s), exp)
3505+
3506+
s = Series(index)
3507+
exp = ("0 2013-01\n1 2013-02\n2 2013-03\n3 2013-04\n"
3508+
"4 2013-05\n5 2013-06\ndtype: object")
3509+
self.assertEqual(str(s), exp)
3510+
3511+
# periods with mixed freq
3512+
s = Series([pd.Period('2011-01', freq='M'),
3513+
pd.Period('2011-02-01', freq='D'),
3514+
pd.Period('2011-03-01 09:00', freq='H')])
3515+
exp = ("0 2011-01\n1 2011-02-01\n"
3516+
"2 2011-03-01 09:00\ndtype: object")
3517+
self.assertEqual(str(s), exp)
3518+
34843519
def test_max_multi_index_display(self):
34853520
# GH 7101
34863521

pandas/tseries/common.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pandas.core.base import PandasDelegate, NoNewAttributesMixin
77
from pandas.core import common as com
88
from pandas.tseries.index import DatetimeIndex
9+
from pandas._period import IncompatibleFrequency # flake8: noqa
910
from pandas.tseries.period import PeriodIndex
1011
from pandas.tseries.tdi import TimedeltaIndex
1112
from pandas import tslib

pandas/tseries/period.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@
88
from pandas.tseries.tools import parse_time_string
99
import pandas.tseries.offsets as offsets
1010

11-
from pandas._period import Period
1211
import pandas._period as period
13-
from pandas._period import (
14-
get_period_field_arr,
15-
_validate_end_alias,
16-
_quarter_to_myear,
17-
)
12+
from pandas._period import (Period, IncompatibleFrequency,
13+
get_period_field_arr, _validate_end_alias,
14+
_quarter_to_myear)
1815

1916
import pandas.core.common as com
2017
from pandas.core.common import (isnull, _INT64_DTYPE, _maybe_box,
@@ -69,13 +66,13 @@ def wrapper(self, other):
6966
other_base, _ = _gfc(other.freq)
7067
if other.freq != self.freq:
7168
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
72-
raise ValueError(msg)
69+
raise IncompatibleFrequency(msg)
7370

7471
result = func(other.ordinal)
7572
elif isinstance(other, PeriodIndex):
7673
if other.freq != self.freq:
7774
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
78-
raise ValueError(msg)
75+
raise IncompatibleFrequency(msg)
7976

8077
result = getattr(self.values, opname)(other.values)
8178

@@ -392,7 +389,7 @@ def searchsorted(self, key, side='left'):
392389
if isinstance(key, Period):
393390
if key.freq != self.freq:
394391
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, key.freqstr)
395-
raise ValueError(msg)
392+
raise IncompatibleFrequency(msg)
396393
key = key.ordinal
397394
elif isinstance(key, compat.string_types):
398395
key = Period(key, freq=self.freq).ordinal
@@ -573,6 +570,8 @@ def _maybe_convert_timedelta(self, other):
573570
base = frequencies.get_base_alias(freqstr)
574571
if base == self.freq.rule_code:
575572
return other.n
573+
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
574+
raise IncompatibleFrequency(msg)
576575
elif isinstance(other, np.ndarray):
577576
if com.is_integer_dtype(other):
578577
return other
@@ -583,8 +582,9 @@ def _maybe_convert_timedelta(self, other):
583582
offset_nanos = tslib._delta_to_nanoseconds(offset)
584583
if (nanos % offset_nanos).all() == 0:
585584
return nanos // offset_nanos
585+
# raise when input doesn't have freq
586586
msg = "Input has different freq from PeriodIndex(freq={0})"
587-
raise ValueError(msg.format(self.freqstr))
587+
raise IncompatibleFrequency(msg.format(self.freqstr))
588588

589589
def _add_delta(self, other):
590590
ordinal_delta = self._maybe_convert_timedelta(other)
@@ -663,8 +663,8 @@ def get_value(self, series, key):
663663

664664
def get_indexer(self, target, method=None, limit=None, tolerance=None):
665665
if hasattr(target, 'freq') and target.freq != self.freq:
666-
raise ValueError('target and index have different freq: '
667-
'(%s, %s)' % (target.freq, self.freq))
666+
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, target.freqstr)
667+
raise IncompatibleFrequency(msg)
668668
return Index.get_indexer(self, target, method, limit, tolerance)
669669

670670
def get_loc(self, key, method=None, tolerance=None):
@@ -801,7 +801,7 @@ def _assert_can_do_setop(self, other):
801801

802802
if self.freq != other.freq:
803803
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
804-
raise ValueError(msg)
804+
raise IncompatibleFrequency(msg)
805805

806806
def _wrap_union_result(self, other, result):
807807
name = self.name if self.name == other.name else None

pandas/tseries/tests/test_base.py

+21-19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
PeriodIndex, TimedeltaIndex, Timedelta, timedelta_range,
77
date_range, Float64Index)
88
import pandas.tslib as tslib
9+
import pandas.tseries.period as period
910

1011
import pandas.util.testing as tm
1112

@@ -1617,9 +1618,9 @@ def test_add_iadd(self):
16171618
for o in [pd.offsets.YearBegin(2), pd.offsets.MonthBegin(1),
16181619
pd.offsets.Minute(), np.timedelta64(365, 'D'),
16191620
timedelta(365), Timedelta(days=365)]:
1620-
msg = 'Input has different freq from PeriodIndex\\(freq=A-DEC\\)'
1621-
with tm.assertRaisesRegexp(ValueError,
1622-
'Input has different freq from Period'):
1621+
msg = ('Input has different freq(=.+)? '
1622+
'from PeriodIndex\\(freq=A-DEC\\)')
1623+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
16231624
rng + o
16241625

16251626
rng = pd.period_range('2014-01', '2016-12', freq='M')
@@ -1633,8 +1634,8 @@ def test_add_iadd(self):
16331634
pd.offsets.Minute(), np.timedelta64(365, 'D'),
16341635
timedelta(365), Timedelta(days=365)]:
16351636
rng = pd.period_range('2014-01', '2016-12', freq='M')
1636-
msg = 'Input has different freq from PeriodIndex\\(freq=M\\)'
1637-
with tm.assertRaisesRegexp(ValueError, msg):
1637+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)'
1638+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
16381639
rng + o
16391640

16401641
# Tick
@@ -1654,8 +1655,8 @@ def test_add_iadd(self):
16541655
pd.offsets.Minute(), np.timedelta64(4, 'h'),
16551656
timedelta(hours=23), Timedelta('23:00:00')]:
16561657
rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
1657-
msg = 'Input has different freq from PeriodIndex\\(freq=D\\)'
1658-
with tm.assertRaisesRegexp(ValueError, msg):
1658+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)'
1659+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
16591660
rng + o
16601661

16611662
offsets = [pd.offsets.Hour(2), timedelta(hours=2),
@@ -1676,10 +1677,10 @@ def test_add_iadd(self):
16761677
np.timedelta64(30, 's'), Timedelta(seconds=30)]:
16771678
rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00',
16781679
freq='H')
1679-
msg = 'Input has different freq from PeriodIndex\\(freq=H\\)'
1680-
with tm.assertRaisesRegexp(ValueError, msg):
1680+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=H\\)'
1681+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
16811682
result = rng + delta
1682-
with tm.assertRaisesRegexp(ValueError, msg):
1683+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
16831684
rng += delta
16841685

16851686
# int
@@ -1745,8 +1746,9 @@ def test_sub_isub(self):
17451746
pd.offsets.Minute(), np.timedelta64(365, 'D'),
17461747
timedelta(365)]:
17471748
rng = pd.period_range('2014', '2024', freq='A')
1748-
msg = 'Input has different freq from PeriodIndex\\(freq=A-DEC\\)'
1749-
with tm.assertRaisesRegexp(ValueError, msg):
1749+
msg = ('Input has different freq(=.+)? '
1750+
'from PeriodIndex\\(freq=A-DEC\\)')
1751+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
17501752
rng - o
17511753

17521754
rng = pd.period_range('2014-01', '2016-12', freq='M')
@@ -1760,8 +1762,8 @@ def test_sub_isub(self):
17601762
pd.offsets.Minute(), np.timedelta64(365, 'D'),
17611763
timedelta(365)]:
17621764
rng = pd.period_range('2014-01', '2016-12', freq='M')
1763-
msg = 'Input has different freq from PeriodIndex\\(freq=M\\)'
1764-
with tm.assertRaisesRegexp(ValueError, msg):
1765+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)'
1766+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
17651767
rng - o
17661768

17671769
# Tick
@@ -1780,8 +1782,8 @@ def test_sub_isub(self):
17801782
pd.offsets.Minute(), np.timedelta64(4, 'h'),
17811783
timedelta(hours=23)]:
17821784
rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
1783-
msg = 'Input has different freq from PeriodIndex\\(freq=D\\)'
1784-
with tm.assertRaisesRegexp(ValueError, msg):
1785+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)'
1786+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
17851787
rng - o
17861788

17871789
offsets = [pd.offsets.Hour(2), timedelta(hours=2),
@@ -1801,10 +1803,10 @@ def test_sub_isub(self):
18011803
np.timedelta64(30, 's')]:
18021804
rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00',
18031805
freq='H')
1804-
msg = 'Input has different freq from PeriodIndex\\(freq=H\\)'
1805-
with tm.assertRaisesRegexp(ValueError, msg):
1806+
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=H\\)'
1807+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
18061808
result = rng + delta
1807-
with tm.assertRaisesRegexp(ValueError, msg):
1809+
with tm.assertRaisesRegexp(period.IncompatibleFrequency, msg):
18081810
rng += delta
18091811

18101812
# int

0 commit comments

Comments
 (0)