Skip to content

Commit a19ab47

Browse files
jtratnerjreback
authored andcommitted
BUG/ENH: Make names, levels and labels properties.
* `names` is now a property *and* is set up as an immutable tuple. * `levels` are always (shallow) copied now and it is deprecated to set directly * `labels` are set up as a property now, moving all the processing of labels out of `__new__` + shallow-copied. * `levels` and `labels` are immutable. * Add names tests, motivating example from #3742, reflect tuple-ish output from names, and level names check to reindex test. * Add set_levels, set_labels, set_names and rename to index * Deprecate setting labels and levels directly Similar to other set_* methods...allows mutation if necessary but otherwise returns same object. Labels are now converted to `FrozenNDArray` and wrapped in a `FrozenList`. Should mostly resolve #3714 because you have to work to actually make assignments to an `Index`. BUG: Give MultiIndex its own astype method Fixes issue with set_value forgetting names.
1 parent dc3bef2 commit a19ab47

21 files changed

+493
-177
lines changed

Diff for: doc/source/indexing.rst

+83-61
Original file line numberDiff line numberDiff line change
@@ -868,66 +868,6 @@ convert to an integer index:
868868
df_new[(df_new['index'] >= 1.0) & (df_new['index'] < 2)]
869869
870870
871-
.. _indexing.class:
872-
873-
Index objects
874-
-------------
875-
876-
The pandas Index class and its subclasses can be viewed as implementing an
877-
*ordered set* in addition to providing the support infrastructure necessary for
878-
lookups, data alignment, and reindexing. The easiest way to create one directly
879-
is to pass a list or other sequence to ``Index``:
880-
881-
.. ipython:: python
882-
883-
index = Index(['e', 'd', 'a', 'b'])
884-
index
885-
'd' in index
886-
887-
You can also pass a ``name`` to be stored in the index:
888-
889-
890-
.. ipython:: python
891-
892-
index = Index(['e', 'd', 'a', 'b'], name='something')
893-
index.name
894-
895-
Starting with pandas 0.5, the name, if set, will be shown in the console
896-
display:
897-
898-
.. ipython:: python
899-
900-
index = Index(list(range(5)), name='rows')
901-
columns = Index(['A', 'B', 'C'], name='cols')
902-
df = DataFrame(np.random.randn(5, 3), index=index, columns=columns)
903-
df
904-
df['A']
905-
906-
907-
Set operations on Index objects
908-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
909-
910-
.. _indexing.set_ops:
911-
912-
The three main operations are ``union (|)``, ``intersection (&)``, and ``diff
913-
(-)``. These can be directly called as instance methods or used via overloaded
914-
operators:
915-
916-
.. ipython:: python
917-
918-
a = Index(['c', 'b', 'a'])
919-
b = Index(['c', 'e', 'd'])
920-
a.union(b)
921-
a | b
922-
a & b
923-
a - b
924-
925-
``isin`` method of Index objects
926-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
927-
928-
One additional operation is the ``isin`` method that works analogously to the
929-
``Series.isin`` method found :ref:`here <indexing.boolean>`.
930-
931871
.. _indexing.hierarchical:
932872

933873
Hierarchical indexing (MultiIndex)
@@ -1189,7 +1129,7 @@ are named.
11891129

11901130
.. ipython:: python
11911131
1192-
s.index.names = ['L1', 'L2']
1132+
s.index.set_names(['L1', 'L2'], inplace=True)
11931133
s.sortlevel(level='L1')
11941134
s.sortlevel(level='L2')
11951135
@@ -1276,6 +1216,88 @@ not check (or care) whether the levels themselves are sorted. Fortunately, the
12761216
constructors ``from_tuples`` and ``from_arrays`` ensure that this is true, but
12771217
if you compute the levels and labels yourself, please be careful.
12781218

1219+
.. _indexing.class:
1220+
1221+
Index objects
1222+
-------------
1223+
1224+
The pandas Index class and its subclasses can be viewed as implementing an
1225+
*ordered set* in addition to providing the support infrastructure necessary for
1226+
lookups, data alignment, and reindexing. The easiest way to create one directly
1227+
is to pass a list or other sequence to ``Index``:
1228+
1229+
.. ipython:: python
1230+
1231+
index = Index(['e', 'd', 'a', 'b'])
1232+
index
1233+
'd' in index
1234+
1235+
You can also pass a ``name`` to be stored in the index:
1236+
1237+
1238+
.. ipython:: python
1239+
1240+
index = Index(['e', 'd', 'a', 'b'], name='something')
1241+
index.name
1242+
1243+
Starting with pandas 0.5, the name, if set, will be shown in the console
1244+
display:
1245+
1246+
.. ipython:: python
1247+
1248+
index = Index(list(range(5)), name='rows')
1249+
columns = Index(['A', 'B', 'C'], name='cols')
1250+
df = DataFrame(np.random.randn(5, 3), index=index, columns=columns)
1251+
df
1252+
df['A']
1253+
1254+
1255+
Set operations on Index objects
1256+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1257+
1258+
.. _indexing.set_ops:
1259+
1260+
The three main operations are ``union (|)``, ``intersection (&)``, and ``diff
1261+
(-)``. These can be directly called as instance methods or used via overloaded
1262+
operators:
1263+
1264+
.. ipython:: python
1265+
1266+
a = Index(['c', 'b', 'a'])
1267+
b = Index(['c', 'e', 'd'])
1268+
a.union(b)
1269+
a | b
1270+
a & b
1271+
a - b
1272+
1273+
``isin`` method of Index objects
1274+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1275+
1276+
One additional operation is the ``isin`` method that works analogously to the
1277+
``Series.isin`` method found :ref:`here <indexing.boolean>`.
1278+
1279+
Setting index metadata (``name(s)``, ``levels``, ``labels``)
1280+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1281+
1282+
.. _indexing.set_metadata:
1283+
1284+
Indexes are "mostly immutable", but it is possible to set and change their
1285+
metadata, like the index ``name`` (or, for ``MultiIndex``, ``levels`` and
1286+
``labels``).
1287+
1288+
You can use the ``rename``, ``set_names``, ``set_levels``, and ``set_labels``
1289+
to set these attributes directly. They default to returning a copy; however,
1290+
you can specify ``inplace=True`` to have the data change inplace.
1291+
1292+
.. ipython:: python
1293+
1294+
ind = Index([1, 2, 3])
1295+
ind.rename("apple")
1296+
ind
1297+
ind.set_names(["apple"], inplace=True)
1298+
ind.name = "bob"
1299+
ind
1300+
12791301
Adding an index to an existing DataFrame
12801302
----------------------------------------
12811303

