Skip to content

Commit 70eb92b

Browse files
committed
BUG: DatetimeIndex.asobject raises ValueError when contains NaT
1 parent ac755b5 commit 70eb92b

File tree

8 files changed

+125
-82
lines changed

8 files changed

+125
-82
lines changed

doc/source/v0.14.1.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ Bug Fixes
248248
- BUG in ``resample`` raises ``ValueError`` when target contains ``NaT`` (:issue:`7227`)
249249

250250
- Bug in ``Timestamp.tz_localize`` resets ``nanosecond`` info (:issue:`7534`)
251-
251+
- Bug in ``DatetimeIndex.asobject`` raises ``ValueError`` when it contains ``NaT`` (:issue:`7539`)
252252

253253

254254
- Bug in ``Index.astype(float)`` where it would return an ``object`` dtype

pandas/core/base.py

+26
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,29 @@ def _ops_compat(self, name, op_accessor):
376376
is_quarter_end = _field_accessor('is_quarter_end', "Logical indicating if last day of quarter (defined by frequency)")
377377
is_year_start = _field_accessor('is_year_start', "Logical indicating if first day of year (defined by frequency)")
378378
is_year_end = _field_accessor('is_year_end', "Logical indicating if last day of year (defined by frequency)")
379+
380+
@property
381+
def _box_func(self):
382+
"""
383+
box function to get object from internal representation
384+
"""
385+
raise NotImplementedError
386+
387+
def _box_values(self, values):
388+
"""
389+
apply box func to passed values
390+
"""
391+
import pandas.lib as lib
392+
return lib.map_infer(values, self._box_func)
393+
394+
@property
395+
def asobject(self):
396+
from pandas.core.index import Index
397+
return Index(self._box_values(self.asi8), name=self.name, dtype=object)
398+
399+
def tolist(self):
400+
"""
401+
See ndarray.tolist
402+
"""
403+
return list(self.asobject)
404+

pandas/tests/test_base.py

+70
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,44 @@ def test_ops_properties_basic(self):
515515
self.assertEquals(s.day,10)
516516
self.assertRaises(AttributeError, lambda : s.weekday)
517517

518+
def test_asobject_tolist(self):
519+
idx = pd.date_range(start='2013-01-01', periods=4, freq='M', name='idx')
520+
expected_list = [pd.Timestamp('2013-01-31'), pd.Timestamp('2013-02-28'),
521+
pd.Timestamp('2013-03-31'), pd.Timestamp('2013-04-30')]
522+
expected = pd.Index(expected_list, dtype=object, name='idx')
523+
result = idx.asobject
524+
self.assertTrue(isinstance(result, Index))
525+
self.assertEqual(result.dtype, object)
526+
self.assertTrue(result.equals(expected))
527+
self.assertEqual(result.name, expected.name)
528+
self.assertEqual(idx.tolist(), expected_list)
529+
530+
idx = pd.date_range(start='2013-01-01', periods=4, freq='M', name='idx', tz='Asia/Tokyo')
531+
expected_list = [pd.Timestamp('2013-01-31', tz='Asia/Tokyo'),
532+
pd.Timestamp('2013-02-28', tz='Asia/Tokyo'),
533+
pd.Timestamp('2013-03-31', tz='Asia/Tokyo'),
534+
pd.Timestamp('2013-04-30', tz='Asia/Tokyo')]
535+
expected = pd.Index(expected_list, dtype=object, name='idx')
536+
result = idx.asobject
537+
self.assertTrue(isinstance(result, Index))
538+
self.assertEqual(result.dtype, object)
539+
self.assertTrue(result.equals(expected))
540+
self.assertEqual(result.name, expected.name)
541+
self.assertEqual(idx.tolist(), expected_list)
542+
543+
idx = DatetimeIndex([datetime(2013, 1, 1), datetime(2013, 1, 2),
544+
pd.NaT, datetime(2013, 1, 4)], name='idx')
545+
expected_list = [pd.Timestamp('2013-01-01'), pd.Timestamp('2013-01-02'),
546+
pd.NaT, pd.Timestamp('2013-01-04')]
547+
expected = pd.Index(expected_list, dtype=object, name='idx')
548+
result = idx.asobject
549+
self.assertTrue(isinstance(result, Index))
550+
self.assertEqual(result.dtype, object)
551+
self.assertTrue(result.equals(expected))
552+
self.assertEqual(result.name, expected.name)
553+
self.assertEqual(idx.tolist(), expected_list)
554+
555+
518556
class TestPeriodIndexOps(Ops):
519557
_allowed = '_allow_period_index_ops'
520558

