在 SQL 中实现威尔逊评分

Posted

技术标签:

【中文标题】在 SQL 中实现威尔逊评分【英文标题】:Implementing Wilson Score in SQL 【发布时间】:2010-12-23 18:18:50 【问题描述】:

我们有一个相对较小的表,我们希望使用Wilson interval 或合理的等效值根据评级对它进行排序。我是一个相当聪明的人,但我的数学能力远不足以理解这一点:

有人告诉我,上面的公式计算了正/负(大拇指向上/大拇指向下)投票系统的分数。我从来没有上过统计学课程,而且我已经有 15 年没有学过任何高等数学了。我不知道 p 戴的小帽子是什么意思,也不知道 z 下面的耶稣鱼是什么意思。

我想知道两件事:

    能否更改此公式以适应 5 星评级系统?我找到了this,但作者对其公式的准确性表示怀疑。

    如何在 SQL 函数中表达这个公式?请注意,我不需要实时计算和排序。分数可以每天计算和缓存。

    我是否忽略了 Microsoft SQL Server 的内置内容?

【问题讨论】:

p hat = 您对随机变量 p 的估计。\n 向后 jesus fish = alpha,这是您的显着性截止值 +1 表示“向后的耶稣鱼”,好像鱼有方向 【参考方案1】:

Wilson 分数实际上并不是一个很好的按等级对项目进行排序的方法。这肯定比仅按平均评分排序要好,但它仍然存在很多问题。例如,一个有 1 条差评(其质量仍然非常不确定)的商品将被排在有 10 条差评和 1 条好评(我们可以相当肯定是质量差)的商品下方。

我建议改用 SteamDB rating formula 的改编版(由 Reddit 用户 /u/tornmandate 提供)。除了比威尔逊评分更适合这类事情(原因在链接文章中解释)之外,它还可以比威尔逊更容易地适应 5 星评级系统。

原始 SteamDB 公式:

( Total Reviews = Positive Reviews + Negative Reviews )
( Review Score = fracPositive ReviewsTotal Reviews )
( Rating = Review Score - (Review Score - 0.5)*2^-log_10(Total Reviews + 1) )

5 星版本(请注意从 0.5(50% 得分,赞成/反对票)到 2.5(50% 得分,5 星评级)的变化):

( Total Reviews = total count of all reviews )
( Review Score = mean star rating of all reviews )
( Rating = Review Score - (Review Score - 2.5)*2^-log_10(Total Reviews + 1) )

这个公式也更容易被非数学家理解,并且很容易翻译成代码。

【讨论】:

【参考方案2】:

而不是试图操纵威尔逊的算法来做一个 5 星评级系统。你为什么不研究一个不同的算法?这是 imdb 用于前 250 名的内容:Bayesian Estimate

关于威尔逊算法中的数学解释,下面是您第一篇文章中的链接。它是用 Ruby 编写的。

require 'statistics2'

