package org.araqne.logdb.query.aggregator; import java.util.List; import org.araqne.logdb.Row; import org.araqne.logdb.query.command.NumberUtil; import org.araqne.logdb.query.expr.Expression; public class Covariance extends AbstractAggregationFunction { // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Covariance private Double coMoment; private Double mean1; private Double mean2; private int n; public Covariance(List<Expression> exprs) { super(exprs); n = 0; } @Override public String getName() { return "covar"; } @Override public void apply(Row map) { Expression expr1 = exprs.get(0); Expression expr2 = exprs.get(1); Object obj1 = expr1.eval(map); if (obj1 == null || !(obj1 instanceof Number)) return; Object obj2 = expr2.eval(map); if (obj2 == null | !(obj2 instanceof Number)) return; ++n; Number delta1 = NumberUtil.sub(obj1, mean1); Number delta2 = NumberUtil.sub(obj2, mean2); mean1 = NumberUtil.add(mean1, delta1.doubleValue() / n).doubleValue(); mean2 = NumberUtil.add(mean2, delta2.doubleValue() / n).doubleValue(); coMoment = NumberUtil.add(coMoment, NumberUtil.mul(delta1, delta2).doubleValue() * (n - 1) / n).doubleValue(); } @Override public Object eval() { if (coMoment == null) return null; else return coMoment / n; } @Override public void merge(AggregationFunction func) { Covariance other = (Covariance) func; if (this.coMoment == null) { this.coMoment = other.coMoment; this.mean1 = other.mean1; this.mean2 = other.mean2; this.n = other.n; } else { this.coMoment = this.coMoment + other.coMoment + (this.mean1 - other.mean1) * (this.mean2 - other.mean2) * this.n * other.n / (this.n + other.n); this.n = this.n + other.n; this.mean1 = (this.mean1 * this.n + other.mean1 * other.n) / (this.n + other.n); this.mean2 = (this.mean2 * this.n + other.mean2 * other.n) / (this.n + other.n); } } @Override public Object[] serialize() { Object[] l = new Object[4]; l[0] = coMoment; l[1] = mean1; l[2] = mean2; l[3] = n; return l; } @Override public void deserialize(Object[] values) { this.coMoment = (Double) values[0]; this.mean1 = (Double) values[1]; this.mean2 = (Double) values[2]; this.n = (Integer) values[3]; } @Override public void clean() { this.coMoment = null; this.mean1 = null; this.mean2 = null; this.n = 0; } @Override public AggregationFunction clone() { Covariance cov = new Covariance(exprs); cov.coMoment = this.coMoment; cov.mean1 = this.mean1; cov.mean2 = this.mean2; cov.n = this.n; return cov; } @Override public String toString() { return "covar(" + exprs.get(0) + ", " + exprs.get(1) + ")"; } @Override public boolean canBeDistributed() { return true; } @Override public AggregationFunction mapper(List<Expression> exprs) { return new CovarianceMapper(exprs); } @Override public AggregationFunction reducer(List<Expression> exprs) { return new CovarianceReducer(exprs); } public static class CovarianceMapper extends AbstractAggregationFunction { private Double coMoment; private Double mean1; private Double mean2; private int n; public CovarianceMapper(List<Expression> exprs) { super(exprs); n = 0; } @Override public String getName() { return "covarMapper"; } @Override public void apply(Row map) { Expression expr1 = exprs.get(0); Expression expr2 = exprs.get(1); Object obj1 = expr1.eval(map); if (obj1 == null || !(obj1 instanceof Number)) return; Object obj2 = expr2.eval(map); if (obj2 == null | !(obj2 instanceof Number)) return; ++n; Number delta1 = NumberUtil.sub(obj1, mean1); Number delta2 = NumberUtil.sub(obj2, mean2); mean1 = NumberUtil.add(mean1, delta1.doubleValue() / n).doubleValue(); mean2 = NumberUtil.add(mean2, delta2.doubleValue() / n).doubleValue(); coMoment = NumberUtil.add(coMoment, NumberUtil.mul(delta1, delta2).doubleValue() * (n - 1) / n).doubleValue(); } @Override public Object eval() { Object[] l = new Object[4]; l[0] = coMoment; l[1] = mean1; l[2] = mean2; l[3] = n; return l; } @Override public void merge(AggregationFunction func) { CovarianceMapper other = (CovarianceMapper) func; if (this.coMoment == null) { this.coMoment = other.coMoment; this.mean1 = other.mean1; this.mean2 = other.mean2; this.n = other.n; } else { this.coMoment = this.coMoment + other.coMoment + (this.mean1 - other.mean1) * (this.mean2 - other.mean2) * this.n * other.n / (this.n + other.n); this.mean1 = (this.mean1 * this.n + other.mean1 * other.n) / (this.n + other.n); this.mean2 = (this.mean2 * this.n + other.mean2 * other.n) / (this.n + other.n); this.n = this.n + other.n; } } @Override public Object[] serialize() { Object[] l = new Object[4]; l[0] = coMoment; l[1] = mean1; l[2] = mean2; l[3] = n; return l; } @Override public void deserialize(Object[] values) { this.coMoment = (Double) values[0]; this.mean1 = (Double) values[1]; this.mean2 = (Double) values[2]; this.n = (Integer) values[3]; } @Override public void clean() { this.coMoment = null; this.mean1 = null; this.mean2 = null; this.n = 0; } @Override public AggregationFunction clone() { CovarianceMapper cov = new CovarianceMapper(exprs); cov.coMoment = this.coMoment; cov.mean1 = this.mean1; cov.mean2 = this.mean2; cov.n = this.n; return cov; } public String toString() { return "covarMapper(" + exprs.get(0) + ", " + exprs.get(1) + ")"; } } public static class CovarianceReducer extends AbstractAggregationFunction { private Double coMoment; private Double mean1; private Double mean2; private int n; public CovarianceReducer(List<Expression> exprs) { super(exprs); } @Override public String getName() { return "covarReducer"; } @Override public void apply(Row map) { Expression expr = exprs.get(0); Object obj = expr.eval(map); if (obj == null || !(obj instanceof Object[])) return; Object[] values = (Object[]) obj; Double coMoment = (Double) values[0]; Double mean1 = (Double) values[1]; Double mean2 = (Double) values[2]; Integer n = (Integer) values[3]; if (this.coMoment == null) { this.coMoment = coMoment; this.mean1 = mean1; this.mean2 = mean2; this.n = n; } else { this.coMoment = this.coMoment + coMoment + (this.mean1 - mean1) * (this.mean2 - mean2) * this.n * n / (this.n + n); this.mean1 = (this.mean1 * this.n + mean1 * n) / (this.n + n); this.mean2 = (this.mean2 * this.n + mean2 * n) / (this.n + n); this.n = this.n + n; } } @Override public Object eval() { if (coMoment == null) return null; else return coMoment / n; } @Override public void merge(AggregationFunction func) { CovarianceReducer other = (CovarianceReducer) func; if (this.coMoment == null) { this.coMoment = other.coMoment; this.mean1 = other.mean1; this.mean2 = other.mean2; this.n = other.n; } else { this.coMoment = this.coMoment + other.coMoment + (this.mean1 - other.mean1) * (this.mean2 - other.mean2) * this.n * other.n / (this.n + other.n); this.n = this.n + other.n; this.mean1 = (this.mean1 * this.n + other.mean1 * other.n) / (this.n + other.n); this.mean2 = (this.mean2 * this.n + other.mean2 * other.n) / (this.n + other.n); } } @Override public Object[] serialize() { Object[] l = new Object[4]; l[0] = coMoment; l[1] = mean1; l[2] = mean2; l[3] = n; return l; } @Override public void deserialize(Object[] values) { this.coMoment = (Double) values[0]; this.mean1 = (Double) values[1]; this.mean2 = (Double) values[2]; this.n = (Integer) values[3]; } @Override public void clean() { this.coMoment = null; this.mean1 = null; this.mean2 = null; this.n = 0; } @Override public AggregationFunction clone() { CovarianceReducer cov = new CovarianceReducer(exprs); cov.coMoment = this.coMoment; cov.mean1 = this.mean1; cov.mean2 = this.mean2; cov.n = this.n; return cov; } @Override public String toString() { return "covarReducer(" + exprs.get(0) + ")"; } } }