@@ -528,6 +566,38 @@ def test_ops_properties(self):
528566
self.check_ops_properties(['year','month','day','hour','minute','second','weekofyear','week','dayofweek','dayofyear','quarter'])
529567
self.check_ops_properties(['qyear'], lambda x: isinstance(x,PeriodIndex))
530568

569+
def test_asobject_tolist(self):
570+
idx = pd.period_range(start='2013-01-01', periods=4, freq='M', name='idx')
571+
expected_list = [pd.Period('2013-01-31', freq='M'), pd.Period('2013-02-28', freq='M'),
572+
pd.Period('2013-03-31', freq='M'), pd.Period('2013-04-30', freq='M')]
573+
expected = pd.Index(expected_list, dtype=object, name='idx')
574+
result = idx.asobject
575+
self.assertTrue(isinstance(result, Index))
576+
self.assertEqual(result.dtype, object)
577+
self.assertTrue(result.equals(expected))
578+
self.assertEqual(result.name, expected.name)
579+
self.assertEqual(idx.tolist(), expected_list)
580+
581+
idx = PeriodIndex(['2013-01-01', '2013-01-02', 'NaT', '2013-01-04'], freq='D', name='idx')
582+
expected_list = [pd.Period('2013-01-01', freq='D'), pd.Period('2013-01-02', freq='D'),
583+
pd.Period('NaT', freq='D'), pd.Period('2013-01-04', freq='D')]
584+
expected = pd.Index(expected_list, dtype=object, name='idx')
585+
result = idx.asobject
586+
self.assertTrue(isinstance(result, Index))
587+
self.assertEqual(result.dtype, object)
588+
for i in [0, 1, 3]:
589+
self.assertTrue(result[i], expected[i])
590+
self.assertTrue(result[2].ordinal, pd.tslib.iNaT)
591+
self.assertTrue(result[2].freq, 'D')
592+
self.assertEqual(result.name, expected.name)
593+
594+
result_list = idx.tolist()
595+
for i in [0, 1, 3]:
596+
self.assertTrue(result_list[i], expected_list[i])
597+
self.assertTrue(result_list[2].ordinal, pd.tslib.iNaT)
598+
self.assertTrue(result_list[2].freq, 'D')
599+
600+
531601
if __name__ == '__main__':
532602
import nose
533603

pandas/tests/test_multilevel.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def test_append(self):
7272
tm.assert_series_equal(result, self.frame['A'])
7373

7474
def test_append_index(self):
75+
tm._skip_if_no_pytz()
7576

7677
idx1 = Index([1.1, 1.2, 1.3])
7778
idx2 = pd.date_range('2011-01-01', freq='D', periods=3, tz='Asia/Tokyo')
@@ -81,17 +82,18 @@ def test_append_index(self):
8182
midx_lv3 = MultiIndex.from_arrays([idx1, idx2, idx3])
8283

8384
result = idx1.append(midx_lv2)
84-
expected = Index([1.1, 1.2, 1.3,
85-
(1.1, datetime.datetime(2010, 12, 31, 15, 0)),
86-
(1.2, datetime.datetime(2011, 1, 1, 15, 0)),
87-
(1.3, datetime.datetime(2011, 1, 2, 15, 0))])
85+
86+
# GH 7112
87+
import pytz
88+
tz = pytz.timezone('Asia/Tokyo')
89+
expected_tuples = [(1.1, datetime.datetime(2011, 1, 1, tzinfo=tz)),
90+
(1.2, datetime.datetime(2011, 1, 2, tzinfo=tz)),
91+
(1.3, datetime.datetime(2011, 1, 3, tzinfo=tz))]
92+
expected = Index([1.1, 1.2, 1.3] + expected_tuples)
8893
self.assert_(result.equals(expected))
8994

