Skip to content

Commit 084ceae

Browse files
gfyoungjreback
authored andcommitted
API, DEPR: Raise and Deprecate Reshape for Pandas Objects
Author: gfyoung <[email protected]> Closes pandas-dev#13012 from gfyoung/categorical-reshape-validate and squashes the following commits: 3ad161d [gfyoung] API: Prevent invalid arguments to Categorical.reshape
1 parent a711b42 commit 084ceae

File tree

9 files changed

+151
-43
lines changed

9 files changed

+151
-43
lines changed

doc/source/whatsnew/v0.19.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ API changes
256256
~~~~~~~~~~~
257257

258258

259+
- ``Index.reshape`` will raise a ``NotImplementedError`` exception when called (:issue: `12882`)
259260
- Non-convertible dates in an excel date column will be returned without conversion and the column will be ``object`` dtype, rather than raising an exception (:issue:`10001`)
260261
- ``eval``'s upcasting rules for ``float32`` types have been updated to be more consistent with NumPy's rules. New behavior will not upcast to ``float64`` if you multiply a pandas ``float32`` object by a scalar float64. (:issue:`12388`)
261262
- An ``UnsupportedFunctionCall`` error is now raised if NumPy ufuncs like ``np.mean`` are called on groupby or resample objects (:issue:`12811`)
@@ -449,6 +450,8 @@ Furthermore:
449450

450451
Deprecations
451452
^^^^^^^^^^^^
453+
- ``Categorical.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`)
454+
- ``Series.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`)
452455

453456
- ``compact_ints`` and ``use_unsigned`` have been deprecated in ``pd.read_csv()`` and will be removed in a future version (:issue:`13320`)
454457
- ``buffer_lines`` has been deprecated in ``pd.read_csv()`` and will be removed in a future version (:issue:`13360`)

pandas/core/categorical.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,28 @@ def itemsize(self):
383383

384384
def reshape(self, new_shape, *args, **kwargs):
385385
"""
386-
An ndarray-compatible method that returns
387-
`self` because categorical instances cannot
388-
actually be reshaped.
386+
DEPRECATED: calling this method will raise an error in a
387+
future release.
388+
389+
An ndarray-compatible method that returns `self` because
390+
`Categorical` instances cannot actually be reshaped.
391+
392+
Parameters
393+
----------
394+
new_shape : int or tuple of ints
395+
A 1-D array of integers that correspond to the new
396+
shape of the `Categorical`. For more information on
397+
the parameter, please refer to `np.reshape`.
389398
"""
399+
warn("reshape is deprecated and will raise "
400+
"in a subsequent release", FutureWarning, stacklevel=2)
401+
390402
nv.validate_reshape(args, kwargs)
403+
404+
# while the 'new_shape' parameter has no effect,
405+
# we should still enforce valid shape parameters
406+
np.reshape(self.codes, new_shape)
407+
391408
return self
392409

393410
@property

pandas/core/internals.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -1839,7 +1839,7 @@ def convert(self, *args, **kwargs):
18391839
try:
18401840
values = values.reshape(shape)
18411841
values = _block_shape(values, ndim=self.ndim)
1842-
except AttributeError:
1842+
except (AttributeError, NotImplementedError):
18431843
pass
18441844
newb = make_block(values, ndim=self.ndim, placement=[rl])
18451845
blocks.append(newb)
@@ -3616,7 +3616,7 @@ def value_getitem(placement):
36163616
return value
36173617
else:
36183618
if value.ndim == self.ndim - 1:
3619-
value = value.reshape((1,) + value.shape)
3619+
value = _safe_reshape(value, (1,) + value.shape)
36203620

36213621
def value_getitem(placement):
36223622
return value
@@ -4686,6 +4686,28 @@ def rrenamer(x):
46864686
_transform_index(right, rrenamer))
46874687

46884688

4689+
def _safe_reshape(arr, new_shape):
4690+
"""
4691+
If possible, reshape `arr` to have shape `new_shape`,
4692+
with a couple of exceptions (see gh-13012):
4693+
4694+
1) If `arr` is a Categorical or Index, `arr` will be
4695+
returned as is.
4696+
2) If `arr` is a Series, the `_values` attribute will
4697+
be reshaped and returned.
4698+
4699+
Parameters
4700+
----------
4701+
arr : array-like, object to be reshaped
4702+
new_shape : int or tuple of ints, the new shape
4703+
"""
4704+
if isinstance(arr, ABCSeries):
4705+
arr = arr._values
4706+
if not isinstance(arr, Categorical):
4707+
arr = arr.reshape(new_shape)
4708+
return arr
4709+
4710+
46894711
def _transform_index(index, func):
46904712
"""
46914713
Apply function to all values found in index.

pandas/core/series.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -843,14 +843,22 @@ def repeat(self, reps, *args, **kwargs):
843843

844844
def reshape(self, *args, **kwargs):
845845
"""
846-
Return the values attribute of `self` with shape `args`.
847-
However, if the specified shape matches exactly the current
848-
shape, `self` is returned for compatibility reasons.
846+
DEPRECATED: calling this method will raise an error in a
847+
future release. Please call ``.values.reshape(...)`` instead.
848+
849+
return an ndarray with the values shape
850+
if the specified shape matches exactly the current shape, then
851+
return self (for compat)
849852
850853
See also
851854
--------
852855
numpy.ndarray.reshape
853856
"""
857+
warnings.warn("reshape is deprecated and will raise "
858+
"in a subsequent release. Please use "
859+
".values.reshape(...) instead", FutureWarning,
860+
stacklevel=2)
861+
854862
if len(args) == 1 and hasattr(args[0], '__iter__'):
855863
shape = args[0]
856864
else:

pandas/indexes/base.py

+10
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,16 @@ def rename(self, name, inplace=False):
957957
"""
958958
return self.set_names([name], inplace=inplace)
959959

