Skip to content

Commit 298390f

Browse files
committed
wip
1 parent 42ab137 commit 298390f

File tree

7 files changed

+78
-43
lines changed

7 files changed

+78
-43
lines changed

pandas/core/arrays/datetimelike.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,9 @@ def _addsub_offset_array(self, other, op):
518518
left = lib.values_from_object(self.astype('O'))
519519

520520
res_values = op(left, np.array(other))
521-
kwargs = {}
522521
if not is_period_dtype(self):
523-
kwargs['freq'] = 'infer'
524-
return type(self)(res_values, **kwargs)
522+
return type(self)(res_values, freq='infer')
523+
return self._from_sequence(res_values)
525524

526525
@deprecate_kwarg(old_arg_name='n', new_arg_name='periods')
527526
def shift(self, periods, freq=None):
@@ -603,8 +602,6 @@ def __add__(self, other):
603602
elif lib.is_integer(other):
604603
# This check must come after the check for np.timedelta64
605604
# as is_integer returns True for these
606-
# TODO: make a _shift method that's consistent between
607-
# Index and EA
608605
result = self._tshift(other)
609606

610607
# array-like others
@@ -657,7 +654,7 @@ def __sub__(self, other):
657654
elif lib.is_integer(other):
658655
# This check must come after the check for np.timedelta64
659656
# as is_integer returns True for these
660-
result = self.shift(-other)
657+
result = self._tshift(-other)
661658
elif isinstance(other, Period):
662659
result = self._sub_period(other)
663660

pandas/core/arrays/period.py

+38-13
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
)
3131
from pandas.core.dtypes.dtypes import PeriodDtype
3232
from pandas.core.dtypes.generic import (
33-
ABCSeries, ABCIndex, ABCPeriodIndex
33+
ABCSeries, ABCPeriodIndex, ABCIndexClass,
3434
)
3535

3636
import pandas.core.common as com
@@ -63,7 +63,7 @@ def _period_array_cmp(cls, op):
6363

6464
def wrapper(self, other):
6565
op = getattr(self._ndarray_values, opname)
66-
if isinstance(other, (ABCSeries, ABCIndex)):
66+
if isinstance(other, (ABCSeries, ABCIndexClass)):
6767
other = other.values
6868

6969
if isinstance(other, Period):
@@ -72,7 +72,7 @@ def wrapper(self, other):
7272
raise IncompatibleFrequency(msg)
7373

7474
result = op(other.ordinal)
75-
elif isinstance(other, (ABCPeriodIndex, cls)):
75+
elif isinstance(other, cls):
7676
if other.freq != self.freq:
7777
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
7878
raise IncompatibleFrequency(msg)
@@ -88,7 +88,8 @@ def wrapper(self, other):
8888
result = np.empty(len(self._ndarray_values), dtype=bool)
8989
result.fill(nat_result)
9090
elif isinstance(other, (list, np.ndarray)):
91-
# XXX: is this correct?
91+
# XXX: is this correct? Why not convert the
92+
# sequence to a PeriodArray?
9293
return NotImplemented
9394
else:
9495
other = Period(other, freq=self.freq)
@@ -111,16 +112,16 @@ class PeriodArray(DatetimeLikeArrayMixin, ExtensionArray):
111112
- ordinals : integer ndarray
112113
- freq : pd.tseries.offsets.Tick
113114
114-
The values are physically stored as an ndarray of integers. These are
115+
The values are physically stored as a 1-D ndarray of integers. These are
115116
called "ordinals" and represent some kind of offset from a base.
116117
117118
The `freq` indicates the span covered by each element of the array.
118119
All elements in the PeriodArray have the same `freq`.
119120
"""
120121
_attributes = ["freq"]
121-
_typ = "periodarray" # ABCPeriodAray
122+
_typ = "periodarray" # ABCPeriodArray
122123

123-
# Names others delegate to us on
124+
# Names others delegate to us
124125
_other_ops = []
125126
_bool_ops = ['is_leap_year']
126127
_object_ops = ['start_time', 'end_time', 'freq']
@@ -134,7 +135,7 @@ class PeriodArray(DatetimeLikeArrayMixin, ExtensionArray):
134135
# --------------------------------------------------------------------
135136
# Constructors
136137
def __init__(self, values, freq=None):
137-
# type: (np.ndarray[int64], Union[str, Tick]) -> None
138+
# type: (np.ndarray[np.int64], Union[str, Tick]) -> None
138139
values = np.array(values, dtype='int64', copy=False)
139140
self._data = values
140141
if freq is None:
@@ -237,7 +238,7 @@ def _from_ordinals(cls, values, freq=None):
237238
# type: (ndarray[int], Optional[Tick]) -> PeriodArray
238239
"""
239240
Values should be int ordinals
240-
`__new__` & `_simple_new` cooerce to ordinals and call this method
241+
`__new__` & `_simple_new` coerce to ordinals and call this method
241242
"""
242243
return cls(values, freq=freq)
243244

@@ -536,7 +537,7 @@ def _add_offset(self, other):
536537
if base != self.freq.rule_code:
537538
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
538539
raise IncompatibleFrequency(msg)
539-
return self.shift(other.n)
540+
return self._tshift(other.n)
540541

541542
def _add_delta_td(self, other):
542543
assert isinstance(other, (timedelta, np.timedelta64, Tick))
@@ -546,7 +547,7 @@ def _add_delta_td(self, other):
546547
if isinstance(own_offset, Tick):
547548
offset_nanos = delta_to_nanoseconds(own_offset)
548549
if np.all(nanos % offset_nanos == 0):
549-
return self.shift(nanos // offset_nanos)
550+
return self._tshift(nanos // offset_nanos)
550551

551552
# raise when input doesn't have freq
552553
raise IncompatibleFrequency("Input has different freq from "
@@ -556,7 +557,7 @@ def _add_delta_td(self, other):
556557

557558
def _add_delta(self, other):
558559
ordinal_delta = self._maybe_convert_timedelta(other)
559-
return self.shift(ordinal_delta)
560+
return self._tshift(ordinal_delta)
560561

561562
def shift(self, periods=1):
562563
"""
@@ -640,6 +641,7 @@ def _maybe_convert_timedelta(self, other):
640641
freqstr=self.freqstr))
641642

