[Scipy-svn] r2552 - in trunk/Lib/sandbox/timeseries: . build build/lib.linux-x86_64-2.4 build/lib.linux-x86_64-2.4/timeseries build/temp.linux-x86_64-2.4 examples src tests
scipy-svn at scipy.org
scipy-svn at scipy.org
Sun Jan 14 17:39:29 EST 2007
Author: pierregm
Date: 2007-01-14 16:39:15 -0600 (Sun, 14 Jan 2007)
New Revision: 2552
Added:
trunk/Lib/sandbox/timeseries/.project
trunk/Lib/sandbox/timeseries/CHANGELOG
trunk/Lib/sandbox/timeseries/MANIFEST
trunk/Lib/sandbox/timeseries/build/
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/__init__.py
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py
trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py
trunk/Lib/sandbox/timeseries/build/temp.linux-x86_64-2.4/
trunk/Lib/sandbox/timeseries/build/temp.linux-x86_64-2.4/src/
trunk/Lib/sandbox/timeseries/tcore.py
trunk/Lib/sandbox/timeseries/tdates.py
trunk/Lib/sandbox/timeseries/tests/test_dates.py
trunk/Lib/sandbox/timeseries/tests/test_timeseries.py
trunk/Lib/sandbox/timeseries/tseries.py
Removed:
trunk/Lib/sandbox/timeseries/corelib.py
trunk/Lib/sandbox/timeseries/mtimeseries/
trunk/Lib/sandbox/timeseries/timeseries.py
trunk/Lib/sandbox/timeseries/tsdate.py
Modified:
trunk/Lib/sandbox/timeseries/
trunk/Lib/sandbox/timeseries/__init__.py
trunk/Lib/sandbox/timeseries/examples/
trunk/Lib/sandbox/timeseries/examples/example.py
trunk/Lib/sandbox/timeseries/src/
Log:
cf changelog
Property changes on: trunk/Lib/sandbox/timeseries
___________________________________________________________________
Name: svn:ignore
+ sandbox
Added: trunk/Lib/sandbox/timeseries/.project
===================================================================
--- trunk/Lib/sandbox/timeseries/.project 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/.project 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>scipy_svn_timeseries</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.python.pydev.PyDevBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.python.pydev.pythonNature</nature>
+ </natures>
+</projectDescription>
Copied: trunk/Lib/sandbox/timeseries/CHANGELOG (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/CHANGELOG)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/CHANGELOG 2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/CHANGELOG 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,15 @@
+#2007-01-14 : Code reorganization:
+# : - Moved Matt's initial version to archived_version
+# : - Moved Pierre's version to base
+# : tdates
+# : - Fixed a bug w/ definition of months and weeks
+# : - Renamed dateOf to asfreq + use cseries
+#2007-01-04 : tsdate
+# : - Corrected a bug w/ Date.__init__ and 'B' freq
+#2007-01-03 : tseries
+# : - Allowed endpoints adjustment after convert
+# : - Put the estimation of the data length in its own function.
+# : - Added a timestep compatibility check.
+# : - The variables in a multi-series correspond now to the last axis.
+# : - Blocked transpose/swapaxes, temporarily.
+# : - Speed-up fill_missing_dates
\ No newline at end of file
Added: trunk/Lib/sandbox/timeseries/MANIFEST
===================================================================
--- trunk/Lib/sandbox/timeseries/MANIFEST 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/MANIFEST 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,8 @@
+setup.py
+./__init__.py
+./corelib.py
+./cseries.c
+./shiftingarray.py
+./timeseries.py
+./tsdate.py
+./examples/example.py
Modified: trunk/Lib/sandbox/timeseries/__init__.py
===================================================================
--- trunk/Lib/sandbox/timeseries/__init__.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/__init__.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,3 +0,0 @@
-from timeseries import *
-from tsdate import *
-from corelib import *
Property changes on: trunk/Lib/sandbox/timeseries/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Date
Author
Revision
Id
Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/__init__.py
===================================================================
Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,157 @@
+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 ---
+#####---------------------------------------------------------------------------
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten_sequence(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_sequence(elm):
+ yield f
+ else:
+ yield elm
+
+def flatargs(*args):
+ "Flattens the arguments."
+ if not hasattr(args, '__iter__'):
+ return args
+ else:
+ return flatten_sequence(args)
+
+
Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1089 @@
+"""
+: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 mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+import tscore as corelib
+import cseries
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#####---------------------------------------------------------------------------
+#---- --- Date Info ---
+#####---------------------------------------------------------------------------
+OriginDate = mxD.Date(1970)
+secondlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(minutes=1)
+hourlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(hours=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':
+ valtmp = (value - 1)//5
+ self.mxDate = mxD.DateTimeFromAbsDateTime(value + valtmp*7 - valtmp*5)
+ elif self.freq in ['D','U']:
+ self.mxDate = mxD.DateTimeFromAbsDateTime(value)
+ elif self.freq == 'H':
+ self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+ elif self.freq == 'M':
+ self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+ mxD.RelativeDateTime(months=value-1, day=-1)
+ elif self.freq == 'Q':
+ self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+ 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(1,1,7) + \
+ mxD.RelativeDateTime(weeks=value-1)
+# 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 = truncateDate(self.freq, 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 = truncateDate(self.freq, 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 = truncateDate(self.freq,
+ 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 = (weeks*5) + (days - weeks*7)
+ val = days - weeks*2
+ # Daily/undefined
+ elif self.freq in ['D', 'U']:
+ val = self.mxDate.absdate
+ # Hourly........
+ elif self.freq == 'H':
+ val = (self.mxDate - hourlyOriginDate).hours
+ # Monthly.......
+ elif self.freq == 'M':
+ val = (self.mxDate.year-1)*12 + self.mxDate.month
+ # Quarterly.....
+ elif self.freq == 'Q':
+ val = (self.mxDate.year-1)*4 + self.mxDate.month//3
+ # Secondly......
+ elif self.freq == 'S':
+ val = (self.mxDate - secondlyOriginDate).seconds
+ # Minutely......
+ elif self.freq == 'T':
+ val = (self.mxDate - minutelyOriginDate).minutes
+ # Weekly........
+ elif self.freq == 'W':
+ #val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+ val = self.mxDate.absdate//7
+ return int(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 == 'H':
+ fmt = "%d-%b-%Y %H:00"
+ elif self.freq == 'T':
+ fmt = "%d-%b-%Y %H:%M"
+ elif self.freq == "S":
+ 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 asfreq(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 truncateDate(freq, mxDate):
+ """Chops off the irrelevant information from the mxDate passed in."""
+ freq = corelib.fmtFreq(freq)
+ if freq == 'A':
+ return mxD.Date(mxDate.year)
+ elif freq == 'Q':
+ return mxD.Date(mxDate.year, monthToQuarter(mxDate.month)*3)
+ elif freq == 'M':
+ return mxD.Date(mxDate.year, mxDate.month)
+ elif freq == 'W':
+ d = mxDate.absdate
+ return mxD.DateTimeFromAbsDateTime(d + (7 - d % 7) % 7 - 1)
+ elif freq in ('B', 'D'):
+ if freq == 'B' and mxDate.day_of_week in [5,6]:
+ raise ValueError("Weekend passed as business day")
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day)
+ elif freq == 'H':
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+ mxDate.hour)
+ elif freq == 'T':
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+ mxDate.hour, mxDate.minute)
+ else:
+ return mxDate
+
+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 asfreq(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):
+ if not isinstance(date, Date):
+ raise DateError, "Date should be a valid Date instance!"
+
+ if date.freq == toFreq:
+ return date
+ else:
+ value = cseries.asfreq(numeric.asarray(date.value), date.freq, toFreq, _rel)
+ if value > 0:
+ return Date(freq=toFreq, value=value)
+ else:
+ return None
+# # 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 isinstance(index, Date):
+ index = self.find_dates(index)
+ elif numeric.asarray(index).dtype.kind == 'O':
+ try:
+ index = self.find_dates(index)
+ except AttributeError:
+ pass
+ r = ndarray.__getitem__(self, index)
+ if r.size == 1:
+ # Only one element, and it's not a scalar: we have a DateArray of size 1
+ if len(r.shape) > 0:
+ r = r.item()
+ return Date(self.freq, value=r)
+ else:
+ return r
+
+ def __repr__(self):
+ return ndarray.__repr__(self)
+ #......................................................
+ @property
+ def years(self):
+ "Returns the years."
+ return numeric.asarray([d.year() for d in self], dtype=int_)
+ @property
+ def months(self):
+ "Returns the months."
+ return numeric.asarray([d.month() for d in self], dtype=int_)
+ @property
+ def day_of_year(self):
+ "Returns the days of years."
+ return numeric.asarray([d.day_of_year() for d in self], dtype=int_)
+ yeardays = day_of_year
+ @property
+ def day_of_week(self):
+ "Returns the days of week."
+ return numeric.asarray([d.day_of_week() for d in self], dtype=int_)
+ #.... Conversion methods ....................
+# def toobject(self):
+# "Converts the dates from ordinals to Date objects."
+# # Note: we better try to cache the result
+# if self.__toobj is None:
+## toobj = numeric.empty(self.size, dtype=object_)
+## toobj[:] = [Date(self.freq, value=d) for d in self]
+## self.__toobj = toobj
+# self.__toobj = self
+# return self.__toobj
+ #
+ def tovalue(self):
+ "Converts the dates to integer values."
+ return numeric.asarray(self)
+ #
+ def toordinal(self):
+ "Converts the dates from values to ordinals."
+ # Note: we better try to cache the result
+ if self.__toord is None:
+# diter = (Date(self.freq, value=d).toordinal() for d in self)
+ diter = (d.toordinal() for d in self)
+ toord = numeric.fromiter(diter, dtype=float_)
+ self.__toord = toord
+ return self.__toord
+ #
+ def tostring(self):
+ "Converts the dates to strings."
+ # Note: we better cache the result
+ if self.__tostr is None:
+ firststr = str(self[0])
+ if self.size > 0:
+ ncharsize = len(firststr)
+ tostr = numpy.fromiter((str(d) for d in self),
+ dtype='|S%i' % ncharsize)
+ else:
+ tostr = firststr
+ self.__tostr = tostr
+ return self.__tostr
+ #
+# def asfreq_ini(self, freq=None):
+# "Converts the dates to another frequency."
+# # Note: As we define a new object, we don't need caching
+# if freq is None:
+# return self
+# freq = corelib.fmtFreq(freq)
+# if freq == self.freq:
+# return self
+# if self.isvalid():
+# new = numeric.arange(self.size, dtype=int_)
+# new += self[0].asfreq(freq).value
+# else:
+# new = numpy.fromiter((d.asfreq(freq).value for d in self),
+# dtype=float_)
+# return DateArray(new, freq=freq)
+
+ def asfreq(self, freq=None, relation="BEFORE"):
+ "Converts the dates to another frequency."
+ # Note: As we define a new object, we don't need caching
+ if freq is None:
+ return self
+ freq = corelib.fmtFreq(freq)
+ if freq == self.freq:
+ return self
+ _rel = relation.upper()[0]
+ new = cseries.asfreq(numeric.asarray(self), self.freq, freq, _rel)
+ return DateArray(new, freq=freq)
+ #......................................................
+ def find_dates(self, *dates):
+ "Returns the indices corresponding to given dates, as an array."
+ ifreq = self.freq
+ c = numpy.zeros(self.shape, bool_)
+ for d in corelib.flatargs(*dates):
+ if d.freq != ifreq:
+ d = d.asfreq(ifreq)
+ c += (self == d.value)
+ c = c.nonzero()
+ if fromnumeric.size(c) == 0:
+ raise ValueError, "Date out of bounds!"
+ return c
+# def find_dates_alt(self, *dates):
+# "Returns the indices corresponding to given dates, as an array."
+# ifreq = self.freq
+# c = numpy.zeros(self.shape, bool_)
+# dates = date_array([d for d in corelib.flatargs(*dates)]).asfreq(ifreq)
+# for d in numeric.asarray(dates):
+# c += (self == d)
+# c = c.nonzero()
+# if fromnumeric.size(c) == 0:
+# raise ValueError, "Date out of bounds!"
+# return c
+ def date_to_index(self, date):
+ "Returns the index corresponding to one given date, as an integer."
+ if self.isvalid():
+ index = date.value - self[0].value
+ if index < 0 or index > self.size:
+ raise ValueError, "Date out of bounds!"
+ return index
+ else:
+ index_asarray = (self == date.value).nonzero()
+ if fromnumeric.size(index_asarray) == 0:
+ raise ValueError, "Date out of bounds!"
+ return index_asarray[0][0]
+ #......................................................
+ def get_steps(self):
+ """Returns the time steps between consecutive dates.
+ The timesteps have the same unit as the frequency of the series."""
+ if self.freq == 'U':
+ warnings.warn("Undefined frequency: assuming daily!")
+ if self.__steps is None:
+ val = numeric.asarray(self).ravel()
+ if val.size > 0:
+ steps = val[1:] - val[:-1]
+ if self.__full is None:
+ self.__full = (steps.max() == 1)
+ if self.__hasdups is None:
+ self.__hasdups = (steps.min() == 0)
+ else:
+ self.__full = True
+ self.__hasdups = False
+ self.__steps = steps
+ return self.__steps
+
+ def has_missing_dates(self):
+ "Returns whether the DateArray have missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return not(self.__full)
+
+ def isfull(self):
+ "Returns whether the DateArray has no missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return self.__full
+
+ def has_duplicated_dates(self):
+ "Returns whether the DateArray has duplicated dates."
+ if self.__hasdups is None:
+ steps = self.get_steps()
+ return self.__hasdups
+
+ def isvalid(self):
+ "Returns whether the DateArray is valid: no missing/duplicated dates."
+ return (self.isfull() and not self.has_duplicated_dates())
+ #......................................................
+class _datearithmetics(object):
+ """Defines a wrapper for arithmetic methods.
+Instead of directly calling a ufunc, the corresponding method of the `array._data`
+object is called instead.
+If `asdates` is True, a DateArray object is returned , else a regular ndarray
+is returned.
+ """
+ def __init__ (self, methodname, asdates=True):
+ """
+:Parameters:
+ - `methodname` (String) : Method name.
+ """
+ self.methodname = methodname
+ self._asdates = asdates
+ self.__doc__ = getattr(methodname, '__doc__')
+ self.obj = None
+ #dalog.info('__datearithmetics got method %s' % methodname)
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args, **kwargs):
+ "Execute the call behavior."
+ instance = self.obj
+ freq = instance.freq
+ if 'context' not in kwargs:
+ kwargs['context'] = 'DateOK'
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ method = getattr(super(DateArray,instance), self.methodname)
+ if isinstance(other, DateArray):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+# other =
+ elif isinstance(other, Date):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+ other = other.value
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ elif isinstance(other, ndarray):
+ if other.dtype.kind not in ['i','f']:
+ raise ArithmeticDateError
+ if self._asdates:
+ return instance.__class__(method(other, *args),
+ freq=freq)
+ else:
+ return method(other, *args)
+#............................
+DateArray.__add__ = _datearithmetics('__add__', asdates=True)
+DateArray.__radd__ = _datearithmetics('__add__', asdates=True)
+DateArray.__sub__ = _datearithmetics('__sub__', asdates=True)
+DateArray.__rsub__ = _datearithmetics('__rsub__', asdates=True)
+DateArray.__le__ = _datearithmetics('__le__', asdates=False)
+DateArray.__lt__ = _datearithmetics('__lt__', asdates=False)
+DateArray.__ge__ = _datearithmetics('__ge__', asdates=False)
+DateArray.__gt__ = _datearithmetics('__gt__', asdates=False)
+DateArray.__eq__ = _datearithmetics('__eq__', asdates=False)
+DateArray.__ne__ = _datearithmetics('__ne__', asdates=False)
+
+#####---------------------------------------------------------------------------
+#---- --- DateArray functions ---
+#####---------------------------------------------------------------------------
+def isDateArray(a):
+ "Tests whether an array is a DateArray object."
+ return isinstance(a,DateArray)
+
+def guess_freq(dates):
+ """Tries to estimate the frequency of a list of dates, by checking the steps
+ between consecutive dates The steps should be in days.
+ Returns a frequency code (alpha character)."""
+ ddif = numeric.asarray(numpy.diff(dates))
+ ddif.sort()
+ if ddif[0] == ddif[-1] == 1.:
+ fcode = 'D'
+ elif (ddif[0] == 1.) and (ddif[-1] == 3.):
+ fcode = 'B'
+ elif (ddif[0] > 3.) and (ddif[-1] == 7.):
+ fcode = 'W'
+ elif (ddif[0] >= 28.) and (ddif[-1] <= 31.):
+ fcode = 'M'
+ elif (ddif[0] >= 90.) and (ddif[-1] <= 92.):
+ fcode = 'Q'
+ elif (ddif[0] >= 365.) and (ddif[-1] <= 366.):
+ fcode = 'A'
+ elif numpy.abs(24.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(24.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'H'
+ elif numpy.abs(1440.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(1440.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'T'
+ elif numpy.abs(86400.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(86400.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'S'
+ else:
+ warnings.warn("Unable to estimate the frequency! %.3f<>%.3f" %\
+ (ddif[0], ddif[-1]))
+ fcode = 'U'
+ return fcode
+
+
+def _listparser(dlist, freq=None):
+ "Constructs a DateArray from a list."
+ dlist = numeric.asarray(dlist)
+ dlist.sort()
+ # Case #1: dates as strings .................
+ if dlist.dtype.kind == 'S':
+ #...construct a list of ordinals
+ ords = numpy.fromiter((mxDFromString(s).absdays for s in dlist),
+ float_)
+ ords += 1
+ #...try to guess the frequency
+ if freq is None:
+ freq = guess_freq(ords)
+ #...construct a list of dates
+ dates = [Date(freq, string=s) for s in dlist]
+ # Case #2: dates as numbers .................
+ elif dlist.dtype.kind in ['i','f']:
+ #...hopefully, they are values
+ if freq is None:
+ freq = guess_freq(dlist)
+ dates = dlist
+ # Case #3: dates as objects .................
+ elif dlist.dtype.kind == 'O':
+ template = dlist[0]
+ #...as Date objects
+ if isinstance(template, Date):
+ dates = numpy.fromiter((d.value for d in dlist), float_)
+ #...as mx.DateTime objects
+ elif hasattr(template,'absdays'):
+ # no freq given: try to guess it from absdays
+ if freq is None:
+ ords = numpy.fromiter((s.absdays for s in dlist), float_)
+ ords += 1
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=m) for m in dlist]
+ #...as datetime objects
+ elif hasattr(dlist[0], 'toordinal'):
+ ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+ if freq is None:
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=mxD.DateTimeFromAbsDays(a)) for a in ords]
+ #
+ result = DateArray(dates, freq)
+ return result
+
+
+def date_array(dlist=None, start_date=None, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from:
+ - a starting date and either an ending date or a given length.
+ - a list of dates.
+ """
+ freq = corelib.fmtFreq(freq)
+ # Case #1: we have a list ...................
+ if dlist is not None:
+ # Already a DateArray....................
+ if isinstance(dlist, DateArray):
+ if freq != dlist.freq:
+ return dlist.asfreq(freq)
+ else:
+ return dlist
+ return _listparser(dlist, freq)
+ # Case #2: we have a starting date ..........
+ if start_date is None:
+ raise InsufficientDateError
+# if not isDateType(start_date):
+ if not isinstance(start_date, Date):
+ raise DateError, "Starting date should be a valid Date instance!"
+ # Check if we have an end_date
+ if end_date is None:
+ if length is None:
+ raise ValueError,"No length precised!"
+ else:
+ if not isinstance(end_date, Date):
+ raise DateError, "Ending date should be a valid Date instance!"
+# assert(isDateType(end_date),
+# "Starting date should be a valid Date instance!")
+ length = end_date - start_date
+ if include_last:
+ length += 1
+# dlist = [(start_date+i).value for i in range(length)]
+ dlist = numeric.arange(length, dtype=int_)
+ dlist += start_date.value
+ if freq is None:
+ freq = start_date.freq
+ return DateArray(dlist, freq=freq)
+datearray = date_array
+
+def date_array_fromlist(dlist, freq=None):
+ "Constructs a DateArray from a list of dates."
+ return date_array(dlist=dlist, freq=freq)
+
+def date_array_fromrange(start_date, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from a starting date and either an ending date or
+ a length."""
+ return date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(DateArray, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__(self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+
+
+################################################################################
Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1254 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+
+ - an array storing the time information (as a `DateArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__ = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+
+import logging
+import weakref
+
+
+import numpy
+from numpy.core import bool_, float_, int_
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+import numpy.core.umath as umath
+from numpy import ndarray
+from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+#from cPickle import dump, dumps
+
+import maskedarray as MA
+#import numpy.core.ma as MA
+reload(MA)
+#MaskedArray = MA.MaskedArray
+from maskedarray.core import MaskedArray
+masked = MA.masked
+nomask = MA.nomask
+MAError = MA.MAError
+masked_array = MA.masked_array
+filled = MA.filled
+getmask = MA.getmask
+getmaskarray = MA.getmaskarray
+make_mask_none = MA.make_mask_none
+mask_or = MA.mask_or
+make_mask = MA.make_mask
+
+oldma = (MA.__name__ == 'numpy.core.ma')
+
+import tscore as corelib
+#reload(corelib)
+from tscore import *
+
+import tdates
+reload(tdates)
+from tdates import DateError, InsufficientDateError
+from tdates import Date, isDate, DateArray, isDateArray, \
+ date_array, date_array_fromlist, date_array_fromrange, thisday
+
+import cseries
+reload(cseries)
+
+#...............................................................................
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+talog = logging.getLogger('log.TimeArray')
+tslog = logging.getLogger('TimeSeries')
+btslog = logging.getLogger('BaseTimeSeries')
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+#### --------------------------------------------------------------------------
+#--- ... TimeSeriesError class ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+ "Class for TS related errors."
+ def __init__ (self, args=None):
+ "Creates an exception."
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculates the string representation."
+ return str(self.args)
+ __repr__ = __str__
+
+class TimeSeriesCompatibilityError(TimeSeriesError):
+ """Defines the exception raised when series are incompatible."""
+ def __init__(self, mode, first, second):
+ if mode == 'freq':
+ msg = "Incompatible time steps! (%s <> %s)"
+ elif mode == 'start_date':
+ msg = "Incompatible starting dates! (%s <> %s)"
+ elif mode == 'size':
+ msg = "Incompatible sizes! (%s <> %s)"
+ msg = msg % (first, second)
+ TimeSeriesError.__init__(self, msg)
+
+
+#def _compatibilitycheck(a, b):
+def _timeseriescompat(a, b):
+ """Checks the date compatibility of two TimeSeries object.
+ Returns True if everything's fine, or raises an exception."""
+ if not (hasattr(a,'freq') and hasattr(b, 'freq')):
+ return True
+ if a.freq != b.freq:
+ raise TimeSeriesCompatibilityError('freq', a.freq, b.freq)
+ elif a.start_date() != b.start_date():
+ raise TimeSeriesCompatibilityError('start_date',
+ a.start_date(), b.start_date())
+ elif (a._dates.get_steps() != b._dates.get_steps()).any():
+ raise TimeSeriesCompatibilityError('time_steps',
+ a._dates.get_steps(), b._dates.get_steps())
+ elif a.shape != b.shape:
+ raise TimeSeriesCompatibilityError('size', "1: %s" % str(a.shape),
+ "2: %s" % str(b.shape))
+ return True
+
+def _datadatescompat(data,dates):
+ """Checks the compatibility of dates and data at the creation of a TimeSeries.
+ Returns True if everything's fine, raises an exception otherwise."""
+ # If there's only 1 element, the date is a Date object, which has no size...
+ tsize = numeric.size(dates)
+ dsize = data.size
+ # Only one data
+ if dsize == tsize:
+ return True
+ elif data.ndim > 1:
+ dsize = numeric.asarray(data.shape)[:-1].prod()
+ if dsize == tsize:
+ return True
+ raise TimeSeriesCompatibilityError('size', "data: %s" % dsize,
+ "dates: %s" % tsize)
+
+def _getdatalength(data):
+ "Estimates the length of a series (size/nb of variables)."
+ if numeric.ndim(data) >= 2:
+ return numeric.asarray(numeric.shape(data))[:-1].prod()
+ else:
+ return numeric.size(data)
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+#if oldma:
+# parentclass = ndarray
+#else:
+# parentclass = MaskedArray
+#
+class TimeSeries(MaskedArray, object):
+ """Base class for the definition of time series.
+A time series is here defined as the combination of three arrays:
+
+ - `series` : *[ndarray]*
+ Data part
+ - `mask` : *[ndarray]*
+ Mask part
+ - `dates` : *[DateArray]*
+ Date part
+
+The combination of `series` and `dates` is the `data` part.
+ """
+ def __new__(cls, data, dates=None, mask=nomask,
+ freq=None, observed=None, start_date=None,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ #tslog.info("__new__: received data types %s, %s" % (type(data), data))
+ options = dict(copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask, )
+ if isinstance(data, TimeSeries):
+ # Check dates ........
+ if dates is None:
+ newdates = data._dates
+ else:
+ if not hasattr(dates,'freq'):
+ raise DateError, "Invalid Dates!"
+ newdates = dates
+ data._dates = newdates
+ if hasattr(data, '_data') and hasattr(data._data, '_dates'):
+ data._data._dates = newdates
+ cls._defaultdates = newdates
+ # Check frequency......
+ if freq is not None:
+ freq = corelib.fmtFreq(freq)
+ if freq != newdates.freq:
+ _dates = newdates.tofreq(freq)
+ else:
+ freq = newdates.freq
+ # Check observed.......
+ if observed is not None:
+ observed = data._observed
+ cls._defaultobserved = observed
+ _data = data._series
+ else:
+ # Check dates ........
+ if dates is None:
+ length = _getdatalength(data)
+ newdates = date_array(start_date=start_date, length=length,
+ freq=freq)
+ elif not hasattr(dates, 'freq'):
+ newdates = date_array(dlist=dates, freq=freq)
+ else:
+ newdates = dates
+ _data = data
+ if hasattr(data, '_mask') :
+ mask = mask_or(data._mask, mask)
+ cls._defaultdates = newdates
+ cls._defaultobserved = observed
+# if oldma:
+# newdata = MaskedArray(data, mask=mask, dtype=dtype,
+# copy=copy,fill_value=fill_value)
+# cls._defaultmask = newdata._mask
+# cls._defaulthardmask = True
+# cls._fill_value = newdata._fill_value
+# assert(_datadatescompat(newdata,dates))
+# return ndarray.__new__(cls,shape=newdata.shape,dtype=newdata.dtype,
+# buffer=newdata._data)
+# _data = data
+# newdata = MaskedArray.__new__(cls, data=_data, mask=mask, **options)
+ newdata = super(TimeSeries,cls).__new__(cls, _data, mask=mask,
+ **options)
+ assert(_datadatescompat(data,newdates))
+ return newdata
+
+ #..................................
+ def __array_wrap__(self, obj, context=None):
+# if oldma:
+# tmpself = MaskedArray(self._data, mask=self._mask)
+# return TimeSeries(MaskedArray.__array_wrap__(tmpself, obj, context),
+# dates=self._dates)
+# print "__array_wrap__"
+ return TimeSeries(super(TimeSeries,self).__array_wrap__(obj, context),
+ dates=self._dates)
+ #............................................
+ def __array_finalize__(self,obj):
+ #tslog.info("__array_finalize__ received %s" % type(obj))
+ if isinstance(obj, TimeSeries):
+ self._dates = obj._dates
+ self._data = obj._series._data
+ self._mask = obj._series._mask
+ self._series = obj._series
+ self._hardmask = obj._series._hardmask
+ self.observed = obj.observed
+ self._fill_value = obj._fill_value
+ else:
+ self._dates = self._defaultdates
+ self.observed = self._defaultobserved
+ self._series = MA.array(obj, mask=self._defaultmask,
+ copy=False, hard_mask=self._defaulthardmask)
+ self._mask = self._defaultmask
+ self._data = obj
+ self._hardmask = self._defaulthardmask
+ self.fill_value = self._fill_value
+ self._mask = self._series._mask
+ self._data = self._series._data
+ self._hardmask = self._series._hardmask
+ #tslog.info("__array_finalize__ sends %s" % type(self))
+ return
+ #............................................
+ def __getattribute__(self,attr):
+ "Returns a given attribute."
+ # Here, we need to be smart: _mask should call _series._mask...
+ if attr in ['_data','_mask','_hardmask']:
+ return getattr(self._series,attr)
+ return super(TimeSeries, self).__getattribute__(attr)
+ def __setattribute__(self,attr, value):
+ """Sets an attribute to a given value."""
+ # Same thing here: if we modify ._mask, we need to modify _series._mask
+ # ...as well
+ super(TimeSeries, self).__setattribute__(attr, value)
+ if attr in ['_data','_mask','_hardmask']:
+ super(self._series.__class__, self._series).__setattribute__(attr, value)
+ setattr(self._series, attr, value)
+ #............................................
+ def __checkindex(self, index):
+ "Checks the validity of an index."
+ if isinstance(index, int):
+ return index
+ if isinstance(index, str):
+ return self._dates.date_to_index(Date(self._dates.freq, string=index))
+ elif isDate(index) or isDateArray(index):
+ return self._dates.date_to_index(index)
+ elif isinstance(index,slice):
+ slice_start = self.__checkindex(index.start)
+ slice_stop = self.__checkindex(index.stop)
+ return slice(slice_start, slice_stop, index.step)
+ elif isTimeSeries(index):
+ index = index._series
+ if getmask(index) is not nomask:
+ msg = "Masked arrays must be filled before they can be used as indices!"
+ raise IndexError, msg
+ return index
+
+ def __getitem__(self, index):
+ """x.__getitem__(y) <==> x[y]
+Returns the item described by i. Not a copy as in previous versions.
+ """
+ index = self.__checkindex(index)
+ data = self._series[index]
+ date = self._dates[index]
+ m = self._mask
+ scalardata = (len(numeric.shape(data))==0)
+ #
+ if m is nomask:
+ if scalardata:
+ return TimeSeries(data, dates=date)
+ else:
+ return TimeSeries(data, dates=date, mask=nomask, keep_mask=True,
+ copy=False)
+ #....
+ mi = m[index]
+ if mi.size == 1:
+ if mi:
+ return TimeSeries(data, dates=date, mask=True)
+ return TimeSeries(data, dates=date, mask=nomask)
+ else:
+ return TimeSeries(data, dates=date, mask=mi)
+ #........................
+ def __setitem__(self, index, value):
+ """x.__setitem__(i, y) <==> x[i]=y
+Sets item described by index. If value is masked, masks those locations.
+ """
+ if self is masked:
+ raise MAError, 'Cannot alter the masked element.'
+ index = self.__checkindex(index)
+ #....
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[index], value))
+ self._series[index] = value._series
+ else:
+ self._series[index] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+
+ #........................
+ def __getslice__(self, i, j):
+ "Gets slice described by i, j"
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ (data, date) = (self._series[i:j], self._dates[i:j])
+ return TimeSeries(data, dates=date, copy=False)
+ #....
+ def __setslice__(self, i, j, value):
+ "Gets item described by i. Not a copy as in previous versions."
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ #....
+ data = self._series[i:j]
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[i:j], value))
+ self._series[i:j] = value._series
+ else:
+ self._series[i:j] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+ #......................................................
+ def __len__(self):
+ if self.ndim == 0:
+ return 0
+ return ndarray.__len__(self)
+ #......................................................
+ def __str__(self):
+ """Returns a string representation of self (w/o the dates...)"""
+ return str(self._series)
+ def __repr__(self):
+ """Calculates the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ desc = """\
+timeseries(data =
+ %(data)s,
+ dates =
+ %(time)s,
+ freq = %(freq)s)
+"""
+ desc_short = """\
+timeseries(data = %(data)s,
+ dates = %(time)s,
+ freq = %(freq)s)
+"""
+ if numeric.size(self._dates) > 2 and self.isvalid():
+ timestr = "[%s ... %s]" % (str(self._dates[0]),str(self._dates[-1]))
+ else:
+ timestr = str(self.dates)
+
+ if self.ndim <= 1:
+ return desc_short % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ return desc % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ #............................................
+ def _get_mask(self):
+ """Returns the current mask."""
+ return self._series._mask
+ def _set_mask(self, mask):
+ """Sets the mask to `mask`."""
+ mask = make_mask(mask, copy=False, small_mask=True)
+ if mask is not nomask:
+ if mask.size != self._data.size:
+ raise ValueError, "Inconsistent shape between data and mask!"
+ if mask.shape != self._data.shape:
+ mask.shape = self._data.shape
+ self._series._mask = mask
+ else:
+ self._series._mask = nomask
+ mask = property(fget=_get_mask, fset=_set_mask, doc="Mask")
+
+ def ids (self):
+ """Return the ids of the data, dates and mask areas"""
+ return (id(self._series), id(self.dates),)
+
+ def copy(self):
+ "Returns a copy of the TimeSeries."
+ return TimeSeries(self, copy=True)
+
+ #------------------------------------------------------
+ @property
+ def series(self):
+ "Returns the series."
+ return self._series
+ @property
+ def dates(self):
+ """Returns the dates"""
+ return self._dates
+ @property
+ def freq(self):
+ """Returns the corresponding frequency."""
+ return self._dates.freq
+# @property
+ def years(self):
+ """Returns the corresponding years."""
+ return self._dates.years
+# @property
+ def months(self):
+ """Returns the corresponding months."""
+ return self._dates.months
+# @property
+ def yeardays(self):
+ """Returns the corresponding days of year."""
+ return self._dates.yeardays
+ day_of_year = yeardays
+# @property
+ def weekdays(self):
+ """Returns the corresponding days of weeks."""
+ return self._dates.day_of_week
+ day_of_week = weekdays
+
+ def start_date(self):
+ """Returns the first date of the series."""
+ return self._dates[0]
+#
+ def end_date(self):
+ """Returns the last date of the series."""
+ return self._dates[-1]
+
+ def isvalid(self):
+ """Returns whether the series has no duplicate/missing dates."""
+ return self._dates.isvalid()
+
+ def has_missing_dates(self):
+ """Returns whether there's a date gap in the series."""
+ return self._dates.has_missing_dates()
+
+ def isfull(self):
+ """Returns whether there's no date gap in the series."""
+ return self._dates.isfull()
+
+ def has_duplicated_dates(self):
+ """Returns whether there are duplicated dates in the series."""
+ return self._dates.has_duplicated_dates()
+
+ def date_to_index(self, date):
+ "Returns the index corresponding to a given date, as an integer."
+ return self._dates.date_to_index(date)
+ #.....................................................
+ def asfreq(self, freq=None):
+ "Converts the dates to another frequency."
+ if freq is None:
+ return self
+ return TimeSeries(self._series, dates=self._dates.asfreq(freq))
+
+ def convert(self, freq, func='auto', position='END', interp=None):
+ "Converts the dates to another frequency, and adapt the data."
+ return convert(self, freq, func=func, position=position, interp=interp)
+
+##### --------------------------------------------------------------------------
+##--- ... Additional methods ...
+##### --------------------------------------------------------------------------
+class _inplacemethod(object):
+ """Defines a wrapper for inplace arithmetic array methods (iadd, imul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ self.obj = None
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ assert(_timeseriescompat(instance,other))
+ func = getattr(instance._series, self.f)
+ func(other, *args)
+ return instance
+#......................................
+TimeSeries.__iadd__ = _inplacemethod('__iadd__')
+TimeSeries.__iand__ = _inplacemethod('__iand__')
+TimeSeries.__idiv__ = _inplacemethod('__idiv__')
+TimeSeries.__isub__ = _inplacemethod('__isub__')
+TimeSeries.__imul__ = _inplacemethod('__imul__')
+
+
+class _tsmathmethod(object):
+ """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ _dates = instance._dates
+ #tslog.info("_tsmathmethod: series: %s" % instance,)
+ #tslog.info("_tsmathmethod: other : %s" % other,)
+ func = getattr(instance._series, self.f)
+ if isinstance(other, TimeSeries):
+ assert(_timeseriescompat(instance, other))
+ return instance.__class__(func(other, *args), dates=_dates,)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+TimeSeries.__eq__ = _tsmathmethod('__eq__')
+TimeSeries.__ne__ = _tsmathmethod('__ne__')
+TimeSeries.__lt__ = _tsmathmethod('__lt__')
+TimeSeries.__le__ = _tsmathmethod('__le__')
+TimeSeries.__gt__ = _tsmathmethod('__gt__')
+TimeSeries.__ge__ = _tsmathmethod('__ge__')
+#................................................
+class _tsarraymethod(object):
+ """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+ """
+ def __init__ (self, methodname, ondates=False):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ self._ondates = ondates
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args):
+ "Execute the call behavior."
+ _name = self._name
+ instance = self.obj
+ func_series = getattr(instance._series, _name)
+ if self._ondates:
+ func_dates = getattr(instance._dates, _name)
+ return instance.__class__(func_series(*args),
+ dates=func_dates(*args))
+ else:
+ return instance.__class__(func_series(*args),
+ dates=instance._dates)
+#TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+TimeSeries.ravel = _tsarraymethod('ravel', ondates=True)
+TimeSeries.filled = _tsarraymethod('filled', ondates=False)
+TimeSeries.cumsum = _tsarraymethod('cumsum',ondates=False)
+TimeSeries.cumprod = _tsarraymethod('cumprod',ondates=False)
+TimeSeries.anom = _tsarraymethod('anom',ondates=False)
+
+#......................................
+class _tsaxismethod(object):
+ """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the series.
+ """
+ def __init__ (self, methodname):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args, **params):
+ "Execute the call behavior."
+ (_dates, _series) = (self.obj._dates, self.obj._series)
+ func = getattr(_series, self._name)
+ result = func(*args, **params)
+ if _series.ndim < 2 or _dates.size == _series.size:
+ return result
+ else:
+ try:
+ axis = params.get('axis', args[0])
+ if axis in [-1, _series.ndim-1]:
+ result = TimeSeries(result, dates=_dates)
+ except IndexError:
+ pass
+ return result
+#.......................................
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+class _tsblockedmethods(object):
+ """Defines a wrapper for array methods that should be temporarily disabled.
+ """
+ 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):
+ raise NotImplementedError
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(TimeSeries, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__ (self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+#
+##### ---------------------------------------------------------------------------
+#---- ... Additional methods ...
+##### ---------------------------------------------------------------------------
+def tofile(self, output, sep='\t', format_dates=None):
+ """Writes the TimeSeries to a file.
+
+:Parameters:
+ - `output` (String) : Name or handle of the output file.
+ - `sep` (String) : Column separator *['\t']*.
+ - `format` (String) : Data format *['%s']*.
+ """
+ if not hasattr(output, 'writeline'):
+ ofile = open(output,'w')
+ else:
+ ofile = output
+ if format_dates is None:
+ format_dates = self.dates[0].default_fmtstr()
+ oformat = "%%s%s%s" % (sep,format_dates)
+ for (_dates,_data) in numpy.broadcast(self._dates.ravel().asstrings(),
+ filled(self)):
+ ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+ ofile.close()
+TimeSeries.tofile = tofile
+
+#............................................
+def asrecords(series):
+ """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+ """
+ desctype = [('_dates',int_), ('_series',series.dtype), ('_mask', bool_)]
+ flat = series.ravel()
+ _dates = numeric.asarray(flat._dates)
+ if flat.size > 0:
+ return recfromarrays([_dates, flat._data, getmaskarray(flat)],
+ dtype=desctype,
+ shape = (flat.size,),
+ )
+ else:
+ return recfromarrays([[], [], []], dtype=desctype,
+ shape = (flat.size,),
+ )
+TimeSeries.asrecords = asrecords
+
+def flatten(series):
+ """Flattens a (multi-) time series to 1D series."""
+ shp_ini = series.shape
+ # Already flat time series....
+ if len(shp_ini) == 1:
+ return series
+ # Folded single time series ..
+ newdates = series._dates.ravel()
+ if series._dates.size == series._series.size:
+ newshape = (series._series.size,)
+ else:
+ newshape = (numeric.asarray(shp_ini[:-1]).prod(), shp_ini[-1])
+ newseries = series._series.reshape(newshape)
+ return time_series(newseries, newdates)
+TimeSeries.flatten = flatten
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Archiving ---
+#####---------------------------------------------------------------------------
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+ """Internal function that builds a new TimeSeries 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, int_)
+ _mask = ndarray.__new__(ndarray, baseshape, bool_)
+ return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask,
+ dtype=basetype, fill_value=fill_value)
+#
+def _tsgetstate(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,
+ a.freq,
+ records.flags.fnc,
+ a.fill_value,
+ records
+ )
+ 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, frq, isf, flv, rec) = state
+ a.fill_value = flv
+ a._dates = a._dates.__class__(rec['_dates'], freq=frq)
+ (a._dates).__tostr = None
+ _data = rec['_series'].view(typ)
+ _mask = rec['_mask'].view(MA.MaskType)
+ a._series = masked_array(_data, mask=_mask)
+# a._data.shape = shp
+# a._dates.shape = shp
+# a._mask = rec['_mask'].view(MA.MaskType)
+# a._mask.shape = shp
+#
+def _tsreduce(a):
+ """Returns a 3-tuple for pickling a MaskedArray."""
+ return (_tsreconstruct,
+ (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+ a.__getstate__())
+#
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, freq=None, observed=None,
+ start_date=None, end_date=None, length=None, include_last=True,
+ mask=nomask,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ """Creates a TimeSeries object
+
+:Parameters:
+ `dates` : ndarray
+ Array of dates.
+ `data` :
+ Array of data.
+ """
+ if dates is None:
+ length = _getdatalength(data)
+ dates = date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+ elif not isinstance(dates, DateArray):
+ dates = date_array(dlist=dates, freq=freq)
+ return TimeSeries(data=data, dates=dates, mask=mask, observed=observed,
+ copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask,)
+
+
+def isTimeSeries(series):
+ "Returns whether the series is a valid TimeSeries object."
+ return isinstance(series, TimeSeries)
+
+##### --------------------------------------------------------------------------
+#---- ... Additional functions ...
+##### --------------------------------------------------------------------------
+def mask_period(data, start_date=None, end_date=None,
+ inside=True, include_edges=True, inplace=True):
+ """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked).
+
+:Parameters:
+ `data` : Timeseries
+ Data to process
+ `start_date` : Date *[None]*
+ Starting date. If None, uses the first date.
+ `end_date` : Date *[None]*
+ Ending date. If None, uses the last date.
+ `inside` : Boolean *[True]*
+ Whether the dates inside the range should be masked. If not, masks outside.
+ `include_edges` : Boolean *[True]*
+ Whether the starting and ending dates should be masked.
+ `inplace` : Boolean *[True]*
+ Whether the data mask should be modified in place. If not, returns a new
+ TimeSeries.
+"""
+ if not isTimeSeries(data):
+ raise ValueError,"Data should be a valid TimeSeries!"
+ # Check the starting date ..............
+ if start_date is None:
+ start_date = data._dates[0]
+ elif isinstance(start_date, str):
+ start_date = Date(data.freq, string=start_date)
+ elif not isDateType(start_date):
+ raise DateError,"Starting date should be a valid date!"
+ start_date = max(start_date, data.dates[0])
+ # Check the ending date ................
+ if end_date is None:
+ end_date = data._dates[-1]
+ elif isinstance(end_date, str):
+ end_date = Date(data.freq, string=end_date)
+ elif not isDateType(end_date):
+ raise DateError,"Starting date should be a valid date!"
+ end_date = min(end_date, data.dates[-1])
+ # Constructs the selection mask .........
+ if inside:
+ if include_edges:
+ selection = (data.dates >= start_date) & (data.dates <= end_date)
+ else:
+ selection = (data.dates > start_date) & (data.dates < end_date)
+ else:
+ if include_edges:
+ selection = (data.dates <= start_date) | (data.dates >= end_date)
+ else:
+ selection = (data.dates < start_date) | (data.dates > end_date)
+ # Process the data:
+ if inplace:
+ if data._mask is nomask:
+ data._mask = selection
+ else:
+ data._mask += selection
+ else:
+ return TimeSeries(data, mask=selection, keep_mask=True)
+ return data
+
+def mask_inside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling inside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=True, include_edges=include_edges, inplace=inplace)
+def mask_outside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling outside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=False, include_edges=include_edges, inplace=inplace)
+#..........................................................
+def adjust_endpoints(a, start_date=None, end_date=None):
+ """Returns a TimeSeries going from `start_date` to `end_date`.
+ If `start_date` and `end_date` both fall into the initial range of dates,
+ the new series is NOT a copy.
+ """
+ # Series validity tests .....................
+ if not isinstance(a, TimeSeries):
+ raise TypeError,"Argument should be a valid TimeSeries object!"
+ if a.freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ if not a.dates.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+ # Flatten the series if needed ..............
+ a = a.flatten()
+ shp_flat = a.shape
+ # Dates validity checks .,...................
+ msg = "%s should be a valid Date instance! (got %s instead)"
+ (dstart, dend) = a.dates[[0,-1]]
+ if start_date is None:
+ start_date = dstart
+ start_lag = 0
+ else:
+ if not isDateType(start_date):
+ raise TypeError, msg % ('start_date', type(start_date))
+ start_lag = start_date - dstart
+ #....
+ if end_date is None:
+ end_date = dend
+ end_lag = 0
+ else:
+ if not isDateType(end_date):
+ raise TypeError, msg % ('end_date', type(end_date))
+ end_lag = end_date - dend
+ # Check if the new range is included in the old one
+ if start_lag >= 0:
+ if end_lag == 0:
+ return a[start_lag:]
+ elif end_lag < 0:
+ return a[start_lag:end_lag]
+ # Create a new series .......................
+ newdates = date_array(start_date=start_date, end_date=end_date)
+ newshape = list(shp_flat)
+ newshape[0] = len(newdates)
+ newshape = tuple(newshape)
+
+ newdata = masked_array(numeric.empty(newshape, dtype=a.dtype), mask=True)
+ newseries = TimeSeries(newdata, newdates)
+ start_date = max(start_date, dstart)
+ end_date = min(end_date, dend) + 1
+ newseries[start_date:end_date] = a[start_date:end_date]
+ return newseries
+#....................................................................
+def align_series(*series, **kwargs):
+ """Aligns several TimeSeries, so that their starting and ending dates match.
+ Series are resized and filled with mased values accordingly.
+
+ The function accepts two extras parameters:
+ - `start_date` forces the series to start at that given date,
+ - `end_date` forces the series to end at that given date.
+ By default, `start_date` and `end_date` are set to the smallest and largest
+ dates respectively.
+ """
+ if len(series) < 2:
+ return series
+ unique_freqs = numpy.unique([x.freq for x in series])
+ try:
+ common_freq = unique_freqs.item()
+ except ValueError:
+ raise TimeSeriesError, \
+ "All series must have same frequency!"
+ if common_freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ valid_states = [x.isvalid() for x in series]
+ if not numpy.all(valid_states):
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ start_date = kwargs.pop('start_date', min([x.start_date() for x in series]))
+ if isinstance(start_date,str):
+ start_date = Date(common_freq, string=start_date)
+ end_date = kwargs.pop('end_date', max([x.end_date() for x in series]))
+ if isinstance(end_date,str):
+ end_date = Date(common_freq, string=end_date)
+
+ return [adjust_endpoints(x, start_date, end_date) for x in series]
+aligned = align_series
+#....................................................................
+def convert(series, freq, func='auto', position='END', interp=None):
+ """Converts a series to a frequency
+
+ When converting to a lower frequency, func is a function that acts
+ on a 1-d array and returns a scalar or 1-d array. func should handle
+ masked values appropriately. If func is "auto", then an
+ appropriate function is determined based on the observed attribute
+ of the series. If func is None, then a 2D array is returned, where each
+ column represents the values appropriately grouped into the new frequency.
+ interp and position will be ignored in this case.
+
+ When converting to a higher frequency, position is 'START' or 'END'
+ and determines where the data point is in each period (eg. if going
+ from monthly to daily, and position is 'END', then each data point is
+ placed at the end of the month). Interp is the method that will be used
+ to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
+ and None.
+
+ Note: interp currently not implemented
+ """
+ if not isinstance(series,TimeSeries):
+ raise TypeError, "The argument should be a valid TimeSeries!"
+ if not series.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ if position.upper() not in ('END','START'):
+ raise ValueError("invalid value for position argument: (%s)",str(position))
+
+ toFreq = corelib.fmtFreq(freq)
+ fromFreq = series.freq
+ start_date = series._dates[0]
+
+ if fromFreq == toFreq:
+ return series.copy()
+
+ if series.size == 0:
+ return TimeSeries(series, freq=toFreq,
+ start_date=start_date.asfreq(toFreq))
+ if func == 'auto':
+ func = corelib.obs_dict[series.observed]
+
+ tempData = series._series.filled()
+ tempMask = getmaskarray(series)
+
+ cRetVal = cseries.reindex(tempData, fromFreq, toFreq, position,
+ int(start_date), tempMask)
+ _values = cRetVal['values']
+ _mask = cRetVal['mask']
+ _startindex = cRetVal['startindex']
+ start_date = Date(freq=toFreq, value=_startindex)
+
+ tempData = masked_array(_values, mask=_mask)
+
+ if tempData.ndim == 2 and func is not None:
+ tempData = MA.apply_along_axis(func, -1, tempData)
+
+# newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+
+ newseries = TimeSeries(tempData, freq=toFreq,
+ observed=series.observed,
+ start_date=start_date)
+ return newseries
+# return adjust_endpoints(newseries, end_date=newEnd)
+TimeSeries.convert = convert
+#....................................................................
+def fill_missing_dates(data, dates=None, freq=None,fill_value=None):
+ """Finds and fills the missing dates in a time series.
+The data corresponding to the initially missing dates are masked, or filled to
+`fill_value`.
+
+:Parameters:
+ `data`
+ Initial array of data.
+ `dates`
+ Initial array of dates.
+ `freq` : float *[None]*
+ New date resolutions. If *None*, the initial resolution is used instead.
+ `fill_value` : float *[None]*
+ Default value for missing data. If None, the data are just masked.
+ """
+ freq = corelib.fmtFreq(freq)
+ if freq == 'U':
+ raise ValueError,\
+ "Unable to define a proper date resolution (found %s)." % freq
+ if dates is None:
+ if not isTimeSeries(data):
+ raise InsufficientDateError
+ dates = data._dates
+ freq = dates.freq
+ datad = data._series._data
+ datam = data._series._mask
+# if fill_value is None:
+# fill_value = data._fill_value
+ elif not isinstance(dates, DateArray):
+ dates = DateArray(dates, freq)
+ if isinstance(data, MaskedArray):
+ datad = data._data
+ datam = data._mask
+ else:
+ datad = data
+ datam = nomask
+ dflat = dates.asfreq(freq).ravel()
+ n = len(dflat)
+ if not dflat.has_missing_dates():
+ return time_series(data, dflat)
+
+ # ...and now, fill it ! ......
+ (tstart, tend) = dflat[[0,-1]]
+ newdates = date_array(start_date=tstart, end_date=tend, include_last=True)
+ nsize = newdates.size
+ #.............................
+ # Get the steps between consecutive data.
+ delta = dflat.get_steps()-1
+ gap = delta.nonzero()
+ slcid = numpy.r_[[0,], numpy.arange(1,n)[gap], [n,]]
+ oldslc = numpy.array([slice(i,e) for (i,e) in numpy.broadcast(slcid[:-1],slcid[1:])])
+ addidx = delta[gap].astype(int_).cumsum()
+ newslc = numpy.r_[[oldslc[0]],
+ [slice(i+d,e+d) for (i,e,d) in \
+ numpy.broadcast(slcid[1:-1],slcid[2:],addidx)]
+ ]
+ #.............................
+ # Just a quick check
+ vdflat = numeric.asarray(dflat)
+ vnewdates = numeric.asarray(newdates)
+ for (osl,nsl) in zip(oldslc,newslc):
+ assert numpy.equal(vdflat[osl],vnewdates[nsl]).all(),\
+ "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+ #.............................
+ data = MA.asarray(data)
+ newdatad = numeric.empty(nsize, data.dtype)
+ newdatam = numeric.ones(nsize, bool_)
+# if fill_value is None:
+# if hasattr(data,'fill_value'):
+# fill_value = data.fill_value
+# else:
+# fill_value = MA.default_fill_value(data)
+ #data = data.filled(fill_value)
+ #....
+ if datam is nomask:
+ for (new,old) in zip(newslc,oldslc):
+ newdatad[new] = datad[old]
+ newdatam[new] = False
+ else:
+ for (new,old) in zip(newslc,oldslc):
+ newdatad[new] = datad[old]
+ newdatam[new] = datam[old]
+ newdata = MA.masked_array(newdatad, mask=newdatam, fill_value=fill_value)
+ # Get new shape ..............
+ if data.ndim == 1:
+ nshp = (newdates.size,)
+ else:
+ nshp = tuple([-1,] + list(data.shape[1:]))
+ return time_series(newdata.reshape(nshp), newdates)
+
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ from maskedarray.testutils import assert_equal
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ data = masked_array(numeric.arange(15, dtype=float_), mask=[1,0,0,0,0]*3)
+# btseries = BaseTimeSeries(data._data, dates)
+ tseries = time_series(data, dlist)
+ dseries = numpy.log(tseries)
+ if 1:
+ mlist = ['2005-%02i' % i for i in range(1,13)]
+ mlist += ['2006-%02i' % i for i in range(1,13)]
+ mdata = numpy.arange(24)
+ mser1 = time_series(mdata, mlist, observed='SUMMED')
+ #
+ mlist2 = ['2004-%02i' % i for i in range(1,13)]
+ mlist2 += ['2005-%02i' % i for i in range(1,13)]
+ mser2 = time_series(mdata, mlist2, observed='SUMMED')
+ #
+ today = thisday('m')
+ (malg1,malg2) = aligned(mser1, mser2)
+
+ C = convert(mser2,'A')
+ D = convert(mser2,'A',func=None)
+
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ print "."*50+"\ndata"
+ data = masked_array(numeric.arange(15)-6, mask=[1,0,0,0,0]*3)
+ print "."*50+"\nseries"
+ tseries = time_series(data, dlist)
+
+ if 1:
+ dlist_1 = ['2007-01-%02i' % i for i in range(1,8)]
+ dlist_2 = ['2007-01-%02i' % i for i in numpy.arange(1,28)[::4]]
+ data = masked_array(numeric.arange(7), mask=[1,0,0,0,0,0,0])
+ tseries_1 = time_series(data, dlist_1)
+ tseries_2 = time_series(data, dlist_2)
+ tseries_3 = time_series(data[::-1], dlist_2)
+
+ try:
+ tseries = tseries_1 + tseries_2
+ except TimeSeriesCompatibilityError:
+ print "I knew it!"
+ tseries = tseries_2 + tseries_3
+ assert_equal(tseries._dates, tseries_3._dates)
+ assert_equal(tseries._mask, [1,0,0,0,0,0,1])
+
+ if 1:
+ mser3 = time_series(MA.mr_[malg1._series, 100+malg2._series].reshape(2,-1).T,
+ dates=malg1.dates)
+ data = mser3._series._data
+
Deleted: trunk/Lib/sandbox/timeseries/corelib.py
===================================================================
--- trunk/Lib/sandbox/timeseries/corelib.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/corelib.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,181 +0,0 @@
-import numpy
-from numpy import ma
-
-
-#############################################################
-############## generally applicable functions ###############
-#############################################################
-def apply_along_axis(func1d, axis, arr, *args):
- """ Execute func1d(arr[i],*args) where func1d takes 1-D arrays
- and arr is an N-d array. i varies so as to apply the function
- along the given axis for each 1-d subarray in arr.
-
- Slightly modified version of the standard numpy version to work with masked arrays.
- """
-
- nd = arr.ndim
- if axis < 0:
- axis += nd
- if (axis >= nd):
- raise ValueError("axis must be less than arr.ndim; axis=%d, rank=%d."
- % (axis,nd))
- ind = [0]*(nd-1)
- i = numpy.zeros(nd,'O')
- indlist = range(nd)
- indlist.remove(axis)
- i[axis] = slice(None,None)
- outshape = numpy.asarray(arr.shape).take(indlist)
- i.put(indlist, ind)
- res = func1d(arr[tuple(i.tolist())],*args)
- # if res is a number, then we have a smaller output array
- if not hasattr(res,'shape') or len(res.shape) == 0:
- outarr = ma.zeros(outshape,ma.asarray(res).dtype)
- outarr[ind] = res
- Ntot = numpy.product(outshape)
- k = 1
- while k < Ntot:
- # increment the index
- ind[-1] += 1
- n = -1
- while (ind[n] >= outshape[n]) and (n > (1-nd)):
- ind[n-1] += 1
- ind[n] = 0
- n -= 1
- i.put(indlist,ind)
- res = func1d(arr[tuple(i.tolist())],*args)
- outarr[ind] = res
- k += 1
- return outarr
- else:
- Ntot = numpy.product(outshape)
- holdshape = outshape
- outshape = list(arr.shape)
- outshape[axis] = len(res)
- outarr = ma.zeros(outshape,ma.asarray(res).dtype)
- outarr[tuple(i.tolist())] = res
- k = 1
- while k < Ntot:
- # increment the index
- ind[-1] += 1
- n = -1
- while (ind[n] >= holdshape[n]) and (n > (1-nd)):
- ind[n-1] += 1
- ind[n] = 0
- n -= 1
- i.put(indlist, ind)
- res = func1d(arr[tuple(i.tolist())],*args)
- outarr[tuple(i.tolist())] = res
- k += 1
- return outarr
-
-
-def first_unmasked(m):
- return __unmasked(m, False, 0)
-
-def last_unmasked(m):
- return __unmasked(m, False, -1)
-
-def first_unmasked_val(m):
- return __unmasked(m, True, 0)
-
-def last_unmasked_val(m):
- return __unmasked(m, True, -1)
-
-
-def __unmasked(m, get_val, relpos):
-
- if m.mask is ma.nomask:
- return 0
- else:
- idx = numpy.where(m.mask == False)
- if len(idx) != 0 and len(idx[0]) != 0:
- idx = idx[0][relpos]
- else:
- idx = None
-
- if get_val:
- if idx is None: return ma.masked
- else: return m[idx]
- else:
- return idx
-#############################################################
-
-#converts possible strings for frequency into acceptable values
-def fmtFreq (freqStr):
- if freqStr is None:
- return None
- elif freqStr.upper() in ("A","ANNUAL","B","BUSINESS","D","DAILY","M","MONTHLY","Q","QUARTERLY","S","SECONDLY"):
- return freqStr[0].upper()
- else:
- raise ValueError("Invalid frequency: "+str(freqStr))
-
-
-obsDict = {
- "UNDEFINED":None,
- "BEGINNING":first_unmasked_val,
- "END":last_unmasked_val,
- "AVERAGED":ma.average,
- "SUMMED":ma.sum,
- "MAXIMUM":ma.maximum,
- "MINIMUM":ma.minimum
- }
-
-#converts possible strings for observed into acceptable values
-def fmtObserv(obStr):
-
- obsVals = list(obsDict)
-
- if obStr is None:
- return None
- elif obStr.upper() in obsVals:
- return obStr.upper()
- elif obStr.upper() in ("UNDEFINED", "BEGIN", "END", "AVERAGE", "SUM", "MAX", "MIN"):
- obStr = obStr.upper()
- for x in obsVals:
- if obStr[:2] == x[:2]:
- return x
- else:
- raise ValueError("Invalid value for observed attribute: "+str(obStr))
-
-def freqToType(freq):
- return freqTypeMapping[fmtFreq(freq)]
-
-
-# fake data type for date variables
-class DateSpec:
- 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(" + 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.dateS = DateSpec("Secondly")
-numpy.dateD = DateSpec("Daily")
-numpy.dateB = DateSpec("Business")
-numpy.dateM = DateSpec("Monthly")
-numpy.dateQ = DateSpec("Quarterly")
-numpy.dateA = DateSpec("Annual")
-
-freqTypeMapping = {
- 'S':numpy.dateS,
- 'D':numpy.dateD,
- 'B':numpy.dateB,
- 'M':numpy.dateM,
- 'Q':numpy.dateQ,
- 'A':numpy.dateA
-}
-
-def isDateType(dtype):
- if len([x for x in (numpy.dateS,numpy.dateD,numpy.dateB,numpy.dateM,numpy.dateQ,numpy.dateA) if x == dtype]) > 0: return True
- else: return False
-
Property changes on: trunk/Lib/sandbox/timeseries/examples
___________________________________________________________________
Name: svn:ignore
+ example_alt.py
Modified: trunk/Lib/sandbox/timeseries/examples/example.py
===================================================================
--- trunk/Lib/sandbox/timeseries/examples/example.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/examples/example.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,147 +1,290 @@
-import numpy as np
-import timeseries as ts
-from numpy import ma
-
-
-# create a time series at business frequency and fill it with random data
-bSer = ts.TimeSeries(np.random.uniform(-100,100,600),dtype=np.float64,freq='B',observed='SUMMED',start_date=ts.thisday('B')-600)
-
-
-# Set negative values to zero.
-bSer[bSer < 0] = 0
-
-
-# Set values occurring on Fridays to 100.
-weekdays = ts.day_of_week(ts.tser(bSer.start_date(),bSer.end_date()))
-bSer[weekdays == 4] = 100
-
-
-"""
-Convert bSer to a monthly frequency series.
-
-The optional func argument to the convert method specifies is a
-function that acts on a 1-dimension masked array and returns a single
-value.
-"""
-mSer1 = bSer.convert('M',func=ts.average)
-
-
-"""
-If func is None, a "2 dimensional" time series will be returned. In this
-example, the value for each month will be a length 23 masked array (23
-being the max number of business days in a month)
-"""
-mSer1_2d = bSer.convert('M',func=None)
-
-
-"""
-If func is not specified, the observed attribute of the series
-will be used to determine the method. (SUMMED for this example)
-"""
-mSer1_default = bSer.convert('M')
-
-
-"""
-Convert mSer to a business frequency series.
-
-when converting from a lower frequency to a higher frequency, position is one
-of '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.
-"""
-mToB = bSer.convert('M',position='START')
-
-
-# create another monthly frequency series
-mSer2 = ts.TimeSeries(np.random.uniform(-100,100,100),dtype=np.float64,freq='m',observed='END',start_date=ts.thisday('M')-110)
-
-
-"""
-Slicing also supported. The intention is to have indexing behave
-largely in the same manner as regular numpy arrays.
-
-series.adjust_date convert a date object into the corresponding
-integer for indexing the series
-"""
-sixtyMonthsAgoIdx = mSer2.date_to_index(ts.thisday('m')-60)
-mSer2[sixtyMonthsAgoIdx:sixtyMonthsAgoIdx+10] = 12
-
-
-# Mask the last value in the series
-mSer2[-1] = ts.masked #ts.masked is the same thing as numpy.ma.masked
-
-
-# dates can be used as indices as well
-mSer2[ts.thisday('m')-55] = 400
-
-
-"""
-the tser function makes it easy to index a series over a range of dates
-without worrying about converting the dates to appropriate integers first
-"""
-mSer2[ts.tser(ts.thisday('m')-59, ts.thisday('m')-45)] = 25
-
-
-"""
-Only series of the same frequency and size and same start date
-can be used in the basic operations.
-
-The results are the same as you would expect for masked arrays with the
-basic operations.
-
-start_date and end_date are optional parameters to the aligned function.
-If omitted, the min start_date() and end_date() of all series is used as
-the new boundaries for each series.
-"""
-mSer1, mSer2 = ts.aligned(mSer1, mSer2, start_date=ts.thisday('m')-100, end_date=ts.thisday('m'))
-mAdd1 = mSer1 + mSer2
-
-
-# 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=ts.Date(freq='M', year=1954, month=5), end_date=ts.Date(freq='M', year=2000, month=6))
-
-# calculate the average value in the series. Behaves the same as in ma
-bAverage = ts.average(bSer)
-
-
-# Take the sqrt root of each element in the series (returns a TimeSeries object).
-# Not all functions from ma supported yet, but they are easy to implement
-# for the most part.
-bSqrt = ts.sqrt(bSer)
-
-
-# get the last day of this year, at daily frequency
-dLastDayOfYear = ts.dateOf(ts.thisday('A'),'D','AFTER')
-
-
-# get the first day of this year, at business frequency
-bFirstDayOfYear = ts.dateOf(ts.thisday('A'),'B','BEFORE')
-
-
-# get the last day of the previous quarter, business frequency
-bLastDayOfLastQuarter = ts.dateOf(ts.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 = (ts.thisday('Q') == ts.dateOf(ts.thisday('b'),'Q'))
-
-
-# dates of the same frequency can be subtracted (but not added obviously)
-numberOfBusinessDaysPassedThisYear = ts.thisday('b') - bFirstDayOfYear
-
-
-# integers can be added/substracted to/from dates
-fiveDaysFromNow = ts.thisday('d') + 5
-
-
-# get the previous business day, where business day is considered to
-# end at day_end_hour and day_end_min
-pbd = ts.prevbusday(day_end_hour=18,day_end_min=0)
-
-
-# construct a date object explicitly
-myDateQ = ts.Date(freq='Q',year=2004,quarter=3)
-myDateD = ts.Date(freq='D',year=1985,month=10,day=4)
+
+"""
+=== 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
Property changes on: trunk/Lib/sandbox/timeseries/src
___________________________________________________________________
Name: svn:ignore
+ buildit.sh
Added: trunk/Lib/sandbox/timeseries/tcore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tcore.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tcore.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,157 @@
+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 ---
+#####---------------------------------------------------------------------------
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten_sequence(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_sequence(elm):
+ yield f
+ else:
+ yield elm
+
+def flatargs(*args):
+ "Flattens the arguments."
+ if not hasattr(args, '__iter__'):
+ return args
+ else:
+ return flatten_sequence(args)
+
+
Property changes on: trunk/Lib/sandbox/timeseries/tcore.py
___________________________________________________________________
Name: svn:keywords
+ Date
Author
Revision
Id
Added: trunk/Lib/sandbox/timeseries/tdates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tdates.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tdates.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1089 @@
+"""
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__ = '$Date$'
+
+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 mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+import tcore as corelib
+import cseries
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#####---------------------------------------------------------------------------
+#---- --- Date Info ---
+#####---------------------------------------------------------------------------
+OriginDate = mxD.Date(1970)
+secondlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(minutes=1)
+hourlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(hours=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':
+ valtmp = (value - 1)//5
+ self.mxDate = mxD.DateTimeFromAbsDateTime(value + valtmp*7 - valtmp*5)
+ elif self.freq in ['D','U']:
+ self.mxDate = mxD.DateTimeFromAbsDateTime(value)
+ elif self.freq == 'H':
+ self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+ elif self.freq == 'M':
+ self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+ mxD.RelativeDateTime(months=value-1, day=-1)
+ elif self.freq == 'Q':
+ self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+ 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(1,1,7) + \
+ mxD.RelativeDateTime(weeks=value-1)
+# 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 = truncateDate(self.freq, 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 = truncateDate(self.freq, 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 = truncateDate(self.freq,
+ 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 = (weeks*5) + (days - weeks*7)
+ val = days - weeks*2
+ # Daily/undefined
+ elif self.freq in ['D', 'U']:
+ val = self.mxDate.absdate
+ # Hourly........
+ elif self.freq == 'H':
+ val = (self.mxDate - hourlyOriginDate).hours
+ # Monthly.......
+ elif self.freq == 'M':
+ val = (self.mxDate.year-1)*12 + self.mxDate.month
+ # Quarterly.....
+ elif self.freq == 'Q':
+ val = (self.mxDate.year-1)*4 + self.mxDate.month//3
+ # Secondly......
+ elif self.freq == 'S':
+ val = (self.mxDate - secondlyOriginDate).seconds
+ # Minutely......
+ elif self.freq == 'T':
+ val = (self.mxDate - minutelyOriginDate).minutes
+ # Weekly........
+ elif self.freq == 'W':
+ #val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+ val = self.mxDate.absdate//7
+ return int(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 == 'H':
+ fmt = "%d-%b-%Y %H:00"
+ elif self.freq == 'T':
+ fmt = "%d-%b-%Y %H:%M"
+ elif self.freq == "S":
+ 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 asfreq(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 truncateDate(freq, mxDate):
+ """Chops off the irrelevant information from the mxDate passed in."""
+ freq = corelib.fmtFreq(freq)
+ if freq == 'A':
+ return mxD.Date(mxDate.year)
+ elif freq == 'Q':
+ return mxD.Date(mxDate.year, monthToQuarter(mxDate.month)*3)
+ elif freq == 'M':
+ return mxD.Date(mxDate.year, mxDate.month)
+ elif freq == 'W':
+ d = mxDate.absdate
+ return mxD.DateTimeFromAbsDateTime(d + (7 - d % 7) % 7 - 1)
+ elif freq in ('B', 'D'):
+ if freq == 'B' and mxDate.day_of_week in [5,6]:
+ raise ValueError("Weekend passed as business day")
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day)
+ elif freq == 'H':
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+ mxDate.hour)
+ elif freq == 'T':
+ return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+ mxDate.hour, mxDate.minute)
+ else:
+ return mxDate
+
+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 asfreq(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):
+ if not isinstance(date, Date):
+ raise DateError, "Date should be a valid Date instance!"
+
+ if date.freq == toFreq:
+ return date
+ else:
+ value = cseries.asfreq(numeric.asarray(date.value), date.freq, toFreq, _rel)
+ if value > 0:
+ return Date(freq=toFreq, value=value)
+ else:
+ return None
+# # 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 isinstance(index, Date):
+ index = self.find_dates(index)
+ elif numeric.asarray(index).dtype.kind == 'O':
+ try:
+ index = self.find_dates(index)
+ except AttributeError:
+ pass
+ r = ndarray.__getitem__(self, index)
+ if r.size == 1:
+ # Only one element, and it's not a scalar: we have a DateArray of size 1
+ if len(r.shape) > 0:
+ r = r.item()
+ return Date(self.freq, value=r)
+ else:
+ return r
+
+ def __repr__(self):
+ return ndarray.__repr__(self)
+ #......................................................
+ @property
+ def years(self):
+ "Returns the years."
+ return numeric.asarray([d.year() for d in self], dtype=int_)
+ @property
+ def months(self):
+ "Returns the months."
+ return numeric.asarray([d.month() for d in self], dtype=int_)
+ @property
+ def day_of_year(self):
+ "Returns the days of years."
+ return numeric.asarray([d.day_of_year() for d in self], dtype=int_)
+ yeardays = day_of_year
+ @property
+ def day_of_week(self):
+ "Returns the days of week."
+ return numeric.asarray([d.day_of_week() for d in self], dtype=int_)
+ #.... Conversion methods ....................
+# def toobject(self):
+# "Converts the dates from ordinals to Date objects."
+# # Note: we better try to cache the result
+# if self.__toobj is None:
+## toobj = numeric.empty(self.size, dtype=object_)
+## toobj[:] = [Date(self.freq, value=d) for d in self]
+## self.__toobj = toobj
+# self.__toobj = self
+# return self.__toobj
+ #
+ def tovalue(self):
+ "Converts the dates to integer values."
+ return numeric.asarray(self)
+ #
+ def toordinal(self):
+ "Converts the dates from values to ordinals."
+ # Note: we better try to cache the result
+ if self.__toord is None:
+# diter = (Date(self.freq, value=d).toordinal() for d in self)
+ diter = (d.toordinal() for d in self)
+ toord = numeric.fromiter(diter, dtype=float_)
+ self.__toord = toord
+ return self.__toord
+ #
+ def tostring(self):
+ "Converts the dates to strings."
+ # Note: we better cache the result
+ if self.__tostr is None:
+ firststr = str(self[0])
+ if self.size > 0:
+ ncharsize = len(firststr)
+ tostr = numpy.fromiter((str(d) for d in self),
+ dtype='|S%i' % ncharsize)
+ else:
+ tostr = firststr
+ self.__tostr = tostr
+ return self.__tostr
+ #
+# def asfreq_ini(self, freq=None):
+# "Converts the dates to another frequency."
+# # Note: As we define a new object, we don't need caching
+# if freq is None:
+# return self
+# freq = corelib.fmtFreq(freq)
+# if freq == self.freq:
+# return self
+# if self.isvalid():
+# new = numeric.arange(self.size, dtype=int_)
+# new += self[0].asfreq(freq).value
+# else:
+# new = numpy.fromiter((d.asfreq(freq).value for d in self),
+# dtype=float_)
+# return DateArray(new, freq=freq)
+
+ def asfreq(self, freq=None, relation="BEFORE"):
+ "Converts the dates to another frequency."
+ # Note: As we define a new object, we don't need caching
+ if freq is None:
+ return self
+ freq = corelib.fmtFreq(freq)
+ if freq == self.freq:
+ return self
+ _rel = relation.upper()[0]
+ new = cseries.asfreq(numeric.asarray(self), self.freq, freq, _rel)
+ return DateArray(new, freq=freq)
+ #......................................................
+ def find_dates(self, *dates):
+ "Returns the indices corresponding to given dates, as an array."
+ ifreq = self.freq
+ c = numpy.zeros(self.shape, bool_)
+ for d in corelib.flatargs(*dates):
+ if d.freq != ifreq:
+ d = d.asfreq(ifreq)
+ c += (self == d.value)
+ c = c.nonzero()
+ if fromnumeric.size(c) == 0:
+ raise ValueError, "Date out of bounds!"
+ return c
+# def find_dates_alt(self, *dates):
+# "Returns the indices corresponding to given dates, as an array."
+# ifreq = self.freq
+# c = numpy.zeros(self.shape, bool_)
+# dates = date_array([d for d in corelib.flatargs(*dates)]).asfreq(ifreq)
+# for d in numeric.asarray(dates):
+# c += (self == d)
+# c = c.nonzero()
+# if fromnumeric.size(c) == 0:
+# raise ValueError, "Date out of bounds!"
+# return c
+ def date_to_index(self, date):
+ "Returns the index corresponding to one given date, as an integer."
+ if self.isvalid():
+ index = date.value - self[0].value
+ if index < 0 or index > self.size:
+ raise ValueError, "Date out of bounds!"
+ return index
+ else:
+ index_asarray = (self == date.value).nonzero()
+ if fromnumeric.size(index_asarray) == 0:
+ raise ValueError, "Date out of bounds!"
+ return index_asarray[0][0]
+ #......................................................
+ def get_steps(self):
+ """Returns the time steps between consecutive dates.
+ The timesteps have the same unit as the frequency of the series."""
+ if self.freq == 'U':
+ warnings.warn("Undefined frequency: assuming daily!")
+ if self.__steps is None:
+ val = numeric.asarray(self).ravel()
+ if val.size > 0:
+ steps = val[1:] - val[:-1]
+ if self.__full is None:
+ self.__full = (steps.max() == 1)
+ if self.__hasdups is None:
+ self.__hasdups = (steps.min() == 0)
+ else:
+ self.__full = True
+ self.__hasdups = False
+ self.__steps = steps
+ return self.__steps
+
+ def has_missing_dates(self):
+ "Returns whether the DateArray have missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return not(self.__full)
+
+ def isfull(self):
+ "Returns whether the DateArray has no missing dates."
+ if self.__full is None:
+ steps = self.get_steps()
+ return self.__full
+
+ def has_duplicated_dates(self):
+ "Returns whether the DateArray has duplicated dates."
+ if self.__hasdups is None:
+ steps = self.get_steps()
+ return self.__hasdups
+
+ def isvalid(self):
+ "Returns whether the DateArray is valid: no missing/duplicated dates."
+ return (self.isfull() and not self.has_duplicated_dates())
+ #......................................................
+class _datearithmetics(object):
+ """Defines a wrapper for arithmetic methods.
+Instead of directly calling a ufunc, the corresponding method of the `array._data`
+object is called instead.
+If `asdates` is True, a DateArray object is returned , else a regular ndarray
+is returned.
+ """
+ def __init__ (self, methodname, asdates=True):
+ """
+:Parameters:
+ - `methodname` (String) : Method name.
+ """
+ self.methodname = methodname
+ self._asdates = asdates
+ self.__doc__ = getattr(methodname, '__doc__')
+ self.obj = None
+ #dalog.info('__datearithmetics got method %s' % methodname)
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args, **kwargs):
+ "Execute the call behavior."
+ instance = self.obj
+ freq = instance.freq
+ if 'context' not in kwargs:
+ kwargs['context'] = 'DateOK'
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ method = getattr(super(DateArray,instance), self.methodname)
+ if isinstance(other, DateArray):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+# other =
+ elif isinstance(other, Date):
+ if other.freq != freq:
+ raise FrequencyDateError("Cannot operate on dates", \
+ freq, other.freq)
+ other = other.value
+ #dalog.info('__datearithmetics got other %s' % type(other))
+ elif isinstance(other, ndarray):
+ if other.dtype.kind not in ['i','f']:
+ raise ArithmeticDateError
+ if self._asdates:
+ return instance.__class__(method(other, *args),
+ freq=freq)
+ else:
+ return method(other, *args)
+#............................
+DateArray.__add__ = _datearithmetics('__add__', asdates=True)
+DateArray.__radd__ = _datearithmetics('__add__', asdates=True)
+DateArray.__sub__ = _datearithmetics('__sub__', asdates=True)
+DateArray.__rsub__ = _datearithmetics('__rsub__', asdates=True)
+DateArray.__le__ = _datearithmetics('__le__', asdates=False)
+DateArray.__lt__ = _datearithmetics('__lt__', asdates=False)
+DateArray.__ge__ = _datearithmetics('__ge__', asdates=False)
+DateArray.__gt__ = _datearithmetics('__gt__', asdates=False)
+DateArray.__eq__ = _datearithmetics('__eq__', asdates=False)
+DateArray.__ne__ = _datearithmetics('__ne__', asdates=False)
+
+#####---------------------------------------------------------------------------
+#---- --- DateArray functions ---
+#####---------------------------------------------------------------------------
+def isDateArray(a):
+ "Tests whether an array is a DateArray object."
+ return isinstance(a,DateArray)
+
+def guess_freq(dates):
+ """Tries to estimate the frequency of a list of dates, by checking the steps
+ between consecutive dates The steps should be in days.
+ Returns a frequency code (alpha character)."""
+ ddif = numeric.asarray(numpy.diff(dates))
+ ddif.sort()
+ if ddif[0] == ddif[-1] == 1.:
+ fcode = 'D'
+ elif (ddif[0] == 1.) and (ddif[-1] == 3.):
+ fcode = 'B'
+ elif (ddif[0] > 3.) and (ddif[-1] == 7.):
+ fcode = 'W'
+ elif (ddif[0] >= 28.) and (ddif[-1] <= 31.):
+ fcode = 'M'
+ elif (ddif[0] >= 90.) and (ddif[-1] <= 92.):
+ fcode = 'Q'
+ elif (ddif[0] >= 365.) and (ddif[-1] <= 366.):
+ fcode = 'A'
+ elif numpy.abs(24.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(24.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'H'
+ elif numpy.abs(1440.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(1440.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'T'
+ elif numpy.abs(86400.*ddif[0] - 1) <= 1e-5 and \
+ numpy.abs(86400.*ddif[-1] - 1) <= 1e-5:
+ fcode = 'S'
+ else:
+ warnings.warn("Unable to estimate the frequency! %.3f<>%.3f" %\
+ (ddif[0], ddif[-1]))
+ fcode = 'U'
+ return fcode
+
+
+def _listparser(dlist, freq=None):
+ "Constructs a DateArray from a list."
+ dlist = numeric.asarray(dlist)
+ dlist.sort()
+ # Case #1: dates as strings .................
+ if dlist.dtype.kind == 'S':
+ #...construct a list of ordinals
+ ords = numpy.fromiter((mxDFromString(s).absdays for s in dlist),
+ float_)
+ ords += 1
+ #...try to guess the frequency
+ if freq is None:
+ freq = guess_freq(ords)
+ #...construct a list of dates
+ dates = [Date(freq, string=s) for s in dlist]
+ # Case #2: dates as numbers .................
+ elif dlist.dtype.kind in ['i','f']:
+ #...hopefully, they are values
+ if freq is None:
+ freq = guess_freq(dlist)
+ dates = dlist
+ # Case #3: dates as objects .................
+ elif dlist.dtype.kind == 'O':
+ template = dlist[0]
+ #...as Date objects
+ if isinstance(template, Date):
+ dates = numpy.fromiter((d.value for d in dlist), float_)
+ #...as mx.DateTime objects
+ elif hasattr(template,'absdays'):
+ # no freq given: try to guess it from absdays
+ if freq is None:
+ ords = numpy.fromiter((s.absdays for s in dlist), float_)
+ ords += 1
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=m) for m in dlist]
+ #...as datetime objects
+ elif hasattr(dlist[0], 'toordinal'):
+ ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+ if freq is None:
+ freq = guess_freq(ords)
+ dates = [Date(freq, mxDate=mxD.DateTimeFromAbsDays(a)) for a in ords]
+ #
+ result = DateArray(dates, freq)
+ return result
+
+
+def date_array(dlist=None, start_date=None, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from:
+ - a starting date and either an ending date or a given length.
+ - a list of dates.
+ """
+ freq = corelib.fmtFreq(freq)
+ # Case #1: we have a list ...................
+ if dlist is not None:
+ # Already a DateArray....................
+ if isinstance(dlist, DateArray):
+ if freq != dlist.freq:
+ return dlist.asfreq(freq)
+ else:
+ return dlist
+ return _listparser(dlist, freq)
+ # Case #2: we have a starting date ..........
+ if start_date is None:
+ raise InsufficientDateError
+# if not isDateType(start_date):
+ if not isinstance(start_date, Date):
+ raise DateError, "Starting date should be a valid Date instance!"
+ # Check if we have an end_date
+ if end_date is None:
+ if length is None:
+ raise ValueError,"No length precised!"
+ else:
+ if not isinstance(end_date, Date):
+ raise DateError, "Ending date should be a valid Date instance!"
+# assert(isDateType(end_date),
+# "Starting date should be a valid Date instance!")
+ length = end_date - start_date
+ if include_last:
+ length += 1
+# dlist = [(start_date+i).value for i in range(length)]
+ dlist = numeric.arange(length, dtype=int_)
+ dlist += start_date.value
+ if freq is None:
+ freq = start_date.freq
+ return DateArray(dlist, freq=freq)
+datearray = date_array
+
+def date_array_fromlist(dlist, freq=None):
+ "Constructs a DateArray from a list of dates."
+ return date_array(dlist=dlist, freq=freq)
+
+def date_array_fromrange(start_date, end_date=None, length=None,
+ include_last=True, freq=None):
+ """Constructs a DateArray from a starting date and either an ending date or
+ a length."""
+ return date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(DateArray, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__(self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+
+
+################################################################################
Property changes on: trunk/Lib/sandbox/timeseries/tdates.py
___________________________________________________________________
Name: svn:keywords
+ Date
Author
Revision
Id
Added: trunk/Lib/sandbox/timeseries/tests/test_dates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tests/test_dates.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tests/test_dates.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,144 @@
+# 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
+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
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tdates
+reload(tdates)
+from tdates import date_array_fromlist, Date, DateArray, mxDFromString
+
+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 = date_array_fromlist(dlist, 'D')
+ assert_equal(dates.freq,'D')
+ assert(dates.isfull())
+ assert(not dates.has_duplicated_dates())
+ assert_equal(dates, 732677+numpy.arange(len(dlist)))
+ # as simple, but we need to guess the frequency this time
+ dates = date_array_fromlist(dlist, 'D')
+ assert_equal(dates.freq,'D')
+ assert(dates.isfull())
+ assert(not dates.has_duplicated_dates())
+ assert_equal(dates, 732677+numpy.arange(len(dlist)))
+ # Still daily data, that we force to month
+ dates = date_array_fromlist(dlist, 'M')
+ assert_equal(dates.freq,'M')
+ assert(not dates.isfull())
+ assert(dates.has_duplicated_dates())
+ assert_equal(dates, [24073]*len(dlist))
+ # Now, for monthly data
+ dlist = ['2007-%02i' % i for i in range(1,13)]
+ dates = date_array_fromlist(dlist, 'M')
+ assert_equal(dates.freq,'M')
+ assert(dates.isfull())
+ assert(not dates.has_duplicated_dates())
+ assert_equal(dates, 24073 + numpy.arange(12))
+ # Monthly data w/ guessing
+ dlist = ['2007-%02i' % i for i in range(1,13)]
+ dates = date_array_fromlist(dlist, )
+ assert_equal(dates.freq,'M')
+ assert(dates.isfull())
+ assert(not dates.has_duplicated_dates())
+ assert_equal(dates, 24073 + 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 = date_array_fromlist(dlist)
+ assert_equal(dates.freq,'U')
+ assert(not dates.isfull())
+ assert(not dates.has_duplicated_dates())
+ assert_equal(dates.tovalue(),732676+numpy.array([1,2,4,5,7,8,10,11,13]))
+ #
+ ddates = date_array_fromlist(dlist, 'D')
+ assert_equal(ddates.freq,'D')
+ assert(not ddates.isfull())
+ assert(not ddates.has_duplicated_dates())
+ #
+ mdates = date_array_fromlist(dlist, 'M')
+ assert_equal(mdates.freq,'M')
+ assert(not dates.isfull())
+ assert(mdates.has_duplicated_dates())
+ #
+
+ 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 = date_array_fromlist(dlist)
+ dobj = [datetime.datetime.fromordinal(d) for d in dates.toordinal()]
+ odates = date_array_fromlist(dobj)
+ assert_equal(dates,odates)
+ dobj = [mxDFromString(d) for d in dlist]
+ odates = date_array_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 = date_array_fromlist(dlist, 'M')
+ # Using an integer
+ assert_equal(mdates[0].value, 24073)
+ assert_equal(mdates[-1].value, 24084)
+ # Using a date
+ lag = mdates.find_dates(mdates[0])
+ assert_equal(mdates[lag], mdates[0])
+ lag = mdates.find_dates(Date('M',value=24080))
+ assert_equal(mdates[lag], mdates[5])
+ # Using several dates
+ lag = mdates.find_dates(Date('M',value=24073), Date('M',value=24084))
+ 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 = date_array_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 = date_array_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
Copied: trunk/Lib/sandbox/timeseries/tests/test_timeseries.py (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py 2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/tests/test_timeseries.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,283 @@
+# 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$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__ = '$Date$'
+
+import types
+
+import numpy as N
+from numpy import bool_, complex_, float_, int_, object_
+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, masked, nomask
+
+import maskedarray.testutils
+#reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+#import tdates
+##reload(tdates)
+#from tdates import date_array_fromlist
+import tseries
+#reload(tseries)
+from tseries import Date, date_array_fromlist
+from tseries import time_series, TimeSeries, adjust_endpoints, mask_period
+
+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"
+ (series, data, dates) = self.d
+ last_date = series[-1]._dates
+ assert_equal(series[-1], series[last_date])
+ assert_equal(series._dates[-1], dates[-1])
+ assert_equal(series[-1]._dates, dates[-1])
+ assert_equal(series[last_date]._dates, dates[-1])
+ assert_equal(series._series[-1], data._data[-1])
+ assert_equal(series[-1]._series, data._data[-1])
+ assert_equal(series._mask[-1], data._mask[-1])
+ #
+ series['2007-01-06'] = 999
+ assert_equal(series[5], 999)
+ #
+ def test_wtimeseries(self):
+ "Tests getitem w/ TimeSeries as index"
+ (series, data, dates) = self.d
+ # Testing a basic condition on data
+ cond = (series<8).filled(False)
+ dseries = series[cond]
+ assert_equal(dseries._data, [1,2,3,4,6,7])
+ assert_equal(dseries._dates, series._dates[[1,2,3,4,6,7]])
+ assert_equal(dseries._mask, nomask)
+ # Testing a basic condition on dates
+ series[series._dates < Date('D',string='2007-01-06')] = masked
+ assert_equal(series[:5]._series._mask, [1,1,1,1,1])
+
+ def test_wslices(self):
+ "Test get/set items."
+ (series, data, dates) = self.d
+ # Basic slices
+ assert_equal(series[3:7]._series._data, data[3:7]._data)
+ assert_equal(series[3:7]._series._mask, data[3:7]._mask)
+ assert_equal(series[3:7]._dates, dates[3:7])
+ # Ditto
+ assert_equal(series[:5]._series._data, data[:5]._data)
+ assert_equal(series[:5]._series._mask, data[:5]._mask)
+ assert_equal(series[:5]._dates, dates[:5])
+ # With set
+ series[:5] = 0
+ assert_equal(series[:5]._series, [0,0,0,0,0])
+ dseries = N.log(series)
+ series[-5:] = dseries[-5:]
+ assert_equal(series[-5:], dseries[-5:])
+ # Now, using dates !
+ dseries = series[series.dates[3]:series.dates[7]]
+ assert_equal(dseries, series[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"
+ (series, data, dates) = self.d
+ dseries = adjust_endpoints(series, series.dates[0], series.dates[-1])
+ assert_equal(dseries, series)
+ dseries = adjust_endpoints(series, series.dates[3], series.dates[-3])
+ assert_equal(dseries, series[3:-2])
+ dseries = adjust_endpoints(series, end_date=Date('D', string='2007-01-31'))
+ assert_equal(dseries.size, 31)
+ assert_equal(dseries._mask, N.r_[series._mask, [1]*16])
+ dseries = adjust_endpoints(series, end_date=Date('D', string='2007-01-06'))
+ assert_equal(dseries.size, 6)
+ assert_equal(dseries, series[:6])
+ dseries = adjust_endpoints(series,
+ 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, N.r_[series._mask[5:], [1]*16])
+ #
+ def test_maskperiod(self):
+ "Test mask_period"
+ (series, data, dates) = self.d
+ series.mask = nomask
+ (start, end) = ('2007-01-06', '2007-01-12')
+ mask = mask_period(series, start, end, inside=True, include_edges=True,
+ inplace=False)
+ assert_equal(mask._mask, N.array([0,0,0,0,0,1,1,1,1,1,1,1,0,0,0]))
+ mask = mask_period(series, 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(series, 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(series, 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])
+ #
+ def pickling(self):
+ "Tests pickling/unpickling"
+ (series, data, dates) = self.d
+ tmp = maskedarray.loads(series.dumps())
+ assert_equal(tmp._data, series._data)
+ assert_equal(tmp._dates, series._dates)
+ assert_equal(tmp._mask, series._mask)
+
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+ NumpyTest().run()
\ No newline at end of file
Property changes on: trunk/Lib/sandbox/timeseries/tests/test_timeseries.py
___________________________________________________________________
Name: svn:keywords
+ Date
Author
Revision
Id
Deleted: trunk/Lib/sandbox/timeseries/timeseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/timeseries.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/timeseries.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,504 +0,0 @@
-import numpy
-from numpy import ma
-
-import corelib
-import cseries
-import tsdate
-import copy as copytools
-
-from types import MethodType
-
-masked = ma.masked
-nomask = ma.nomask
-
-def ts_compatible(a, b):
- if a.freq != b.freq:
- raise ValueError("Both TimeSeries must have same freq!")
- elif a.start_date() != b.start_date():
- raise ValueError("Both TimeSeries must have same start_date!")
- elif a.shape != b.shape:
- raise ValueError("Both TimeSeries must be of the same size!")
-
-
-class ts_unary_operation:
- def __init__ (self, abfunc):
- self.f = abfunc
- self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
-
- def __call__ (self, a, *args, **kwargs):
- "Execute the call behavior."
- if isinstance(a, TimeSeries):
- return TimeSeries(self.f(a, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
- else:
- return self.f(a, *args, **kwargs)
-
-
-class ts_binary_operation:
- def __init__ (self, abfunc):
- self.f = abfunc
- self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
-
- def __call__ (self, a, b, *args, **kwargs):
- "Execute the call behavior."
-
- if isinstance(a, TimeSeries) and isinstance(b, TimeSeries):
- ts_compatible(a, b)
- return TimeSeries(self.f(a, b, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
- elif isinstance(a, TimeSeries):
- if corelib.isDateType(a.tstype):
- return TimeSeries(self.f(a, b, *args, **kwargs), dtype=a.tstype, freq=a.freq, observed=a.observed, start_date=a.start_date())
- else:
- return TimeSeries(self.f(a, b, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
- elif isinstance(b, TimeSeries):
- if corelib.isDateType(b.tstype):
- return TimeSeries(self.f(a, b, *args, **kwargs), dtype=b.tstype, freq=b.freq, observed=b.observed, start_date=b.start_date())
- else:
- return TimeSeries(self.f(a, b, *args, **kwargs), freq=b.freq, observed=b.observed, start_date=b.start_date())
- else:
- return self.f(a, b, *args, **kwargs)
-
- def reduce (self, target, axis=0, dtype=None):
- return self.f.reduce(target, axis, dtype)
-
- def outer (self, a, b):
- return self.f.outer(a, b)
-
- def accumulate (self, target, axis=0):
- return datawrap(self.f.accumulate(target, axis), target)
-
- def __str__ (self):
- return "Masked version of " + str(self.f)
-
-
-class TimeSeries(ma.MaskedArray):
-
- __array_priority__ = 10.2
-
- def __init__(self, data, dtype=None, freq=None, start_date=None, observed=None, copy=True, order=False, mask=ma.nomask, fill_value=None):
-
- if isinstance(data, TimeSeries):
- if freq is None: freq = data.freq
- if start_date is None: start_date = data.start_date()
- if observed is None: observed = data.observed
- else:
- if observed is None: observed = 'END'
-
- self.freq = corelib.fmtFreq(freq)
-
- if isinstance(start_date, tsdate.Date):
- if start_date.freq != self.freq: raise ValueError("frequency of start_date must match frequency of series")
- else: self.__start_date = start_date
- else:
- self.__start_date = tsdate.Date(freq=self.freq, value=start_date)
-
- self.observed = corelib.fmtObserv(observed)
-
- self.tstype = None
-
- if corelib.isDateType(dtype) or (isinstance(data, TimeSeries) and corelib.isDateType(data.tstype)):
- self.tstype = dtype
- dtype = numpy.int_
-
- super(TimeSeries, self).__init__(data=data, dtype=dtype, copy=copy, order=order, mask=mask, fill_value=fill_value)
-
- if self.tstype is None: self.tstype = self.dtype
-
-
- def __getitem__(self, key):
- return super(TimeSeries, self).__getitem__(self.__prepKey(key))
-
- def __setitem__(self, key, value):
- super(TimeSeries, self).__setitem__(self.__prepKey(key), value)
-
- def __prepKey(self, key):
-
- if isinstance(key, tsdate.Date):
- key = int(key - self.start_date())
- if key < 0: raise ValueError("Date out of bounds")
- else: return key
-
- elif isinstance(key, TimeSeries):
- if corelib.isDateType(key.tstype):
- if key.tstype.freq != self.freq:
- raise ValueError("series of frequency "+str(self.freq)+" given date expression of type "+str(key.tstype.freq))
-
- if key.mask is ma.nomask: key = numpy.asarray(key) - int(self.start_date())
- else: key = numpy.asarray(key[key.mask == False]) - int(self.start_date())
-
- if len(numpy.where(key < 0)[0]) > 0: raise ValueError("Indices out of bounds")
-
- return key
-
- else:
-
- # frequency, size, and start_date of key must all match self
- # when the data type is note a date
- ts_compatible(key, self)
-
- if key.tstype is numpy.bool_:
- key = key.filled(False)
- elif numpy.ravel(key.mask).any():
- raise ValueError("masked values cannot be used as indices!")
-
- return numpy.asarray(key)
-
- elif isinstance(key, ma.MaskedArray):
-
- if key.dtype is numpy.bool_:
- key = key.filled(False)
- elif numpy.ravel(key.mask).any():
- raise ValueError("masked values cannot be used as indices!")
-
- return numpy.asarray(key)
-
- else: return key
-
-
- def convert(self, freq, func='auto', position='END', interp=None):
- """
- return self converted to freq.
-
- When converting to a lower frequency, func is a function that acts
- on a 1-d array and returns a scalar or 1-d array. func should handle
- masked values appropriately. If func is "auto", then an
- appropriate function is determined based on the observed attribute
- of the series. If func is None, then a 2D array is returned, where each
- column represents the values appropriately grouped into the new frequency.
- interp and position will be ignored in this case.
-
- When converting to a higher frequency, position is 'START' or 'END'
- and determines where the data point is in each period (eg. if going
- from monthly to daily, and position is 'END', then each data point is
- placed at the end of the month). Interp is the method that will be used
- to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
- and None.
-
- Note: interp currently not implemented
-
- """
-
- if position.upper() not in ('END','START'): raise ValueError("invalid value for position argument: (%s)",str(position))
-
- toFreq = corelib.fmtFreq(freq)
- fromFreq = self.freq
-
- if fromFreq != toFreq:
-
- if func == 'auto':
- func = corelib.obsDict[self.observed]
-
- if self.size == 0:
- return TimeSeries(self, freq=toFreq, start_date=tsdate.dateOf(self.start_date(), toFreq))
-
- tempData = self.filled()
-
- if self.mask is ma.nomask:
- tempMask = numpy.empty(tempData.shape, dtype=numpy.bool_)
- tempMask[:] = False
- else: tempMask = self.mask
-
- cRetVal = cseries.convert(tempData, fromFreq, toFreq, position, int(self.start_date()), tempMask)
-
- _values = cRetVal['values']
- _mask = cRetVal['mask']
- startIndex = cRetVal['startindex']
-
- tempData = ma.array(_values)
- tempMask = ma.make_mask(_mask)
- tempData[tempMask] = ma.masked
-
- if func is not None and tempData.ndim == 2:
- tempData = corelib.apply_along_axis(func, 1, tempData)
-
- return TimeSeries(tempData, freq=toFreq, observed=self.observed, start_date=startIndex)
-
- else:
- return copytools.deepcopy(self)
-
-
- def adjust_endpoints(self, start_date=None, end_date=None):
- self.__init__(adjust_endpoints(self, start_date=start_date, end_date=end_date))
-
-
- def __str__(self):
- retVal = ""
-
- if self.shape[0] > 0:
- for i in range(self.shape[0]):
- index = str(self.start_date() + i)
- index = index + (" " * (6-len(index)))
- retVal += index + " --> " + str(self[i])+"\n"
- return retVal
- else:
- return "<no data>"
-
-
- def first_value(self, asDate=False):
- firstIndex = corelib.first_unmasked(self)
- if asDate:
- return self.start_date() + firstIndex
- else:
- return firstIndex
-
- def last_value(self, asDate=False):
- lastIndex = corelib.last_unmasked(self)
- if asDate:
- return self.start_date() + lastIndex
- else:
- return lastIndex
-
- def start_date(self): return self.__start_date
- def end_date(self): return self.__start_date + (self.shape[0] - 1)
-
- def date_to_index(self, date):
- if date.freq != self.freq: raise ValueError("date.freq != self.freq")
- return date - self.start_date()
-
-
- # built-in methods
-
- def __and__(self, other): return bitwise_and(self, other)
- def __or__(self, other): return bitwise_or(self, other)
- def __xor__(self, other): return bitwise_xor(self, other)
- __rand__ = __and__
- __ror__ = __or__
- __rxor__ = __xor__
- def __abs__(self): return absolute(self)
- def __neg__(self): return negative(self)
- def __pos__(self): return TimeSeries(self)
- def __add__(self, other): return add(self, other)
- __radd__ = __add__
- def __mod__ (self, other): return remainder(self, other)
- def __rmod__ (self, other): return remainder(other, self)
- def __lshift__ (self, n): return left_shift(self, n)
- def __rshift__ (self, n): return right_shift(self, n)
- def __sub__(self, other): return subtract(self, other)
- def __rsub__(self, other): return subtract(other, self)
- def __mul__(self, other): return multiply(self, other)
- __rmul__ = __mul__
- def __div__(self, other): return divide(self, other)
- def __rdiv__(self, other): return divide(other, self)
- def __truediv__(self, other): return true_divide(self, other)
- def __rtruediv__(self, other): return true_divide(other, self)
- def __floordiv__(self, other): return floor_divide(self, other)
- def __rfloordiv__(self, other): return floor_divide(other, self)
- def __pow__(self, other, third=None): return power(self, other, third)
- def __sqrt__(self): return sqrt(self)
-
- def __iadd__(self, other):
- return self + other
-
- def __imul__(self, other):
- return self * other
-
- def __isub__(self, other):
- return self - other
-
- def __idiv__(self, other):
- return self / other
-
- def __eq__(self, other): return equal(self,other)
- def __ne__(self, other): return not_equal(self,other)
- def __lt__(self, other): return less(self,other)
- def __le__(self, other): return less_equal(self,other)
- def __gt__(self, other): return greater(self,other)
- def __ge__(self, other): return greater_equal(self,other)
-
- def astype (self, tc):
- "return self as array of given type."
- d = self._data.astype(tc)
- return datawrap(ma.array(d, mask=self._mask), self)
-
- def filled (self, fill_value=None, ts=False):
- d = super(TimeSeries, self).filled(fill_value)
- if ts: return datawrap(d, self)
- else: return d
-
-
-def datawrap(data, ts): return TimeSeries(data, freq=ts.freq, observed=ts.observed, start_date=ts.start_date())
-
-## wrappers for numpy.ma funcs
-
-sqrt = ts_unary_operation(ma.sqrt)
-log = ts_unary_operation(ma.log)
-log10 = ts_unary_operation(ma.log10)
-exp = ts_unary_operation(ma.exp)
-sin = ts_unary_operation(ma.sin)
-cos = ts_unary_operation(ma.cos)
-tan = ts_unary_operation(ma.tan)
-arcsin = ts_unary_operation(ma.arcsin)
-arccos = ts_unary_operation(ma.arccos)
-arctan = ts_unary_operation(ma.arctan)
-
-def cumprod(self, axis=0, dtype=None, out=None): return datawrap(ma._cumprod(self, axis, dtype, out), self)
-def cumsum(self, axis=0, dtype=None, out=None): return datawrap(ma._cumsum(self, axis, dtype, out), self)
-
-arcsinh = ts_unary_operation(ma.arcsinh)
-arccosh = ts_unary_operation(ma.arccosh)
-arctanh = ts_unary_operation(ma.arctanh)
-sinh = ts_unary_operation(ma.sinh)
-cosh = ts_unary_operation(ma.cosh)
-tanh = ts_unary_operation(ma.tanh)
-absolute = ts_unary_operation(ma.absolute)
-fabs = ts_unary_operation(ma.fabs)
-negative = ts_unary_operation(ma.negative)
-nonzero = ts_unary_operation(ma.nonzero)
-
-around = ts_unary_operation(ma.around)
-floor = ts_unary_operation(ma.floor)
-ceil = ts_unary_operation(ma.ceil)
-logical_not = ts_unary_operation(ma.logical_not)
-
-def zeros(shape, dtype=float, freq=None, start_date=None, observed=None):
- return TimeSeries(ma.zeros(shape, dtype), freq=freq, start_date=start_date, observed=observed)
-def ones(shape, dtype=float, freq=None, start_date=None, observed=None):
- return TimeSeries(ma.ones(shape, dtype), freq=freq, start_date=start_date, observed=observed)
-
-
-# functions from ma that we want to return scalars or masked arrays
-count = ma.count
-sum = ma.sum
-product = ma.product
-average = ma.average
-compress = ma.compress
-minimum = ma.minimum
-maximum = ma.maximum
-alltrue = ma.alltrue
-allclose = ma.allclose
-allequal = ma.allequal
-sometrue = ma.sometrue
-std = ma._std
-var = ma._var
-
-def argmin (x, axis = -1, out=None, fill_value=None):
- # same as argmin for ma, but returns a date instead of integer
- return x.start_date() + ma.argmin(x, axis, out, fill_value)
-
-def argmax (x, axis = -1, out=None, fill_value=None):
- # same as argmax for ma, but returns a date instead of integer
- return x.start_date() + ma.argmax(x, axis, out, fill_value)
-
-
-# binary operators
-add = ts_binary_operation(ma.add)
-subtract = ts_binary_operation(ma.subtract)
-multiply = ts_binary_operation(ma.multiply)
-divide = ts_binary_operation(ma.divide)
-power = ts_binary_operation(ma.power)
-true_divide = ts_binary_operation(ma.true_divide)
-floor_divide = ts_binary_operation(ma.floor_divide)
-remainder = ts_binary_operation(ma.remainder)
-fmod = ts_binary_operation(ma.fmod)
-hypot = ts_binary_operation(ma.hypot)
-arctan2 = ts_binary_operation(ma.arctan2)
-equal = ts_binary_operation(ma.equal)
-not_equal = ts_binary_operation(ma.not_equal)
-less_equal = ts_binary_operation(ma.less_equal)
-greater_equal = ts_binary_operation(ma.greater_equal)
-less = ts_binary_operation(ma.less)
-greater = ts_binary_operation(ma.greater)
-logical_and = ts_binary_operation(ma.logical_and)
-logical_or = ts_binary_operation(ma.logical_or)
-logical_xor = ts_binary_operation(ma.logical_xor)
-bitwise_and = ts_binary_operation(ma.bitwise_and)
-bitwise_or = ts_binary_operation(ma.bitwise_or)
-bitwise_xor = ts_binary_operation(ma.bitwise_xor)
-
-def left_shift (a, n): return datawrap(ma.left_shift(a, n), a)
-def right_shift (a, n): return datawrap(ma.right_shift(a, n), a)
-
-def masked_where(condition, x, copy=1): return datawrap(ma.masked_where(condition, x, copy), x)
-def masked_greater(x, value, copy=1): return datawrap(ma.masked_greater(x, value, copy), x)
-def masked_greater_equal(x, value, copy=1): return datawrap(ma.masked_greater_equal(x, value, copy), x)
-def masked_less(x, value, copy=1): return datawrap(ma.masked_less(x, value, copy), x)
-def masked_less_equal(x, value, copy=1): return datawrap(ma.masked_less_equal(x, value, copy), x)
-def masked_not_equal(x, value, copy=1): return datawrap(ma.masked_not_equal(x, value, copy), x)
-def masked_equal(x, value, copy=1): return datawrap(ma.masked_equal(x, value, copy), x)
-def masked_inside(x, v1, v2, copy=1): return datawrap(ma.masked_inside(x, v1, v2, copy), x)
-def masked_outside(x, v1, v2, copy=1): return datawrap(ma.masked_outside(x, v1, v2, copy), x)
-
-def clip(self,a_min,a_max,out=None): return datawrap(ma._clip(self, a_min, a_max, out=None), self)
-
-
-array = TimeSeries
-
-def _m(f):
- return MethodType(f, None, array)
-
-array.clip = _m(clip)
-array.argmax = _m(argmax)
-array.argmin = _m(argmin)
-array.cumprod = _m(cumprod)
-array.cumsum = _m(cumsum)
-
-
-# time series specific functions
-
-def tser(start, end):
- if start.freq != end.freq:
- raise ValueError("start and end dates must have same frequency!")
- return TimeSeries(numpy.arange(int(start), int(end)+1), dtype=corelib.freqTypeMapping[start.freq], freq=start.freq, start_date=start)
-
-def year(dateSer):
- return __getDateInfo(dateSer,'Y')
-
-def quarter(dateSer):
- return __getDateInfo(dateSer,'Q')
-
-def month(dateSer):
- return __getDateInfo(dateSer,'M')
-
-def day(dateSer):
- return __getDateInfo(dateSer,'D')
-
-def day_of_week(dateSer):
- return __getDateInfo(dateSer,'W')
-
-def __getDateInfo(dateSer,infoCode):
- newData = ma.array(cseries.getDateInfo(dateSer.filled(), dateSer.tstype.freq, infoCode))
- if dateSer.mask is not ma.nomask:
- newData[dateSer.mask] = ma.masked
- return datawrap(newData, dateSer)
-
-
-def adjust_endpoints(a, start_date=None, end_date=None):
- """adjust_endpoints(a, start_date=None, end_date=None) returns a new
- TimeSeries going from start_date to end_date"""
-
- if start_date is None: start_date = a.start_date()
- if end_date is None: end_date = a.end_date()
-
- tmpShape = list(a.shape)
- tmpShape[0] = max(end_date - start_date + 1, 0)
- tmpShape = tuple(tmpShape)
-
- tmpSer = TimeSeries(ma.resize(a, tmpShape), freq=a.freq, observed=a.observed, start_date=start_date)
-
- setStart, setEnd = max(start_date, a.start_date()), min(end_date, a.end_date())
- setLen = setEnd - setStart
-
- tmpSer[:] = ma.masked
-
- if setLen >= 0:
- tmpSer[tmpSer.date_to_index(setStart):tmpSer.date_to_index(setEnd)+1] = a[a.date_to_index(setStart):a.date_to_index(setEnd)+1]
-
- return tmpSer
-
-
-def aligned(*series, **kwargs):
-
- if len(series) < 2:
- return series
-
- freq = series[0].freq
-
- if len(set([x.freq for x in series])) > 1: raise ValueError("All series must have same frequency!")
-
- if 'start_date' in kwargs: start_date = kwargs['start_date']
- else: start_date = min([x.start_date() for x in series])
-
- if 'end_date' in kwargs: end_date = kwargs['end_date']
- else: end_date = max([x.end_date() for x in series])
-
- return [adjust_endpoints(x, start_date=start_date, end_date=end_date) for x in series]
-
\ No newline at end of file
Deleted: trunk/Lib/sandbox/timeseries/tsdate.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tsdate.py 2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tsdate.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,197 +0,0 @@
-import corelib
-import cseries
-import numpy as np
-import mx.DateTime
-
-class Date:
- def __init__(self, freq, year=None, month=None, day=None, seconds=None, quarter=None, mxDate=None, value=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 == 'D':
- self.mxDate = mx.DateTime.DateTimeFromAbsDays(value-1)
- elif self.freq == 'B':
- value = value - 1
- self.mxDate = mx.DateTime.DateTimeFromAbsDays(value + (value//5)*7 - (value//5)*5)
- elif self.freq == 'S':
- self.mxDate = secondlyOriginDate + mx.DateTime.DateTimeDeltaFromSeconds(value)
- elif self.freq == 'M':
- self.mxDate = (mx.DateTime.Date(0)) + mx.DateTime.RelativeDateTime(months=value-1, day=-1)
- elif self.freq == 'A':
- self.mxDate = mx.DateTime.Date(value, -1, -1)
- elif self.freq == 'Q':
- self.mxDate = (mx.DateTime.Date(0)) + mx.DateTime.RelativeDateTime(years=(value // 4), month=((value * 3) % 12), day=-1)
- elif mxDate is not None:
- self.mxDate = mxDate
- else:
- error = ValueError("Insufficient parameters given to create a date at the given frequency")
-
- if year is None:
- raise error
-
- if self.freq in ('B', 'D'):
- if month is None or day is None: raise error
- elif self.freq == 'M':
- if month is None: raise error
- day = -1
- elif self.freq == 'Q':
- if quarter is None: raise error
- 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 error
-
- if self.freq != 'S':
- self.mxDate = mx.DateTime.Date(year, month, day)
- if self.freq == 'B':
- if self.mxDate.day_of_week == 5 or self.mxDate.day_of_week == 6:
- raise ValueError("Weekend passed as business day")
- else:
- _hours = int(seconds/3600)
- _minutes = int((seconds - _hours*3600)/60)
- _seconds = seconds % 60
-
- self.mxDate = mx.DateTime.Date(year, month, day, _hours, _minutes, _seconds)
-
- self.value = self.__value()
-
- def day(self): return self.mxDate.day
- def day_of_week(self): return self.mxDate.day_of_week
- def month(self): return self.mxDate.month
- def quarter(self): return monthToQuarter(self.mxDate.month)
- def year(self): return self.mxDate.year
- def seconds(self): return int(self.mxDate.second)
- def minute(self): return int(self.mxDate.minute)
- def hour(self): return int(self.mxDate.hour)
-
- def strfmt(self, fmt):
- 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 default_fmtstr(self):
- if self.freq in ("B", "D"):
- return "%d-%b-%y"
- elif self.freq == "S":
- return "%d-%b-%Y %H:%M:%S"
- elif self.freq == "M":
- return "%b-%Y"
- elif self.freq == "Q":
- return "%Yq%q"
- elif self.freq == "A":
- return "%Y"
- else:
- return "%d-%b-%y"
-
- def __add__(self, other):
- if isinstance(other, Date):
- raise TypeError("Cannot add dates")
- 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 ValueError("Cannont subtract dates of different frequency (" + str(self.freq) + " != " + str(other.freq) + ")")
- else:
- return int(self) - int(other)
- else:
- return self + (-1) * int(other)
-
-
- def __repr__(self): return "<" + str(self.freq) + ":" + str(self) + ">"
-
- def __eq__(self, other):
- if self.freq != other.freq:
- raise TypeError("frequencies are not equal!")
- return int(self) == int(other)
-
- def __cmp__(self, other):
- if self.freq != other.freq:
- raise TypeError("frequencies are not equal!")
- return int(self)-int(other)
-
- def __hash__(self): return hash(int(self)) ^ hash(self.freq)
-
- def __int__(self):
- return self.value
-
- def __value(self):
-
- if self.freq == 'D':
- return self.mxDate.absdate
- elif self.freq == 'B':
- days = self.mxDate.absdate
- weeks = days // 7
- return int((weeks*5) + (days - weeks*7))
- elif self.freq == 'M':
- return self.mxDate.year*12 + self.mxDate.month
- elif self.freq == 'S':
- return int((self.mxDate - secondlyOriginDate).seconds)
- elif self.freq == 'A':
- return int(self.mxDate.year)
- elif self.freq == 'Q':
- return int(self.mxDate.year*4 + self.mxDate.month/3)
-
-
-secondlyOriginDate = mx.DateTime.Date(1980) - mx.DateTime.DateTimeDeltaFromSeconds(1)
-
-
-#######################
-# FUNCTIONS
-#######################
-def monthToQuarter(monthNum):
- return int((monthNum-1)/3)+1
-
-def thisday(freq):
-
- freq = corelib.fmtFreq(freq)
-
- tempDate = mx.DateTime.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 == 'B' or freq == 'D' or freq == 'S':
- 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)
-
-
-def prevbusday(day_end_hour=18, day_end_min=0):
- tempDate = mx.DateTime.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')
-
-
-# returns date converted to a date of toFreq according to relation
-# relation = "BEFORE" or "AFTER" (not case sensitive)
-def dateOf(date, toFreq, relation="BEFORE"):
-
- toFreq = corelib.fmtFreq(toFreq)
- _rel = relation.upper()[0]
-
- if date.freq == toFreq:
- return date
- else:
- return Date(freq=toFreq, value=cseries.asfreq(np.asarray(date.value), date.freq, toFreq, _rel))
Copied: trunk/Lib/sandbox/timeseries/tseries.py (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py 2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/tseries.py 2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1242 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+
+ - an array storing the time information (as a `DateArray` instance);
+ - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__ = '$Date$'
+
+
+import logging
+import weakref
+
+
+import numpy
+from numpy import ndarray
+from numpy.core import bool_, complex_, float_, int_, object_
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+import numpy.core.umath as umath
+#from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+import maskedarray as MA
+#reload(MA)
+#MaskedArray = MA.MaskedArray
+from maskedarray.core import MaskedArray, MAError, masked, nomask, \
+ filled, getmask, getmaskarray, make_mask_none, mask_or, make_mask, \
+ masked_array
+
+import tcore as corelib
+#reload(corelib)
+from tcore import *
+
+import tdates
+#reload(tdates)
+from tdates import DateError, InsufficientDateError
+from tdates import Date, isDate, DateArray, isDateArray, \
+ date_array, date_array_fromlist, date_array_fromrange, thisday
+
+import cseries
+#reload(cseries)
+
+#...............................................................................
+logging.basicConfig(level=logging.DEBUG,
+ format='%(name)-15s %(levelname)s %(message)s',)
+talog = logging.getLogger('log.TimeArray')
+tslog = logging.getLogger('TimeSeries')
+btslog = logging.getLogger('BaseTimeSeries')
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+#### --------------------------------------------------------------------------
+#--- ... TimeSeriesError class ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+ "Class for TS related errors."
+ def __init__ (self, args=None):
+ "Creates an exception."
+ Exception.__init__(self)
+ self.args = args
+ def __str__(self):
+ "Calculates the string representation."
+ return str(self.args)
+ __repr__ = __str__
+
+class TimeSeriesCompatibilityError(TimeSeriesError):
+ """Defines the exception raised when series are incompatible."""
+ def __init__(self, mode, first, second):
+ if mode == 'freq':
+ msg = "Incompatible time steps! (%s <> %s)"
+ elif mode == 'start_date':
+ msg = "Incompatible starting dates! (%s <> %s)"
+ elif mode == 'size':
+ msg = "Incompatible sizes! (%s <> %s)"
+ msg = msg % (first, second)
+ TimeSeriesError.__init__(self, msg)
+
+
+#def _compatibilitycheck(a, b):
+def _timeseriescompat(a, b):
+ """Checks the date compatibility of two TimeSeries object.
+ Returns True if everything's fine, or raises an exception."""
+ if not (hasattr(a,'freq') and hasattr(b, 'freq')):
+ return True
+ if a.freq != b.freq:
+ raise TimeSeriesCompatibilityError('freq', a.freq, b.freq)
+ elif a.start_date() != b.start_date():
+ raise TimeSeriesCompatibilityError('start_date',
+ a.start_date(), b.start_date())
+ elif (a._dates.get_steps() != b._dates.get_steps()).any():
+ raise TimeSeriesCompatibilityError('time_steps',
+ a._dates.get_steps(), b._dates.get_steps())
+ elif a.shape != b.shape:
+ raise TimeSeriesCompatibilityError('size', "1: %s" % str(a.shape),
+ "2: %s" % str(b.shape))
+ return True
+
+def _datadatescompat(data,dates):
+ """Checks the compatibility of dates and data at the creation of a TimeSeries.
+ Returns True if everything's fine, raises an exception otherwise."""
+ # If there's only 1 element, the date is a Date object, which has no size...
+ tsize = numeric.size(dates)
+ dsize = data.size
+ # Only one data
+ if dsize == tsize:
+ return True
+ elif data.ndim > 1:
+ dsize = numeric.asarray(data.shape)[:-1].prod()
+ if dsize == tsize:
+ return True
+ raise TimeSeriesCompatibilityError('size', "data: %s" % dsize,
+ "dates: %s" % tsize)
+
+def _getdatalength(data):
+ "Estimates the length of a series (size/nb of variables)."
+ if numeric.ndim(data) >= 2:
+ return numeric.asarray(numeric.shape(data))[:-1].prod()
+ else:
+ return numeric.size(data)
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+#if oldma:
+# parentclass = ndarray
+#else:
+# parentclass = MaskedArray
+#
+class TimeSeries(MaskedArray, object):
+ """Base class for the definition of time series.
+A time series is here defined as the combination of three arrays:
+
+ - `series` : *[ndarray]*
+ Data part
+ - `mask` : *[ndarray]*
+ Mask part
+ - `dates` : *[DateArray]*
+ Date part
+
+The combination of `series` and `dates` is the `data` part.
+ """
+ def __new__(cls, data, dates=None, mask=nomask,
+ freq=None, observed=None, start_date=None,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ #tslog.info("__new__: received data types %s, %s" % (type(data), data))
+ options = dict(copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask, )
+ if isinstance(data, TimeSeries):
+ # Check dates ........
+ if dates is None:
+ newdates = data._dates
+ else:
+ if not hasattr(dates,'freq'):
+ raise DateError, "Invalid Dates!"
+ newdates = dates
+ data._dates = newdates
+ if hasattr(data, '_data') and hasattr(data._data, '_dates'):
+ data._data._dates = newdates
+ cls._defaultdates = newdates
+ # Check frequency......
+ if freq is not None:
+ freq = corelib.fmtFreq(freq)
+ if freq != newdates.freq:
+ _dates = newdates.tofreq(freq)
+ else:
+ freq = newdates.freq
+ # Check observed.......
+ if observed is not None:
+ observed = data._observed
+ cls._defaultobserved = observed
+ _data = data._series
+ else:
+ # Check dates ........
+ if dates is None:
+ length = _getdatalength(data)
+ newdates = date_array(start_date=start_date, length=length,
+ freq=freq)
+ elif not hasattr(dates, 'freq'):
+ newdates = date_array(dlist=dates, freq=freq)
+ else:
+ newdates = dates
+ _data = data
+ if hasattr(data, '_mask') :
+ mask = mask_or(data._mask, mask)
+ cls._defaultdates = newdates
+ cls._defaultobserved = observed
+# if oldma:
+# newdata = MaskedArray(data, mask=mask, dtype=dtype,
+# copy=copy,fill_value=fill_value)
+# cls._defaultmask = newdata._mask
+# cls._defaulthardmask = True
+# cls._fill_value = newdata._fill_value
+# assert(_datadatescompat(newdata,dates))
+# return ndarray.__new__(cls,shape=newdata.shape,dtype=newdata.dtype,
+# buffer=newdata._data)
+# _data = data
+# newdata = MaskedArray.__new__(cls, data=_data, mask=mask, **options)
+ newdata = super(TimeSeries,cls).__new__(cls, _data, mask=mask,
+ **options)
+ assert(_datadatescompat(data,newdates))
+ return newdata
+
+ #..................................
+ def __array_wrap__(self, obj, context=None):
+# if oldma:
+# tmpself = MaskedArray(self._data, mask=self._mask)
+# return TimeSeries(MaskedArray.__array_wrap__(tmpself, obj, context),
+# dates=self._dates)
+# print "__array_wrap__"
+ return TimeSeries(super(TimeSeries,self).__array_wrap__(obj, context),
+ dates=self._dates)
+ #............................................
+ def __array_finalize__(self,obj):
+ #tslog.info("__array_finalize__ received %s" % type(obj))
+ if isinstance(obj, TimeSeries):
+ self._dates = obj._dates
+ self._data = obj._series._data
+ self._mask = obj._series._mask
+ self._series = obj._series
+ self._hardmask = obj._series._hardmask
+ self.observed = obj.observed
+ self._fill_value = obj._fill_value
+ else:
+ self._dates = self._defaultdates
+ self.observed = self._defaultobserved
+ self._series = MA.array(obj, mask=self._defaultmask,
+ copy=False, hard_mask=self._defaulthardmask)
+ self._mask = self._defaultmask
+ self._data = obj
+ self._hardmask = self._defaulthardmask
+ self.fill_value = self._fill_value
+ self._mask = self._series._mask
+ self._data = self._series._data
+ self._hardmask = self._series._hardmask
+ #tslog.info("__array_finalize__ sends %s" % type(self))
+ return
+ #............................................
+ def __getattribute__(self,attr):
+ "Returns a given attribute."
+ # Here, we need to be smart: _mask should call _series._mask...
+ if attr in ['_data','_mask','_hardmask']:
+ return getattr(self._series,attr)
+ return super(TimeSeries, self).__getattribute__(attr)
+
+ def __setattribute__(self,attr, value):
+ """Sets an attribute to a given value."""
+ # Same thing here: if we modify ._mask, we need to modify _series._mask
+ # ...as well
+ super(TimeSeries, self).__setattribute__(attr, value)
+ if attr in ['_data','_mask','_hardmask']:
+ super(self._series.__class__, self._series).__setattribute__(attr, value)
+ setattr(self._series, attr, value)
+ #............................................
+ def __checkindex(self, index):
+ "Checks the validity of an index."
+ if isinstance(index, int):
+ return index
+ if isinstance(index, str):
+ return self._dates.date_to_index(Date(self._dates.freq, string=index))
+ elif isDate(index) or isDateArray(index):
+ return self._dates.date_to_index(index)
+ elif isinstance(index,slice):
+ slice_start = self.__checkindex(index.start)
+ slice_stop = self.__checkindex(index.stop)
+ return slice(slice_start, slice_stop, index.step)
+ elif isTimeSeries(index):
+ index = index._series
+ if getmask(index) is not nomask:
+ msg = "Masked arrays must be filled before they can be used as indices!"
+ raise IndexError, msg
+ return index
+
+ def __getitem__(self, index):
+ """x.__getitem__(y) <==> x[y]
+Returns the item described by i. Not a copy as in previous versions.
+ """
+ index = self.__checkindex(index)
+ data = self._series[index]
+ date = self._dates[index]
+ m = self._mask
+ scalardata = (len(numeric.shape(data))==0)
+ #
+ if m is nomask:
+ if scalardata:
+ return TimeSeries(data, dates=date)
+ else:
+ return TimeSeries(data, dates=date, mask=nomask, keep_mask=True,
+ copy=False)
+ #....
+ mi = m[index]
+ if mi.size == 1:
+ if mi:
+ return TimeSeries(data, dates=date, mask=True)
+ return TimeSeries(data, dates=date, mask=nomask)
+ else:
+ return TimeSeries(data, dates=date, mask=mi)
+ #........................
+ def __setitem__(self, index, value):
+ """x.__setitem__(i, y) <==> x[i]=y
+Sets item described by index. If value is masked, masks those locations.
+ """
+ if self is masked:
+ raise MAError, 'Cannot alter the masked element.'
+ index = self.__checkindex(index)
+ #....
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[index], value))
+ self._series[index] = value._series
+ else:
+ self._series[index] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+
+ #........................
+ def __getslice__(self, i, j):
+ "Gets slice described by i, j"
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ (data, date) = (self._series[i:j], self._dates[i:j])
+ return TimeSeries(data, dates=date, copy=False)
+ #....
+ def __setslice__(self, i, j, value):
+ "Gets item described by i. Not a copy as in previous versions."
+ i = self.__checkindex(i)
+ j = self.__checkindex(j)
+ #....
+ data = self._series[i:j]
+ if isinstance(value, TimeSeries):
+ assert(_timeseriescompat(self[i:j], value))
+ self._series[i:j] = value._series
+ else:
+ self._series[i:j] = value
+ # Don't forget to update the mask !
+ self._mask = self._series._mask
+ #......................................................
+ def __len__(self):
+ if self.ndim == 0:
+ return 0
+ return ndarray.__len__(self)
+ #......................................................
+ def __str__(self):
+ """Returns a string representation of self (w/o the dates...)"""
+ return str(self._series)
+ def __repr__(self):
+ """Calculates the repr representation, using masked for fill if
+ it is enabled. Otherwise fill with fill value.
+ """
+ desc = """\
+timeseries(data =
+ %(data)s,
+ dates =
+ %(time)s,
+ freq = %(freq)s)
+"""
+ desc_short = """\
+timeseries(data = %(data)s,
+ dates = %(time)s,
+ freq = %(freq)s)
+"""
+ if numeric.size(self._dates) > 2 and self.isvalid():
+ timestr = "[%s ... %s]" % (str(self._dates[0]),str(self._dates[-1]))
+ else:
+ timestr = str(self.dates)
+
+ if self.ndim <= 1:
+ return desc_short % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ return desc % {'data': str(self._series),
+ 'time': timestr,
+ 'freq': self.freq, }
+ #............................................
+ def _get_mask(self):
+ """Returns the current mask."""
+ return self._series._mask
+ def _set_mask(self, mask):
+ """Sets the mask to `mask`."""
+ mask = make_mask(mask, copy=False, small_mask=True)
+ if mask is not nomask:
+ if mask.size != self._data.size:
+ raise ValueError, "Inconsistent shape between data and mask!"
+ if mask.shape != self._data.shape:
+ mask.shape = self._data.shape
+ self._series._mask = mask
+ else:
+ self._series._mask = nomask
+ mask = property(fget=_get_mask, fset=_set_mask, doc="Mask")
+
+ def ids (self):
+ """Return the ids of the data, dates and mask areas"""
+ return (id(self._series), id(self.dates),)
+
+ def copy(self):
+ "Returns a copy of the TimeSeries."
+ return TimeSeries(self, copy=True)
+
+ #------------------------------------------------------
+ @property
+ def series(self):
+ "Returns the series."
+ return self._series
+ @property
+ def dates(self):
+ """Returns the dates"""
+ return self._dates
+ @property
+ def freq(self):
+ """Returns the corresponding frequency."""
+ return self._dates.freq
+# @property
+ def years(self):
+ """Returns the corresponding years."""
+ return self._dates.years
+# @property
+ def months(self):
+ """Returns the corresponding months."""
+ return self._dates.months
+# @property
+ def yeardays(self):
+ """Returns the corresponding days of year."""
+ return self._dates.yeardays
+ day_of_year = yeardays
+# @property
+ def weekdays(self):
+ """Returns the corresponding days of weeks."""
+ return self._dates.day_of_week
+ day_of_week = weekdays
+
+ def start_date(self):
+ """Returns the first date of the series."""
+ return self._dates[0]
+#
+ def end_date(self):
+ """Returns the last date of the series."""
+ return self._dates[-1]
+
+ def isvalid(self):
+ """Returns whether the series has no duplicate/missing dates."""
+ return self._dates.isvalid()
+
+ def has_missing_dates(self):
+ """Returns whether there's a date gap in the series."""
+ return self._dates.has_missing_dates()
+
+ def isfull(self):
+ """Returns whether there's no date gap in the series."""
+ return self._dates.isfull()
+
+ def has_duplicated_dates(self):
+ """Returns whether there are duplicated dates in the series."""
+ return self._dates.has_duplicated_dates()
+
+ def date_to_index(self, date):
+ "Returns the index corresponding to a given date, as an integer."
+ return self._dates.date_to_index(date)
+ #.....................................................
+ def asfreq(self, freq=None):
+ "Converts the dates to another frequency."
+ if freq is None:
+ return self
+ return TimeSeries(self._series, dates=self._dates.asfreq(freq))
+
+ def convert(self, freq, func='auto', position='END', interp=None):
+ "Converts the dates to another frequency, and adapt the data."
+ return convert(self, freq, func=func, position=position, interp=interp)
+
+##### --------------------------------------------------------------------------
+##--- ... Additional methods ...
+##### --------------------------------------------------------------------------
+class _inplacemethod(object):
+ """Defines a wrapper for inplace arithmetic array methods (iadd, imul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ self.obj = None
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ assert(_timeseriescompat(instance,other))
+ func = getattr(instance._series, self.f)
+ func(other, *args)
+ return instance
+#......................................
+TimeSeries.__iadd__ = _inplacemethod('__iadd__')
+TimeSeries.__iand__ = _inplacemethod('__iand__')
+TimeSeries.__idiv__ = _inplacemethod('__idiv__')
+TimeSeries.__isub__ = _inplacemethod('__isub__')
+TimeSeries.__imul__ = _inplacemethod('__imul__')
+
+
+class _tsmathmethod(object):
+ """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+ """
+ def __init__ (self, binop):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self.f = binop
+ #
+ def __get__(self, obj, objtype=None):
+ "Gets the calling object."
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, other, *args):
+ "Execute the call behavior."
+ instance = self.obj
+ _dates = instance._dates
+ #tslog.info("_tsmathmethod: series: %s" % instance,)
+ #tslog.info("_tsmathmethod: other : %s" % other,)
+ func = getattr(instance._series, self.f)
+ if isinstance(other, TimeSeries):
+ assert(_timeseriescompat(instance, other))
+ return instance.__class__(func(other, *args), dates=_dates,)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+TimeSeries.__eq__ = _tsmathmethod('__eq__')
+TimeSeries.__ne__ = _tsmathmethod('__ne__')
+TimeSeries.__lt__ = _tsmathmethod('__lt__')
+TimeSeries.__le__ = _tsmathmethod('__le__')
+TimeSeries.__gt__ = _tsmathmethod('__gt__')
+TimeSeries.__ge__ = _tsmathmethod('__ge__')
+#................................................
+class _tsarraymethod(object):
+ """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+ """
+ def __init__ (self, methodname, ondates=False):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ self._ondates = ondates
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args):
+ "Execute the call behavior."
+ _name = self._name
+ instance = self.obj
+ func_series = getattr(instance._series, _name)
+ if self._ondates:
+ func_dates = getattr(instance._dates, _name)
+ return instance.__class__(func_series(*args),
+ dates=func_dates(*args))
+ else:
+ return instance.__class__(func_series(*args),
+ dates=instance._dates)
+#TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+TimeSeries.ravel = _tsarraymethod('ravel', ondates=True)
+TimeSeries.filled = _tsarraymethod('filled', ondates=False)
+TimeSeries.cumsum = _tsarraymethod('cumsum',ondates=False)
+TimeSeries.cumprod = _tsarraymethod('cumprod',ondates=False)
+TimeSeries.anom = _tsarraymethod('anom',ondates=False)
+
+#......................................
+class _tsaxismethod(object):
+ """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the series.
+ """
+ def __init__ (self, methodname):
+ """abfunc(fillx, filly) must be defined.
+ abinop(x, filly) = x for all x to enable reduce.
+ """
+ self._name = methodname
+ #
+ def __get__(self, obj, objtype=None):
+ self.obj = obj
+ return self
+ #
+ def __call__ (self, *args, **params):
+ "Execute the call behavior."
+ (_dates, _series) = (self.obj._dates, self.obj._series)
+ func = getattr(_series, self._name)
+ result = func(*args, **params)
+ if _series.ndim < 2 or _dates.size == _series.size:
+ return result
+ else:
+ try:
+ axis = params.get('axis', args[0])
+ if axis in [-1, _series.ndim-1]:
+ result = TimeSeries(result, dates=_dates)
+ except IndexError:
+ pass
+ return result
+#.......................................
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+class _tsblockedmethods(object):
+ """Defines a wrapper for array methods that should be temporarily disabled.
+ """
+ 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):
+ raise NotImplementedError
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+ """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+ """
+ def __init__(self, methodname):
+ self._methodname = methodname
+ self.__doc__ = self.getdoc()
+ def getdoc(self):
+ "Returns the doc of the function (from the doc of the method)."
+ try:
+ return getattr(TimeSeries, self._methodname).__doc__
+ except:
+ return "???"
+ #
+ def __call__ (self, caller, *args, **params):
+ if hasattr(caller, self._methodname):
+ method = getattr(caller, self._methodname)
+ # If method is not callable, it's a property, and don't call it
+ if hasattr(method, '__call__'):
+ return method.__call__(*args, **params)
+ return method
+ method = getattr(fromnumeric.asarray(caller), self._methodname)
+ try:
+ return method(*args, **params)
+ except SystemError:
+ return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+#
+##### ---------------------------------------------------------------------------
+#---- ... Additional methods ...
+##### ---------------------------------------------------------------------------
+def tofile(self, output, sep='\t', format_dates=None):
+ """Writes the TimeSeries to a file.
+
+:Parameters:
+ - `output` (String) : Name or handle of the output file.
+ - `sep` (String) : Column separator *['\t']*.
+ - `format` (String) : Data format *['%s']*.
+ """
+ if not hasattr(output, 'writeline'):
+ ofile = open(output,'w')
+ else:
+ ofile = output
+ if format_dates is None:
+ format_dates = self.dates[0].default_fmtstr()
+ oformat = "%%s%s%s" % (sep,format_dates)
+ for (_dates,_data) in numpy.broadcast(self._dates.ravel().asstrings(),
+ filled(self)):
+ ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+ ofile.close()
+TimeSeries.tofile = tofile
+
+#............................................
+def asrecords(series):
+ """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+ """
+ desctype = [('_dates',int_), ('_series',series.dtype), ('_mask', bool_)]
+ flat = series.ravel()
+ _dates = numeric.asarray(flat._dates)
+ if flat.size > 0:
+ return recfromarrays([_dates, flat._data, getmaskarray(flat)],
+ dtype=desctype,
+ shape = (flat.size,),
+ )
+ else:
+ return recfromarrays([[], [], []], dtype=desctype,
+ shape = (flat.size,),
+ )
+TimeSeries.asrecords = asrecords
+
+def flatten(series):
+ """Flattens a (multi-) time series to 1D series."""
+ shp_ini = series.shape
+ # Already flat time series....
+ if len(shp_ini) == 1:
+ return series
+ # Folded single time series ..
+ newdates = series._dates.ravel()
+ if series._dates.size == series._series.size:
+ newshape = (series._series.size,)
+ else:
+ newshape = (numeric.asarray(shp_ini[:-1]).prod(), shp_ini[-1])
+ newseries = series._series.reshape(newshape)
+ return time_series(newseries, newdates)
+TimeSeries.flatten = flatten
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Archiving ---
+#####---------------------------------------------------------------------------
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+ """Internal function that builds a new TimeSeries 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, int_)
+ _mask = ndarray.__new__(ndarray, baseshape, bool_)
+ return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask,
+ dtype=basetype, fill_value=fill_value)
+#
+def _tsgetstate(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,
+ a.freq,
+ records.flags.fnc,
+ a.fill_value,
+ records
+ )
+ 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, frq, isf, flv, rec) = state
+ a.fill_value = flv
+ a._dates = a._dates.__class__(rec['_dates'], freq=frq)
+ (a._dates).__tostr = None
+ _data = rec['_series'].view(typ)
+ _mask = rec['_mask'].view(MA.MaskType)
+ a._series = masked_array(_data, mask=_mask)
+# a._data.shape = shp
+# a._dates.shape = shp
+# a._mask = rec['_mask'].view(MA.MaskType)
+# a._mask.shape = shp
+#
+def _tsreduce(a):
+ """Returns a 3-tuple for pickling a MaskedArray."""
+ return (_tsreconstruct,
+ (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+ a.__getstate__())
+#
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, freq=None, observed=None,
+ start_date=None, end_date=None, length=None, include_last=True,
+ mask=nomask,
+ dtype=None, copy=False, fill_value=None,
+ keep_mask=True, small_mask=True, hard_mask=False):
+ """Creates a TimeSeries object
+
+:Parameters:
+ `dates` : ndarray
+ Array of dates.
+ `data` :
+ Array of data.
+ """
+ if dates is None:
+ length = _getdatalength(data)
+ dates = date_array(start_date=start_date, end_date=end_date,
+ length=length, include_last=include_last, freq=freq)
+ elif not isinstance(dates, DateArray):
+ dates = date_array(dlist=dates, freq=freq)
+ return TimeSeries(data=data, dates=dates, mask=mask, observed=observed,
+ copy=copy, dtype=dtype, fill_value=fill_value,
+ keep_mask=keep_mask, small_mask=small_mask,
+ hard_mask=hard_mask,)
+
+
+def isTimeSeries(series):
+ "Returns whether the series is a valid TimeSeries object."
+ return isinstance(series, TimeSeries)
+
+##### --------------------------------------------------------------------------
+#---- ... Additional functions ...
+##### --------------------------------------------------------------------------
+def mask_period(data, start_date=None, end_date=None,
+ inside=True, include_edges=True, inplace=True):
+ """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked).
+
+:Parameters:
+ `data` : Timeseries
+ Data to process
+ `start_date` : Date *[None]*
+ Starting date. If None, uses the first date.
+ `end_date` : Date *[None]*
+ Ending date. If None, uses the last date.
+ `inside` : Boolean *[True]*
+ Whether the dates inside the range should be masked. If not, masks outside.
+ `include_edges` : Boolean *[True]*
+ Whether the starting and ending dates should be masked.
+ `inplace` : Boolean *[True]*
+ Whether the data mask should be modified in place. If not, returns a new
+ TimeSeries.
+"""
+ if not isTimeSeries(data):
+ raise ValueError,"Data should be a valid TimeSeries!"
+ # Check the starting date ..............
+ if start_date is None:
+ start_date = data._dates[0]
+ elif isinstance(start_date, str):
+ start_date = Date(data.freq, string=start_date)
+ elif not isDateType(start_date):
+ raise DateError,"Starting date should be a valid date!"
+ start_date = max(start_date, data.dates[0])
+ # Check the ending date ................
+ if end_date is None:
+ end_date = data._dates[-1]
+ elif isinstance(end_date, str):
+ end_date = Date(data.freq, string=end_date)
+ elif not isDateType(end_date):
+ raise DateError,"Starting date should be a valid date!"
+ end_date = min(end_date, data.dates[-1])
+ # Constructs the selection mask .........
+ if inside:
+ if include_edges:
+ selection = (data.dates >= start_date) & (data.dates <= end_date)
+ else:
+ selection = (data.dates > start_date) & (data.dates < end_date)
+ else:
+ if include_edges:
+ selection = (data.dates <= start_date) | (data.dates >= end_date)
+ else:
+ selection = (data.dates < start_date) | (data.dates > end_date)
+ # Process the data:
+ if inplace:
+ if data._mask is nomask:
+ data._mask = selection
+ else:
+ data._mask += selection
+ else:
+ return TimeSeries(data, mask=selection, keep_mask=True)
+ return data
+
+def mask_inside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling inside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=True, include_edges=include_edges, inplace=inplace)
+def mask_outside_period(data, start_date=None, end_date=None,
+ include_edges=True, inplace=True):
+ """Masks values falling outside a given range of dates."""
+ return mask_period(data, start_date=start_date, end_date=end_date,
+ inside=False, include_edges=include_edges, inplace=inplace)
+#..........................................................
+def adjust_endpoints(a, start_date=None, end_date=None):
+ """Returns a TimeSeries going from `start_date` to `end_date`.
+ If `start_date` and `end_date` both fall into the initial range of dates,
+ the new series is NOT a copy.
+ """
+ # Series validity tests .....................
+ if not isinstance(a, TimeSeries):
+ raise TypeError,"Argument should be a valid TimeSeries object!"
+ if a.freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ if not a.dates.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+ # Flatten the series if needed ..............
+ a = a.flatten()
+ shp_flat = a.shape
+ # Dates validity checks .,...................
+ msg = "%s should be a valid Date instance! (got %s instead)"
+ (dstart, dend) = a.dates[[0,-1]]
+ if start_date is None:
+ start_date = dstart
+ start_lag = 0
+ else:
+ if not isDateType(start_date):
+ raise TypeError, msg % ('start_date', type(start_date))
+ start_lag = start_date - dstart
+ #....
+ if end_date is None:
+ end_date = dend
+ end_lag = 0
+ else:
+ if not isDateType(end_date):
+ raise TypeError, msg % ('end_date', type(end_date))
+ end_lag = end_date - dend
+ # Check if the new range is included in the old one
+ if start_lag >= 0:
+ if end_lag == 0:
+ return a[start_lag:]
+ elif end_lag < 0:
+ return a[start_lag:end_lag]
+ # Create a new series .......................
+ newdates = date_array(start_date=start_date, end_date=end_date)
+ newshape = list(shp_flat)
+ newshape[0] = len(newdates)
+ newshape = tuple(newshape)
+
+ newdata = masked_array(numeric.empty(newshape, dtype=a.dtype), mask=True)
+ newseries = TimeSeries(newdata, newdates)
+ start_date = max(start_date, dstart)
+ end_date = min(end_date, dend) + 1
+ newseries[start_date:end_date] = a[start_date:end_date]
+ return newseries
+#....................................................................
+def align_series(*series, **kwargs):
+ """Aligns several TimeSeries, so that their starting and ending dates match.
+ Series are resized and filled with mased values accordingly.
+
+ The function accepts two extras parameters:
+ - `start_date` forces the series to start at that given date,
+ - `end_date` forces the series to end at that given date.
+ By default, `start_date` and `end_date` are set to the smallest and largest
+ dates respectively.
+ """
+ if len(series) < 2:
+ return series
+ unique_freqs = numpy.unique([x.freq for x in series])
+ try:
+ common_freq = unique_freqs.item()
+ except ValueError:
+ raise TimeSeriesError, \
+ "All series must have same frequency!"
+ if common_freq == 'U':
+ raise TimeSeriesError, \
+ "Cannot adjust a series with 'Undefined' frequency."
+ valid_states = [x.isvalid() for x in series]
+ if not numpy.all(valid_states):
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ start_date = kwargs.pop('start_date', min([x.start_date() for x in series]))
+ if isinstance(start_date,str):
+ start_date = Date(common_freq, string=start_date)
+ end_date = kwargs.pop('end_date', max([x.end_date() for x in series]))
+ if isinstance(end_date,str):
+ end_date = Date(common_freq, string=end_date)
+
+ return [adjust_endpoints(x, start_date, end_date) for x in series]
+aligned = align_series
+#....................................................................
+def convert(series, freq, func='auto', position='END', interp=None):
+ """Converts a series to a frequency
+
+ When converting to a lower frequency, func is a function that acts
+ on a 1-d array and returns a scalar or 1-d array. func should handle
+ masked values appropriately. If func is "auto", then an
+ appropriate function is determined based on the observed attribute
+ of the series. If func is None, then a 2D array is returned, where each
+ column represents the values appropriately grouped into the new frequency.
+ interp and position will be ignored in this case.
+
+ When converting to a higher frequency, position is 'START' or 'END'
+ and determines where the data point is in each period (eg. if going
+ from monthly to daily, and position is 'END', then each data point is
+ placed at the end of the month). Interp is the method that will be used
+ to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
+ and None.
+
+ Note: interp currently not implemented
+ """
+ if not isinstance(series,TimeSeries):
+ raise TypeError, "The argument should be a valid TimeSeries!"
+ if not series.isvalid():
+ raise TimeSeriesError, \
+ "Cannot adjust a series with missing or duplicated dates."
+
+ if position.upper() not in ('END','START'):
+ raise ValueError("invalid value for position argument: (%s)",str(position))
+
+ toFreq = corelib.fmtFreq(freq)
+ fromFreq = series.freq
+ start_date = series._dates[0]
+
+ if fromFreq == toFreq:
+ return series.copy()
+
+ if series.size == 0:
+ return TimeSeries(series, freq=toFreq,
+ start_date=start_date.asfreq(toFreq))
+ if func == 'auto':
+ func = corelib.obs_dict[series.observed]
+
+ tempData = series._series.filled()
+ tempMask = getmaskarray(series)
+
+ cRetVal = cseries.reindex(tempData, fromFreq, toFreq, position,
+ int(start_date), tempMask)
+ _values = cRetVal['values']
+ _mask = cRetVal['mask']
+ _startindex = cRetVal['startindex']
+ start_date = Date(freq=toFreq, value=_startindex)
+
+ tempData = masked_array(_values, mask=_mask)
+
+ if tempData.ndim == 2 and func is not None:
+ tempData = MA.apply_along_axis(func, -1, tempData)
+
+# newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+
+ newseries = TimeSeries(tempData, freq=toFreq,
+ observed=series.observed,
+ start_date=start_date)
+ return newseries
+# return adjust_endpoints(newseries, end_date=newEnd)
+TimeSeries.convert = convert
+#....................................................................
+def fill_missing_dates(data, dates=None, freq=None,fill_value=None):
+ """Finds and fills the missing dates in a time series.
+The data corresponding to the initially missing dates are masked, or filled to
+`fill_value`.
+
+:Parameters:
+ `data`
+ Initial array of data.
+ `dates`
+ Initial array of dates.
+ `freq` : float *[None]*
+ New date resolutions. If *None*, the initial resolution is used instead.
+ `fill_value` : float *[None]*
+ Default value for missing data. If None, the data are just masked.
+ """
+ freq = corelib.fmtFreq(freq)
+ if freq == 'U':
+ raise ValueError,\
+ "Unable to define a proper date resolution (found %s)." % freq
+ if dates is None:
+ if not isTimeSeries(data):
+ raise InsufficientDateError
+ dates = data._dates
+ freq = dates.freq
+ datad = data._series._data
+ datam = data._series._mask
+# if fill_value is None:
+# fill_value = data._fill_value
+ elif not isinstance(dates, DateArray):
+ dates = DateArray(dates, freq)
+ if isinstance(data, MaskedArray):
+ datad = data._data
+ datam = data._mask
+ else:
+ datad = data
+ datam = nomask
+ dflat = dates.asfreq(freq).ravel()
+ n = len(dflat)
+ if not dflat.has_missing_dates():
+ return time_series(data, dflat)
+
+ # ...and now, fill it ! ......
+ (tstart, tend) = dflat[[0,-1]]
+ newdates = date_array(start_date=tstart, end_date=tend, include_last=True)
+ nsize = newdates.size
+ #.............................
+ # Get the steps between consecutive data.
+ delta = dflat.get_steps()-1
+ gap = delta.nonzero()
+ slcid = numpy.r_[[0,], numpy.arange(1,n)[gap], [n,]]
+ oldslc = numpy.array([slice(i,e) for (i,e) in numpy.broadcast(slcid[:-1],slcid[1:])])
+ addidx = delta[gap].astype(int_).cumsum()
+ newslc = numpy.r_[[oldslc[0]],
+ [slice(i+d,e+d) for (i,e,d) in \
+ numpy.broadcast(slcid[1:-1],slcid[2:],addidx)]
+ ]
+ #.............................
+ # Just a quick check
+ vdflat = numeric.asarray(dflat)
+ vnewdates = numeric.asarray(newdates)
+ for (osl,nsl) in zip(oldslc,newslc):
+ assert numpy.equal(vdflat[osl],vnewdates[nsl]).all(),\
+ "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+ #.............................
+ data = MA.asarray(data)
+ newdatad = numeric.empty(nsize, data.dtype)
+ newdatam = numeric.ones(nsize, bool_)
+# if fill_value is None:
+# if hasattr(data,'fill_value'):
+# fill_value = data.fill_value
+# else:
+# fill_value = MA.default_fill_value(data)
+ #data = data.filled(fill_value)
+ #....
+ if datam is nomask:
+ for (new,old) in zip(newslc,oldslc):
+ newdatad[new] = datad[old]
+ newdatam[new] = False
+ else:
+ for (new,old) in zip(newslc,oldslc):
+ newdatad[new] = datad[old]
+ newdatam[new] = datam[old]
+ newdata = MA.masked_array(newdatad, mask=newdatam, fill_value=fill_value)
+ # Get new shape ..............
+ if data.ndim == 1:
+ nshp = (newdates.size,)
+ else:
+ nshp = tuple([-1,] + list(data.shape[1:]))
+ return time_series(newdata.reshape(nshp), newdates)
+
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ from maskedarray.testutils import assert_equal
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ data = masked_array(numeric.arange(15, dtype=float_), mask=[1,0,0,0,0]*3)
+# btseries = BaseTimeSeries(data._data, dates)
+ tseries = time_series(data, dlist)
+ dseries = numpy.log(tseries)
+ if 1:
+ mlist = ['2005-%02i' % i for i in range(1,13)]
+ mlist += ['2006-%02i' % i for i in range(1,13)]
+ mdata = numpy.arange(24)
+ mser1 = time_series(mdata, mlist, observed='SUMMED')
+ #
+ mlist2 = ['2004-%02i' % i for i in range(1,13)]
+ mlist2 += ['2005-%02i' % i for i in range(1,13)]
+ mser2 = time_series(mdata, mlist2, observed='SUMMED')
+ #
+ today = thisday('m')
+ (malg1,malg2) = aligned(mser1, mser2)
+
+ C = convert(mser2,'A')
+ D = convert(mser2,'A',func=None)
+
+ if 0:
+ dlist = ['2007-01-%02i' % i for i in range(1,16)]
+ dates = date_array(dlist)
+ print "."*50+"\ndata"
+ data = masked_array(numeric.arange(15)-6, mask=[1,0,0,0,0]*3)
+ print "."*50+"\nseries"
+ tseries = time_series(data, dlist)
+
+ if 1:
+ dlist_1 = ['2007-01-%02i' % i for i in range(1,8)]
+ dlist_2 = ['2007-01-%02i' % i for i in numpy.arange(1,28)[::4]]
+ data = masked_array(numeric.arange(7), mask=[1,0,0,0,0,0,0])
+ tseries_1 = time_series(data, dlist_1)
+ tseries_2 = time_series(data, dlist_2)
+ tseries_3 = time_series(data[::-1], dlist_2)
+
+ try:
+ tseries = tseries_1 + tseries_2
+ except TimeSeriesCompatibilityError:
+ print "I knew it!"
+ tseries = tseries_2 + tseries_3
+ assert_equal(tseries._dates, tseries_3._dates)
+ assert_equal(tseries._mask, [1,0,0,0,0,0,1])
+
+ if 1:
+ mser3 = time_series(MA.mr_[malg1._series, 100+malg2._series].reshape(2,-1).T,
+ dates=malg1.dates)
+ data = mser3._series._data
+
Property changes on: trunk/Lib/sandbox/timeseries/tseries.py
___________________________________________________________________
Name: svn:keywords
+ Date
Author
Revision
Id
More information about the Scipy-svn
mailing list