Skip to content

Commit 44b9490

Browse files
jbrockmendelNico Cernek
authored and
Nico Cernek
committed
CLN: remove Block._try_coerce_arg (pandas-dev#29139)
1 parent 1da6cf0 commit 44b9490

File tree

3 files changed

+60
-131
lines changed

3 files changed

+60
-131
lines changed

pandas/_libs/index.pyx

+12-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cnp.import_array()
1818
cimport pandas._libs.util as util
1919

2020
from pandas._libs.tslibs.conversion cimport maybe_datetimelike_to_i8
21+
from pandas._libs.tslibs.nattype cimport c_NaT as NaT
2122

2223
from pandas._libs.hashtable cimport HashTable
2324

@@ -547,30 +548,31 @@ cpdef convert_scalar(ndarray arr, object value):
547548
if util.is_array(value):
548549
pass
549550
elif isinstance(value, (datetime, np.datetime64, date)):
550-
return Timestamp(value).value
551+
return Timestamp(value).to_datetime64()
551552
elif util.is_timedelta64_object(value):
552553
# exclude np.timedelta64("NaT") from value != value below
553554
pass
554555
elif value is None or value != value:
555-
return NPY_NAT
556-
elif isinstance(value, str):
557-
return Timestamp(value).value
558-
raise ValueError("cannot set a Timestamp with a non-timestamp")
556+
return np.datetime64("NaT", "ns")
557+
raise ValueError("cannot set a Timestamp with a non-timestamp {typ}"
558+
.format(typ=type(value).__name__))
559559

560560
elif arr.descr.type_num == NPY_TIMEDELTA:
561561
if util.is_array(value):
562562
pass
563563
elif isinstance(value, timedelta) or util.is_timedelta64_object(value):
564-
return Timedelta(value).value
564+
value = Timedelta(value)
565+
if value is NaT:
566+
return np.timedelta64("NaT", "ns")
567+
return value.to_timedelta64()
565568
elif util.is_datetime64_object(value):
566569
# exclude np.datetime64("NaT") which would otherwise be picked up
567570
# by the `value != value check below
568571
pass
569572
elif value is None or value != value:
570-
return NPY_NAT
571-
elif isinstance(value, str):
572-
return Timedelta(value).value
573-
raise ValueError("cannot set a Timedelta with a non-timedelta")
573+
return np.timedelta64("NaT", "ns")
574+
raise ValueError("cannot set a Timedelta with a non-timedelta {typ}"
575+
.format(typ=type(value).__name__))
574576

575577
if (issubclass(arr.dtype.type, (np.integer, np.floating, np.complex)) and
576578
not issubclass(arr.dtype.type, np.bool_)):

pandas/core/internals/blocks.py

+32-113
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import date, datetime, timedelta
1+
from datetime import datetime, timedelta
22
import functools
33
import inspect
44
import re
@@ -7,7 +7,8 @@
77

88
import numpy as np
99

10-
from pandas._libs import NaT, Timestamp, lib, tslib, writers
10+
from pandas._libs import NaT, lib, tslib, writers
11+
from pandas._libs.index import convert_scalar
1112
import pandas._libs.internals as libinternals
1213
from pandas._libs.tslibs import Timedelta, conversion
1314
from pandas._libs.tslibs.timezones import tz_compare
@@ -54,7 +55,6 @@
5455
from pandas.core.dtypes.dtypes import CategoricalDtype, ExtensionDtype
5556
from pandas.core.dtypes.generic import (
5657
ABCDataFrame,
57-
ABCDatetimeIndex,
5858
ABCExtensionArray,
5959
ABCPandasArray,
6060
ABCSeries,
@@ -64,7 +64,6 @@
6464
array_equivalent,
6565
is_valid_nat_for_dtype,
6666
isna,
67-
notna,
6867
)
6968

7069
import pandas.core.algorithms as algos
@@ -663,28 +662,6 @@ def _can_hold_element(self, element: Any) -> bool:
663662
return issubclass(tipo.type, dtype)
664663
return isinstance(element, dtype)
665664

666-
def _try_coerce_args(self, other):
667-
""" provide coercion to our input arguments """
668-
669-
if np.any(notna(other)) and not self._can_hold_element(other):
670-
# coercion issues
671-
# let higher levels handle
672-
raise TypeError(
673-
"cannot convert {} to an {}".format(
674-
type(other).__name__,
675-
type(self).__name__.lower().replace("Block", ""),
676-
)
677-
)
678-
if np.any(isna(other)) and not self._can_hold_na:
679-
raise TypeError(
680-
"cannot convert {} to an {}".format(
681-
type(other).__name__,
682-
type(self).__name__.lower().replace("Block", ""),
683-
)
684-
)
685-
686-
return other
687-
688665
def to_native_types(self, slicer=None, na_rep="nan", quoting=None, **kwargs):
689666
""" convert to our native types format, slicing if desired """
690667
values = self.get_values()
@@ -766,7 +743,11 @@ def replace(
766743
)
767744

768745
values = self.values
769-
to_replace = self._try_coerce_args(to_replace)
746+
if lib.is_scalar(to_replace) and isinstance(values, np.ndarray):
747+
# The only non-DatetimeLike class that also has a non-trivial
748+
# try_coerce_args is ObjectBlock, but that overrides replace,
749+
# so does not get here.
750+
to_replace = convert_scalar(values, to_replace)
770751

771752
mask = missing.mask_missing(values, to_replace)
772753
if filter is not None:
@@ -813,7 +794,8 @@ def _replace_single(self, *args, **kwargs):
813794
return self if kwargs["inplace"] else self.copy()
814795

815796
def setitem(self, indexer, value):
816-
"""Set the value inplace, returning a a maybe different typed block.
797+
"""
798+
Set the value inplace, returning a a maybe different typed block.
817799
818800
Parameters
819801
----------
@@ -841,7 +823,10 @@ def setitem(self, indexer, value):
841823
# coerce if block dtype can store value
842824
values = self.values
843825
if self._can_hold_element(value):
844-
value = self._try_coerce_args(value)
826+
# We only get here for non-Extension Blocks, so _try_coerce_args
827+
# is only relevant for DatetimeBlock and TimedeltaBlock
828+
if lib.is_scalar(value):
829+
value = convert_scalar(values, value)
845830

846831
else:
847832
# current dtype cannot store value, coerce to common dtype
@@ -862,7 +847,12 @@ def setitem(self, indexer, value):
862847
return b.setitem(indexer, value)
863848

864849
# value must be storeable at this moment
865-
arr_value = np.array(value)
850+
if is_extension_array_dtype(getattr(value, "dtype", None)):
851+
# We need to be careful not to allow through strings that
852+
# can be parsed to EADtypes
853+
arr_value = value
854+
else:
855+
arr_value = np.array(value)
866856

867857
# cast the values to a type that can hold nan (if necessary)
868858
if not self._can_hold_element(value):
@@ -938,7 +928,10 @@ def putmask(self, mask, new, align=True, inplace=False, axis=0, transpose=False)
938928
new = self.fill_value
939929

940930
if self._can_hold_element(new):
941-
new = self._try_coerce_args(new)
931+
# We only get here for non-Extension Blocks, so _try_coerce_args
932+
# is only relevant for DatetimeBlock and TimedeltaBlock
933+
if lib.is_scalar(new):
934+
new = convert_scalar(new_values, new)
942935

943936
if transpose:
944937
new_values = new_values.T
@@ -1176,7 +1169,10 @@ def _interpolate_with_fill(
11761169
return [self.copy()]
11771170

11781171
values = self.values if inplace else self.values.copy()
1179-
fill_value = self._try_coerce_args(fill_value)
1172+
1173+
# We only get here for non-ExtensionBlock
1174+
fill_value = convert_scalar(self.values, fill_value)
1175+
11801176
values = missing.interpolate_2d(
11811177
values,
11821178
method=method,
@@ -1375,7 +1371,10 @@ def func(cond, values, other):
13751371
and np.isnan(other)
13761372
):
13771373
# np.where will cast integer array to floats in this case
1378-
other = self._try_coerce_args(other)
1374+
if not self._can_hold_element(other):
1375+
raise TypeError
1376+
if lib.is_scalar(other) and isinstance(values, np.ndarray):
1377+
other = convert_scalar(values, other)
13791378

13801379
fastres = expressions.where(cond, values, other)
13811380
return fastres
@@ -1641,7 +1640,6 @@ def putmask(self, mask, new, align=True, inplace=False, axis=0, transpose=False)
16411640
# use block's copy logic.
16421641
# .values may be an Index which does shallow copy by default
16431642
new_values = self.values if inplace else self.copy().values
1644-
new = self._try_coerce_args(new)
16451643

16461644
if isinstance(new, np.ndarray) and len(new) == len(mask):
16471645
new = new[mask]
@@ -2194,38 +2192,6 @@ def _can_hold_element(self, element: Any) -> bool:
21942192

21952193
return is_valid_nat_for_dtype(element, self.dtype)
21962194

2197-
def _try_coerce_args(self, other):
2198-
"""
2199-
Coerce other to dtype 'i8'. NaN and NaT convert to
2200-
the smallest i8, and will correctly round-trip to NaT if converted
2201-
back in _try_coerce_result. values is always ndarray-like, other
2202-
may not be
2203-
2204-
Parameters
2205-
----------
2206-
other : ndarray-like or scalar
2207-
2208-
Returns
2209-
-------
2210-
base-type other
2211-
"""
2212-
if is_valid_nat_for_dtype(other, self.dtype):
2213-
other = np.datetime64("NaT", "ns")
2214-
elif isinstance(other, (datetime, np.datetime64, date)):
2215-
other = Timestamp(other)
2216-
if other.tz is not None:
2217-
raise TypeError("cannot coerce a Timestamp with a tz on a naive Block")
2218-
other = other.asm8
2219-
elif hasattr(other, "dtype") and is_datetime64_dtype(other):
2220-
# TODO: can we get here with non-nano?
2221-
pass
2222-
else:
2223-
# coercion issues
2224-
# let higher levels handle
2225-
raise TypeError(other)
2226-
2227-
return other
2228-
22292195
def to_native_types(
22302196
self, slicer=None, na_rep=None, date_format=None, quoting=None, **kwargs
22312197
):
@@ -2364,10 +2330,6 @@ def _slice(self, slicer):
23642330
return self.values[loc]
23652331
return self.values[slicer]
23662332

2367-
def _try_coerce_args(self, other):
2368-
# DatetimeArray handles this for us
2369-
return other
2370-
23712333
def diff(self, n: int, axis: int = 0) -> List["Block"]:
23722334
"""
23732335
1st discrete difference.
@@ -2505,34 +2467,6 @@ def fillna(self, value, **kwargs):
25052467
value = Timedelta(value, unit="s")
25062468
return super().fillna(value, **kwargs)
25072469

2508-
def _try_coerce_args(self, other):
2509-
"""
2510-
Coerce values and other to datetime64[ns], with null values
2511-
converted to datetime64("NaT", "ns").
2512-
2513-
Parameters
2514-
----------
2515-
other : ndarray-like or scalar
2516-
2517-
Returns
2518-
-------
2519-
base-type other
2520-
"""
2521-
2522-
if is_valid_nat_for_dtype(other, self.dtype):
2523-
other = np.timedelta64("NaT", "ns")
2524-
elif isinstance(other, (timedelta, np.timedelta64)):
2525-
other = Timedelta(other).to_timedelta64()
2526-
elif hasattr(other, "dtype") and is_timedelta64_dtype(other):
2527-
# TODO: can we get here with non-nano dtype?
2528-
pass
2529-
else:
2530-
# coercion issues
2531-
# let higher levels handle
2532-
raise TypeError(other)
2533-
2534-
return other
2535-
25362470
def should_store(self, value):
25372471
return issubclass(
25382472
value.dtype.type, np.timedelta64
@@ -2668,21 +2602,6 @@ def _maybe_downcast(self, blocks: List["Block"], downcast=None) -> List["Block"]
26682602
def _can_hold_element(self, element: Any) -> bool:
26692603
return True
26702604

2671-
def _try_coerce_args(self, other):
2672-
""" provide coercion to our input arguments """
2673-
2674-
if isinstance(other, ABCDatetimeIndex):
2675-
# May get a DatetimeIndex here. Unbox it.
2676-
other = other.array
2677-
2678-
if isinstance(other, DatetimeArray):
2679-
# hit in pandas/tests/indexing/test_coercion.py
2680-
# ::TestWhereCoercion::test_where_series_datetime64[datetime64tz]
2681-
# when falling back to ObjectBlock.where
2682-
other = other.astype(object)
2683-
2684-
return other
2685-
26862605
def should_store(self, value):
26872606
return not (
26882607
issubclass(

pandas/tests/internals/test_internals.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,27 @@ def test_make_block_same_class(self):
327327

328328

329329
class TestDatetimeBlock:
330-
def test_try_coerce_arg(self):
330+
def test_can_hold_element(self):
331331
block = create_block("datetime", [0])
332332

333+
# We will check that block._can_hold_element iff arr.__setitem__ works
334+
arr = pd.array(block.values.ravel())
335+
333336
# coerce None
334-
none_coerced = block._try_coerce_args(None)
335-
assert pd.Timestamp(none_coerced) is pd.NaT
337+
assert block._can_hold_element(None)
338+
arr[0] = None
339+
assert arr[0] is pd.NaT
336340

337-
# coerce different types of date bojects
338-
vals = (np.datetime64("2010-10-10"), datetime(2010, 10, 10), date(2010, 10, 10))
341+
# coerce different types of datetime objects
342+
vals = [np.datetime64("2010-10-10"), datetime(2010, 10, 10)]
339343
for val in vals:
340-
coerced = block._try_coerce_args(val)
341-
assert np.datetime64 == type(coerced)
342-
assert pd.Timestamp("2010-10-10") == pd.Timestamp(coerced)
344+
assert block._can_hold_element(val)
345+
arr[0] = val
346+
347+
val = date(2010, 10, 10)
348+
assert not block._can_hold_element(val)
349+
with pytest.raises(TypeError):
350+
arr[0] = val
343351

344352

345353
class TestBlockManager:

0 commit comments

Comments
 (0)