2010-09-25 22 views
16

Tengo una situación en la que necesito procesar 5000 muestras de un dispositivo cada 0.5 segundos.¿Hay una función en java para obtener una media móvil?

Digamos que el tamaño de la ventana es 100, luego habría 50 puntos como resultado de la media móvil. Estoy intentando con el método convencional, es decir, con bucles. Pero esta es una forma muy ineficiente de hacerlo. Alguna sugerencia ?

+4

Ya leyó http://en.wikipedia.org/wiki/Moving_average, especialmente "Media móvil ponderada" –

+0

Una media móvil ponderada es mucho más simple de calcular, mucho más rápida y, en muchos casos, un valor más útil para calcular . es decir, se usa para modelar muchos sistemas. –

Respuesta

8

Puede hacerlo en O (1): mantenga una cola de las últimas 50 entradas. Cuando agrega una entrada y la cola tiene 50 elementos más cortos, simplemente actualice el total y el recuento. Si es más de 50 elementos, actualice también el total y el recuento. Pseudocódigo:

add(double x) { 
    total += x; 
    addToQueue(x); 
    if (queueSize > 50) { 
     total -= removeLastFromQueue(); 
    } else { 
     count++; 
    } 
} 
double getAverage() { 
    return total/count; 
} 
23

Mira la biblioteca Apache Maths. Esto tiene métodos para hacer exactamente lo que quieres. Consulte DescriptiveStatistics y Mean para obtener más información.

+2

Hola, revisé la biblioteca. ¿Qué método usa la media móvil? Parece que no puedo encontrarlo – Snake

+1

DescriptiveStatistics opcionalmente media móvil – gerardw

18

Aquí hay una manera.

public class Rolling { 

    private int size; 
    private double total = 0d; 
    private int index = 0; 
    private double samples[]; 

    public Rolling(int size) { 
     this.size = size; 
     samples = new double[size]; 
     for (int i = 0; i < size; i++) samples[i] = 0d; 
    } 

    public void add(double x) { 
     total -= samples[index]; 
     samples[index] = x; 
     total += x; 
     if (++index == size) index = 0; // cheaper than modulus 
    } 

    public double getAverage() { 
     return total/size; 
    } 
} 

public class RollingTest extends TestCase { 

    private final static int SIZE = 5; 
    private static final double FULL_SUM = 12.5d; 

    private Rolling r; 

    public void setUp() { 
     r = new Rolling(SIZE); 
    } 

    public void testInitial() { 
     assertEquals(0d, r.getAverage()); 
    } 

    public void testOne() { 
     r.add(3.5d); 
     assertEquals(3.5d/SIZE, r.getAverage()); 
    } 

    public void testFillBuffer() { 
     fillBufferAndTest(); 
    } 

    public void testForceOverWrite() { 
     fillBufferAndTest(); 

     double newVal = SIZE + .5d; 
     r.add(newVal); 
     // get the 'full sum' from fillBufferAndTest(), add the value we just added, 
     // and subtract off the value we anticipate overwriting. 
     assertEquals((FULL_SUM + newVal - .5d)/SIZE, r.getAverage()); 
    } 

    public void testManyValues() { 
     for (int i = 0; i < 1003; i++) r.add((double) i); 
     fillBufferAndTest(); 
    } 


    private void fillBufferAndTest() { 
     // Don't write a zero value so we don't confuse an initialized 
     // buffer element with a data element. 
     for (int i = 0; i < SIZE; i++) r.add(i + .5d); 
     assertEquals(FULL_SUM/SIZE, r.getAverage()); 
    } 
} 
+3

Error: si llamas método add menos que SIZE (especificado en el constructor), obtienes un valor promedio incorrecto. Eso es porque hay una división por TAMAÑO en getAverage(). Probablemente necesitemos otro contador para encargarnos de este caso. – sscarduzio

+0

@sscarduzio buena captura. No lo he intentado. Pero el caso de prueba de TestOne() parece sospechoso. Supongo que podría ser preciso si se supone que los valores en la lista se inicializan a cero. Sin embargo, es un requisito bastante torturado. ;-) –

+2

No es solo incorrecto para conjuntos de valores inferiores a SIZE; este código es inexacto para los últimos valores de TAMAÑO de * cada * conjunto de valores. No utilice. –

4

Aquí es una buena aplicación, utilizando BigDecimal:

import java.math.BigDecimal; 
import java.math.RoundingMode; 
import java.util.LinkedList; 
import java.util.Queue; 

public class MovingAverage { 

    private final Queue<BigDecimal> window = new LinkedList<BigDecimal>(); 
    private final int period; 
    private BigDecimal sum = BigDecimal.ZERO; 

    public MovingAverage(int period) { 
     assert period > 0 : "Period must be a positive integer"; 
     this.period = period; 
    } 

    public void add(BigDecimal num) { 
     sum = sum.add(num); 
     window.add(num); 
     if (window.size() > period) { 
      sum = sum.subtract(window.remove()); 
     } 
    } 

    public BigDecimal getAverage() { 
     if (window.isEmpty()) return BigDecimal.ZERO; // technically the average is undefined 
     BigDecimal divisor = BigDecimal.valueOf(window.size()); 
     return sum.divide(divisor, 2, RoundingMode.HALF_UP); 
    } 
} 
4

Por lo que yo sé, no existe tal función (clase) en Java. Pero puedes hacer uno solo. Aquí está un ejemplo simple (SMA-media móvil simple):

public class MovingAverage { 
    private int [] window; 
    private int n, insert; 
    private long sum; 

    public MovingAverage(int size) { 
     window = new int[size]; 
     insert = 0; 
     sum = 0; 
    } 

    public double next(int val) { 
     if (n < window.length) n++; 
     sum -= window[insert]; 
     sum += val; 
     window[insert] = val; 
     insert = (insert + 1) % window.length; 
     return (double)sum/n; 
    } 
} 
0
static int[] myIntArray = new int[16]; 
public static double maf(double number) 
{ 
    double avgnumber=0; 
    for(int i=0; i<15; i++) 
    { 
     myIntArray[i] = myIntArray[i+1]; 
    } 
    myIntArray[15]= (int) number; 
    /* Doing Average */ 
    for(int i=0; i<16; i++) 
    { 
     avgnumber=avgnumber+ myIntArray[i]; 
    } 
    return avgnumber/16; 

} 

este algoritmo también puede ser llamado como filtro de media móvil que está funcionando bien para mí ... He implementado esto algo en mi proyecto gráfico !

Cuestiones relacionadas