Что эквивалентно функции pnormaldist статистики Ruby в Haskell?

Как видно здесь: http://www.evanmiller.org/how-not-to-sort-by-average-rating.html

вот сам код Ruby, реализованный в Statistics2 библиотека:

# inverse of normal distribution ([2])
# Pr( (-infty, x] ) = qn -> x
def pnormaldist(qn)
  b = [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)
    $stderr.printf("Error : qn <= 0 or qn >= 1  in pnorm()!n")
    return 0.0;
  end
  qn == 0.5 and return 0.0

  w1 = qn
  qn > 0.5 and w1 = 1.0 - w1
  w3 = -Math.log(4.0 * w1 * (1.0 - w1))
  w1 = b[0]
  1.upto 10 do |i|
    w1 += b[i] * w3**i;
  end
  qn > 0.5 and return Math.sqrt(w1 * w3)
  -Math.sqrt(w1 * w3)
end

6 ответов


это довольно просто перевести:

module PNormalDist where

pnormaldist :: (Ord a, Floating a) => a -> Either String a
pnormaldist qn
  | qn < 0 || 1 < qn = Left "Error: qn must be in [0,1]"
  | qn == 0.5        = Right 0.0
  | otherwise        = Right $
      let w3 = negate . log $ 4 * qn * (1 - qn)
          b = [ 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]
          w1 = sum . zipWith (*) b $ iterate (*w3) 1
      in (signum $ qn - 0.5) * sqrt (w1 * w3)

во - первых, давайте посмотрим на ruby-он возвращает значение, но иногда он печатает сообщение об ошибке (при неправильном аргументе). Это не очень haskellish, так пусть наше возвращаемое значение будет Either String a - куда мы вернемся Left String С сообщением об ошибке, если задан неправильный аргумент, и Right a иначе.

теперь мы проверяем два случая вверху:

  • qn < 0 || 1 < qn = Left "Error: qn must be in [0,1]" - это условие ошибки, когда qn вне диапазона.
  • qn == 0.5 = Right 0.0 - это рубиновый чек qn == 0.5 and return * 0.0

далее определим w1 в коде ruby. Но мы переопределить его несколькими строками ниже, что не очень rubyish. Значение, которое мы храним в w1 в первый раз используется сразу в определении w3, так почему бы нам не пропустить хранить его в w1? Нам даже не нужно делать qn > 0.5 and w1 = 1.0 - w1 шаг, потому что мы используем продукт w1 * (1.0 - w1) в определении w3.

Итак, мы пропускаем все это и переходим прямо к определению w3 = negate . log $ 4 * qn * (1 - qn).

Далее идет определение b, который является прямым подъемом из кода ruby (синтаксис ruby для литерала массива-это синтаксис haskell для списка).

вот самый сложный бит-определение конечного значения w3. Какой код в Ruby в

w1 = b[0]
1.upto 10 do |i|
  w1 += b[i] * w3**i;
end

это то, что называется сгибанием набора значений (хранится в массиве ruby) в одно значение. Мы можем повторить это более функционально (но все еще в ruby), используя Array#reduce:

w1 = b.zip(0..10).reduce(0) do |accum, (bval,i)|
  accum + bval * w3^i
end

обратите внимание, как я толкнул b[0] в цикл, используя identity b[0] == b[0] * w3^0.

теперь мы могли бы перенести это непосредственно в Хаскелл, но это немного уродливо

w1 = foldl 0 (\accum (bval,i) -> accum + bval * w3**i) $ zip b [0..10]

вместо этого я разбил его на несколько шагов-во-первых, нам действительно не нужно i, нам просто нужны силы w3 (начиная с w3^0 == 1), так Давайте вычислить те, с iterate (*w3) 1.

затем, вместо того, чтобы застегивать их на пары с элементами b, нам в конечном итоге просто нужны их продукты, поэтому мы можем застегивать их на продукты каждой пары с помощью zipWith (*) b.

теперь наша складывая функция действительно легка-нам как раз нужно суммировать продукты, которые мы можем сделать используя sum.

