I have made a list that uses a list of dates as an index, like so:
>>> import datedlist
>>> import datetime
>>> dates = [datetime.date(2012,1,x) for x in range(2,6)]
>>> values = range(4,8)
>>> dates
[datetime.date(2012, 1, 2), datetime.date(2012, 1, 3), datetime.date(2012, 1, 4), datetime.date(2012, 1, 5)]
>>> dl = datedlist.DatedList(values, dates)
>>> dl
[4, 5, 6, 7]
>>> dl[datetime.date(2012,1,3)]
5
So far all is well, but I also want to be able to use slicing (not extended slicing), like so (the following does not work - it is the result I want):
>>> datedlist[datetime.date(2012,1,3):datetime.date(2012,1,4)]
[5, 6]
Here is my attempt at this (which obviously doesn't work):
class DatedList(list):
def __init__(self, values, dates):
self.dates = dates
list.__init__(self, values)
def __getitem__(self, date):
if isinstance(date, slice):
start = self.dates.index(slice[0])
end = self.dates.index(slice[1])
return [list.__getitem__(self, index) for index in range(start, end)]
elif isinstance( date, datetime.date ) :
index = self.dates.index(date)
return list.__getitem__(self, index)
elif isinstance(date, int):
if date < 0:
date += len(self)
if date >= len(self):
raise IndexError, "index out of range {}".format(date)
return list.__getitem__(self, date)
else:
raise TypeError, "Invalid argument type."
The slice[0] and slice[1] are only there to explain my intention. The isinstance(date, int) is only there for debugging - will be removed for production code.
Here is the question: how can I implement slicing that uses datetime.date objects as indices?
EDIT (after gnibblers 2nd comment): I did try getslice too (even though the docs say that getslice is obsolete). The class then looks like this (isinstance-slice bit commented out because of syntax):
class DatedList(list):
def __init__(self, values, dates):
self.dates = dates
list.__init__(self, values)
def __contains__(self, date):
return date in self.dates
def __getslice__(self, fromdate, todate):
i_from = self.get_index(fromdate)
i_to = self.get_index(todate)
print i_from, i_to
return [list.__getitem__(self, i) for i in range(i_from, i_to)]
def __getitem__(self, date):
if isinstance(date, slice):
pass
# start = self.dates.index(slice[0])
# end = self.dates.index(slice[1])
# return [list.__getitem__(self, i) for i in range(start, end)]
elif isinstance(date, datetime.date):
index = self.get_index(date)
return list.__getitem__(self, index)
elif isinstance(date, int):
if date < 0:
date += len(self)
if date >= len(self):
raise IndexError, "index out of range {}".format(date)
return list.__getitem__(self, date)
else:
raise TypeError, "Invalid argument type."
def get_index(self, date):
if date in self.dates:
index = self.dates.index(date)
elif date < self.dates[0]:
index = 0
elif date > self.dates[-1]:
index = len(self.dates) - 1
return index
The result is:
>>> print dl[datetime.date(2012,1,3):datetime.date(2012,1,5)]
>>> None
Apparently getslice does not get used at all, because the print is not executed. It appears that getitem is executed when a slice is requested, but I don't seem to be able to use a datetime.date in a slice. /EDIT
Note: apparently it is not a good idea to subclass list, but none of the alternatives I tried so far seemed to work better (or at all):
Building a class from scratch: I could not get the [] notation to work:
dl = DatedList(values, dates) value = dl[some_date] # I want this to work value = dl.value(same_date) # I don't want this
I have considered using a dict, but my list needs to be ordered and I need to use slicing as well.
I also tried to subclass collections.Sequence, but that resulted in:
TypeError: descriptor 'init' requires a 'list' object but received a 'DatedList'
Rather than reimplement this, you might want to check out some of the existing timeseries implementations. Pandas has a pretty good one, as does scikits.timeseries.
As an example, with Pandas:
In [1]: from pandas import Series, DateRange
In [2]: import datetime
In [3]: ts = Series(range(12), index=DateRange('1/1/2000', periods=12, freq='T'))
In [4]: ts
Out[4]:
2000-01-03 0
2000-01-04 1
2000-01-05 2
2000-01-06 3
2000-01-07 4
2000-01-10 5
2000-01-11 6
2000-01-12 7
2000-01-13 8
2000-01-14 9
2000-01-17 10
2000-01-18 11
In [5]: ts[datetime.datetime(2000,1,10):]
Out[5]:
2000-01-10 5
2000-01-11 6
2000-01-12 7
2000-01-13 8
2000-01-14 9
2000-01-17 10
2000-01-18 11
Or, you could investigate the source code there and reimplement for your specific case.
Here is a really simple example, basically you are taking parameters passed to __getitem__ and passing them through a mapping so you can leverage the builting list behaviour.
As you mentioned, __getslice__ is obsolete, __getitem__ just needs to notice that it has been passed a slice and deal with it appropriately.
import datetime
class DatedList(list):
def __init__(self, values, dates):
list.__init__(self, values)
self.dates = dates
self._dt_to_idx = {k:v for v,k in enumerate(dates)}
def __getitem__(self, arg):
if isinstance(arg, slice):
start = self._dt_to_idx[arg.start]
stop = self._dt_to_idx[arg.stop]
return list.__getitem__(self, slice(start, stop, arg.step))
else:
return list.__getitem__(self, self._dt_to_idx[arg])
dates = [datetime.date(2012,1,x) for x in range(2,6)]
dl = DatedList([1,2,3,4], dates)
print dl[dates[2]]
print dl[dates[1]:dates[3]]
If you need a more complicated mapping - to deal with skipped dates, etc, simply define a method to do the mapping and call that whereever you use self._dt_to_idx[...]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With