Trending using iterative values

We configured iReport to create the following chart:

HqsBl.png

Real data points are in blue, the trend line is green. The problems include:

  • Too many data points for the trend line.
  • The trend line does not match the Bezier curve (spline)

The source of the problem is the incrementer class. Increment is provided with data points iteratively. There seems to be no way to get the data set. The code that calculates the trend line is as follows:

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 ) ); } } 

How would you create a smoother and more accurate display of the trend line?

+4
source share
2 answers

It depends on the behavior of the object that you are measuring. Is it something that moves (or changes) in a way that can be modeled?

If the element is not changed, then your trend should be the main average value of the entire set of patterns, and not just the last two measurements. You can get this using Bayes theorem. The average value can be calculated using a simple formula

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

where x is the measurement at time t + 1, Mtn1 is the average time t + 1, Mtn is the average at time t, and N is the number of measurements taken by time t.

If the element you are measuring changes in a way that can be predicted by some basic equation, then you can use the Kalman filter to provide the best estimate of the next point based on previous (recent) measurements and an equation that simulates predicted behavior.

As a starting point, a Wikipedia entry on Bayesian estimates and Kalman filters will be useful.

+4
source

Resulting image

The result is still incomplete, but it clearly shows a better trend line than in the question.

bpCpY.png

Payment

Two key components were missing:

  • Sliding window. A List of Double values ​​that cannot grow beyond a given size.
  • Payment

    . Answer option for receiving (one call less than getIterations() ):

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

Source

 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; } } 
+1
source

Source: https://habr.com/ru/post/1304506/


All Articles