root/MGET/Branches/Jason/PythonPackage/src/GeoEco/SpatialAnalysis/Temporal.py @ 974

Revision 974, 55.4 KB (checked in by jjr8, 13 months ago)

Fixed/implemented:
* #542: Add a tool for calculating the moon phase
* #543: Binary output from Predict GLM, Predict GAM, etc. should be NoData? where inputs are NoData?
* Added temporal periodicity analysis tool.

Line 
1# SpatialAnalysis/Temporal.py - Classes for temporal analysis.
2#
3# Copyright (C) 2012 Jason J. Roberts
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License (available in the file LICENSE.TXT)
14# for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20import datetime
21import itertools
22import math
23
24from GeoEco.DynamicDocString import DynamicDocString
25from GeoEco.Internationalization import _
26from GeoEco.Logging import Logger
27
28
29class Periodicity(object):
30    __doc__ = DynamicDocString()
31
32    @classmethod
33    def AnalyzeTable(cls, table, dateField, valueField=None, normalizationField=None, aggregationMethod=u'Mean', where=None, binWidth=None, timeUnits=None, yAxisLabel=None, scaleFactor=None, samplingPeriod=None, maxPeriod=400., periodogramMethod=u'Lomb-Scargle', oversamplingFactor=4., outputFile=None, title=None, width=4., dpi=300., dayOfYearPlot=u'Automatic', moonPhasePlot=u'Automatic', timeOfDayPlot=u'Automatic', overwriteExisting=False):
34        cls.__doc__.Obj.ValidateMethodInvocation()
35
36        # Perform additional validation.
37
38        if valueField is None:
39            normalizationField = None
40
41        if normalizationField is not None and aggregationMethod == u'sum':
42            raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
43
44        if binWidth is not None and timeUnits == u'automatic':
45            raise ValueError(_(u'A bin width was specified but the time units were not. This is not allowed. If a bin width is specified, the time units must also be specified.'))
46
47        # Read the data from the table into a dictionary that maps
48        # dates to values for those dates. If the caller's data have
49        # high temporal precision then each date will usually have
50        # just one value. But many datasets are not that precise--they
51        # may have a date with no time component, for example. These
52        # may have more than one value per date. Later, we may need
53        # to average or sum them together, as requested by the caller.
54
55        import numpy
56
57        Logger.Info(_(u'Retrieving records to analyze from %(table)s.') % {u'table': table.DisplayName})
58
59        countsForDates = {}
60        valuesForDates = {}
61        normalizeByForDates = {}
62        minDate = None
63        maxDate = None
64
65        if where is None:
66            cursor = table.OpenSelectCursor(rowCount=table.GetRowCount())     # Provide a value for rowCount, so the ArcGIS progressor will be used.
67        else:
68            cursor = table.OpenSelectCursor(where=where)
69        try:
70            while cursor.NextRow():
71                date = cursor.GetValue(dateField)
72                if date is None:
73                    continue
74
75                if valueField is not None:
76                    value = cursor.GetValue(valueField)
77                    if value is None:
78                        continue
79                    value = float(value)
80               
81                if normalizationField is not None:
82                    normalizeBy = cursor.GetValue(normalizationField)
83                    if normalizeBy is None or normalizeBy == 0:
84                        continue
85                    normalizeBy = float(normalizeBy)
86
87                if date not in countsForDates:
88                    countsForDates[date] = 1
89                    if valueField is not None:
90                        valuesForDates[date] = [value]
91                    if normalizationField is not None:
92                        normalizeByForDates[date] = [normalizeBy]
93                else:
94                    countsForDates[date] += 1
95                    if valueField is not None:
96                        valuesForDates[date].append(value)
97                    if normalizationField is not None:
98                        normalizeByForDates[date].append(normalizeBy)
99
100                if minDate is None or date < minDate:
101                    minDate = date
102                if maxDate is None or date > maxDate:
103                    maxDate = date
104        finally:
105            del cursor
106
107        # Validate that we got some data to analyze.
108
109        if len(countsForDates) <= 0:
110            if where is None:
111                raise ValueError(_(u'There are no records in %(table)s to analyze.') % {u'table': table.DisplayName})
112            raise ValueError(_(u'There were no records in %(table)s selected by the where clause %(where)s') % {u'table': table.DisplayName, u'where': where})
113
114        # Build an ascending list of dates.
115
116        dates = sorted(countsForDates.keys())
117
118        # Determine if the dates have time components.
119
120        datesHaveTimes = False
121        for i in range(1, len(dates)):
122            if dates[i].hour != dates[i-1].hour or dates[i].minute != dates[i-1].minute or dates[i].second != dates[i-1].second or dates[i].microsecond != dates[i-1].microsecond:
123                datesHaveTimes = True
124                break
125
126        # If the caller did not specify a sampling period, choose 1
127        # hour or 1 day, depending on whether or not the dates have
128        # time components.
129
130        if samplingPeriod is None:
131            if datesHaveTimes:
132                samplingPeriod = 1./24
133            else:
134                samplingPeriod = 1.
135
136        # Otherwise (the caller did not specify a sampling period) and
137        # the caller specified a sampling period of less than 1 day
138        # but the dates do not include time components, set the
139        # sampling period to 1 day. (Daily periodicity cannot be
140        # detected without time components, so there is no use
141        # checking for it. Doing so will just produce big spectral
142        # power spikes at 1 day and its harmonics, which will be
143        # confusing to interpret.)
144
145        elif samplingPeriod < 1. and not datesHaveTimes:
146            samplingPeriod = 1.
147            Logger.Warning(_(u'A sampling period of less than 1 day was specified but the dates in %(dn)s do not have time components. Without times, it is not possible to detect daily periodicity and therefore there is no reason to use such a small sampling period. A sampling period of 1 day will be used instead.') % {u'dn': table.DisplayName})
148
149        # Validate that the duration of the data is long enough.
150
151        if minDate == maxDate:
152            raise ValueError(_(u'All of the records have the same date (%(date)s). To analyze temporal periodicity, the records must have some variation in their dates.') % {u'date': minDate.strftime('%c')})
153
154        dataDuration = maxDate - minDate
155        dataDuration = float(dataDuration.days) + dataDuration.seconds/86400. + dataDuration.microseconds/(86400.*1000000.)
156        if dataDuration <= samplingPeriod * 2:
157            raise ValueError(_(u'The sampling period (%(sp)g days) is greater than one-half of the duration of the records (%(dur)g days). In order for a signal to be detected, the duration of the records must be at least twice as long as the sampling period. Please decrease the sampling period or select records that cover a longer duration.') % {u'sp': samplingPeriod, u'dur': dataDuration})
158
159        # If the caller requested the Lomb-Scargle method, calculate
160        # the periodogram using lomb_scargle.py.
161
162        if periodogramMethod.lower() == u'lomb-scargle':
163
164            # Build a list of values for the dates. If the caller did
165            # not provide a value field, then we use the count of
166            # records for the dates. If the caller did provide a value
167            # field then we must aggregate the values that occur on
168            # the same date as requested. Note that, unlike the FFT
169            # method, we do not have to aggregate values from
170            # *different* dates together in order to provide values at
171            # a constant time increment.
172
173            if valueField is None:
174                values = [float(countsForDates[d]) for d in dates]
175            elif aggregationMethod == u'sum':
176                if normalizationField is None:
177                    values = [sum(valuesForDates[d]) for d in dates]
178                else:
179                    raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
180            elif normalizationField is None:
181                values = [sum(valuesForDates[d]) / countsForDates[d] if countsForDates[d] > 0 else 0. for d in dates]
182            else:
183                values = [sum(valuesForDates[d]) / sum(normalizeByForDates[d]) if sum(normalizeByForDates[d]) != 0. else 0. for d in dates]
184
185            # Convert the datetime values to the number of days
186            # elapsed since minDate.
187
188            elapsed = [float((d - minDate).days) + (d - minDate).seconds/86400. + (d - minDate).microseconds/(86400.*1000000.) for d in dates]
189
190            # Calculate the periodogram.
191
192            from GeoEco.AssimilatedModules.lomb_scargle import fasper
193
194            frequencies, powers, Nout, Jmax, Prob = fasper(numpy.array(elapsed), numpy.array(values), oversamplingFactor, 1./samplingPeriod)
195            periods = 1/frequencies
196
197##        # If the caller requested the fast fourier transform method,
198##        # calculate the periodgram using numpy.
199##
200##        elif periodogramMethod.lower() == u'fft':
201##
202##            # TODO: REWRITE THIS CODE. FOR NOW, I'M LEAVING FFT OUT.
203##
204##            # Allocate a list for holding the data at the requested
205##            # sampling frequency, sort the data into these buckets, and
206##            # perform the requested aggregation.
207##
208##            n = int(math.ceil(dataDuration / samplingPeriod))
209##            valueTimeSeries = [[] for i in range(n)]
210##
211##            for i in range(len(dates)):
212##                elapsed = dates[i] - minDate
213##                elapsed = float(elapsed.days) + elapsed.seconds/86400. + elapsed.microseconds/(86400.*1000000.)
214##                valueTimeSeries[int(math.floor(elapsed / samplingPeriod))].append(values[i])
215##
216##            del values, dates
217##           
218##            if aggregationMethod == u'mean':
219##                valueTimeSeries = [v[0] / max(1, v[1]) for v in zip([sum(v) for v in valueTimeSeries], [len(v) for v in valueTimeSeries])]
220##            else:
221##                valueTimeSeries = [sum(v) for v in valueTimeSeries]
222##
223##            # Compute a descrete Fourier Transform for the time series of
224##            # values.
225##
226##            dft = numpy.fft.rfft(valueTimeSeries)[1:]       # Discard the zero-frequency term at index 0
227##
228##            if transform is None:
229##                powers = abs(dft)
230##            elif transform == u'square':
231##                powers = abs(dft)**2
232##            elif transform == u'log':
233##                powers = math.log(abs(dft))
234##            elif transform == u'log10':
235##                powers = math.log10(abs(dft))
236##            else:
237##                raise ValueError(_(u'Unknown transform value.'))
238##
239##            periods = 1. / (numpy.arange(1, len(powers) + 1, dtype='float64')  )
240
241        else:
242            raise NotImplementedError(_(u'"%(method)s" is not a supported method for computing the periodogram.') % {u'method': periodogramMethod})
243
244        # If the caller specified 'Automatic' for any of the radial
245        # plots, determine whether we should plot them, based on the
246        # temporal range and other characteristics of the data.
247
248        if timeOfDayPlot == u'automatic':
249            datesHaveTimes = False
250            for i in range(1, len(dates)):
251                if dates[i].hour != dates[i-1].hour or dates[i].minute != dates[i-1].minute or dates[i].second != dates[i-1].second or dates[i].microsecond != dates[i-1].microsecond:
252                    datesHaveTimes = True
253                    break
254
255        dayOfYearPlot = dayOfYearPlot == u'yes' or dayOfYearPlot == u'automatic' and maxDate - minDate >= datetime.timedelta(335)
256        moonPhasePlot = moonPhasePlot == u'yes' or moonPhasePlot == u'automatic' and maxDate - minDate >= datetime.timedelta(28)
257        timeOfDayPlot = timeOfDayPlot == u'yes' or timeOfDayPlot == u'automatic' and maxDate - minDate >= datetime.timedelta(23./24.) and datesHaveTimes
258
259        # Create the figure. Compute the height from the width and the
260        # number of subplots.
261
262        import matplotlib
263        import matplotlib.pyplot as plt
264        import matplotlib.gridspec as gridspec
265        import matplotlib.lines as lines
266
267        numRows = 2 + int(dayOfYearPlot or moonPhasePlot or timeOfDayPlot)
268        numCols = max(1, int(dayOfYearPlot) + int(moonPhasePlot) + int(timeOfDayPlot))
269
270        if outputFile is None:
271            figsize = (max(6, 3 * numCols), 3 * numRows)
272            dpi = 80
273        else:
274            figsize = (width, width * (float(numRows)/float(numCols)))
275       
276        fig = plt.figure(figsize=figsize, dpi=dpi)
277        try:
278            gs = gridspec.GridSpec(numRows, numCols)
279           
280            # Subplot 1: bar plot of values for dates.
281            # ========================================
282
283            # If the caller did not specify a bin width, select one
284            # automatically. Our goal is to show fine resolution, so
285            # we try to have 400 or 600 bins (we use 600 if the figure
286            # has 3 columns) using a bin width that would be naturally
287            # chosen by a person (e.g. "1 week", "30 days", etc).
288
289            TotalSeconds = lambda dt: dt.days*86400. + dt.seconds + dt.microseconds*0.000001
290
291            if binWidth is None:
292                binWidthNames = [_(u'Hour'),
293                                 _(u'4 Hours'),
294                                 _(u'6 Hours'),
295                                 _(u'12 Hours'),
296                                 _(u'Day'),
297                                 _(u'3 Days'),
298                                 _(u'Week'),
299                                 _(u'14 Days'),
300                                 _(u'30 Days'),
301                                 _(u'90 Days'),
302                                 _(u'Year')]
303
304                binWidthDeltas = [datetime.timedelta(seconds=3600),
305                                  datetime.timedelta(seconds=3600*4),
306                                  datetime.timedelta(seconds=3600*6),
307                                  datetime.timedelta(seconds=3600*12),
308                                  datetime.timedelta(days=1),
309                                  datetime.timedelta(days=3),
310                                  datetime.timedelta(days=7),
311                                  datetime.timedelta(days=14),
312                                  datetime.timedelta(days=30),
313                                  datetime.timedelta(days=90),
314                                  datetime.timedelta(days=365)]
315
316                if numCols <= 2:
317                    desiredBins = 400
318                else:
319                    desiredBins = 600
320
321                numBinsForDeltas = [TotalSeconds(maxDate - minDate) / TotalSeconds(delta) < desiredBins for delta in binWidthDeltas]
322
323                if True not in numBinsForDeltas:
324                    binWidthInSeconds = TotalSeconds(binWidthDeltas[-1])
325                    binWidthName = binWidthNames[-1]
326                else:
327                    binWidthInSeconds = TotalSeconds(binWidthDeltas[numBinsForDeltas.index(True)])
328                    binWidthName = binWidthNames[numBinsForDeltas.index(True)]
329
330            # Otherwise (the caller did specify a bin width), create a
331            # display name for it and compute its width in seconds.
332
333            else:
334                if timeUnits == u'hours':
335                    binWidthInSeconds = TotalSeconds(datetime.timedelta(hours=binWidth))
336                    if binWidthInSeconds == 3600:
337                        binWidthName = 'Hour'
338                    elif binWidthInSeconds > 3600:
339                        binWidthName = '%r Hours' % round((binWidthInSeconds / 3600), 1)
340                    else:
341                        binWidthName = '%r Hour' % round((binWidthInSeconds / 3600), 2)
342                       
343                elif timeUnits == u'days':
344                    binWidthInSeconds = TotalSeconds(datetime.timedelta(binWidth))
345                    if binWidthInSeconds == 86400*365:
346                        binWidthName = 'Year'
347                    elif binWidthInSeconds == 86400*7:
348                        binWidthName = 'Week'
349                    elif binWidthInSeconds == 86400:
350                        binWidthName = 'Day'
351                    elif binWidthInSeconds > 86400:
352                        binWidthName = '%r Days' % round((binWidthInSeconds / 3600), 1)
353                    else:
354                        binWidthName = '%r Day' % round((binWidthInSeconds / 3600), 2)
355
356                else:
357                    raise ValueError(_(u'Programming error in this tool: Unknown timeUnits %r. Please contact the author of this tool for assistance.') % timeUnits)
358
359            # Determine the start date for the first bin. For display
360            # purposes, we want this to be rounded to a whole hour,
361            # day, or year.
362
363            if binWidthName.endswith('Hour') or binWidthName.endswith('Hours'):
364                firstBinStartDate = datetime.datetime(minDate.year, minDate.month, minDate.day, minDate.hour)
365            elif binWidthName.endswith('Year'):
366                firstBinStartDate = datetime.datetime(minDate.year, 1, 1)
367            else:
368                firstBinStartDate = datetime.datetime(minDate.year, minDate.month, minDate.day)
369
370            # Define a function that returns the start date of the bin
371            # that a given date falls within.
372
373            BinStartDate = lambda d: firstBinStartDate + datetime.timedelta(seconds=math.floor(TotalSeconds(d - firstBinStartDate) / binWidthInSeconds) * binWidthInSeconds)
374
375            # Calculate the values of the bins.
376
377            binStartDates = []
378            binValues = []
379
380            for binStartDate, datesInBin in itertools.groupby(dates, BinStartDate):
381                binStartDates.append(binStartDate)
382
383                if valueField is None:
384                    binValues.append(sum([countsForDates[d] for d in datesInBin]))
385
386                elif aggregationMethod == u'sum':
387                    if normalizationField is None:
388                        binValues.append(sum([sum(valuesForDates[d]) for d in datesInBin]))
389                    else:
390                        raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
391
392                else:
393                    datesInBinList = list(datesInBin)                                           # This is necessary because datesInBin is an iterator and we need to access it multiple times
394                    sumOfValues = float(sum([sum(valuesForDates[d]) for d in datesInBinList]))
395
396                    if normalizationField is None:
397                        count = sum([countsForDates[d] for d in datesInBinList])
398                        if count > 0:
399                            binValues.append(sumOfValues / count)
400                        else:
401                            binValues.append(0.)
402
403                    else:
404                        sumOfNormalizeBy = float(sum([sum(normalizeByForDates[d]) for d in datesInBinList]))
405                        if sumOfNormalizeBy > 0:
406                            binValues.append(sumOfValues / sumOfNormalizeBy)
407                        else:
408                            binValues.append(0.)
409
410            # If the caller provided a scale factor, apply it.
411
412            if scaleFactor is not None:
413                binValues = [value * scaleFactor for value in binValues]
414                scaleFactorLabel = u' \xd7 %g' % scaleFactor
415            else:
416                scaleFactorLabel = u''
417
418            # Now create the plot
419
420            matplotlib.rc('xtick', direction='out')
421            matplotlib.rc('ytick', direction='out')
422
423            ax1 = plt.subplot(gs[0, :])
424            ax1.bar(binStartDates, binValues, width=binWidthInSeconds / 86400., color='black', linewidth=0)     # Convert seconds to days, which matplotlib uses as width of bars when x axis uses datetimes
425
426            if yAxisLabel is not None:
427                ax1.set_ylabel(yAxisLabel)
428            elif valueField is None:
429                ax1.set_ylabel(u'Count / ' + binWidthName + scaleFactorLabel)
430            elif aggregationMethod == u'sum':
431                if normalizationField is None:
432                    ax1.set_ylabel(valueField + u' / ' + binWidthName + scaleFactorLabel)
433                else:
434                    raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
435            elif normalizationField is None:
436                ax1.set_ylabel(u'Mean ' + valueField + scaleFactorLabel)
437            else:
438                ax1.set_ylabel(valueField + u' / ' + normalizationField + scaleFactorLabel)
439
440            # Rotate the dates on the x axis to make them easier to
441            # read. This code was adapted from matplotlib's
442            # autofmt_xdate().
443           
444            for label in ax1.get_xticklabels():
445                label.set_ha('right')
446                label.set_rotation(30)
447
448            # Subplot 2: the periodogram.
449            # ===========================
450
451            periodsToPlot = numpy.logical_and(periods >= samplingPeriod * 2, periods <= min(maxPeriod, max(periods)))
452
453            ax2 = plt.subplot(gs[1, :])
454            plt.plot(periods[periodsToPlot], powers[periodsToPlot], 'k')
455            ax2.set_xlabel('Period (days)')
456            ax2.set_ylabel('Spectral Power')
457            #ax2.set_xlim(right=min(maxPeriod, max(periods)))
458
459            matplotlib.rc('xtick', direction='in')
460            matplotlib.rc('ytick', direction='in')
461
462            # Subplot 3: day-of-year radial bar plot.
463            # =======================================
464
465            if dayOfYearPlot:
466
467                # Calculate the 0-indexed day of the year as 0 to 364
468                # (Jan 1 to Dec 31). On leap years, treat both Dec 30
469                # and 31 as day 364.
470
471                daysOfYear = [min(364, int(d.strftime('%j')) - 1) for d in dates]
472
473                # Classify the moon phase values into 73 bins, each 5
474                # days long.
475
476                binForDOY = [int(math.floor(doy / 5.)) for doy in daysOfYear]
477
478                # Compute the mean value for each bin.
479
480                binCount = numpy.zeros(73, int)
481                binSum = numpy.zeros(len(binCount))
482               
483                for i in range(len(values)):
484                    binCount[binForDOY[i]] += 1
485                    binSum[binForDOY[i]] += values[i]
486
487                binMean = numpy.zeros(len(binCount))
488                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
489
490                # Normalize the means.
491
492                binMean /= binSum.sum()
493
494                # Create a polar bar plot.
495
496                ax3 = plt.subplot(gs[2, 0], polar=True)
497                ax3.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
498                ax3.set_yticklabels([])
499
500                # Label the first days of the months.
501
502                ax3.set_theta_zero_location('S')
503                ax3.set_theta_direction(-1)
504
505                ax3.set_thetagrids([(int(datetime.datetime(2001, month, 1).strftime('%j')) - 1)/365. * 360. for month in range(1, 13)],
506                                   ['Jan 1', 'Feb 1', 'Mar 1', 'Apr 1', 'May 1', 'Jun 1', 'Jul 1', 'Aug 1', 'Sep 1', 'Oct 1', 'Nov 1', 'Dec 1']);
507
508                for label in ax3.get_xticklabels():
509                    if label.get_text() == 'Jan 1':
510                        label.set_va('top')
511                    elif label.get_text() in ['Feb 1', 'Mar 1', 'Apr 1', 'May 1', 'Jun 1']:
512                        label.set_ha('right')
513                    elif label.get_text() == 'Jul 1':
514                        label.set_va('bottom')
515                    else:
516                        label.set_ha('left')
517
518            # Subplot 4: moon-phase radial bar plot.
519            # ======================================
520
521            if moonPhasePlot:
522
523                # Calculate the moon phase as a number ranging from 0
524                # to 1, where 0 and 1 are new moon and 0.5 is full.
525
526                from GeoEco.AssimilatedModules.moon import MoonPhase
527
528                mp = [MoonPhase(d).phase for d in dates]
529
530                # Classify the moon phase values into 30 bins, each
531                # just under 1 day long.
532
533                binForMP = [int(math.floor(p * 30.)) for p in mp]
534
535                # Compute the mean value for each bin.
536
537                binCount = numpy.zeros(30, int)
538                binSum = numpy.zeros(len(binCount))
539               
540                for i in range(len(values)):
541                    binCount[binForMP[i]] += 1
542                    binSum[binForMP[i]] += values[i]
543
544                binMean = numpy.zeros(len(binCount))
545                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
546
547                # Normalize the means.
548
549                binMean /= binSum.sum()
550
551                # Create a polar bar plot.
552
553                ax4 = plt.subplot(gs[2, 0 + int(dayOfYearPlot)], polar=True)
554                ax4.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
555                ax4.set_yticklabels([])
556
557                # Label the quadrants with moon phase names.
558
559                ax4.set_theta_zero_location('W')
560                ax4.set_theta_direction(-1)
561               
562                ax4.set_thetagrids([0, 90, 180, 270], ['New\nMoon', 'First Quarter', 'Full\nMoon', 'Last Quarter']);
563
564                for label in ax4.get_xticklabels():
565                    if label.get_text() == 'New\nMoon':
566                        label.set_ha('right')
567                    elif label.get_text() == 'First Quarter':
568                        label.set_va('bottom')
569                    elif label.get_text() == 'Full\nMoon':
570                        label.set_ha('left')
571                    else:
572                        label.set_va('top')
573
574            # Subplot 5: day-of-year radial bar plot.
575            # =======================================
576
577            if timeOfDayPlot:
578
579                # Extract the hours from the datetime instances.
580
581                hours = [d.hour for d in dates]
582
583                # Compute the mean value for each bin.
584
585                binCount = numpy.zeros(24, int)
586                binSum = numpy.zeros(len(binCount))
587               
588                for i in range(len(values)):
589                    binCount[hours[i]] += 1
590                    binSum[hours[i]] += values[i]
591
592                binMean = numpy.zeros(len(binCount))
593                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
594
595                # Normalize the means.
596
597                binMean /= binSum.sum()
598
599                # Create a polar bar plot.
600
601                ax5 = plt.subplot(gs[2, -1], polar=True)
602                ax5.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
603                ax5.set_yticklabels([])
604
605                # Label the first days of the months.
606
607                ax5.set_theta_zero_location('S')
608                ax5.set_theta_direction(-1)
609
610                ax5.set_thetagrids([360 * hour/24. for hour in range(0, 24, 3)],
611                                   ['%02i:00' % hour for hour in range(0, 24, 3)]);
612
613                for label in ax5.get_xticklabels():
614                    if label.get_text() == '00:00':
615                        label.set_va('top')
616                    elif label.get_text() in ['03:00', '06:00', '09:00']:
617                        label.set_ha('right')
618                    elif label.get_text() == '12:00':
619                        label.set_va('bottom')
620                    else:
621                        label.set_ha('left')
622
623            # Use tight layout to allow dates to display properly.
624
625            plt.tight_layout()
626
627            # If the caller provided a plot title, add it. This must
628            # be done after calling plt.tight_layout().
629           
630            if title is not None:
631                plt.subplots_adjust(top=0.93)
632                fig.suptitle(title)
633
634            # If the caller provided an outputFile, save the figure to
635            # it. If not, display the figure interactively.
636
637            if outputFile is not None:
638                plt.savefig(outputFile)
639            else:
640                Logger.Info(_(u'Waiting for the interactive plot window to be closed...'))
641                plt.show()
642               
643        finally:
644            plt.close(fig)
645
646    @classmethod
647    def AnalyzeArcGISTable(cls, table, dateField, valueField=None, normalizationField=None, aggregationMethod=u'Mean', where=None, binWidth=None, timeUnits=None, yAxisLabel=None, scaleFactor=None, samplingPeriod=None, maxPeriod=400., periodogramMethod=u'Lomb-Scargle', oversamplingFactor=4., outputFile=None, title=None, width=4., dpi=300., dayOfYearPlot=u'Automatic', moonPhasePlot=u'Automatic', timeOfDayPlot=u'Automatic', overwriteExisting=False):
648        cls.__doc__.Obj.ValidateMethodInvocation()
649        from GeoEco.Datasets.ArcGIS import ArcGISTable
650        cls.AnalyzeTable(ArcGISTable(table), dateField, valueField, normalizationField, aggregationMethod, where, binWidth, timeUnits, yAxisLabel, scaleFactor, samplingPeriod, maxPeriod, periodogramMethod, oversamplingFactor, outputFile, title, width, dpi, dayOfYearPlot, moonPhasePlot, timeOfDayPlot, overwriteExisting)
651       
652
653###############################################################################
654# Metadata: module
655###############################################################################
656
657from GeoEco.ArcGIS import ArcGISDependency
658from GeoEco.Datasets import Table
659from GeoEco.Dependencies import MatplotlibDependency
660from GeoEco.Metadata import *
661from GeoEco.Types import *
662
663AddModuleMetadata(shortDescription=_(u'Classes for temporal analysis.'))
664
665###############################################################################
666# Metadata: Periodicity class
667###############################################################################
668
669AddClassMetadata(Periodicity,
670    shortDescription=_(u'Methods for analyzing temporal perodicity in time-series data.'),
671    isExposedAsCOMServer=True,
672    comIID=u'{CABE75DC-A0A4-410D-8698-9FEF7085232A}',
673    comCLSID=u'{8D268D4D-320B-4E07-8948-954D90F87544}')
674
675# Public method: Periodicity.AnalyzeTable
676
677AddMethodMetadata(Periodicity.AnalyzeTable,
678    shortDescription=_(u'Creates several plots that show the temporal periodicity of the records in a Table.'),
679    longDescription=_(
680u"""This tool creates several plots that show whether or not the
681records or the value of some field of them exhibit any temporal
682periodicity (i.e., patterns that repeat in time). What is analyzed and
683presented in the plots depends on what fields you select, if any, for
684analysis.
685
686The first plot is a bar plot showing values at different times over
687the temporal extent of the data. If you do not select a Value Field
688for analysis, then this plot will be a simple histogram showing the
689counts of records in a series of equal-duration time bins. If you do
690select a Value Field, then the plot will show the sum or mean of that
691field for each bin (the Aggregation Method parameter specifies whether
692the sum or mean is used). Finally, if you also specify a Normalization
693Field, the plot will show the sum of the Value Field divided by the
694sum of the Normalization Field, for each bin (the Aggregation Method
695parameter is ignored in this case).
696
697The second plot is a periodogram that indicates whether or not a
698repeating signal exists in the data at various temporal periods. The
699second plot is produced by conducting Fourier analysis on the data
700plotted in the first plot. In Fourier analysis, the overall pattern in
701the data is decomposed into a series of sine waves having different
702periods (or frequencies), which accurately reconstruct the overall
703pattern when summed together. The periodogram summarizes the
704collection of sine waves by plotting a point for each one; the x axis
705is the period of the sine wave (the inverse of its frequency) and the
706y axis is its amplitude.
707
708Sine waves with high amplitudes represent strong cyclic patterns
709present in the overall pattern. These appear as peaks in the
710periodogram. In marine ecological data, patterns are often observed in
711relation to solar, lunar, or tidal cycles. In the periodogram, solar
712cycles are indicated by peaks at 365 days (an annual cycle) and at 24
713hours (a diurnal cycle). Lunar cycles are indicated by a peak at 29.5
714days. Tidal cycles are more complicated to observe, and depend on
715tidal patterns in the region of interest.
716
717To interpret this periodogram, look for high spikes at 365 days, 29.5
718days, 1 day, with very low values elsewhere. If you see high spikes at
719one or more of those locations, a solar or lunar cycle is indicated.
720But if those spikes are not very high relative to other periods--if
721the periodogram displays a generally jittery or noisy pattern--then
722solar and lunar cycles may not exist.
723
724In order for Fourier analysis to detect such patterns, the data must
725span at least two cycles. For example, to detect an annual pattern,
726the data must span at least two years. The results will be much more
727reliable if many years of data are available.
728
729To better determine whether annual, lunar, or diurnal patterns exist,
730up to three optional plots will be created. Each of these is a radial
731bar plot, similar to the first bar plot but binning the data according
732to day of the year, phase of the lunar cycle, or hour of the day. If
733temporal cycles exist, the radial plots will show high values at some
734times and low values at others.
735
736By default, all three radial plots will be produced if there are
737sufficient data available, but you can manually enable or disable each
738one."""),
739    isExposedToPythonCallers=True,
740    dependencies=[MatplotlibDependency()])
741
742AddArgumentMetadata(Periodicity.AnalyzeTable, u'cls',
743    typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Periodicity),
744    description=_(u'Periodicity class or instance.'))
745
746AddArgumentMetadata(Periodicity.AnalyzeTable, u'table',
747    typeMetadata=ClassInstanceTypeMetadata(cls=Table),
748    description=_(
749u"""Table to analyze.
750
751The table must contain, at minimum, a date field. It may optionally
752contain an integer or floating point field of values to analyze, and
753another integer or floating point field used to normalize the
754values."""))
755
756AddArgumentMetadata(Periodicity.AnalyzeTable, u'dateField',
757    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'date', u'datetime']),
758    description=_(
759u"""Field containing the dates of the records."""))
760
761AddArgumentMetadata(Periodicity.AnalyzeTable, u'valueField',
762    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32', u'int64', u'uint64', u'float32', u'float64'], canBeNone=True),
763    description=_(
764u"""Field containing the values to analyze.
765
766If this field is not provided, the temporal analysis will be conducted
767only on the dates of the records. The first plot will be a simple
768histogram showing the counts of records for a series of equal-duration
769time bins. The periodogram will be based on a Fourier analysis of the
770counts.
771
772If this field is provided and the Normalization Field is not provided,
773the temporal analysis will be conducted on the values of this field
774for the dates they occur. The first plot will show the sum or mean of
775the values for each time bin (the Aggregation Method parameter
776specifies whether the sum or mean is used). The periodogram will be
777based on a Fourier analysis of the values.
778
779If this field is provided and the Normalization Field is also
780provided, the first plot will show the sum of values of this field
781divided by the sum of the values of the Normalization Field, for each
782time bin (the Aggregation Method parameter is ignored in this case).
783The periodogram will be based on a Fourier analysis of the normalized
784values."""))
785
786AddArgumentMetadata(Periodicity.AnalyzeTable, u'normalizationField',
787    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32', u'int64', u'uint64', u'float32', u'float64'], mustBeDifferentThanArguments=[u'valueField'], canBeNone=True),
788    description=_(
789u"""Field for normalizing the Value Field. Please see the
790documentation for that parameter for more information.
791
792The Normalization Field is ignored if the Value Field is not
793provided."""))
794
795AddArgumentMetadata(Periodicity.AnalyzeTable, u'aggregationMethod',
796    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Mean', u'Sum'], makeLowercase=True),
797    description=_(
798u"""Specifies whether the analysis should use the mean or the sum of
799the Value Field. Please see the documentation for that parameter for
800more information.
801
802If a Value Field is not specified, this parameter is ignored. If a
803Value Field and Normalization Field both are provided, then this
804parameter must be set to Mean."""),
805    arcGISDisplayName=_(u'Aggregation method'))
806
807AddArgumentMetadata(Periodicity.AnalyzeTable, u'where',
808    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
809    description=_(
810u"""SQL WHERE clause expression that specifies the subset of points to
811use. If this parameter is not provided, all of the points will be
812used. The exact syntax of this expression depends on the underlying
813storage format of the input Table."""))
814
815AddArgumentMetadata(Periodicity.AnalyzeTable, u'binWidth',
816    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
817    description=_(
818u"""Width, in time units, of the bins used to produce the first plot.
819If you provide a value for this parmaeter, you must also specify the
820value for the Time Units parameter.
821
822If this parameter is omitted, a value that allows a large number of
823bins will be automatically selected, with the hope that fine temporal
824dynamics may be observable."""),
825    arcGISDisplayName=_(u'Bar plot bin width'),
826    arcGISCategory=_(u'Bar plot options'))
827
828AddArgumentMetadata(Periodicity.AnalyzeTable, u'timeUnits',
829    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Days', u'Hours'], makeLowercase=True, canBeNone=True),
830    description=_(
831u"""Specifies the time units of the Bar Plot Bin Width parameter. If
832you specify a Bar Plot Bin Width, you must also specify the time
833units."""),
834    arcGISDisplayName=_(u'Time units'),
835    arcGISCategory=_(u'Bar plot options'))
836
837AddArgumentMetadata(Periodicity.AnalyzeTable, u'yAxisLabel',
838    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
839    description=_(
840u"""Label for the y axis of the bar plot. If you do not provide a
841label, a generic label will be created for you."""),
842    arcGISDisplayName=_(u'Bar plot y axis label'),
843    arcGISCategory=_(u'Bar plot options'))
844
845AddArgumentMetadata(Periodicity.AnalyzeTable, u'scaleFactor',
846    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
847    description=_(
848u"""Scale factor. If provided, the values will be multiplied by this
849value prior to plotting.
850
851Use this value to scale the y axis values to a preferred range. For
852example, which calculating catch per unit effort (CPUE) for longline
853fishing gear, it is traditional to present catch per 1000 hooks. But
854your Value Field contains the amount of catch and your Normalization
855Field contains the number of hooks, the unscaled result will be catch
856per 1 hook. To plot catch per 1000 hooks, provide 1000 for the Scale
857Factor parameter."""),
858    arcGISDisplayName=_(u'Scale factor'),
859    arcGISCategory=_(u'Bar plot options'))
860
861AddArgumentMetadata(Periodicity.AnalyzeTable, u'samplingPeriod',
862    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
863    description=_(
864u"""Sampling period, in days, for the Fourier analysis used to produce
865the periodogram.
866
867If you do not supply a value, one will be selected for you. If the
868dates in your table include time components, 1 hour will be used. If
869they do not, 1 day will be used.
870
871The sampling period determines the period of cycles that can be
872detected. The shorter the sampling period (i.e. the higher the
873sampling frequency), the shorter the cycles that can be detected, with
874the shortest detectable cycle equal to twice the sampling period.
875
876If you specify a sampling period of less than 1 day but the dates in
877your table do not include time components, or all of the time values
878are the same, a sampling period of 1 day will be used. This is
879because, when all the times are the same, strong periodicity will be
880detected the period of 1 day (because no records will occur at
881fractional days) and the harmonics of 1 day (e.g. 1/2 day, 1/3 day,
8821/4 day, and so on). These strong but spurious cycles will produce
883spikes in the periodogram at those periods, obscuring the cycles of
884true interest that occur at higher periods."""),
885    arcGISDisplayName=_(u'Sampling period'),
886    arcGISCategory=_(u'Periodogram options'))
887
888AddArgumentMetadata(Periodicity.AnalyzeTable, u'maxPeriod',
889    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
890    description=_(
891u"""Maximum period, in days, to plot on the periodogram.
892
893By default, this parameter is set to 400 days, under the observation
894that natural ecological phenomena frequenty exhibit annual periodicity
895(which results in a spike in the periodogram at 365 days) but that
896most users of this tool will either not be interested in or have the
897data to detect cycles having longer periods. If you have sufficient
898data and wish to check for longer cycles (e.g. decadal oscillations),
899increase this parameter."""),
900    arcGISDisplayName=_(u'Maximum period to plot'),
901    arcGISCategory=_(u'Periodogram options'))
902
903AddArgumentMetadata(Periodicity.AnalyzeTable, u'periodogramMethod',
904    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Lomb-Scargle'], makeLowercase=True),
905    description=_(
906u"""Method to use for computing the periodogram. Currently only one
907method is available:
908
909* Lomb-Scargle - the Lomb (1976) and Scargle (1976) method is designed
910  to perform spectral analysis on unevenly sampled data, making it
911  more suitable for analysis of ecological data than the most common
912  method for spectral analysis, the Fast Fourier transform (FFT). This
913  tool uses a Python implementation of Press and Rybicki's (1989)
914  algorithm for the Lomb-Scargle method.
915
916References
917
918Lomb, N (1976). Least-Squares Frequency Analysis of Unequally Spaced
919Data. Astrophysics and Space Science 39: 447-462.
920
921Press, WH and GB Rybicki (1989). Fast algorithm for spectral analysis
922of unevenly sampled data. Astrophysical Journal, Part 1, vol. 338, p. 277-280.
923
924Scargle, J. (1982). Studies in Astronomical Time Series Analysis II.
925Statistical Aspects of Spectral Analysis of Unevenly Spaced Data.
926Astrophysical Journal, Part 1, vol. 263, p. 835-853."""),
927    arcGISDisplayName=_(u'Periodogram method'),
928    arcGISCategory=_(u'Periodogram options'))
929
930AddArgumentMetadata(Periodicity.AnalyzeTable, u'oversamplingFactor',
931    typeMetadata=FloatTypeMetadata(minValue=1., canBeNone=True),
932    description=_(
933u"""Oversampling factor for the Lomb-Scargle method.
934
935In order to produce accurate values for the peaks in the peridogram,
936it is necessary to sample the input data at a higher frequency than is
937necessary to merely detect a short-period cycle. This is called
938oversampling. In practice, an oversampling factor of 4 or higher is
939recommended for the Lomb-Scargle method. (The effective sampling
940period is the Sampling Period parameter divided by the Oversampling
941Factor parameter.)"""),
942    arcGISDisplayName=_(u'Oversampling factor'),
943    arcGISCategory=_(u'Periodogram options'))
944
945AddArgumentMetadata(Periodicity.AnalyzeTable, u'outputFile',
946    typeMetadata=FileTypeMetadata(deleteIfParameterIsTrue=u'overwriteExisting', createParentDirectories=True, canBeNone=True),
947    description=_(
948u"""Output plot file.
949
950If this parameter is not provided (the default), a plot will be
951created in an interactive window.
952
953If this parameter is provided, a plot will be written to the file and
954an interactive window will not be created. The extension of the file
955determines its format and may be:
956
957* .emf - Enhanced Metafile
958* .eps - Encapsulated Postscript
959* .pdf - Portable Document Format
960* .png - Portable Network Graphics
961* .ps - Postscript
962* .raw - Raw RGBA bitmap
963* .rgba - Raw RGBA bitmap
964* .svg - Scalable Vector Graphics
965* .svgz - Scalable Vector Graphics
966"""),
967    direction=u'Output',
968    arcGISDisplayName=_(u'Output plot file'),
969    arcGISCategory=_(u'Other plotting options'))
970
971AddArgumentMetadata(Periodicity.AnalyzeTable, u'title',
972    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
973    description=_(
974u"""Title of the plot, to appear at the very top."""),
975    arcGISDisplayName=_(u'Plot title'),
976    arcGISCategory=_(u'Other plotting options'))
977
978AddArgumentMetadata(Periodicity.AnalyzeTable, u'width',
979    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0.),
980    description=_(
981u"""Width of the output plot file, in inches. This parameter is
982ignored if the plot is not written to an output file."""),
983    arcGISDisplayName=_(u'Plot width'),
984    arcGISCategory=_(u'Other plotting options'))
985
986AddArgumentMetadata(Periodicity.AnalyzeTable, u'dpi',
987    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0.),
988    description=_(
989u"""Dots per inch (DPI) of the output plot file. This parameter is
990ignored if the plot is not written to an output file, and may not
991apply for vector-based output formats."""),
992    arcGISDisplayName=_(u'Plot DPI'),
993    arcGISCategory=_(u'Other plotting options'))
994
995AddArgumentMetadata(Periodicity.AnalyzeTable, u'dayOfYearPlot',
996    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
997    description=_(
998u"""Specifies whether or not a radial bar plot should be generated
999showing the distribution of values according day of the year. If
1000'Automatic' is specified, a plot will be generated if the data span
1001335 days or more.
1002
1003Each bar of the plot encompasses 5 days (the first bin is January 1-5,
1004the second is January 6-10, and so on). On leap years, the last bar
1005encompasses 6 days.
1006
1007The values of the bars are calculated similar to the main bar plot: if
1008a Value Field is not specified, each 5-day bar shows the count of
1009records for those days. If a Value Field is specified but a
1010Normalization Field is not, each bar shows the sum or mean of the
1011values for those days (the Aggregation Method parameter specifies
1012whether the sum or mean is used). If both a Value Field and
1013Normalization Field are specified, each bar shows the sum of the Value
1014Field divided by the sum of the Normalization Field for those
1015days."""),
1016    arcGISDisplayName=_(u'Create day of year plot'),
1017    arcGISCategory=_(u'Other plotting options'))
1018
1019AddArgumentMetadata(Periodicity.AnalyzeTable, u'moonPhasePlot',
1020    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
1021    description=_(
1022u"""Specifies whether or not a radial bar plot should be generated
1023showing the distribution of values according to the phase of the moon
1024calculated from the records' dates. If 'Automatic' is specified, a
1025plot will be generated if the data span 28 days or more.
1026
1027The plot has 30 equal-width bars, each corresponding to about 1 day
1028(the moon phase is actually about 29.53 days long, so each bar is
1029about 29.53/30 days long).
1030
1031The values of the bars are calculated similar to the main bar plot: if
1032a Value Field is not specified, each bar shows the count of records
1033for that fraction of the moon phase. If a Value Field is specified but
1034a Normalization Field is not, each bar shows the sum or mean of the
1035values for that fraction (the Aggregation Method parameter specifies
1036whether the sum or mean is used). If both a Value Field and
1037Normalization Field are specified, each bar shows the sum of the Value
1038Field divided by the sum of the Normalization Field for that
1039fraction."""),
1040    arcGISDisplayName=_(u'Create moon phase plot'),
1041    arcGISCategory=_(u'Other plotting options'))
1042
1043AddArgumentMetadata(Periodicity.AnalyzeTable, u'timeOfDayPlot',
1044    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
1045    description=_(
1046u"""Specifies whether or not a radial bar plot should be generated
1047showing the distribution of values according to the time of the day.
1048If 'Automatic' is specified, a plot will be generated if the data span
104923 hours or more.
1050
1051The plot has 24 hour-long bars.
1052
1053The values of the bars are calculated similar to the main bar plot: if
1054a Value Field is not specified, each bar shows the count of records
1055for that hour of the day. If a Value Field is specified but a
1056Normalization Field is not, each bar shows the sum or mean of the
1057values for that hour (the Aggregation Method parameter specifies
1058whether the sum or mean is used). If both a Value Field and
1059Normalization Field are specified, each bar shows the sum of the Value
1060Field divided by the sum of the Normalization Field for that
1061hour."""),
1062    arcGISDisplayName=_(u'Create time of day plot'),
1063    arcGISCategory=_(u'Other plotting options'))
1064
1065AddArgumentMetadata(Periodicity.AnalyzeTable, u'overwriteExisting',
1066    typeMetadata=BooleanTypeMetadata(),
1067    description=_(
1068u"""If True, the output plot file will be overwritten, if it exists.
1069If False, a ValueError will be raised if the output plot file
1070exists."""))
1071
1072# Public method: Periodicity.AnalyzeArcGISTable
1073
1074AddMethodMetadata(Periodicity.AnalyzeArcGISTable,
1075    shortDescription=_(u'Creates several plots that show the temporal periodicity of the records in a table.'),
1076    longDescription=Periodicity.AnalyzeTable.__doc__.Obj.LongDescription,
1077    isExposedToPythonCallers=True,
1078    dependencies=[ArcGISDependency(9, 1), MatplotlibDependency()])
1079
1080CopyArgumentMetadata(Periodicity.AnalyzeTable, u'cls', Periodicity.AnalyzeArcGISTable, u'cls')
1081
1082AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'table',
1083    typeMetadata=ArcGISTableViewTypeMetadata(mustExist=True),
1084    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[1].Description,
1085    arcGISDisplayName=_(u'Table'))
1086
1087AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'dateField',
1088    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'date']),
1089    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[2].Description,
1090    arcGISDisplayName=_(u'Date field'),
1091    arcGISParameterDependencies=[u'table'])
1092
1093AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'valueField',
1094    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'SHORT', u'LONG', u'FLOAT', u'DOUBLE'], canBeNone=True),
1095    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[3].Description,
1096    arcGISDisplayName=_(u'Value field'),
1097    arcGISParameterDependencies=[u'table'])
1098
1099AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'normalizationField',
1100    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'SHORT', u'LONG', u'FLOAT', u'DOUBLE'], canBeNone=True),
1101    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[4].Description,
1102    arcGISDisplayName=_(u'Normalization field'),
1103    arcGISParameterDependencies=[u'table'])
1104
1105CopyArgumentMetadata(Periodicity.AnalyzeTable, u'aggregationMethod', Periodicity.AnalyzeArcGISTable, u'aggregationMethod')
1106
1107AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'where',
1108    typeMetadata=SQLWhereClauseTypeMetadata(canBeNone=True),
1109    description=_(
1110u"""SQL WHERE clause expression that specifies the subset of rows to
1111use. If this parameter is not provided, all of the rows will be used.
1112
1113The exact syntax of this expression depends on the type of feature
1114class you're using. ESRI recommends you reference fields using the
1115following syntax:
1116
1117* For shapefiles, ArcInfo coverages, or feature classes stored in file
1118  geodatabases, ArcSDE geodatabases, or ArcIMS, enclose field names in
1119  double quotes: "MY_FIELD"
1120
1121* For feature classes stored in personal geodatabases, enclose field
1122  names in square brackets: [MY_FIELD].
1123"""),
1124    arcGISDisplayName=_(u'Where clause'),
1125    arcGISParameterDependencies=[u'table'])
1126
1127CopyArgumentMetadata(Periodicity.AnalyzeTable, u'binWidth', Periodicity.AnalyzeArcGISTable, u'binWidth')
1128CopyArgumentMetadata(Periodicity.AnalyzeTable, u'timeUnits', Periodicity.AnalyzeArcGISTable, u'timeUnits')
1129CopyArgumentMetadata(Periodicity.AnalyzeTable, u'yAxisLabel', Periodicity.AnalyzeArcGISTable, u'yAxisLabel')
1130CopyArgumentMetadata(Periodicity.AnalyzeTable, u'scaleFactor', Periodicity.AnalyzeArcGISTable, u'scaleFactor')
1131CopyArgumentMetadata(Periodicity.AnalyzeTable, u'samplingPeriod', Periodicity.AnalyzeArcGISTable, u'samplingPeriod')
1132CopyArgumentMetadata(Periodicity.AnalyzeTable, u'maxPeriod', Periodicity.AnalyzeArcGISTable, u'maxPeriod')
1133CopyArgumentMetadata(Periodicity.AnalyzeTable, u'periodogramMethod', Periodicity.AnalyzeArcGISTable, u'periodogramMethod')
1134CopyArgumentMetadata(Periodicity.AnalyzeTable, u'oversamplingFactor', Periodicity.AnalyzeArcGISTable, u'oversamplingFactor')
1135CopyArgumentMetadata(Periodicity.AnalyzeTable, u'outputFile', Periodicity.AnalyzeArcGISTable, u'outputFile')
1136CopyArgumentMetadata(Periodicity.AnalyzeTable, u'title', Periodicity.AnalyzeArcGISTable, u'title')
1137CopyArgumentMetadata(Periodicity.AnalyzeTable, u'width', Periodicity.AnalyzeArcGISTable, u'width')
1138CopyArgumentMetadata(Periodicity.AnalyzeTable, u'dpi', Periodicity.AnalyzeArcGISTable, u'dpi')
1139CopyArgumentMetadata(Periodicity.AnalyzeTable, u'dayOfYearPlot', Periodicity.AnalyzeArcGISTable, u'dayOfYearPlot')
1140CopyArgumentMetadata(Periodicity.AnalyzeTable, u'moonPhasePlot', Periodicity.AnalyzeArcGISTable, u'moonPhasePlot')
1141CopyArgumentMetadata(Periodicity.AnalyzeTable, u'timeOfDayPlot', Periodicity.AnalyzeArcGISTable, u'timeOfDayPlot')
1142
1143AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'overwriteExisting',
1144    typeMetadata=BooleanTypeMetadata(),
1145    description=_(
1146u"""If True, the output plot file will be overwritten, if it exists.
1147If False, a ValueError will be raised if the output plot file
1148exists."""),
1149    initializeToArcGISGeoprocessorVariable=u'OverwriteOutput')
1150
1151###############################################################################
1152# Names exported by this module
1153###############################################################################
1154
1155__all__ = ['Periodicity']
Note: See TracBrowser for help on using the browser.