001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, 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 if (source == null) { 078 throw new IllegalArgumentException("Null 'source' argument."); 079 } 080 if (periodCount < 1) { 081 throw new IllegalArgumentException("periodCount must be greater " 082 + "than or equal to 1."); 083 } 084 085 TimeSeriesCollection result = new TimeSeriesCollection(); 086 for (int i = 0; i < source.getSeriesCount(); i++) { 087 TimeSeries sourceSeries = source.getSeries(i); 088 TimeSeries maSeries = createMovingAverage(sourceSeries, 089 sourceSeries.getKey() + suffix, periodCount, skip); 090 result.addSeries(maSeries); 091 } 092 return result; 093 094 } 095 096 /** 097 * Creates a new {@link TimeSeries} containing moving average values for 098 * the given series. If the series is empty (contains zero items), the 099 * result is an empty series. 100 * 101 * @param source the source series. 102 * @param name the name of the new series. 103 * @param periodCount the number of periods used in the average 104 * calculation. 105 * @param skip the number of initial periods to skip. 106 * 107 * @return The moving average series. 108 */ 109 public static TimeSeries createMovingAverage(TimeSeries source, 110 String name, int periodCount, int skip) { 111 112 if (source == null) { 113 throw new IllegalArgumentException("Null source."); 114 } 115 if (periodCount < 1) { 116 throw new IllegalArgumentException("periodCount must be greater " + 117 "than or equal to 1."); 118 119 } 120 121 TimeSeries result = new TimeSeries(name); 122 123 if (source.getItemCount() > 0) { 124 125 // if the initial averaging period is to be excluded, then 126 // calculate the index of the 127 // first data item to have an average calculated... 128 long firstSerial 129 = source.getDataItem(0).getPeriod().getSerialIndex() + skip; 130 131 for (int i = source.getItemCount() - 1; i >= 0; i--) { 132 133 // get the current data item... 134 TimeSeriesDataItem current = source.getDataItem(i); 135 RegularTimePeriod period = current.getPeriod(); 136 long serial = period.getSerialIndex(); 137 138 if (serial >= firstSerial) { 139 // work out the average for the earlier values... 140 int n = 0; 141 double sum = 0.0; 142 long serialLimit = period.getSerialIndex() - periodCount; 143 int offset = 0; 144 boolean finished = false; 145 146 while ((offset < periodCount) && (!finished)) { 147 if ((i - offset) >= 0) { 148 TimeSeriesDataItem item = source.getDataItem( 149 i - offset); 150 RegularTimePeriod p = item.getPeriod(); 151 Number v = item.getValue(); 152 long currentIndex = p.getSerialIndex(); 153 if (currentIndex > serialLimit) { 154 if (v != null) { 155 sum = sum + v.doubleValue(); 156 n = n + 1; 157 } 158 } 159 else { 160 finished = true; 161 } 162 } 163 offset = offset + 1; 164 } 165 if (n > 0) { 166 result.add(period, sum / n); 167 } 168 else { 169 result.add(period, null); 170 } 171 } 172 173 } 174 } 175 176 return result; 177 178 } 179 180 /** 181 * Creates a new {@link TimeSeries} containing moving average values for 182 * the given series, calculated by number of points (irrespective of the 183 * 'age' of those points). If the series is empty (contains zero items), 184 * the result is an empty series. 185 * <p> 186 * Developed by Benoit Xhenseval (www.ObjectLab.co.uk). 187 * 188 * @param source the source series. 189 * @param name the name of the new series. 190 * @param pointCount the number of POINTS used in the average calculation 191 * (not periods!) 192 * 193 * @return The moving average series. 194 */ 195 public static TimeSeries createPointMovingAverage(TimeSeries source, 196 String name, int pointCount) { 197 198 if (source == null) { 199 throw new IllegalArgumentException("Null 'source'."); 200 } 201 if (pointCount < 2) { 202 throw new IllegalArgumentException("periodCount must be greater " + 203 "than or equal to 2."); 204 } 205 206 TimeSeries result = new TimeSeries(name); 207 double rollingSumForPeriod = 0.0; 208 for (int i = 0; i < source.getItemCount(); i++) { 209 // get the current data item... 210 TimeSeriesDataItem current = source.getDataItem(i); 211 RegularTimePeriod period = current.getPeriod(); 212 rollingSumForPeriod += current.getValue().doubleValue(); 213 214 if (i > pointCount - 1) { 215 // remove the point i-periodCount out of the rolling sum. 216 TimeSeriesDataItem startOfMovingAvg = source.getDataItem( 217 i - pointCount); 218 rollingSumForPeriod -= startOfMovingAvg.getValue() 219 .doubleValue(); 220 result.add(period, rollingSumForPeriod / pointCount); 221 } 222 else if (i == pointCount - 1) { 223 result.add(period, rollingSumForPeriod / pointCount); 224 } 225 } 226 return result; 227 } 228 229 /** 230 * Creates a new {@link XYDataset} containing the moving averages of each 231 * series in the <code>source</code> dataset. 232 * 233 * @param source the source dataset. 234 * @param suffix the string to append to source series names to create 235 * target series names. 236 * @param period the averaging period. 237 * @param skip the length of the initial skip period. 238 * 239 * @return The dataset. 240 */ 241 public static XYDataset createMovingAverage(XYDataset source, String suffix, 242 long period, long skip) { 243 244 return createMovingAverage(source, suffix, (double) period, 245 (double) skip); 246 247 } 248 249 250 /** 251 * Creates a new {@link XYDataset} containing the moving averages of each 252 * series in the <code>source</code> dataset. 253 * 254 * @param source the source dataset. 255 * @param suffix the string to append to source series names to create 256 * target series names. 257 * @param period the averaging period. 258 * @param skip the length of the initial skip period. 259 * 260 * @return The dataset. 261 */ 262 public static XYDataset createMovingAverage(XYDataset source, 263 String suffix, double period, double skip) { 264 265 if (source == null) { 266 throw new IllegalArgumentException("Null source (XYDataset)."); 267 } 268 269 XYSeriesCollection result = new XYSeriesCollection(); 270 271 for (int i = 0; i < source.getSeriesCount(); i++) { 272 XYSeries s = createMovingAverage(source, i, source.getSeriesKey(i) 273 + suffix, period, skip); 274 result.addSeries(s); 275 } 276 277 return result; 278 279 } 280 281 /** 282 * Creates a new {@link XYSeries} containing the moving averages of one 283 * series in the <code>source</code> dataset. 284 * 285 * @param source the source dataset. 286 * @param series the series index (zero based). 287 * @param name the name for the new series. 288 * @param period the averaging period. 289 * @param skip the length of the initial skip period. 290 * 291 * @return The dataset. 292 */ 293 public static XYSeries createMovingAverage(XYDataset source, 294 int series, String name, double period, double skip) { 295 296 if (source == null) { 297 throw new IllegalArgumentException("Null source (XYDataset)."); 298 } 299 if (period < Double.MIN_VALUE) { 300 throw new IllegalArgumentException("period must be positive."); 301 } 302 if (skip < 0.0) { 303 throw new IllegalArgumentException("skip must be >= 0.0."); 304 305 } 306 307 XYSeries result = new XYSeries(name); 308 309 if (source.getItemCount(series) > 0) { 310 311 // if the initial averaging period is to be excluded, then 312 // calculate the lowest x-value to have an average calculated... 313 double first = source.getXValue(series, 0) + skip; 314 315 for (int i = source.getItemCount(series) - 1; i >= 0; i--) { 316 317 // get the current data item... 318 double x = source.getXValue(series, i); 319 320 if (x >= first) { 321 // work out the average for the earlier values... 322 int n = 0; 323 double sum = 0.0; 324 double limit = x - period; 325 int offset = 0; 326 boolean finished = false; 327 328 while (!finished) { 329 if ((i - offset) >= 0) { 330 double xx = source.getXValue(series, i - offset); 331 Number yy = source.getY(series, i - offset); 332 if (xx > limit) { 333 if (yy != null) { 334 sum = sum + yy.doubleValue(); 335 n = n + 1; 336 } 337 } 338 else { 339 finished = true; 340 } 341 } 342 else { 343 finished = true; 344 } 345 offset = offset + 1; 346 } 347 if (n > 0) { 348 result.add(x, sum / n); 349 } 350 else { 351 result.add(x, null); 352 } 353 } 354 355 } 356 } 357 358 return result; 359 360 } 361 362 }