Skip to content

EA: preliminary EA reshape ops #27153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions pandas/core/arrays/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,20 @@ def copy(self) -> ABCExtensionArray:
"""
raise AbstractMethodError(self)

def view(self, dtype=None) -> ABCExtensionArray:
"""
Return a view on the array.

Returns
-------
ExtensionArray

Notes
-----
This must return a *new* object, not self.
"""
raise AbstractMethodError(self)

# ------------------------------------------------------------------------
# Printing
# ------------------------------------------------------------------------
Expand Down Expand Up @@ -897,6 +911,23 @@ def _formatting_values(self) -> np.ndarray:
# Reshaping
# ------------------------------------------------------------------------

@property
def T(self) -> ABCExtensionArray:
"""
Return a transposed view on self. For 1-D arrays this is a no-op.
"""
if self.ndim != 1:
raise NotImplementedError
return self

def ravel(self, order=None) -> ABCExtensionArray:
"""
Return a flattened view on self. For 1-D arrays this is a no-op.
"""
if self.ndim != 1:
raise NotImplementedError
return self

@classmethod
def _concat_same_type(
cls,
Expand Down
20 changes: 6 additions & 14 deletions pandas/core/arrays/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -1675,19 +1675,7 @@ def _values_for_rank(self):
)
return values

def ravel(self, order='C'):
"""
Return a flattened (numpy) array.

For internal compatibility with numpy arrays.

Returns
-------
numpy.array
"""
return np.array(self)

def view(self):
def view(self, dtype=None):
"""
Return a view of myself.

Expand All @@ -1698,7 +1686,11 @@ def view(self):
view : Categorical
Returns `self`!
"""
return self
if dtype is not None:
return NotImplementedError(dtype)
return self._constructor(values=self._codes,
dtype=self.dtype,
fastpath=True)

def to_dense(self):
"""
Expand Down
4 changes: 3 additions & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,11 @@ def view(self, dtype=None):

Returns
-------
ndarray
ndarray or ExtensionArray
With the specified `dtype`.
"""
if dtype is None:
return type(self)(self._data, dtype=self.dtype, freq=self.freq)
return self._data.view(dtype=dtype)

# ------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/arrays/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ def copy(self):
mask = mask.copy()
return type(self)(data, mask, copy=False)

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return type(self)(self._data, self._mask, copy=False)

def __setitem__(self, key, value):
_is_scalar = is_scalar(value)
if _is_scalar:
Expand Down
7 changes: 7 additions & 0 deletions pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,13 @@ def copy(self):
# TODO: Could skip verify_integrity here.
return type(self).from_arrays(left, right, closed=closed)

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return type(self)._simple_new(self._left, self._right, self.closed,
copy=False, dtype=None,
verify_integrity=False)

def isna(self):
return isna(self.left)

Expand Down
5 changes: 5 additions & 0 deletions pandas/core/arrays/numpy_.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ def unique(self):

return type(self)(unique(self._ndarray))

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError(dtype)
return type(self)(self._ndarray)

# ------------------------------------------------------------------------
# Reductions

Expand Down
5 changes: 5 additions & 0 deletions pandas/core/arrays/sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,11 @@ def copy(self):
values = self.sp_values.copy()
return self._simple_new(values, self.sp_index, self.dtype)

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return self._simple_new(self.sp_values, self.sp_index, self.dtype)

@classmethod
def _concat_same_type(cls, to_concat):
fill_values = [x.fill_value for x in to_concat]
Expand Down
5 changes: 5 additions & 0 deletions pandas/tests/extension/arrow/bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ def __init__(self, values):
self._data = values
self._dtype = ArrowBoolDtype()

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return type(self)(self._data)

def __repr__(self):
return "ArrowBoolArray({})".format(repr(self._data))

Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/arrow/test_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def test_copy(self, data):
# __setitem__ does not work, so we only have a smoke-test
data.copy()

def test_view(self, data):
# __setitem__ does not work, so we only have a smoke-test
data.view()


class TestConstructors(BaseArrowTests, base.BaseConstructorsTests):
def test_from_dtype(self, data):
Expand Down
11 changes: 11 additions & 0 deletions pandas/tests/extension/base/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,14 @@ def test_copy(self, data):

data[1] = data[0]
assert result[1] != result[0]

def test_view(self, data):
# view with no dtype should return a shallow copy, *not* the same
# object
assert data[1] != data[0]

result = data.view()
assert result is not data

result[1] = result[0]
assert data[1] == data[0]
12 changes: 12 additions & 0 deletions pandas/tests/extension/base/reshaping.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,15 @@ def test_unstack(self, data, index, obj):
result = result.astype(object)

self.assert_frame_equal(result, expected)

def test_ravel(self, data):
# Test is invalid if data.ndim > 1
assert data.ndim == 1
result = data.ravel()
assert result is data

def test_transpose(self, data):
# Test is invalid if data.ndim > 1
assert data.ndim == 1
result = data.T
assert result is data
5 changes: 5 additions & 0 deletions pandas/tests/extension/decimal/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ def take(self, indexer, allow_fill=False, fill_value=None):
def copy(self):
return type(self)(self._data.copy())

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return type(self)(self._data)

def astype(self, dtype, copy=True):
if isinstance(dtype, type(self.dtype)):
return type(self)(self._data, context=dtype.context)
Expand Down
5 changes: 5 additions & 0 deletions pandas/tests/extension/json/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def take(self, indexer, allow_fill=False, fill_value=None):
def copy(self):
return type(self)(self.data[:])

def view(self, dtype=None):
if dtype is not None:
raise NotImplementedError
return type(self)(self.data)

def astype(self, dtype, copy=True):
# NumPy has issues when all the dicts are the same length.
# np.array([UserDict(...), UserDict(...)]) fails,
Expand Down
13 changes: 11 additions & 2 deletions pandas/tests/extension/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ def dtype():
return IntervalDtype()


@pytest.fixture
def data():
"""Length-100 PeriodArray for semantics test."""
return IntervalArray(make_data())


@pytest.fixture(name="data")
def data_fixture():
"""Length-100 PeriodArray for semantics test."""
return IntervalArray(make_data())


@pytest.fixture
def data_missing():
"""Length 2 array with [NA, Valid]"""
Expand Down Expand Up @@ -95,7 +100,11 @@ class TestGrouping(BaseInterval, base.BaseGroupbyTests):


class TestInterface(BaseInterval, base.BaseInterfaceTests):
pass

def test_view(self, data):
# __setitem__ incorrectly makes a copy (GH#27147), so we only
# have a smoke-test
data.view()


class TestReduce(base.BaseNoReduceTests):
Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/test_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def test_copy(self, data):
# __setitem__ does not work, so we only have a smoke-test
data.copy()

def test_view(self, data):
# __setitem__ does not work, so we only have a smoke-test
data.view()


class TestConstructors(BaseSparseTests, base.BaseConstructorsTests):
pass
Expand Down