2010-03-18 20 views
4

Nous avons configuré iReport pour générer le graphique suivant:analyse des tendances par incréments de valeur itérative

Les points de données réels sont en bleu, la ligne de tendance est verte. Les problèmes comprennent:

  • Trop de points de données pour la ligne de tendance
  • Ligne de tendance ne suit pas une courbe de Bézier (spline)

La source du problème est avec la classe incrémenteur. L'incrémenteur reçoit les points de données de manière itérative. Il ne semble pas y avoir de moyen d'obtenir l'ensemble des données. Le code qui calcule la ligne de tendance se présente comme suit:

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

Comment décririez-vous créer une représentation plus lisse et plus précise de la ligne de tendance?

Répondre

4

Cela dépend du comportement de l'article que vous mesurez. Est-ce quelque chose qui bouge (ou change) d'une manière qui peut être modélisée?

Si l'élément ne devrait pas changer, alors votre tendance devrait être la valeur moyenne sous-jacente de l'ensemble de l'échantillon entier, pas seulement les deux dernières mesures. Vous pouvez l'obtenir en utilisant le théorème de Bayes. La moyenne mobile est calculée en utilisant incrémentielle la formule simple

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

où x est la mesure à l'instant t + 1, NCM1 est la moyenne d'une temps t + 1, Mtn est la moyenne à l'instant t, et N est le nombre de mesures prises par le temps t.

Si l'élément que vous mesurez fluctue d'une manière qui peut être prédite par une équation sous-jacente, vous pouvez utiliser un Kalman filter pour obtenir une meilleure estimation du point suivant en fonction des mesures (récentes) précédentes et de l'équation modélise le comportement prévu. Pour commencer, l'entrée Wikipédia sur Bayesian estimators et Kalman Filters sera utile.

1

image résultante

Le résultat est encore incomplet, mais il montre clairement une meilleure ligne de tendance que dans la question.

Calcul

Il y avait deux éléments clés manquants:

  • fenêtre coulissante. Un List de Double valeurs qui ne peuvent pas dépasser une taille donnée.
  • Calcul.Une variante de l'accepter réponse (un de moins appel à getIterations()):

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

Source Code

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