rangekeeper 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rangekeeper/__init__.py +40 -0
- rangekeeper/api.py +88 -0
- rangekeeper/distribution.py +269 -0
- rangekeeper/dynamics/__init__.py +6 -0
- rangekeeper/dynamics/black_swan.py +77 -0
- rangekeeper/dynamics/cyclicality.py +414 -0
- rangekeeper/dynamics/market.py +328 -0
- rangekeeper/dynamics/noise.py +34 -0
- rangekeeper/dynamics/trend.py +79 -0
- rangekeeper/dynamics/volatility.py +140 -0
- rangekeeper/extrapolation.py +100 -0
- rangekeeper/flux.py +733 -0
- rangekeeper/graph.py +144 -0
- rangekeeper/measure.py +138 -0
- rangekeeper/models/__init__.py +5 -0
- rangekeeper/models/deterministic.py +143 -0
- rangekeeper/models/flexible.py +177 -0
- rangekeeper/models/linear.py +144 -0
- rangekeeper/models/linear_graph.py +171 -0
- rangekeeper/models/probabilistic.py +157 -0
- rangekeeper/periodicity.py +236 -0
- rangekeeper/policy.py +20 -0
- rangekeeper/projection.py +248 -0
- rangekeeper/segmentation.py +220 -0
- rangekeeper/setup.py +14 -0
- rangekeeper/space.py +42 -0
- rangekeeper/span.py +226 -0
- rangekeeper-0.1.0.dist-info/LICENSE +373 -0
- rangekeeper-0.1.0.dist-info/METADATA +32 -0
- rangekeeper-0.1.0.dist-info/RECORD +31 -0
- rangekeeper-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from typing import Union, Optional
|
|
5
|
+
|
|
6
|
+
import aenum
|
|
7
|
+
import enum
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import dateutil.relativedelta
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
import rangekeeper as rk
|
|
13
|
+
|
|
14
|
+
class Type(enum.Enum):
|
|
15
|
+
YEAR = 'A-DEC' # Anchored on end of December
|
|
16
|
+
QUARTER = 'Q-DEC' # Anchored on end of March, June, September, & December
|
|
17
|
+
MONTH = 'M' # Anchored on end-of-month
|
|
18
|
+
SEMIMONTH = 'SM' # Twice-monthly periods anchored on mid-month and end-of-month dates
|
|
19
|
+
WEEK = 'W' # Anchored on Sundays
|
|
20
|
+
DAY = 'D' # Daily
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def from_value(value: str):
|
|
24
|
+
if value == 'A-DEC':
|
|
25
|
+
return Type.YEAR
|
|
26
|
+
if value == 'Q-DEC':
|
|
27
|
+
return Type.QUARTER
|
|
28
|
+
if value == 'M':
|
|
29
|
+
return Type.MONTH
|
|
30
|
+
if value == 'SM':
|
|
31
|
+
return Type.SEMIMONTH
|
|
32
|
+
if value == 'W':
|
|
33
|
+
return Type.WEEK
|
|
34
|
+
if value == 'D':
|
|
35
|
+
return Type.DAY
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def include_date(
|
|
39
|
+
date: pd.Timestamp,
|
|
40
|
+
duration: Type) -> pd.Period:
|
|
41
|
+
"""
|
|
42
|
+
Returns a pd.Period that encompasses the given date
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return pd.Period(year=date.year, month=date.month, day=date.day, freq=duration.value)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def period_index(
|
|
49
|
+
include_start: pd.Timestamp,
|
|
50
|
+
period_type: Type,
|
|
51
|
+
bound: Union[pd.Timestamp, int] = None) -> pd.PeriodIndex:
|
|
52
|
+
"""
|
|
53
|
+
Returns a pd.PeriodIndex from a start date with periods of given duration.
|
|
54
|
+
Either an end date, or number of periods must be given to bound the sequence.
|
|
55
|
+
|
|
56
|
+
:param include_start: pd.Timestamp start date
|
|
57
|
+
:param period_type: Period frequency
|
|
58
|
+
:param bound: A terminating condition; either a pd.Timestamp end date or a (integer) number of periods
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(bound, pd.Timestamp):
|
|
61
|
+
return pd.period_range(
|
|
62
|
+
start=include_start,
|
|
63
|
+
end=bound,
|
|
64
|
+
freq=period_type.value,
|
|
65
|
+
name='periods')
|
|
66
|
+
elif isinstance(bound, int):
|
|
67
|
+
return pd.period_range(
|
|
68
|
+
start=include_start,
|
|
69
|
+
periods=bound,
|
|
70
|
+
freq=period_type.value,
|
|
71
|
+
name='periods')
|
|
72
|
+
|
|
73
|
+
def offset_periodindex(
|
|
74
|
+
index: pd.PeriodIndex,
|
|
75
|
+
offset_start: Optional[int] = None,
|
|
76
|
+
offset_end: Optional[int] = None) -> pd.PeriodIndex:
|
|
77
|
+
"""
|
|
78
|
+
Returns a pd.PeriodIndex with periods offset by given number of periods (of type given by the index)
|
|
79
|
+
"""
|
|
80
|
+
start = index[0].to_timestamp()
|
|
81
|
+
if offset_start is not None:
|
|
82
|
+
start = offset_date(
|
|
83
|
+
date=period_index[0].to_timestamp(),
|
|
84
|
+
period_type=from_value(index.freq),
|
|
85
|
+
num_periods=offset_start)
|
|
86
|
+
end = index[-1].to_timestamp()
|
|
87
|
+
if offset_end is not None:
|
|
88
|
+
end = offset_date(
|
|
89
|
+
date=index[-1].to_timestamp(),
|
|
90
|
+
period_type=from_value(index.freq),
|
|
91
|
+
num_periods=offset_end)
|
|
92
|
+
return period_index(
|
|
93
|
+
include_start=start,
|
|
94
|
+
period_type=from_value(index.freq),
|
|
95
|
+
bound=end)
|
|
96
|
+
|
|
97
|
+
def to_span(period_index: pd.PeriodIndex):
|
|
98
|
+
"""
|
|
99
|
+
Returns a Span encompassing a pd.PeriodIndex
|
|
100
|
+
"""
|
|
101
|
+
return rk.span.Span(
|
|
102
|
+
start_date=period_index[0].to_timestamp(how='start'),
|
|
103
|
+
end_date=period_index[-1].to_timestamp(how='end'))
|
|
104
|
+
|
|
105
|
+
def single_period_index(period: pd.Period) -> pd.PeriodIndex:
|
|
106
|
+
return pd.period_range(
|
|
107
|
+
start=period,
|
|
108
|
+
periods=1)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def to_datestamps(
|
|
112
|
+
period_index: pd.PeriodIndex,
|
|
113
|
+
end: bool = True) -> pd.DatetimeIndex:
|
|
114
|
+
"""
|
|
115
|
+
Returns a pd.DatetimeIndex from a pd.PeriodIndex with Datetimes being Dates
|
|
116
|
+
:param end: If True, the end of the period is used. If False, the start of the period is used.
|
|
117
|
+
"""
|
|
118
|
+
if end:
|
|
119
|
+
timestamps = period_index.to_timestamp(how='end')
|
|
120
|
+
else:
|
|
121
|
+
timestamps = period_index.to_timestamp(how='start')
|
|
122
|
+
datestamps = timestamps.date
|
|
123
|
+
return pd.DatetimeIndex(datestamps)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def periods_per_year(period_type: Type) -> int:
|
|
127
|
+
return {
|
|
128
|
+
Type.YEAR: 1,
|
|
129
|
+
Type.QUARTER: 4,
|
|
130
|
+
Type.MONTH: 12,
|
|
131
|
+
Type.SEMIMONTH: 24,
|
|
132
|
+
Type.WEEK: 52,
|
|
133
|
+
Type.DAY: 365
|
|
134
|
+
}.get(period_type, None)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def offset_date(
|
|
138
|
+
date: pd.Timestamp,
|
|
139
|
+
period_type: Type = Type.DAY,
|
|
140
|
+
num_periods: int = 1) -> pd.Timestamp:
|
|
141
|
+
"""
|
|
142
|
+
Offsets a date by a given number of periods
|
|
143
|
+
:param date:
|
|
144
|
+
:type date:
|
|
145
|
+
:param period_type:
|
|
146
|
+
:type period_type:
|
|
147
|
+
:param num_periods:
|
|
148
|
+
:type num_periods:
|
|
149
|
+
:return:
|
|
150
|
+
:rtype:
|
|
151
|
+
"""
|
|
152
|
+
if period_type.value == Type.YEAR.value:
|
|
153
|
+
return date + pd.DateOffset(years=num_periods)
|
|
154
|
+
elif period_type.value == Type.QUARTER.value:
|
|
155
|
+
return date + pd.DateOffset(months=3 * num_periods)
|
|
156
|
+
elif period_type.value == Type.MONTH.value:
|
|
157
|
+
return date + pd.DateOffset(months=num_periods)
|
|
158
|
+
elif period_type.value == Type.WEEK.value:
|
|
159
|
+
return date + pd.DateOffset(weeks=num_periods)
|
|
160
|
+
elif period_type.value == Type.DAY.value:
|
|
161
|
+
return date + pd.DateOffset(days=num_periods)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def duration(
|
|
165
|
+
start_date: pd.Timestamp,
|
|
166
|
+
end_date: pd.Timestamp,
|
|
167
|
+
period_type: Type,
|
|
168
|
+
inclusive: bool = False) -> int:
|
|
169
|
+
"""
|
|
170
|
+
Returns the whole integer (i.e. no remainder) number of periods between
|
|
171
|
+
given dates.
|
|
172
|
+
If inclusive is True, the end_date is included in the calculation.
|
|
173
|
+
"""
|
|
174
|
+
calc_end_date = end_date
|
|
175
|
+
if inclusive:
|
|
176
|
+
calc_end_date = offset_date(
|
|
177
|
+
date=end_date,
|
|
178
|
+
period_type=Type.DAY,
|
|
179
|
+
num_periods=1)
|
|
180
|
+
|
|
181
|
+
delta = dateutil.relativedelta.relativedelta(calc_end_date, start_date)
|
|
182
|
+
|
|
183
|
+
if period_type.value == Type.YEAR.value:
|
|
184
|
+
result = delta.years
|
|
185
|
+
elif period_type.value == Type.QUARTER.value:
|
|
186
|
+
result = (delta.years * 4) + math.floor(delta.months / 3)
|
|
187
|
+
elif period_type.value == Type.MONTH.value:
|
|
188
|
+
result = (delta.years * 12) + delta.months
|
|
189
|
+
elif period_type.value == Type.SEMIMONTH.value:
|
|
190
|
+
result = (delta.years * 24) + delta.months * 2
|
|
191
|
+
elif period_type.value == Type.WEEK.value:
|
|
192
|
+
result = math.floor((calc_end_date - start_date).days / 7)
|
|
193
|
+
elif period_type.value == Type.DAY.value:
|
|
194
|
+
result = (calc_end_date - start_date).days
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError('Unsupported period type: {0}'.format(period_type))
|
|
197
|
+
|
|
198
|
+
def direction(rd: dateutil.relativedelta.relativedelta) -> int:
|
|
199
|
+
"""
|
|
200
|
+
Check whether a relativedelta object is negative
|
|
201
|
+
From https://stackoverflow.com/a/57906103/10964780
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
datetime.min + rd
|
|
205
|
+
return 1
|
|
206
|
+
except OverflowError:
|
|
207
|
+
return -1
|
|
208
|
+
|
|
209
|
+
return result * direction(delta)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def to_range_index(
|
|
213
|
+
period_index: pd.PeriodIndex,
|
|
214
|
+
start_period: pd.Period = None,
|
|
215
|
+
end_period: pd.Period = None) -> pd.RangeIndex:
|
|
216
|
+
"""
|
|
217
|
+
Returns a RangeIndex that maps a PeriodIndex to a range of integers.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
index_offset = None
|
|
221
|
+
if start_period is None:
|
|
222
|
+
start_period = period_index[0]
|
|
223
|
+
if end_period is None:
|
|
224
|
+
end_period = period_index[-1]
|
|
225
|
+
bounds_index = pd.period_range(
|
|
226
|
+
start=start_period,
|
|
227
|
+
end=end_period,
|
|
228
|
+
freq=period_index.freq)
|
|
229
|
+
|
|
230
|
+
for i in range(bounds_index.size):
|
|
231
|
+
if period_index.__contains__(bounds_index[i]):
|
|
232
|
+
index_offset = i
|
|
233
|
+
break
|
|
234
|
+
else:
|
|
235
|
+
continue
|
|
236
|
+
return pd.RangeIndex(start=index_offset, stop=period_index.size + index_offset, step=1)
|
rangekeeper/policy.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Callable, List, Any, Dict
|
|
3
|
+
|
|
4
|
+
import rangekeeper as rk
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Policy:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
condition: Callable[[rk.flux.Flow], List[bool]],
|
|
11
|
+
action: Callable[[object, List[bool]], object]):
|
|
12
|
+
self.condition = condition
|
|
13
|
+
self.action = action
|
|
14
|
+
|
|
15
|
+
def execute(
|
|
16
|
+
self,
|
|
17
|
+
args: tuple) -> object:
|
|
18
|
+
state, model = args # This is done to assist multiprocessing
|
|
19
|
+
decision = self.condition(state)
|
|
20
|
+
return self.action(model, decision)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
import typing
|
|
5
|
+
from typing import Optional, Union, Tuple
|
|
6
|
+
|
|
7
|
+
import enum
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import scipy.stats as ss
|
|
11
|
+
|
|
12
|
+
import rangekeeper as rk
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Padding(enum.Enum):
|
|
16
|
+
"""
|
|
17
|
+
Enum for padding types.
|
|
18
|
+
"""
|
|
19
|
+
NIL = 'nil' # Zero out factors or densities until bound
|
|
20
|
+
UNITIZE = 'unitize' # Repeat unit factor or density until bound
|
|
21
|
+
EXTEND = 'extend' # 'Repeat last factor or density until bound'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Projection:
|
|
25
|
+
sequence: pd.PeriodIndex
|
|
26
|
+
bounds: Tuple[pd.Period, pd.Period]
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
sequence: pd.PeriodIndex,
|
|
31
|
+
bounds: Tuple[pd.Period, pd.Period] = None):
|
|
32
|
+
self.sequence = sequence
|
|
33
|
+
|
|
34
|
+
if bounds is None:
|
|
35
|
+
self.bounds = (sequence[0], sequence[-1])
|
|
36
|
+
else:
|
|
37
|
+
self.bounds = bounds
|
|
38
|
+
|
|
39
|
+
# def __init__(
|
|
40
|
+
# self,
|
|
41
|
+
# sequence: Union[pd.RangeIndex, pd.PeriodIndex],
|
|
42
|
+
# bounds: Union[Tuple[int, int], Tuple[pd.Period, pd.Period]] = None):
|
|
43
|
+
#
|
|
44
|
+
# if isinstance(sequence, pd.RangeIndex):
|
|
45
|
+
# if bounds is None:
|
|
46
|
+
# self.sequence = sequence
|
|
47
|
+
# self.bounds = (0, len(sequence))
|
|
48
|
+
# elif isinstance(bounds[0], int):
|
|
49
|
+
# self.sequence = sequence
|
|
50
|
+
# self.bounds = bounds
|
|
51
|
+
# else:
|
|
52
|
+
# raise ValueError(f"sequence and bounds must match in type (int or pd.Period)")
|
|
53
|
+
#
|
|
54
|
+
# elif isinstance(sequence, pd.PeriodIndex):
|
|
55
|
+
# if bounds is None:
|
|
56
|
+
# self.sequence = periodicity.to_range_index(
|
|
57
|
+
# period_index=sequence)
|
|
58
|
+
# self.bounds = (0, len(sequence))
|
|
59
|
+
# elif isinstance(bounds[0], pd.Period):
|
|
60
|
+
# self.sequence = periodicity.to_range_index(
|
|
61
|
+
# period_index=sequence,
|
|
62
|
+
# start=bounds[0],
|
|
63
|
+
# end=bounds[1])
|
|
64
|
+
# bounds_range = pd.period_range(
|
|
65
|
+
# start=bounds[0],
|
|
66
|
+
# end=bounds[1],
|
|
67
|
+
# freq=sequence.freq)
|
|
68
|
+
# self.bounds = (0, len(bounds_range))
|
|
69
|
+
# else:
|
|
70
|
+
# raise ValueError(f"sequence and bounds must match in type (int or pd.Period)")
|
|
71
|
+
#
|
|
72
|
+
# else:
|
|
73
|
+
# raise ValueError(f"sequence must be of type (pd.RangeIndex or pd.PeriodIndex)")
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _pad(
|
|
77
|
+
value: float,
|
|
78
|
+
length: int,
|
|
79
|
+
type: Optional[Padding]) -> np.ndarray:
|
|
80
|
+
if type == Padding.NIL:
|
|
81
|
+
return np.zeros(shape=length)
|
|
82
|
+
elif type == Padding.UNITIZE:
|
|
83
|
+
return np.ones(shape=length)
|
|
84
|
+
elif type == Padding.EXTEND:
|
|
85
|
+
return np.full(shape=length, fill_value=value)
|
|
86
|
+
elif type is None:
|
|
87
|
+
return np.empty(shape=0)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Extrapolation(Projection):
|
|
91
|
+
form: rk.extrapolation.Form
|
|
92
|
+
padding: Tuple[Optional[Padding], Optional[Padding]]
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
form: rk.extrapolation.Form,
|
|
97
|
+
sequence: pd.PeriodIndex,
|
|
98
|
+
bounds: Tuple[pd.Period, pd.Period] = None,
|
|
99
|
+
padding: Tuple[Padding, Padding] = None):
|
|
100
|
+
super().__init__(
|
|
101
|
+
sequence=sequence,
|
|
102
|
+
bounds=bounds)
|
|
103
|
+
self.form = form
|
|
104
|
+
|
|
105
|
+
if padding is None:
|
|
106
|
+
if self.bounds[0] == sequence[0] and self.bounds[1] == sequence[-1]:
|
|
107
|
+
self.padding = (None, None)
|
|
108
|
+
else:
|
|
109
|
+
raise ValueError('Error: Since the bounds do not match sequence start & end,'
|
|
110
|
+
' padding must be specified.')
|
|
111
|
+
else:
|
|
112
|
+
self.padding = padding
|
|
113
|
+
|
|
114
|
+
def terms(self) -> pd.Series:
|
|
115
|
+
terms = self.form.terms(
|
|
116
|
+
sequence=rk.periodicity.to_range_index(period_index=self.sequence))
|
|
117
|
+
left_length = self.sequence[0] - self.bounds[0]
|
|
118
|
+
right_length = self.bounds[1] - self.sequence[-1]
|
|
119
|
+
left = self._pad(
|
|
120
|
+
value=terms[0],
|
|
121
|
+
length=left_length.n,
|
|
122
|
+
type=self.padding[0])
|
|
123
|
+
right = self._pad(
|
|
124
|
+
value=terms[-1],
|
|
125
|
+
length=right_length.n,
|
|
126
|
+
type=self.padding[1])
|
|
127
|
+
data = np.concatenate((left, terms, right))
|
|
128
|
+
|
|
129
|
+
return pd.Series(
|
|
130
|
+
data=data,
|
|
131
|
+
index=rk.periodicity.to_datestamps(
|
|
132
|
+
rk.periodicity.period_index(
|
|
133
|
+
include_start=self.bounds[0].to_timestamp(how='start'),
|
|
134
|
+
period_type=rk.periodicity.from_value(self.sequence.freq),
|
|
135
|
+
bound=self.bounds[1].to_timestamp(how='start'))))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Distribution(Projection):
|
|
139
|
+
form: rk.distribution.Form
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
form: rk.distribution.Form,
|
|
144
|
+
sequence: pd.PeriodIndex,
|
|
145
|
+
bounds: Tuple[pd.Period, pd.Period] = None):
|
|
146
|
+
super().__init__(
|
|
147
|
+
sequence=sequence)
|
|
148
|
+
self.form = form
|
|
149
|
+
|
|
150
|
+
if bounds is None:
|
|
151
|
+
self.bounds = (sequence[0], sequence[-1])
|
|
152
|
+
elif (bounds[0] > sequence[0]) or (bounds[1] < sequence[-1]):
|
|
153
|
+
raise ValueError('Error: Bounds cannot be within the sequence for '
|
|
154
|
+
'Distribution Projections')
|
|
155
|
+
else:
|
|
156
|
+
self.bounds = bounds
|
|
157
|
+
|
|
158
|
+
self._parameters = self._seq_to_params()
|
|
159
|
+
|
|
160
|
+
left_length = self.sequence[0] - self.bounds[0]
|
|
161
|
+
right_length = self.bounds[1] - self.sequence[-1]
|
|
162
|
+
self._left_padding = self._pad(
|
|
163
|
+
value=0,
|
|
164
|
+
length=left_length.n,
|
|
165
|
+
type=Padding.NIL)
|
|
166
|
+
self._right_padding = self._pad(
|
|
167
|
+
value=0,
|
|
168
|
+
length=right_length.n,
|
|
169
|
+
type=Padding.NIL)
|
|
170
|
+
|
|
171
|
+
# self._index = periodicity.period_index(
|
|
172
|
+
# include_start=self.bounds[0].to_timestamp(),
|
|
173
|
+
# period_type=periodicity.from_value(self.sequence.freq),
|
|
174
|
+
# bound=self.bounds[1].to_timestamp())
|
|
175
|
+
|
|
176
|
+
def _seq_to_params(self) -> [float]:
|
|
177
|
+
range_index = rk.periodicity.to_range_index(
|
|
178
|
+
period_index=self.sequence)
|
|
179
|
+
range_index = range_index.insert(
|
|
180
|
+
loc=len(range_index),
|
|
181
|
+
item=range_index[-1] + 1)
|
|
182
|
+
max = range_index[-1]
|
|
183
|
+
return [index / max for index in range_index]
|
|
184
|
+
|
|
185
|
+
def interval_density(self) -> pd.Series:
|
|
186
|
+
densities = self.form.interval_density(parameters=self._parameters)
|
|
187
|
+
data = np.concatenate((self._left_padding, densities, self._right_padding))
|
|
188
|
+
|
|
189
|
+
return pd.Series(
|
|
190
|
+
data=data,
|
|
191
|
+
index=rk.periodicity.to_datestamps(
|
|
192
|
+
rk.periodicity.period_index(
|
|
193
|
+
include_start=self.bounds[0].to_timestamp(),
|
|
194
|
+
period_type=rk.periodicity.from_value(self.sequence.freq),
|
|
195
|
+
bound=self.bounds[1].to_timestamp())))
|
|
196
|
+
|
|
197
|
+
def cumulative_density(self) -> [float]:
|
|
198
|
+
densities = self.form.cumulative_density(parameters=self._parameters)
|
|
199
|
+
data = np.concatenate((self._left_padding, densities, self._right_padding))
|
|
200
|
+
|
|
201
|
+
period_index = rk.periodicity.period_index(
|
|
202
|
+
include_start=self.bounds[0].to_timestamp(),
|
|
203
|
+
period_type=rk.periodicity.from_value(self.sequence.freq),
|
|
204
|
+
bound=self.bounds[1].to_timestamp())
|
|
205
|
+
datestamp_index = rk.periodicity.to_datestamps(period_index=period_index)
|
|
206
|
+
datestamp_index = datestamp_index.insert(loc=0, item=self.bounds[0].start_time)
|
|
207
|
+
|
|
208
|
+
return pd.Series(
|
|
209
|
+
data=data,
|
|
210
|
+
index=datestamp_index)
|
|
211
|
+
|
|
212
|
+
# class DistributiveInterpolation(Interpolation):
|
|
213
|
+
# """
|
|
214
|
+
# A projection that apportions values over a range, according to a specified
|
|
215
|
+
# distribution type.
|
|
216
|
+
# """
|
|
217
|
+
# dist: distribution.Form
|
|
218
|
+
#
|
|
219
|
+
# def __init__(
|
|
220
|
+
# self,
|
|
221
|
+
# dist: distribution.Form,
|
|
222
|
+
# sequence: Union[pd.RangeIndex, pd.PeriodIndex],
|
|
223
|
+
# bounds: Union[Tuple[int, int], Tuple[pd.Period, pd.Period]]):
|
|
224
|
+
# super().__init__(
|
|
225
|
+
# sequence=sequence,
|
|
226
|
+
# bounds=bounds)
|
|
227
|
+
# self.dist = dist
|
|
228
|
+
|
|
229
|
+
# def map(
|
|
230
|
+
# self,
|
|
231
|
+
# left_padding: Padding = None,
|
|
232
|
+
# right_padding: Padding = None) -> pd.Series:
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# if isinstance(self.sequence, pd.RangeIndex):
|
|
236
|
+
# if isinstance(self, Extrapolation):
|
|
237
|
+
# factors = self.factor()
|
|
238
|
+
# left = self.pad(
|
|
239
|
+
# value=factors[0],
|
|
240
|
+
# length=self.sequence[0] - self.bounds[0],
|
|
241
|
+
# type=left_padding)
|
|
242
|
+
# right = self.pad(
|
|
243
|
+
# value=factors[-1],
|
|
244
|
+
# length=self.bounds[1] - self.sequence[-1],
|
|
245
|
+
# type=right_padding)
|
|
246
|
+
# return pd.Series(
|
|
247
|
+
# data=np.concatenate((left, factors, right)),
|
|
248
|
+
# index=range(self.bounds[0], self.bounds[1]))
|