642643
def _format_native_types(self, na_rep=u'NaT', date_format=None):
644+
# TODO(DatetimeArray): remove
643645
values = self.astype(object)
644646

645647
if date_format:
@@ -658,7 +660,8 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None):
658660
return values
659661

660662
def view(self, dtype=None, type=None):
661-
# This is to support PeriodIndex.view('i8')
663+
# This is to support things like `.asi8`
664+
# PeriodIndex's parent does .values.view('i8').
662665
# I don't like adding this,
663666
return self._data.view(dtype=dtype)
664667

@@ -757,6 +760,28 @@ def _box_values_as_index(self):
757760
from pandas.core.index import Index
758761
return Index(self._box_values(self.asi8), dtype=object)
759762

763+
@property
764+
def flags(self):
765+
"""Deprecated"""
766+
# Just here to support Index.flags deprecation.
767+
# could also override PeriodIndex.flags if we don't want a
768+
# version with PeriodArray.flags
769+
return self.values.flags
770+
771+
@property
772+
def base(self):
773+
return self.values.base
774+
775+
@property
776+
def data(self):
777+
return self.astype(object).data
778+
779+
def item(self):
780+
if len(self) == 1:
781+
return Period._from_ordinal(self.values[0], self.freq)
782+
else:
783+
raise ValueError('can only convert an array of size 1 to a '
784+
'Python scalar')
760785

761786
PeriodArray._add_comparison_ops()
762787
PeriodArray._add_datetimelike_methods()

pandas/core/indexes/period.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,13 @@ def _shallow_copy(self, values=None, **kwargs):
262262
else:
263263
# this differs too
264264
if not isinstance(values, PeriodArray):
265-
try:
266-
values = PeriodArray._from_ordinals(values, freq=self.freq)
267-
except TypeError:
268-
# TODO: this is probably ambiguous for some oridinals.
269-
values = PeriodArray._complex_new(values, freq=self.freq)
265+
values = PeriodArray._complex_new(values, freq=self.freq)
266+
267+
# I don't like overloading shallow_copy with freq changes.
268+
# See if it's used anywhere outside of test_resample_empty_dataframe
269+
freq = kwargs.pop("freq", None)
270+
if freq:
271+
values = values.asfreq(freq)
270272

271273
attributes = self._get_attributes_dict()
272274
attributes.update(kwargs)

pandas/core/ops.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,11 @@ def dispatch_to_extension_op(op, left, right):
11731173
if is_extension_array_dtype(left):
11741174