def ci_lower_bound(pos, n, power)
    if n == 0
        return 0
    end
    z = Statistics2.pnormaldist(1-power/2)
    phat = 1.0*pos/n
    (phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
end

如果您想要另一个示例,请使用 php 中的示例: http://www.derivante.com/2009/09/01/php-content-rating-confidence/

编辑:derivante.com 似乎不再存在。您可以在archive.org - https://web.archive.org/web/20121018032822/http://derivante.com/2009/09/01/php-content-rating-confidence/ 上查看原始文章,我已经添加了下面文章中的代码。

class Rating

  public static function ratingAverage($positive, $total, $power = '0.05')
  
    if ($total == 0)
      return 0;
    $z = Rating::pnormaldist(1-$power/2,0,1);
    $p = 1.0 * $positive / $total;
    $s = ($p + $z*$z/(2*$total) - $z * sqrt(($p*(1-$p)+$z*$z/(4*$total))/$total))/(1+$z*$z/$total);
    return $s;
  
  public static function pnormaldist($qn)
  
    $b = array(
      1.570796288, 0.03706987906, -0.8364353589e-3,
      -0.2250947176e-3, 0.6841218299e-5, 0.5824238515e-5,
      -0.104527497e-5, 0.8360937017e-7, -0.3231081277e-8,
      0.3657763036e-10, 0.6936233982e-12);
    if ($qn < 0.0 || 1.0 < $qn)
      return 0.0;
    if ($qn == 0.5)
      return 0.0;
    $w1 = $qn;
    if ($qn > 0.5)
      $w1 = 1.0 - $w1;
    $w3 = - log(4.0 * $w1 * (1.0 - $w1));
    $w1 = $b[0];
    for ($i = 1;$i <= 10; $i++)
      $w1 += $b[$i] * pow($w3,$i);
    if ($qn > 0.5)
      return sqrt($w1 * $w3);
    return - sqrt($w1 * $w3);
  

至于在 SQL 中执行此操作,SQL 的库中已经包含所有这些数学函数。如果我是你,我会在你的应用程序中这样做。让您的应用程序每隔一段时间(几小时?几天?)更新您的数据库,而不是即时执行此操作,否则您的应用程序将变得非常缓慢。

【讨论】:

不是phat = 1.0*pos/n === phat = pos/n? 不,将pos 乘以1.0 会将其转换为浮点数,从而使除法变为浮点除法。 @AlfonsoFernandez-Ocampo 我已经用 archive.org 链接和文章中的原始代码更新了我的帖子。【参考方案3】:

我已将 Oracle PL/SQL 实现上传到https://github.com/mattgrogan/stats_wilson_score

create or replace function stats_wilson_score(

/*****************************************************************************************************************

Author      : Matthew Grogan
Website     : https://github.com/mattgrogan
Name        : stats_wilson_score.sql 
Description : Oracle PL/SQL function to return the Wilson Score Interval for the given proportion. 
Citation    : Wilson E.B. J Am Stat Assoc 1927, 22, 209-212

Example:
  select 
    round(29 / 250, 4) point_estimate, 
    stats_wilson_score(29, 250, 0.10, 'LCL') lcl, 
    stats_wilson_score(29, 250, 0.10, 'UCL') ucl
  from dual;

******************************************************************************************************************/

  x integer,  -- Number of successes
  m integer,  -- Number of trials
  alpha number default 0.95,  -- Probability of a Type I error
  return_value varchar2 default 'LCL' -- LCL = Lower control limit, UCL = upper control limit
)

return number is

  z float(10);
  phat float(10)  := 0.0;
  lcl float(10)   := 0.0;
  ucl float(10)   := 0.0;

begin

  if m = 0 then
    return(0);
  end if;

  case alpha
    when 0.10 then z := 1.644854;
    when 0.05 then z := 1.959964;
    when 0.01 then z := 2.575829;
    else return(null); -- No Z value for this alpha
  end case;

  phat := x/m;

  lcl := (phat + z*z/(2*m) - z * sqrt( (phat * (1-phat) ) / m + z * z / (4 * (m * m)) ) ) / (1 + z * z / m);
  ucl := (phat + z*z/(2*m) + z * sqrt((phat*(1-phat)+z*z/(4*m))/m))/(1+z*z/m);

  case return_value
    when 'LCL' then return(lcl);
    when 'UCL' then return(ucl);
    else return(null);
  end case;

end;
/
grant execute on stats_wilson_score to public;

【讨论】:

【参考方案4】:

first link 的作者最近在他的帖子中添加了一个 SQL 实现。

这里是:

SELECT widget_id, ((positive + 1.9208) / (positive + negative) - 
               1.96 * SQRT((positive * negative) / (positive + negative) + 0.9604) / 
                      (positive + negative)) / (1 + 3.8416 / (positive + negative)) 
   AS ci_lower_bound FROM widgets WHERE positive + negative > 0 
   ORDER BY ci_lower_bound DESC;

这是否可以适应 5 星评级系统也超出了我的范围。

【讨论】:

【参考方案5】:

关于您的第一个问题(将公式调整为 5 星系统),我同意 Paul Creasey 的观点。

转换公式:[3 +/- i 星 -> i 赞成/反对票](3 星 -> 0)

示例:4 星 -> +1 赞成票,5 星 -> +2、1 -> -2 等等。

我会注意到,虽然不是 ruby​​ 和 php 函数计算的区间的下限,我只计算更简单的 wilson 中点:

(x + (z^2)/2) / (n + z^2)

在哪里: n = Sum(up_votes) + Sum(|down_votes|) x = (赞成票)/n = Sum(up_votes) / n z = 1.96(固定值)

【讨论】:

【参考方案6】:

将 Williams 链接到 php 解决方案 http://www.derivante.com/2009/09/01/php-content-rating-confidence/ 并使您的系统具有正负(5 星可能是 2 正,1 开始可能是 2 负),然后将其转换为相当容易T-SQL,但最好在服务器端逻辑中执行。

【讨论】:

补充一下保罗所说的,我会在你的表中创建一个“分数”字段,然后有一个 cron 任务每 X 小时(或几天)更新一次数据库中的分数。动态进行这种计算对任何应用程序的性能都是非常不利的。 同意。我打算每天计算一次值,然后根据缓存的值进行排序。

以上是关于在 SQL 中实现威尔逊评分的主要内容,如果未能解决你的问题,请参考以下文章

如何在新的导航抽屉架构组件中实现共享应用或评分应用或注销功能

在推荐系统的皮尔逊相关用户-用户相似度矩阵中,NaN 是如何处理的?

在 SQL 中平均电影评分

SQL从另一个表中选择平均评分

android进度条滑动条和评分控件

威尔逊分数区间可能的结果范围