[Scipy-svn] r2476 - in trunk/Lib/sandbox/timeseries: . mtimeseries
scipy-svn at scipy.org
scipy-svn at scipy.org
Tue Jan 2 12:55:03 EST 2007
Author: pierregm
Date: 2007-01-02 11:54:31 -0600 (Tue, 02 Jan 2007)
New Revision: 2476
Added:
trunk/Lib/sandbox/timeseries/mtimeseries/
trunk/Lib/sandbox/timeseries/mtimeseries/__init__.py
trunk/Lib/sandbox/timeseries/mtimeseries/example.py
trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py
trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py
trunk/Lib/sandbox/timeseries/mtimeseries/tests/
trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py
trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py
trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py
trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py
Log:
support for maskedarray
Property changes on: trunk/Lib/sandbox/timeseries/mtimeseries
___________________________________________________________________
Name: svn:ignore
+ test_timeseriesA.py
tseriesA.py
crapper
Added: trunk/Lib/sandbox/timeseries/mtimeseries/__init__.py
===================================================================
Added: trunk/Lib/sandbox/timeseries/mtimeseries/example.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/example.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/example.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,290 @@
+
+"""
+=== TimeSeries ===
+
+A TimeSeries object is the combination of three ndarrays:
+
+ - `dates`: DateArray object.
+ - `data` : ndarray.
+ - `mask` : Boolean ndarray, indicating missing or invalid data.
+
+
+==== Construction ====
+
+To construct a TimeSeries, you can use the class constructor:
+
+>>> TimeSeries(data, dates=None, mask=nomask,
+ freq=None, observed=None, start_date=None,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False)
+
+where `data` is a sequence.
+If `dates` is None, a DateArray of the same length as the data is constructed at
+a `freq` frequency, starting at `start_date`.
+
+Alternatively, you can use the `time_series` function:
+
+
+time_series(data, dates=None, freq=None,
+ start_date=None, end_date=None, length=None, include_last=True,
+ mask=nomask, dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False)
+
+
+Let us construct a series of 600 random elements, starting 600 business days ago,
+at a business daily frequency
+
+>>> import numpy as np
+>>> import tseries as ts
+>>> import tsdate as td
+>>> data = np.random.uniform(-100,100,600)
+>>> today = td.thisday('B')
+>>> series = ts.time_series(data, dtype=np.float_, freq='B', observed='SUMMED',
+ start_date=today-600)
+
+Let us set negative values to zero...
+
+>>> series[series<0] = 0
+
+... and the values falling on Fridays to 100
+>>> series[series.day_of_week == 4] = 100
+
+Note that we could also create a temporary array of 'day_of weeks' for the
+corresponding period, and use it as condition.
+
+>>> weekdays = td.day_of_week(series)
+>>> series[weekdays == 4] = 100
+
+==== Slicing ====
+
+Accessing elements of a TimeSeries works just like slicing
+>>> series[-30:]
+
+But you can also use a date:
+>>> thirtydaysago = today-30
+>>> series[thirtydaysago:]
+
+Or even a string
+>>> series[thirtydaysago.tostring():]
+
+
+==== Conversions ====
+
+To convert a TimeSeries to another frequency, use the `convert` method or function.
+The optional argument `func` must be a function that acts on a 1D masked array
+and returns a scalar.
+
+>>> mSer1 = series.convert('M',func=ma.average)
+
+If `func` is not specified, the default value `'auto'` is used instead. In that case,
+the conversion function is estimated from the `observed` attribute of the series.
+For example, if `observed='SUMMED'`, then `func='auto'` is in fact `func=sum`.
+
+>>> mSer1_default = series.convert('M')
+
+If `func` is None, the convert method/function returns a 2D array, where each row
+corresponds to the new frequency, and the columns to the original data. In our
+example, convert will return a 2D array with 23 columns, as there are at most
+23 business days per month.
+
+>>> mSer1_2d = series.convert('M',func=None)
+
+When converting from a lower frequency to a higher frequency, an extra argument
+`position` is required. The value of the argument is either 'START' or 'END',
+and determines where the data point will be placed in the
+period. In the future, interpolation methods will be supported to fill in the
+resulting masked values.
+
+Let us create a second series, this time with a monthly frequency, starting 110
+months ago.
+>>> data = np.random.uniform(-100,100,100).astype(np.float_)
+>>> today = today.asfreq('M') - 110
+>>> mSer2 = ts.TimeSeries(data, freq='m', observed='END',start_date=today)
+>>> sixtymonthsago = today-60
+>>> mSer2[sixtymonthsago:sixtymonthsago+10] = 12
+
+"""
+import numpy as np
+import tseries as ts
+reload(ts)
+import tsdate as td
+reload(td)
+#from numpy import ma
+import maskedarray as ma
+
+data = np.random.uniform(-100,100,600)
+today = td.thisday('B')
+series = ts.time_series(data, dtype=np.float_, freq='B', observed='SUMMED',
+ start_date=today-600)
+series[series < 0] = 0
+
+#WARNING: The ORIGINAL day_of_week version seemed bugged.
+#It told me that 2006/12/28 was a Friday.
+weekdays = td.day_of_week(series)
+series[weekdays == 4] = 100
+
+mSer1 = series.convert('M',func=ma.average)
+mSer1_2d = series.convert('M',func=None)
+mSer1_default = series.convert('M')
+mToB = series.convert('M',position='START')
+
+
+# Create another monthly frequency series
+data = np.random.uniform(-100,100,100).astype(np.float_)
+today = today.asfreq('M') - 110
+mSer2 = ts.TimeSeries(data, freq='m', observed='END',start_date=today)
+sixtymonthsago = today-60
+mSer2[sixtymonthsago:sixtymonthsago+10] = 12
+
+
+"""
+==== Operations on TimeSeries ====
+
+If you work with only one TimeSeries, you can use regular commands to process
+the data. For example:
+
+>>> mSer2_log = np.log(mSer2)
+
+Note that invalid values (negative, in that case), are automatically masked.
+Note as well that trying to use the maskedarray command directly is unlikely to
+work: you would end up with a regular MaskedArray.
+
+When working with multiple series, only series of the same frequency, size and
+starting date can be used in basic operations. The function `align_series` forces
+series to have matching starting and ending dates. By default, the starting date
+will be set to the smallest starting date of the series, and the ending date to
+the largest. [An alias to `align_series` is aligned]
+
+Let's construct a list of months, starting on Jan 2005 and ending on Dec 2006,
+with a gap from Oct 2005 to Dec 2006.
+
+>>> mlist = ['2005-%02i' % i for i in range(1,10)]
+>>> mlist += ['2006-%02i' % i for i in range(2,13)]
+>>> mdata = numpy.arange(len(mlist))
+>>> mser1 = time_series(mdata, mlist, observed='SUMMED')
+
+Note that the frequency is 'U', for undefined. In fact, you have to specify what
+kind of data is actually missing by forcing a given frequency.
+
+>>> mser1 = mser1.asfreq('M')
+
+Let us check whether there are some duplicated dates (no):
+
+>>> mser1.has_duplicated_dates()
+
+...or missing dates (yes):
+
+>>> mser1.has_missing_dates()
+
+Let us construct a second monthly series, this time without missing dates
+
+>>> mlist2 = ['2004-%02i' % i for i in range(1,13)]
+>>> mlist2 += ['2005-%02i' % i for i in range(1,13)]
+>>> mser2 = time_series(mdata, mlist2, observed='SUMMED')
+
+Let's try to add the two series:
+
+>>> mser3 = mser1 + mser2
+
+That doesn't work, as the series have different starting dates. We need to align
+them first.
+
+>>> (malg1,malg2) = aligned(mser1, mser2)
+
+That still doesnt' work, as `malg1` has missing dates. Let us fill it, then:
+data falling on a date that was missing will be masked.
+
+>>> mser1_filled = fill_missing_dates(mser1)
+>>> (malg1,malg2) = align_series(mser1_filled, mser2)
+
+Now we can add the two series. Only the data that fall on dates common to the
+original, non-aligned series will be actually added, the others will be masked.
+After all, we are adding masked arrays.
+
+>>> mser3 = malg1 + malg2
+
+We could have filled the initial series first:
+>>> mser3 = filled(malg1,0) + filled(malg2,0)
+
+Alternatively, we can force the series to start/end at some given dates
+
+>>> (malg1,malg2) = aligned(mser1_filled, mser2,
+>>> start_date='2004-06', end_date='2006-06')
+
+"""
+mlist = ['2005-%02i' % i for i in range(1,10)]
+mlist += ['2006-%02i' % i for i in range(2,13)]
+mdata = np.arange(len(mlist))
+mser1 = ts.time_series(mdata, mlist, observed='SUMMED')
+mser1 = mser1.asfreq('M')
+#
+mlist2 = ['2004-%02i' % i for i in range(1,13)]
+mlist2 += ['2005-%02i' % i for i in range(1,13)]
+mser2 = ts.time_series(np.arange(len(mlist2)), mlist2, observed='SUMMED')
+#
+mser1_filled = ts.fill_missing_dates(mser1)
+(malg1,malg2) = ts.aligned(mser1_filled, mser2)
+mser3 = malg1 + malg2
+
+"""
+# add the two series together, first filling in masked values with zeros
+mAdd1_filled = mSer1.filled(fill_value=0, ts=True) + mSer2.filled(fill_value=0, ts=True)
+
+# adjust the start and end dates of a series
+newSer = ts.adjust_endpoints(mSer1, start_date=td.Date(freq='M', year=1954, month=5), end_date=td.Date(freq='M', year=2000, month=6))
+
+# calculate the average value in the series. Behaves the same as in ma
+bAverage = ma.average(series)
+
+
+
+
+
+# Get the last day of this year, at daily frequency
+dLastDayOfYear = td.dateOf(td.thisday('A'),'D','AFTER')
+
+
+# Get the first day of this year, at business frequency
+bFirstDayOfYear = td.dateOf(td.thisday('A'),'B','BEFORE')
+
+# Get the last day of the previous quarter, business frequency
+bLastDayOfLastQuarter = td.dateOf(td.thisday('Q')-1,'B','AFTER')
+
+# dateOf can also go from high frequency to low frequency. In this case, the third parameter has no impact
+aTrueValue = (td.thisday('Q') == td.dateOf(td.thisday('b'),'Q'))
+
+# Dates of the same frequency can be subtracted (but not added obviously)
+numberOfBusinessDaysPassedThisYear = td.thisday('b') - bFirstDayOfYear
+
+# Integers can be added/substracted to/from dates
+fiveDaysFromNow = td.thisday('d') + 5
+
+
+# Get the previous business day, where business day is considered to
+# end at day_end_hour and day_end_min
+pbd = td.prevbusday(day_end_hour=18,day_end_min=0)
+"""
+# ------------------------------------------------------------------------------
+"""
+=== Date construction ===
+Several options are available to construct a Date object explicitly:
+
+ - Give appropriate values to the `year`, `month`, `day`, `quarter`, `hours`,
+ `minutes`, `seconds` arguments.
+
+ >>> td.Date(freq='Q',year=2004,quarter=3)
+ >>> td.Date(freq='D',year=2001,month=1,day=1)
+
+ - Use the `string` keyword. This method calls the `mx.DateTime.Parser`
+ submodule, more information is available in its documentation.
+
+ >>> ts.Date('D', '2007-01-01')
+
+ - Use the `mxDate` keyword with an existing mx.DateTime.DateTime object, or
+ even a datetime.datetime object.
+
+ >>> td.Date('D', mxDate=mx.DateTime.now())
+ >>> td.Date('D', mxDate=datetime.datetime.now())
+"""
+
+# Construct a sequence of dates at a given frequency
Added: trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,145 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for MaskedArray.
+Adapted from the original test_ma by Pierre Gerard-Marchant
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: test_core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__ = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+import types
+import datetime
+
+import numpy as N
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+from numpy.testing import NumpyTest, NumpyTestCase
+from numpy.testing.utils import build_err_msg
+
+import maskedarray
+from maskedarray import masked_array
+
+import maskedarray.testutils
+reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tsdate
+reload(tsdate)
+from tsdate import *
+
+class test_creation(NumpyTestCase):
+ "Base test class for MaskedArrays."
+
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+
+ def test_fromstrings(self):
+ "Tests creation from list of strings"
+ dlist = ['2007-01-%02i' % i for i in range(1,15)]
+ # A simple case: daily data
+ dates = datearray_fromlist(dlist, 'D')
+ assert_equal(dates.freq,'D')
+ assert(dates.isfull())
+ assert(not dates.ispacked())
+ assert_equal(dates, 732677+numpy.arange(len(dlist)))
+ # as simple, but we need to guess the frequency this time
+ dates = datearray_fromlist(dlist, 'D')
+ assert_equal(dates.freq,'D')
+ assert(dates.isfull())
+ assert(not dates.ispacked())
+ assert_equal(dates, 732677+numpy.arange(len(dlist)))
+ # Still daily data, that we force to month
+ dates = datearray_fromlist(dlist, 'M')
+ assert_equal(dates.freq,'M')
+ assert(not dates.isfull())
+ assert(dates.ispacked())
+ assert_equal(dates, [24085]*len(dlist))
+ # Now, for monthly data
+ dlist = ['2007-%02i' % i for i in range(1,13)]
+ dates = datearray_fromlist(dlist, 'M')
+ assert_equal(dates.freq,'M')
+ assert(dates.isfull())
+ assert(not dates.ispacked())
+ assert_equal(dates, 24085 + numpy.arange(12))
+ # Monthly data w/ guessing
+ dlist = ['2007-%02i' % i for i in range(1,13)]
+ dates = datearray_fromlist(dlist, )
+ assert_equal(dates.freq,'M')
+ assert(dates.isfull())
+ assert(not dates.ispacked())
+ assert_equal(dates, 24085 + numpy.arange(12))
+
+ def test_fromstrings_wmissing(self):
+ "Tests creation from list of strings w/ missing dates"
+ dlist = ['2007-01-%02i' % i for i in (1,2,4,5,7,8,10,11,13)]
+ dates = datearray_fromlist(dlist)
+ assert_equal(dates.freq,'U')
+ assert(not dates.isfull())
+ assert(not dates.ispacked())
+ assert_equal(dates.tovalue(),732676+numpy.array([1,2,4,5,7,8,10,11,13]))
+ #
+ ddates = datearray_fromlist(dlist, 'D')
+ assert_equal(ddates.freq,'D')
+ assert(not ddates.isfull())
+ assert(not ddates.ispacked())
+ #
+ mdates = datearray_fromlist(dlist, 'M')
+ assert_equal(mdates.freq,'M')
+ assert(not dates.isfull())
+ assert(mdates.ispacked())
+ #
+
+ def test_fromsobjects(self):
+ "Tests creation from list of objects."
+ dlist = ['2007-01-%02i' % i for i in (1,2,4,5,7,8,10,11,13)]
+ dates = datearray_fromlist(dlist)
+ dobj = [datetime.datetime.fromordinal(d) for d in dates.toordinal()]
+ odates = datearray_fromlist(dobj)
+ assert_equal(dates,odates)
+ dobj = [mxDFromString(d) for d in dlist]
+ odates = datearray_fromlist(dobj)
+ assert_equal(dates,odates)
+
+
+class test_methods(NumpyTestCase):
+ "Base test class for MaskedArrays."
+
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+
+ def test_getitem(self):
+ "Tests getitem"
+ dlist = ['2007-%02i' % i for i in range(1,5)+range(7,13)]
+ mdates = datearray_fromlist(dlist).asfreq('M')
+ # Using an integer
+ assert_equal(mdates[0].value, 24085)
+ assert_equal(mdates[-1].value, 24096)
+ # Using a date
+ lag = mdates.find_dates(mdates[0])
+ assert_equal(mdates[lag], mdates[0])
+ lag = mdates.find_dates(Date('M',value=24092))
+ assert_equal(mdates[lag], mdates[5])
+ # Using several dates
+ lag = mdates.find_dates(Date('M',value=24085), Date('M',value=24096))
+ assert_equal(mdates[lag],
+ DateArray([mdates[0], mdates[-1]], freq='M'))
+ assert_equal(mdates[[mdates[0],mdates[-1]]], mdates[lag])
+ #
+ assert_equal(mdates>=mdates[-4], [0,0,0,0,0,0,1,1,1,1])
+ dlist = ['2006-%02i' % i for i in range(1,5)+range(7,13)]
+ mdates = datearray_fromlist(dlist).asfreq('M')
+
+
+ def test_getsteps(self):
+ dlist = ['2007-01-%02i' %i for i in (1,2,3,4,8,9,10,11,12,15)]
+ ddates = datearray_fromlist(dlist)
+ assert_equal(ddates.get_steps(), [1,1,1,4,1,1,1,1,3])
+
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+ NumpyTest().run()
\ No newline at end of file
Added: trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,273 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for MaskedArray.
+Adapted from the original test_ma by Pierre Gerard-Marchant
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: test_core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__ = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+import types
+
+import numpy as N
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+from numpy.testing import NumpyTest, NumpyTestCase
+from numpy.testing.utils import build_err_msg
+
+import maskedarray
+from maskedarray import masked_array
+
+import maskedarray.testutils
+reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tsdate
+reload(tsdate)
+from tsdate import date_array_fromlist
+import tseries
+reload(tseries)
+from tseries import *
+
+class test_creation(NumpyTestCase):
+ "Base test class for MaskedArrays."
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+ dlist = ['2007-01-%02i' %i for i in range(1,16)]
+ dates = date_array_fromlist(dlist)
+ data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+ self.d = (dlist, dates, data)
+
+ def test_fromlist (self):
+ "Base data definition."
+ (dlist, dates, data) = self.d
+ series = time_series(data, dlist)
+ assert(isinstance(series, TimeSeries))
+ assert_equal(series._mask, [1,0,0,0,0]*3)
+ assert_equal(series._series, data)
+ assert_equal(series._dates, date_array_fromlist(dlist))
+ assert_equal(series.freq, 'D')
+
+ def test_fromrange (self):
+ "Base data definition."
+ (dlist, dates, data) = self.d
+ series = time_series(data, start_date=Date('D',value=dates[0]),
+ length=15)
+ assert(isinstance(series, TimeSeries))
+ assert_equal(series._mask, [1,0,0,0,0]*3)
+ assert_equal(series._series, data)
+ assert_equal(series._dates, dates)
+ assert_equal(series.freq, 'D')
+
+ def test_fromseries (self):
+ "Base data definition."
+ (dlist, dates, data) = self.d
+ series = time_series(data, dlist)
+ dates = dates+15
+ series = time_series(series, dates)
+ assert(isinstance(series, TimeSeries))
+ assert_equal(series._mask, [1,0,0,0,0]*3)
+ assert_equal(series._series, data)
+ assert_equal(series._dates, dates)
+ assert_equal(series.freq, 'D')
+
+class test_arithmetics(NumpyTestCase):
+ "Some basic arithmetic tests"
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array_fromlist(dlist)
+ data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+ self.d = (time_series(data, dlist), data)
+
+
+ def test_intfloat(self):
+ "Test arithmetic timeseries/integers"
+ (series, data) =self.d
+ #
+ nseries = series+1
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data+1)
+ assert_equal(nseries._dates, series._dates)
+ #
+ nseries = series-1
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data-1)
+ assert_equal(nseries._dates, series._dates)
+ #
+ nseries = series*1
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data*1)
+ assert_equal(nseries._dates, series._dates)
+ #
+ nseries = series/1.
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data/1.)
+ assert_equal(nseries._dates, series._dates)
+
+ def test_intfloat_inplace(self):
+ "Test int/float arithmetics in place."
+ (series, data) =self.d
+ nseries = series.astype(float_)
+ idini = id(nseries)
+ data = data.astype(float_)
+ #
+ nseries += 1.
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data+1.)
+ assert_equal(nseries._dates, series._dates)
+ assert_equal(id(nseries),idini)
+ #
+ nseries -= 1.
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data)
+ assert_equal(nseries._dates, series._dates)
+ assert_equal(id(nseries),idini)
+ #
+ nseries *= 2.
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data*2.)
+ assert_equal(nseries._dates, series._dates)
+ assert_equal(id(nseries),idini)
+ #
+ nseries /= 2.
+ assert(isinstance(nseries, TimeSeries))
+ assert_equal(nseries._mask, [1,0,0,0,0]*3)
+ assert_equal(nseries._series, data)
+ assert_equal(nseries._dates, series._dates)
+ assert_equal(id(nseries),idini)
+ #
+ def test_updatemask(self):
+ "Checks modification of mask."
+ (series, data) =self.d
+ assert_equal(series._mask, [1,0,0,0,0]*3)
+ series.mask = nomask
+ assert(series._mask is nomask)
+ assert(series._series._mask is nomask)
+ series._series.mask = [1,0,0]*5
+ assert_equal(series._mask, [1,0,0]*5)
+ assert_equal(series._series._mask, [1,0,0]*5)
+ series[2] = masked
+ assert_equal(series._mask, [1,0,1]+[1,0,0]*4)
+ assert_equal(series._series._mask, [1,0,1]+[1,0,0]*4)
+
+
+class test_getitem(NumpyTestCase):
+ "Some getitem tests"
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array_fromlist(dlist)
+ data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3, dtype=float_)
+ self.d = (time_series(data, dlist), data, dates)
+
+ def test_wdate(self):
+ "Tests getitem with date as index"
+ (tseries, data, dates) = self.d
+ last_date = tseries[-1]._dates
+ assert_equal(tseries[-1], tseries[last_date])
+ assert_equal(tseries._dates[-1], dates[-1])
+ assert_equal(tseries[-1]._dates, dates[-1])
+ assert_equal(tseries[last_date]._dates, dates[-1])
+ assert_equal(tseries._series[-1], data._data[-1])
+ assert_equal(tseries[-1]._series, data._data[-1])
+ assert_equal(tseries._mask[-1], data._mask[-1])
+ #
+ tseries['2007-01-06'] = 999
+ assert_equal(tseries[5], 999)
+ #
+ def test_wtimeseries(self):
+ "Tests getitem w/ TimeSeries as index"
+ (tseries, data, dates) = self.d
+ # Testing a basic condition on data
+ cond = (tseries<8).filled(False)
+ dseries = tseries[cond]
+ assert_equal(dseries._data, [1,2,3,4,6,7])
+ assert_equal(dseries._dates, tseries._dates[[1,2,3,4,6,7]])
+ assert_equal(dseries._mask, nomask)
+ # Testing a basic condition on dates
+ tseries[tseries._dates < Date('D',string='2007-01-06')] = MA.masked
+ assert_equal(tseries[:5]._series._mask, [1,1,1,1,1])
+
+ def test_wslices(self):
+ "Test get/set items."
+ (tseries, data, dates) = self.d
+ # Basic slices
+ assert_equal(tseries[3:7]._series._data, data[3:7]._data)
+ assert_equal(tseries[3:7]._series._mask, data[3:7]._mask)
+ assert_equal(tseries[3:7]._dates, dates[3:7])
+ # Ditto
+ assert_equal(tseries[:5]._series._data, data[:5]._data)
+ assert_equal(tseries[:5]._series._mask, data[:5]._mask)
+ assert_equal(tseries[:5]._dates, dates[:5])
+ # With set
+ tseries[:5] = 0
+ assert_equal(tseries[:5]._series, [0,0,0,0,0])
+ dseries = numpy.log(tseries)
+ tseries[-5:] = dseries[-5:]
+ assert_equal(tseries[-5:], dseries[-5:])
+ # Now, using dates !
+ dseries = tseries[tseries.dates[3]:tseries.dates[7]]
+ assert_equal(dseries, tseries[3:7])
+
+class test_functions(NumpyTestCase):
+ "Some getitem tests"
+ def __init__(self, *args, **kwds):
+ NumpyTestCase.__init__(self, *args, **kwds)
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array_fromlist(dlist)
+ data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+ self.d = (time_series(data, dlist), data, dates)
+ #
+ def test_adjustendpoints(self):
+ "Tests adjust_endpoints"
+ (tseries, data, dates) = self.d
+ dseries = adjust_endpoints(tseries, tseries.dates[0], tseries.dates[-1])
+ assert_equal(dseries, tseries)
+ dseries = adjust_endpoints(tseries, tseries.dates[3], tseries.dates[-3])
+ assert_equal(dseries, tseries[3:-2])
+ dseries = adjust_endpoints(tseries, end_date=Date('D', string='2007-01-31'))
+ assert_equal(dseries.size, 31)
+ assert_equal(dseries._mask, numpy.r_[tseries._mask, [1]*16])
+ dseries = adjust_endpoints(tseries, end_date=Date('D', string='2007-01-06'))
+ assert_equal(dseries.size, 6)
+ assert_equal(dseries, tseries[:6])
+ dseries = adjust_endpoints(tseries,
+ start_date=Date('D', string='2007-01-06'),
+ end_date=Date('D', string='2007-01-31'))
+ assert_equal(dseries.size, 26)
+ assert_equal(dseries._mask, numpy.r_[tseries._mask[5:], [1]*16])
+ #
+ def test_maskperiod(self):
+ "Test mask_period"
+ (tseries, data, dates) = self.d
+ tseries.mask = nomask
+ (start, end) = ('2007-01-06', '2007-01-12')
+ mask = mask_period(tseries, start, end, inside=True, include_edges=True,
+ inplace=False)
+ assert_equal(mask._mask, numpy.array([0,0,0,0,0,1,1,1,1,1,1,1,0,0,0]))
+ mask = mask_period(tseries, start, end, inside=True, include_edges=False,
+ inplace=False)
+ assert_equal(mask._mask, [0,0,0,0,0,0,1,1,1,1,1,0,0,0,0])
+ mask = mask_period(tseries, start, end, inside=False, include_edges=True,
+ inplace=False)
+ assert_equal(mask._mask, [1,1,1,1,1,1,0,0,0,0,0,1,1,1,1])
+ mask = mask_period(tseries, start, end, inside=False, include_edges=False,
+ inplace=False)
+ assert_equal(mask._mask, [1,1,1,1,1,0,0,0,0,0,0,0,1,1,1])
+
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+ NumpyTest().run()
\ No newline at end of file
Added: trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1494 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+
+ - an array storing the time information (as a `DateArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+:author: Pierre GF Gerard-Marchant
+:contact: pierregm_at_uga_edu
+:date: $Date: 2006-12-05 20:40:46 -0500 (Tue, 05 Dec 2006) $
+:version: $Id: timeseries.py 25 2006-12-06 01:40:46Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 25 $"
+__date__ = '$Date: 2006-12-05 20:40:46 -0500 (Tue, 05 Dec 2006) $'
+
+
+#import numpy as N
+
+#from numpy.core import bool_, float_, int_
+import numpy.core.umath as umath
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+from numpy import ndarray
+from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+#from cPickle import dump, dumps
+
+import core
+reload(core)
+from core import *
+
+
+import maskedarray as MA
+#reload(MA)
+from maskedarray.core import domain_check_interval, \
+ domain_greater_equal, domain_greater, domain_safe_divide, domain_tan
+from maskedarray.core import MaskedArray, MAError, masked_array, isMaskedArray,\
+ getmask, getmaskarray, filled, mask_or, make_mask
+from maskedarray.core import convert_typecode, masked_print_option, \
+ masked_singleton
+from maskedarray.core import load, loads, dump, dumps
+
+import addons.numpyaddons
+reload(addons.numpyaddons)
+from addons.numpyaddons import ascondition
+
+#...............................................................................
+talog = logging.getLogger('TimeArray')
+tslog = logging.getLogger('TimeSeries')
+mtslog = logging.getLogger('MaskedTimeSeries')
+
+nomask = MA.nomask
+masked = MA.masked
+masked_array = MA.masked_array
+ufunc_domain = {}
+ufunc_fills = {}
+
+
+#### --------------------------------------------------------------------------
+#--- ... Date Tools ...
+#### --------------------------------------------------------------------------
+dtmdefault = dtm.datetime(2001,1,1)
+
+def isDate(obj):
+ """Returns *True* if the argument is a `datetime.date` or `datetime.datetime`
+instance."""
+ return isinstance(obj,dtm.date) or isinstance(obj,dtm.datetime)
+
+def fake_dates(count, start=dtmdefault):
+ """fake_dates(count, start=dtmdefault)
+Returns a *count x 1* array of daily `datetime` objects.
+:Parameters:
+ - `count` (Integer) : Number of dates to output
+ - `start` (datetime object) : Starting date *[NOW]*."""
+ dobj = list(dtmrule.rrule(DAILY,count=count,dtstart=start))
+ return N.array(dobj)
+
+#### --------------------------------------------------------------------------
+#--- ... TimeArray class ...
+#### --------------------------------------------------------------------------
+class TimeArrayError(Exception):
+ """Defines a generic DateArrayError."""
+ def __init__ (self, args=None):
+ "Create an exception"
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculate the string representation"
+ return str(self.args)
+ __repr__ = __str__
+
+class TimeArray(ndarray):
+ """Stores datetime.dateime objects in an array."""
+ def __new__(subtype, series, copy=False):
+ vlev = 3
+ if isinstance(series, TimeArray):
+# verbose.report("DA __new__: data isDA types %s, %s, %i" % \
+# (type(series), series, len(series)), vlev)
+ if not copy:
+ return series
+ else:
+ return series.copy()
+ else:
+ data = N.array(series)
+ if data.dtype.str == "|O8":
+ new = data #.view(subtype)
+ talog.debug("__new__: data isnotDA types %s, %s, %s, %i" % \
+ (type(series), data.dtype, series, data.size),)
+ else:
+ new = N.empty(data.shape, dtype="|O8")
+ newflat = new.flat
+ talog.debug("DA __new__: data isnotDA types %s, %s, %s, %i" % \
+ (type(series), data.dtype, series, data.size),)
+ # Forces years (as integers) to be read as characters
+ if N.all(data < 9999):
+ data = data.astype("|S4")
+ # Parse dates as characters
+ if data.dtype.char == "S":
+ for (k,s) in enumerate(data.flat):
+ newflat[k] = dtmparser.parse(s, default=dtmdefault)
+ else:
+ # Parse dates as ordinals
+ try:
+ for k,s in enumerate(data.flat):
+ newflat[k] = dtm.datetime.fromordinal(s)
+ except TypeError:
+ raise TypeError, "unable to process series !"
+ subtype._asobject = new
+ return new.view(subtype)
+# #............................................
+# def __array_wrap__(self, obj):
+# return TimeArray(obj)
+ #............................................
+ def __array_finalize__(self, obj):
+ self._resolution = None
+ (self._years, self._months, self._days, self._yeardays) = [None]*4
+ self._asobjects = None
+ self._asstrings = None
+ self._asordinals = None
+ return
+ #............................................
+ def __len__(self):
+ "Returns the length of the object. Or zero if it fails."
+ if self.ndim == 0:
+ return 0
+ return ndarray.__len__(self)
+
+
+ def __getitem__(self,i):
+ # Force a singleton to DateArray
+ try:
+ obj = ndarray.__getitem__(self,i)
+ except IndexError:
+ obj = self
+ return TimeArray(obj)
+# if isDate(obj):
+# return DateArray(obj)
+# else:
+# return obj
+ #............................................
+ def __eq__(self, other):
+ return N.asarray(self).__eq__(N.asarray(other))
+
+ def __add__(self, other):
+ if not isinstance(other, dtm.timedelta):
+ raise TypeError, "Unsupported type for add operation"
+ return TimeArray(N.asarray(self).__add__(other))
+
+ def __sub__(self, other):
+ if not isinstance(other, dtm.timedelta) and \
+ not isinstance(other, TimeArray):
+ raise TypeError, "Unsupported type for sub operation"
+ return N.asarray(self).__sub__(N.asarray(other))
+
+ def __mul__(self, other):
+ raise TimeArrayError, "TimeArray objects cannot be multiplied!"
+ __rmul__ = __mul__
+ __imul__ = __mul__
+# def shape(self):
+# if self.size == 1:
+# return (1,)
+# else:
+# return self._data.shape
+ #............................................
+ def __str__(self):
+ """x.__str__() <=> str(x)"""
+ return str(self.asstrings())
+ def __repr__(self):
+ """Calculate the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ template = """\
+timearray(
+ %(data)s,)"""
+ template_short = """\
+timearray(%(data)s)"""
+ if self.ndim <= 1:
+ return template_short % {'data': str(self)}
+ else:
+ return template % {'data': str(self)}
+ #............................................
+ @property
+ def resolution(self):
+ """Calculates the initial resolution, as the smallest difference
+between consecutive values (in days)."""
+ if self._resolution is None:
+ self._resolution = N.diff(self.asordinals().ravel()).min()
+ if self._resolution <= 3:
+ self._resolution = 1
+ elif self._resolution <= 10:
+ self._resolution = 7
+ elif self._resolution <= 270:
+ self._resolution = 30
+ else:
+ self._resolution = 365
+ return self._resolution
+ timestep = resolution
+ #............................................
+ def has_missing_dates(self, resolution=None):
+ """Returns *True* there's a gap in the series, assuming a regular series
+with a constant timestep of `resolution`.
+If `resolution` is None, uses the default resolution of `self`.
+"""
+ if self.size < 2:
+ return False
+ dt = N.diff(self.asordinals().ravel())
+ if resolution is None:
+ resolution = self.resolution
+ if resolution == 1:
+ return (dt.max() > 1)
+ elif resolution == 30:
+ return (dt.max() > 31)
+ elif resolution == 365:
+ return (dt.max() > 730)
+ else:
+ #FIXME: OK, there's probly something wrong here...
+ return True
+ #............................................
+ def asobjects(self):
+ """Transforms an array of ordinal dates to an array of date objects."""
+ if self._asobjects is None:
+ if self.size == 1:
+ self._asobjects = self.item()
+ else:
+ self._asobjects = self
+ return self._asobjects
+ #............................................
+ def asordinals(self):
+ """Transforms an array of dates to an array of date objects."""
+ if self._asordinals is None:
+ # Build list of datetime objects
+ self._asordinals = N.empty(self.shape,dtype=int_)
+ _asordinalsflat = self._asordinals.flat
+ if self.size == 0:
+ self._asordinals = dtm.datetime.toordinal(dtmdefault)
+ elif self.size == 1:
+ self._asordinals = dtm.datetime.toordinal(self.item())
+ else:
+ itr = (dtm.datetime.toordinal(val) for val in self.flat)
+ self._asordinals = N.fromiter(itr, float_)
+ return self._asordinals
+ #............................................
+ def asstrings(self, stringsize=10):
+ """Transforms a *N*-array of ordinal dates to a *N*-list of
+datestrings `YYYY-MM-DD`.
+
+:Parameters:
+ `stringsize` : Integer *[10]*
+ String size.
+
+ - `< 4' outputs 4
+ - `< 8' outputs 8
+ - anything else outputs 10.
+ """
+ if not stringsize:
+ strsize = 10
+ elif stringsize <= 4:
+ strsize = 4
+ elif stringsize <= 8:
+ strsize = 7
+ else:
+ strsize = 10
+ if self._asstrings is None:
+ deftype = "|S10"
+ if self.size == 0:
+ self._asstrings = N.array(dtmdefault.isoformat(), dtype=deftype)
+ elif self.size == 1:
+ self._asstrings = N.array(self.item().isoformat(), dtype=deftype)
+ else:
+ itr = (val.isoformat() for val in self.flat)
+ self._asstrings = N.fromiter(itr,dtype=deftype).reshape(self.shape)
+ return self._asstrings.getfield('S%i' % strsize)
+ #............................................
+ def astype(self, newtype):
+ """Outputs the array in the type provided in argument."""
+ newtype = N.dtype(newtype)
+ if newtype.type == int_:
+ return self.asordinals()
+ elif newtype.type == str_:
+ n = newtype.itemsize
+ if n > 0 :
+ return self.asstrings(stringsize=n)
+ else:
+ return self.asstrings(stringsize=n)
+ elif newtype.type == object_:
+ return self
+ else:
+ raise ValueError, "Invalid type: %s" % newtype
+ #------------------------------------------------------
+ @property
+ def years(self):
+ """Returns the corresponding year (as integer)."""
+ if self._years is None:
+ self._years = self.asstrings(1).astype(int_)
+ return self._years
+ @property
+ def months(self):
+ """Returns the corresponding month (as integer)."""
+ if self._months is None:
+ self._months = N.empty(self.shape, dtype=int_)
+ _monthsflat = self._months.flat
+ if self.size == 1:
+ try:
+ _monthsflat[:] = self.asobjects().month
+ except AttributeError:
+ _monthsflat[:] = 1
+ else:
+ for (k,val) in enumerate(self.asobjects().flat):
+ _monthsflat[k] = val.month
+ return self._months
+ @property
+ def days(self):
+ """Returns the corresponding day of month (as integer)."""
+ if self._days is None:
+ self._days = N.empty(self.shape, dtype=int_)
+ _daysflat = self._days.flat
+ if self.size == 1:
+ try:
+ _daysflat[:] = self.asobjects().day
+ except AttributeError:
+ _daysflat[:] = 1
+ else:
+ for (k,val) in enumerate(self.asobjects().flat):
+ _daysflat[k] = val.day
+ return self._days
+ @property
+ def yeardays(self):
+ """Returns the corresponding day of year (as integer)."""
+ if self._yeardays is None:
+ self._yeardays = N.empty(self.size, dtype=int_)
+ _doyflat = self._yeardays.flat
+ for (k,val,yyy) in N.broadcast(N.arange(len(self)),
+ self.asordinals(),
+ self.years.flat):
+ _doyflat[k] = val - dtm.datetime(yyy-1,12,31).toordinal()
+ self._yeardays.reshape(self.shape).astype(int_)
+ return self._yeardays
+#................................................
+def isTimeArray(x):
+ """Checks whether `x` is n array of times/dates (an instance of `TimeArray` )."""
+ return isinstance(x,TimeArray)
+
+def time_array(t, copy=False):
+ """Creates a date array from the series `t`.
+ """
+ if isinstance(t,TimeArray):
+ dobj = t
+ else:
+ t = N.asarray(t)
+ if t.dtype.str == "|O8":
+ dobj = t
+ else:
+ dobj = N.empty(t.shape, dtype="|O8")
+ dobjflat = dobj.flat
+ if t.dtype.char == 'S':
+ for k,s in enumerate(t.flat):
+ dobjflat[k] = dtmparser.parse(s)
+ else:
+ try:
+ for k,s in enumerate(t.flat):
+ dobjflat[k] = dtm.datetime.fromordinal(s)
+ except TypeError:
+ raise TypeError, "unable to process series !"
+ return TimeArray(dobj, copy=copy)
+
+#### --------------------------------------------------------------------------
+#--- ... Time Series ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+ "Class for TS related errors."
+ def __init__ (self, args=None):
+ "Creates an exception."
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculates the string representation."
+ return str(self.args)
+ __repr__ = __str__
+#......................................
+#TODO: __eq__, __cmp__ classes
+class TimeSeries(ndarray, object):
+ """Base class for the definition of time series.
+A time series is here defined as the combination of two arrays:
+
+ - an array storing the time information (as a `TimeArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+ """
+ def __new__(cls, data, dates=None, dtype=None, copy=True):
+ tslog.debug("__new__: data types %s, %s" % (type(data), dtype))
+# if isinstance(data, TimeSeries):
+ if hasattr(data,"_series") and hasattr(data,"_dates"):
+ tslog.debug("__new__: data has _series and _dates")
+ tslog.debug("__new__: setting basedates",)
+ cls._basedates = data._dates
+ tslog.debug("__new__: setting basedates done",)
+ if (not copy) and (dtype == data.dtype):
+ return N.asarray(data).view(cls)
+ else:
+ return N.array(data, dtype=dtype).view(cls)
+ #..........
+ dsize = N.size(data)
+ tslog.debug("__new__: args dates type %s, %s" % (type(dates), dates),)
+ if dates is None:
+ _dates = TimeArray(fake_dates(N.size(data)))
+ tslog.debug("__new__: args dates FAKED type %s, %s, %i" % \
+ (type(dates), _dates, _dates.size),)
+ else:
+ _dates = TimeArray(dates)
+ tslog.debug("__new__: dates set to %s" % _dates,)
+ if _dates.size != dsize:
+ msg = "Incompatible sizes between dates (%i) and data (%i) !"
+ raise ValueError, msg % (_dates.size, dsize)
+ _dates.shape = N.shape(data)
+ cls._basedates = _dates
+ return N.array(data, copy=copy, dtype=dtype).view(cls)
+ #..................................
+ def __array_wrap__(self, obj):
+ return TimeSeries(obj, dates=self._dates)
+ #..................................
+ def __array_finalize__(self,obj):
+ if not hasattr(self,"_series"):
+ try:
+ self._series = obj._series
+ tslog.debug("__array_finalize__: obj._data => : %s - %s" % \
+ (id(self._series), self._series.ravel() ),)
+ except AttributeError:
+ self._series = obj
+ tslog.debug("__array_finalize__: obj => : %s - %s" % \
+ (id(self._series), self._series.ravel() ),)
+ if not hasattr(self,"_dates"):
+ self._dates = self._basedates
+ tslog.debug("__array_finalize__: dates set! => : %s - %s" % \
+ (id(self._dates), self._dates.ravel() ))
+ return
+ #........................
+ def _get_flat(self):
+ """Calculate the flat value.
+ """
+ return self.__class__(self._series.ravel(),
+ dates = self._dates.ravel(), copy=False)
+ #....
+ def _set_flat (self, value):
+ "x.flat = value"
+ y = self.ravel()
+ y[:] = value
+ flat = property(fget=_get_flat,fset=_set_flat,doc='Access array in flat form.')
+ #........................
+ def _get_shape(self):
+ "Return the current shape."
+ return self._series.shape
+ #....
+ def _set_shape (self, newshape):
+ "Set the array's shape."
+ self._series.shape = self._dates.shape = newshape
+ #....
+ shape = property(fget=_get_shape, fset=_set_shape, doc="Array shape")
+ #....
+ @property
+ def ndim(self):
+ """Returns the number of dimensions."""
+ return self._series.ndim
+ @property
+ def size(self):
+ """Returns the total number of elements."""
+ return self._series.size
+ #........................
+ def __getitem__(self, i):
+ "Get item described by i. Not a copy as in previous versions."
+ (tout, dout) = (self._dates[i], self._series[i])
+ return self.__class__(dout, dates=tout)
+ #....
+ def __setitem__(self, index, value):
+ "Gets item described by i. Not a copy as in previous versions."
+ self._series[index] = value
+ return self
+ #........................
+ def __getslice__(self, i, j):
+ "Gets slice described by i, j"
+ (tout, dout) = (self._dates[i:j], self._series[i:j])
+ return self.__class__(dout, dates=tout,)
+ #....
+ def __setslice__(self, i, j, value):
+ "Gets item described by i. Not a copy as in previous versions."
+ #TODO: Here, we should have a better test for self._dates==value._dates
+ if hasattr(value,"_dates"):
+ tslog.debug("__setslice__: value: %s" % str(value))
+ tslog.debug("__setslice__: dates: %s" % str(value._dates),)
+ if not (self._dates == value._dates).all():
+ raise TimeSeriesError,"Can't force a change of dates !"
+ self._series[i:j] = value
+ return self
+ #........................
+ def ravel (self):
+ """Returns a 1-D view of self."""
+ return self.__class__(self._series.ravel(),
+ dates=self._dates.ravel())
+ #........................
+ def __len__(self):
+ if self.ndim == 0:
+ return 0
+ return ndarray.__len__(self)
+ def __str__(self):
+ """Returns a string representation of self (w/o the dates...)"""
+ return str(self._series)
+ def __repr__(self):
+ """Calculates the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ desc = """\
+timeseries(data =
+ %(data)s,
+ dates =
+ %(time)s, )
+"""
+ desc_short = """\
+timeseries(data = %(data)s,
+ dates = %(time)s,)
+"""
+ if self.ndim <= 1:
+ return desc_short % {
+ 'data': str(self._series),
+ 'time': str(self.dates),
+ }
+ return desc % {
+ 'data': str(self._series),
+ 'time': str(self.dates),
+ }
+
+ def ids (self):
+ """Return the ids of the data, dates and mask areas"""
+ return (id(self._series), id(self.dates),)
+
+ @property
+ def series(self):
+ "Returnhs the series."
+ return self._series
+
+ #------------------------------------------------------
+ @property
+ def dates(self):
+ """Returns the dates"""
+ return self._dates
+### def _set_dates(self, object):
+### """Returns the dates"""
+### self._dates = object
+### dates = property(fget=_get_dates, fset=_set_dates, doc="Dates")
+ @property
+ def years(self):
+ """Returns the corresponding years."""
+ return self.dates.years
+ @property
+ def months(self):
+ """Returns the corresponding months."""
+ return self._dates.months
+ @property
+ def yeardays(self):
+ """Returns the corresponding days of yuear."""
+ return self._dates.yeardays
+
+ def has_missing_dates(self):
+ """Returns whether there's a date gap in the series."""
+ return self._dates.has_missing_dates()
+
+#### --------------------------------------------------------------------------
+#--- ... Additional methods ...
+#### --------------------------------------------------------------------------
+class _tsmathmethod(object):
+ """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ _dates = instance._dates
+ tslog.debug("_tsmathmethod: series: %s" % instance,)
+ tslog.debug("_tsmathmethod: other : %s" % other,)
+ func = getattr(instance._series, self.f)
+ if hasattr(instance,"_dates") and hasattr(other,"_dates"):
+ tslog.debug("_tsmathmethod: instance : %s" % instance,)
+ tslog.debug("_tsmathmethod: other : %s" % other,)
+ if N.any(instance._dates != other._dates):
+ tslog.debug("_tsmathmethod %s on %s and %s" % \
+ (self.f, instance, other),)
+ raise TimeSeriesError, "Method %s not yet implemented !" % self.f
+ return instance.__class__(func(other, *args), dates=_dates)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+#................................................
+class _tsarraymethod(object):
+ """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+ """
+ def __init__ (self, methodname, ondates=False):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ self._ondates = ondates
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args):
+ "Execute the call behavior."
+ _name = self._name
+ instance = self.obj
+ func_series = getattr(instance._series, _name)
+ if self._ondates:
+ func_dates = getattr(instance._dates, _name)
+ return instance.__class__(func_series(*args),
+ dates=func_dates(*args))
+ else:
+ return instance.__class__(func_series(*args),
+ dates=instance._dates)
+#......................................
+class _tsaxismethod(object):
+ """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the original series.
+ """
+ def __init__ (self, methodname):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args, **params):
+ "Execute the call behavior."
+ func = getattr(self.obj._series, self._name)
+ return func(*args, **params)
+#.......................................
+TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+#
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.cumsum = _tsaxismethod('cumsum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.cumprod = _tsaxismethod('cumprod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+
+#..............................................................................
+def concatenate(arrays, axis=0):
+ """Concatenates a sequence of time series."""
+ concatenate.__doc__ = N.concatenate.__doc__
+ for a in arrays:
+ if hasattr(a,"_dates"):
+ raise TimeSeriesError, "Not yet implemented for TimeSeries !"
+
+#### ---------------------------------------------------------------------------
+#--- ... Pickling ...
+#### ---------------------------------------------------------------------------
+#FIXME: We're kinda stuck with forcing the mask to have the same shape as the data
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype):
+ """Internal function that builds a new MaskedArray from the information stored
+in a pickle."""
+ _series = ndarray.__new__(ndarray, baseshape, basetype)
+ _dates = ndarray.__new__(datesclass, baseshape, basetype)
+ return TimeSeries.__new__(baseclass, _series, dates=_dates, dtype=basetype)
+
+def _tsgetstate(a):
+ "Returns the internal state of the TimeSeries, for pickling purposes."
+ #TODO: We should prolly go through a recarray here as well.
+ state = (1,
+ a.shape,
+ a.dtype,
+ a.flags.fnc,
+ (a._series).__reduce__()[-1][-1],
+ (a._dates).__reduce__()[-1][-1])
+ return state
+
+def _tssetstate(a, state):
+ """Restores the internal state of the TimeSeries, for pickling purposes.
+`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+
+ - class name
+ - a tuple giving the shape of the data
+ - a typecode for the data
+ - a binary string for the data
+ - a binary string for the mask.
+ """
+ (ver, shp, typ, isf, raw, dti) = state
+ (a._series).__setstate__((shp, typ, isf, raw))
+ (a._dates).__setstate__((shp, N.dtype('|O8'), isf, dti))
+ (a._dates)._asstrings = None
+
+def _tsreduce(a):
+ """Returns a 3-tuple for pickling a MaskedArray."""
+ return (_tsreconstruct,
+ (a.__class__, a._dates.__class__, (0,), 'b', ),
+ a.__getstate__())
+
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+TimeSeries.__dump__ = dump
+TimeSeries.__dumps__ = dumps
+
+#................................................
+def tofile(self, output, sep='\t', format='%s', format_dates=None):
+ """Writes the TimeSeries to a file.
+
+:Parameters:
+ - `output` (String) : Name or handle of the output file.
+ - `sep` (String) : Column separator *['\t']*.
+ - `format` (String) : Data format *['%s']*.
+ """
+ if not hasattr(output, 'writeline'):
+ ofile = open(output,'w')
+ else:
+ ofile = output
+ oformat = "%%s%s%s" % (sep,format)
+ for (_dates,_data) in N.broadcast(self._dates.ravel().asstrings(),
+ filled(self)):
+ ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+ ofile.close()
+TimeSeries.tofile = tofile
+
+#### --------------------------------------------------------------------------
+#--- ... Shortcuts ...
+#### --------------------------------------------------------------------------
+def isTimeSeries(x):
+ """Checks whether `x` is a time series (an instance of `TimeSeries` )."""
+ return isinstance(x, TimeSeries)
+
+#### --------------------------------------------------------------------------
+#--- ... MaskedTimeSeries class ...
+#### --------------------------------------------------------------------------
+class MaskedTimeSeries(MaskedArray, TimeSeries):
+ """Base class for the definition of time series.
+A time series is here defined as the combination of two arrays:
+
+ - an array storing the time information (as a `TimeArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+ """
+ def __new__(cls, data, dates=None, mask=nomask,
+ dtype=None, copy=True, fill_value=-9999):
+ mtslog.log(5, "__new__: data types %s, %s" % (type(data), dtype))
+# if isinstance(data, TimeSeries):
+ #....................
+ if isinstance(data, TimeSeries):
+ if isinstance(data, MaskedTimeSeries):
+ _data = data._data
+ else:
+ _data = data
+ _dates = data._dates
+ _series = data._series
+ mtslog.log(5, "__new__ from TS: data %i - %s - %s" % \
+ (id(_data._series), type(_data._series), _data.ravel()))
+ mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+ (id(_dates), type(_dates), _dates.ravel()))
+ elif isinstance(data, recarray):
+ assert(data.dtype.names == ('_dates', '_series', '_mask'),
+ "Invalid fields names (got %s)" % (data.dtype.names,))
+ _dates = data['_dates']
+ _series = data['_series']
+ _mask = data['_mask']
+ else:
+ if hasattr(data, "_data"):
+ _data = TimeSeries(data._data, dates=dates,
+ dtype=dtype, copy=copy)
+ else:
+ _data = TimeSeries(data, dates=dates,
+ dtype=dtype, copy=copy)
+ _dates = _data._dates
+ _series = _data._series
+ mtslog.log(5,"__new__ from scratch: data %i - %s - %s" % \
+ (id(_data._series), type(_data._series), _data.ravel()))
+ mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+ (id(_dates), type(_dates), _dates.ravel()))
+ #.....................
+ if mask is nomask:
+ if hasattr(data, "_mask"):
+ _mask = data._mask
+ else:
+ _mask = nomask
+ else:
+ _mask = make_mask(mask, copy=copy, flag=True)
+ #....Check shapes compatibility
+ if _mask is not nomask:
+ (nd, nm) = (_data.size, _mask.size)
+ if (nm != nd):
+ if nm == 1:
+ _mask = N.resize(_mask, _data.shape)
+ elif nd == 1:
+ _data = N.resize(_data, _mask.shape)
+ else:
+ msg = "Mask and data not compatible (size issues: %i & %i)."
+ raise MAError, msg % (nm, nd)
+ elif (_mask.shape != _data.shape):
+ mtslog.log(5,"__new__ from scratch: force _mask shape %s > %s" % \
+ (_mask.shape, _data.shape))
+ _mask.shape = _data.shape
+ #....
+ cls._fill_value = fill_value
+ cls._basemask = _mask
+ cls._basedates = _dates
+ cls._baseseries = _series
+ return _data.view(cls)
+# return _series.view(cls)
+ #..............
+ def __array_wrap__(self, obj, context=None):
+ """Special hook for ufuncs.
+Wraps the numpy array and sets the mask according to context.
+ """
+# return MaskedArray.__array_wrap__(obj, context=None)
+ return MaskedTimeSeries(obj, dates=self._dates, mask=self._mask,
+ fill_value=self._fill_value)
+
+ #..............
+ def __array_finalize__(self,obj):
+ mtslog.log(5, "__array_finalize__: obj is %s" % (type(obj), ))
+ if not hasattr(self, "_data"):
+ self._data = obj
+ if not hasattr(self, "_dates"):
+ self._dates = self._basedates
+ mtslog.log(5, "__array_finalize__: set dates to: %s - %s" % \
+ (id(self._dates), self._dates.ravel() ))
+ if not hasattr(self, "_mask"):
+ self._mask = self._basemask
+ mtslog.log(5, "__array_finalize__: set mask to: %s - %s" % \
+ (id(self._mask), self._mask.ravel() ))
+ if not hasattr(self, "_series"):
+ if hasattr(obj, "_series"):
+ self._series = obj._series
+ else:
+ self._series = obj
+ self.fill_value = self._fill_value
+ return
+
+ #------------------------------------------------------
+# def __mul__(self):
+ #------------------------------------------------------
+ def __str__(self):
+ """Calculate the str representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ if masked_print_option.enabled():
+ f = masked_print_option
+ # XXX: Without the following special case masked
+ # XXX: would print as "[--]", not "--". Can we avoid
+ # XXX: checks for masked by choosing a different value
+ # XXX: for the masked singleton? 2005-01-05 -- sasha
+ if self is masked:
+ return str(f)
+ m = self._mask
+ if m is nomask:
+ res = self._data
+ else:
+ if m.shape == () and m:
+ return str(f)
+ # convert to object array to make filled work
+ res = (self._series).astype("|O8")
+ res[self._mask] = f
+ else:
+ res = self.filled(self.fill_value)
+ return str(res)
+
+ def __repr__(self):
+ """Calculate the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ desc = """\
+timeseries(data =
+ %(data)s,
+ mask =
+ %(mask)s,
+ date =
+ %(time)s, )
+"""
+ desc_short = """\
+timeseries(data = %(data)s,
+ mask = %(mask)s,
+ date = %(time)s,)
+"""
+# if (self._mask is nomask) and (not self._mask.any()):
+# if self.ndim <= 1:
+# return without_mask1 % {'data':str(self.filled()),
+# 'time':str(self._dates.asstrings())}
+# return without_mask % {'data':str(self.filled()),
+# 'time':str(self._dates.asstrings())}
+# else:
+ if self.ndim <= 1:
+ return desc_short % {
+ 'data': str(self),
+ 'mask': str(self._mask),
+ 'time': str(self.dates),
+ }
+ return desc % {
+ 'data': str(self),
+ 'mask': str(self._mask),
+ 'time': str(self.dates),
+ }
+ #............................................
+ def ids (self):
+ """Return the ids of the data, dates and mask areas"""
+ return (id(self._series), id(self.dates), id(self._mask))
+ #............................................
+ @property
+ def maskedseries(self):
+ """Returns a masked array of the series (dates are omitteed)."""
+ return masked_array(self._series, mask=self._mask)
+ _mseries = maskedseries
+ #............................................
+ def filled(self, fill_value=None):
+ """A numeric array with masked values filled. If fill_value is None,
+ use self.fill_value().
+
+ If mask is nomask, copy data only if not contiguous.
+ Result is always a contiguous, numeric array.
+# Is contiguous really necessary now?
+ """
+ (d, m) = (self._data, self._mask)
+ if m is nomask:
+ return d
+ #
+ if fill_value is None:
+ value = self._fill_value
+ else:
+ value = fill_value
+ #
+ if self is masked_singleton:
+ return numeric.array(value)
+ #
+ result = d.copy()
+ try:
+ result.__setitem__(m, value)
+ except (TypeError, AttributeError):
+ #ok, can't put that value in here
+ value = numeric.array(value, dtype=object)
+ d = d.astype(object)
+ result = fromnumeric.choose(m, (d, value))
+ except IndexError:
+ #ok, if scalar
+ if d.shape:
+ raise
+ elif m:
+ result = numeric.array(value, dtype=d.dtype)
+ else:
+ result = d
+ return result
+ #............................................
+ def sum(self, axis=None, dtype=None):
+ """a.sum(axis=None, dtype=None)
+Sums the array `a` over the given axis `axis`.
+Masked values are set to 0.
+If `axis` is None, applies to a flattened version of the array.
+ """
+ if self._mask is nomask:
+ return self._data.sum(axis, dtype=dtype)
+ else:
+ if axis is None:
+ return self.filled(0).sum(None, dtype=dtype)
+ return MaskedArray(self.filled(0).sum(axis, dtype=dtype),
+ mask=self._mask.all(axis))
+
+ def cumsum(self, axis=None, dtype=None):
+ """a.cumprod(axis=None, dtype=None)
+Returns the cumulative sum of the elements of array `a` along the given axis `axis`.
+Masked values are set to 0.
+If `axis` is None, applies to a flattened version of the array.
+ """
+ if self._mask is nomask:
+ return self._data.cumsum(axis=axis, dtype=dtype)
+ else:
+ if axis is None:
+ return self.filled(0).cumsum(None, dtype=dtype)
+ return MaskedArray(self.filled(0).cumsum(axis=axis, dtype=dtype),
+ mask=self._mask)
+
+ def prod(self, axis=None, dtype=None):
+ """a.prod(axis=None, dtype=None)
+Returns the product of the elements of array `a` along the given axis `axis`.
+Masked elements are set to 1.
+If `axis` is None, applies to a flattened version of the array.
+ """
+ if self._mask is nomask:
+ return self._data.prod(axis=axis, dtype=dtype)
+ else:
+ if axis is None:
+ return self.filled(1).prod(None, dtype=dtype)
+ return MaskedArray(self.filled(1).prod(axis=axis, dtype=dtype),
+ mask=self._mask.all(axis))
+ product = prod
+
+ def cumprod(self, axis=None, dtype=None):
+ """a.cumprod(axis=None, dtype=None)
+Returns the cumulative product of ethe lements of array `a` along the given axis `axis`.
+Masked values are set to 1.
+If `axis` is None, applies to a flattened version of the array.
+ """
+ if self._mask is nomask:
+ return self._data.cumprod(axis=axis, dtype=dtype)
+ else:
+ if axis is None:
+ return self.filled(1).cumprod(None, dtype=dtype)
+ return MaskedArray(self.filled(1).cumprod(axis=axis, dtype=dtype),
+ mask=self._mask)
+
+ def mean(self, axis=None, dtype=None):
+ """mean(a, axis=None, dtype=None)
+Returns the arithmetic mean.
+
+The mean is the sum of the elements divided by the number of elements.
+ """
+ if self._mask is nomask:
+ return self._data.mean(axis=axis, dtype=dtype)
+ else:
+ sum = N.sum(self.filled(0), axis=axis, dtype=dtype)
+ cnt = self.count(axis=axis)
+ if axis is None:
+ if self._mask.all(None):
+ return masked
+ else:
+ return sum*1./cnt
+ return MaskedArray(sum*1./cnt, mask=self._mask.all(axis))
+
+ def anom(self, axis=None, dtype=None):
+ """a.anom(axis=None, dtype=None)
+Returns the anomalies, or deviation from the average.
+ """
+ m = self.mean(axis, dtype)
+ if not axis:
+ return (self - m)
+ else:
+ return (self - N.expand_dims(m,axis))
+
+ def var(self, axis=None, dtype=None):
+ """a.var(axis=None, dtype=None)
+Returns the variance, a measure of the spread of a distribution.
+
+The variance is the average of the squared deviations from the mean,
+i.e. var = mean((x - x.mean())**2).
+ """
+ if self._mask is nomask:
+ return MaskedArray(self._data.var(axis=axis, dtype=dtype),
+ mask=nomask)
+ else:
+ cnt = self.count(axis=axis)
+ anom = self.anom(axis=axis, dtype=dtype)
+ anom *= anom
+ dvar = anom.sum(axis)
+ dvar /= cnt
+ if axis is None:
+ return dvar
+ return MaskedArray(dvar, mask=mask_or(self._mask.all(axis), (cnt==1)))
+
+ def std(self, axis=None, dtype=None):
+ """a.std(axis=None, dtype=None)
+Returns the standard deviation, a measure of the spread of a distribution.
+
+The standard deviation is the square root of the average of the squared
+deviations from the mean, i.e. std = sqrt(mean((x - x.mean())**2)).
+ """
+ var = self.var(axis,dtype)
+ if axis is None:
+ if var is masked:
+ return masked
+ else:
+ return N.sqrt(var)
+ return MaskedArray(N.sqrt(var._data), mask=var._mask)
+
+ def varu(self, axis=None, dtype=None):
+ """a.var(axis=None, dtype=None)
+Returns an unbiased estimate of the variance.
+
+Instead of dividing the sum of squared anomalies by n, the number of elements,
+this sum is divided by n-1.
+ """
+ cnt = self.count(axis=axis)
+ anom = self.anom(axis=axis, dtype=dtype)
+ anom *= anom
+ var = anom.sum(axis)
+ var /= (cnt-1)
+ if axis is None:
+ return var
+ return MaskedArray(var, mask=mask_or(self._mask.all(axis), (cnt==1)))
+
+ def stdu(self, axis=None, dtype=None):
+ """a.var(axis=None, dtype=None)
+Returns an unbiased estimate of the standard deviation.
+ """
+ var = self.varu(axis,dtype)
+ if axis is None:
+ if var is masked:
+ return masked
+ else:
+ return N.sqrt(var)
+ return MaskedArray(N.sqrt(var._data), mask=var._mask)
+ #............................................
+ def asrecords(self):
+ """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+ """
+ desctype = [('_dates','|O8'), ('_series',self.dtype), ('_mask',N.bool_)]
+ flat = self.ravel()
+ if flat.size > 0:
+ return recfromarrays([flat._dates, flat._series, getmaskarray(flat)],
+ dtype=desctype,
+ shape = (flat.size,),
+ )
+ else:
+ return recfromarrays([[], [], []], dtype=desctype,
+ shape = (flat.size,),
+ )
+
+
+# def reshape (self, *s):
+# """This array reshaped to shape s"""
+# self._data = self._data.reshape(*s)
+# self._dates = self._dates.reshape(*s)
+# if self._mask is not nomask:
+# self._mask = self._mask.reshape(*s)
+# return self.view()
+#### --------------------------------------------------------------------------
+#--- ... Pickling ...
+#### --------------------------------------------------------------------------
+def _mtsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+ """Internal function that builds a new MaskedArray from the information stored
+in a pickle."""
+# raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+ _series = ndarray.__new__(ndarray, baseshape, basetype)
+ _dates = ndarray.__new__(datesclass, baseshape, '|O8')
+ _mask = ndarray.__new__(ndarray, baseshape, '|O8')
+ return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask,
+ dtype=basetype, fill_value=fill_value)
+#
+def _mtsgetstate(a):
+ "Returns the internal state of the TimeSeries, for pickling purposes."
+# raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+ records = a.asrecords()
+ state = (1,
+ a.shape,
+ a.dtype,
+ records.flags.fnc,
+ a.fill_value,
+ records
+ )
+ return state
+#
+def _mtssetstate(a, state):
+ """Restores the internal state of the TimeSeries, for pickling purposes.
+`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+
+ - class name
+ - a tuple giving the shape of the data
+ - a typecode for the data
+ - a binary string for the data
+ - a binary string for the mask.
+ """
+ (ver, shp, typ, isf, flv, rec) = state
+ a.fill_value = flv
+ a._data._series = a._series = N.asarray(rec['_series'])
+ a._data._series.shape = a._series.shape = shp
+ a._data._dates = a._dates = a._dates.__class__(rec['_dates'])
+ a._data._dates.shape = a._dates.shape = shp
+ (a._dates)._asstrings = None
+ a._mask = N.array(rec['_mask'], dtype=MA.MaskType)
+ a._mask.shape = shp
+#
+def _mtsreduce(a):
+ """Returns a 3-tuple for pickling a MaskedArray."""
+ return (_mtsreconstruct,
+ (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+ a.__getstate__())
+#
+MaskedTimeSeries.__getstate__ = _mtsgetstate
+MaskedTimeSeries.__setstate__ = _mtssetstate
+MaskedTimeSeries.__reduce__ = _mtsreduce
+MaskedTimeSeries.__dump__ = dump
+MaskedTimeSeries.__dumps__ = dumps
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, mask=nomask, copy=False, fill_value=None):
+ """Creates a TimeSeries object
+
+:Parameters:
+ `dates` : ndarray
+ Array of dates.
+ `data` :
+ Array of data.
+ """
+ if isinstance(data, MaskedTimeSeries):
+ if not copy:
+ data._mask = mask_or(data._mask, mask)
+ return data
+ _data = data._data
+ _mask = mask_or(data._mask, mask)
+ _dates = _data.dates
+ elif isinstance(data, TimeSeries):
+ _data = data._series
+ _mask = make_mask(mask)
+ _dates = data.dates
+ else:
+ data = masked_array(data, copy=False)
+ _data = data._data
+ _mask = mask_or(data._mask, mask)
+ if dates is None:
+ _dates = fake_dates(data.size)
+ else:
+ _dates = time_array(dates)
+ return MaskedTimeSeries(_data, dates=_dates, mask=_mask, copy=copy,
+ fill_value=fill_value)
+
+
+#
+
+#### --------------------------------------------------------------------------
+#--- ... Additional functions ...
+#### --------------------------------------------------------------------------
+def check_dates(a,b):
+ """Returns the array of dates from the two objects `a` or `b` (or None)."""
+ if isTimeSeries(a):
+ if isTimeSeries(b) and (a._dates == b._dates).all() is False:
+ raise ValueError, "Incompatible dates !"
+ return a._dates
+ elif isTimeSeries(b):
+ return b._dates
+ else:
+ return
+
+def parse_period(period):
+ """Returns a TimeArray couple (starting date; ending date) from the arguments."""
+#### print "........DEBUG PARSE DATES: period %s is %s" % (period, type(period))
+# if isinstance(period,TimeArray) or isinstance(period,Dates):
+#### print "........DEBUG PARSE_PERIOD: OK"
+ if isinstance(period,TimeArray):
+ return (period[0],period[-1])
+ elif hasattr(period,"__len__"):
+ if not isinstance(period[0], TimeArray):
+ tstart = TimeArray(period[0])
+ else:
+ tstart = period[0]
+ if not isinstance(period[-1], TimeArray):
+ tend = TimeArray(period[-1])
+ else:
+ tend = period[-1]
+ return (tstart, tend)
+ else:
+ p = N.asarray(period)
+ if N.all(p < 9999):
+ p = N.array(period,dtype="|S4")
+ p = time_array(p)
+ return (p[0], p[-1])
+
+def where_period(period, dates, *choices):
+ """Returns choices fro True/False, whether dates fall during a given period.
+If no choices are given, outputs the array indices for the dates falling in the
+period.
+
+:Parameters:
+ `period` : Sequence
+ Selection period, as a sequence (starting date, ending date).
+ `dates` : TimeArray
+ Array of dates.
+ `choices` : *(optional)*
+ Arrays to select from when the condition is True/False.
+ """
+ (tstart, tend) = parse_period(period)
+ condition = ascondition((dates>=tstart)&(dates<=tend))
+ condition = (dates>=tstart)&(dates<=tend)
+ return N.where(condition, *choices)
+
+def masked_inside_period(data, period, dates=None):
+ """Returns x as an array masked where dates fall inside the selection period,
+as well as where data are initially missing (masked)."""
+ (tstart, tend) = parse_period(period)
+ # Get dates ..................
+ if hasattr(data, "_dates"):
+ dates = data._dates
+ elif dates is None:
+ raise ValueError,"Undefined dates !"
+ else:
+ assert(N.size(dates)==N.size(data),
+ "Inconsistent data and dates sizes!")
+ # where_period yields True inside the period, when mask should yield False
+ condition = ascondition(N.logical_and((dates>=tstart), (dates<=tend)))
+ cm = filled(condition,True).reshape(data.shape)
+ mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+ if isinstance(data, MaskedTimeSeries):
+ return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+ if isinstance(data, TimeSeries):
+ return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+ else:
+ return masked_array(data, mask=mask, copy=True)
+
+def masked_outside_period(data, period, dates=None):
+ """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked)."""
+ (tstart, tend) = parse_period(period)
+ if hasattr(data, "_dates"):
+ dates = data._dates
+ elif dates is None:
+ raise ValueError,"Undefined dates !"
+ else:
+ assert(N.size(dates)==N.size(data),
+ "Inconsistent data and dates sizes!")
+ #................
+ condition = ascondition(N.logical_or((dates<tstart),(dates>tend)))
+ cm = filled(condition,True).reshape(data.shape)
+ mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+ if isinstance(data, MaskedTimeSeries):
+ return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+ if isinstance(data, TimeSeries):
+ return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+ else:
+ return masked_array(data, mask=mask, copy=True)
+
+#..............................................................................
+def fill_missing_dates(dates,data,resolution=None,fill_value=None):
+ """Finds and fills the missing dates in a time series, and allocates a
+default filling value to the data corresponding to the newly added dates.
+
+:Parameters:
+ `dates`
+ Initial array of dates.
+ `data`
+ Initial array of data.
+ `resolution` : float *[None]*
+ New date resolutions, in years. For example, a value of 1/365.25 indicates
+ a daily resolution. If *None*, the initial resolution is used instead.
+ `fill_value` : float
+ Default value for missing data.
+ """
+ if not isinstance(dates, TimeArray):
+ print "DEBUG FILL_MISSING_DATES: dates was %s" % type(dates)
+ dates = TimeArray(dates)
+ print "DEBUG FILL_MISSING_DATES: dates is %s" % type(dates)
+ dflat = dates.ravel()
+ n = len(dflat)
+ # Get data ressolution .......
+ if resolution is None:
+ resolution = dflat.resolution
+ if resolution >= 28 and resolution <= 31:
+ resolution = 30
+ else:
+ resolution = int(1./float(resolution))
+ # Find on what to fill .......
+ if resolution == 1:
+ (resol, freq, refdelta) = (DAILY, 1, dtmdelta.relativedelta(days=+1))
+ elif resolution == 7:
+ (resol, freq, refdelta) = (WEEKLY, 1, dtmdelta.relativedelta(days=+7))
+ elif resolution == 30:
+ (resol, freq, refdelta) = (MONTHLY, 1, dtmdelta.relativedelta(months=+1))
+ elif resolution == 365:
+ (resol, freq, refdelta) = (YEARLY, 1, dtmdelta.relativedelta(years=+1))
+ else:
+ raise ValueError,\
+ "Unable to define a proper date resolution (found %s)." % resolution
+ # ...and now, fill it ! ......
+ (tstart, tend) = dflat.asobjects()[[0,-1]].tolist()
+ gaprule = dtmrule.rrule(resol, interval=freq, dtstart=tstart, until=tend)
+ newdates = dates.__class__(list(gaprule))
+ #.............................
+ # Get the steps between consecutive data. We need relativedelta to deal w/ months
+ delta = N.array([dtmdelta.relativedelta(b,a)
+ for (b,a) in N.broadcast(dflat[1:],dflat[:-1])])
+ dOK = N.equal(delta,refdelta)
+ slcid = N.r_[[0,], N.arange(1,n).compress(-dOK), [n,]]
+ oldslc = N.array([slice(i,e) for (i,e) in N.broadcast(slcid[:-1],slcid[1:])])
+ if resolution == 1:
+ addidx = N.cumsum([d.days for d in N.diff(dflat).compress(-dOK)])
+ elif resolution == 30:
+ addidx = N.cumsum([d.years*12+d.months for d in delta.compress(-dOK)])
+ elif resolution == 365:
+ addidx = N.cumsum([d.years for d in delta.compress(-dOK)])
+ addidx -= N.arange(len(addidx))
+ newslc = N.r_[[oldslc[0]],
+ [slice(i+d-1,e+d-1) for (i,e,d) in \
+ N.broadcast(slcid[1:-1],slcid[2:],addidx)]
+ ]
+# misslc = [slice(i,i+d-1)
+# for (i,d) in N.broadcast(slcid[1:-1],addidx)]
+ #.............................
+ # Just a quick check
+ for (osl,nsl) in zip(oldslc,newslc):
+ assert N.equal(dflat[osl],newdates[nsl]).all(),\
+ "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+ #.............................
+ data = MA.asarray(data)
+ oldmask = MA.getmaskarray(data)
+ newdata = N.empty(newdates.size,data.dtype)
+ newmask = N.ones(newdates.size, bool_)
+ if fill_value is None:
+ if hasattr(data,'fill_value'):
+ fill_value = data.fill_value
+ else:
+ fill_value = MA.default_fill_value(data)
+ data = data.filled(fill_value)
+ newdata.fill(fill_value)
+ #....
+ for (new,old) in zip(newslc,oldslc):
+ newdata[new] = data[old]
+ newmask[new] = oldmask[old]
+# for mis in misslc:
+# newdata[mis].fill(fill_value)
+ # Get new shape ..............
+ if data.ndim == 1:
+ nshp = (newdates.size,)
+ else:
+ nshp = tuple([-1,] + list(data.shape[1:]))
+ return MaskedTimeSeries(newdata.reshape(nshp),
+ dates=newdates.reshape(nshp),
+ mask=newmask.reshape(nshp),
+ fill_value=fill_value)
+
+######--------------------------------------------------------------------------
+##---- --- Archiving ---
+######--------------------------------------------------------------------------
+#import iodata.iotools as iotools
+#def archive(timeseries,filename,compression=None):
+# data = timeseries.asrecords()
+# iotools.archive(data, filename, compression)
+#
+#def unarchive(filename):
+# raise NotImplementedError
+
+
+###############################################################################
\ No newline at end of file
Added: trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,159 @@
+import numpy
+import maskedarray as MA
+
+
+#####---------------------------------------------------------------------------
+#---- --- Generic functions ---
+#####---------------------------------------------------------------------------
+def first_unmasked_val(a):
+ "Returns the first unmasked value in a 1d maskedarray."
+ (i,j) = MA.extras.flatnotmasked_edges(a)
+ return a[i]
+
+def last_unmasked_val(a):
+ "Returns the last unmasked value in a 1d maskedarray."
+ (i,j) = MA.extras.flatnotmasked_edges(a)
+ return a[j]
+
+def reverse_dict(d):
+ "Reverses the keys and values of a dictionary."
+ alt = []
+ tmp = [alt.extend([(w,k) for w in v]) for (k,v) in d.iteritems()]
+ return dict(alt)
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Option conversion ---
+#####---------------------------------------------------------------------------
+obs_dict = {"UNDEFINED":None,
+ "UNDEF":None,
+ "BEGIN": first_unmasked_val,
+ "BEGINNING": first_unmasked_val,
+ "END": last_unmasked_val,
+ "ENDING": last_unmasked_val,
+ "AVERAGED": MA.average,
+ "AVERAGE": MA.average,
+ "MEAN": MA.average,
+ "SUMMED": MA.sum,
+ "SUM": MA.sum,
+ "MAXIMUM": MA.maximum,
+ "MAX": MA.maximum,
+ "MINIMUM": MA.minimum,
+ "MIN": MA.minimum,
+ }
+obsDict = obs_dict
+#
+def fmtObserv(obStr):
+ "Converts a possible 'Observed' string into acceptable values."
+ if obStr is None:
+ return None
+ elif obStr.upper() in obs_dict.keys():
+ return obStr.upper()
+ else:
+ raise ValueError("Invalid value for observed attribute: %s " % str(obStr))
+
+
+fmtfreq_dict = {'A': ['ANNUAL','ANNUALLY','YEAR','YEARLY'],
+ 'B': ['BUSINESS','BUSINESSLYT'],
+ 'D': ['DAY','DAILY',],
+ 'H': ['HOUR','HOURLY',],
+ 'M': ['MONTH','MONTHLY',],
+ 'Q': ['QUARTER','QUARTERLY',],
+ 'S': ['SECOND','SECONDLY',],
+ 'T': ['MINUTE','MINUTELY',],
+ 'W': ['WEEK','WEEKLY',],
+ 'U': ['UNDEF','UNDEFINED'],
+ }
+fmtfreq_revdict = reverse_dict(fmtfreq_dict)
+
+def fmtFreq (freqStr):
+ "Converts a possible 'frequency' string to acceptable values."
+ if freqStr is None:
+ return None
+ elif freqStr.upper() in fmtfreq_dict.keys():
+ return freqStr[0].upper()
+ elif freqStr.upper() in fmtfreq_revdict.keys():
+ return fmtfreq_revdict[freqStr.upper()]
+ else:
+ raise ValueError("Invalid frequency: %s " % str(freqStr))
+
+class DateSpec:
+ "Fake data type for date variables."
+ def __init__(self, freq):
+ self.freq = fmtFreq(freq)
+
+ def __hash__(self):
+ return hash(self.freq)
+
+ def __eq__(self, other):
+ if hasattr(other, "freq"):
+ return self.freq == other.freq
+ else:
+ return False
+ def __str__(self):
+ return "Date(%s)" % str(self.freq)
+
+
+
+# define custom numpy types.
+# Note: A more robust approach would register these as actual valid numpy types
+# this is just a hack for now
+numpy.dateA = DateSpec("Annual")
+numpy.dateB = DateSpec("Business")
+numpy.dateD = DateSpec("Daily")
+numpy.dateH = DateSpec("Hourly")
+numpy.dateM = DateSpec("Monthly")
+numpy.dateQ = DateSpec("Quarterly")
+numpy.dateS = DateSpec("Secondly")
+numpy.dateT = DateSpec("Minutely")
+numpy.dateW = DateSpec("Weekly")
+numpy.dateU = DateSpec("Undefined")
+
+
+freq_type_mapping = {'A': numpy.dateA,
+ 'B': numpy.dateB,
+ 'D': numpy.dateD,
+ 'H': numpy.dateH,
+ 'M': numpy.dateM,
+ 'Q': numpy.dateQ,
+ 'S': numpy.dateS,
+ 'T': numpy.dateT,
+ 'W': numpy.dateW,
+ 'U': numpy.dateU,
+ }
+
+def freqToType(freq):
+ return freq_type_mapping[fmtFreq(freq)]
+
+def isDateType(dtype):
+ #TODO: That looks messy. We should simplify that
+ if len([x for x in freq_type_mapping.values() if x == dtype]) > 0:
+ return True
+ else:
+ return False
+
+#####---------------------------------------------------------------------------
+#---- --- Misc functions ---
+#####---------------------------------------------------------------------------
+#def flatten(listOfLists):
+# return list(chain(*listOfLists))
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten(iterable):
+ """Flattens a compound of nested iterables."""
+ itm = iter(iterable)
+ for elm in itm:
+ if hasattr(elm,'__iter__') and not isinstance(elm, basestring):
+ for f in flatten(elm):
+ yield f
+ else:
+ yield elm
+
+def flatargs(*args):
+ "Flattens the arguments."
+ if not hasattr(args, '__iter__'):
+ return args
+ else:
+ return flatten(args)
+
+
Added: trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1054 @@
+"""
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__ = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+import datetime
+import itertools
+import warnings
+
+
+import numpy
+from numpy import bool_, float_, int_, object_
+from numpy import ndarray
+import numpy.core.numeric as numeric
+import numpy.core.fromnumeric as fromnumeric
+
+import maskedarray as MA
+#reload(MA)
+
+import tscore as corelib
+#reload(corelib)
+from tscore import isDateType
+
+#from corelib import fmtFreq, freqToType
+import mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#TODO: - it's possible to change the freq on the fly, e.g. mydateD.freq = 'A'
+#TODO: ...We can prevent that by making freq a readonly property, or call dateOf
+#TODO: ...or decide it's OK
+#TODO: - Do we still need dateOf, or should we call cseries.convert instead ?
+#TODO: - It'd be really nice if cseries.convert could accpt an array,
+#TODO: ...that way, we could call it instead of looping in asfreq
+
+secondlyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(minute=1)
+hourlyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(hour=1)
+
+#####---------------------------------------------------------------------------
+#---- --- Date Exceptions ---
+#####---------------------------------------------------------------------------
+class DateError(Exception):
+ """Defines a generic DateArrayError."""
+ def __init__ (self, args=None):
+ "Create an exception"
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculate the string representation"
+ return str(self.args)
+ __repr__ = __str__
+
+class InsufficientDateError(DateError):
+ """Defines the exception raised when there is not enough information
+ to create a Date object."""
+ def __init__(self, msg=None):
+ if msg is None:
+ msg = "Insufficient parameters given to create a date at the given frequency"
+ DateError.__init__(self, msg)
+
+class FrequencyDateError(DateError):
+ """Defines the exception raised when the frequencies are incompatible."""
+ def __init__(self, msg, freql=None, freqr=None):
+ msg += " : Incompatible frequencies!"
+ if not (freql is None or freqr is None):
+ msg += " (%s<>%s)" % (freql, freqr)
+ DateError.__init__(self, msg)
+
+class ArithmeticDateError(DateError):
+ """Defines the exception raised when dates are used in arithmetic expressions."""
+ def __init__(self, msg=''):
+ msg += " Cannot use dates for arithmetics!"
+ DateError.__init__(self, msg)
+
+#####---------------------------------------------------------------------------
+#---- --- Date Class ---
+#####---------------------------------------------------------------------------
+
+class Date:
+ """Defines a Date object, as the combination of a date and a frequency.
+ Several options are available to construct a Date object explicitly:
+
+ - Give appropriate values to the `year`, `month`, `day`, `quarter`, `hours`,
+ `minutes`, `seconds` arguments.
+
+ >>> td.Date(freq='Q',year=2004,quarter=3)
+ >>> td.Date(freq='D',year=2001,month=1,day=1)
+
+ - Use the `string` keyword. This method calls the `mx.DateTime.Parser`
+ submodule, more information is available in its documentation.
+
+ >>> ts.Date('D', '2007-01-01')
+
+ - Use the `mxDate` keyword with an existing mx.DateTime.DateTime object, or
+ even a datetime.datetime object.
+
+ >>> td.Date('D', mxDate=mx.DateTime.now())
+ >>> td.Date('D', mxDate=datetime.datetime.now())
+ """
+ def __init__(self, freq, year=None, month=None, day=None, quarter=None,
+ hours=None, minutes=None, seconds=None,
+ mxDate=None, value=None, string=None):
+
+ if hasattr(freq, 'freq'):
+ self.freq = corelib.fmtFreq(freq.freq)
+ else:
+ self.freq = corelib.fmtFreq(freq)
+ self.type = corelib.freqToType(self.freq)
+
+ if value is not None:
+ if self.freq == 'A':
+ self.mxDate = mxD.Date(value, -1, -1)
+ elif self.freq == 'B':
+ value -= 1
+ self.mxDate = mxD.DateTimeFromAbsDays(value + (value//5)*7 - (value//5)*5)
+ elif self.freq in ['D','U']:
+ self.mxDate = mxD.DateTimeFromAbsDays(value-1)
+ elif self.freq == 'H':
+ self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+ elif self.freq == 'M':
+ self.mxDate = mxD.Date(0) + \
+ mxD.RelativeDateTime(months=value-1, day=-1)
+ elif self.freq == 'Q':
+ self.mxDate = mxD.Date(0) + \
+ mxD.RelativeDateTime(years=(value // 4),
+ month=((value * 3) % 12), day=-1)
+ elif self.freq == 'S':
+ self.mxDate = secondlyOriginDate + mxD.DateTimeDeltaFromSeconds(value)
+ elif self.freq == 'T':
+ self.mxDate = minutelyOriginDate + mxD.DateTimeDeltaFrom(minutes=value)
+ elif self.freq == 'W':
+ self.mxDate = mxD.Date(0) + \
+ mxD.RelativeDateTime(weeks=value-5./7-1)
+
+ elif string is not None:
+ self.mxDate = mxDFromString(string)
+
+ elif mxDate is not None:
+ if isinstance(mxDate, datetime.datetime):
+ mxDate = mxD.strptime(mxDate.isoformat()[:19], "%Y-%m-%dT%H:%M:%S")
+ self.mxDate = mxDate
+
+ else:
+ # First, some basic checks.....
+ if year is None:
+ raise InsufficientDateError
+ if self.freq in ('B', 'D', 'W'):
+ if month is None or day is None:
+ raise InsufficientDateError
+ elif self.freq == 'M':
+ if month is None:
+ raise InsufficientDateError
+ day = -1
+ elif self.freq == 'Q':
+ if quarter is None:
+ raise InsufficientDateError
+ month = quarter * 3
+ day = -1
+ elif self.freq == 'A':
+ month = -1
+ day = -1
+ elif self.freq == 'S':
+ if month is None or day is None or seconds is None:
+ raise InsufficientDateError
+
+ if self.freq in ['A','B','D','M','Q','W']:
+ self.mxDate = mxD.Date(year, month, day)
+ if self.freq == 'B':
+ if self.mxDate.day_of_week in [5,6]:
+ raise ValueError("Weekend passed as business day")
+ elif self.freq in ['H','S','T']:
+ if not hours:
+ if not minutes:
+ if not seconds:
+ hours = 0
+ else:
+ hours = seconds//3600
+ else:
+ hours = minutes // 60
+ if not minutes:
+ if not seconds:
+ minutes = 0
+ else:
+ minutes = (seconds-hours*3600)//60
+ if not seconds:
+ seconds = 0
+ else:
+ seconds = seconds % 60
+ self.mxDate = mxD.Date(year, month, day, hours, minutes, seconds)
+ self.value = self.__value()
+ # FIXME: Shall we set them as properties ?
+ def day(self):
+ "Returns the day of month."
+ return self.mxDate.day
+ def day_of_week(self):
+ "Returns the day of week."
+ return self.mxDate.day_of_week
+ def day_of_year(self):
+ "Returns the day of year."
+ return self.mxDate.day_of_year
+ def month(self):
+ "Returns the month."
+ return self.mxDate.month
+ def quarter(self):
+ "Returns the quarter."
+ return monthToQuarter(self.mxDate.month)
+ def year(self):
+ "Returns the year."
+ return self.mxDate.year
+ def second(self):
+ "Returns the seconds."
+ return int(self.mxDate.second)
+ def minute(self):
+ "Returns the minutes."
+ return int(self.mxDate.minute)
+ def hour(self):
+ "Returns the hour."
+ return int(self.mxDate.hour)
+ def week(self):
+ "Returns the week."
+ return self.mxDate.iso_week[1]
+
+ def __add__(self, other):
+ if isinstance(other, Date):
+ raise FrequencyDateError("Cannot add dates", self.freq, other.freq)
+ return Date(freq=self.freq, value=int(self) + other)
+
+ def __radd__(self, other):
+ return self+other
+
+ def __sub__(self, other):
+ if isinstance(other, Date):
+ if self.freq != other.freq:
+ raise FrequencyDateError("Cannot subtract dates", \
+ self.freq, other.freq)
+ else:
+ return int(self) - int(other)
+ else:
+ return self + (-1) * int(other)
+
+ def __eq__(self, other):
+ if not hasattr(other, 'freq'):
+ return False
+ elif self.freq != other.freq:
+ raise FrequencyDateError("Cannot subtract dates", \
+ self.freq, other.freq)
+ return int(self) == int(other)
+
+ def __cmp__(self, other):
+ if not hasattr(other, 'freq'):
+ return False
+ elif self.freq != other.freq:
+ raise FrequencyDateError("Cannot subtract dates", \
+ self.freq, other.freq)
+ return int(self)-int(other)
+
+ def __hash__(self):
+ return hash(int(self)) ^ hash(self.freq)
+
+ def __int__(self):
+ return self.value
+
+ def __float__(self):
+ return float(self.value)
+
+ def __value(self):
+ "Converts the date to an integer, depending on the current frequency."
+ # Annual .......
+ if self.freq == 'A':
+ val = int(self.mxDate.year)
+ # Business days.
+ elif self.freq == 'B':
+ days = self.mxDate.absdate
+ weeks = days // 7
+ val = int((weeks*5) + (days - weeks*7))
+ # Daily/undefined
+ elif self.freq in ['D', 'U']:
+ val = self.mxDate.absdate
+ # Hourly........
+ elif self.freq == 'H':
+ val = int((self.mxDate - hourlyOriginDate).hours)
+ # Monthly.......
+ elif self.freq == 'M':
+ val = self.mxDate.year*12 + self.mxDate.month
+ # Quarterly.....
+ elif self.freq == 'Q':
+ val = int(self.mxDate.year*4 + self.mxDate.month/3)
+ # Secondly......
+ elif self.freq == 'S':
+ val = int((self.mxDate - secondlyOriginDate).seconds)
+ # Minutely......
+ elif self.freq == 'T':
+ val = int((self.mxDate - minutelyOriginDate).minutes)
+ # Weekly........
+ elif self.freq == 'W':
+ val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+ return val
+ #......................................................
+ def default_fmtstr(self):
+ "Defines the default formats for printing Dates."
+ if self.freq == "A":
+ fmt = "%Y"
+ elif self.freq in ("B","D"):
+ fmt = "%d-%b-%y"
+ elif self.freq == "M":
+ fmt = "%b-%Y"
+ elif self.freq == "Q":
+ fmt = "%YQ%q"
+ elif self.freq in ("H","S","T"):
+ fmt = "%d-%b-%Y %H:%M:%S"
+ elif self.freq == "W":
+ fmt = "%YW%W"
+ else:
+ fmt = "%d-%b-%y"
+ return fmt
+
+ def strfmt(self, fmt):
+ "Formats the date"
+ qFmt = fmt.replace("%q", "XXXX")
+ tmpStr = self.mxDate.strftime(qFmt)
+ return tmpStr.replace("XXXX", str(self.quarter()))
+
+ def __str__(self):
+ return self.strfmt(self.default_fmtstr())
+
+ def __repr__(self):
+ return "<%s : %s>" % (str(self.freq), str(self))
+ #......................................................
+ def toordinal(self):
+ "Returns the date as an ordinal."
+ return self.mxDate.absdays
+
+ def fromordinal(self, ordinal):
+ "Returns the date as an ordinal."
+ return Date(self.freq, mxDate=mxD.DateTimeFromAbsDays(ordinal))
+
+ def tostring(self):
+ "Returns the date as a string."
+ return str(self)
+
+ def toobject(self):
+ "Returns the date as itself."
+ return self
+
+ def asfreq(self, toFreq, relation='before'):
+ """Converts the date to a new frequency."""
+ return dateOf(self, toFreq, relation)
+
+ def isvalid(self):
+ "Returns whether the DateArray is valid: no missing/duplicated dates."
+ # A date is always valid by itself, but we need the object to support the function
+ # when we're working with singletons.
+ return True
+
+#####---------------------------------------------------------------------------
+#---- --- Functions ---
+#####---------------------------------------------------------------------------
+def monthToQuarter(monthNum):
+ """Returns the quarter corresponding to the month `monthnum`.
+ For example, December is the 4th quarter, Januray the first."""
+ return int((monthNum-1)/3)+1
+
+def thisday(freq):
+ "Returns today's date, at the given frequency `freq`."
+ freq = corelib.fmtFreq(freq)
+ tempDate = mxD.now()
+ # if it is Saturday or Sunday currently, freq==B, then we want to use Friday
+ if freq == 'B' and tempDate.day_of_week >= 5:
+ tempDate -= (tempDate.day_of_week - 4)
+ if freq in ('B','D','H','S','T','W'):
+ return Date(freq, mxDate=tempDate)
+ elif freq == 'M':
+ return Date(freq, year=tempDate.year, month=tempDate.month)
+ elif freq == 'Q':
+ return Date(freq, year=tempDate.year, quarter=monthToQuarter(tempDate.month))
+ elif freq == 'A':
+ return Date(freq, year=tempDate.year)
+today = thisday
+
+def prevbusday(day_end_hour=18, day_end_min=0):
+ "Returns the previous business day."
+ tempDate = mxD.localtime()
+ dateNum = tempDate.hour + float(tempDate.minute)/60
+ checkNum = day_end_hour + float(day_end_min)/60
+ if dateNum < checkNum:
+ return thisday('B') - 1
+ else:
+ return thisday('B')
+
+def dateOf(date, toFreq, relation="BEFORE"):
+ """Returns a date converted to another frequency `toFreq`, according to the
+ relation `relation` ."""
+ toFreq = corelib.fmtFreq(toFreq)
+ _rel = relation.upper()[0]
+ if _rel not in ['B', 'A']:
+ msg = "Invalid relation '%s': Should be in ['before', 'after']"
+ raise ValueError, msg % relation
+ elif _rel == 'B':
+ before = True
+ else:
+ before = False
+
+ if not isDateType(date):
+ raise DateError, "Date should be a valid Date instance!"
+
+ if date.freq == toFreq:
+ return date
+ # Convert to annual ....................
+ elif toFreq == 'A':
+ return Date(freq='A', year=date.year())
+ # Convert to quarterly .................
+ elif toFreq == 'Q':
+ if date.freq == 'A':
+ if before:
+ return Date(freq='A', year=date.year(), quarter=1)
+ else:
+ return Date(freq='A', year=date.year(), quarter=4)
+ else:
+ return Date(freq='Q', year=date.year(), quarter=date.quarter())
+ # Convert to monthly....................
+ elif toFreq == 'M':
+ if date.freq == 'A':
+ if before:
+ return Date(freq='M', year=date.year(), month=1)
+ else:
+ return Date(freq='M', year=date.year(), month=12)
+ elif date.freq == 'Q':
+ if before:
+ return dateOf(date-1, 'M', "AFTER")+1
+ else:
+ return Date(freq='M', year=date.year(), month=date.month())
+ else:
+ return Date(freq='M', year=date.year(), month=date.month())
+ # Convert to weekly ....................
+ elif toFreq == 'W':
+ if date.freq == 'A':
+ if before:
+ return Date(freq='W', year=date.year(), month=1, day=1)
+ else:
+ return Date(freq='W', year=date.year(), month=12, day=-1)
+ elif date.freq in ['Q','M']:
+ if before:
+ return dateOf(date-1, 'W', "AFTER")+1
+ else:
+ return Date(freq='W', year=date.year(), month=date.month())
+ else:
+ val = date.weeks() + int(date.year()*365.25/7.-1)
+ return Date(freq='W', value=val)
+ # Convert to business days..............
+ elif toFreq == 'B':
+ if date.freq in ['A','Q','M','W']:
+ if before:
+ return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+ else:
+ return dateOf(dateOf(date, 'D', "AFTER"), 'B', "BEFORE")
+ elif date.freq == 'D':
+ # BEFORE result: preceeding Friday if date is a weekend, same day otherwise
+ # AFTER result: following Monday if date is a weekend, same day otherwise
+ tempDate = date.mxDate
+ if before:
+ if tempDate.day_of_week >= 5:
+ tempDate -= (tempDate.day_of_week - 4)
+ else:
+ if tempDate.day_of_week >= 5:
+ tempDate += 7 - tempDate.day_of_week
+ return Date(freq='B', mxDate=tempDate)
+ else:
+ if before:
+ return dateOf(dateOf(date, 'D'), 'B', "BEFORE")
+ else:
+ return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+ # Convert to day .......................
+ elif toFreq == 'D':
+ # ...from annual
+ if date.freq == 'A':
+ if before:
+ return Date(freq='D', year=date.year(), month=1, day=1)
+ else:
+ return Date(freq='D', year=date.year(), month=12, day=31)
+ # ...from quarter
+ elif date.freq == 'Q':
+ if before:
+ return dateOf(date-1, 'D', "AFTER")+1
+ else:
+ return Date(freq='D', year=date.year(), month=date.month(),
+ day=date.day())
+ # ...from month
+ elif date.freq == 'M':
+ if before:
+ return Date(freq='D', year=date.year(), month=date.month(), day=1)
+ else:
+ (mm,yy) = (date.month(), date.year())
+ if date.month() == 12:
+ (mm, yy) = (1, yy + 1)
+ else:
+ mm = mm + 1
+ return Date('D', year=yy, month=mm, day=1)-1
+ # ...from week
+ elif date.freq == 'W':
+ if before:
+ return Date(freq='D', year=date.year(), month=date.month(),
+ day=date.day())
+ else:
+ ndate = date + 1
+ return Date(freq='D', year=ndate.year(), month=ndate.month(),
+ day=ndate.day())
+ # ...from a lower freq
+ else:
+ return Date('D', year=date.year(), month=date.month(), day=date.day())
+ #Convert to hour........................
+ elif toFreq == 'H':
+ if date.freq in ['A','Q','M','W']:
+ if before:
+ return dateOf(dateOf(date, 'D', "BEFORE"), 'H', "BEFORE")
+ else:
+ return dateOf(dateOf(date, 'D', "AFTER"), 'H', "AFTER")
+ if date.freq in ['B','D']:
+ if before:
+ return Date(freq='H', year=date.year(), month=date.month(),
+ day=date.day(), hours=0)
+ else:
+ return Date(freq='H', year=date.year(), month=date.month(),
+ day=date.day(), hours=23)
+ else:
+ return Date(freq='H', year=date.year(), month=date.month(),
+ day=date.day(), hours=date.hour())
+ #Convert to second......................
+ elif toFreq == 'T':
+ if date.freq in ['A','Q','M','W']:
+ if before:
+ return dateOf(dateOf(date, 'D', "BEFORE"), 'T', "BEFORE")
+ else:
+ return dateOf(dateOf(date, 'D', "AFTER"), 'T', "AFTER")
+ elif date.freq in ['B','D','H']:
+ if before:
+ return Date(freq='T', year=date.year(), month=date.month(),
+ day=date.day(), minutes=0)
+ else:
+ return Date(freq='T', year=date.year(), month=date.month(),
+ day=date.day(), minutes=24*60-1)
+ else:
+ return Date(freq='H', year=date.year(), month=date.month(),
+ day=date.day(), hours=date.hour(), minutes=date.minute())
+ #Convert to minute......................
+ elif toFreq == 'S':
+ if date.freq in ['A','Q','M','W']:
+ if before:
+ return dateOf(dateOf(date, 'D', "BEFORE"), 'S', "BEFORE")
+ else:
+ return dateOf(dateOf(date, 'D', "AFTER"), 'S', "AFTER")
+ elif date.freq in ['B','D']:
+ if before:
+ return Date(freq='S', year=date.year(), month=date.month(),
+ day=date.day(), seconds=0)
+ else:
+ return Date(freq='S', year=date.year(), month=date.month(),
+ day=date.day(), seconds=24*60*60-1)
+
+def isDate(data):
+ "Returns whether `data` is an instance of Date."
+ return isinstance(data, Date)
+
+
+#####---------------------------------------------------------------------------
+#---- --- DateArray ---
+#####---------------------------------------------------------------------------
+ufunc_dateOK = ['add','subtract',
+ 'equal','not_equal','less','less_equal', 'greater','greater_equal',
+ 'isnan']
+
+class DateArray(ndarray):
+ """Defines a ndarray of dates, as ordinals.
+
+When viewed globally (array-wise), DateArray is an array of integers.
+When viewed element-wise, DateArray is a sequence of dates.
+For example, a test such as :
+>>> DateArray(...) = value
+will be valid only if value is an integer, not a Date
+However, a loop such as :
+>>> for d in DateArray(...):
+accesses the array element by element. Therefore, `d` is a Date object.
+ """
+ def __new__(cls, dates=None, freq='U', copy=False):
+ #dalog.info("__new__ received %s [%i]" % (type(dates), numpy.size(dates)))
+ if isinstance(dates, DateArray):
+ #dalog.info("__new__ sends %s as %s" % (type(dates), cls))
+ cls.__defaultfreq = dates.freq
+ if not copy:
+ return dates.view(cls)
+ return dates.copy().view(cls)
+ else:
+ _dates = numeric.asarray(dates, dtype=int_)
+ if copy:
+ _dates = _dates.copy()
+ #dalog.info("__new__ sends %s as %s" % (type(_dates), cls))
+ if freq is None:
+ freq = 'U'
+ cls.__defaultfreq = corelib.fmtFreq(freq)
+ (cls.__toobj, cls.__toord, cls.__tostr) = (None, None, None)
+ (cls.__steps, cls.__full, cls.__hasdups) = (None, None, None)
+ return _dates.view(cls)
+
+ def __array_wrap__(self, obj, context=None):
+ if context is None:
+ return self
+ elif context[0].__name__ not in ufunc_dateOK:
+ raise ArithmeticDateError, "(function %s)" % context[0].__name__
+
+ def __array_finalize__(self, obj):
+ #dalog.info("__array_finalize__ received %s" % type(obj))
+ if hasattr(obj, 'freq'):
+ self.freq = obj.freq
+ else:
+ self.freq = self.__defaultfreq
+ #dalog.info("__array_finalize__ sends %s" % type(self))
+
+ def __getitem__(self, index):
+ #dalog.info("__getitem__ got index %s (%s)"%(index, type(index)))
+ if isDateType(index):
+ index = self.find_dates(index)
+ elif numeric.asarray(index).dtype.kind == 'O':
+ try:
+ index = self.find_dates(index)
+ except AttributeError:
+ pass
+ r = ndarray.__getitem__(self, index)
+ if r.size == 1:
+ # Only one element, and it's not a scalar: we have a DateArray of size 1
+ if len(r.shape) > 0:
+ r = r.item()
+ return Date(self.freq, value=r)
+ else:
+ return r
+
+ def __repr__(self):
+ return ndarray.__repr__(self)
+ #......................................................
+ @property
+ def years(self):
+ "Returns the years."
+ return numeric.asarray([d.year() for d in self], dtype=int_)
+ @property
+ def months(self):
+ "Returns the months."
+ return numeric.asarray([d.month() for d in self], dtype=int_)
+ @property
+ def day_of_year(self):
+ "Returns the days of years."
+ return numeric.asarray([d.day_of_year() for d in self], dtype=int_)
+ yeardays = day_of_year
+ @property
+ def day_of_week(self):
+ "Returns the days of week."
+ return numeric.asarray([d.day_of_week() for d in self], dtype=int_)
+ #.... Conversion methods ....................
+# def toobject(self):
+# "Converts the dates from ordinals to Date objects."
+# # Note: we better try to cache the result
+# if self.__toobj is None:
+## toobj = numeric.empty(self.size, dtype=object_)
+## toobj[:] = [Date(self.freq, value=d) for d in self]
+## self.__toobj = toobj
+# self.__toobj = self
+# return self.__toobj
+ #
+ def tovalue(self):
+ "Converts the dates to integer values."
+ return numeric.asarray(self)
+ #
+ def toordinal(self):
+ "Converts the dates from values to ordinals."
+ # Note: we better try to cache the result
+ if self.__toord is None:
+# diter = (Date(self.freq, value=d).toordinal() for d in self)
+ diter = (d.toordinal() for d in self)
+ toord = numeric.fromiter(diter, dtype=float_)
+ self.__toord = toord
+ return self.__toord
+ #
+ def tostring(self):
+ "Converts the dates to strings."
+ # Note: we better cache the result
+ if self.__tostr is None:
+ firststr = str(self[0])
+ if self.size > 0:
+ ncharsize = len(firststr)
+ tostr = numpy.fromiter((str(d) for d in self),
+ dtype='|S%i' % ncharsize)
+ else:
+ tostr = firststr
+ self.__tostr = tostr
+ return self.__tostr
+ #
+ def asfreq(self, freq=None):
+ "Converts the dates to another frequency."
+ # Note: As we define a new object, we don't need caching
+ if freq is None:
+ return self
+ freq = corelib.fmtFreq(freq)
+ if freq == self.freq:
+ return self
+ if self.isvalid():
+ new = numeric.arange(self.size, dtype=int_)
+ new += self[0].asfreq(freq).value
+ else:
+ new = numpy.fromiter((d.asfreq(freq).value for d in self),
+ dtype=float_)
+ return DateArray(new, freq=freq)
+ #......................................................
+ def find_dates(self, *dates):
+ "Returns the indices corresponding to given dates, as an array."
+ ifreq = self.freq
+ c = numpy.zeros(self.shape, bool_)
+ for d in corelib.flatargs(*dates):
+ if d.freq != ifreq:
+ d = d.asfreq(ifreq)
+ c += (self == d.value)
+ c = c.nonzero()
+ if fromnumeric.size(c) == 0:
+ raise ValueError, "Date out of bounds!"
+ return c
+ def date_to_index(self, date):
+ "Returns the index corresponding to one given date, as an integer."
+ if self.isvalid():
+ index = date.value - self[0].value
+ if index < 0 or index > self.size:
+ raise ValueError, "Date out of bounds!"
+ return index
+ else:
+ index_asarray = (self == date.value).nonzero()
+ if fromnumeric.size(index_asarray) == 0:
+ raise ValueError, "Date out of bounds!"
+ return index_asarray[0][0]
+ #......................................................
+ def get_steps(self):
+ """Returns the time steps between consecutive dates.
+ The timesteps have the same unit as the frequency of the series."""
+ if self.freq == 'U':
+ warnings.warn("Undefined frequency: assuming daily!")
+ if self.__steps is None:
+ steps = numeric.asarray(numpy.diff(self))
+ if steps.size > 0:
+ if self.__full is None:
+ self.__full = (steps.max() == 1)
+ if self.__hasdups is None:
+ self.__hasdups = (steps.min() == 0)
+ else:
+ self.__full = True
+ self.__hasdups = False
+ self.__steps = steps
+ return self.__steps
+
+ def has_missing_dates(self):
+ "Returns whether the DateArray have missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return not(self.__full)
+
+ def isfull(self):
+ "Returns whether the DateArray has no missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return self.__full
+
+ def has_duplicated_dates(self):
+ "Returns whether the DateArray has duplicated dates."
+ if self.__hasdups is None:
+ steps = self.get_steps()
+ return self.__hasdups
+
+ def isvalid(self):
+ "Returns whether the DateArray is valid: no missing/duplicated dates."
+ return (self.isfull() and not self.has_duplicated_dates())
+ #......................................................
+class _datearithmetics(object):
+ """Defines a wrapper for arithmetic methods.
+Instead of directly calling a ufunc, the corresponding method of the `array._data`
+object is called instead.
+If `asdates` is True, a DateArray object is returned , else a regular ndarray
+is returned.
+ """
+ def __init__ (self, methodname, asdates=True):
+ """
+:Parameters:
+ - `methodname` (String) : Method name.
+ """
+ self.methodname = methodname
+ self._asdates = asdates
+ self.__doc__ = getattr(methodname, '__doc__')
+ self.obj = None
+ #dalog.info('__datearithmetics got method %s' % methodname)
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args, **kwargs):
+ "Execute the call behavior."
+ instance = self.obj
+ freq = instance.freq
+ if 'context' not in kwargs:
+ kwargs['context'] = 'DateOK'
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ method = getattr(super(DateArray,instance), self.methodname)
+ if isinstance(other, DateArray):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+# other =
+ elif isDateType(other):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+ other = other.value
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ elif isinstance(other, ndarray):
+ if other.dtype.kind not in ['i','f']:
+ raise ArithmeticDateError
+ if self._asdates:
+ return instance.__class__(method(other, *args),
+ freq=freq)
+ else:
+ return method(other, *args)
+#............................
+DateArray.__add__ = _datearithmetics('__add__', asdates=True)
+DateArray.__radd__ = _datearithmetics('__add__', asdates=True)
+DateArray.__sub__ = _datearithmetics('__sub__', asdates=True)
+DateArray.__rsub__ = _datearithmetics('__rsub__', asdates=True)
+DateArray.__le__ = _datearithmetics('__le__', asdates=False)
+DateArray.__lt__ = _datearithmetics('__lt__', asdates=False)
+DateArray.__ge__ = _datearithmetics('__ge__', asdates=False)
+DateArray.__gt__ = _datearithmetics('__gt__', asdates=False)
+DateArray.__eq__ = _datearithmetics('__eq__', asdates=False)
+DateArray.__ne__ = _datearithmetics('__ne__', asdates=False)
+
+#####---------------------------------------------------------------------------
+#---- --- DateArray functions ---
+#####---------------------------------------------------------------------------
+def isDateArray(a):
+ "Tests whether an array is a DateArray object."
+ return isinstance(a,DateArray)
+
+def guess_freq(dates):
+ """Tries to estimate the frequency of a list of dates, by checking the steps
+ between consecutive dates The steps should be in days.
+ Returns a frequency code (alpha character)."""
+ ddif = numeric.asarray(numpy.diff(dates))
+ ddif.sort()
+ if ddif[0] == ddif[-1] == 1.:
+ fcode = 'D'
+ elif (ddif[0] == 1.) and (ddif[-1] == 3.):
+ fcode = 'B'
+ elif (ddif[0] > 3.) and (ddif[-1] == 7.):
+ fcode = 'W'
+ elif (ddif[0] >= 28.) and (ddif[-1] <= 31.):
+ fcode = 'M'
+ elif (ddif[0] >= 90.) and (ddif[-1] <= 92.):
+ fcode = 'Q'
+ elif (ddif[0] >= 365.) and (ddif[-1] <= 366.):
+ fcode = 'A'
+ elif numpy.abs(24.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(24.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'H'
+ elif numpy.abs(1440.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(1440.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'T'
+ elif numpy.abs(86400.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(86400.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'S'
+ else:
+ warnings.warn("Unable to estimate the frequency! %.3f<>%.3f" %\
+ (ddif[0], ddif[-1]))
+ fcode = 'U'
+ return fcode
+
+
+def _listparser(dlist, freq=None):
+ "Constructs a DateArray from a list."
+ dlist = numeric.asarray(dlist)
+ dlist.sort()
+ # Case #1: dates as strings .................
+ if dlist.dtype.kind == 'S':
+ #...construct a list of ordinals
+ ords = numpy.fromiter((mxDFromString(s).absdays for s in dlist),
+ float_)
+ ords += 1
+ #...try to guess the frequency
+ if freq is None:
+ freq = guess_freq(ords)
+ #...construct a list of dates
+ dates = [Date(freq, string=s) for s in dlist]
+ # Case #2: dates as numbers .................
+ elif dlist.dtype.kind in ['i','f']:
+ #...hopefully, they are values
+ if freq is None:
+ freq = guess_freq(dlist)
+ dates = dlist
+ # Case #3: dates as objects .................
+ elif dlist.dtype.kind == 'O':
+ template = dlist[0]
+ #...as Date objects
+ if isDateType(template):
+ dates = numpy.fromiter((d.value for d in dlist), float_)
+ #...as mx.DateTime objects
+ elif hasattr(template,'absdays'):
+ # no freq given: try to guess it from absdays
+ if freq is None:
+ ords = numpy.fromiter((s.absdays for s in dlist), float_)
+ ords += 1
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=m) for m in dlist]
+ #...as datetime objects
+ elif hasattr(dlist[0], 'toordinal'):
+ ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+ if freq is None:
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=mxD.DateTimeFromAbsDays(a)) for a in ords]
+ #
+ result = DateArray(dates, freq)
+ return result
+
+
+def date_array(dlist=None, start_date=None, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from:
+ - a starting date and either an ending date or a given length.
+ - a list of dates.
+ """
+ freq = corelib.fmtFreq(freq)
+ # Case #1: we have a list ...................
+ if dlist is not None:
+ # Already a DateArray....................
+ if isinstance(dlist, DateArray):
+ if freq != dlist.freq:
+ return dlist.asfreq(freq)
+ else:
+ return dlist
+ return _listparser(dlist, freq)
+ # Case #2: we have a starting date ..........
+ if start_date is None:
+ raise InsufficientDateError
+ if not isDateType(start_date):
+ raise DateError, "Starting date should be a valid Date instance!"
+ # Check if we have an end_date
+ if end_date is None:
+ if length is None:
+ raise ValueError,"No length precised!"
+ else:
+ assert(isDateType(end_date),
+ "Starting date should be a valid Date instance!")
+ length = end_date - start_date
+ if include_last:
+ length += 1
+# dlist = [(start_date+i).value for i in range(length)]
+ dlist = numeric.arange(length, dtype=int_)
+ dlist += start_date.value
+ if freq is None:
+ freq = start_date.freq
+ return DateArray(dlist, freq=freq)
+datearray = date_array
+
+def date_array_fromlist(dlist, freq=None):
+ "Constructs a DateArray from a list of dates."
+ return date_array(dlist=dlist, freq=freq)
+
+def date_array_fromrange(start_date, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from a starting date and either an ending date or
+ a length."""
+ return date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(DateArray, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__(self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+
+
+################################################################################
+if __name__ == '__main__':
+# from maskedarray.testutils import assert_equal
+ import datetime
+ if 1:
+ # get the last day of this year, at daily frequency
+ dLastDayOfYear = dateOf(thisday('A'),'D','AFTER')
+ if 0:
+ # get the first day of this year, at business frequency
+ bFirstDayOfYear = dateOf(thisday('A'),'B','BEFORE')
+ # get the last day of the previous quarter, business frequency
+ bLastDayOfLastQuarter = dateOf(thisday('Q')-1,'B','AFTER')
+ # dateOf can also go from high frequency to low frequency. In this case, the third parameter has no impact
+ aTrueValue = (thisday('Q') == dateOf(thisday('b'),'Q'))
+ # dates of the same frequency can be subtracted (but not added obviously)
+ numberOfBusinessDaysPassedThisYear = thisday('b') - bFirstDayOfYear
+
+ # integers can be added/substracted to/from dates
+ fiveDaysFromNow = thisday('d') + 5
+
+ # get the previous business day, where business day is considered to
+ # end at day_end_hour and day_end_min
+ pbd = prevbusday(day_end_hour=18,day_end_min=0)
+
+ # construct a date object explicitly
+ myDateQ = Date(freq='Q',year=2004,quarter=3)
+ myDateD = Date(freq='D',year=1985,month=10,day=4)
+
+ #------------------------------------------------
Added: trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py 2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py 2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1506 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+
+ - an array storing the time information (as a `DateArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__ = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+
+import logging
+import weakref
+
+
+import numpy
+from numpy.core import bool_, float_, int_
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+import numpy.core.umath as umath
+from numpy import ndarray
+from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+#from cPickle import dump, dumps
+
+import maskedarray as MA
+#import numpy.core.ma as MA
+reload(MA)
+#MaskedArray = MA.MaskedArray
+from maskedarray.core import MaskedArray
+masked = MA.masked
+nomask = MA.nomask
+MAError = MA.MAError
+masked_array = MA.masked_array
+filled = MA.filled
+getmask = MA.getmask
+getmaskarray = MA.getmaskarray
+make_mask_none = MA.make_mask_none
+mask_or = MA.mask_or
+make_mask = MA.make_mask
+
+oldma = (MA.__name__ == 'numpy.core.ma')
+
+import tscore as corelib
+#reload(corelib)
+from tscore import *
+
+import tsdate
+reload(tsdate)
+from tsdate import DateError, InsufficientDateError
+from tsdate import Date, isDate, DateArray, isDateArray, \
+ date_array, date_array_fromlist, date_array_fromrange, thisday
+
+import cseries
+reload(cseries)
+
+#...............................................................................
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+talog = logging.getLogger('log.TimeArray')
+tslog = logging.getLogger('TimeSeries')
+btslog = logging.getLogger('BaseTimeSeries')
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+#### --------------------------------------------------------------------------
+#--- ... TimeSeriesError class ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+ "Class for TS related errors."
+ def __init__ (self, args=None):
+ "Creates an exception."
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculates the string representation."
+ return str(self.args)
+ __repr__ = __str__
+
+class TimeSeriesCompatibilityError(TimeSeriesError):
+ """Defines the exception raised when series are incompatible."""
+ def __init__(self, mode, first, second):
+ if mode == 'freq':
+ msg = "Incompatible time steps! (%s <> %s)"
+ elif mode == 'start_date':
+ msg = "Incompatible starting dates! (%s <> %s)"
+ elif mode == 'size':
+ msg = "Incompatible sizes! (%s <> %s)"
+ msg = msg % (first, second)
+ TimeSeriesError.__init__(self, msg)
+
+
+#def _compatibilitycheck(a, b):
+def _timeseriescompat(a, b):
+ """Checks the date compatibility of two TimeSeries object.
+ Returns True if everything's fine, or raises an exception."""
+ if not (hasattr(a,'freq') and hasattr(b, 'freq')):
+ return True
+ if a.freq != b.freq:
+ raise TimeSeriesCompatibilityError('freq', a.freq, b.freq)
+ elif a.start_date() != b.start_date():
+ raise TimeSeriesCompatibilityError('start_date', a.start_date(), b.start_date())
+ elif a.shape != b.shape:
+ raise TimeSeriesCompatibilityError('size', str(a.shape), str(b.shape))
+ return True
+
+def _datadatescompat(data,dates):
+ """Checks the compatibility of dates and data at the creation of a TimeSeries.
+ Returns True if everything's fine, raises an exception otherwise."""
+ # If there's only 1 element, the date is a Date object, which has no size...
+ tsize = numeric.size(dates)
+ dsize = data.size
+ # Only one data
+ if dsize == tsize:
+ return True
+ elif data.ndim > 1:
+ dsize = numeric.asarray(data.shape[1:]).prod()
+ if dsize == tsize:
+ return True
+ raise TimeSeriesCompatibilityError('size', dsize, tsize)
+
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+if oldma:
+ parentclass = ndarray
+else:
+ parentclass = MaskedArray
+#
+class TimeSeries(parentclass, object):
+ """Base class for the definition of time series.
+A time series is here defined as the combination of three arrays:
+
+ - `series` : *[ndarray]*
+ Data part
+ - `mask` : *[ndarray]*
+ Mask part
+ - `dates` : *[DateArray]*
+ Date part
+
+The combination of `series` and `dates` is the `data` part.
+ """
+ def __new__(cls, data, dates=None, mask=nomask,
+ freq=None, observed=None, start_date=None,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ #tslog.info("__new__: received data types %s, %s" % (type(data), data))
+ options = dict(copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask, )
+ if isinstance(data, TimeSeries):
+ # Check dates ........
+ if dates is None:
+ newdates = data._dates
+ else:
+ if not hasattr(dates,'freq'):
+ raise DateError, "Invalid Dates!"
+ newdates = dates
+ data._dates = newdates
+ if hasattr(data, '_data') and hasattr(data._data, '_dates'):
+ data._data._dates = newdates
+ cls._defaultdates = newdates
+ # Check frequency......
+ if freq is not None:
+ freq = corelib.fmtFreq(freq)
+ if freq != newdates.freq:
+ _dates = newdates.tofreq(freq)
+ else:
+ freq = newdates.freq
+ # Check observed.......
+ if observed is not None:
+ observed = data._observed
+ cls._defaultobserved = observed
+ _data = data._series
+ else:
+ # Check dates ........
+ if dates is None:
+ if numeric.ndim(data) >= 2:
+ length = numeric.asarray(numeric.shape(data))[1:].prod()
+ else:
+ length = numeric.size(data)
+ newdates = date_array(start_date=start_date, length=length,
+ freq=freq)
+ elif not hasattr(dates, 'freq'):
+ newdates = date_array(dlist=dates, freq=freq)
+ else:
+ newdates = dates
+ _data = data
+ if hasattr(data, '_mask') :
+ mask = mask_or(data._mask, mask)
+ cls._defaultdates = newdates
+ cls._defaultobserved = observed
+# if oldma:
+# newdata = MaskedArray(data, mask=mask, dtype=dtype,
+# copy=copy,fill_value=fill_value)
+# cls._defaultmask = newdata._mask
+# cls._defaulthardmask = True
+# cls._fill_value = newdata._fill_value
+# assert(_datadatescompat(newdata,dates))
+# return ndarray.__new__(cls,shape=newdata.shape,dtype=newdata.dtype,
+# buffer=newdata._data)
+# _data = data
+# newdata = MaskedArray.__new__(cls, data=_data, mask=mask, **options)
+ newdata = super(TimeSeries,cls).__new__(cls, _data, mask=mask,
+ **options)
+ assert(_datadatescompat(data,newdates))
+ return newdata
+
+ #..................................
+ def __array_wrap__(self, obj, context=None):
+# if oldma:
+# tmpself = MaskedArray(self._data, mask=self._mask)
+# return TimeSeries(MaskedArray.__array_wrap__(tmpself, obj, context),
+# dates=self._dates)
+# print "__array_wrap__"
+ return TimeSeries(super(TimeSeries,self).__array_wrap__(obj, context),
+ dates=self._dates)
+ #............................................
+ def __array_finalize__(self,obj):
+ #tslog.info("__array_finalize__ received %s" % type(obj))
+ if isinstance(obj, TimeSeries):
+ self._dates = obj._dates
+ self._data = obj._series._data
+ self._mask = obj._series._mask
+ self._series = obj._series
+ self._hardmask = obj._series._hardmask
+ self.observed = obj.observed
+ self._fill_value = obj._fill_value
+ else:
+ self._dates = self._defaultdates
+ self.observed = self._defaultobserved
+ self._series = MA.array(obj, mask=self._defaultmask,
+ copy=False, hard_mask=self._defaulthardmask)
+ self._mask = self._defaultmask
+ self._data = obj
+ self._hardmask = self._defaulthardmask
+ self.fill_value = self._fill_value
+ self._mask = self._series._mask
+ self._data = self._series._data
+ self._hardmask = self._series._hardmask
+ #tslog.info("__array_finalize__ sends %s" % type(self))
+ return
+ #............................................
+ def __getattribute__(self,attr):
+ "Returns a given attribute."
+ # Here, we need to be smart: _mask should call _series._mask...
+ if attr in ['_data','_mask','_hardmask']:
+ return getattr(self._series,attr)
+ return super(TimeSeries, self).__getattribute__(attr)
+ def __setattribute__(self,attr, value):
+ """Sets an attribute to a given value."""
+ # Same thing here: if we modify ._mask, we need to modify _series._mask
+ # ...as well
+ super(TimeSeries, self).__setattribute__(attr, value)
+ if attr in ['_data','_mask','_hardmask']:
+ super(self._series.__class__, self._series).__setattribute__(attr, value)
+ setattr(self._series, attr, value)
+ #............................................
+ def __checkindex(self, index):
+ "Checks the validity of an index."
+ if isinstance(index, int):
+ return index
+ if isinstance(index, str):
+ return self._dates.date_to_index(Date(self._dates.freq, string=index))
+ elif isDate(index) or isDateArray(index):
+ return self._dates.date_to_index(index)
+ elif isinstance(index,slice):
+ slice_start = self.__checkindex(index.start)
+ slice_stop = self.__checkindex(index.stop)
+ return slice(slice_start, slice_stop, index.step)
+ elif isTimeSeries(index):
+ index = index._series
+ if getmask(index) is not nomask:
+ msg = "Masked arrays must be filled before they can be used as indices!"
+ raise IndexError, msg
+ return index
+
+ def __getitem__(self, index):
+ """x.__getitem__(y) <==> x[y]
+Returns the item described by i. Not a copy as in previous versions.
+ """
+ index = self.__checkindex(index)
+ data = self._series[index]
+ date = self._dates[index]
+ m = self._mask
+ scalardata = (len(numeric.shape(data))==0)
+ #
+ if m is nomask:
+ if scalardata:
+ return TimeSeries(data, dates=date)
+ else:
+ return TimeSeries(data, dates=date, mask=nomask, keep_mask=True,
+ copy=False)
+ #....
+ mi = m[index]
+ if mi.size == 1:
+ if mi:
+ return TimeSeries(data, dates=date, mask=True)
+ return TimeSeries(data, dates=date, mask=nomask)
+ else:
+ return TimeSeries(data, dates=date, mask=mi)
+ #........................
+ def __setitem__(self, index, value):
+ """x.__setitem__(i, y) <==> x[i]=y
+Sets item described by index. If value is masked, masks those locations.
+ """
+ if self is masked:
+ raise MAError, 'Cannot alter the masked element.'
+ index = self.__checkindex(index)
+ #....
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[index], value))
+ self._series[index] = value._series
+ else:
+ self._series[index] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+
+ #........................
+ def __getslice__(self, i, j):
+ "Gets slice described by i, j"
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ (data, date) = (self._series[i:j], self._dates[i:j])
+ return TimeSeries(data, dates=date, copy=False)
+ #....
+ def __setslice__(self, i, j, value):
+ "Gets item described by i. Not a copy as in previous versions."
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ #....
+ data = self._series[i:j]
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[i:j], value))
+ self._series[i:j] = value._series
+ else:
+ self._series[i:j] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+ #......................................................
+ def __len__(self):
+ if self.ndim == 0:
+ return 0
+ return ndarray.__len__(self)
+ #......................................................
+ def __str__(self):
+ """Returns a string representation of self (w/o the dates...)"""
+ return str(self._series)
+ def __repr__(self):
+ """Calculates the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ desc = """\
+timeseries(data =
+ %(data)s,
+ dates =
+ %(time)s,
+ freq = %(freq)s)
+"""
+ desc_short = """\
+timeseries(data = %(data)s,
+ dates = %(time)s,
+ freq = %(freq)s)
+"""
+ if numeric.size(self._dates) > 2 and self.isvalid():
+ timestr = "[%s ... %s]" % (str(self._dates[0]),str(self._dates[-1]))
+ else:
+ timestr = str(self.dates)
+
+ if self.ndim <= 1:
+ return desc_short % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ return desc % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ #............................................
+ def _get_mask(self):
+ """Returns the current mask."""
+ return self._series._mask
+ def _set_mask(self, mask):
+ """Sets the mask to `mask`."""
+ mask = make_mask(mask, copy=False, small_mask=True)
+ if mask is not nomask:
+ if mask.size != self._data.size:
+ raise ValueError, "Inconsistent shape between data and mask!"
+ if mask.shape != self._data.shape:
+ mask.shape = self._data.shape
+ self._series._mask = mask
+ else:
+ self._series._mask = nomask
+ mask = property(fget=_get_mask, fset=_set_mask, doc="Mask")
+
+ def ids (self):
+ """Return the ids of the data, dates and mask areas"""
+ return (id(self._series), id(self.dates),)
+
+ def copy(self):
+ "Returns a copy of the TimeSeries."
+ return TimeSeries(self, copy=True)
+
+ #------------------------------------------------------
+ @property
+ def series(self):
+ "Returns the series."
+ return self._series
+ @property
+ def dates(self):
+ """Returns the dates"""
+ return self._dates
+ @property
+ def freq(self):
+ """Returns the corresponding frequency."""
+ return self._dates.freq
+# @property
+ def years(self):
+ """Returns the corresponding years."""
+ return self._dates.years
+# @property
+ def months(self):
+ """Returns the corresponding months."""
+ return self._dates.months
+# @property
+ def yeardays(self):
+ """Returns the corresponding days of year."""
+ return self._dates.yeardays
+ day_of_year = yeardays
+# @property
+ def weekdays(self):
+ """Returns the corresponding days of weeks."""
+ return self._dates.day_of_week
+ day_of_week = weekdays
+
+ def start_date(self):
+ """Returns the first date of the series."""
+ return self._dates[0]
+#
+ def end_date(self):
+ """Returns the last date of the series."""
+ return self._dates[-1]
+
+ def isvalid(self):
+ """Returns whether the series has no duplicate/missing dates."""
+ return self._dates.isvalid()
+
+ def has_missing_dates(self):
+ """Returns whether there's a date gap in the series."""
+ return self._dates.has_missing_dates()
+
+ def isfull(self):
+ """Returns whether there's no date gap in the series."""
+ return self._dates.isfull()
+
+ def has_duplicated_dates(self):
+ """Returns whether there are duplicated dates in the series."""
+ return self._dates.has_duplicated_dates()
+
+ def date_to_index(self, date):
+ "Returns the index corresponding to a given date, as an integer."
+ return self._dates.date_to_index(date)
+ #.....................................................
+ def asfreq(self, freq=None):
+ "Converts the dates to another frequency."
+ if freq is None:
+ return self
+ return TimeSeries(self._series, dates=self._dates.asfreq(freq))
+
+ def convert(self, freq, func='auto', position='END', interp=None):
+ "Converts the dates to another frequency, and adapt the data."
+ return convert(self, freq, func=func, position=position, interp=interp)
+
+##### --------------------------------------------------------------------------
+##--- ... Additional methods ...
+##### --------------------------------------------------------------------------
+class _inplacemethod(object):
+ """Defines a wrapper for inplace arithmetic array methods (iadd, imul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ self.obj = None
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ assert(_timeseriescompat(instance,other))
+ func = getattr(instance._series, self.f)
+ func(other, *args)
+ return instance
+#......................................
+TimeSeries.__iadd__ = _inplacemethod('__iadd__')
+TimeSeries.__iand__ = _inplacemethod('__iand__')
+TimeSeries.__idiv__ = _inplacemethod('__idiv__')
+TimeSeries.__isub__ = _inplacemethod('__isub__')
+TimeSeries.__imul__ = _inplacemethod('__imul__')
+
+
+class _tsmathmethod(object):
+ """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ _dates = instance._dates
+ #tslog.info("_tsmathmethod: series: %s" % instance,)
+ #tslog.info("_tsmathmethod: other : %s" % other,)
+ func = getattr(instance._series, self.f)
+ if isinstance(other, TimeSeries):
+ assert(_timeseriescompat(instance, other))
+ return instance.__class__(func(other, *args), dates=_dates,)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+TimeSeries.__eq__ = _tsmathmethod('__eq__')
+TimeSeries.__ne__ = _tsmathmethod('__ne__')
+TimeSeries.__lt__ = _tsmathmethod('__lt__')
+TimeSeries.__le__ = _tsmathmethod('__le__')
+TimeSeries.__gt__ = _tsmathmethod('__gt__')
+TimeSeries.__ge__ = _tsmathmethod('__ge__')
+#................................................
+class _tsarraymethod(object):
+ """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+ """
+ def __init__ (self, methodname, ondates=False):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ self._ondates = ondates
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args):
+ "Execute the call behavior."
+ _name = self._name
+ instance = self.obj
+ func_series = getattr(instance._series, _name)
+ if self._ondates:
+ func_dates = getattr(instance._dates, _name)
+ return instance.__class__(func_series(*args),
+ dates=func_dates(*args))
+ else:
+ return instance.__class__(func_series(*args),
+ dates=instance._dates)
+#TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+TimeSeries.filled = _tsarraymethod('filled', ondates=False)
+TimeSeries.cumsum = _tsarraymethod('cumsum',ondates=False)
+TimeSeries.cumprod = _tsarraymethod('cumprod',ondates=False)
+TimeSeries.anom = _tsarraymethod('anom',ondates=False)
+
+#......................................
+class _tsaxismethod(object):
+ """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the series.
+ """
+ def __init__ (self, methodname):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args, **params):
+ "Execute the call behavior."
+ (_dates, _series) = (self.obj._dates, self.obj._series)
+ func = getattr(_series, self._name)
+ result = func(*args, **params)
+ if _series.ndim < 2 or _dates.size == _series.size:
+ return result
+ else:
+ try:
+ axis = params.get('axis', args[0])
+ if axis == 0:
+ result = TimeSeries(result, dates=_dates)
+ except IndexError:
+ pass
+ return result
+#.......................................
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(TimeSeries, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__ (self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+#
+##### ---------------------------------------------------------------------------
+#---- ... Additional methods ...
+##### ---------------------------------------------------------------------------
+def tofile(self, output, sep='\t', format='%s', format_dates=None):
+ """Writes the TimeSeries to a file.
+
+:Parameters:
+ - `output` (String) : Name or handle of the output file.
+ - `sep` (String) : Column separator *['\t']*.
+ - `format` (String) : Data format *['%s']*.
+ """
+ if not hasattr(output, 'writeline'):
+ ofile = open(output,'w')
+ else:
+ ofile = output
+ if format_dates is None:
+ format_dates = self.dates[0].default_fmtstr()
+ oformat = "%%s%s%s" % (sep,format_dates)
+ for (_dates,_data) in numpy.broadcast(self._dates.ravel().asstrings(),
+ filled(self)):
+ ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+ ofile.close()
+TimeSeries.tofile = tofile
+
+##### ---------------------------------------------------------------------------
+##--- ... Pickling ...
+##### ---------------------------------------------------------------------------
+##FIXME: We're kinda stuck with forcing the mask to have the same shape as the data
+#def _tsreconstruct(baseclass, datesclass, baseshape, basetype):
+# """Internal function that builds a new MaskedArray from the information stored
+#in a pickle."""
+# _series = ndarray.__new__(ndarray, baseshape, basetype)
+# _dates = ndarray.__new__(datesclass, baseshape, basetype)
+# return TimeSeries.__new__(baseclass, _series, dates=_dates, dtype=basetype)
+#
+#def _tsgetstate(a):
+# "Returns the internal state of the TimeSeries, for pickling purposes."
+# #TODO: We should prolly go through a recarray here as well.
+# state = (1,
+# a.shape,
+# a.dtype,
+# a.flags.fnc,
+# (a._series).__reduce__()[-1][-1],
+# (a._dates).__reduce__()[-1][-1])
+# return state
+#
+#def _tssetstate(a, state):
+# """Restores the internal state of the TimeSeries, for pickling purposes.
+#`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+#
+# - class name
+# - a tuple giving the shape of the data
+# - a typecode for the data
+# - a binary string for the data
+# - a binary string for the mask.
+# """
+# (ver, shp, typ, isf, raw, dti) = state
+# (a._series).__setstate__((shp, typ, isf, raw))
+# (a._dates).__setstate__((shp, N.dtype('|O8'), isf, dti))
+# (a._dates)._asstrings = None
+#
+#def _tsreduce(a):
+# """Returns a 3-tuple for pickling a MaskedArray."""
+# return (_tsreconstruct,
+# (a.__class__, a._dates.__class__, (0,), 'b', ),
+# a.__getstate__())
+#
+#TimeSeries.__getstate__ = _tsgetstate
+#TimeSeries.__setstate__ = _tssetstate
+#TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+#
+##................................................
+
+#
+##### --------------------------------------------------------------------------
+##--- ... Shortcuts ...
+##### --------------------------------------------------------------------------
+#def isTimeSeries(x):
+# """Checks whether `x` is a time series (an instance of `TimeSeries` )."""
+# return isinstance(x, TimeSeries)
+#
+##### --------------------------------------------------------------------------
+##--- ... MaskedTimeSeries class ...
+##### --------------------------------------------------------------------------
+#class MaskedTimeSeries(MaskedArray, TimeSeries):
+# """Base class for the definition of time series.
+#A time series is here defined as the combination of two arrays:
+#
+# - an array storing the time information (as a `TimeArray` instance);
+# - an array storing the data (as a `MaskedArray` instance.
+# """
+# def __new__(cls, data, dates=None, mask=nomask,
+# dtype=None, copy=True, fill_value=-9999):
+# mtslog.log(5, "__new__: data types %s, %s" % (type(data), dtype))
+## if isinstance(data, TimeSeries):
+# #....................
+# if isinstance(data, TimeSeries):
+# if isinstance(data, MaskedTimeSeries):
+# _data = data._data
+# else:
+# _data = data
+# _dates = data._dates
+# _series = data._series
+# mtslog.log(5, "__new__ from TS: data %i - %s - %s" % \
+# (id(_data._series), type(_data._series), _data.ravel()))
+# mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+# (id(_dates), type(_dates), _dates.ravel()))
+# elif isinstance(data, recarray):
+# assert(data.dtype.names == ('_dates', '_series', '_mask'),
+# "Invalid fields names (got %s)" % (data.dtype.names,))
+# _dates = data['_dates']
+# _series = data['_series']
+# _mask = data['_mask']
+# else:
+# if hasattr(data, "_data"):
+# _data = TimeSeries(data._data, dates=dates,
+# dtype=dtype, copy=copy)
+# else:
+# _data = TimeSeries(data, dates=dates,
+# dtype=dtype, copy=copy)
+# _dates = _data._dates
+# _series = _data._series
+# mtslog.log(5,"__new__ from scratch: data %i - %s - %s" % \
+# (id(_data._series), type(_data._series), _data.ravel()))
+# mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+# (id(_dates), type(_dates), _dates.ravel()))
+# #.....................
+# if mask is nomask:
+# if hasattr(data, "_mask"):
+# _mask = data._mask
+# else:
+# _mask = nomask
+# else:
+# _mask = make_mask(mask, copy=copy, flag=True)
+# #....Check shapes compatibility
+# if _mask is not nomask:
+# (nd, nm) = (_data.size, _mask.size)
+# if (nm != nd):
+# if nm == 1:
+# _mask = N.resize(_mask, _data.shape)
+# elif nd == 1:
+# _data = N.resize(_data, _mask.shape)
+# else:
+# msg = "Mask and data not compatible (size issues: %i & %i)."
+# raise MAError, msg % (nm, nd)
+# elif (_mask.shape != _data.shape):
+# mtslog.log(5,"__new__ from scratch: force _mask shape %s > %s" % \
+# (_mask.shape, _data.shape))
+# _mask.shape = _data.shape
+# #....
+# cls._fill_value = fill_value
+# cls._basemask = _mask
+# cls._basedates = _dates
+# cls._baseseries = _series
+# return _data.view(cls)
+## return _series.view(cls)
+# #..............
+# def __array_wrap__(self, obj, context=None):
+# """Special hook for ufuncs.
+#Wraps the numpy array and sets the mask according to context.
+# """
+## return MaskedArray.__array_wrap__(obj, context=None)
+# return MaskedTimeSeries(obj, dates=self._dates, mask=self._mask,
+# fill_value=self._fill_value)
+#
+# #..............
+# def __array_finalize__(self,obj):
+# mtslog.log(5, "__array_finalize__: obj is %s" % (type(obj), ))
+# if not hasattr(self, "_data"):
+# self._data = obj
+# if not hasattr(self, "_dates"):
+# self._dates = self._basedates
+# mtslog.log(5, "__array_finalize__: set dates to: %s - %s" % \
+# (id(self._dates), self._dates.ravel() ))
+# if not hasattr(self, "_mask"):
+# self._mask = self._basemask
+# mtslog.log(5, "__array_finalize__: set mask to: %s - %s" % \
+# (id(self._mask), self._mask.ravel() ))
+# if not hasattr(self, "_series"):
+# if hasattr(obj, "_series"):
+# self._series = obj._series
+# else:
+# self._series = obj
+# self.fill_value = self._fill_value
+# return
+# #------------------------------------------------------
+# def __str__(self):
+# """Calculate the str representation, using masked for fill if
+# it is enabled. Otherwise fill with fill value.
+# """
+# if masked_print_option.enabled():
+# f = masked_print_option
+# # XXX: Without the following special case masked
+# # XXX: would print as "[--]", not "--". Can we avoid
+# # XXX: checks for masked by choosing a different value
+# # XXX: for the masked singleton? 2005-01-05 -- sasha
+# if self is masked:
+# return str(f)
+# m = self._mask
+# if m is nomask:
+# res = self._data
+# else:
+# if m.shape == () and m:
+# return str(f)
+# # convert to object array to make filled work
+# res = (self._series).astype("|O8")
+# res[self._mask] = f
+# else:
+# res = self.filled(self.fill_value)
+# return str(res)
+# #............................................
+# def ids (self):
+# """Return the ids of the data, dates and mask areas"""
+# return (id(self._series), id(self.dates), id(self._mask))
+# #............................................
+# @property
+# def maskedseries(self):
+# """Returns a masked array of the series (dates are omitteed)."""
+# return masked_array(self._series, mask=self._mask)
+# _mseries = maskedseries
+# #............................................
+# def filled(self, fill_value=None):
+# """A numeric array with masked values filled. If fill_value is None,
+# use self.fill_value().
+#
+# If mask is nomask, copy data only if not contiguous.
+# Result is always a contiguous, numeric array.
+## Is contiguous really necessary now?
+# """
+# (d, m) = (self._data, self._mask)
+# if m is nomask:
+# return d
+# #
+# if fill_value is None:
+# value = self._fill_value
+# else:
+# value = fill_value
+# #
+# if self is masked_singleton:
+# return numeric.array(value)
+# #
+# result = d.copy()
+# try:
+# result.__setitem__(m, value)
+# except (TypeError, AttributeError):
+# #ok, can't put that value in here
+# value = numeric.array(value, dtype=object)
+# d = d.astype(object)
+# result = fromnumeric.choose(m, (d, value))
+# except IndexError:
+# #ok, if scalar
+# if d.shape:
+# raise
+# elif m:
+# result = numeric.array(value, dtype=d.dtype)
+# else:
+# result = d
+# return result
+# #............................................
+
+# #............................................
+# def asrecords(self):
+# """Returns the masked time series as a recarray.
+#Fields are `_dates`, `_data` and _`mask`.
+# """
+# desctype = [('_dates','|O8'), ('_series',self.dtype), ('_mask',N.bool_)]
+# flat = self.ravel()
+# if flat.size > 0:
+# return recfromarrays([flat._dates, flat._series, getmaskarray(flat)],
+# dtype=desctype,
+# shape = (flat.size,),
+# )
+# else:
+# return recfromarrays([[], [], []], dtype=desctype,
+# shape = (flat.size,),
+# )
+#
+#
+## def reshape (self, *s):
+## """This array reshaped to shape s"""
+## self._data = self._data.reshape(*s)
+## self._dates = self._dates.reshape(*s)
+## if self._mask is not nomask:
+## self._mask = self._mask.reshape(*s)
+## return self.view()
+##### --------------------------------------------------------------------------
+##--- ... Pickling ...
+##### --------------------------------------------------------------------------
+#def _mtsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+# """Internal function that builds a new MaskedArray from the information stored
+#in a pickle."""
+## raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+# _series = ndarray.__new__(ndarray, baseshape, basetype)
+# _dates = ndarray.__new__(datesclass, baseshape, '|O8')
+# _mask = ndarray.__new__(ndarray, baseshape, '|O8')
+# return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask,
+# dtype=basetype, fill_value=fill_value)
+##
+#def _mtsgetstate(a):
+# "Returns the internal state of the TimeSeries, for pickling purposes."
+## raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+# records = a.asrecords()
+# state = (1,
+# a.shape,
+# a.dtype,
+# records.flags.fnc,
+# a.fill_value,
+# records
+# )
+# return state
+##
+#def _mtssetstate(a, state):
+# """Restores the internal state of the TimeSeries, for pickling purposes.
+#`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+#
+# - class name
+# - a tuple giving the shape of the data
+# - a typecode for the data
+# - a binary string for the data
+# - a binary string for the mask.
+# """
+# (ver, shp, typ, isf, flv, rec) = state
+# a.fill_value = flv
+# a._data._series = a._series = N.asarray(rec['_series'])
+# a._data._series.shape = a._series.shape = shp
+# a._data._dates = a._dates = a._dates.__class__(rec['_dates'])
+# a._data._dates.shape = a._dates.shape = shp
+# (a._dates)._asstrings = None
+# a._mask = N.array(rec['_mask'], dtype=MA.MaskType)
+# a._mask.shape = shp
+##
+#def _mtsreduce(a):
+# """Returns a 3-tuple for pickling a MaskedArray."""
+# return (_mtsreconstruct,
+# (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+# a.__getstate__())
+##
+#MaskedTimeSeries.__getstate__ = _mtsgetstate
+#MaskedTimeSeries.__setstate__ = _mtssetstate
+#MaskedTimeSeries.__reduce__ = _mtsreduce
+#MaskedTimeSeries.__dump__ = dump
+#MaskedTimeSeries.__dumps__ = dumps
+
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, freq=None, observed=None,
+ start_date=None, end_date=None, length=None, include_last=True,
+ mask=nomask,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ """Creates a TimeSeries object
+
+:Parameters:
+ `dates` : ndarray
+ Array of dates.
+ `data` :
+ Array of data.
+ """
+ if dates is None:
+ if numeric.ndim(data) >= 2:
+ length = numeric.asarray(numeric.shape(data))[1:].prod()
+ else:
+ length = numeric.size(data)
+ dates = date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+ elif not isinstance(dates, DateArray):
+ dates = date_array(dlist=dates, freq=freq)
+ return TimeSeries(data=data, dates=dates, mask=mask, observed=observed,
+ copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask,)
+
+
+def isTimeSeries(series):
+ "Returns whether the series is a valid TimeSeries object."
+ return isinstance(series, TimeSeries)
+
+##### --------------------------------------------------------------------------
+##--- ... Additional functions ...
+##### --------------------------------------------------------------------------
+#def check_dates(a,b):
+# """Returns the array of dates from the two objects `a` or `b` (or None)."""
+# if isTimeSeries(a):
+# if isTimeSeries(b) and (a._dates == b._dates).all() is False:
+# raise ValueError, "Incompatible dates !"
+# return a._dates
+# elif isTimeSeries(b):
+# return b._dates
+# else:
+# return
+#
+#def parse_period(period):
+# """Returns a TimeArray couple (starting date; ending date) from the arguments."""
+##### print "........DEBUG PARSE DATES: period %s is %s" % (period, type(period))
+## if isinstance(period,TimeArray) or isinstance(period,Dates):
+##### print "........DEBUG PARSE_PERIOD: OK"
+# if isinstance(period,TimeArray):
+# return (period[0],period[-1])
+# elif hasattr(period,"__len__"):
+# if not isinstance(period[0], TimeArray):
+# tstart = TimeArray(period[0])
+# else:
+# tstart = period[0]
+# if not isinstance(period[-1], TimeArray):
+# tend = TimeArray(period[-1])
+# else:
+# tend = period[-1]
+# return (tstart, tend)
+# else:
+# p = N.asarray(period)
+# if N.all(p < 9999):
+# p = N.array(period,dtype="|S4")
+# p = time_array(p)
+# return (p[0], p[-1])
+#
+#def where_period(period, dates, *choices):
+# """Returns choices fro True/False, whether dates fall during a given period.
+#If no choices are given, outputs the array indices for the dates falling in the
+#period.
+#
+#:Parameters:
+# `period` : Sequence
+# Selection period, as a sequence (starting date, ending date).
+# `dates` : TimeArray
+# Array of dates.
+# `choices` : *(optional)*
+# Arrays to select from when the condition is True/False.
+# """
+# (tstart, tend) = parse_period(period)
+# condition = ascondition((dates>=tstart)&(dates<=tend))
+# condition = (dates>=tstart)&(dates<=tend)
+# return N.where(condition, *choices)
+#
+#def masked_inside_period(data, period, dates=None):
+# """Returns x as an array masked where dates fall inside the selection period,
+#as well as where data are initially missing (masked)."""
+# (tstart, tend) = parse_period(period)
+# # Get dates ..................
+# if hasattr(data, "_dates"):
+# dates = data._dates
+# elif dates is None:
+# raise ValueError,"Undefined dates !"
+# else:
+# assert(N.size(dates)==N.size(data),
+# "Inconsistent data and dates sizes!")
+# # where_period yields True inside the period, when mask should yield False
+# condition = ascondition(N.logical_and((dates>=tstart), (dates<=tend)))
+# cm = filled(condition,True).reshape(data.shape)
+# mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+# if isinstance(data, MaskedTimeSeries):
+# return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+# if isinstance(data, TimeSeries):
+# return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+# else:
+# return masked_array(data, mask=mask, copy=True)
+#
+def mask_period(data, start_date=None, end_date=None,
+ inside=True, include_edges=True, inplace=True):
+ """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked).
+
+:Parameters:
+ `data` : Timeseries
+ Data to process
+ `start_date` : Date *[None]*
+ Starting date. If None, uses the first date.
+ `end_date` : Date *[None]*
+ Ending date. If None, uses the last date.
+ `inside` : Boolean *[True]*
+ Whether the dates inside the range should be masked. If not, masks outside.
+ `include_edges` : Boolean *[True]*
+ Whether the starting and ending dates should be masked.
+ `inplace` : Boolean *[True]*
+ Whether the data mask should be modified in place. If not, returns a new
+ TimeSeries.
+"""
+ if not isTimeSeries(data):
+ raise ValueError,"Data should be a valid TimeSeries!"
+ # Check the starting date ..............
+ if start_date is None:
+ start_date = data._dates[0]
+ elif isinstance(start_date, str):
+ start_date = Date(data.freq, string=start_date)
+ elif not isDateType(start_date):
+ raise DateError,"Starting date should be a valid date!"
+ start_date = max(start_date, data.dates[0])
+ # Check the ending date ................
+ if end_date is None:
+ end_date = data._dates[-1]
+ elif isinstance(end_date, str):
+ end_date = Date(data.freq, string=end_date)
+ elif not isDateType(end_date):
+ raise DateError,"Starting date should be a valid date!"
+ end_date = min(end_date, data.dates[-1])
+ # Constructs the selection mask .........
+ if inside:
+ if include_edges:
+ selection = (data.dates >= start_date) & (data.dates <= end_date)
+ else:
+ selection = (data.dates > start_date) & (data.dates < end_date)
+ else:
+ if include_edges:
+ selection = (data.dates <= start_date) | (data.dates >= end_date)
+ else:
+ selection = (data.dates < start_date) | (data.dates > end_date)
+ # Process the data:
+ if inplace:
+ if data._mask is nomask:
+ data._mask = selection
+ else:
+ data._mask += selection
+ else:
+ return TimeSeries(data, mask=selection, keep_mask=True)
+ return data
+
+def mask_inside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling inside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=True, include_edges=include_edges, inplace=inplace)
+def mask_outside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling outside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=False, include_edges=include_edges, inplace=inplace)
+
+def adjust_endpoints(a, start_date=None, end_date=None):
+ """Returns a TimeSeries going from `start_date` to `end_date`.
+ If `start_date` and `end_date` both fall into the initial range of dates,
+ the new series is NOT a copy.
+ """
+ # Series validity tests .....................
+ if not isinstance(a, TimeSeries):
+ raise TypeError,"Argument should be a valid TimeSeries object!"
+ if a.freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ if not a.dates.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+ # Dates validity checks .,...................
+ msg = "%s should be a valid Date instance! (got %s instead)"
+ (dstart, dend) = a.dates[[0,-1]]
+ if start_date is None:
+ start_date = dstart
+ start_lag = 0
+ else:
+ if not isDateType(start_date):
+ raise TypeError, msg % ('start_date', type(start_date))
+ start_lag = start_date - dstart
+
+ if end_date is None:
+ end_date = dend
+ end_lag = 0
+ else:
+ if not isDateType(end_date):
+ raise TypeError, msg % ('end_date', type(end_date))
+ end_lag = end_date - dend
+ if start_lag >= 0:
+ if end_lag == 0:
+ return a[start_lag:]
+ elif end_lag < 0:
+ return a[start_lag:end_lag]
+
+ newdates = date_array(start_date=start_date, end_date=end_date)
+ newshape = list(a.shape)
+ newshape[0] = len(newdates)
+ newshape = tuple(newshape)
+
+ newdata = masked_array(numeric.empty(newshape, dtype=a.dtype), mask=True)
+ newseries = TimeSeries(newdata, newdates)
+ start_date = max(start_date, dstart)
+ end_date = min(end_date, dend) + 1
+ newseries[start_date:end_date] = a[start_date:end_date]
+ return newseries
+
+
+def align_series(*series, **kwargs):
+ """Aligns several TimeSeries, so that their starting and ending dates match.
+ Series are resized and filled with mased values accordingly.
+
+ The function accepts two extras parameters:
+ - `start_date` forces the series to start at that given date,
+ - `end_date` forces the series to end at that given date.
+ By default, `start_date` and `end_date` are set to the smallest and largest
+ dates respectively.
+ """
+ if len(series) < 2:
+ return series
+
+ unique_freqs = numpy.unique([x.freq for x in series])
+ try:
+ common_freq = unique_freqs.item()
+ except ValueError:
+ raise TimeSeriesError, \
+ "All series must have same frequency!"
+ if common_freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ valid_states = [x.isvalid() for x in series]
+ if not numpy.all(valid_states):
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ start_date = kwargs.pop('start_date', min([x.start_date() for x in series]))
+ if isinstance(start_date,str):
+ start_date = Date(common_freq, string=start_date)
+ end_date = kwargs.pop('end_date', max([x.end_date() for x in series]))
+ if isinstance(end_date,str):
+ end_date = Date(common_freq, string=end_date)
+
+ return [adjust_endpoints(x, start_date, end_date) for x in series]
+aligned = align_series
+
+
+def convert(series, freq, func='auto', position='END', interp=None):
+ """Converts a series to a frequency
+
+ When converting to a lower frequency, func is a function that acts
+ on a 1-d array and returns a scalar or 1-d array. func should handle
+ masked values appropriately. If func is "auto", then an
+ appropriate function is determined based on the observed attribute
+ of the series. If func is None, then a 2D array is returned, where each
+ column represents the values appropriately grouped into the new frequency.
+ interp and position will be ignored in this case.
+
+ When converting to a higher frequency, position is 'START' or 'END'
+ and determines where the data point is in each period (eg. if going
+ from monthly to daily, and position is 'END', then each data point is
+ placed at the end of the month). Interp is the method that will be used
+ to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
+ and None.
+
+ Note: interp currently not implemented
+ """
+ if not isinstance(series,TimeSeries):
+ raise TypeError, "The argument should be a valid TimeSeries!"
+ if not series.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ if position.upper() not in ('END','START'):
+ raise ValueError("invalid value for position argument: (%s)",str(position))
+
+ toFreq = corelib.fmtFreq(freq)
+ fromFreq = series.freq
+ start_date = series._dates[0]
+
+ if fromFreq == toFreq:
+ return series.copy()
+
+ if series.size == 0:
+ return TimeSeries(series, freq=toFreq,
+ start_date=start_date.asfreq(toFreq))
+ if func == 'auto':
+ func = corelib.obs_dict[series.observed]
+
+ tempData = series._series.filled()
+ tempMask = getmaskarray(series)
+
+ cRetVal = cseries.reindex(tempData, fromFreq, toFreq, position,
+ int(start_date), tempMask)
+ _values = cRetVal['values']
+ _mask = cRetVal['mask']
+
+ tempData = masked_array(_values, mask=_mask)
+
+ if tempData.ndim == 2:
+# tempData = tempData.T
+ if func is not None:
+ tempData = MA.apply_along_axis(func, 1, tempData)
+ else:
+ tempData = tempData.T
+
+ #startIndex = cseries.convert(start_date, fromFreq, toFreq)
+ newStart = series._dates[0].asfreq(toFreq, "BEFORE")
+ newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+
+ newseries = TimeSeries(tempData, freq=toFreq,
+ observed=series.observed,
+ start_date=newStart)
+ return newseries
+# return adjust_endpoints(newseries, end_date=newEnd)
+# return (tempData, newStart, toFreq, newseries, _values, _mask)
+
+
+def fill_missing_dates(data, dates=None, freq=None,fill_value=None):
+ """Finds and fills the missing dates in a time series.
+The data corresponding to the initially missing dates are masked, or filled to
+`fill_value`.
+
+:Parameters:
+ `data`
+ Initial array of data.
+ `dates`
+ Initial array of dates.
+ `freq` : float *[None]*
+ New date resolutions. If *None*, the initial resolution is used instead.
+ `fill_value` : float *[None]*
+ Default value for missing data. If None, the data are just masked.
+ """
+ freq = corelib.fmtFreq(freq)
+# if freq == 'U':
+# raise ValueError,\
+# "Unable to define a proper date resolution (found %s)." % freq
+ if dates is None:
+ if not isTimeSeries(data):
+ raise InsufficientDateError
+ dates = data._dates
+ data = data._series
+ freq = dates.freq
+ elif not isinstance(dates, DateArray):
+ dates = DateArray(dates, freq)
+ dflat = dates.ravel().asfreq(freq)
+ n = len(dflat)
+ if not dflat.has_missing_dates():
+ return time_series(data, dflat)
+
+ # ...and now, fill it ! ......
+ (tstart, tend) = dflat[[0,-1]]
+ newdates = date_array(start_date=tstart, end_date=tend, include_last=True)
+ nsize = newdates.size
+ #.............................
+ # Get the steps between consecutive data. We need relativedelta to deal w/ months
+ delta = dflat.get_steps()-1
+ gap = delta.nonzero()
+ slcid = numpy.r_[[0,], numpy.arange(1,n)[gap], [n,]]
+ oldslc = numpy.array([slice(i,e) for (i,e) in numpy.broadcast(slcid[:-1],slcid[1:])])
+ addidx = delta[gap].astype(int_).cumsum()
+ newslc = numpy.r_[[oldslc[0]],
+ [slice(i+d,e+d) for (i,e,d) in \
+ numpy.broadcast(slcid[1:-1],slcid[2:],addidx)]
+ ]
+ #.............................
+ # Just a quick check
+ for (osl,nsl) in zip(oldslc,newslc):
+ assert numpy.equal(dflat[osl],newdates[nsl]).all(),\
+ "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+ #.............................
+ data = MA.asarray(data)
+ newdata = MA.masked_array(numeric.empty(nsize, data.dtype),
+ mask=numeric.ones(nsize, bool_))
+ if fill_value is None:
+ if hasattr(data,'fill_value'):
+ fill_value = data.fill_value
+ else:
+ fill_value = MA.default_fill_value(data)
+ #data = data.filled(fill_value)
+ #....
+ for (new,old) in zip(newslc,oldslc):
+ newdata[new] = data[old]
+ # Get new shape ..............
+ if data.ndim == 1:
+ nshp = (newdates.size,)
+ else:
+ nshp = tuple([-1,] + list(data.shape[1:]))
+ return time_series(newdata.reshape(nshp), newdates)
+
+
+#######--------------------------------------------------------------------------
+###---- --- Archiving ---
+#######--------------------------------------------------------------------------
+
+
+
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ from maskedarray.testutils import assert_equal
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ data = masked_array(numeric.arange(15, dtype=float_), mask=[1,0,0,0,0]*3)
+# btseries = BaseTimeSeries(data._data, dates)
+ tseries = time_series(data, dlist)
+ dseries = numpy.log(tseries)
+ if 1:
+ mlist = ['2005-%02i' % i for i in range(1,13)]
+ mlist += ['2006-%02i' % i for i in range(1,13)]
+ mdata = numpy.arange(24)
+ mser1 = time_series(mdata, mlist, observed='SUMMED')
+ #
+ mlist2 = ['2004-%02i' % i for i in range(1,13)]
+ mlist2 += ['2005-%02i' % i for i in range(1,13)]
+ mser2 = time_series(mdata, mlist2, observed='SUMMED')
+ #
+ today = thisday('m')
+ (malg1,malg2) = aligned(mser1, mser2)
+
+ C = convert(mser2,'A')
+ D = convert(mser2,'A',func=None)
+
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ print "."*50+"\ndata"
+ data = masked_array(numeric.arange(15)-6, mask=[1,0,0,0,0]*3)
+ print "."*50+"\nseries"
+ tseries = time_series(data, dlist)
+
+ if 1:
+ mser3 = time_series(MA.mr_[malg1._series, malg2._series].reshape(2,-1),
+ dates=malg1.dates)
+
+
More information about the Scipy-svn
mailing list