11751175
new_left = left.values
1176+
if (is_extension_array_dtype(right)
1177+
and isinstance(right, (ABCIndexClass, ABCSeries))):
1178+
# unbox
1179+
right = right._values
1180+
11761181
if isinstance(right, np.ndarray):
11771182

11781183
# handle numpy scalars, this is a PITA
@@ -1181,8 +1186,11 @@ def dispatch_to_extension_op(op, left, right):
11811186
if is_scalar(new_right):
11821187
new_right = [new_right]
11831188
new_right = list(new_right)
1189+
elif (is_extension_array_dtype(right) and
1190+
type(new_left) == type(right)):
1191+
new_right = right
11841192
elif is_extension_array_dtype(right) and type(left) != type(right):
1185-
new_right = list(new_right)
1193+
new_right = list(right)
11861194
else:
11871195
new_right = right
11881196

pandas/tests/arithmetic/test_period.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def test_pi_sub_isub_timedeltalike_daily(self, three_days):
623623
def test_pi_add_iadd_timedeltalike_freq_mismatch_daily(self, not_daily):
624624
other = not_daily
625625
rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
626-
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)'
626+
msg = 'Input has different freq(=.+)? from Period.*?\\(freq=D\\)'
627627
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
628628
rng + other
629629
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
@@ -632,7 +632,7 @@ def test_pi_add_iadd_timedeltalike_freq_mismatch_daily(self, not_daily):
632632
def test_pi_sub_timedeltalike_freq_mismatch_daily(self, not_daily):
633633
other = not_daily
634634
rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
635-
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)'
635+
msg = 'Input has different freq(=.+)? from Period.*?\\(freq=D\\)'
636636
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
637637
rng - other
638638

@@ -651,7 +651,7 @@ def test_pi_add_iadd_timedeltalike_hourly(self, two_hours):
651651
def test_pi_add_timedeltalike_mismatched_freq_hourly(self, not_hourly):
652652
other = not_hourly
653653
rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H')
654-
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=H\\)'
654+
msg = 'Input has different freq(=.+)? from Period.*?\\(freq=H\\)'
655655

656656
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
657657
rng + other
@@ -686,7 +686,7 @@ def test_pi_add_iadd_timedeltalike_freq_mismatch_annual(self,
686686
other = mismatched_freq
687687
rng = pd.period_range('2014', '2024', freq='A')
688688
msg = ('Input has different freq(=.+)? '
689-
'from PeriodIndex\\(freq=A-DEC\\)')
689+
'from Period.*?\\(freq=A-DEC\\)')
690690
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
691691
rng + other
692692
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
@@ -697,7 +697,7 @@ def test_pi_sub_isub_timedeltalike_freq_mismatch_annual(self,
697697
other = mismatched_freq
698698
rng = pd.period_range('2014', '2024', freq='A')
699699
msg = ('Input has different freq(=.+)? '
700-
'from PeriodIndex\\(freq=A-DEC\\)')
700+
'from Period.*?\\(freq=A-DEC\\)')
701701
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
702702
rng - other
703703
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
@@ -717,7 +717,7 @@ def test_pi_add_iadd_timedeltalike_freq_mismatch_monthly(self,
717717
mismatched_freq):
718718
other = mismatched_freq
719719
rng = pd.period_range('2014-01', '2016-12', freq='M')
720-
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)'
720+
msg = 'Input has different freq(=.+)? from Period.*?\\(freq=M\\)'
721721
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
722722
rng + other
723723
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
@@ -727,7 +727,7 @@ def test_pi_sub_isub_timedeltalike_freq_mismatch_monthly(self,
727727
mismatched_freq):
728728
other = mismatched_freq
729729
rng = pd.period_range('2014-01', '2016-12', freq='M')
730-
msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)'
730+
msg = 'Input has different freq(=.+)? from Period.*?\\(freq=M\\)'
731731
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
732732
rng - other
733733
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
@@ -852,6 +852,7 @@ def test_pi_ops_errors(self, ng):
852852
with pytest.raises(TypeError):
853853
np.subtract(ng, obj)
854854

855+
@pytest.mark.xfail(reason="GH-22798", strict=True)
855856
def test_pi_ops_nat(self):
856857
idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
857858
freq='M', name='idx')
@@ -876,6 +877,7 @@ def test_pi_ops_nat(self):
876877
self._check(idx + 3, lambda x: x - 3, idx)
877878
self._check(idx + 3, lambda x: np.subtract(x, 3), idx)
878879

