Skip to content

Commit fd3a709

Browse files
writing some notes
1 parent 4b7342b commit fd3a709

File tree

2 files changed

+58
-48
lines changed

2 files changed

+58
-48
lines changed

traces/decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55
def _is_none(obj):
6-
return bool(obj is None)
6+
return obj is None
77

88

99
def ignorant(func):

traces/timeseries.py

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import itertools
1212
from queue import PriorityQueue
1313

14-
import sortedcontainers
1514
from infinity import inf
15+
from sortedcontainers import SortedDict
1616

1717
from . import histogram, operations, plot, utils
1818

@@ -47,9 +47,8 @@ class TimeSeries:
4747
"""
4848

4949
def __init__(self, data=None, default=None):
50-
self._d = sortedcontainers.SortedDict(data)
50+
self._d = SortedDict(data)
5151
self.default = default
52-
5352
self.getter_functions = {
5453
"previous": self._get_previous,
5554
"linear": self._get_linear_interpolate,
@@ -66,43 +65,34 @@ def __setstate__(self, state):
6665

6766
def __iter__(self):
6867
"""Iterate over sorted (time, value) pairs."""
69-
return iter(self._d.items())
68+
return iter(self.items())
7069

7170
def __bool__(self):
7271
return bool(self._d)
7372

7473
def is_empty(self):
7574
return len(self) == 0
7675

77-
@property
78-
def default(self):
79-
"""Return the default value of the time series."""
80-
return self._default
76+
@staticmethod
77+
def linear_interpolate(v0, v1, t):
78+
return v0 + t * (v1 - v0)
8179

82-
@default.setter
83-
def default(self, value):
84-
"""Set the default value of the time series."""
85-
self._default = value
80+
@staticmethod
81+
def scaled_time(t0, t1, time):
82+
return (time - t0) / (t1 - t0)
8683

8784
def _get_linear_interpolate(self, time):
8885
right_index = self._d.bisect_right(time)
8986
left_index = right_index - 1
90-
if left_index < 0:
87+
if left_index < 0: # before first measurement
9188
return self.default
92-
elif right_index == len(self._d):
93-
# right of last measurement
94-
return self.last_item()[1]
89+
elif right_index == len(self._d): # after last measurement
90+
return self.last_value()
9591
else:
9692
left_time, left_value = self._d.peekitem(left_index)
9793
right_time, right_value = self._d.peekitem(right_index)
98-
dt_interval = right_time - left_time
99-
dt_start = time - left_time
100-
if isinstance(dt_interval, datetime.timedelta):
101-
dt_interval = dt_interval.total_seconds()
102-
dt_start = dt_start.total_seconds()
103-
slope = (right_value - left_value) / dt_interval
104-
value = slope * dt_start + left_value
105-
return value
94+
t = self.scaled_time(left_time, right_time, time)
95+
return self.linear_interpolate(left_value, right_value, t)
10696

10797
def _get_previous(self, time):
10898
right_index = self._d.bisect_right(time)
@@ -197,6 +187,9 @@ def compact(self):
197187
same at all times, but repeated measurements are discarded.
198188
199189
"""
190+
191+
# todo: change to to_compact, do not modify in place. mark as deprecated
192+
200193
previous_value = object()
201194
redundant = []
202195
for time, value in self:
@@ -215,6 +208,9 @@ def exists(self):
215208
otherwise
216209
217210
"""
211+
212+
# todo: this needs a better name. mark exists as deprecated
213+
218214
result = TimeSeries(default=self.default is not None)
219215
for t, v in self:
220216
result[t] = v is not None
@@ -236,6 +232,9 @@ def remove_points_from_interval(self, start, end):
236232
[start:end].
237233
238234
"""
235+
236+
# todo: consider whether this key error should be suppressed
237+
239238
for s, _e, _v in self.iterperiods(start, end):
240239
with contextlib.suppress(KeyError):
241240
del self._d[s]
@@ -251,6 +250,8 @@ def __len__(self):
251250
def __repr__(self):
252251
"""A detailed string representation for debugging."""
253252

253+
# todo: show default in string representation
254+
254255
def format_item(item):
255256
return "{!r}: {!r}".format(*item)
256257

