Hypergeometric distribution

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Raven

    #1

    Hypergeometric distribution

    Hi to all, I need to calculate the hpergeometric distribution:


    choose(r, x) * choose(b, n-x)
    p(x; r,b,n) = -----------------------------
    choose(r+b, n)

    choose(r,x) is the binomial coefficient
    I use the factorial to calculate the above formula but since I am using
    large numbers, the result of choose(a,b) (ie: the binomial coefficient)
    is too big even for large int. I've tried the scipy library, but this
    library calculates
    the hypergeometric using the factorials too, so the problem subsist. Is
    there any other libray or an algorithm to calculate
    the hypergeometric distribution? The statistical package R can handle
    such calculations but I don't want to use python R binding since I want
    a standalone app.
    Thanks a lot
    Ale

  • Robert Kern

    #2
    Re: Hypergeometric distribution

    Raven wrote:[color=blue]
    > Hi to all, I need to calculate the hpergeometric distribution:
    >
    >
    > choose(r, x) * choose(b, n-x)
    > p(x; r,b,n) = -----------------------------
    > choose(r+b, n)
    >
    > choose(r,x) is the binomial coefficient
    > I use the factorial to calculate the above formula but since I am using
    > large numbers, the result of choose(a,b) (ie: the binomial coefficient)
    > is too big even for large int. I've tried the scipy library, but this
    > library calculates
    > the hypergeometric using the factorials too, so the problem subsist. Is
    > there any other libray or an algorithm to calculate
    > the hypergeometric distribution?[/color]

    Use logarithms.

    Specifically,

    from scipy import special

    def logchoose(n, k):
    lgn1 = special.gammaln (n+1)
    lgk1 = special.gammaln (k+1)
    lgnk1 = special.gammaln (n-k+1)
    return lgn1 - (lgnk1 + lgk1)

    def gauss_hypergeom (x, r, b, n):
    return exp(logchoose(r , x) +
    logchoose(b, n-x) -
    logchoose(r+b, n))

    Or you could use gmpy if you need exact rational arithmetic rather than floating
    point.

    --
    Robert Kern
    robert.kern@gma il.com

    "In the fields of hell where the grass grows high
    Are the graves of dreams allowed to die."
    -- Richard Harter

    Comment

    • Gerard Flanagan

      #3
      Re: Hypergeometric distribution

      Raven wrote:
      [color=blue]
      > Hi to all, I need to calculate the hpergeometric distribution:
      >
      >
      > choose(r, x) * choose(b, n-x)
      > p(x; r,b,n) = -----------------------------
      > choose(r+b, n)
      >
      > choose(r,x) is the binomial coefficient
      > I use the factorial to calculate the above formula but since I am using
      > large numbers, the result of choose(a,b) (ie: the binomial coefficient)
      > is too big even for large int. I've tried the scipy library, but this
      > library calculates
      > the hypergeometric using the factorials too, so the problem subsist. Is
      > there any other libray or an algorithm to calculate
      > the hypergeometric distribution? The statistical package R can handle
      > such calculations but I don't want to use python R binding since I want
      > a standalone app.
      > Thanks a lot
      > Ale[/color]

      Ale

      I had this code lying about if it helps. I don't know if it's even
      correct but it's non-recursive!

      def Binomial( n, k ):
      ret = 0
      if k == 0:
      ret = 1
      elif k > 0:
      a = range( n+1 )
      a[0] = 1
      for i in a[1:]:
      a[i] = 1
      for j in range(i-1,0,-1):
      a[j] = a[j] + a[j-1]
      ret = a[k]
      return ret

      Gerard

      Comment

      • Steven D'Aprano

        #4
        Re: Hypergeometric distribution

        On Mon, 26 Dec 2005 12:18:55 -0800, Raven wrote:
        [color=blue]
        > Hi to all, I need to calculate the hpergeometric distribution:
        >
        >
        > choose(r, x) * choose(b, n-x)
        > p(x; r,b,n) = -----------------------------
        > choose(r+b, n)
        >
        > choose(r,x) is the binomial coefficient
        > I use the factorial to calculate the above formula but since I am using
        > large numbers, the result of choose(a,b) (ie: the binomial coefficient)
        > is too big even for large int.[/color]

        Are you sure about that? Python long ints can be as big as you have enough
        memory for. My Python can print 10L**10000 to the console with a barely
        detectable pause, and 10L**100000 with about a ten second delay. (Most of
        that delay is printing it, not calculating it.)

        25206 is the first integer whose factorial exceeds 10L**100000, so even if
        you are calculating the binomial coefficient using the most naive
        algorithm, calculating the factorials and dividing, you should easily be
        able to generate it for a,b up to 20,000 unless you have a severe
        shortage of memory.
        [color=blue]
        > I've tried the scipy library, but this
        > library calculates
        > the hypergeometric using the factorials too, so the problem subsist.[/color]

        What exactly is your problem? What values of hypergeometric( x; r,b,n) fail
        for you?



        --
        Steven.

        Comment

        • Alex Martelli

          #5
          Re: Hypergeometric distribution

          Raven <balckraven@gma il.com> wrote:
          [color=blue]
          > Hi to all, I need to calculate the hpergeometric distribution:
          >
          >
          > choose(r, x) * choose(b, n-x)
          > p(x; r,b,n) = -----------------------------
          > choose(r+b, n)
          >
          > choose(r,x) is the binomial coefficient
          > I use the factorial to calculate the above formula but since I am using
          > large numbers, the result of choose(a,b) (ie: the binomial coefficient)
          > is too big even for large int. I've tried the scipy library, but this
          > library calculates
          > the hypergeometric using the factorials too, so the problem subsist. Is
          > there any other libray or an algorithm to calculate
          > the hypergeometric distribution? The statistical package R can handle
          > such calculations but I don't want to use python R binding since I want
          > a standalone app.[/color]

          Have you tried with gmpy?


          Alex

          Comment

          • Raven

            #6
            Re: Hypergeometric distribution

            Thanks to all of you guys, I could resolve my problem using the
            logarithms as proposed by Robert. I needed to calculate the factorial
            for genomic data, more specifically for the number of genes in the
            human genome i.e. about 30.000 and that is a big number :-)
            I didn't know gmpy
            Thanks a lot, really

            Ale

            Comment

            • Steven D'Aprano

              #7
              Re: Hypergeometric distribution

              On Sat, 31 Dec 2005 16:24:02 -0800, Raven wrote:
              [color=blue]
              > Thanks to all of you guys, I could resolve my problem using the
              > logarithms as proposed by Robert. I needed to calculate the factorial
              > for genomic data, more specifically for the number of genes in the
              > human genome i.e. about 30.000 and that is a big number :-)
              > I didn't know gmpy
              > Thanks a lot, really[/color]

              Are you *sure* the existing functions didn't work? Did you try them?
              [color=blue][color=green][color=darkred]
              >>> def log2(x):[/color][/color][/color]
              .... return math.log(x)/math.log(2)
              ....[color=blue][color=green][color=darkred]
              >>> n = 0.0
              >>> for i in range(1, 300000): # ten times bigger than you need[/color][/color][/color]
              .... n += log2(i)
              ....[color=blue][color=green][color=darkred]
              >>> n[/color][/color][/color]
              5025564.6087276 665[color=blue][color=green][color=darkred]
              >>> t = time.time(); x = 2L**(int(n) + 1); time.time() - t[/color][/color][/color]
              0.2664909362792 9688

              That's about one quarter of a second to calculate 300,000 factorial
              (approximately) , and it shows that the calculations are well within
              Python's capabilities.

              Of course, converting this 1.5 million-plus digit number to a string takes
              a bit longer:
              [color=blue][color=green][color=darkred]
              >>> t = time.time(); len(str(x)); time.time() - t[/color][/color][/color]
              1512846
              6939.3762848377 228

              A quarter of a second to calculate, and almost two hours to convert to a
              string. Lesson one: calculations on longints are fast. Converting them to
              strings is not.

              As far as your original question goes, try something like this:

              (formula from memory, may be wrong)
              [color=blue][color=green][color=darkred]
              >>> def bincoeff(n,r):[/color][/color][/color]
              .... x = 1
              .... for i in range(r+1, n+1):
              .... x *= i
              .... for i in range(1, n-r+1):
              .... x /= i
              .... return x
              ....[color=blue][color=green][color=darkred]
              >>> bincoeff(10, 0)[/color][/color][/color]
              1[color=blue][color=green][color=darkred]
              >>> bincoeff(10, 1)[/color][/color][/color]
              10[color=blue][color=green][color=darkred]
              >>> bincoeff(10, 2)[/color][/color][/color]
              45[color=blue][color=green][color=darkred]
              >>> bincoeff(10, 3)[/color][/color][/color]
              120[color=blue][color=green][color=darkred]
              >>> import time
              >>> t = time.time(); L = bincoeff(30000, 7000); time.time() - t[/color][/color][/color]
              28.317800045013 428

              Less than thirty seconds to calculate a rather large binomial coefficient
              exactly. How many digits?
              [color=blue][color=green][color=darkred]
              >>> len(str(L))[/color][/color][/color]
              7076

              If you are calculating hundreds of hypergeometric probabilities, 30
              seconds each could be quite painful, but it certainly shows that Python is
              capable of doing it without resorting to logarithms which may lose some
              significant digits. Although, in fairness, the log function doesn't seem
              to lose much accuracy for arguments in the range you are dealing with.


              How long does it take to calculate factorials?
              [color=blue][color=green][color=darkred]
              >>> def timefact(n):[/color][/color][/color]
              .... # calculate n! and return the time taken in seconds
              .... t = time.time()
              .... L = 1
              .... for i in range(1, n+1):
              .... L *= i
              .... return time.time() - t
              ....[color=blue][color=green][color=darkred]
              >>> timefact(3000)[/color][/color][/color]
              0.0549139976501 46484[color=blue][color=green][color=darkred]
              >>> timefact(30000) # equivalent to human genome[/color][/color][/color]
              5.0699510574340 82[color=blue][color=green][color=darkred]
              >>> timefact(300000 ) # ten times bigger[/color][/color][/color]
              4255.2370519638 062

              Keep in mind, if you are calculating the hypergeometric probabilities
              using raw factorials, you are doing way too much work.


              --
              Steven.

              Comment

              • Raven

                #8
                Re: Hypergeometric distribution

                Thanks Steven for your very interesting post.

                This was a critical instance from my problem:
                [color=blue][color=green][color=darkred]
                >>>from scipy import comb
                >>> comb(14354,174)[/color][/color][/color]
                inf

                The scipy.stats.dis tributions.hype rgeom function uses the scipy.comb
                function, so it returned nan since it tries to divide an infinite. I
                did not tried to write a self-made function using standard python as I
                supposed that the scipy functions reached python's limits but I was
                wrong, what a fool :-)
                [color=blue]
                >If you are calculating hundreds of hypergeometric probabilities, 30
                >seconds each could be quite painful, but it certainly shows that Python is
                >capable of doing it without resorting to logarithms which may lose some
                >significant digits. Although, in fairness, the log function doesn't seem
                >to lose much accuracy for arguments in the range you are dealing with.[/color]

                Yes I am calculating hundreds of hypergeometric probabilities so I need
                fast calculations

                Ale

                Comment

                • Paul Rubin

                  #9
                  Re: Hypergeometric distribution

                  "Raven" <balckraven@gma il.com> writes:[color=blue]
                  > Yes I am calculating hundreds of hypergeometric probabilities so I need
                  > fast calculations[/color]

                  Can you use Stirling's approximation to get the logs of the factorials?

                  Comment

                  • Steven D'Aprano

                    #10
                    Re: Hypergeometric distribution

                    On Sun, 01 Jan 2006 14:24:39 -0800, Raven wrote:
                    [color=blue]
                    > Thanks Steven for your very interesting post.
                    >
                    > This was a critical instance from my problem:
                    >[color=green][color=darkred]
                    >>>>from scipy import comb
                    >>>> comb(14354,174)[/color][/color]
                    > inf[/color]

                    Curious. It wouldn't surprise me if scipy was using floats, because 'inf'
                    is usually a floating point value, not an integer.

                    Using my test code from yesterday, I got:
                    [color=blue][color=green][color=darkred]
                    >>> bincoeff(14354, 174)[/color][/color][/color]
                    111727771935623 249173533679580 244374733360180 53487854593870
                    070906374894056 044891924883461 446844023623444 09632515556732
                    335635231613081 458252082763952 387644418578294 54464446478336
                    901737770950418 910676375517833 240712336253706 19908633625448
                    310766773824486 162461253466677 378968915481668 98009878730510
                    574761395158405 427699564142041 306927336297233 05869285300247
                    645972456505830 620188961902165 086857407612722 931651840L

                    Took about three seconds on my system.


                    [color=blue]
                    > Yes I am calculating hundreds of hypergeometric probabilities so I
                    > need fast calculations[/color]

                    Another possibility, if you want exact integer maths rather than floating
                    point with logarithms, is to memoise the binomial coefficients. Something
                    like this:

                    # untested
                    def bincoeff(n,r, \
                    cache={}):
                    try:
                    return cache((n,r))
                    except KeyError:
                    x = 1
                    for i in range(r+1, n+1):
                    x *= i
                    for i in range(1, n-r+1):
                    x /= i
                    cache((n,r)) = x
                    return x


                    --
                    Steven.

                    Comment

                    • Scott David Daniels

                      #11
                      Re: Hypergeometric distribution

                      Steven D'Aprano wrote:[color=blue]
                      > On Sun, 01 Jan 2006 14:24:39 -0800, Raven wrote:
                      >[color=green]
                      >> Thanks Steven for your very interesting post.
                      >>
                      >> This was a critical instance from my problem:
                      >>[color=darkred]
                      >>>> >from scipy import comb
                      >>>>> comb(14354,174)[/color]
                      >> inf[/color]
                      >
                      > Curious. It wouldn't surprise me if scipy was using floats, because 'inf'
                      > is usually a floating point value, not an integer.
                      >
                      > Using my test code from yesterday, I got:
                      >[color=green][color=darkred]
                      >>>> bincoeff(14354, 174)[/color][/color]
                      > ...[color=green]
                      >> Yes I am calculating hundreds of hypergeometric probabilities so I
                      >> need fast calculations[/color]
                      >
                      > Another possibility, if you want exact integer maths rather than floating
                      > point with logarithms, is to memoise the binomial coefficients. Something
                      > like this:
                      >
                      > # untested
                      > def bincoeff(n,r, \
                      > cache={}):
                      > try:
                      > return cache((n,r))
                      > except KeyError:
                      > x = 1
                      > for i in range(r+1, n+1):
                      > x *= i
                      > for i in range(1, n-r+1):
                      > x /= i
                      > cache((n,r)) = x
                      > return x[/color]

                      Well, there is a much better optimization to use first:

                      def bincoeff1(n, r):
                      if r < n - r:
                      r = n - r
                      x = 1
                      for i in range(n, r, -1):
                      x *= i
                      for i in range(n - r, 1, -1):
                      x /= i
                      return x

                      Then, if you still need to speed it up:

                      def bincoeff2(n, r, cache={}):
                      if r < n - r:
                      r = n - r
                      try:
                      return cache[n, r]
                      except KeyError:
                      pass
                      x = 1
                      for i in range(n, r, -1):
                      x *= i
                      for i in range(n - r, 1, -1):
                      x /= i
                      cache[n, r] = x
                      return x


                      --Scott David Daniels
                      scott.daniels@a cm.org

                      Comment

                      • Raven

                        #12
                        Re: Hypergeometric distribution

                        Well, what to say? I am very happy for all the solutions you guys have
                        posted :-)
                        For Paul:
                        I would prefer not to use Stirling's approximation


                        The problem with long integers is that to calculate the hypergeometric
                        I need to do float division and multiplication because integer division
                        returns 0. A solution could be to calculate log(Long_Factor ial_Integer)
                        and finally calculate the hypergeometric with the logarithmic values.
                        I've done a test: iterated 1000 times two different functions for the
                        hypergeometric, the first one based on scipy.special.g ammaln:

                        from scipy.special import gammaln

                        def lnchoose(n, m):
                        nf = gammaln(n + 1)
                        mf = gammaln(m + 1)
                        nmmnf = gammaln(n - m + 1)
                        return nf - (mf + nmmnf)

                        def hypergeometric_ gamma(k, n1, n2, t):
                        if t > n1 + n2:
                        t = n1 + n2
                        if k > n1 or k > t:
                        return 0
                        elif t > n2 and ((k + n2) < t):
                        return 0
                        else:
                        c1 = lnchoose(n1,k)
                        c2 = lnchoose(n2, t - k)
                        c3 = lnchoose(n1 + n2 ,t)

                        return exp(c1 + c2 - c3)

                        and the second one based on the code by Steven and Scott:


                        import time
                        from math import log, exp

                        def bincoeff1(n, r):
                        if r < n - r:
                        r = n - r
                        x = 1
                        for i in range(n, r, -1):
                        x *= i
                        for i in range(n - r, 1, -1):
                        x /= i
                        return x

                        def hypergeometric( k, n1, n2, t):
                        if t > n1 + n2:
                        t = n1 + n2
                        if k > n1 or k > t:
                        return 0
                        elif t > n2 and ((k + n2) < t):
                        return 0
                        else:
                        c1 = log(raw_bincoef f1(n1,k))
                        c2 = log(raw_bincoef f1(n2, t - k))
                        c3 = log(raw_bincoef f1(n1 + n2 ,t))

                        return exp(c1 + c2 - c3)

                        def main():
                        t = time.time()
                        for i in range(1000):
                        r = hypergeometric( 6,6,30,6)
                        print time.time() - t

                        t = time.time()
                        for i in range(1000):
                        r = hypergeometric_ gamma(6,6,30,6)
                        print time.time() - t


                        if __name__ == "__main__":
                        main()


                        and the result is:

                        0.0386447906494
                        0.192448139191

                        The first approach is faster so I think I will adopt it.

                        Comment

                        • Scott David Daniels

                          #13
                          Re: Hypergeometric distribution

                          Raven wrote:[color=blue]
                          > ...
                          > def main():
                          > t = time.time()
                          > for i in range(1000):
                          > r = hypergeometric( 6,6,30,6)
                          > print time.time() - t
                          >
                          > t = time.time()
                          > for i in range(1000):
                          > r = hypergeometric_ gamma(6,6,30,6)
                          > print time.time() - t
                          >
                          > and the result is:
                          >
                          > 0.0386447906494
                          > 0.192448139191
                          >
                          > The first approach is faster so I think I will adopt it.
                          >[/color]

                          You should really look into the timeit module -- you'll get nice
                          solid timings slightly easier to tweak.
                          Imagine something like:

                          import timeit
                          ...
                          t0 = timeit.Timer(st mt='f(6, 6, 30, 6)',
                          setup='from __main__ import hypergeometric as f')
                          t1 = timeit.Timer(st mt='f(6, 6, 30, 6)',
                          setup='from __main__ import hypergeometric_ gamma as f')

                          repetitions = 1 # Gross under-estimate of needed repetitions
                          while t0.timeit(repet itions) < .25: # .25 = minimum Secs per round
                          repetitions *= 10
                          print 'Going for %s repetitions' % repetitions
                          print 'hypergeometric :', t0.repeat(3, repetitions)
                          print 'hypergeometric _gamma:', t1.repeat(3, repetitions)

                          --Scott David Daniels
                          scott.daniels@a cm.org

                          Comment

                          • Bengt Richter

                            #14
                            Re: Hypergeometric distribution

                            On 2 Jan 2006 03:35:33 -0800, "Raven" <balckraven@gma il.com> wrote:
                            [...][color=blue]
                            >
                            >The problem with long integers is that to calculate the hypergeometric
                            >I need to do float division and multiplication because integer division
                            >returns 0. A solution could be to calculate log(Long_Factor ial_Integer)[/color]

                            ISTM you wouldn't get zero if you scaled by 10**significant _digits (however many
                            you require) before dividing. E.g., expected hits per trillion (or septillion or whatever)
                            expresses probability too. Perhaps that could work in your calculation?

                            Regards,
                            Bengt Richter

                            Comment

                            • Raven

                              #15
                              Re: Hypergeometric distribution

                              Scott David Daniels ha scritto:
                              [color=blue]
                              > You should really look into the timeit module -- you'll get nice
                              > solid timings slightly easier to tweak.[/color]

                              This seems a very interesting module, I will give it a try as soon as
                              possible. Thanks Scott.
                              Ale

                              Comment

                              Working...