880+
@pytest.mark.xfail(reason="TODO", strict=True)
879881
def test_pi_ops_array_int(self):
880882
idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
881883
freq='M', name='idx')
@@ -924,7 +926,7 @@ def test_pi_offset_errors(self):
924926

925927
# Series op is applied per Period instance, thus error is raised
926928
# from Period
927-
msg_idx = r"Input has different freq from PeriodIndex\(freq=D\)"
929+
msg_idx = r"Input has different freq from Period.*?\(freq=D\)"
928930
msg_s = r"Input cannot be converted to Period\(freq=D\)"
929931
for obj, msg in [(idx, msg_idx), (ser, msg_s)]:
930932
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):

pandas/tests/test_base.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,8 @@ def test_iter_box(self):
11981198
(pd.DatetimeIndex(['2017', '2018'], tz="US/Central"), pd.DatetimeIndex,
11991199
'datetime64[ns, US/Central]'),
12001200
(pd.TimedeltaIndex([10**10]), np.ndarray, 'm8[ns]'),
1201-
(pd.PeriodIndex([2018, 2019], freq='A'), np.ndarray, 'object'),
1201+
(pd.PeriodIndex([2018, 2019], freq='A'), pd.core.arrays.PeriodArray,
1202+
pd.core.dtypes.dtypes.PeriodDtype("A-DEC")),
12021203
(pd.IntervalIndex.from_breaks([0, 1, 2]), pd.core.arrays.IntervalArray,
12031204
'interval'),
12041205
])
@@ -1214,6 +1215,8 @@ def test_values_consistent(array, expected_type, dtype):
12141215
tm.assert_index_equal(l_values, r_values)
12151216
elif pd.api.types.is_categorical(l_values):
12161217
tm.assert_categorical_equal(l_values, r_values)
1218+
elif pd.api.types.is_period_dtype(l_values):
1219+
tm.assert_period_array_equal(l_values, r_values)
12171220
elif pd.api.types.is_interval_dtype(l_values):
12181221
tm.assert_interval_array_equal(l_values, r_values)
12191222
else:
@@ -1232,12 +1235,8 @@ def test_values_consistent(array, expected_type, dtype):
12321235
(pd.DatetimeIndex(['2017-01-01T00:00:00'], tz="US/Eastern"),
12331236
np.array(['2017-01-01T05:00:00'], dtype='M8[ns]')),
12341237
(pd.TimedeltaIndex([10**10]), np.array([10**10], dtype='m8[ns]')),
1235-
pytest.param(
1236-
pd.PeriodIndex(['2017', '2018'], freq='D'),
1237-
np.array([17167, 17532]),
1238-
marks=pytest.mark.xfail(reason="PeriodArray Not implemented",
1239-
strict=True)
1240-
),
1238+
(pd.PeriodIndex(['2017', '2018'], freq='D'),
1239+
np.array([17167, 17532])),
12411240
])
12421241
def test_ndarray_values(array, expected):
12431242
l_values = pd.Series(array)._ndarray_values

pandas/tests/test_multilevel.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pandas.core.dtypes.common import is_float_dtype, is_integer_dtype
1717
import pandas.core.common as com
1818
import pandas.util.testing as tm
19+
from pandas.core.arrays import PeriodArray
1920
from pandas.compat import (range, lrange, StringIO, lzip, u, product as
2021
cart_product, zip)
2122
import pandas as pd
@@ -2319,9 +2320,10 @@ def test_reset_index_period(self):
23192320
df = DataFrame(np.arange(9, dtype='int64').reshape(-1, 1),
23202321
index=idx, columns=['a'])
23212322
expected = DataFrame({
2322-
'month': ([pd.Period('2013-01', freq='M')] * 3 +
2323-
[pd.Period('2013-02', freq='M')] * 3 +
2324-
[pd.Period('2013-03', freq='M')] * 3),
2323+
'month': PeriodArray._from_periods(np.array(
2324+
[pd.Period('2013-01', freq='M')] * 3 +
2325+
[pd.Period('2013-02', freq='M')] * 3 +
2326+
[pd.Period('2013-03', freq='M')] * 3, dtype=object)),
23252327
'feature': ['a', 'b', 'c'] * 3,
23262328
'a': np.arange(9, dtype='int64')
23272329
}, columns=['month', 'feature', 'a'])

0 commit comments

Comments
 (0)