@@ -265,6 +266,8 @@ def __str__(self):
265266
266267
"""
267268

269+
# todo: show default in string representation
270+
268271
def format_item(item):
269272
return "{!s}: {!s}".format(*item)
270273

@@ -309,6 +312,9 @@ def iterintervals(self, n=2):
309312

310313
@staticmethod
311314
def _value_function(value):
315+
# todo: should this be able to take NotGiven, so that it would
316+
# be possible to filter for None explicitly?
317+
312318
# if value is None, don't filter
313319
if value is None:
314320

@@ -333,9 +339,11 @@ def iterperiods(self, start=None, end=None, value=None):
333339
"""This iterates over the periods (optionally, within a given time
334340
span) and yields (interval start, interval end, value) tuples.
335341
336-
TODO: add mask argument here.
337-
338342
"""
343+
344+
# todo: add mask argument here.
345+
# todo: check whether this can be simplified with newer SortedDict
346+
339347
start, end, mask = self._check_boundaries(
340348
start, end, allow_infinite=False
341349
)
@@ -647,7 +655,7 @@ def moving_average( # noqa: C901
647655

648656
@staticmethod
649657
def rebin(binned, key_function):
650-
result = sortedcontainers.SortedDict()
658+
result = SortedDict()
651659
for bin_start, value in binned.items():
652660
new_bin_start = key_function(bin_start)
653661
try:
@@ -668,10 +676,10 @@ def bin(
668676
transform="distribution",
669677
):
670678
if mask is not None and mask.is_empty():
671-
return sortedcontainers.SortedDict()
679+
return SortedDict()
672680

673681
if start is not None and start == end:
674-
return sortedcontainers.SortedDict()
682+
return SortedDict()
675683

676684
# use smaller if available
677685
if smaller:
@@ -685,7 +693,7 @@ def bin(
685693
start = utils.datetime_floor(start, unit=unit, n_units=n_units)
686694

687695
function = getattr(self, transform)
688-
result = sortedcontainers.SortedDict()
696+
result = SortedDict()
689697
dt_range = utils.datetime_range(start, end, unit, n_units=n_units)
690698
for bin_start, bin_end in utils.pairwise(dt_range):
691699
result[bin_start] = function(
@@ -898,11 +906,6 @@ def iter_merge(cls, timeseries_list):
898906
if not timeseries_list:
899907
return
900908

901-
# for ts in timeseries_list:
902-
# if ts.is_floating():
903-
# msg = "can't merge empty TimeSeries with no default value"
904-
# raise KeyError(msg)
905-
906909
# This function mostly wraps _iter_merge, the main point of
907910
# this is to deal with the case of tied times, where we only
908911
# want to yield the last list of values that occurs for any
@@ -940,14 +943,6 @@ def merge(cls, ts_list, compact=True, operation=None):
940943
result.set(t, value, compact=compact)
941944
return result
942945

943-
@staticmethod
944-
def csv_time_transform(raw):
945-
return datetime.datetime.strptime(raw, "%Y-%m-%d %H:%M:%S")
946-
947-
@staticmethod
948-
def csv_value_transform(raw):
949-
return str(raw)
950-
951946
@classmethod
952947
def from_csv(
953948
cls,
@@ -959,11 +954,15 @@ def from_csv(
959954
skip_header=True,
960955
default=None,
961956
):
957+
# todo: allowing skipping n header rows
958+
962959
# use default on class if not given
963960
if time_transform is None:
964-
time_transform = cls.csv_time_transform
961+
time_transform = lambda s: datetime.datetime.strptime(
962+
s, "%Y-%m-%d %H:%M:%S"
963+
)
965964
if value_transform is None:
966-
value_transform = cls.csv_value_transform
965+
value_transform = lambda s: s
967966

968967
result = cls(default=default)
969968
with open(filename) as infile:
@@ -976,7 +975,7 @@ def from_csv(
976975
result[time] = value
977976
return result
978977

979-
def operation(self, other, function, **kwargs):
978+
def operation(self, other, function, default=None):
980979
"""Calculate "elementwise" operation either between this TimeSeries
981980
and another one, i.e.
982981
@@ -992,7 +991,11 @@ def operation(self, other, function, **kwargs):
992991
constant, the measurement times will not change.
993992
994993
"""
995-
result = TimeSeries(**kwargs)
994+
995+
# todo: consider the best way to deal with default, and make
996+
# consistent with other methods. check to_bool maybe
997+
998+
result = TimeSeries(default=default)
996999
if isinstance(other, TimeSeries):
9971000
for time, value in self:
9981001
result[time] = function(value, other[time])
@@ -1047,6 +1050,10 @@ def threshold(self, value, inclusive=False):
10471050
inclusive=True).
10481051
10491052
"""
1053+
1054+
# todo: this seems like it's wrong... make a test to check (and fix if so!)
1055+
# todo: deal with default
1056+
10501057
if inclusive:
10511058

10521059
def function(x, y):
@@ -1061,6 +1068,9 @@ def function(x, y):
10611068

10621069
def sum(self, other):
10631070
"""sum(x, y) = x(t) + y(t)."""
1071+
1072+
# todo: better consistency and documentation about when Nones are ignored
1073+
10641074
return TimeSeries.merge(
10651075
[self, other], operation=operations.ignorant_sum
10661076
)

0 commit comments

Comments
 (0)