9095
result = midx_lv2.append(idx1)
91-
expected = Index([(1.1, datetime.datetime(2010, 12, 31, 15, 0)),
92-
(1.2, datetime.datetime(2011, 1, 1, 15, 0)),
93-
(1.3, datetime.datetime(2011, 1, 2, 15, 0)),
94-
1.1, 1.2, 1.3])
96+
expected = Index(expected_tuples + [1.1, 1.2, 1.3])
9597
self.assert_(result.equals(expected))
9698

9799
result = midx_lv2.append(midx_lv2)
@@ -103,12 +105,10 @@ def test_append_index(self):
103105

104106
result = midx_lv3.append(midx_lv2)
105107
expected = Index._simple_new(
106-
np.array([(1.1, datetime.datetime(2010, 12, 31, 15, 0), 'A'),
107-
(1.2, datetime.datetime(2011, 1, 1, 15, 0), 'B'),
108-
(1.3, datetime.datetime(2011, 1, 2, 15, 0), 'C'),
109-
(1.1, datetime.datetime(2010, 12, 31, 15, 0)),
110-
(1.2, datetime.datetime(2011, 1, 1, 15, 0)),
111-
(1.3, datetime.datetime(2011, 1, 2, 15, 0))]), None)
108+
np.array([(1.1, datetime.datetime(2011, 1, 1, tzinfo=tz), 'A'),
109+
(1.2, datetime.datetime(2011, 1, 2, tzinfo=tz), 'B'),
110+
(1.3, datetime.datetime(2011, 1, 3, tzinfo=tz), 'C')]
111+
+ expected_tuples), None)
112112
self.assert_(result.equals(expected))
113113

114114
def test_dataframe_constructor(self):

pandas/tseries/index.py

+10-31
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
import numpy as np
88

9-
from pandas.core.common import (isnull, _NS_DTYPE, _INT64_DTYPE,
10-
is_list_like,_values_from_object, _maybe_box,
11-
notnull, ABCSeries)
12-
from pandas.core.index import Index, Int64Index, _Identity, Float64Index
9+
from pandas.core.common import (_NS_DTYPE, _INT64_DTYPE,
10+
_values_from_object, _maybe_box,
11+
ABCSeries)
12+
from pandas.core.index import Index, Int64Index, Float64Index
1313
import pandas.compat as compat
1414
from pandas.compat import u
1515
from pandas.tseries.frequencies import (
1616
infer_freq, to_offset, get_period_alias,
17-
Resolution, get_reso_string, get_offset)
17+
Resolution, get_reso_string)
1818
from pandas.core.base import DatetimeIndexOpsMixin
1919
from pandas.tseries.offsets import DateOffset, generate_range, Tick, CDay
2020
from pandas.tseries.tools import parse_time_string, normalize_date
@@ -29,7 +29,6 @@
2929
import pandas.algos as _algos
3030
import pandas.index as _index
3131

32-
from pandas.tslib import isleapyear
3332

