|
8 | 8 | from pandas._libs.tslib import NaT, iNaT
|
9 | 9 | from pandas._libs.tslibs.period import (
|
10 | 10 | Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX,
|
11 |
| - get_period_field_arr, period_asfreq_arr) |
| 11 | + get_period_field_arr, period_asfreq_arr, _quarter_to_myear) |
12 | 12 | from pandas._libs.tslibs import period as libperiod
|
13 | 13 | from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
|
14 | 14 | from pandas._libs.tslibs.fields import isleapyear_arr
|
|
19 | 19 | from pandas.core.dtypes.common import (
|
20 | 20 | is_integer_dtype, is_float_dtype, is_period_dtype)
|
21 | 21 | from pandas.core.dtypes.dtypes import PeriodDtype
|
| 22 | +from pandas.core.dtypes.generic import ABCSeries |
| 23 | + |
| 24 | +import pandas.core.common as com |
22 | 25 |
|
23 | 26 | from pandas.tseries import frequencies
|
24 | 27 | from pandas.tseries.offsets import Tick, DateOffset
|
@@ -157,6 +160,25 @@ def _from_ordinals(cls, values, freq=None):
|
157 | 160 | result._freq = Period._maybe_convert_freq(freq)
|
158 | 161 | return result
|
159 | 162 |
|
| 163 | + @classmethod |
| 164 | + def _generate_range(cls, start, end, periods, freq, fields): |
| 165 | + if freq is not None: |
| 166 | + freq = Period._maybe_convert_freq(freq) |
| 167 | + |
| 168 | + field_count = len(fields) |
| 169 | + if com._count_not_none(start, end) > 0: |
| 170 | + if field_count > 0: |
| 171 | + raise ValueError('Can either instantiate from fields ' |
| 172 | + 'or endpoints, but not both') |
| 173 | + subarr, freq = _get_ordinal_range(start, end, periods, freq) |
| 174 | + elif field_count > 0: |
| 175 | + subarr, freq = _range_from_fields(freq=freq, **fields) |
| 176 | + else: |
| 177 | + raise ValueError('Not enough parameters to construct ' |
| 178 | + 'Period range') |
| 179 | + |
| 180 | + return subarr, freq |
| 181 | + |
160 | 182 | # --------------------------------------------------------------------
|
161 | 183 | # Vectorized analogues of Period properties
|
162 | 184 |
|
@@ -371,3 +393,102 @@ def _add_comparison_methods(cls):
|
371 | 393 |
|
372 | 394 |
|
373 | 395 | PeriodArrayMixin._add_comparison_methods()
|
| 396 | + |
| 397 | + |
| 398 | +# ------------------------------------------------------------------- |
| 399 | +# Constructor Helpers |
| 400 | + |
| 401 | +def _get_ordinal_range(start, end, periods, freq, mult=1): |
| 402 | + if com._count_not_none(start, end, periods) != 2: |
| 403 | + raise ValueError('Of the three parameters: start, end, and periods, ' |
| 404 | + 'exactly two must be specified') |
| 405 | + |
| 406 | + if freq is not None: |
| 407 | + _, mult = frequencies.get_freq_code(freq) |
| 408 | + |
| 409 | + if start is not None: |
| 410 | + start = Period(start, freq) |
| 411 | + if end is not None: |
| 412 | + end = Period(end, freq) |
| 413 | + |
| 414 | + is_start_per = isinstance(start, Period) |
| 415 | + is_end_per = isinstance(end, Period) |
| 416 | + |
| 417 | + if is_start_per and is_end_per and start.freq != end.freq: |
| 418 | + raise ValueError('start and end must have same freq') |
| 419 | + if (start is NaT or end is NaT): |
| 420 | + raise ValueError('start and end must not be NaT') |
| 421 | + |
| 422 | + if freq is None: |
| 423 | + if is_start_per: |
| 424 | + freq = start.freq |
| 425 | + elif is_end_per: |
| 426 | + freq = end.freq |
| 427 | + else: # pragma: no cover |
| 428 | + raise ValueError('Could not infer freq from start/end') |
| 429 | + |
| 430 | + if periods is not None: |
| 431 | + periods = periods * mult |
| 432 | + if start is None: |
| 433 | + data = np.arange(end.ordinal - periods + mult, |
| 434 | + end.ordinal + 1, mult, |
| 435 | + dtype=np.int64) |
| 436 | + else: |
| 437 | + data = np.arange(start.ordinal, start.ordinal + periods, mult, |
| 438 | + dtype=np.int64) |
| 439 | + else: |
| 440 | + data = np.arange(start.ordinal, end.ordinal + 1, mult, dtype=np.int64) |
| 441 | + |
| 442 | + return data, freq |
| 443 | + |
| 444 | + |
| 445 | +def _range_from_fields(year=None, month=None, quarter=None, day=None, |
| 446 | + hour=None, minute=None, second=None, freq=None): |
| 447 | + if hour is None: |
| 448 | + hour = 0 |
| 449 | + if minute is None: |
| 450 | + minute = 0 |
| 451 | + if second is None: |
| 452 | + second = 0 |
| 453 | + if day is None: |
| 454 | + day = 1 |
| 455 | + |
| 456 | + ordinals = [] |
| 457 | + |
| 458 | + if quarter is not None: |
| 459 | + if freq is None: |
| 460 | + freq = 'Q' |
| 461 | + base = frequencies.FreqGroup.FR_QTR |
| 462 | + else: |
| 463 | + base, mult = frequencies.get_freq_code(freq) |
| 464 | + if base != frequencies.FreqGroup.FR_QTR: |
| 465 | + raise AssertionError("base must equal FR_QTR") |
| 466 | + |
| 467 | + year, quarter = _make_field_arrays(year, quarter) |
| 468 | + for y, q in compat.zip(year, quarter): |
| 469 | + y, m = _quarter_to_myear(y, q, freq) |
| 470 | + val = libperiod.period_ordinal(y, m, 1, 1, 1, 1, 0, 0, base) |
| 471 | + ordinals.append(val) |
| 472 | + else: |
| 473 | + base, mult = frequencies.get_freq_code(freq) |
| 474 | + arrays = _make_field_arrays(year, month, day, hour, minute, second) |
| 475 | + for y, mth, d, h, mn, s in compat.zip(*arrays): |
| 476 | + ordinals.append(libperiod.period_ordinal( |
| 477 | + y, mth, d, h, mn, s, 0, 0, base)) |
| 478 | + |
| 479 | + return np.array(ordinals, dtype=np.int64), freq |
| 480 | + |
| 481 | + |
| 482 | +def _make_field_arrays(*fields): |
| 483 | + length = None |
| 484 | + for x in fields: |
| 485 | + if isinstance(x, (list, np.ndarray, ABCSeries)): |
| 486 | + if length is not None and len(x) != length: |
| 487 | + raise ValueError('Mismatched Period array lengths') |
| 488 | + elif length is None: |
| 489 | + length = len(x) |
| 490 | + |
| 491 | + arrays = [np.asarray(x) if isinstance(x, (np.ndarray, list, ABCSeries)) |
| 492 | + else np.repeat(x, length) for x in fields] |
| 493 | + |
| 494 | + return arrays |
0 commit comments