Diff for: doc/source/release.rst

+23
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ pandas 0.13
4747
- Added a more informative error message when plot arguments contain
4848
overlapping color and style arguments (:issue:`4402`)
4949
- Significant table writing performance improvements in ``HDFStore``
50+
- Add ``rename`` and ``set_names`` methods to ``Index`` as well as
51+
``set_names``, ``set_levels``, ``set_labels`` to ``MultiIndex``.
52+
(:issue:`4039`)
5053

5154
**API Changes**
5255

@@ -66,6 +69,7 @@ pandas 0.13
6669
an alias of iteritems used to get around ``2to3``'s changes).
6770
(:issue:`4384`, :issue:`4375`, :issue:`4372`)
6871
- ``Series.get`` with negative indexers now returns the same as ``[]`` (:issue:`4390`)
72+
6973
- ``HDFStore``
7074

7175
- added an ``is_open`` property to indicate if the underlying file handle is_open;
@@ -83,6 +87,21 @@ pandas 0.13
8387
be raised if you try to use ``mode='w'`` with an OPEN file handle (:issue:`4367`)
8488
- allow a passed locations array or mask as a ``where`` condition (:issue:`4467`)
8589

90+
- ``Index`` and ``MultiIndex`` changes (:issue:`4039`):
91+
92+
- Setting ``levels`` and ``labels`` directly on ``MultiIndex`` is now
93+
deprecated. Instead, you can use the ``set_levels()`` and
94+
``set_labels()`` methods.
95+
- ``levels``, ``labels`` and ``names`` properties no longer return lists,
96+
but instead return containers that do not allow setting of items
97+
('mostly immutable')
98+
- ``levels``, ``labels`` and ``names`` are validated upon setting and are
99+
either copied or shallow-copied.
100+
- ``__deepcopy__`` now returns a shallow copy (currently: a view) of the
101+
data - allowing metadata changes.
102+
- ``MultiIndex.astype()`` now only allows ``np.object_``-like dtypes and
103+
now returns a ``MultiIndex`` rather than an ``Index``. (:issue:`4039`)
104+
86105
**Experimental Features**
87106

88107
**Bug Fixes**
@@ -136,6 +155,10 @@ pandas 0.13
136155
- frozenset objects now raise in the ``Series`` constructor (:issue:`4482`,
137156
:issue:`4480`)
138157
- Fixed issue with sorting a duplicate multi-index that has multiple dtypes (:issue:`4516`)
158+
- Fixed bug in ``DataFrame.set_values`` which was causing name attributes to
159+
be lost when expanding the index. (:issue:`3742`, :issue:`4039`)
160+
- Fixed issue where individual ``names``, ``levels`` and ``labels`` could be
161+
set on ``MultiIndex`` without validation (:issue:`3714`, :issue:`4039`)
139162

140163
pandas 0.12
141164
===========

Diff for: doc/source/v0.13.0.txt

+18
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ API changes
7272
import os
7373
os.remove(path)
7474

75+
- Changes to how ``Index`` and ``MultiIndex`` handle metadata (``levels``,
76+
``labels``, and ``names``) (:issue:`4039`):
77+
78+
..code-block ::
79+
80+
# previously, you would have set levels or labels directly
81+
index.levels = [[1, 2, 3, 4], [1, 2, 4, 4]]
82+
83+
# now, you use the set_levels or set_labels methods
84+
index = index.set_levels([[1, 2, 3, 4], [1, 2, 4, 4]])
85+
86+
# similarly, for names, you can rename the object
87+
# but setting names is not deprecated.
88+
index = index.set_names(["bob", "cranberry"])
89+
90+
# and all methods take an inplace kwarg
91+
index.set_names(["bob", "cranberry"], inplace=True)
92+
7593
Enhancements
7694
~~~~~~~~~~~~
7795

Diff for: pandas/core/frame.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ def to_records(self, index=True, convert_datetime64=True):
11501150
arrays = ix_vals+ [self[c].values for c in self.columns]
11511151

11521152
count = 0
1153-
index_names = self.index.names
1153+
index_names = list(self.index.names)
11541154
if isinstance(self.index, MultiIndex):
11551155
for i, n in enumerate(index_names):
11561156
if n is None:

Diff for: pandas/core/generic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def drop(self, labels, axis=0, level=None):
404404
new_axis = axis.drop(labels)
405405
dropped = self.reindex(**{axis_name: new_axis})
406406
try:
407-
dropped.axes[axis_].names = axis.names
407+
dropped.axes[axis_].set_names(axis.names, inplace=True)
408408
except AttributeError:
409409
pass
410410
return dropped

0 commit comments

Comments
 (0)