diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index b658c21..d88c8c9 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; + class DecisionTreeLeaf { /** @@ -79,12 +81,7 @@ class DecisionTreeLeaf $recordField = $record[$this->columnIndex]; if ($this->isContinuous) { - $op = $this->operator; - $value = $this->numericValue; - $recordField = (string) $recordField; - eval("\$result = $recordField $op $value;"); - - return $result; + return Comparison::compare((string) $recordField, $this->numericValue, $this->operator); } return $recordField == $this->value; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 2780638..179c117 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -8,6 +8,7 @@ use Phpml\Helper\Predictable; use Phpml\Helper\OneVsRest; use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; class DecisionStump extends WeightedClassifier { @@ -236,29 +237,6 @@ class DecisionStump extends WeightedClassifier return $split; } - /** - * - * @param mixed $leftValue - * @param string $operator - * @param mixed $rightValue - * - * @return boolean - */ - protected function evaluate($leftValue, string $operator, $rightValue) - { - switch ($operator) { - case '>': return $leftValue > $rightValue; - case '>=': return $leftValue >= $rightValue; - case '<': return $leftValue < $rightValue; - case '<=': return $leftValue <= $rightValue; - case '=': return $leftValue === $rightValue; - case '!=': - case '<>': return $leftValue !== $rightValue; - } - - return false; - } - /** * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter @@ -278,7 +256,7 @@ class DecisionStump extends WeightedClassifier $rightLabel = $this->binaryLabels[1]; foreach ($values as $index => $value) { - if ($this->evaluate($value, $operator, $threshold)) { + if (Comparison::compare($value, $threshold, $operator)) { $predicted = $leftLabel; } else { $predicted = $rightLabel; @@ -337,7 +315,7 @@ class DecisionStump extends WeightedClassifier */ protected function predictSampleBinary(array $sample) { - if ($this->evaluate($sample[$this->column], $this->operator, $this->value)) { + if (Comparison::compare($sample[$this->column], $this->value, $this->operator)) { return $this->binaryLabels[0]; } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 2c32a6a..8dcbd03 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -157,4 +157,14 @@ class InvalidArgumentException extends \Exception { return new self(sprintf('The specified path "%s" is not writable', $path)); } + + /** + * @param string $operator + * + * @return InvalidArgumentException + */ + public static function invalidOperator(string $operator) + { + return new self(sprintf('Invalid operator "%s" provided', $operator)); + } } diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 72757df..7776ccd 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Helper; +use Phpml\Classification\Classifier; + trait OneVsRest { /** @@ -100,7 +102,7 @@ trait OneVsRest /** * Returns an instance of the current class after cleaning up OneVsRest stuff. * - * @return \Phpml\Estimator + * @return Classifier|OneVsRest */ protected function getClassifierCopy() { diff --git a/src/Phpml/Math/Comparison.php b/src/Phpml/Math/Comparison.php new file mode 100644 index 0000000..1c8b6aa --- /dev/null +++ b/src/Phpml/Math/Comparison.php @@ -0,0 +1,45 @@ +': + return $a > $b; + case '>=': + return $a >= $b; + case '=': + case '==': + return $a == $b; + case '===': + return $a === $b; + case '<=': + return $a <= $b; + case '<': + return $a < $b; + case '!=': + case '<>': + return $a != $b; + case '!==': + return $a !== $b; + default: + throw InvalidArgumentException::invalidOperator($operator); + } + } +} diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php new file mode 100644 index 0000000..2d41273 --- /dev/null +++ b/tests/Phpml/Math/ComparisonTest.php @@ -0,0 +1,80 @@ +assertEquals($expected, $result); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid operator "~=" provided + */ + public function testThrowExceptionWhenOperatorIsInvalid() + { + Comparison::compare(1, 1, '~='); + } + + /** + * @return array + */ + public function provideData() + { + return [ + // Greater + [1, 0, '>', true], + [1, 1, '>', false], + [0, 1, '>', false], + // Greater or equal + [1, 0, '>=', true], + [1, 1, '>=', true], + [0, 1, '>=', false], + // Equal + [1, 0, '=', false], + [1, 1, '==', true], + [1, '1', '=', true], + [1, '0', '==', false], + // Identical + [1, 0, '===', false], + [1, 1, '===', true], + [1, '1', '===', false], + ['a', 'a', '===', true], + // Not equal + [1, 0, '!=', true], + [1, 1, '<>', false], + [1, '1', '!=', false], + [1, '0', '<>', true], + // Not identical + [1, 0, '!==', true], + [1, 1, '!==', false], + [1, '1', '!==', true], + [1, '0', '!==', true], + // Less or equal + [1, 0, '<=', false], + [1, 1, '<=', true], + [0, 1, '<=', true], + // Less + [1, 0, '<', false], + [1, 1, '<', false], + [0, 1, '<', true], + ]; + } +}