Skip to content

Commit ea76415

Browse files
jbrockmendelnoatamir
authored andcommitted
API: Series([pydate, pydatetime]) (pandas-dev#49446)
1 parent acff952 commit ea76415

File tree

5 files changed

+50
-54
lines changed

5 files changed

+50
-54
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ Other API changes
144144
- The ``other`` argument in :meth:`DataFrame.mask` and :meth:`Series.mask` now defaults to ``no_default`` instead of ``np.nan`` consistent with :meth:`DataFrame.where` and :meth:`Series.where`. Entries will be filled with the corresponding NULL value (``np.nan`` for numpy dtypes, ``pd.NA`` for extension dtypes). (:issue:`49111`)
145145
- When creating a :class:`Series` with a object-dtype :class:`Index` of datetime objects, pandas no longer silently converts the index to a :class:`DatetimeIndex` (:issue:`39307`, :issue:`23598`)
146146
- :meth:`Series.unique` with dtype "timedelta64[ns]" or "datetime64[ns]" now returns :class:`TimedeltaArray` or :class:`DatetimeArray` instead of ``numpy.ndarray`` (:issue:`49176`)
147+
- Passing a sequence containing ``datetime`` objects and ``date`` objects to :class:`Series` constructor will return with ``object`` dtype instead of ``datetime64[ns]`` dtype, consistent with :class:`Index` behavior (:issue:`49341`)
147148
- Passing strings that cannot be parsed as datetimes to :class:`Series` or :class:`DataFrame` with ``dtype="datetime64[ns]"`` will raise instead of silently ignoring the keyword and returning ``object`` dtype (:issue:`24435`)
148149
-
149150

pandas/_libs/lib.pyx

+13-5
Original file line numberDiff line numberDiff line change
@@ -1640,8 +1640,11 @@ def infer_datetimelike_array(arr: ndarray[object]) -> tuple[str, bool]:
16401640
return "interval"
16411641
return "mixed"
16421642

1643-
if seen_date and not (seen_datetime or seen_timedelta):
1644-
return "date"
1643+
if seen_date:
1644+
if not seen_datetime and not seen_timedelta:
1645+
return "date"
1646+
return "mixed"
1647+
16451648
elif seen_datetime and not seen_timedelta:
16461649
return "datetime"
16471650
elif seen_timedelta and not seen_datetime:
@@ -2570,10 +2573,15 @@ def maybe_convert_objects(ndarray[object] objects,
25702573
if seen.datetimetz_:
25712574
if is_datetime_with_singletz_array(objects):
25722575
from pandas import DatetimeIndex
2573-
dti = DatetimeIndex(objects)
25742576

2575-
# unbox to DatetimeArray
2576-
return dti._data
2577+
try:
2578+
dti = DatetimeIndex(objects)
2579+
except OutOfBoundsDatetime:
2580+
# e.g. test_to_datetime_cache_coerce_50_lines_outofbounds
2581+
pass
2582+
else:
2583+
# unbox to DatetimeArray
2584+
return dti._data
25772585
seen.object_ = True
25782586

25792587
elif seen.datetime_:

pandas/core/dtypes/cast.py

+13-47
Original file line numberDiff line numberDiff line change
@@ -1224,71 +1224,37 @@ def maybe_infer_to_datetimelike(
12241224

12251225
v = np.array(value, copy=False)
12261226

1227-
shape = v.shape
12281227
if v.ndim != 1:
12291228
v = v.ravel()
12301229

12311230
if not len(v):
12321231
return value
12331232

1234-
def try_datetime(v: np.ndarray) -> np.ndarray | DatetimeArray:
1235-
# Coerce to datetime64, datetime64tz, or in corner cases
1236-
# object[datetimes]
1237-
from pandas.core.arrays.datetimes import sequence_to_datetimes
1238-
1239-
try:
1240-
dta = sequence_to_datetimes(v)
1241-
except (ValueError, OutOfBoundsDatetime):
1242-
# ValueError for e.g. mixed tzs
1243-
# GH#19761 we may have mixed timezones, in which cast 'dta' is
1244-
# an ndarray[object]. Only 1 test
1245-
# relies on this behavior, see GH#40111
1246-
return v.reshape(shape)
1247-
else:
1248-
return dta.reshape(shape)
1249-
1250-
def try_timedelta(v: np.ndarray) -> np.ndarray:
1251-
# safe coerce to timedelta64
1252-
1253-
# will try first with a string & object conversion
1254-
try:
1255-
# bc we know v.dtype == object, this is equivalent to
1256-
# `np.asarray(to_timedelta(v))`, but using a lower-level API that
1257-
# does not require a circular import.
1258-
td_values = array_to_timedelta64(v).view("m8[ns]")
1259-
except OutOfBoundsTimedelta:
1260-
return v.reshape(shape)
1261-
else:
1262-
return td_values.reshape(shape)
1263-
1264-
# TODO: this is _almost_ equivalent to lib.maybe_convert_objects,
1265-
# the main differences are described in GH#49340 and GH#49341
1266-
# and maybe_convert_objects doesn't catch OutOfBoundsDatetime
12671233
inferred_type = lib.infer_datetimelike_array(ensure_object(v))
12681234

1269-
if inferred_type in ["period", "interval"]:
1235+
if inferred_type in ["period", "interval", "timedelta", "datetime"]:
12701236
# Incompatible return value type (got "Union[ExtensionArray, ndarray]",
12711237
# expected "Union[ndarray, DatetimeArray, TimedeltaArray, PeriodArray,
12721238
# IntervalArray]")
12731239
return lib.maybe_convert_objects( # type: ignore[return-value]
1274-
v, convert_period=True, convert_interval=True
1240+
v,
1241+
convert_period=True,
1242+
convert_interval=True,
1243+
convert_timedelta=True,
1244+
convert_datetime=True,
1245+
dtype_if_all_nat=np.dtype("M8[ns]"),
12751246
)
12761247

1277-
if inferred_type == "datetime":
1278-
# Incompatible types in assignment (expression has type
1279-
# "Union[ndarray[Any, Any], DatetimeArray]", variable has type
1280-
# "ndarray[Any, Any]")
1281-
value = try_datetime(v) # type: ignore[assignment]
1282-
elif inferred_type == "timedelta":
1283-
value = try_timedelta(v)
12841248
elif inferred_type == "nat":
12851249
# if all NaT, return as datetime
12861250
# only reached if we have at least 1 NaT and the rest (NaT or None or np.nan)
1251+
# This is slightly different from what we'd get with maybe_convert_objects,
1252+
# which only converts of all-NaT
1253+
from pandas.core.arrays.datetimes import sequence_to_datetimes
12871254

1288-
# Incompatible types in assignment (expression has type
1289-
# "Union[ndarray[Any, Any], DatetimeArray]", variable has type
1290-
# "ndarray[Any, Any]")
1291-
value = try_datetime(v) # type: ignore[assignment]
1255+
# Incompatible types in assignment (expression has type "DatetimeArray",
1256+
# variable has type "ndarray[Any, Any]")
1257+
value = sequence_to_datetimes(v) # type: ignore[assignment]
12921258
assert value.dtype == "M8[ns]"
12931259

12941260
return value

pandas/tests/dtypes/test_inference.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1340,19 +1340,26 @@ def test_infer_dtype_period_with_na(self, na_value):
13401340
Timestamp("20170612", tz="US/Eastern"),
13411341
Timestamp("20170311", tz="US/Eastern"),
13421342
],
1343-
[date(2017, 6, 12), Timestamp("20170311", tz="US/Eastern")],
13441343
[np.datetime64("2017-06-12"), np.datetime64("2017-03-11")],
13451344
[np.datetime64("2017-06-12"), datetime(2017, 3, 11, 1, 15)],
13461345
],
13471346
)
13481347
def test_infer_datetimelike_array_datetime(self, data):
13491348
assert lib.infer_datetimelike_array(data) == "datetime"
13501349

