Skip to content

Commit

Permalink
DEPR: remove FrozenNDarray (#29840)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel authored and jreback committed Nov 29, 2019
1 parent a91194e commit 9dd1b50
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 166 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ or ``matplotlib.Axes.plot``. See :ref:`plotting.formatters` for more.
- Removed previously deprecated :func:`pandas.tseries.plotting.tsplot` (:issue:`18627`)
- Removed the previously deprecated ``reduce`` and ``broadcast`` arguments from :meth:`DataFrame.apply` (:issue:`18577`)
- Removed the previously deprecated ``assert_raises_regex`` function in ``pandas.util.testing`` (:issue:`29174`)
- Removed the previously deprecated ``FrozenNDArray`` class in ``pandas.core.indexes.frozen`` (:issue:`29335`)
- Removed previously deprecated "nthreads" argument from :func:`read_feather`, use "use_threads" instead (:issue:`23053`)
- Removed :meth:`Index.is_lexsorted_for_tuple` (:issue:`29305`)
- Removed support for nexted renaming in :meth:`DataFrame.aggregate`, :meth:`Series.aggregate`, :meth:`DataFrameGroupBy.aggregate`, :meth:`SeriesGroupBy.aggregate`, :meth:`Rolling.aggregate` (:issue:`29608`)
Expand Down
17 changes: 2 additions & 15 deletions pandas/compat/pickle_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,8 @@ def __new__(cls) -> "DataFrame": # type: ignore
_class_locations_map = {
("pandas.core.sparse.array", "SparseArray"): ("pandas.core.arrays", "SparseArray"),
# 15477
#
# TODO: When FrozenNDArray is removed, add
# the following lines for compat:
#
# ('pandas.core.base', 'FrozenNDArray'):
# ('numpy', 'ndarray'),
# ('pandas.core.indexes.frozen', 'FrozenNDArray'):
# ('numpy', 'ndarray'),
#
# Afterwards, remove the current entry
# for `pandas.core.base.FrozenNDArray`.
("pandas.core.base", "FrozenNDArray"): (
"pandas.core.indexes.frozen",
"FrozenNDArray",
),
("pandas.core.base", "FrozenNDArray"): ("numpy", "ndarray"),
("pandas.core.indexes.frozen", "FrozenNDArray"): ("numpy", "ndarray"),
("pandas.core.base", "FrozenList"): ("pandas.core.indexes.frozen", "FrozenList"),
# 10890
("pandas.core.series", "TimeSeries"): ("pandas.core.series", "Series"),
Expand Down
80 changes: 0 additions & 80 deletions pandas/core/indexes/frozen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@
These are used for:
- .names (FrozenList)
- .levels & .codes (FrozenNDArray)
"""
import warnings

import numpy as np

from pandas.core.dtypes.cast import coerce_indexer_dtype

from pandas.core.base import PandasObject

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

__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
pop = append = extend = remove = sort = insert = _disabled


class FrozenNDArray(PandasObject, np.ndarray):

# no __array_finalize__ for now because no metadata
def __new__(cls, data, dtype=None, copy=False):
warnings.warn(
"\nFrozenNDArray is deprecated and will be removed in a "
"future version.\nPlease use `numpy.ndarray` instead.\n",
FutureWarning,
stacklevel=2,
)

if copy is None:
copy = not isinstance(data, FrozenNDArray)
res = np.array(data, dtype=dtype, copy=copy).view(cls)
return res

def _disabled(self, *args, **kwargs):
"""This method will not function because object is immutable."""
raise TypeError(
"'{cls}' does not support mutable operations.".format(cls=type(self))
)

__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
put = itemset = fill = _disabled

def _shallow_copy(self):
return self.view()

def values(self):
"""returns *copy* of underlying array"""
arr = self.view(np.ndarray).copy()
return arr

def __repr__(self) -> str:
"""
Return a string representation for this object.
"""
prepr = pprint_thing(self, escape_chars=("\t", "\r", "\n"), quote_strings=True)
return f"{type(self).__name__}({prepr}, dtype='{self.dtype}')"

def searchsorted(self, value, side="left", sorter=None):
"""
Find indices to insert `value` so as to maintain order.
For full documentation, see `numpy.searchsorted`
See Also
--------
numpy.searchsorted : Equivalent function.
"""

# We are much more performant if the searched
# indexer is the same type as the array.
#
# This doesn't matter for int64, but DOES
# matter for smaller int dtypes.
#
# xref: https://github.com/numpy/numpy/issues/5370
try:
value = self.dtype.type(value)
except ValueError:
pass

return super().searchsorted(value, side=side, sorter=sorter)


def _ensure_frozen(array_like, categories, copy=False):
array_like = coerce_indexer_dtype(array_like, categories)
array_like = array_like.view(FrozenNDArray)
if copy:
array_like = array_like.copy()
return array_like
37 changes: 30 additions & 7 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pandas.errors import PerformanceWarning, UnsortedIndexError
from pandas.util._decorators import Appender, cache_readonly

from pandas.core.dtypes.cast import coerce_indexer_dtype
from pandas.core.dtypes.common import (
ensure_int64,
ensure_platform_int,
Expand Down Expand Up @@ -40,7 +41,7 @@
_index_shared_docs,
ensure_index,
)
from pandas.core.indexes.frozen import FrozenList, _ensure_frozen
from pandas.core.indexes.frozen import FrozenList
import pandas.core.missing as missing
from pandas.core.sorting import (
get_group_index,
Expand Down Expand Up @@ -821,17 +822,15 @@ def _set_codes(

if level is None:
new_codes = FrozenList(
_ensure_frozen(level_codes, lev, copy=copy)._shallow_copy()
_coerce_indexer_frozen(level_codes, lev, copy=copy).view()
for lev, level_codes in zip(self._levels, codes)
)
else:
level_numbers = [self._get_level_number(lev) for lev in level]
new_codes = list(self._codes)
for lev_num, level_codes in zip(level_numbers, codes):
lev = self.levels[lev_num]
new_codes[lev_num] = _ensure_frozen(
level_codes, lev, copy=copy
)._shallow_copy()
new_codes[lev_num] = _coerce_indexer_frozen(level_codes, lev, copy=copy)
new_codes = FrozenList(new_codes)

if verify_integrity:
Expand Down Expand Up @@ -1095,7 +1094,8 @@ def _format_native_types(self, na_rep="nan", **kwargs):
if mask.any():
nan_index = len(level)
level = np.append(level, na_rep)
level_codes = level_codes.values()
assert not level_codes.flags.writeable # i.e. copy is needed
level_codes = level_codes.copy() # make writeable
level_codes[mask] = nan_index
new_levels.append(level)
new_codes.append(level_codes)
Expand Down Expand Up @@ -1998,7 +1998,7 @@ def _assert_take_fillable(
if mask.any():
masked = []
for new_label in taken:
label_values = new_label.values()
label_values = new_label
label_values[mask] = na_value
masked.append(np.asarray(label_values))
taken = masked
Expand Down Expand Up @@ -3431,3 +3431,26 @@ def maybe_droplevels(index, key):
pass

return index


def _coerce_indexer_frozen(array_like, categories, copy: bool = False) -> np.ndarray:
"""
Coerce the array_like indexer to the smallest integer dtype that can encode all
of the given categories.
Parameters
----------
array_like : array-like
categories : array-like
copy : bool
Returns
-------
np.ndarray
Non-writeable.
"""
array_like = coerce_indexer_dtype(array_like, categories)
if copy:
array_like = array_like.copy()
array_like.flags.writeable = False
return array_like
2 changes: 1 addition & 1 deletion pandas/tests/indexes/multi/test_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def test_metadata_immutable(idx):
# ditto for labels
with pytest.raises(TypeError, match=mutable_regex):
codes[0] = codes[0]
with pytest.raises(TypeError, match=mutable_regex):
with pytest.raises(ValueError, match="assignment destination is read-only"):
codes[0][0] = codes[0][0]
# and for names
names = idx.names
Expand Down
64 changes: 1 addition & 63 deletions pandas/tests/indexes/test_frozen.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import warnings

import numpy as np
import pytest

from pandas.core.indexes.frozen import FrozenList, FrozenNDArray
from pandas.core.indexes.frozen import FrozenList
from pandas.tests.test_base import CheckImmutable, CheckStringMixin
import pandas.util.testing as tm


class TestFrozenList(CheckImmutable, CheckStringMixin):
Expand Down Expand Up @@ -55,61 +51,3 @@ def test_tricky_container_to_bytes_raises(self):
msg = "^'str' object cannot be interpreted as an integer$"
with pytest.raises(TypeError, match=msg):
bytes(self.unicode_container)


class TestFrozenNDArray(CheckImmutable, CheckStringMixin):
mutable_methods = ("put", "itemset", "fill")

def setup_method(self, _):
self.lst = [3, 5, 7, -2]
self.klass = FrozenNDArray

with warnings.catch_warnings(record=True):
warnings.simplefilter("ignore", FutureWarning)

self.container = FrozenNDArray(self.lst)
self.unicode_container = FrozenNDArray(["\u05d0", "\u05d1", "c"])

def test_constructor_warns(self):
# see gh-9031
with tm.assert_produces_warning(FutureWarning):
FrozenNDArray([1, 2, 3])

def test_tricky_container_to_bytes(self):
bytes(self.unicode_container)

def test_shallow_copying(self):
original = self.container.copy()
assert isinstance(self.container.view(), FrozenNDArray)
assert not isinstance(self.container.view(np.ndarray), FrozenNDArray)
assert self.container.view() is not self.container
tm.assert_numpy_array_equal(self.container, original)

# Shallow copy should be the same too
assert isinstance(self.container._shallow_copy(), FrozenNDArray)

# setting should not be allowed
def testit(container):
container[0] = 16

self.check_mutable_error(testit, self.container)

def test_values(self):
original = self.container.view(np.ndarray).copy()
n = original[0] + 15

vals = self.container.values()
tm.assert_numpy_array_equal(original, vals)

assert original is not vals
vals[0] = n

assert isinstance(self.container, FrozenNDArray)
tm.assert_numpy_array_equal(self.container.values(), original)
assert vals[0] == n

def test_searchsorted(self):
expected = 2
assert self.container.searchsorted(7) == expected

assert self.container.searchsorted(value=7) == expected

0 comments on commit 9dd1b50

Please sign in to comment.