[Scipy-svn] r2476 - in trunk/Lib/sandbox/timeseries: . mtimeseries
scipy-svn at scipy.org
scipy-svn at scipy.org
Tue Jan 2 11:55:03 CST 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.si