1350+
def test_infer_datetimelike_array_date_mixed(self):
1351+
# GH49341 pre-2.0 we these were inferred as "datetime" and "timedelta",
1352+
# respectively
1353+
data = [date(2017, 6, 12), Timestamp("20170311", tz="US/Eastern")]
1354+
assert lib.infer_datetimelike_array(data) == "mixed"
1355+
1356+
data = ([timedelta(2017, 6, 12), date(2017, 3, 11)],)
1357+
assert lib.infer_datetimelike_array(data) == "mixed"
1358+
13511359
@pytest.mark.parametrize(
13521360
"data",
13531361
[
13541362
[timedelta(2017, 6, 12), timedelta(2017, 3, 11)],
1355-
[timedelta(2017, 6, 12), date(2017, 3, 11)],
13561363
[np.timedelta64(2017, "D"), np.timedelta64(6, "s")],
13571364
[np.timedelta64(2017, "D"), timedelta(2017, 3, 11)],
13581365
],

pandas/tests/series/test_constructors.py

+14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@
5252

5353

5454
class TestSeriesConstructors:
55+
def test_infer_with_date_and_datetime(self):
56+
# GH#49341 pre-2.0 we inferred datetime-and-date to datetime64, which
57+
# was inconsistent with Index behavior
58+
ts = Timestamp(2016, 1, 1)
59+
vals = [ts.to_pydatetime(), ts.date()]
60+
61+
ser = Series(vals)
62+
expected = Series(vals, dtype=object)
63+
tm.assert_series_equal(ser, expected)
64+
65+
idx = Index(vals)
66+
expected = Index(vals, dtype=object)
67+
tm.assert_index_equal(idx, expected)
68+
5569
def test_unparseable_strings_with_dt64_dtype(self):
5670
# pre-2.0 these would be silently ignored and come back with object dtype
5771
vals = ["aa"]

0 commit comments

Comments
 (0)