From 37239cc8acee98b048225190a934340b4d204f36 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 26 Oct 2018 13:12:58 -0700 Subject: [PATCH 1/6] tslibs cleanup, typing, memoryviews --- pandas/_libs/missing.pxd | 2 - pandas/_libs/tslibs/ccalendar.pyx | 2 +- pandas/_libs/tslibs/conversion.pyx | 46 ++++++++++------- pandas/_libs/tslibs/fields.pyx | 83 ++++++++++++------------------ pandas/_libs/tslibs/nattype.pyx | 5 +- pandas/_libs/tslibs/offsets.pyx | 3 +- pandas/_libs/tslibs/parsing.pyx | 2 +- pandas/_libs/tslibs/period.pyx | 33 +++++------- pandas/_libs/tslibs/timedeltas.pyx | 2 +- pandas/_libs/tslibs/timestamps.pyx | 25 ++++----- pandas/_libs/tslibs/timezones.pyx | 4 +- 11 files changed, 96 insertions(+), 111 deletions(-) diff --git a/pandas/_libs/missing.pxd b/pandas/_libs/missing.pxd index 2c1f13eeb5dff..9f660cc6785c8 100644 --- a/pandas/_libs/missing.pxd +++ b/pandas/_libs/missing.pxd @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from tslibs.nattype cimport is_null_datetimelike - cpdef bint checknull(object val) cpdef bint checknull_old(object val) diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx index 7d58b43e5d460..07c146c06b510 100644 --- a/pandas/_libs/tslibs/ccalendar.pyx +++ b/pandas/_libs/tslibs/ccalendar.pyx @@ -54,7 +54,7 @@ weekday_to_int = {int_to_weekday[key]: key for key in int_to_weekday} @cython.wraparound(False) @cython.boundscheck(False) -cpdef inline int32_t get_days_in_month(int year, Py_ssize_t month) nogil: +cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil: """Return the number of days in the given month of the given year. Parameters diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index f9c604cd76472..7a5e135a76d6f 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -49,10 +49,10 @@ TD_DTYPE = np.dtype('m8[ns]') UTC = pytz.UTC + # ---------------------------------------------------------------------- # Misc Helpers -# TODO: How to declare np.datetime64 as the input type? cdef inline int64_t get_datetime64_nanos(object val) except? -1: """ Extract the value and unit from a np.datetime64 object, then convert the @@ -74,7 +74,7 @@ cdef inline int64_t get_datetime64_nanos(object val) except? -1: return ival -def ensure_datetime64ns(ndarray arr, copy=True): +def ensure_datetime64ns(arr: ndarray, copy: bint = True): """ Ensure a np.datetime64 array has dtype specifically 'datetime64[ns]' @@ -121,7 +121,7 @@ def ensure_datetime64ns(ndarray arr, copy=True): return result -def ensure_timedelta64ns(ndarray arr, copy=True): +def ensure_timedelta64ns(arr: ndarray, copy: bint = True): """ Ensure a np.timedelta64 array has dtype specifically 'timedelta64[ns]' @@ -133,23 +133,23 @@ def ensure_timedelta64ns(ndarray arr, copy=True): Returns ------- result : ndarray with dtype timedelta64[ns] - """ return arr.astype(TD_DTYPE, copy=copy) + # TODO: check for overflows when going from a lower-resolution to nanos -def datetime_to_datetime64(object[:] values): +def datetime_to_datetime64(values: object[:]): """ Convert ndarray of datetime-like objects to int64 array representing nanosecond timestamps. Parameters ---------- - values : ndarray + values : ndarray[object] Returns ------- - result : ndarray with dtype int64 + result : ndarray[int64_t] inferred_tz : tzinfo or None """ cdef: @@ -225,6 +225,7 @@ cdef class _TSObject: @property def value(self): + # This is needed in order for `value` to be accessible in lib.pyx return self.value @@ -849,10 +850,11 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, ndarray[int64_t] trans int64_t[:] deltas, idx_shifted ndarray ambiguous_array - Py_ssize_t i, idx, pos, ntrans, n = len(vals) + Py_ssize_t i, idx, pos, pos_left, pos_right, ntrans, n = len(vals) int64_t *tdata - int64_t v, left, right, val, v_left, v_right - ndarray[int64_t] result, result_a, result_b, dst_hours + int64_t v, left, right, val, v_left, v_right, delta_idx + int64_t[:] result, dst_hours, idx_shifted_left, idx_shifted_right + ndarray[int64_t] result_a, result_b npy_datetimestruct dts bint infer_dst = False, is_dst = False, fill = False bint shift = False, fill_nonexist = False @@ -867,7 +869,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, for i in range(n): v = vals[i] result[i] = _tz_convert_tzlocal_utc(v, tz, to_utc=True) - return result + return result.base # `.base` to access underlying np.array if is_string_object(ambiguous): if ambiguous == 'infer': @@ -929,7 +931,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, if infer_dst: dst_hours = np.empty(n, dtype=np.int64) - dst_hours.fill(NPY_NAT) + dst_hours[:] = NPY_NAT # Get the ambiguous hours (given the above, these are the hours # where result_a != result_b and neither of them are NAT) @@ -970,7 +972,13 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, # Pull the only index and adjust a_idx = grp[:switch_idx] b_idx = grp[switch_idx:] - dst_hours[grp] = np.hstack((result_a[a_idx], result_b[b_idx])) + + # __setitem__ on dst_hours.base because indexing with + # an ndarray (grp) directly on a memoryview is not supported + # TODO: is `grp` necessarily contiguous? i.e. could we + # equivalently write dst_hours[grp[0]:grp[-1]] = ... ? + dst_hours.base[grp] = np.hstack((result_a[a_idx], + result_b[b_idx])) for i in range(n): val = vals[i] @@ -1015,7 +1023,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, stamp = _render_tstamp(val) raise pytz.NonExistentTimeError(stamp) - return result + return result.base # `.base` to access underlying np.array cdef inline bisect_right_i8(int64_t *data, int64_t val, Py_ssize_t n): @@ -1051,7 +1059,7 @@ cdef inline str _render_tstamp(int64_t val): # Normalization -def normalize_date(object dt): +def normalize_date(dt: object) -> datetime: """ Normalize datetime.datetime value to midnight. Returns datetime.date as a datetime.datetime at midnight @@ -1085,7 +1093,7 @@ def normalize_date(object dt): @cython.wraparound(False) @cython.boundscheck(False) -def normalize_i8_timestamps(int64_t[:] stamps, tz=None): +def normalize_i8_timestamps(int64_t[:] stamps, object tz=None): """ Normalize each of the (nanosecond) timestamps in the given array by rounding down to the beginning of the day (i.e. midnight). If `tz` @@ -1122,7 +1130,7 @@ def normalize_i8_timestamps(int64_t[:] stamps, tz=None): @cython.wraparound(False) @cython.boundscheck(False) -cdef int64_t[:] _normalize_local(int64_t[:] stamps, object tz): +cdef int64_t[:] _normalize_local(int64_t[:] stamps, tzinfo tz): """ Normalize each of the (nanosecond) timestamps in the given array by rounding down to the beginning of the day (i.e. midnight) for the @@ -1131,7 +1139,7 @@ cdef int64_t[:] _normalize_local(int64_t[:] stamps, object tz): Parameters ---------- stamps : int64 ndarray - tz : tzinfo or None + tz : tzinfo Returns ------- @@ -1207,7 +1215,7 @@ cdef inline int64_t _normalized_stamp(npy_datetimestruct *dts) nogil: return dtstruct_to_dt64(dts) -def is_date_array_normalized(int64_t[:] stamps, tz=None): +def is_date_array_normalized(int64_t[:] stamps, object tz=None): """ Check if all of the given (nanosecond) timestamps are normalized to midnight, i.e. hour == minute == second == 0. If the optional timezone diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index 1af9cd619c5f9..774c32daf025e 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -47,7 +47,7 @@ def build_field_sarray(int64_t[:] dtindex): cdef: Py_ssize_t i, count = 0 npy_datetimestruct dts - ndarray[int32_t] years, months, days, hours, minutes, seconds, mus + int32_t[:] years, months, days, hours, minutes, seconds, mus count = len(dtindex) @@ -91,7 +91,7 @@ def get_date_name_field(int64_t[:] dtindex, object field, object locale=None): """ cdef: Py_ssize_t i, count = 0 - ndarray[object] out, names + object[:] out, names npy_datetimestruct dts int dow @@ -112,7 +112,7 @@ def get_date_name_field(int64_t[:] dtindex, object field, object locale=None): dt64_to_dtstruct(dtindex[i], &dts) dow = dayofweek(dts.year, dts.month, dts.day) out[i] = names[dow].capitalize() - return out + elif field == 'month_name': if locale is None: names = np.array(MONTHS_FULL, dtype=np.object_) @@ -126,9 +126,11 @@ def get_date_name_field(int64_t[:] dtindex, object field, object locale=None): dt64_to_dtstruct(dtindex[i], &dts) out[i] = names[dts.month].capitalize() - return out - raise ValueError("Field %s not supported" % field) + else: + raise ValueError("Field {field} not supported".format(field=field)) + + return out.base # `.base` to access underlying np.array @cython.wraparound(False) @@ -145,8 +147,8 @@ def get_start_end_field(int64_t[:] dtindex, object field, bint is_business = 0 int end_month = 12 int start_month = 1 - ndarray[int8_t] out - ndarray[int32_t, ndim=2] _month_offset + int8_t[:] out + int32_t[:, :] _month_offset bint isleap npy_datetimestruct dts int mo_off, dom, doy, dow, ldom @@ -161,8 +163,8 @@ def get_start_end_field(int64_t[:] dtindex, object field, if freqstr: if freqstr == 'C': - raise ValueError( - "Custom business days is not supported by %s" % field) + raise ValueError("Custom business days is not supported " + "by {field}".format(field=field)) is_business = freqstr[0] == 'B' # YearBegin(), BYearBegin() use month = starting month of year. @@ -194,7 +196,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, if (dom == 1 and dow < 5) or (dom <= 3 and dow == 0): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -206,7 +208,6 @@ def get_start_end_field(int64_t[:] dtindex, object field, if dom == 1: out[i] = 1 - return out.view(bool) elif field == 'is_month_end': if is_business: @@ -226,7 +227,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, if (ldom == doy and dow < 5) or ( dow == 4 and (ldom - doy <= 2)): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -242,7 +243,6 @@ def get_start_end_field(int64_t[:] dtindex, object field, if ldom == doy: out[i] = 1 - return out.view(bool) elif field == 'is_quarter_start': if is_business: @@ -258,7 +258,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, if ((dts.month - start_month) % 3 == 0) and ( (dom == 1 and dow < 5) or (dom <= 3 and dow == 0)): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -270,7 +270,6 @@ def get_start_end_field(int64_t[:] dtindex, object field, if ((dts.month - start_month) % 3 == 0) and dom == 1: out[i] = 1 - return out.view(bool) elif field == 'is_quarter_end': if is_business: @@ -291,7 +290,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, (ldom == doy and dow < 5) or ( dow == 4 and (ldom - doy <= 2))): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -307,7 +306,6 @@ def get_start_end_field(int64_t[:] dtindex, object field, if ((dts.month - end_month) % 3 == 0) and (ldom == doy): out[i] = 1 - return out.view(bool) elif field == 'is_year_start': if is_business: @@ -323,7 +321,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, if (dts.month == start_month) and ( (dom == 1 and dow < 5) or (dom <= 3 and dow == 0)): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -335,7 +333,6 @@ def get_start_end_field(int64_t[:] dtindex, object field, if (dts.month == start_month) and dom == 1: out[i] = 1 - return out.view(bool) elif field == 'is_year_end': if is_business: @@ -356,7 +353,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, (ldom == doy and dow < 5) or ( dow == 4 and (ldom - doy <= 2))): out[i] = 1 - return out.view(bool) + else: for i in range(count): if dtindex[i] == NPY_NAT: @@ -372,21 +369,23 @@ def get_start_end_field(int64_t[:] dtindex, object field, if (dts.month == end_month) and (ldom == doy): out[i] = 1 - return out.view(bool) - raise ValueError("Field %s not supported" % field) + else: + raise ValueError("Field {field} not supported".format(field=field)) + + return out.base.view(bool) # `.base` to access underlying np.array @cython.wraparound(False) @cython.boundscheck(False) -def get_date_field(ndarray[int64_t] dtindex, object field): +def get_date_field(int64_t[:] dtindex, object field): """ Given a int64-based datetime index, extract the year, month, etc., field and return an array of these values. """ cdef: Py_ssize_t i, count = 0 - ndarray[int32_t] out + int32_t[:] out npy_datetimestruct dts count = len(dtindex) @@ -401,7 +400,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.year - return out elif field == 'M': with nogil: @@ -412,7 +410,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.month - return out elif field == 'D': with nogil: @@ -423,7 +420,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.day - return out elif field == 'h': with nogil: @@ -434,7 +430,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.hour - return out elif field == 'm': with nogil: @@ -445,7 +440,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.min - return out elif field == 's': with nogil: @@ -456,7 +450,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.sec - return out elif field == 'us': with nogil: @@ -467,7 +460,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.us - return out elif field == 'ns': with nogil: @@ -478,7 +470,7 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.ps / 1000 - return out + elif field == 'doy': with nogil: for i in range(count): @@ -488,7 +480,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = get_day_of_year(dts.year, dts.month, dts.day) - return out elif field == 'dow': with nogil: @@ -499,7 +490,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dayofweek(dts.year, dts.month, dts.day) - return out elif field == 'woy': with nogil: @@ -510,7 +500,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = get_week_of_year(dts.year, dts.month, dts.day) - return out elif field == 'q': with nogil: @@ -522,7 +511,6 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = dts.month out[i] = ((out[i] - 1) / 3) + 1 - return out elif field == 'dim': with nogil: @@ -533,11 +521,14 @@ def get_date_field(ndarray[int64_t] dtindex, object field): dt64_to_dtstruct(dtindex[i], &dts) out[i] = get_days_in_month(dts.year, dts.month) - return out + elif field == 'is_leap_year': return isleapyear_arr(get_date_field(dtindex, 'Y')) - raise ValueError("Field %s not supported" % field) + else: + raise ValueError("Field %s not supported" % field) + + return out.base # `.base` to access underlying np.array @cython.wraparound(False) @@ -549,7 +540,7 @@ def get_timedelta_field(int64_t[:] tdindex, object field): """ cdef: Py_ssize_t i, count = 0 - ndarray[int32_t] out + int32_t[:] out pandas_timedeltastruct tds count = len(tdindex) @@ -564,7 +555,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.days - return out elif field == 'h': with nogil: @@ -575,7 +565,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.hrs - return out elif field == 's': with nogil: @@ -586,7 +575,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.sec - return out elif field == 'seconds': with nogil: @@ -597,7 +585,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.seconds - return out elif field == 'ms': with nogil: @@ -608,7 +595,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.ms - return out elif field == 'microseconds': with nogil: @@ -619,7 +605,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.microseconds - return out elif field == 'us': with nogil: @@ -630,7 +615,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.us - return out elif field == 'ns': with nogil: @@ -641,7 +625,6 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.ns - return out elif field == 'nanoseconds': with nogil: @@ -652,9 +635,11 @@ def get_timedelta_field(int64_t[:] tdindex, object field): td64_to_tdstruct(tdindex[i], &tds) out[i] = tds.nanoseconds - return out - raise ValueError("Field %s not supported" % field) + else: + raise ValueError("Field {field} not supported".format(field=field)) + + return out.base # `.base` to access underlying np.array cpdef isleapyear_arr(ndarray years): diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index 0eec84ecf8285..a52ad1fba4143 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -97,7 +97,8 @@ cdef class _NaT(datetime): return hash(self.value) def __richcmp__(_NaT self, object other, int op): - cdef int ndim = getattr(other, 'ndim', -1) + cdef: + int ndim = getattr(other, 'ndim', -1) if ndim == -1: return _nat_scalar_rules[op] @@ -181,7 +182,7 @@ cdef class _NaT(datetime): def to_datetime64(self): """ Returns a numpy.datetime64 object with 'ns' precision """ - return np.datetime64('NaT') + return np.datetime64('NaT', 'ns') class NaTType(_NaT): diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 5baacfe5f725f..290287c51883e 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -5,6 +5,7 @@ from cython import Py_ssize_t import time from cpython.datetime cimport (PyDateTime_IMPORT, + PyDateTime_Check, datetime, timedelta, time as dt_time) PyDateTime_IMPORT @@ -354,7 +355,7 @@ class _BaseOffset(object): return NotImplemented def __sub__(self, other): - if isinstance(other, datetime): + if PyDateTime_Check(other): raise TypeError('Cannot subtract datetime from offset.') elif type(other) == type(self): return type(self)(self.n - other.n, normalize=self.normalize, diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 3887957aeefd4..71bb8f79642dc 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -361,7 +361,7 @@ cdef dateutil_parse(object timestr, object default, ignoretz=False, return ret, reso -cpdef object _get_rule_month(object source, object default='DEC'): +cdef object _get_rule_month(object source, object default='DEC'): """ Return starting month of given freq, default is December. diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 43dc415bfd464..d928394cb669a 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2,7 +2,6 @@ from datetime import datetime, date from cpython cimport ( - PyUnicode_Check, PyObject_RichCompareBool, Py_EQ, Py_NE) @@ -14,9 +13,9 @@ from libc.stdlib cimport free, malloc from libc.time cimport strftime, tm from libc.string cimport strlen, memset -cimport cython +import cython -from cpython.datetime cimport (PyDateTime_Check, PyDelta_Check, +from cpython.datetime cimport (PyDateTime_Check, PyDelta_Check, PyDate_Check, PyDateTime_IMPORT) # import datetime C API PyDateTime_IMPORT @@ -551,7 +550,7 @@ cdef int64_t asfreq_AtoA(int64_t ordinal, asfreq_info *af_info): cdef int64_t asfreq_AtoQ(int64_t ordinal, asfreq_info *af_info): return transform_via_day(ordinal, af_info, asfreq_AtoDT, - asfreq_DTtoQ); + asfreq_DTtoQ) cdef int64_t asfreq_AtoM(int64_t ordinal, asfreq_info *af_info): @@ -1106,7 +1105,7 @@ cdef inline int calc_week_end(int freq, int group) nogil: return freq - group -def period_asfreq_arr(ndarray[int64_t] arr, int freq1, int freq2, bint end): +def period_asfreq_arr(int64_t[:] arr, int freq1, int freq2, bint end): """ Convert int64-array of period ordinals from one frequency to another, and if upsampling, choose to use start ('S') or end ('E') of period. @@ -1124,21 +1123,13 @@ def period_asfreq_arr(ndarray[int64_t] arr, int freq1, int freq2, bint end): func = get_asfreq_func(freq1, freq2) get_asfreq_info(freq1, freq2, end, &af_info) - mask = arr == iNaT - if mask.any(): # NaT process - for i in range(n): - val = arr[i] - if val != iNaT: - val = func(val, &af_info) - if val == INT32_MIN: - raise ValueError("Unable to convert to desired frequency.") - result[i] = val - else: - for i in range(n): - val = func(arr[i], &af_info) + for i in range(n): + val = arr[i] + if val != iNaT: + val = func(val, &af_info) if val == INT32_MIN: raise ValueError("Unable to convert to desired frequency.") - result[i] = val + result[i] = val return result.base # .base to access underlying np.ndarray @@ -1248,7 +1239,7 @@ cdef object _period_strftime(int64_t value, int freq, object fmt): list found_pat = [False] * len(extra_fmts) int year, quarter - if PyUnicode_Check(fmt): + if isinstance(fmt, unicode): fmt = fmt.encode('utf-8') get_date_info(value, freq, &dts) @@ -2459,7 +2450,7 @@ class Period(_Period): "Invalid frequency or could not infer: {reso}" .format(reso=reso)) - elif isinstance(value, datetime): + elif PyDateTime_Check(value): dt = value if freq is None: raise ValueError('Must supply freq for datetime value') @@ -2467,7 +2458,7 @@ class Period(_Period): dt = Timestamp(value) if freq is None: raise ValueError('Must supply freq for datetime value') - elif isinstance(value, date): + elif PyDate_Check(value): dt = datetime(year=value.year, month=value.month, day=value.day) if freq is None: raise ValueError('Must supply freq for datetime value') diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 74bbc64af25f2..8322fb4d544ed 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -82,7 +82,7 @@ _no_input = object() # ---------------------------------------------------------------------- # API -def ints_to_pytimedelta(int64_t[:] arr, box=False): +def ints_to_pytimedelta(int64_t[:] arr, bint box=False): """ convert an i8 repr to an ndarray of timedelta or Timedelta (if box == True) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 08b0c5472549e..27fa62568464b 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -267,7 +267,8 @@ cdef class _Timestamp(datetime): cdef bint _compare_outside_nanorange(_Timestamp self, datetime other, int op) except -1: - cdef datetime dtval = self.to_pydatetime() + cdef: + datetime dtval = self.to_pydatetime() self._assert_tzawareness_compat(other) @@ -287,8 +288,7 @@ cdef class _Timestamp(datetime): elif op == Py_GE: return dtval >= other - cdef int _assert_tzawareness_compat(_Timestamp self, - object other) except -1: + cdef _assert_tzawareness_compat(_Timestamp self, datetime other): if self.tzinfo is None: if other.tzinfo is not None: raise TypeError('Cannot compare tz-naive and tz-aware ' @@ -296,7 +296,7 @@ cdef class _Timestamp(datetime): elif other.tzinfo is None: raise TypeError('Cannot compare tz-naive and tz-aware timestamps') - cpdef datetime to_pydatetime(_Timestamp self, warn=True): + cpdef datetime to_pydatetime(_Timestamp self, bint warn=True): """ Convert a Timestamp object to a native Python datetime object. @@ -315,7 +315,8 @@ cdef class _Timestamp(datetime): return np.datetime64(self.value, 'ns') def __add__(self, other): - cdef int64_t other_int, nanos + cdef: + int64_t other_int, nanos if is_timedelta64_object(other): other_int = other.astype('timedelta64[ns]').view('i8') @@ -916,49 +917,49 @@ class Timestamp(_Timestamp): return getattr(self.freq, 'freqstr', self.freq) @property - def is_month_start(self): + def is_month_start(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return self.day == 1 return self._get_start_end_field('is_month_start') @property - def is_month_end(self): + def is_month_end(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return self.day == self.days_in_month return self._get_start_end_field('is_month_end') @property - def is_quarter_start(self): + def is_quarter_start(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return self.day == 1 and self.month % 3 == 1 return self._get_start_end_field('is_quarter_start') @property - def is_quarter_end(self): + def is_quarter_end(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return (self.month % 3) == 0 and self.day == self.days_in_month return self._get_start_end_field('is_quarter_end') @property - def is_year_start(self): + def is_year_start(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return self.day == self.month == 1 return self._get_start_end_field('is_year_start') @property - def is_year_end(self): + def is_year_end(self) -> bool: if self.freq is None: # fast-path for non-business frequencies return self.month == 12 and self.day == 31 return self._get_start_end_field('is_year_end') @property - def is_leap_year(self): + def is_leap_year(self) -> bool: return bool(ccalendar.is_leapyear(self.year)) def tz_localize(self, tz, ambiguous='raise', nonexistent='raise', diff --git a/pandas/_libs/tslibs/timezones.pyx b/pandas/_libs/tslibs/timezones.pyx index 1fc1347c8b9e3..19a00fbd37dd5 100644 --- a/pandas/_libs/tslibs/timezones.pyx +++ b/pandas/_libs/tslibs/timezones.pyx @@ -36,8 +36,8 @@ cdef inline bint is_tzlocal(object tz): cdef inline bint treat_tz_as_pytz(object tz): - return hasattr(tz, '_utc_transition_times') and hasattr( - tz, '_transition_info') + return (hasattr(tz, '_utc_transition_times') and + hasattr(tz, '_transition_info')) cdef inline bint treat_tz_as_dateutil(object tz): From c7bd679871d6eb4342e59283e4e532404636191e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 26 Oct 2018 13:26:40 -0700 Subject: [PATCH 2/6] cleanup and typing --- pandas/_libs/tslibs/np_datetime.pyx | 1 + pandas/_libs/tslibs/offsets.pyx | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/_libs/tslibs/np_datetime.pyx b/pandas/_libs/tslibs/np_datetime.pyx index e0ecfc24804a9..61d0f697e7fe4 100644 --- a/pandas/_libs/tslibs/np_datetime.pyx +++ b/pandas/_libs/tslibs/np_datetime.pyx @@ -136,6 +136,7 @@ cdef inline void dt64_to_dtstruct(int64_t dt64, pandas_datetime_to_datetimestruct(dt64, NPY_FR_ns, out) return + cdef inline void td64_to_tdstruct(int64_t td64, pandas_timedeltastruct* out) nogil: """Convenience function to call pandas_timedelta_to_timedeltastruct diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 290287c51883e..3af4107a333f8 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -497,7 +497,7 @@ class _Tick(object): # ---------------------------------------------------------------------- # RelativeDelta Arithmetic -cpdef datetime shift_day(datetime other, int days): +def shift_day(other: datetime, days: int) -> datetime: """ Increment the datetime `other` by the given number of days, retaining the time-portion of the datetime. For tz-naive datetimes this is @@ -816,7 +816,8 @@ def shift_months(int64_t[:] dtindex, int months, object day=None): return np.asarray(out) -cpdef datetime shift_month(datetime stamp, int months, object day_opt=None): +def shift_month(stamp: datetime, months: int, + day_opt: object = None) -> datetime: """ Given a datetime (or Timestamp) `stamp`, an integer `months` and an option `day_opt`, return a new datetimelike that many months later, @@ -946,8 +947,8 @@ cpdef int roll_convention(int other, int n, int compare) nogil: return n -cpdef int roll_qtrday(datetime other, int n, int month, object day_opt, - int modby=3) except? -1: +def roll_qtrday(other: datetime, n: int, month: int, + day_opt: object, modby: int = 3) -> int: """ Possibly increment or decrement the number of periods to shift based on rollforward/rollbackward conventions. @@ -989,8 +990,7 @@ cpdef int roll_qtrday(datetime other, int n, int month, object day_opt, return n -cpdef int roll_yearday(datetime other, int n, int month, - object day_opt) except? -1: +def roll_yearday(other: datetime, n: int, month: int, day_opt: object) -> int: """ Possibly increment or decrement the number of periods to shift based on rollforward/rollbackward conventions. From e2a2df9cc8fc7fcb5a7edd356f9de41047daa588 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 26 Oct 2018 18:47:55 -0700 Subject: [PATCH 3/6] small optimizations --- pandas/_libs/tslibs/period.pyx | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index d928394cb669a..a6cef2e13213e 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# cython: boundscheck=False, wraparound=False from datetime import datetime, date from cpython cimport ( @@ -46,7 +47,7 @@ from frequencies cimport (get_freq_code, get_base_alias, get_rule_month) from parsing import parse_time_string from resolution import Resolution -from nattype import nat_strings, NaT, iNaT +from nattype import nat_strings, NaT from nattype cimport _nat_scalar_rules, NPY_NAT, is_null_datetimelike from offsets cimport to_offset from offsets import _Tick @@ -976,8 +977,6 @@ cdef inline int month_to_quarter(int month): # Period logic -@cython.wraparound(False) -@cython.boundscheck(False) def dt64arr_to_periodarr(int64_t[:] dtarr, int freq, tz=None): """ Convert array of datetime64 values (passed in as 'i8' dtype) to a set of @@ -1005,8 +1004,6 @@ def dt64arr_to_periodarr(int64_t[:] dtarr, int freq, tz=None): return out.base # .base to access underlying np.ndarray -@cython.wraparound(False) -@cython.boundscheck(False) def periodarr_to_dt64arr(int64_t[:] periodarr, int freq): """ Convert array to datetime64 values from a set of ordinals corresponding to @@ -1040,8 +1037,8 @@ cpdef int64_t period_asfreq(int64_t ordinal, int freq1, int freq2, bint end): freq_conv_func func asfreq_info af_info - if ordinal == iNaT: - return iNaT + if ordinal == NPY_NAT: + return NPY_NAT func = get_asfreq_func(freq1, freq2) get_asfreq_info(freq1, freq2, end, &af_info) @@ -1125,7 +1122,7 @@ def period_asfreq_arr(int64_t[:] arr, int freq1, int freq2, bint end): for i in range(n): val = arr[i] - if val != iNaT: + if val != NPY_NAT: val = func(val, &af_info) if val == INT32_MIN: raise ValueError("Unable to convert to desired frequency.") @@ -1183,7 +1180,7 @@ def period_format(int64_t value, int freq, object fmt=None): cdef: int freq_group - if value == iNaT: + if value == NPY_NAT: return repr(NaT) if fmt is None: @@ -1239,6 +1236,7 @@ cdef object _period_strftime(int64_t value, int freq, object fmt): list found_pat = [False] * len(extra_fmts) int year, quarter + if isinstance(fmt, unicode): fmt = fmt.encode('utf-8') @@ -1386,7 +1384,7 @@ def get_period_field_arr(int code, int64_t[:] arr, int freq): out = np.empty(sz, dtype=np.int64) for i in range(sz): - if arr[i] == iNaT: + if arr[i] == NPY_NAT: out[i] = -1 continue out[i] = func(arr[i], freq) @@ -1434,7 +1432,7 @@ def extract_ordinals(object[:] values, freq): p = values[i] if is_null_datetimelike(p): - ordinals[i] = iNaT + ordinals[i] = NPY_NAT else: try: ordinals[i] = p.ordinal @@ -1447,7 +1445,7 @@ def extract_ordinals(object[:] values, freq): p = Period(p, freq=freq) if p is NaT: # input may contain NaT-like string - ordinals[i] = iNaT + ordinals[i] = NPY_NAT else: ordinals[i] = p.ordinal @@ -1475,8 +1473,6 @@ def extract_freq(object[:] values): # ----------------------------------------------------------------------- # period helpers -@cython.wraparound(False) -@cython.boundscheck(False) cdef int64_t[:] localize_dt64arr_to_period(int64_t[:] stamps, int freq, object tz): cdef: @@ -1572,7 +1568,7 @@ cdef class _Period(object): """ Fast creation from an ordinal and freq that are already validated! """ - if ordinal == iNaT: + if ordinal == NPY_NAT: return NaT else: freq = cls._maybe_convert_freq(freq) @@ -2406,7 +2402,7 @@ class Period(_Period): if (year is None and month is None and quarter is None and day is None and hour is None and minute is None and second is None): - ordinal = iNaT + ordinal = NPY_NAT else: if freq is None: raise ValueError("If value is None, freq cannot be None") @@ -2432,7 +2428,7 @@ class Period(_Period): ordinal = converted.ordinal elif is_null_datetimelike(value) or value in nat_strings: - ordinal = iNaT + ordinal = NPY_NAT elif is_string_object(value) or util.is_integer_object(value): if util.is_integer_object(value): @@ -2440,7 +2436,7 @@ class Period(_Period): value = value.upper() dt, _, reso = parse_time_string(value, freq) if dt is NaT: - ordinal = iNaT + ordinal = NPY_NAT if freq is None: try: From ae04c77e14cc39200dba73559a26a5afc4bcceca Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 26 Oct 2018 19:26:10 -0700 Subject: [PATCH 4/6] optimizations --- pandas/_libs/tslibs/conversion.pyx | 10 ++++++++++ pandas/_libs/tslibs/fields.pyx | 3 +++ pandas/_libs/tslibs/timedeltas.pyx | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 7a5e135a76d6f..999955cb88d3c 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -74,6 +74,8 @@ cdef inline int64_t get_datetime64_nanos(object val) except? -1: return ival +@cython.boundscheck(False) +@cython.wraparound(False) def ensure_datetime64ns(arr: ndarray, copy: bint = True): """ Ensure a np.datetime64 array has dtype specifically 'datetime64[ns]' @@ -138,6 +140,8 @@ def ensure_timedelta64ns(arr: ndarray, copy: bint = True): # TODO: check for overflows when going from a lower-resolution to nanos +@cython.boundscheck(False) +@cython.wraparound(False) def datetime_to_datetime64(values: object[:]): """ Convert ndarray of datetime-like objects to int64 array representing @@ -611,6 +615,8 @@ cpdef inline datetime localize_pydatetime(datetime dt, object tz): # ---------------------------------------------------------------------- # Timezone Conversion +@cython.boundscheck(False) +@cython.wraparound(False) cdef inline int64_t[:] _tz_convert_dst(int64_t[:] values, tzinfo tz, bint to_utc=True): """ @@ -757,6 +763,8 @@ cpdef int64_t tz_convert_single(int64_t val, object tz1, object tz2): return _tz_convert_dst(arr, tz2, to_utc=False)[0] +@cython.boundscheck(False) +@cython.wraparound(False) cdef inline int64_t[:] _tz_convert_one_way(int64_t[:] vals, object tz, bint to_utc): """ @@ -1215,6 +1223,8 @@ cdef inline int64_t _normalized_stamp(npy_datetimestruct *dts) nogil: return dtstruct_to_dt64(dts) +@cython.boundscheck(False) +@cython.wraparound(False) def is_date_array_normalized(int64_t[:] stamps, object tz=None): """ Check if all of the given (nanosecond) timestamps are normalized to diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index 774c32daf025e..25d8bf844b4f4 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -40,6 +40,8 @@ def get_time_micros(ndarray[int64_t] dtindex): return micros +@cython.wraparound(False) +@cython.boundscheck(False) def build_field_sarray(int64_t[:] dtindex): """ Datetime as int64 representation to a structured array of fields @@ -134,6 +136,7 @@ def get_date_name_field(int64_t[:] dtindex, object field, object locale=None): @cython.wraparound(False) +@cython.boundscheck(False) def get_start_end_field(int64_t[:] dtindex, object field, object freqstr=None, int month_kw=12): """ diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 8322fb4d544ed..75394f0794708 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -6,6 +6,7 @@ import warnings import sys cdef bint PY3 = (sys.version_info[0] >= 3) +import cython from cython import Py_ssize_t from cpython cimport Py_NE, Py_EQ, PyObject_RichCompare @@ -82,6 +83,8 @@ _no_input = object() # ---------------------------------------------------------------------- # API +@cython.boundscheck(False) +@cython.wraparound(False) def ints_to_pytimedelta(int64_t[:] arr, bint box=False): """ convert an i8 repr to an ndarray of timedelta or Timedelta (if box == @@ -198,6 +201,8 @@ cpdef convert_to_timedelta64(object ts, object unit): return ts.astype('timedelta64[ns]') +@cython.boundscheck(False) +@cython.wraparound(False) def array_to_timedelta64(object[:] values, unit='ns', errors='raise'): """ Convert an ndarray to an array of timedeltas. If errors == 'coerce', From c89ddffc3592cd29cd2f564add6dc9f8dcec8a26 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Fri, 26 Oct 2018 21:17:36 -0700 Subject: [PATCH 5/6] Whitespace fixup --- pandas/_libs/tslibs/period.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index a6cef2e13213e..23093042823d1 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -307,6 +307,7 @@ cdef inline int64_t transform_via_day(int64_t ordinal, result = second_func(result, af_info) return result + # -------------------------------------------------------------------- # Conversion _to_ Daily Freq @@ -976,7 +977,6 @@ cdef inline int month_to_quarter(int month): # ---------------------------------------------------------------------- # Period logic - def dt64arr_to_periodarr(int64_t[:] dtarr, int freq, tz=None): """ Convert array of datetime64 values (passed in as 'i8' dtype) to a set of @@ -1227,6 +1227,7 @@ cdef list extra_fmts = [(b"%q", b"^`AB`^"), cdef list str_extra_fmts = ["^`AB`^", "^`CD`^", "^`EF`^", "^`GH`^", "^`IJ`^", "^`KL`^"] + cdef object _period_strftime(int64_t value, int freq, object fmt): cdef: Py_ssize_t i @@ -1236,7 +1237,6 @@ cdef object _period_strftime(int64_t value, int freq, object fmt): list found_pat = [False] * len(extra_fmts) int year, quarter - if isinstance(fmt, unicode): fmt = fmt.encode('utf-8') From f501cd725ce3af3558ea053106a127783284ed42 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 1 Nov 2018 08:51:05 -0700 Subject: [PATCH 6/6] use np.asarray instead of .base --- pandas/_libs/tslibs/conversion.pyx | 15 +++++++-------- pandas/_libs/tslibs/fields.pyx | 8 ++++---- pandas/_libs/tslibs/period.pyx | 20 +++++++++++++++----- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 999955cb88d3c..312d9a618f5a0 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -76,7 +76,7 @@ cdef inline int64_t get_datetime64_nanos(object val) except? -1: @cython.boundscheck(False) @cython.wraparound(False) -def ensure_datetime64ns(arr: ndarray, copy: bint = True): +def ensure_datetime64ns(arr: ndarray, copy: bint=True): """ Ensure a np.datetime64 array has dtype specifically 'datetime64[ns]' @@ -123,7 +123,7 @@ def ensure_datetime64ns(arr: ndarray, copy: bint = True): return result -def ensure_timedelta64ns(arr: ndarray, copy: bint = True): +def ensure_timedelta64ns(arr: ndarray, copy: bint=True): """ Ensure a np.timedelta64 array has dtype specifically 'timedelta64[ns]' @@ -855,14 +855,13 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, localized : ndarray[int64_t] """ cdef: - ndarray[int64_t] trans + ndarray[int64_t] trans, result_a, result_b int64_t[:] deltas, idx_shifted + int64_t[:] result, dst_hours, idx_shifted_left, idx_shifted_right ndarray ambiguous_array Py_ssize_t i, idx, pos, pos_left, pos_right, ntrans, n = len(vals) int64_t *tdata int64_t v, left, right, val, v_left, v_right, delta_idx - int64_t[:] result, dst_hours, idx_shifted_left, idx_shifted_right - ndarray[int64_t] result_a, result_b npy_datetimestruct dts bint infer_dst = False, is_dst = False, fill = False bint shift = False, fill_nonexist = False @@ -877,7 +876,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, for i in range(n): v = vals[i] result[i] = _tz_convert_tzlocal_utc(v, tz, to_utc=True) - return result.base # `.base` to access underlying np.array + return np.asarray(result) if is_string_object(ambiguous): if ambiguous == 'infer': @@ -1031,7 +1030,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, object tz, object ambiguous=None, stamp = _render_tstamp(val) raise pytz.NonExistentTimeError(stamp) - return result.base # `.base` to access underlying np.array + return np.asarray(result) cdef inline bisect_right_i8(int64_t *data, int64_t val, Py_ssize_t n): @@ -1133,7 +1132,7 @@ def normalize_i8_timestamps(int64_t[:] stamps, object tz=None): dt64_to_dtstruct(stamps[i], &dts) result[i] = _normalized_stamp(&dts) - return result.base # .base to access underlying np.ndarray + return np.asarray(result) @cython.wraparound(False) diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index 25d8bf844b4f4..50aa22ae49d7c 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -132,7 +132,7 @@ def get_date_name_field(int64_t[:] dtindex, object field, object locale=None): else: raise ValueError("Field {field} not supported".format(field=field)) - return out.base # `.base` to access underlying np.array + return np.asarray(out) @cython.wraparound(False) @@ -376,7 +376,7 @@ def get_start_end_field(int64_t[:] dtindex, object field, else: raise ValueError("Field {field} not supported".format(field=field)) - return out.base.view(bool) # `.base` to access underlying np.array + return np.asarray(out).view(bool) @cython.wraparound(False) @@ -531,7 +531,7 @@ def get_date_field(int64_t[:] dtindex, object field): else: raise ValueError("Field %s not supported" % field) - return out.base # `.base` to access underlying np.array + return np.asarray(out) @cython.wraparound(False) @@ -642,7 +642,7 @@ def get_timedelta_field(int64_t[:] tdindex, object field): else: raise ValueError("Field {field} not supported".format(field=field)) - return out.base # `.base` to access underlying np.array + return np.asarray(out) cpdef isleapyear_arr(ndarray years): diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 7ca83e5937fe3..8514d1f13b3d8 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -977,6 +977,8 @@ cdef inline int month_to_quarter(int month): # ---------------------------------------------------------------------- # Period logic +@cython.wraparound(False) +@cython.boundscheck(False) def dt64arr_to_periodarr(int64_t[:] dtarr, int freq, tz=None): """ Convert array of datetime64 values (passed in as 'i8' dtype) to a set of @@ -1001,9 +1003,11 @@ def dt64arr_to_periodarr(int64_t[:] dtarr, int freq, tz=None): out[i] = get_period_ordinal(&dts, freq) else: out = localize_dt64arr_to_period(dtarr, freq, tz) - return out.base # .base to access underlying np.ndarray + return np.asarray(out) +@cython.wraparound(False) +@cython.boundscheck(False) def periodarr_to_dt64arr(int64_t[:] periodarr, int freq): """ Convert array to datetime64 values from a set of ordinals corresponding to @@ -1024,7 +1028,7 @@ def periodarr_to_dt64arr(int64_t[:] periodarr, int freq): continue out[i] = period_ordinal_to_dt64(periodarr[i], freq) - return out.base # .base to access underlying np.ndarray + return np.asarray(out) cpdef int64_t period_asfreq(int64_t ordinal, int freq1, int freq2, bint end): @@ -1102,6 +1106,8 @@ cdef inline int calc_week_end(int freq, int group) nogil: return freq - group +@cython.wraparound(False) +@cython.boundscheck(False) def period_asfreq_arr(int64_t[:] arr, int freq1, int freq2, bint end): """ Convert int64-array of period ordinals from one frequency to another, and @@ -1128,7 +1134,7 @@ def period_asfreq_arr(int64_t[:] arr, int freq1, int freq2, bint end): raise ValueError("Unable to convert to desired frequency.") result[i] = val - return result.base # .base to access underlying np.ndarray + return np.asarray(result) cpdef int64_t period_ordinal(int y, int m, int d, int h, int min, @@ -1389,7 +1395,7 @@ def get_period_field_arr(int code, int64_t[:] arr, int freq): continue out[i] = func(arr[i], freq) - return out.base # .base to access underlying np.ndarray + return np.asarray(out) cdef accessor _get_accessor_func(int code): @@ -1420,6 +1426,8 @@ cdef accessor _get_accessor_func(int code): return NULL +@cython.wraparound(False) +@cython.boundscheck(False) def extract_ordinals(object[:] values, freq): cdef: Py_ssize_t i, n = len(values) @@ -1449,7 +1457,7 @@ def extract_ordinals(object[:] values, freq): else: ordinals[i] = p.ordinal - return ordinals.base # .base to access underlying np.ndarray + return np.asarray(ordinals) def extract_freq(object[:] values): @@ -1473,6 +1481,8 @@ def extract_freq(object[:] values): # ----------------------------------------------------------------------- # period helpers +@cython.wraparound(False) +@cython.boundscheck(False) cdef int64_t[:] localize_dt64arr_to_period(int64_t[:] stamps, int freq, object tz): cdef: