001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------
028: * MovingAverage.java
029: * ------------------
030: * (C) Copyright 2003-2005, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): Benoit Xhenseval;
034: *
035: * $Id: MovingAverage.java,v 1.5.2.1 2005/10/25 21:35:24 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 28-Jan-2003 : Version 1 (DG);
040: * 10-Mar-2003 : Added createPointMovingAverage() method contributed by Benoit
041: * Xhenseval (DG);
042: * 01-Aug-2003 : Added new method for TimeSeriesCollection, and fixed bug in
043: * XYDataset method (DG);
044: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
045: * getYValue() (DG);
046: * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
047: * release (DG);
048: *
049: */
050:
051: package org.jfree.data.time;
052:
053: import org.jfree.data.xy.XYDataset;
054: import org.jfree.data.xy.XYSeries;
055: import org.jfree.data.xy.XYSeriesCollection;
056:
057: /**
058: * A utility class for calculating moving averages of time series data.
059: */
060: public class MovingAverage {
061:
062: /**
063: * Creates a new {@link TimeSeriesCollection} containing a moving average
064: * series for each series in the source collection.
065: *
066: * @param source the source collection.
067: * @param suffix the suffix added to each source series name to create the
068: * corresponding moving average series name.
069: * @param periodCount the number of periods in the moving average
070: * calculation.
071: * @param skip the number of initial periods to skip.
072: *
073: * @return A collection of moving average time series.
074: */
075: public static TimeSeriesCollection createMovingAverage(
076: TimeSeriesCollection source, String suffix,
077: int periodCount, int skip) {
078:
079: // check arguments
080: if (source == null) {
081: throw new IllegalArgumentException(
082: "MovingAverage.createMovingAverage() : null source.");
083: }
084:
085: if (periodCount < 1) {
086: throw new IllegalArgumentException(
087: "periodCount must be greater than or equal to 1.");
088: }
089:
090: TimeSeriesCollection result = new TimeSeriesCollection();
091:
092: for (int i = 0; i < source.getSeriesCount(); i++) {
093: TimeSeries sourceSeries = source.getSeries(i);
094: TimeSeries maSeries = createMovingAverage(sourceSeries,
095: sourceSeries.getKey() + suffix, periodCount, skip);
096: result.addSeries(maSeries);
097: }
098:
099: return result;
100:
101: }
102:
103: /**
104: * Creates a new {@link TimeSeries} containing moving average values for
105: * the given series. If the series is empty (contains zero items), the
106: * result is an empty series.
107: *
108: * @param source the source series.
109: * @param name the name of the new series.
110: * @param periodCount the number of periods used in the average
111: * calculation.
112: * @param skip the number of initial periods to skip.
113: *
114: * @return The moving average series.
115: */
116: public static TimeSeries createMovingAverage(TimeSeries source,
117: String name, int periodCount, int skip) {
118:
119: // check arguments
120: if (source == null) {
121: throw new IllegalArgumentException("Null source.");
122: }
123:
124: if (periodCount < 1) {
125: throw new IllegalArgumentException(
126: "periodCount must be greater than or equal to 1.");
127:
128: }
129:
130: TimeSeries result = new TimeSeries(name, source
131: .getTimePeriodClass());
132:
133: if (source.getItemCount() > 0) {
134:
135: // if the initial averaging period is to be excluded, then
136: // calculate the index of the
137: // first data item to have an average calculated...
138: long firstSerial = source.getDataItem(0).getPeriod()
139: .getSerialIndex()
140: + skip;
141:
142: for (int i = source.getItemCount() - 1; i >= 0; i--) {
143:
144: // get the current data item...
145: TimeSeriesDataItem current = source.getDataItem(i);
146: RegularTimePeriod period = current.getPeriod();
147: long serial = period.getSerialIndex();
148:
149: if (serial >= firstSerial) {
150: // work out the average for the earlier values...
151: int n = 0;
152: double sum = 0.0;
153: long serialLimit = period.getSerialIndex()
154: - periodCount;
155: int offset = 0;
156: boolean finished = false;
157:
158: while ((offset < periodCount) && (!finished)) {
159: if ((i - offset) >= 0) {
160: TimeSeriesDataItem item = source
161: .getDataItem(i - offset);
162: RegularTimePeriod p = item.getPeriod();
163: Number v = item.getValue();
164: long currentIndex = p.getSerialIndex();
165: if (currentIndex > serialLimit) {
166: if (v != null) {
167: sum = sum + v.doubleValue();
168: n = n + 1;
169: }
170: } else {
171: finished = true;
172: }
173: }
174: offset = offset + 1;
175: }
176: if (n > 0) {
177: result.add(period, sum / n);
178: } else {
179: result.add(period, null);
180: }
181: }
182:
183: }
184: }
185:
186: return result;
187:
188: }
189:
190: /**
191: * Creates a new {@link TimeSeries} containing moving average values for
192: * the given series, calculated by number of points (irrespective of the
193: * 'age' of those points). If the series is empty (contains zero items),
194: * the result is an empty series.
195: * <p>
196: * Developed by Benoit Xhenseval (www.ObjectLab.co.uk).
197: *
198: * @param source the source series.
199: * @param name the name of the new series.
200: * @param pointCount the number of POINTS used in the average calculation
201: * (not periods!)
202: *
203: * @return The moving average series.
204: */
205: public static TimeSeries createPointMovingAverage(
206: TimeSeries source, String name, int pointCount) {
207:
208: // check arguments
209: if (source == null) {
210: throw new IllegalArgumentException("Null 'source'.");
211: }
212:
213: if (pointCount < 2) {
214: throw new IllegalArgumentException(
215: "periodCount must be greater than or equal to 2.");
216: }
217:
218: TimeSeries result = new TimeSeries(name, source
219: .getTimePeriodClass());
220: double rollingSumForPeriod = 0.0;
221: for (int i = 0; i < source.getItemCount(); i++) {
222: // get the current data item...
223: TimeSeriesDataItem current = source.getDataItem(i);
224: RegularTimePeriod period = current.getPeriod();
225: rollingSumForPeriod += current.getValue().doubleValue();
226:
227: if (i > pointCount - 1) {
228: // remove the point i-periodCount out of the rolling sum.
229: TimeSeriesDataItem startOfMovingAvg = source
230: .getDataItem(i - pointCount);
231: rollingSumForPeriod -= startOfMovingAvg.getValue()
232: .doubleValue();
233: result.add(period, rollingSumForPeriod / pointCount);
234: } else if (i == pointCount - 1) {
235: result.add(period, rollingSumForPeriod / pointCount);
236: }
237: }
238: return result;
239: }
240:
241: /**
242: * Creates a new {@link XYDataset} containing the moving averages of each
243: * series in the <code>source</code> dataset.
244: *
245: * @param source the source dataset.
246: * @param suffix the string to append to source series names to create
247: * target series names.
248: * @param period the averaging period.
249: * @param skip the length of the initial skip period.
250: *
251: * @return The dataset.
252: */
253: public static XYDataset createMovingAverage(XYDataset source,
254: String suffix, long period, final long skip) {
255:
256: return createMovingAverage(source, suffix, (double) period,
257: (double) skip);
258:
259: }
260:
261: /**
262: * Creates a new {@link XYDataset} containing the moving averages of each
263: * series in the <code>source</code> dataset.
264: *
265: * @param source the source dataset.
266: * @param suffix the string to append to source series names to create
267: * target series names.
268: * @param period the averaging period.
269: * @param skip the length of the initial skip period.
270: *
271: * @return The dataset.
272: */
273: public static XYDataset createMovingAverage(XYDataset source,
274: String suffix, double period, double skip) {
275:
276: // check arguments
277: if (source == null) {
278: throw new IllegalArgumentException(
279: "Null source (XYDataset).");
280: }
281:
282: XYSeriesCollection result = new XYSeriesCollection();
283:
284: for (int i = 0; i < source.getSeriesCount(); i++) {
285: XYSeries s = createMovingAverage(source, i, source
286: .getSeriesKey(i)
287: + suffix, period, skip);
288: result.addSeries(s);
289: }
290:
291: return result;
292:
293: }
294:
295: /**
296: * Creates a new {@link XYSeries} containing the moving averages of one
297: * series in the <code>source</code> dataset.
298: *
299: * @param source the source dataset.
300: * @param series the series index (zero based).
301: * @param name the name for the new series.
302: * @param period the averaging period.
303: * @param skip the length of the initial skip period.
304: *
305: * @return The dataset.
306: */
307: public static XYSeries createMovingAverage(XYDataset source,
308: int series, String name, double period, double skip) {
309:
310: // check arguments
311: if (source == null) {
312: throw new IllegalArgumentException(
313: "Null source (XYDataset).");
314: }
315:
316: if (period < Double.MIN_VALUE) {
317: throw new IllegalArgumentException(
318: "period must be positive.");
319:
320: }
321:
322: if (skip < 0.0) {
323: throw new IllegalArgumentException("skip must be >= 0.0.");
324:
325: }
326:
327: XYSeries result = new XYSeries(name);
328:
329: if (source.getItemCount(series) > 0) {
330:
331: // if the initial averaging period is to be excluded, then
332: // calculate the lowest x-value to have an average calculated...
333: double first = source.getXValue(series, 0) + skip;
334:
335: for (int i = source.getItemCount(series) - 1; i >= 0; i--) {
336:
337: // get the current data item...
338: double x = source.getXValue(series, i);
339:
340: if (x >= first) {
341: // work out the average for the earlier values...
342: int n = 0;
343: double sum = 0.0;
344: double limit = x - period;
345: int offset = 0;
346: boolean finished = false;
347:
348: while (!finished) {
349: if ((i - offset) >= 0) {
350: double xx = source.getXValue(series, i
351: - offset);
352: Number yy = source.getY(series, i - offset);
353: if (xx > limit) {
354: if (yy != null) {
355: sum = sum + yy.doubleValue();
356: n = n + 1;
357: }
358: } else {
359: finished = true;
360: }
361: } else {
362: finished = true;
363: }
364: offset = offset + 1;
365: }
366: if (n > 0) {
367: result.add(x, sum / n);
368: } else {
369: result.add(x, null);
370: }
371: }
372:
373: }
374: }
375:
376: return result;
377:
378: }
379:
380: }
|