1<?php
2
3namespace Rubix\ML\Other\Strategies;
4
5use Rubix\ML\DataType;
6use Rubix\ML\Other\Helpers\Stats;
7use Rubix\ML\Exceptions\InvalidArgumentException;
8use Rubix\ML\Exceptions\RuntimeException;
9
10/**
11 * Percentile
12 *
13 * A strategy that always guesses the p-th percentile of the fitted data.
14 *
15 * @category    Machine Learning
16 * @package     Rubix/ML
17 * @author      Andrew DalPino
18 */
19class Percentile implements Strategy
20{
21    /**
22     * The percentile of the fitted data to use as a guess.
23     *
24     * @var float
25     */
26    protected $p;
27
28    /**
29     * The pth percentile of the fitted data.
30     *
31     * @var float|null
32     */
33    protected $percentile;
34
35    /**
36     * @param float $p
37     * @throws \Rubix\ML\Exceptions\InvalidArgumentException
38     */
39    public function __construct(float $p = 50.0)
40    {
41        if ($p < 0.0 or $p > 100.0) {
42            throw new InvalidArgumentException('Percentile must be'
43                . " between 0 and 100, $p given.");
44        }
45
46        $this->p = $p;
47    }
48
49    /**
50     * Return the data type the strategy handles.
51     *
52     * @return \Rubix\ML\DataType
53     */
54    public function type() : DataType
55    {
56        return DataType::continuous();
57    }
58
59    /**
60     * Has the strategy been fitted?
61     *
62     * @internal
63     *
64     * @return bool
65     */
66    public function fitted() : bool
67    {
68        return isset($this->percentile);
69    }
70
71    /**
72     * Fit the guessing strategy to a set of values.
73     *
74     * @internal
75     *
76     * @param list<int|float> $values
77     * @throws \Rubix\ML\Exceptions\InvalidArgumentException
78     */
79    public function fit(array $values) : void
80    {
81        if (empty($values)) {
82            throw new InvalidArgumentException('Strategy must be fitted'
83                . ' to at least 1 value.');
84        }
85
86        $q = $this->p / 100.0;
87
88        $this->percentile = Stats::quantile($values, $q);
89    }
90
91    /**
92     * Make a guess.
93     *
94     * @internal
95     *
96     * @throws \Rubix\ML\Exceptions\RuntimeException
97     * @return float
98     */
99    public function guess() : float
100    {
101        if ($this->percentile === null) {
102            throw new RuntimeException('Strategy has not been fitted.');
103        }
104
105        return $this->percentile;
106    }
107
108    /**
109     * Return the string representation of the object.
110     *
111     * @return string
112     */
113    public function __toString() : string
114    {
115        return "Percentile (p: {$this->p})";
116    }
117}
118