Недавно мне нужно вычислить среднее и стандартное отклонение большого числа (около 800 000 000) двойных чисел. Учитывая, что удвоение занимает 8 байтов, если все удвоения считываются в оперативную память, это займет около 6 ГБ. Я думаю, что могу использовать подход «разделяй и властвуй» с C ++ или другими языками высокого уровня, но это кажется утомительным. Есть ли способ, которым я могу сделать это все сразу на языках высокого уровня, таких как R, Scilab или Octave? Благодарю.
Похоже, вы могли бы использовать R-Grid или Hadoop с хорошим преимуществом.
Вы, конечно, понимаете, что легко рассчитать среднее и стандартное отклонение без необходимости считывать все значения в память. Просто держите промежуточный итог, как этот класс Java. Все, что вам нужно, это общая сумма, общая сумма квадратов и количество очков. Я держу мин и макс бесплатно.
Это также дает понять, как будет работать map-Reduce. Вы бы создали несколько экземпляров статистики, чтобы каждый из них сохранял сумму, сумму квадратов и количество баллов для своей части 800M баллов. Затем позвольте шагу сокращения объединить их и использовать те же формулы, чтобы получить конечный результат.
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
/**
* Statistics accumulates simple statistics for a given quantity "on the fly" - no array needed.
* Resets back to zero when adding a value will overflow the sum of squares.
* @author mduffy
* @since 9/19/12 8:16 AM
*/
public class Statistics {
private String quantityName;
private int numValues;
private double x;
private double xsq;
private double xmin;
private double xmax;
/**
* Constructor
*/
public Statistics() {
this(null);
}
/**
* Constructor
* @param quantityName to describe the quantity (e.g. "heap size")
*/
public Statistics(String quantityName) {
this.quantityName = (StringUtils.isBlank(quantityName) ? "x" : quantityName);
this.reset();
}
/**
* Reset the object in the event of overflow by the sum of squares
*/
public synchronized void reset() {
this.numValues = 0;
this.x = 0.0;
this.xsq = 0.0;
this.xmin = Double.MAX_VALUE;
this.xmax = -Double.MAX_VALUE;
}
/**
* Add a List of values
* @param values to add to the statistics
*/
public synchronized void addAll(Collection<Double> values) {
for (Double value : values) {
add(value);
}
}
/**
* Add an array of values
* @param values to add to the statistics
*/
public synchronized void allAll(double [] values) {
for (double value : values) {
add(value);
}
}
/**
* Add a value to current statistics
* @param value to add for this quantity
*/
public synchronized void add(double value) {
double vsq = value*value;
++this.numValues;
this.x += value;
this.xsq += vsq; // TODO: how to detect overflow in Java?
if (value < this.xmin) {
this.xmin = value;
}
if (value > this.xmax) {
this.xmax = value;
}
}
/**
* Get the current value of the mean or average
* @return mean or average if one or more values have been added or zero for no values added
*/
public synchronized double getMean() {
double mean = 0.0;
if (this.numValues > 0) {
mean = this.x/this.numValues;
}
return mean;
}
/**
* Get the current min value
* @return current min value or Double.MAX_VALUE if no values added
*/
public synchronized double getMin() {
return this.xmin;
}
/**
* Get the current max value
* @return current max value or Double.MIN_VALUE if no values added
*/
public synchronized double getMax() {
return this.xmax;
}
/**
* Get the current standard deviation
* @return standard deviation for (N-1) dof or zero if one or fewer values added
*/
public synchronized double getStdDev() {
double stdDev = 0.0;
if (this.numValues > 1) {
stdDev = Math.sqrt((this.xsq-this.x*this.x/this.numValues)/(this.numValues-1));
}
return stdDev;
}
/**
* Get the current number of values added
* @return current number of values added or zero if overflow condition is encountered
*/
public synchronized int getNumValues() {
return this.numValues;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Statistics");
sb.append("{quantityName='").append(quantityName).append('\'');
sb.append(", numValues=").append(numValues);
sb.append(", xmin=").append(xmin);
sb.append(", mean=").append(this.getMean());
sb.append(", std dev=").append(this.getStdDev());
sb.append(", xmax=").append(xmax);
sb.append('}');
return sb.toString();
}
}
И вот тест JUnit, чтобы доказать, что он работает:
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* StatisticsTest
* @author mduffy
* @since 9/19/12 11:21 AM
*/
public class StatisticsTest {
public static final double TOLERANCE = 1.0e-4;
@Test
public void testAddAll() {
// The test uses a full array, but it's obvious that you could read them from a file one at a time and process until you're done.
List<Double> values = Arrays.asList( 2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0 );
Statistics stats = new Statistics();
stats.addAll(values);
Assert.assertEquals(8, stats.getNumValues());
Assert.assertEquals(2.0, stats.getMin(), TOLERANCE);
Assert.assertEquals(9.0, stats.getMax(), TOLERANCE);
Assert.assertEquals(5.0, stats.getMean(), TOLERANCE);
Assert.assertEquals(2.138089935299395, stats.getStdDev(), TOLERANCE);
}
}
Не утверждая, что это оптимально, но в python (с модулями numpy и Numberxpr) легко выполнить следующее (на 8G RAM):
import numpy, numpy as np, numexpr
x = np.random.uniform(0, 1, size=8e8)
print x.mean(), (numexpr.evaluate('sum(x*x)')/len(x)-
(numexpr.evaluate('sum(x)')/len(x))**2)**.5
>>> 0.499991593345 0.288682001731
Это не потребляет больше памяти, чем исходный массив.
Это похоже на хороший вызов, разве вы не можете создать что-то похожее с измененной сортировкой? Просто идея. Однако это похоже на динамическое программирование, вы можете использовать несколько компьютеров, чтобы сделать вещи быстрее.