960+
def reshape(self, *args, **kwargs):
961+
"""
962+
NOT IMPLEMENTED: do not call this method, as reshaping is not
963+
supported for Index objects and will raise an error.
964+
965+
Reshape an Index.
966+
"""
967+
raise NotImplementedError("reshaping is not supported "
968+
"for Index objects")
969+
960970
@property
961971
def _has_complex_internals(self):
962972
# to disable groupby tricks in MultiIndex

pandas/io/packers.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
from pandas.core.generic import NDFrame
6262
from pandas.core.common import PerformanceWarning
6363
from pandas.io.common import get_filepath_or_buffer
64-
from pandas.core.internals import BlockManager, make_block
64+
from pandas.core.internals import BlockManager, make_block, _safe_reshape
6565
import pandas.core.internals as internals
6666

6767
from pandas.msgpack import Unpacker as _Unpacker, Packer as _Packer, ExtType
@@ -622,8 +622,9 @@ def decode(obj):
622622
axes = obj[u'axes']
623623

624624
def create_block(b):
625-
values = unconvert(b[u'values'], dtype_for(b[u'dtype']),
626-
b[u'compress']).reshape(b[u'shape'])
625+
values = _safe_reshape(unconvert(
626+
b[u'values'], dtype_for(b[u'dtype']),
627+
b[u'compress']), b[u'shape'])
627628

628629
# locs handles duplicate column names, and should be used instead
629630
# of items; see GH 9618

pandas/tests/indexes/test_base.py

+6
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,12 @@ def test_take_fill_value(self):
14131413
with tm.assertRaises(IndexError):
14141414
idx.take(np.array([1, -5]))
14151415

1416+
def test_reshape_raise(self):
1417+
msg = "reshaping is not supported"
1418+
idx = pd.Index([0, 1, 2])
1419+
tm.assertRaisesRegexp(NotImplementedError, msg,
1420+
idx.reshape, idx.shape)
1421+
14161422
def test_reindex_preserves_name_if_target_is_list_or_ndarray(self):
14171423
# GH6552
14181424
idx = pd.Index([0, 1, 2])

pandas/tests/series/test_analytics.py

+41-27
Original file line numberDiff line numberDiff line change
@@ -1554,49 +1554,63 @@ def test_shift_categorical(self):
15541554
assert_index_equal(s.values.categories, sp1.values.categories)
15551555
assert_index_equal(s.values.categories, sn2.values.categories)
15561556

1557-
def test_reshape_non_2d(self):
1558-
# GH 4554
1559-
x = Series(np.random.random(201), name='x')
1560-
self.assertTrue(x.reshape(x.shape, ) is x)
1557+
def test_reshape_deprecate(self):
1558+
x = Series(np.random.random(10), name='x')
1559+
tm.assert_produces_warning(FutureWarning, x.reshape, x.shape)
15611560

1562-
# GH 2719
1563-
a = Series([1, 2, 3, 4])
1564-
result = a.reshape(2, 2)
1565-
expected = a.values.reshape(2, 2)
1566-
tm.assert_numpy_array_equal(result, expected)
1567-
self.assertIsInstance(result, type(expected))
1561+
def test_reshape_non_2d(self):
1562+
# see gh-4554
1563+
with tm.assert_produces_warning(FutureWarning):
1564+
x = Series(np.random.random(201), name='x')
1565+
self.assertTrue(x.reshape(x.shape, ) is x)
1566+
1567+
# see gh-2719
1568+
with tm.assert_produces_warning(FutureWarning):
1569+
a = Series([1, 2, 3, 4])
1570+
result = a.reshape(2, 2)
1571+
expected = a.values.reshape(2, 2)
1572+
tm.assert_numpy_array_equal(result, expected)
1573+
self.assertIsInstance(result, type(expected))
15681574

15691575
def test_reshape_2d_return_array(self):
15701576
x = Series(np.random.random(201), name='x')
1571-
result = x.reshape((-1, 1))
1572-
self.assertNotIsInstance(result, Series)
15731577

