数学!近似均值,不存储整个数据集

时间:2011-01-09 23:24:26

标签: sql math average

明显(但价格昂贵)的解决方案:

我想在这样的表中存储音轨(1-10)的等级:

TrackID
Vote

然后一个简单的

SELECT AVERAGE(Vote) FROM `table` where `TrackID` = some_val

计算平均值。

但是,我担心这方面的可扩展性,特别是因为每次都需要重新计算。

建议,但可能是愚蠢的解决方案:

TrackID
Rating
NumberOfVotes

每次有人投票时,Rating都会更新

new_rating = ((old_rating * NumberOfVotes) + vote) / (NumberOfVotes + 1)

并存储为TrackID的新Rating值。现在每当需要Rating时,它都是简单的查找,而不是计算。

显然,这并不计算平均值。我尝试了一些小数据集,它近似于均值。我相信它会随着数据集的增加而收敛?但我担心它可能会分歧!

你们觉得怎么样?谢谢!

5 个答案:

答案 0 :(得分:8)

假设您具有无限的数字精度,该计算会正确更新均值。在实践中,您可能正在使用整数类型,因此它并不准确。

如何存储累积投票数和投票数? (即total=total+votenumVotes=numVotes+1)。这样,您可以通过将一个除以另一个来获得精确的平均值。

如果您获得的票数超出您所使用的数据类型的范围,则此方法只会中断。所以使用一个大数据类型(32位应该足够了,除非你期待约40亿票)!

答案 1 :(得分:3)

在您的表格中存储TrackIdRatingSumNumberOfVotes

每次有人投票,

  • NumberOfVotes = NumberOfVotes + 1
  • RatingsSum = RatingsSum + [用户提供的评级]

然后选择

SELECT TrackId, RatingsSum / NumberOfVotes FROM ...

答案 2 :(得分:2)

您的解决方案完全合法。并且差值仅为从完整源集计算的值的浮点精度的几倍。

答案 3 :(得分:2)

你可以在不掌握所有要点的情况下计算出运行平均值和标准偏差。您只需要累积总和,平方和和点数。

这不是近似值;平均值和标准差是准确的。

这是一个演示的Java类。您可以根据需要调整SQL解决方案:

package statistics;

public class StatsUtils
{
    private double sum;
    private double sumOfSquares;
    private long numPoints;

    public StatsUtils()
    {
        this.init();
    }

    private void init()
    {
        this.sum = 0.0;
        this.sumOfSquares = 0.0;
        this.numPoints = 0L;
    }

    public void addValue(double value)
    {
        // Check for overflow in either number of points or sum of squares; reset if overflow is detected
        if ((this.numPoints == Long.MAX_VALUE) || (this.sumOfSquares > (Double.MAX_VALUE-value*value)))
        {
            this.init();
        }

        this.sum += value;
        this.sumOfSquares += value*value;
        ++this.numPoints;
    }

    public double getMean()
    {
        double mean = 0.0;

        if (this.numPoints > 0)
        {
            mean = this.sum/this.numPoints;
        }

        return mean;
    }

    public double getStandardDeviation()
    {
        double standardDeviation = 0.0;

        if (this.numPoints > 1)
        {
            standardDeviation = Math.sqrt((this.sumOfSquares - this.sum*this.sum/this.numPoints)/(this.numPoints-1L));
        }

        return standardDeviation;
    }

    public long getNumPoints() { return this.numPoints; }
}

答案 4 :(得分:1)

您的解决方案的小改进。你有桌子:

TrackID
SumOfVotes
NumberOfVotes

当有人投票时,

NumberOfVotes = NumberOfVotes + 1
SumOfVotes = SumOfVotes + ThisVote

并且看到平均值,然后才进行划分:

SELECT TrackID, (SumOfVotes/NumberOfVotes) AS Rating FROM `table` 

我想补充一点,原始(明显且昂贵)的解决方案与计算平均值时提供的解决方案相比只是昂贵的。 添加,删除或更改投票时更便宜。 我想原来的表

TrackID
Vote
VoterID
仍然需要在提供的解决方案中使用

来跟踪每个选民的投票(评级)。因此,必须为此表中的每个更改(插入,删除或投票更新)更新两个表。

换句话说,原始解决方案可能是最好的方法。