Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Exception;
   7  use PhpOffice\PhpSpreadsheet\Calculation\Functions;
   8  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   9  use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations;
  10  
  11  class Binomial
  12  {
  13      use ArrayEnabled;
  14  
  15      /**
  16       * BINOMDIST.
  17       *
  18       * Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
  19       *        a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
  20       *        when trials are independent, and when the probability of success is constant throughout the
  21       *        experiment. For example, BINOMDIST can calculate the probability that two of the next three
  22       *        babies born are male.
  23       *
  24       * @param mixed $value Integer number of successes in trials
  25       *                      Or can be an array of values
  26       * @param mixed $trials Integer umber of trials
  27       *                      Or can be an array of values
  28       * @param mixed $probability Probability of success on each trial as a float
  29       *                      Or can be an array of values
  30       * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
  31       *                      Or can be an array of values
  32       *
  33       * @return array|float|string
  34       *         If an array of numbers is passed as an argument, then the returned result will also be an array
  35       *            with the same dimensions
  36       */
  37      public static function distribution($value, $trials, $probability, $cumulative)
  38      {
  39          if (is_array($value) || is_array($trials) || is_array($probability) || is_array($cumulative)) {
  40              return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $trials, $probability, $cumulative);
  41          }
  42  
  43          try {
  44              $value = DistributionValidations::validateInt($value);
  45              $trials = DistributionValidations::validateInt($trials);
  46              $probability = DistributionValidations::validateProbability($probability);
  47              $cumulative = DistributionValidations::validateBool($cumulative);
  48          } catch (Exception $e) {
  49              return $e->getMessage();
  50          }
  51  
  52          if (($value < 0) || ($value > $trials)) {
  53              return ExcelError::NAN();
  54          }
  55  
  56          if ($cumulative) {
  57              return self::calculateCumulativeBinomial($value, $trials, $probability);
  58          }
  59          /** @var float */
  60          $comb = Combinations::withoutRepetition($trials, $value);
  61  
  62          return $comb * $probability ** $value
  63              * (1 - $probability) ** ($trials - $value);
  64      }
  65  
  66      /**
  67       * BINOM.DIST.RANGE.
  68       *
  69       * Returns returns the Binomial Distribution probability for the number of successes from a specified number
  70       *     of trials falling into a specified range.
  71       *
  72       * @param mixed $trials Integer number of trials
  73       *                      Or can be an array of values
  74       * @param mixed $probability Probability of success on each trial as a float
  75       *                      Or can be an array of values
  76       * @param mixed $successes The integer number of successes in trials
  77       *                      Or can be an array of values
  78       * @param mixed $limit Upper limit for successes in trials as null, or an integer
  79       *                           If null, then this will indicate the same as the number of Successes
  80       *                      Or can be an array of values
  81       *
  82       * @return array|float|string
  83       *         If an array of numbers is passed as an argument, then the returned result will also be an array
  84       *            with the same dimensions
  85       */
  86      public static function range($trials, $probability, $successes, $limit = null)
  87      {
  88          if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) {
  89              return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit);
  90          }
  91  
  92          $limit = $limit ?? $successes;
  93  
  94          try {
  95              $trials = DistributionValidations::validateInt($trials);
  96              $probability = DistributionValidations::validateProbability($probability);
  97              $successes = DistributionValidations::validateInt($successes);
  98              $limit = DistributionValidations::validateInt($limit);
  99          } catch (Exception $e) {
 100              return $e->getMessage();
 101          }
 102  
 103          if (($successes < 0) || ($successes > $trials)) {
 104              return ExcelError::NAN();
 105          }
 106          if (($limit < 0) || ($limit > $trials) || $limit < $successes) {
 107              return ExcelError::NAN();
 108          }
 109  
 110          $summer = 0;
 111          for ($i = $successes; $i <= $limit; ++$i) {
 112              /** @var float */
 113              $comb = Combinations::withoutRepetition($trials, $i);
 114              $summer += $comb * $probability ** $i
 115                  * (1 - $probability) ** ($trials - $i);
 116          }
 117  
 118          return $summer;
 119      }
 120  
 121      /**
 122       * NEGBINOMDIST.
 123       *
 124       * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
 125       *        there will be number_f failures before the number_s-th success, when the constant
 126       *        probability of a success is probability_s. This function is similar to the binomial
 127       *        distribution, except that the number of successes is fixed, and the number of trials is
 128       *        variable. Like the binomial, trials are assumed to be independent.
 129       *
 130       * @param mixed $failures Number of Failures as an integer
 131       *                      Or can be an array of values
 132       * @param mixed $successes Threshold number of Successes as an integer
 133       *                      Or can be an array of values
 134       * @param mixed $probability Probability of success on each trial as a float
 135       *                      Or can be an array of values
 136       *
 137       * @return array|float|string The result, or a string containing an error
 138       *         If an array of numbers is passed as an argument, then the returned result will also be an array
 139       *            with the same dimensions
 140       *
 141       * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST
 142       *      The cumulative default should be false to reflect the behaviour of NEGBINOMDIST
 143       */
 144      public static function negative($failures, $successes, $probability)
 145      {
 146          if (is_array($failures) || is_array($successes) || is_array($probability)) {
 147              return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability);
 148          }
 149  
 150          try {
 151              $failures = DistributionValidations::validateInt($failures);
 152              $successes = DistributionValidations::validateInt($successes);
 153              $probability = DistributionValidations::validateProbability($probability);
 154          } catch (Exception $e) {
 155              return $e->getMessage();
 156          }
 157  
 158          if (($failures < 0) || ($successes < 1)) {
 159              return ExcelError::NAN();
 160          }
 161          if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
 162              if (($failures + $successes - 1) <= 0) {
 163                  return ExcelError::NAN();
 164              }
 165          }
 166          /** @var float */
 167          $comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1);
 168  
 169          return $comb
 170              * ($probability ** $successes) * ((1 - $probability) ** $failures);
 171      }
 172  
 173      /**
 174       * BINOM.INV.
 175       *
 176       * Returns the smallest value for which the cumulative binomial distribution is greater
 177       *        than or equal to a criterion value
 178       *
 179       * @param mixed $trials number of Bernoulli trials as an integer
 180       *                      Or can be an array of values
 181       * @param mixed $probability probability of a success on each trial as a float
 182       *                      Or can be an array of values
 183       * @param mixed $alpha criterion value as a float
 184       *                      Or can be an array of values
 185       *
 186       * @return array|int|string
 187       *         If an array of numbers is passed as an argument, then the returned result will also be an array
 188       *            with the same dimensions
 189       */
 190      public static function inverse($trials, $probability, $alpha)
 191      {
 192          if (is_array($trials) || is_array($probability) || is_array($alpha)) {
 193              return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha);
 194          }
 195  
 196          try {
 197              $trials = DistributionValidations::validateInt($trials);
 198              $probability = DistributionValidations::validateProbability($probability);
 199              $alpha = DistributionValidations::validateFloat($alpha);
 200          } catch (Exception $e) {
 201              return $e->getMessage();
 202          }
 203  
 204          if ($trials < 0) {
 205              return ExcelError::NAN();
 206          } elseif (($alpha < 0.0) || ($alpha > 1.0)) {
 207              return ExcelError::NAN();
 208          }
 209  
 210          $successes = 0;
 211          while ($successes <= $trials) {
 212              $result = self::calculateCumulativeBinomial($successes, $trials, $probability);
 213              if ($result >= $alpha) {
 214                  break;
 215              }
 216              ++$successes;
 217          }
 218  
 219          return $successes;
 220      }
 221  
 222      /**
 223       * @return float|int
 224       */
 225      private static function calculateCumulativeBinomial(int $value, int $trials, float $probability)
 226      {
 227          $summer = 0;
 228          for ($i = 0; $i <= $value; ++$i) {
 229              /** @var float */
 230              $comb = Combinations::withoutRepetition($trials, $i);
 231              $summer += $comb * $probability ** $i
 232                  * (1 - $probability) ** ($trials - $i);
 233          }
 234  
 235          return $summer;
 236      }
 237  }