count * @param float $fPercentile 0.75 for p75 * @param array $aSides counts of values up to the good threshold, between the two, and * above the poor threshold, or [] when unknown * @param array $aThresholds [good up to, poor above], with `$aSides` */ public static function getPercentile($aHistogram, $fPercentile = 0.75, $iMinSamples = 20, $aSides = [], $aThresholds = []) { $mixedValue = self::_getPercentile($aHistogram, $fPercentile, $iMinSamples); if($mixedValue === null || count($aSides) != 3 || count($aThresholds) != 2) return $mixedValue; $fRank = $fPercentile * array_sum($aSides); if($aSides[0] >= $fRank) return min($mixedValue, (float)$aThresholds[0]); if($aSides[0] + $aSides[1] >= $fRank) return min(max($mixedValue, (float)$aThresholds[0] + 1), (float)$aThresholds[1]); return max($mixedValue, (float)$aThresholds[1] + 1); } protected static function _getPercentile($aHistogram, $fPercentile, $iMinSamples) { $iTotal = 0; foreach($aHistogram as $iCount) $iTotal += (int)$iCount; if(!$iTotal || $iTotal < $iMinSamples) return null; ksort($aHistogram, SORT_NUMERIC); $fRank = $fPercentile * $iTotal; $iBefore = 0; foreach($aHistogram as $iBucket => $iCount) { $iCount = (int)$iCount; if(!$iCount) continue; if($iBefore + $iCount >= $fRank) { if((int)$iBucket < 0) return 0.0; $fShare = min(1, max(0, ($fRank - $iBefore - 0.5) / $iCount)); return exp(((int)$iBucket + $fShare) / self::BUCKETS); } $iBefore += $iCount; } // rounding left the rank past the last bucket end($aHistogram); $iLast = (int)key($aHistogram); return $iLast < 0 ? 0.0 : exp(($iLast + 1) / self::BUCKETS); } /** * good, needs_improvement or poor for a stored value. * * @param array $aThresholds [good up to, poor above] */ public static function getRating($mixedValue, $aThresholds) { if($mixedValue === null) return ''; if($mixedValue <= $aThresholds[0]) return 'good'; return $mixedValue > $aThresholds[1] ? 'poor' : 'needs_improvement'; } } /** @} */