Skip to content

Commit de8f942

Browse files
jbrockmendelproost
authored andcommitted
DEPR: remove FrozenNDarray (pandas-dev#29840)
1 parent b451eee commit de8f942

File tree

6 files changed

+35
-166
lines changed

6 files changed

+35
-166
lines changed

doc/source/whatsnew/v1.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ or ``matplotlib.Axes.plot``. See :ref:`plotting.formatters` for more.
441441
- Removed previously deprecated :func:`pandas.tseries.plotting.tsplot` (:issue:`18627`)
442442
- Removed the previously deprecated ``reduce`` and ``broadcast`` arguments from :meth:`DataFrame.apply` (:issue:`18577`)
443443
- Removed the previously deprecated ``assert_raises_regex`` function in ``pandas.util.testing`` (:issue:`29174`)
444+
- Removed the previously deprecated ``FrozenNDArray`` class in ``pandas.core.indexes.frozen`` (:issue:`29335`)
444445
- Removed previously deprecated "nthreads" argument from :func:`read_feather`, use "use_threads" instead (:issue:`23053`)
445446
- Removed :meth:`Index.is_lexsorted_for_tuple` (:issue:`29305`)
446447
- Removed support for nexted renaming in :meth:`DataFrame.aggregate`, :meth:`Series.aggregate`, :meth:`DataFrameGroupBy.aggregate`, :meth:`SeriesGroupBy.aggregate`, :meth:`Rolling.aggregate` (:issue:`29608`)

pandas/compat/pickle_compat.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,8 @@ def __new__(cls) -> "DataFrame": # type: ignore
8989
_class_locations_map = {
9090
("pandas.core.sparse.array", "SparseArray"): ("pandas.core.arrays", "SparseArray"),
9191
# 15477
92-
#
93-
# TODO: When FrozenNDArray is removed, add
94-
# the following lines for compat:
95-
#
96-
# ('pandas.core.base', 'FrozenNDArray'):
97-
# ('numpy', 'ndarray'),
98-
# ('pandas.core.indexes.frozen', 'FrozenNDArray'):
99-
# ('numpy', 'ndarray'),
100-
#
101-
# Afterwards, remove the current entry
102-
# for `pandas.core.base.FrozenNDArray`.
103-
("pandas.core.base", "FrozenNDArray"): (
104-
"pandas.core.indexes.frozen",
105-
"FrozenNDArray",
106-
),
92+
("pandas.core.base", "FrozenNDArray"): ("numpy", "ndarray"),
93+
("pandas.core.indexes.frozen", "FrozenNDArray"): ("numpy", "ndarray"),
10794
("pandas.core.base", "FrozenList"): ("pandas.core.indexes.frozen", "FrozenList"),
10895
# 10890
10996
("pandas.core.series", "TimeSeries"): ("pandas.core.series", "Series"),

pandas/core/indexes/frozen.py

-80
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@
44
These are used for:
55
66
- .names (FrozenList)
7-
- .levels & .codes (FrozenNDArray)
87
98
"""
10-
import warnings
11-
12-
import numpy as np
13-
14-
from pandas.core.dtypes.cast import coerce_indexer_dtype
159

1610
from pandas.core.base import PandasObject
1711

@@ -111,77 +105,3 @@ def __repr__(self) -> str:
111105

112106
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
113107
pop = append = extend = remove = sort = insert = _disabled
114-
115-
116-
class FrozenNDArray(PandasObject, np.ndarray):
117-
118-
# no __array_finalize__ for now because no metadata
119-
def __new__(cls, data, dtype=None, copy=False):
120-
warnings.warn(
121-
"\nFrozenNDArray is deprecated and will be removed in a "
122-
"future version.\nPlease use `numpy.ndarray` instead.\n",
123-
FutureWarning,
124-
stacklevel=2,
125-
)
126-
127-
if copy is None:
128-
copy = not isinstance(data, FrozenNDArray)
129-
res = np.array(data, dtype=dtype, copy=copy).view(cls)
130-
return res
131-
132-
def _disabled(self, *args, **kwargs):
133-
"""This method will not function because object is immutable."""
134-
raise TypeError(
135-
"'{cls}' does not support mutable operations.".format(cls=type(self))
136-
)
137-
138-
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
139-
put = itemset = fill = _disabled
140-
141-
def _shallow_copy(self):
142-
return self.view()
143-
144-
def values(self):
145-
"""returns *copy* of underlying array"""
146-
arr = self.view(np.ndarray).copy()
147-
return arr
148-
149-
def __repr__(self) -> str:
150-
"""
151-
Return a string representation for this object.
152-
"""
153-
prepr = pprint_thing(self, escape_chars=("\t", "\r", "\n"), quote_strings=True)
154-
return f"{type(self).__name__}({prepr}, dtype='{self.dtype}')"
155-
156-
def searchsorted(self, value, side="left", sorter=None):
157-
"""
158-
Find indices to insert `value` so as to maintain order.
159-
160-
For full documentation, see `numpy.searchsorted`
161-
162-
See Also
163-
--------
164-
numpy.searchsorted : Equivalent function.
165-
"""
166-
167-
# We are much more performant if the searched
168-
# indexer is the same type as the array.
169-
#
170-
# This doesn't matter for int64, but DOES
171-
# matter for smaller int dtypes.
172-
#
173-
# xref: https://github.com/numpy/numpy/issues/5370
174-
try:
175-
value = self.dtype.type(value)
176-
except ValueError:
177-
pass
178-
179-
return super().searchsorted(value, side=side, sorter=sorter)
180-
181-
182-
def _ensure_frozen(array_like, categories, copy=False):
183-
array_like = coerce_indexer_dtype(array_like, categories)
184-
array_like = array_like.view(FrozenNDArray)
185-
if copy:
186-
array_like = array_like.copy()
187-
return array_like

pandas/core/indexes/multi.py

+30-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from pandas.errors import PerformanceWarning, UnsortedIndexError
1414
from pandas.util._decorators import Appender, cache_readonly
1515

16+
from pandas.core.dtypes.cast import coerce_indexer_dtype
1617
from pandas.core.dtypes.common import (
1718
ensure_int64,
1819
ensure_platform_int,
@@ -40,7 +41,7 @@
4041
_index_shared_docs,
4142
ensure_index,
4243
)
43-
from pandas.core.indexes.frozen import FrozenList, _ensure_frozen
44+
from pandas.core.indexes.frozen import FrozenList
4445
import pandas.core.missing as missing
4546
from pandas.core.sorting import (
4647
get_group_index,
@@ -821,17 +822,15 @@ def _set_codes(
821822

822823
if level is None:
823824
new_codes = FrozenList(
824-
_ensure_frozen(level_codes, lev, copy=copy)._shallow_copy()
825+
_coerce_indexer_frozen(level_codes, lev, copy=copy).view()
825826
for lev, level_codes in zip(self._levels, codes)
826827
)
827828
else:
828829
level_numbers = [self._get_level_number(lev) for lev in level]
829830
new_codes = list(self._codes)
830831
for lev_num, level_codes in zip(level_numbers, codes):
831832
lev = self.levels[lev_num]
832-
new_codes[lev_num] = _ensure_frozen(
833-
level_codes, lev, copy=copy
834-
)._shallow_copy()
833+
new_codes[lev_num] = _coerce_indexer_frozen(level_codes, lev, copy=copy)
835834
new_codes = FrozenList(new_codes)
836835

837836
if verify_integrity:
@@ -1095,7 +1094,8 @@ def _format_native_types(self, na_rep="nan", **kwargs):
10951094
if mask.any():
10961095
nan_index = len(level)
10971096
level = np.append(level, na_rep)
1098-
level_codes = level_codes.values()
1097+
assert not level_codes.flags.writeable # i.e. copy is needed
1098+
level_codes = level_codes.copy() # make writeable
10991099
level_codes[mask] = nan_index
11001100
new_levels.append(level)
11011101
new_codes.append(level_codes)
@@ -1998,7 +1998,7 @@ def _assert_take_fillable(
19981998
if mask.any():
19991999
masked = []
20002000
for new_label in taken:
2001-
label_values = new_label.values()
2001+
label_values = new_label
20022002
label_values[mask] = na_value
20032003
masked.append(np.asarray(label_values))
20042004
taken = masked
@@ -3431,3 +3431,26 @@ def maybe_droplevels(index, key):
34313431
pass
34323432

34333433
return index
3434+
3435+
3436+
def _coerce_indexer_frozen(array_like, categories, copy: bool = False) -> np.ndarray:
3437+
"""
3438+
Coerce the array_like indexer to the smallest integer dtype that can encode all
3439+
of the given categories.
3440+
3441+
Parameters
3442+
----------
3443+
array_like : array-like
3444+
categories : array-like
3445+
copy : bool
3446+
3447+
Returns
3448+
-------
3449+
np.ndarray
3450+
Non-writeable.
3451+
"""
3452+
array_like = coerce_indexer_dtype(array_like, categories)
3453+
if copy:
3454+
array_like = array_like.copy()
3455+
array_like.flags.writeable = False
3456+
return array_like

pandas/tests/indexes/multi/test_integrity.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def test_metadata_immutable(idx):
210210
# ditto for labels
211211
with pytest.raises(TypeError, match=mutable_regex):
212212
codes[0] = codes[0]
213-
with pytest.raises(TypeError, match=mutable_regex):
213+
with pytest.raises(ValueError, match="assignment destination is read-only"):
214214
codes[0][0] = codes[0][0]
215215
# and for names
216216
names = idx.names

pandas/tests/indexes/test_frozen.py

+1-63
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import warnings
2-
3-
import numpy as np
41
import pytest
52

6-
from pandas.core.indexes.frozen import FrozenList, FrozenNDArray
3+
from pandas.core.indexes.frozen import FrozenList
74
from pandas.tests.test_base import CheckImmutable, CheckStringMixin
8-
import pandas.util.testing as tm
95

106

117
class TestFrozenList(CheckImmutable, CheckStringMixin):
@@ -55,61 +51,3 @@ def test_tricky_container_to_bytes_raises(self):
5551
msg = "^'str' object cannot be interpreted as an integer$"
5652
with pytest.raises(TypeError, match=msg):
5753
bytes(self.unicode_container)
58-
59-
60-
class TestFrozenNDArray(CheckImmutable, CheckStringMixin):
61-
mutable_methods = ("put", "itemset", "fill")
62-
63-
def setup_method(self, _):
64-
self.lst = [3, 5, 7, -2]
65-
self.klass = FrozenNDArray
66-
67-
with warnings.catch_warnings(record=True):
68-
warnings.simplefilter("ignore", FutureWarning)
69-
70-
self.container = FrozenNDArray(self.lst)
71-
self.unicode_container = FrozenNDArray(["\u05d0", "\u05d1", "c"])
72-
73-
def test_constructor_warns(self):
74-
# see gh-9031
75-
with tm.assert_produces_warning(FutureWarning):
76-
FrozenNDArray([1, 2, 3])
77-
78-
def test_tricky_container_to_bytes(self):
79-
bytes(self.unicode_container)
80-
81-
def test_shallow_copying(self):
82-
original = self.container.copy()
83-
assert isinstance(self.container.view(), FrozenNDArray)
84-
assert not isinstance(self.container.view(np.ndarray), FrozenNDArray)
85-
assert self.container.view() is not self.container
86-
tm.assert_numpy_array_equal(self.container, original)
87-
88-
# Shallow copy should be the same too
89-
assert isinstance(self.container._shallow_copy(), FrozenNDArray)
90-
91-
# setting should not be allowed
92-
def testit(container):
93-
container[0] = 16
94-
95-
self.check_mutable_error(testit, self.container)
96-
97-
def test_values(self):
98-
original = self.container.view(np.ndarray).copy()
99-
n = original[0] + 15
100-
101-
vals = self.container.values()
102-
tm.assert_numpy_array_equal(original, vals)
103-
104-
assert original is not vals
105-
vals[0] = n
106-
107-
assert isinstance(self.container, FrozenNDArray)
108-
tm.assert_numpy_array_equal(self.container.values(), original)
109-
assert vals[0] == n
110-
111-
def test_searchsorted(self):
112-
expected = 2
113-
assert self.container.searchsorted(7) == expected
114-
115-
assert self.container.searchsorted(value=7) == expected

0 commit comments

Comments
 (0)