CE-881 - Update aggregates to include dates, plus product, variance, and standard deviation

This commit is contained in:
2024-04-01 08:54:32 -05:00
parent 5384eb9927
commit 782a07b176
9 changed files with 828 additions and 35 deletions

View File

@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -73,7 +75,9 @@ import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.InstantAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.LocalDateAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.LongAggregates;
@ -103,11 +107,11 @@ public class GenerateReportAction
// Aggregates: (count:47;sum:10,000;max:2,000;min:15) //
// salesSummaryReport > [(state:MO),(city:St.Louis)] > salePrice > (count:47;sum:10,000;max:2,000;min:15) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?>>>> summaryAggregates = new HashMap<>();
Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?>>>> varianceAggregates = new HashMap<>();
Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>>> summaryAggregates = new HashMap<>();
Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>>> varianceAggregates = new HashMap<>();
Map<String, AggregatesInterface<?>> totalAggregates = new HashMap<>();
Map<String, AggregatesInterface<?>> varianceTotalAggregates = new HashMap<>();
Map<String, AggregatesInterface<?, ?>> totalAggregates = new HashMap<>();
Map<String, AggregatesInterface<?, ?>> varianceTotalAggregates = new HashMap<>();
private ExportStreamerInterface reportStreamer;
private List<QReportDataSource> dataSources;
@ -546,9 +550,9 @@ public class GenerateReportAction
/*******************************************************************************
**
*******************************************************************************/
private void addRecordsToSummaryAggregates(QReportView view, QTableMetaData table, List<QRecord> records, Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?>>>> aggregatesMap)
private void addRecordsToSummaryAggregates(QReportView view, QTableMetaData table, List<QRecord> records, Map<String, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>>> aggregatesMap)
{
Map<SummaryKey, Map<String, AggregatesInterface<?>>> viewAggregates = aggregatesMap.computeIfAbsent(view.getName(), (name) -> new HashMap<>());
Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates = aggregatesMap.computeIfAbsent(view.getName(), (name) -> new HashMap<>());
for(QRecord record : records)
{
@ -584,9 +588,9 @@ public class GenerateReportAction
/*******************************************************************************
**
*******************************************************************************/
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?>>> viewAggregates, SummaryKey key)
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates, SummaryKey key)
{
Map<String, AggregatesInterface<?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
Map<String, AggregatesInterface<?, ?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
addRecordToAggregatesMap(table, record, keyAggregates);
}
@ -595,29 +599,45 @@ public class GenerateReportAction
/*******************************************************************************
**
*******************************************************************************/
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?>> aggregatesMap)
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?, ?>> aggregatesMap)
{
for(QFieldMetaData field : table.getFields().values())
{
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
{
continue;
}
if(field.getType().equals(QFieldType.INTEGER))
{
@SuppressWarnings("unchecked")
AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
AggregatesInterface<Integer, ?> fieldAggregates = (AggregatesInterface<Integer, ?>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
fieldAggregates.add(record.getValueInteger(field.getName()));
}
else if(field.getType().equals(QFieldType.LONG))
{
@SuppressWarnings("unchecked")
AggregatesInterface<Long> fieldAggregates = (AggregatesInterface<Long>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new LongAggregates());
AggregatesInterface<Long, ?> fieldAggregates = (AggregatesInterface<Long, ?>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new LongAggregates());
fieldAggregates.add(record.getValueLong(field.getName()));
}
else if(field.getType().equals(QFieldType.DECIMAL))
{
@SuppressWarnings("unchecked")
AggregatesInterface<BigDecimal> fieldAggregates = (AggregatesInterface<BigDecimal>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new BigDecimalAggregates());
AggregatesInterface<BigDecimal, ?> fieldAggregates = (AggregatesInterface<BigDecimal, ?>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new BigDecimalAggregates());
fieldAggregates.add(record.getValueBigDecimal(field.getName()));
}
// todo - more types (dates, at least?)
else if(field.getType().equals(QFieldType.DATE_TIME))
{
@SuppressWarnings("unchecked")
AggregatesInterface<Instant, ?> fieldAggregates = (AggregatesInterface<Instant, ?>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new InstantAggregates());
fieldAggregates.add(record.getValueInstant(field.getName()));
}
else if(field.getType().equals(QFieldType.DATE))
{
@SuppressWarnings("unchecked")
AggregatesInterface<LocalDate, ?> fieldAggregates = (AggregatesInterface<LocalDate, ?>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new LocalDateAggregates());
fieldAggregates.add(record.getValueLocalDate(field.getName()));
}
}
}
@ -735,11 +755,11 @@ public class GenerateReportAction
// create summary rows //
/////////////////////////
List<QRecord> summaryRows = new ArrayList<>();
for(Map.Entry<SummaryKey, Map<String, AggregatesInterface<?>>> entry : summaryAggregates.getOrDefault(view.getName(), Collections.emptyMap()).entrySet())
for(Map.Entry<SummaryKey, Map<String, AggregatesInterface<?, ?>>> entry : summaryAggregates.getOrDefault(view.getName(), Collections.emptyMap()).entrySet())
{
SummaryKey summaryKey = entry.getKey();
Map<String, AggregatesInterface<?>> fieldAggregates = entry.getValue();
Map<String, Serializable> summaryValues = getSummaryValuesForInterpreter(fieldAggregates);
SummaryKey summaryKey = entry.getKey();
Map<String, AggregatesInterface<?, ?>> fieldAggregates = entry.getValue();
Map<String, Serializable> summaryValues = getSummaryValuesForInterpreter(fieldAggregates);
variableInterpreter.addValueMap("pivot", summaryValues);
variableInterpreter.addValueMap("summary", summaryValues);
@ -748,9 +768,9 @@ public class GenerateReportAction
if(!varianceAggregates.isEmpty())
{
Map<SummaryKey, Map<String, AggregatesInterface<?>>> varianceMap = varianceAggregates.getOrDefault(view.getName(), Collections.emptyMap());
Map<String, AggregatesInterface<?>> varianceSubMap = varianceMap.getOrDefault(summaryKey, Collections.emptyMap());
Map<String, Serializable> varianceValues = getSummaryValuesForInterpreter(varianceSubMap);
Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> varianceMap = varianceAggregates.getOrDefault(view.getName(), Collections.emptyMap());
Map<String, AggregatesInterface<?, ?>> varianceSubMap = varianceMap.getOrDefault(summaryKey, Collections.emptyMap());
Map<String, Serializable> varianceValues = getSummaryValuesForInterpreter(varianceSubMap);
variableInterpreter.addValueMap("variancePivot", varianceValues);
variableInterpreter.addValueMap("variance", varianceValues);
}
@ -931,18 +951,24 @@ public class GenerateReportAction
/*******************************************************************************
**
*******************************************************************************/
private Map<String, Serializable> getSummaryValuesForInterpreter(Map<String, AggregatesInterface<?>> fieldAggregates)
private Map<String, Serializable> getSummaryValuesForInterpreter(Map<String, AggregatesInterface<?, ?>> fieldAggregates)
{
Map<String, Serializable> summaryValuesForInterpreter = new HashMap<>();
for(Map.Entry<String, AggregatesInterface<?>> subEntry : fieldAggregates.entrySet())
for(Map.Entry<String, AggregatesInterface<?, ?>> subEntry : fieldAggregates.entrySet())
{
String fieldName = subEntry.getKey();
AggregatesInterface<?> aggregates = subEntry.getValue();
String fieldName = subEntry.getKey();
AggregatesInterface<?, ?> aggregates = subEntry.getValue();
summaryValuesForInterpreter.put("sum." + fieldName, aggregates.getSum());
summaryValuesForInterpreter.put("count." + fieldName, aggregates.getCount());
summaryValuesForInterpreter.put("count_nums." + fieldName, aggregates.getCount());
summaryValuesForInterpreter.put("min." + fieldName, aggregates.getMin());
summaryValuesForInterpreter.put("max." + fieldName, aggregates.getMax());
summaryValuesForInterpreter.put("average." + fieldName, aggregates.getAverage());
summaryValuesForInterpreter.put("product." + fieldName, aggregates.getProduct());
summaryValuesForInterpreter.put("var." + fieldName, aggregates.getVariance());
summaryValuesForInterpreter.put("varp." + fieldName, aggregates.getVarP());
summaryValuesForInterpreter.put("std_dev." + fieldName, aggregates.getStandardDeviation());
summaryValuesForInterpreter.put("std_devp." + fieldName, aggregates.getStdDevP());
}
return summaryValuesForInterpreter;
}

View File

@ -29,8 +29,12 @@ import java.math.BigDecimal;
/*******************************************************************************
** Classes that support doing data aggregations (e.g., count, sum, min, max, average).
** Sub-classes should supply the type parameter.
**
** The AVG_T parameter describes the type used for the average getAverage method
** which, e.g, for date types, might be a date, vs. numbers, they'd probably be
** BigDecimal.
*******************************************************************************/
public interface AggregatesInterface<T extends Serializable>
public interface AggregatesInterface<T extends Serializable, AVG_T extends Serializable>
{
/*******************************************************************************
**
@ -60,5 +64,51 @@ public interface AggregatesInterface<T extends Serializable>
/*******************************************************************************
**
*******************************************************************************/
BigDecimal getAverage();
AVG_T getAverage();
/*******************************************************************************
**
*******************************************************************************/
default BigDecimal getProduct()
{
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
default BigDecimal getVariance()
{
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
default BigDecimal getVarP()
{
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
default BigDecimal getStandardDeviation()
{
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
default BigDecimal getStdDevP()
{
return (null);
}
}

View File

@ -28,13 +28,16 @@ import java.math.BigDecimal;
/*******************************************************************************
** BigDecimal version of data aggregator
*******************************************************************************/
public class BigDecimalAggregates implements AggregatesInterface<BigDecimal>
public class BigDecimalAggregates implements AggregatesInterface<BigDecimal, BigDecimal>
{
private int count = 0;
// private Integer countDistinct;
private BigDecimal sum;
private BigDecimal min;
private BigDecimal max;
private BigDecimal product;
private VarianceCalculator varianceCalculator = new VarianceCalculator();
@ -59,6 +62,15 @@ public class BigDecimalAggregates implements AggregatesInterface<BigDecimal>
sum = sum.add(input);
}
if(product == null)
{
product = input;
}
else
{
product = product.multiply(input);
}
if(min == null || input.compareTo(min) < 0)
{
min = input;
@ -68,6 +80,52 @@ public class BigDecimalAggregates implements AggregatesInterface<BigDecimal>
{
max = input;
}
varianceCalculator.updateVariance(input);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVariance()
{
return (varianceCalculator.getVariance());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVarP()
{
return (varianceCalculator.getVarP());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStandardDeviation()
{
return (varianceCalculator.getStandardDeviation());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStdDevP()
{
return (varianceCalculator.getStdDevP());
}
@ -116,6 +174,18 @@ public class BigDecimalAggregates implements AggregatesInterface<BigDecimal>
/*******************************************************************************
** Getter for product
**
*******************************************************************************/
@Override
public BigDecimal getProduct()
{
return product;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,136 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigInteger;
import java.time.Instant;
/*******************************************************************************
** Instant version of data aggregator
*******************************************************************************/
public class InstantAggregates implements AggregatesInterface<Instant, Instant>
{
private int count = 0;
// private Integer countDistinct;
private BigInteger sumMillis = BigInteger.ZERO;
private Instant min;
private Instant max;
/*******************************************************************************
** Add a new value to this aggregate set
*******************************************************************************/
public void add(Instant input)
{
if(input == null)
{
return;
}
count++;
sumMillis = sumMillis.add(new BigInteger(String.valueOf(input.toEpochMilli())));
if(min == null || input.compareTo(min) < 0)
{
min = input;
}
if(max == null || input.compareTo(max) > 0)
{
max = input;
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int getCount()
{
return (count);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Instant getSum()
{
//////////////////////////////////////////
// sum of date-times doesn't make sense //
//////////////////////////////////////////
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Instant getMin()
{
return (min);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Instant getMax()
{
return (max);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Instant getAverage()
{
if(this.count > 0)
{
BigInteger averageMillis = this.sumMillis.divide(new BigInteger(String.valueOf(count)));
if(averageMillis.compareTo(new BigInteger(String.valueOf(Long.MAX_VALUE))) < 0)
{
return (Instant.ofEpochMilli(averageMillis.longValue()));
}
}
return (null);
}
}

View File

@ -28,13 +28,16 @@ import java.math.BigDecimal;
/*******************************************************************************
** Integer version of data aggregator
*******************************************************************************/
public class IntegerAggregates implements AggregatesInterface<Integer>
public class IntegerAggregates implements AggregatesInterface<Integer, BigDecimal>
{
private int count = 0;
private int count = 0;
// private Integer countDistinct;
private Integer sum;
private Integer min;
private Integer max;
private Integer sum;
private Integer min;
private Integer max;
private BigDecimal product;
private VarianceCalculator varianceCalculator = new VarianceCalculator();
@ -48,6 +51,8 @@ public class IntegerAggregates implements AggregatesInterface<Integer>
return;
}
BigDecimal inputBD = new BigDecimal(input);
count++;
if(sum == null)
@ -59,6 +64,15 @@ public class IntegerAggregates implements AggregatesInterface<Integer>
sum = sum + input;
}
if(product == null)
{
product = inputBD;
}
else
{
product = product.multiply(inputBD);
}
if(min == null || input < min)
{
min = input;
@ -68,6 +82,52 @@ public class IntegerAggregates implements AggregatesInterface<Integer>
{
max = input;
}
varianceCalculator.updateVariance(inputBD);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVariance()
{
return (varianceCalculator.getVariance());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVarP()
{
return (varianceCalculator.getVarP());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStandardDeviation()
{
return (varianceCalculator.getStandardDeviation());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStdDevP()
{
return (varianceCalculator.getStdDevP());
}
@ -116,6 +176,18 @@ public class IntegerAggregates implements AggregatesInterface<Integer>
/*******************************************************************************
** Getter for product
**
*******************************************************************************/
@Override
public BigDecimal getProduct()
{
return product;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,136 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigInteger;
import java.time.LocalDate;
/*******************************************************************************
** LocalDate version of data aggregator
*******************************************************************************/
public class LocalDateAggregates implements AggregatesInterface<LocalDate, LocalDate>
{
private int count = 0;
// private Integer countDistinct;
private BigInteger sumMillis = BigInteger.ZERO;
private LocalDate min;
private LocalDate max;
/*******************************************************************************
** Add a new value to this aggregate set
*******************************************************************************/
public void add(LocalDate input)
{
if(input == null)
{
return;
}
count++;
sumMillis = sumMillis.add(new BigInteger(String.valueOf(input.toEpochDay())));
if(min == null || input.compareTo(min) < 0)
{
min = input;
}
if(max == null || input.compareTo(max) > 0)
{
max = input;
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int getCount()
{
return (count);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public LocalDate getSum()
{
//////////////////////////////////////////
// sum of date-times doesn't make sense //
//////////////////////////////////////////
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public LocalDate getMin()
{
return (min);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public LocalDate getMax()
{
return (max);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public LocalDate getAverage()
{
if(this.count > 0)
{
BigInteger averageEpochDay = this.sumMillis.divide(new BigInteger(String.valueOf(count)));
if(averageEpochDay.compareTo(new BigInteger(String.valueOf(Long.MAX_VALUE))) < 0)
{
return (LocalDate.ofEpochDay(averageEpochDay.longValue()));
}
}
return (null);
}
}

View File

@ -28,13 +28,16 @@ import java.math.BigDecimal;
/*******************************************************************************
** Long version of data aggregator
*******************************************************************************/
public class LongAggregates implements AggregatesInterface<Long>
public class LongAggregates implements AggregatesInterface<Long, BigDecimal>
{
private int count = 0;
// private Long countDistinct;
private Long sum;
private Long min;
private Long max;
private BigDecimal product;
private VarianceCalculator varianceCalculator = new VarianceCalculator();
@ -48,6 +51,8 @@ public class LongAggregates implements AggregatesInterface<Long>
return;
}
BigDecimal inputBD = new BigDecimal(input);
count++;
if(sum == null)
@ -59,6 +64,15 @@ public class LongAggregates implements AggregatesInterface<Long>
sum = sum + input;
}
if(product == null)
{
product = inputBD;
}
else
{
product = product.multiply(inputBD);
}
if(min == null || input < min)
{
min = input;
@ -68,6 +82,52 @@ public class LongAggregates implements AggregatesInterface<Long>
{
max = input;
}
varianceCalculator.updateVariance(inputBD);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVariance()
{
return (varianceCalculator.getVariance());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getVarP()
{
return (varianceCalculator.getVarP());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStandardDeviation()
{
return (varianceCalculator.getStandardDeviation());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getStdDevP()
{
return (varianceCalculator.getStdDevP());
}
@ -116,6 +176,18 @@ public class LongAggregates implements AggregatesInterface<Long>
/*******************************************************************************
** Getter for product
**
*******************************************************************************/
@Override
public BigDecimal getProduct()
{
return product;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,117 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigDecimal;
import java.math.RoundingMode;
/*******************************************************************************
** see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
**
*******************************************************************************/
public class VarianceCalculator
{
private int n;
private BigDecimal runningMean = BigDecimal.ZERO;
private BigDecimal m2 = BigDecimal.ZERO;
public static int scaleForVarianceCalculations = 4;
/*******************************************************************************
**
*******************************************************************************/
public void updateVariance(BigDecimal newInput)
{
n++;
BigDecimal delta = newInput.subtract(runningMean);
runningMean = runningMean.add(delta.divide(new BigDecimal(n), scaleForVarianceCalculations, RoundingMode.HALF_UP));
BigDecimal delta2 = newInput.subtract(runningMean);
m2 = m2.add(delta.multiply(delta2));
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getVariance()
{
if(n < 2)
{
return (null);
}
return m2.divide(new BigDecimal(n - 1), scaleForVarianceCalculations, RoundingMode.HALF_UP);
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getVarP()
{
if(n < 2)
{
return (null);
}
return m2.divide(new BigDecimal(n), scaleForVarianceCalculations, RoundingMode.HALF_UP);
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getStandardDeviation()
{
BigDecimal variance = getVariance();
if(variance == null)
{
return (null);
}
return BigDecimal.valueOf(Math.sqrt(variance.doubleValue())).setScale(scaleForVarianceCalculations, RoundingMode.HALF_UP);
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getStdDevP()
{
BigDecimal varP = getVarP();
if(varP == null)
{
return (null);
}
return BigDecimal.valueOf(Math.sqrt(varP.doubleValue())).setScale(scaleForVarianceCalculations, RoundingMode.HALF_UP);
}
}