3433
def _utc():
3534
import pytz
@@ -452,8 +451,9 @@ def _generate(cls, start, end, periods, name, offset,
452451

453452
return index
454453

455-
def _box_values(self, values):
456-
return lib.map_infer(values, lib.Timestamp)
454+
@property
455+
def _box_func(self):
456+
return lambda x: Timestamp(x, offset=self.offset, tz=self.tz)
457457

458458
def _local_timestamps(self):
459459
utc = _utc()
@@ -673,7 +673,7 @@ def _format_with_header(self, header, **kwargs):
673673

674674
def _format_native_types(self, na_rep=u('NaT'),
675675
date_format=None, **kwargs):
676-
data = self._get_object_index()
676+
data = self.asobject
677677
from pandas.core.format import Datetime64Formatter
678678
return Datetime64Formatter(values=data,
679679
nat_rep=na_rep,
@@ -778,27 +778,6 @@ def _to_embed(self, keep_tz=False):
778778
return self.asobject.values
779779
return self.values
780780

781-
@property
782-
def asobject(self):
783-
"""
784-
Convert to Index of datetime objects
785-
"""
786-
if isnull(self).any():
787-
msg = 'DatetimeIndex with NaT cannot be converted to object'
788-
raise ValueError(msg)
789-
return self._get_object_index()
790-
791-
def tolist(self):
792-
"""
793-
See ndarray.tolist
794-
"""
795-
return list(self.asobject)
796-
797-
def _get_object_index(self):
798-
boxfunc = lambda x: Timestamp(x, offset=self.offset, tz=self.tz)
799-
boxed_values = lib.map_infer(self.asi8, boxfunc)
800-
return Index(boxed_values, dtype=object, name=self.name)
801-
802781
def to_pydatetime(self):
803782
"""
804783
Return DatetimeIndex as object ndarray of datetime.datetime objects
@@ -1515,7 +1494,7 @@ def normalize(self):
15151494
tz=self.tz)
15161495

15171496
def __iter__(self):
1518-
return iter(self._get_object_index())
1497+
return iter(self.asobject)
15191498

15201499
def searchsorted(self, key, side='left'):
15211500
if isinstance(key, np.ndarray):

pandas/tseries/period.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,9 @@ def __contains__(self, key):
723723
return False
724724
return key.ordinal in self._engine
725725

726-
def _box_values(self, values):
727-
f = lambda x: Period(ordinal=x, freq=self.freq)
728-
return lib.map_infer(values, f)
726+
@property
727+
def _box_func(self):
728+
return lambda x: Period(ordinal=x, freq=self.freq)
729729

730730
def asof_locs(self, where, mask):
731731
"""
@@ -747,10 +747,6 @@ def asof_locs(self, where, mask):
747747

748748
return result
749749

750-
@property
751-
def asobject(self):
752-
return Index(self._box_values(self.values), name=self.name, dtype=object)
753-
754750
def _array_values(self):
755751
return self.asobject
756752

@@ -854,12 +850,6 @@ def equals(self, other):
854850

855851
return np.array_equal(self.asi8, other.asi8)
856852

857-
def tolist(self):
858-
"""
859-
Return a list of Period objects
860-
"""
861-
return self._get_object_array().tolist()
862-
863853
def to_timestamp(self, freq=None, how='start'):
864854
"""
865855
Cast to DatetimeIndex

pandas/tseries/tests/test_period.py

-12
Original file line numberDiff line numberDiff line change
@@ -1448,18 +1448,6 @@ def test_to_timestamp_period_nat(self):
14481448
self.assertTrue(result2.equals(index))
14491449
self.assertEqual(result2.name, 'idx')
14501450

1451-
def test_asobject_period_nat(self):
1452-
index = PeriodIndex(['NaT', '2011-01', '2011-02'], freq='M', name='idx')
1453-
1454-
result = index.asobject
1455-
self.assertTrue(isinstance(result, Index))
1456-
self.assertEqual(result.dtype, object)
1457-
self.assertTrue(isinstance(result[0], Period))
1458-
self.assertEqual(result[0].ordinal, tslib.iNaT)
1459-
self.assertEqual(result[1], Period('2011-01', freq='M'))
1460-
self.assertEqual(result[2], Period('2011-02', freq='M'))
1461-
self.assertEqual(result.name, 'idx')
1462-
14631451
def test_as_frame_columns(self):
14641452
rng = period_range('1/1/2000', periods=5)
14651453
df = DataFrame(randn(10, 5), columns=rng)

pandas/tseries/tests/test_timeseries.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pylint: disable-msg=E1101,W0612
2-
from datetime import datetime, time, timedelta, date
2+
from datetime import datetime, time, timedelta
33
import sys
44
import operator
55

@@ -2363,16 +2363,6 @@ def test_order(self):
23632363
self.assertTrue(ordered[::-1].is_monotonic)
23642364
self.assert_numpy_array_equal(dexer, [0, 2, 1])
23652365

2366-
def test_asobject(self):
2367-
idx = date_range(start='2013-01-01', periods=4, freq='M', name='idx')
2368-
expected = Index([Timestamp('2013-01-31'), Timestamp('2013-02-28'),
2369-
Timestamp('2013-03-31'), Timestamp('2013-04-30')],
2370-
dtype=object, name='idx')
2371-
2372-
result = idx.asobject
2373-
self.assertTrue(result.equals(expected))
2374-
self.assertEqual(result.name, expected.name)
2375-
23762366
def test_insert(self):
23772367
idx = DatetimeIndex(['2000-01-04', '2000-01-01', '2000-01-02'], name='idx')
23782368

0 commit comments

Comments
 (0)