Skip to content

Commit 70a8c98

Browse files
committed
BUG: fix metadata copying to derived Index instances with view(...) inside NumPy
1 parent 67121af commit 70a8c98

File tree

4 files changed

+92
-29
lines changed

4 files changed

+92
-29
lines changed

RELEASE.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ pandas 0.9.0
161161
- Fix unicode sheet name failure in to_excel (#1828)
162162
- Override DatetimeIndex.min/max to return Timestamp objects (#1895)
163163
- Fix column name formatting issue in length-truncated column (#1906)
164+
- Fix broken handling of copying Index metadata to new instances created by
165+
view(...) calls inside the NumPy infrastructure
164166

165167
pandas 0.8.1
166168
============

pandas/core/index.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class Index(np.ndarray):
5353
dtype : NumPy dtype (default: object)
5454
copy : bool
5555
Make a copy of input ndarray
56+
name : object
57+
Name to be stored in the index
5658
5759
Note
5860
----
@@ -120,10 +122,14 @@ def __new__(cls, data, dtype=None, copy=False, name=None):
120122
return subarr
121123

122124
def __array_finalize__(self, obj):
125+
if not isinstance(obj, type(self)):
126+
# Only relevant if array being created from an Index instance
127+
return
128+
123129
self.name = getattr(obj, 'name', None)
124130

125131
def _shallow_copy(self):
126-
return self.view(type(self))
132+
return self.view()
127133

128134
def __repr__(self):
129135
try:
@@ -1141,19 +1147,6 @@ def drop(self, labels):
11411147
raise ValueError('labels %s not contained in axis' % labels[mask])
11421148
return self.delete(indexer)
11431149

1144-
def copy(self, order='C'):
1145-
"""
1146-
Overridden ndarray.copy to copy over attributes
1147-
1148-
Returns
1149-
-------
1150-
cp : Index
1151-
Returns view on same base ndarray
1152-
"""
1153-
cp = self.view(np.ndarray).view(type(self))
1154-
cp.__dict__.update(self.__dict__)
1155-
return cp
1156-
11571150

11581151
class Int64Index(Index):
11591152

@@ -1246,6 +1239,11 @@ class MultiIndex(Index):
12461239
The unique labels for each level
12471240
labels : list or tuple of arrays
12481241
Integers for each level designating which label at each location
1242+
sortorder : optional int
1243+
Level of sortedness (must be lexicographically sorted by that
1244+
level)
1245+
names : optional sequence of objects
1246+
Names for each of the index levels.
12491247
"""
12501248
# shadow property
12511249
names = None
@@ -1288,21 +1286,19 @@ def __new__(cls, levels=None, labels=None, sortorder=None, names=None):
12881286

12891287
return subarr
12901288

1291-
def copy(self, order='C'):
1289+
def __array_finalize__(self, obj):
1290+
"""
1291+
Update custom MultiIndex attributes when a new array is created by numpy,
1292+
e.g. when calling ndarray.view()
12921293
"""
1293-
Overridden ndarray.copy to copy over attributes
1294+
if not isinstance(obj, type(self)):
1295+
# Only relevant if this array is being created from an Index instance.
1296+
return
12941297

1295-
Returns
1296-
-------
1297-
cp : Index
1298-
Returns view on same base ndarray
1299-
"""
1300-
cp = self.view(np.ndarray).view(type(self))
1301-
cp.levels = list(self.levels)
1302-
cp.labels = list(self.labels)
1303-
cp.names = list(self.names)
1304-
cp.sortorder = self.sortorder
1305-
return cp
1298+
self.levels = list(getattr(obj, 'levels', []))
1299+
self.labels = list(getattr(obj, 'labels', []))
1300+
self.names = list(getattr(obj, 'names', []))
1301+
self.sortorder = getattr(obj, 'sortorder', None)
13061302

13071303
def _array_values(self):
13081304
# hack for various methods

pandas/tests/test_index.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,21 @@ def test_constructor(self):
8080
# arr = np.array(5.)
8181
# self.assertRaises(Exception, arr.view, Index)
8282

83+
8384
def test_constructor_corner(self):
8485
# corner case
8586
self.assertRaises(Exception, Index, 0)
8687

88+
def test_copy(self):
89+
i = Index([], name='Foo')
90+
i_copy = i.copy()
91+
self.assert_(i_copy.name == 'Foo')
92+
93+
def test_view(self):
94+
i = Index([], name='Foo')
95+
i_view = i.view()
96+
self.assert_(i_view.name == 'Foo')
97+
8798
def test_astype(self):
8899
casted = self.intIndex.astype('i8')
89100

@@ -520,6 +531,16 @@ def test_constructor_corner(self):
520531
arr = np.array([1, '2', 3, '4'], dtype=object)
521532
self.assertRaises(TypeError, Int64Index, arr)
522533

534+
def test_copy(self):
535+
i = Int64Index([], name='Foo')
536+
i_copy = i.copy()
537+
self.assert_(i_copy.name == 'Foo')
538+
539+
def test_view(self):
540+
i = Int64Index([], name='Foo')
541+
i_view = i.view()
542+
self.assert_(i_view.name == 'Foo')
543+
523544
def test_coerce_list(self):
524545
# coerce things
525546
arr = Index([1, 2, 3, 4])
@@ -849,6 +870,50 @@ def test_constructor_single_level(self):
849870
def test_constructor_no_levels(self):
850871
self.assertRaises(Exception, MultiIndex, levels=[], labels=[])
851872

873+
def test_copy(self):
874+
i_copy = self.index.copy()
875+
876+
# Equal...but not the same object
877+
self.assert_(i_copy.levels == self.index.levels)
878+
self.assert_(i_copy.levels is not self.index.levels)
879+
880+
self.assert_(i_copy.labels == self.index.labels)
881+
self.assert_(i_copy.labels is not self.index.labels)
882+
883+
self.assert_(i_copy.names == self.index.names)
884+
self.assert_(i_copy.names is not self.index.names)
885+
886+
self.assert_(i_copy.sortorder == self.index.sortorder)
887+
888+
def test_shallow_copy(self):
889+
i_copy = self.index._shallow_copy()
890+
891+
# Equal...but not the same object
892+
self.assert_(i_copy.levels == self.index.levels)
893+
self.assert_(i_copy.levels is not self.index.levels)
894+
895+
self.assert_(i_copy.labels == self.index.labels)
896+
self.assert_(i_copy.labels is not self.index.labels)
897+
898+
self.assert_(i_copy.names == self.index.names)
899+
self.assert_(i_copy.names is not self.index.names)
900+
901+
self.assert_(i_copy.sortorder == self.index.sortorder)
902+
903+
def test_view(self):
904+
i_view = self.index.view()
905+
906+
# Equal...but not the same object
907+
self.assert_(i_view.levels == self.index.levels)
908+
self.assert_(i_view.levels is not self.index.levels)
909+
910+
self.assert_(i_view.labels == self.index.labels)
911+
self.assert_(i_view.labels is not self.index.labels)
912+
913+
self.assert_(i_view.names == self.index.names)
914+
self.assert_(i_view.names is not self.index.names)
915+
self.assert_(i_view.sortorder == self.index.sortorder)
916+
852917
def test_duplicate_names(self):
853918
self.index.names = ['foo', 'foo']
854919
self.assertRaises(Exception, self.index._get_level_number, 'foo')

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ def srcpath(name=None, suffix='.pyx', subdir='src'):
420420
ext.sources[0] = root + suffix
421421

422422

423-
# if _have_setuptools:
424-
# setuptools_kwargs["test_suite"] = "nose.collector"
423+
if _have_setuptools:
424+
setuptools_kwargs["test_suite"] = "nose.collector"
425425

426426
write_version_py()
427427
setup(name=DISTNAME,

0 commit comments

Comments
 (0)