наконец, мы решаем, возвращать ли плюс или минус sqrt (w1 * w3), согласно ли qn больше или меньше, чем 0.5 (мы уже знаю, что это не равные). Поэтому вместо вычисления квадратного корня в двух отдельных местах, как в коде ruby, Я вычислил его один раз и умножил на +1 или -1 по признаку qn - 0.5 (signum просто возвращает знак стоимостью).


копаясь в Hackage, есть несколько библиотек для статистики:

  • hmatrix-gsl-stats -- чистая привязка к GSL
  • hstatistics -- еще более высокий уровень интерфейса для GSL
  • hstats -- общие статистические методы
  • статистика -- более распространенные статистические методы
  • статистика-linreg -- линейная регрессия между двумя выборки, основанные на другом пакете статистических данных.

вы хотите версию pnormaldist, который " возвращает P-значение normaldist (x)".

возможно, что-то там есть то, что вам нужно?


функция, которую вы хотите, теперь доступна в пакете erf на hackage. Это называется invnormcdf.


вот доверительный интервал оценки моего Уилсона для параметра Бернулли в узле.js

wilson.normaldist = function(qn) {
    var b = [1.570796288, 0.03706987906, -0.0008364353589, -0.0002250947176, 0.000006841218299, 0.000005824238515, -0.00000104527497, 0.00000008360937017, -0.000000003231081277,
        0.00000000003657763036, 0.0000000000006936233982
    ];
    if (qn < 0.0 || 1.0 < qn) return 0;
    if (qn == 0.5) return 0;
    var w1 = qn;
    if (qn > 0.5) w1 = 1.0 - w1;
    var w3 = -Math.log(4.0 * w1 * (1.0 - w1));
    w1 = b[0];

    function loop(i) {
        w1 += b[i] * Math.pow(w3, i);
        if (i < b.length - 1) loop(++i);
    };
    loop(1);
    if (qn > 0.5) return Math.sqrt(w1 * w3);
    else return -Math.sqrt(w1 * w3);
}

wilson.rank = function(up_votes, down_votes) {
    var confidence = 0.95;
    var pos = up_votes;
    var n = up_votes + down_votes;
    if (n == 0) return 0;
    var z = this.normaldist(1 - (1 - confidence) / 2);
    var phat = 1.0 * pos / n;
    return ((phat + z * z / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n)) * 10000;
}

краткий взгляд на hackage ничего не показал, поэтому я предлагаю вам перевести код ruby в Haskell. Все достаточно просто.


код Ruby недокументирован; нет спецификации того, что эта функция должна делать. Как кто-нибудь знает, правильно ли он делает то, что задумано?

Я бы не просто слепо копировал и вставлял эту арифметику из одной реализации в другую (как это сделал автор пакета Ruby).

цитата дается как ([2]) в комментарии, но это болтается. Мы находим его в блоке комментариев собственного кода C в _statistics2.c файл.

/*
   statistics2.c
   distributions of statistics2
   by Shin-ichiro HARA
   2003.09.25
   Ref:
   [1] http://www.matsusaka-u.ac.jp/~okumura/algo/
   [2] http://www5.airnet.ne.jp/tomy/cpro/sslib11.htm
*/

очень небрежная работа, чтобы ссылаться только на исходный код C, откуда коэффициенты были списаны, а не на исходный источник формулы.

на [1] ссылка больше не работает; сервер не найден. К счастью, тот, который мы хотим [2]. Это страница на японском языке с некоторым кодом C для различных функций. Приводятся ссылки. Мы хотим pnorm. В таблице алгоритм приписывается 戸田の近似式, что означает " toda'S Приближение."

Тода-распространенная фамилия в Японии; требуется больше детективной работы, чтобы выяснить, кто это.

после больших усилий, здесь мы идем: бумага (японский):Минимаксное приближение для процентных пунктов стандартного нормального распределения (1993) Хидео Тода и Харуми оно.

алгоритм приписывается Toda (я предполагаю, что тот же самый, который является соавтором статьи), датированный 1967 годом на P. 19.

это кажется довольно неясно; вероятное обоснование использования его в пакете Ruby заключается в том, что он был найден в исходном коде отечественного происхождения со ссылкой на имя отечественного ученого.