Анализ трендов с использованием итерационных приращений значений - PullRequest
4 голосов
/ 19 марта 2010

Мы настроили iReport для генерации следующего графика:

image

Реальные точки данных выделены синим цветом, линия тренда - зеленым. Проблемы включают в себя:

  • Слишком много точек данных для линии тренда
  • Линия тренда не следует кривой Безье (сплайн)

Источник проблемы с классом инкремента. Инкрементатор предоставляется с точками данных итеративно. Кажется, нет способа получить набор данных. Код для расчета линии тренда выглядит следующим образом:

import java.math.BigDecimal;
import net.sf.jasperreports.engine.fill.*;

/**
 * Used by an iReport variable to increment its average.
 */
public class MovingAverageIncrementer
  implements JRIncrementer {
  private BigDecimal average;

  private int incr = 0;

  /**
   * Instantiated by the MovingAverageIncrementerFactory class.
   */
  public MovingAverageIncrementer() {
  }

  /**
   * Returns the newly incremented value, which is calculated by averaging
   * the previous value from the previous call to this method.
   * 
   * @param jrFillVariable Unused.
   * @param object New data point to average.
   * @param abstractValueProvider Unused.
   * @return The newly incremented value.
   */
  public Object increment( JRFillVariable jrFillVariable, Object object, 
                           AbstractValueProvider abstractValueProvider ) {
    BigDecimal value = new BigDecimal( ( ( Number )object ).doubleValue() );

    // Average every 10 data points
    //
    if( incr % 10 == 0 ) {
      setAverage( ( value.add( getAverage() ).doubleValue() / 2.0 ) );
    }

    incr++;

    return getAverage();
  }


  /**
   * Changes the value that is the moving average.
   * @param average The new moving average value.
   */
  private void setAverage( BigDecimal average ) {
    this.average = average;
  }

  /**
   * Returns the current moving average average.
   * @return Value used for plotting on a report.
   */
  protected BigDecimal getAverage() {
    if( this.average == null ) {
      this.average = new BigDecimal( 0 );
    }

    return this.average;
  }

  /** Helper method. */    
  private void setAverage( double d ) {
    setAverage( new BigDecimal( d ) );
  }
}

Как бы вы создали более плавное и более точное представление линии тренда?

Ответы [ 2 ]

4 голосов
/ 19 марта 2010

Это зависит от поведения предмета, который вы измеряете. Это что-то, что движется (или изменяется) таким образом, который можно смоделировать?

Если ожидается, что элемент не изменится, тогда ваш тренд должен быть базовым средним значением всего набора выборок, а не только двух последних измерений. Вы можете получить это, используя теорему Байеса. Скользящее среднее можно рассчитать постепенно, используя простую формулу

Mtn1 = (Mtn * N + x) / (N + 1)

, где x - это измерение в момент времени t + 1, Mtn1 - это среднее время t + 1, Mtn - это среднее время в момент t, а N - число измерений, выполненных за время t.

Если измеряемый вами элемент колеблется таким образом, который можно предсказать с помощью некоторого базового уравнения, тогда вы можете использовать фильтр Калмана , чтобы обеспечить наилучшую оценку следующей точки на основе предыдущего ) измерения и уравнение, которое моделирует прогнозируемое поведение.

В качестве отправной точки будет полезна запись в Википедии Байесовские оценки и фильтры Калмана.

1 голос
/ 19 марта 2010

Результирующее изображение

Результат все еще неполон, однако он явно показывает лучшую линию тренда, чем в вопросе.

image

Расчет

Отсутствуют два ключевых компонента:

  • Раздвижное окно. List из Double значений, которые не могут превышать заданный размер.
  • Расчет. Вариант ответа на ответ (на один звонок меньше getIterations()):

    ((value - previousAverage) / (getIterations() + 1)) + previousAverage

Исходный код

import java.math.BigDecimal;

import java.util.ArrayList;
import java.util.List;

import net.sf.jasperreports.engine.fill.AbstractValueProvider;
import net.sf.jasperreports.engine.fill.JRFillVariable;
import net.sf.jasperreports.engine.fill.JRIncrementer;


/**
 * Used by an iReport variable to increment its average.
 */
public class RunningAverageIncrementer
  implements JRIncrementer {
  /** Default number of tallies. */
  private static final int DEFAULT_TALLIES = 128;

  /** Number of tallies within the sliding window. */
  private static final int DEFAULT_SLIDING_WINDOW_SIZE = 30;

  /** Stores a sliding window of values. */
  private List<Double> values = new ArrayList<Double>( DEFAULT_TALLIES );

  /**
   * Instantiated by the RunningAverageIncrementerFactory class.
   */
  public RunningAverageIncrementer() {
  }

  /**
   * Calculates the average of previously known values.
   * @return The average of the list of values returned by getValues().
   */
  private double calculateAverage() {
    double result = 0.0;
    List<Double> values = getValues();

    for( Double d: getValues() ) {
      result += d.doubleValue();
    }

    return result / values.size();
  }

  /**
   * Called each time a new value to be averaged is received.
   * @param value The new value to include for the average.
   */
  private void recordValue( Double value ) {
    List<Double> values = getValues();

    // Throw out old values that should no longer influence the trend.
    //
    if( values.size() > getSlidingWindowSize() ) {
      values.remove( 0 );
    }

    this.values.add( value );
  }

  private List<Double> getValues() {
    return values;
  }

  private int getIterations() {
    return getValues().size();
  }

  /**
   * Returns the newly incremented value, which is calculated by averaging
   * the previous value from the previous call to this method.
   * 
   * @param jrFillVariable Unused.
   * @param tally New data point to average.
   * @param abstractValueProvider Unused.
   * @return The newly incremented value.
   */
  public Object increment( JRFillVariable jrFillVariable, Object tally, 
                           AbstractValueProvider abstractValueProvider ) {
    double value = ((Number)tally).doubleValue();

    recordValue( value );

    double previousAverage = calculateAverage();
    double newAverage = 
      ((value - previousAverage) / (getIterations() + 1)) + previousAverage;

    return new BigDecimal( newAverage );
  }

  protected int getSlidingWindowSize() {
    return DEFAULT_SLIDING_WINDOW_SIZE;
  }
}
...