1574-
result2 = np.reshape(x, (-1, 1))
1575-
self.assertNotIsInstance(result2, Series)
1578+
with tm.assert_produces_warning(FutureWarning):
1579+
result = x.reshape((-1, 1))
1580+
self.assertNotIsInstance(result, Series)
15761581

1577-
result = x[:, None]
1578-
expected = x.reshape((-1, 1))
1579-
assert_almost_equal(result, expected)
1582+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1583+
result2 = np.reshape(x, (-1, 1))
1584+
self.assertNotIsInstance(result2, Series)
1585+
1586+
with tm.assert_produces_warning(FutureWarning):
1587+
result = x[:, None]
1588+
expected = x.reshape((-1, 1))
1589+
assert_almost_equal(result, expected)
15801590

15811591
def test_reshape_bad_kwarg(self):
15821592
a = Series([1, 2, 3, 4])
15831593

1584-
msg = "'foo' is an invalid keyword argument for this function"
1585-
tm.assertRaisesRegexp(TypeError, msg, a.reshape, (2, 2), foo=2)
1594+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1595+
msg = "'foo' is an invalid keyword argument for this function"
1596+
tm.assertRaisesRegexp(TypeError, msg, a.reshape, (2, 2), foo=2)
15861597

1587-
msg = "reshape\(\) got an unexpected keyword argument 'foo'"
1588-
tm.assertRaisesRegexp(TypeError, msg, a.reshape, a.shape, foo=2)
1598+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1599+
msg = "reshape\(\) got an unexpected keyword argument 'foo'"
1600+
tm.assertRaisesRegexp(TypeError, msg, a.reshape, a.shape, foo=2)
15891601

15901602
def test_numpy_reshape(self):
15911603
a = Series([1, 2, 3, 4])
15921604

1593-
result = np.reshape(a, (2, 2))
1594-
expected = a.values.reshape(2, 2)
1595-
tm.assert_numpy_array_equal(result, expected)
1596-
self.assertIsInstance(result, type(expected))
1605+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1606+
result = np.reshape(a, (2, 2))
1607+
expected = a.values.reshape(2, 2)
1608+
tm.assert_numpy_array_equal(result, expected)
1609+
self.assertIsInstance(result, type(expected))
15971610

1598-
result = np.reshape(a, a.shape)
1599-
tm.assert_series_equal(result, a)
1611+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1612+
result = np.reshape(a, a.shape)
1613+
tm.assert_series_equal(result, a)
16001614

16011615
def test_unstack(self):
16021616
from numpy import nan

pandas/tests/test_categorical.py

+32-5
Original file line numberDiff line numberDiff line change
@@ -4058,13 +4058,40 @@ def test_numpy_repeat(self):
40584058
msg = "the 'axis' parameter is not supported"
40594059
tm.assertRaisesRegexp(ValueError, msg, np.repeat, cat, 2, axis=1)
40604060

4061+
def test_reshape(self):
4062+
cat = pd.Categorical([], categories=["a", "b"])
4063+
tm.assert_produces_warning(FutureWarning, cat.reshape, 0)
4064+
4065+
with tm.assert_produces_warning(FutureWarning):
4066+
cat = pd.Categorical([], categories=["a", "b"])
4067+
self.assert_categorical_equal(cat.reshape(0), cat)
4068+
4069+
with tm.assert_produces_warning(FutureWarning):
4070+
cat = pd.Categorical([], categories=["a", "b"])
4071+
self.assert_categorical_equal(cat.reshape((5, -1)), cat)
4072+
4073+
with tm.assert_produces_warning(FutureWarning):
4074+
cat = pd.Categorical(["a", "b"], categories=["a", "b"])
4075+
self.assert_categorical_equal(cat.reshape(cat.shape), cat)
4076+
4077+
with tm.assert_produces_warning(FutureWarning):
4078+
cat = pd.Categorical(["a", "b"], categories=["a", "b"])
4079+
self.assert_categorical_equal(cat.reshape(cat.size), cat)
4080+
4081+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
4082+
msg = "can only specify one unknown dimension"
4083+
cat = pd.Categorical(["a", "b"], categories=["a", "b"])
4084+
tm.assertRaisesRegexp(ValueError, msg, cat.reshape, (-2, -1))
4085+
40614086
def test_numpy_reshape(self):
4062-
cat = pd.Categorical(["a", "b"], categories=["a", "b"])
4063-
self.assert_categorical_equal(np.reshape(cat, cat.shape), cat)
4087+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
4088+
cat = pd.Categorical(["a", "b"], categories=["a", "b"])
4089+
self.assert_categorical_equal(np.reshape(cat, cat.shape), cat)
40644090

4065-
msg = "the 'order' parameter is not supported"
4066-
tm.assertRaisesRegexp(ValueError, msg, np.reshape,
4067-
cat, cat.shape, order='F')
4091+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
4092+
msg = "the 'order' parameter is not supported"
4093+
tm.assertRaisesRegexp(ValueError, msg, np.reshape,
4094+
cat, cat.shape, order='F')
40684095

40694096
def test_na_actions(self):
40704097

0 commit comments

Comments
 (0)