123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- """
- datetimelike delegation
- """
- from __future__ import annotations
- from typing import (
- TYPE_CHECKING,
- cast,
- )
- import numpy as np
- from pandas.core.dtypes.common import (
- is_categorical_dtype,
- is_datetime64_dtype,
- is_datetime64tz_dtype,
- is_integer_dtype,
- is_list_like,
- is_period_dtype,
- is_timedelta64_dtype,
- )
- from pandas.core.dtypes.generic import ABCSeries
- from pandas.core.accessor import (
- PandasDelegate,
- delegate_names,
- )
- from pandas.core.arrays import (
- DatetimeArray,
- PeriodArray,
- TimedeltaArray,
- )
- from pandas.core.arrays.arrow.array import ArrowExtensionArray
- from pandas.core.arrays.arrow.dtype import ArrowDtype
- from pandas.core.base import (
- NoNewAttributesMixin,
- PandasObject,
- )
- from pandas.core.indexes.datetimes import DatetimeIndex
- from pandas.core.indexes.timedeltas import TimedeltaIndex
- if TYPE_CHECKING:
- from pandas import (
- DataFrame,
- Series,
- )
- class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin):
- _hidden_attrs = PandasObject._hidden_attrs | {
- "orig",
- "name",
- }
- def __init__(self, data: Series, orig) -> None:
- if not isinstance(data, ABCSeries):
- raise TypeError(
- f"cannot convert an object of type {type(data)} to a datetimelike index"
- )
- self._parent = data
- self.orig = orig
- self.name = getattr(data, "name", None)
- self._freeze()
- def _get_values(self):
- data = self._parent
- if is_datetime64_dtype(data.dtype):
- return DatetimeIndex(data, copy=False, name=self.name)
- elif is_datetime64tz_dtype(data.dtype):
- return DatetimeIndex(data, copy=False, name=self.name)
- elif is_timedelta64_dtype(data.dtype):
- return TimedeltaIndex(data, copy=False, name=self.name)
- elif is_period_dtype(data.dtype):
- return PeriodArray(data, copy=False)
- raise TypeError(
- f"cannot convert an object of type {type(data)} to a datetimelike index"
- )
- def _delegate_property_get(self, name):
- from pandas import Series
- values = self._get_values()
- result = getattr(values, name)
- # maybe need to upcast (ints)
- if isinstance(result, np.ndarray):
- if is_integer_dtype(result):
- result = result.astype("int64")
- elif not is_list_like(result):
- return result
- result = np.asarray(result)
- if self.orig is not None:
- index = self.orig.index
- else:
- index = self._parent.index
- # return the result as a Series
- result = Series(result, index=index, name=self.name).__finalize__(self._parent)
- # setting this object will show a SettingWithCopyWarning/Error
- result._is_copy = (
- "modifications to a property of a datetimelike "
- "object are not supported and are discarded. "
- "Change values on the original."
- )
- return result
- def _delegate_property_set(self, name, value, *args, **kwargs):
- raise ValueError(
- "modifications to a property of a datetimelike object are not supported. "
- "Change values on the original."
- )
- def _delegate_method(self, name, *args, **kwargs):
- from pandas import Series
- values = self._get_values()
- method = getattr(values, name)
- result = method(*args, **kwargs)
- if not is_list_like(result):
- return result
- result = Series(result, index=self._parent.index, name=self.name).__finalize__(
- self._parent
- )
- # setting this object will show a SettingWithCopyWarning/Error
- result._is_copy = (
- "modifications to a method of a datetimelike "
- "object are not supported and are discarded. "
- "Change values on the original."
- )
- return result
- @delegate_names(
- delegate=ArrowExtensionArray,
- accessors=DatetimeArray._datetimelike_ops,
- typ="property",
- accessor_mapping=lambda x: f"_dt_{x}",
- raise_on_missing=False,
- )
- @delegate_names(
- delegate=ArrowExtensionArray,
- accessors=DatetimeArray._datetimelike_methods,
- typ="method",
- accessor_mapping=lambda x: f"_dt_{x}",
- raise_on_missing=False,
- )
- class ArrowTemporalProperties(PandasDelegate, PandasObject, NoNewAttributesMixin):
- def __init__(self, data: Series, orig) -> None:
- if not isinstance(data, ABCSeries):
- raise TypeError(
- f"cannot convert an object of type {type(data)} to a datetimelike index"
- )
- self._parent = data
- self._orig = orig
- self._freeze()
- def _delegate_property_get(self, name: str): # type: ignore[override]
- if not hasattr(self._parent.array, f"_dt_{name}"):
- raise NotImplementedError(
- f"dt.{name} is not supported for {self._parent.dtype}"
- )
- result = getattr(self._parent.array, f"_dt_{name}")
- if not is_list_like(result):
- return result
- if self._orig is not None:
- index = self._orig.index
- else:
- index = self._parent.index
- # return the result as a Series, which is by definition a copy
- result = type(self._parent)(
- result, index=index, name=self._parent.name
- ).__finalize__(self._parent)
- return result
- def _delegate_method(self, name: str, *args, **kwargs):
- if not hasattr(self._parent.array, f"_dt_{name}"):
- raise NotImplementedError(
- f"dt.{name} is not supported for {self._parent.dtype}"
- )
- result = getattr(self._parent.array, f"_dt_{name}")(*args, **kwargs)
- if self._orig is not None:
- index = self._orig.index
- else:
- index = self._parent.index
- # return the result as a Series, which is by definition a copy
- result = type(self._parent)(
- result, index=index, name=self._parent.name
- ).__finalize__(self._parent)
- return result
- def to_pydatetime(self):
- return cast(ArrowExtensionArray, self._parent.array)._dt_to_pydatetime()
- def isocalendar(self):
- from pandas import DataFrame
- result = (
- cast(ArrowExtensionArray, self._parent.array)
- ._dt_isocalendar()
- ._data.combine_chunks()
- )
- iso_calendar_df = DataFrame(
- {
- col: type(self._parent.array)(result.field(i)) # type: ignore[call-arg]
- for i, col in enumerate(["year", "week", "day"])
- }
- )
- return iso_calendar_df
- @delegate_names(
- delegate=DatetimeArray,
- accessors=DatetimeArray._datetimelike_ops + ["unit"],
- typ="property",
- )
- @delegate_names(
- delegate=DatetimeArray,
- accessors=DatetimeArray._datetimelike_methods + ["as_unit"],
- typ="method",
- )
- class DatetimeProperties(Properties):
- """
- Accessor object for datetimelike properties of the Series values.
- Examples
- --------
- >>> seconds_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="s"))
- >>> seconds_series
- 0 2000-01-01 00:00:00
- 1 2000-01-01 00:00:01
- 2 2000-01-01 00:00:02
- dtype: datetime64[ns]
- >>> seconds_series.dt.second
- 0 0
- 1 1
- 2 2
- dtype: int32
- >>> hours_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="h"))
- >>> hours_series
- 0 2000-01-01 00:00:00
- 1 2000-01-01 01:00:00
- 2 2000-01-01 02:00:00
- dtype: datetime64[ns]
- >>> hours_series.dt.hour
- 0 0
- 1 1
- 2 2
- dtype: int32
- >>> quarters_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="q"))
- >>> quarters_series
- 0 2000-03-31
- 1 2000-06-30
- 2 2000-09-30
- dtype: datetime64[ns]
- >>> quarters_series.dt.quarter
- 0 1
- 1 2
- 2 3
- dtype: int32
- Returns a Series indexed like the original Series.
- Raises TypeError if the Series does not contain datetimelike values.
- """
- def to_pydatetime(self) -> np.ndarray:
- """
- Return the data as an array of :class:`datetime.datetime` objects.
- Timezone information is retained if present.
- .. warning::
- Python's datetime uses microsecond resolution, which is lower than
- pandas (nanosecond). The values are truncated.
- Returns
- -------
- numpy.ndarray
- Object dtype array containing native Python datetime objects.
- See Also
- --------
- datetime.datetime : Standard library value for a datetime.
- Examples
- --------
- >>> s = pd.Series(pd.date_range('20180310', periods=2))
- >>> s
- 0 2018-03-10
- 1 2018-03-11
- dtype: datetime64[ns]
- >>> s.dt.to_pydatetime()
- array([datetime.datetime(2018, 3, 10, 0, 0),
- datetime.datetime(2018, 3, 11, 0, 0)], dtype=object)
- pandas' nanosecond precision is truncated to microseconds.
- >>> s = pd.Series(pd.date_range('20180310', periods=2, freq='ns'))
- >>> s
- 0 2018-03-10 00:00:00.000000000
- 1 2018-03-10 00:00:00.000000001
- dtype: datetime64[ns]
- >>> s.dt.to_pydatetime()
- array([datetime.datetime(2018, 3, 10, 0, 0),
- datetime.datetime(2018, 3, 10, 0, 0)], dtype=object)
- """
- return self._get_values().to_pydatetime()
- @property
- def freq(self):
- return self._get_values().inferred_freq
- def isocalendar(self) -> DataFrame:
- """
- Calculate year, week, and day according to the ISO 8601 standard.
- .. versionadded:: 1.1.0
- Returns
- -------
- DataFrame
- With columns year, week and day.
- See Also
- --------
- Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
- week number, and weekday for the given Timestamp object.
- datetime.date.isocalendar : Return a named tuple object with
- three components: year, week and weekday.
- Examples
- --------
- >>> ser = pd.to_datetime(pd.Series(["2010-01-01", pd.NaT]))
- >>> ser.dt.isocalendar()
- year week day
- 0 2009 53 5
- 1 <NA> <NA> <NA>
- >>> ser.dt.isocalendar().week
- 0 53
- 1 <NA>
- Name: week, dtype: UInt32
- """
- return self._get_values().isocalendar().set_index(self._parent.index)
- @delegate_names(
- delegate=TimedeltaArray, accessors=TimedeltaArray._datetimelike_ops, typ="property"
- )
- @delegate_names(
- delegate=TimedeltaArray,
- accessors=TimedeltaArray._datetimelike_methods,
- typ="method",
- )
- class TimedeltaProperties(Properties):
- """
- Accessor object for datetimelike properties of the Series values.
- Returns a Series indexed like the original Series.
- Raises TypeError if the Series does not contain datetimelike values.
- Examples
- --------
- >>> seconds_series = pd.Series(
- ... pd.timedelta_range(start="1 second", periods=3, freq="S")
- ... )
- >>> seconds_series
- 0 0 days 00:00:01
- 1 0 days 00:00:02
- 2 0 days 00:00:03
- dtype: timedelta64[ns]
- >>> seconds_series.dt.seconds
- 0 1
- 1 2
- 2 3
- dtype: int32
- """
- def to_pytimedelta(self) -> np.ndarray:
- """
- Return an array of native :class:`datetime.timedelta` objects.
- Python's standard `datetime` library uses a different representation
- timedelta's. This method converts a Series of pandas Timedeltas
- to `datetime.timedelta` format with the same length as the original
- Series.
- Returns
- -------
- numpy.ndarray
- Array of 1D containing data with `datetime.timedelta` type.
- See Also
- --------
- datetime.timedelta : A duration expressing the difference
- between two date, time, or datetime.
- Examples
- --------
- >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="d"))
- >>> s
- 0 0 days
- 1 1 days
- 2 2 days
- 3 3 days
- 4 4 days
- dtype: timedelta64[ns]
- >>> s.dt.to_pytimedelta()
- array([datetime.timedelta(0), datetime.timedelta(days=1),
- datetime.timedelta(days=2), datetime.timedelta(days=3),
- datetime.timedelta(days=4)], dtype=object)
- """
- return self._get_values().to_pytimedelta()
- @property
- def components(self):
- """
- Return a Dataframe of the components of the Timedeltas.
- Returns
- -------
- DataFrame
- Examples
- --------
- >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit='s'))
- >>> s
- 0 0 days 00:00:00
- 1 0 days 00:00:01
- 2 0 days 00:00:02
- 3 0 days 00:00:03
- 4 0 days 00:00:04
- dtype: timedelta64[ns]
- >>> s.dt.components
- days hours minutes seconds milliseconds microseconds nanoseconds
- 0 0 0 0 0 0 0 0
- 1 0 0 0 1 0 0 0
- 2 0 0 0 2 0 0 0
- 3 0 0 0 3 0 0 0
- 4 0 0 0 4 0 0 0
- """
- return (
- self._get_values()
- .components.set_index(self._parent.index)
- .__finalize__(self._parent)
- )
- @property
- def freq(self):
- return self._get_values().inferred_freq
- @delegate_names(
- delegate=PeriodArray, accessors=PeriodArray._datetimelike_ops, typ="property"
- )
- @delegate_names(
- delegate=PeriodArray, accessors=PeriodArray._datetimelike_methods, typ="method"
- )
- class PeriodProperties(Properties):
- """
- Accessor object for datetimelike properties of the Series values.
- Returns a Series indexed like the original Series.
- Raises TypeError if the Series does not contain datetimelike values.
- Examples
- --------
- >>> seconds_series = pd.Series(
- ... pd.period_range(
- ... start="2000-01-01 00:00:00", end="2000-01-01 00:00:03", freq="s"
- ... )
- ... )
- >>> seconds_series
- 0 2000-01-01 00:00:00
- 1 2000-01-01 00:00:01
- 2 2000-01-01 00:00:02
- 3 2000-01-01 00:00:03
- dtype: period[S]
- >>> seconds_series.dt.second
- 0 0
- 1 1
- 2 2
- 3 3
- dtype: int64
- >>> hours_series = pd.Series(
- ... pd.period_range(start="2000-01-01 00:00", end="2000-01-01 03:00", freq="h")
- ... )
- >>> hours_series
- 0 2000-01-01 00:00
- 1 2000-01-01 01:00
- 2 2000-01-01 02:00
- 3 2000-01-01 03:00
- dtype: period[H]
- >>> hours_series.dt.hour
- 0 0
- 1 1
- 2 2
- 3 3
- dtype: int64
- >>> quarters_series = pd.Series(
- ... pd.period_range(start="2000-01-01", end="2000-12-31", freq="Q-DEC")
- ... )
- >>> quarters_series
- 0 2000Q1
- 1 2000Q2
- 2 2000Q3
- 3 2000Q4
- dtype: period[Q-DEC]
- >>> quarters_series.dt.quarter
- 0 1
- 1 2
- 2 3
- 3 4
- dtype: int64
- """
- class CombinedDatetimelikeProperties(
- DatetimeProperties, TimedeltaProperties, PeriodProperties
- ):
- def __new__(cls, data: Series):
- # CombinedDatetimelikeProperties isn't really instantiated. Instead
- # we need to choose which parent (datetime or timedelta) is
- # appropriate. Since we're checking the dtypes anyway, we'll just
- # do all the validation here.
- if not isinstance(data, ABCSeries):
- raise TypeError(
- f"cannot convert an object of type {type(data)} to a datetimelike index"
- )
- orig = data if is_categorical_dtype(data.dtype) else None
- if orig is not None:
- data = data._constructor(
- orig.array,
- name=orig.name,
- copy=False,
- dtype=orig._values.categories.dtype,
- index=orig.index,
- )
- if isinstance(data.dtype, ArrowDtype) and data.dtype.kind == "M":
- return ArrowTemporalProperties(data, orig)
- if is_datetime64_dtype(data.dtype):
- return DatetimeProperties(data, orig)
- elif is_datetime64tz_dtype(data.dtype):
- return DatetimeProperties(data, orig)
- elif is_timedelta64_dtype(data.dtype):
- return TimedeltaProperties(data, orig)
- elif is_period_dtype(data.dtype):
- return PeriodProperties(data, orig)
- raise AttributeError("Can only use .dt accessor with datetimelike values")
|