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