Histogram::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace HuasoFoundries\Histogram;
3
4
class Histogram extends AbstractHistogram
5
{
6
7
    /**
8
     * Constructor
9
     *
10
     * @access  public
11
     * @param   optional    int $type   one of HISTOGRAM_SIMPLE or HISTOGRAM_CUMMULATIVE
12
     * @param   optional    int $nbins  number of bins to use
13
     * @param   optional    float   $rangeLow   lowest value to use for bin frequency calculation
14
     * @param   optional    float   $rangeHigh   highest value to use for bin frequency calculation
15
     * @return  object  Histogram
16
     *
17
     * @see setBinOptions()
18
     * @see Math_AbstractHistogram::setType()
19
     * @see Math_AbstractHistogram
20
     */
21
    public function __construct($type = self::HISTOGRAM_SIMPLE, $nbins = -1, $rangeLow = null, $rangeHigh = null)
22
    {
23
        $this->setType($type);
24
        $this->setBinOptions($nbins, $rangeLow, $rangeHigh);
25
    }
26
27
    /**
28
     * Sets the binning options. Overrides parent's method.
29
     *
30
     * @access  public
31
     * @param   int $nbins  the number of bins to use for computing the histogram
32
     * @param   optional    float   $rangeLow   lowest value to use for bin frequency calculation
33
     * @param   optional    float   $rangeHigh   highest value to use for bin frequency calculation
34
     * @return  void
35
     */
36
    public function setBinOptions($nbins, $rangeLow = null, $rangeHigh = null)
37
    {
38
        $this->_nbins     = (is_int($nbins) && $nbins > 2) ? $nbins : 10;
39
        $this->_rangeLow  = $rangeLow;
40
        $this->_rangeHigh = $rangeHigh;
41
    }
42
43
    /**
44
     * Returns an associative array with the bin options
45
     *
46
     * @access public
47
     * @return array Associative array of bin options:
48
     *                  array(  'nbins'=>$nbins,
49
     *                          'rangeLow'=>$rangeLow,
50
     *                          'rangeHigh'=>$rangeHigh);
51
     */
52
    public function getBinOptions()
53
    {
54
        return array(
55
            'nbins'     => $this->_nbins,
56
            'rangeLow'  => $this->_rangeLow,
57
            'rangeHigh' => $this->_rangeHigh,
58
        );
59
    }
60
61
    /**
62
     * Sets the data to be processed. The data will be validated to
63
     * be a simple uni-dimensional numerical array
64
     *
65
     * @access  public
66
     * @param   array   $data   the numeric array
67
     * @return  mixed   boolean true on success, a \PEAR_Error object otherwise
68
     *
69
     * @see _clear()
70
     * @see Math_AbstractHistogram::getData()
71
     * @see Math_AbstractHistogram
72
     * @see getHistogramData()
73
     */
74
    public function setData($data)
75
    {
76
        $this->_clear();
77
        if (!is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
78
            throw new \PEAR_Exception("array of numeric data expected");
79
        }
80
81
        foreach ($data as $item) {
82
            if (!is_numeric($item)) {
83
                throw new \PEAR_Exception("non-numeric item in array");
84
            }
85
        }
86
87
        $this->_data = $data;
88
        if (is_null($this->_rangeLow)) {
89
            $this->_rangeLow = min($this->_data);
90
        }
91
92
        if (is_null($this->_rangeHigh)) {
93
            $this->_rangeHigh = max($this->_data);
94
        }
95
96
        sort($this->_data);
97
        return true;
98
    }
99
100
    /**
101
     * Calculates the histogram bins and frequencies
102
     *
103
     * @access  public
104
     * @param   optional    $statsMode  calculate basic statistics (STATS_BASIC) or full (STATS_FULL)
0 ignored issues
show
Bug introduced by
The type HuasoFoundries\Histogram\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
105
     * @return  mixed   boolean true on success, a \PEAR_Error object otherwise
106
     *
107
     * @see Math_Stats
108
     */
109
    public function calculate($statsMode = \HuasoFoundries\Math\Stats::STATS_BASIC)
110
    {
111
        $this->_stats = new \HuasoFoundries\Math\Stats();
112
113
        $this->_statsMode = $statsMode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $statsMode can also be of type HuasoFoundries\Histogram\optional. However, the property $_statsMode is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
114
        $delta            = ($this->_rangeHigh - $this->_rangeLow) / $this->_nbins;
115
        $lastpos          = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $lastpos is dead and can be removed.
Loading history...
116
        $cumm             = 0;
117
        $data             = $this->_histogramData();
118
        $ndata            = count($data);
119
        $ignoreList       = array();
120
121
        for ($i = 0; $i < $this->_nbins; $i++) {
122
            $loBin                   = $this->_rangeLow + $i * $delta;
123
            $hiBin                   = $loBin + $delta;
124
            $this->_bins[$i]["low"]  = $loBin;
125
            $this->_bins[$i]["high"] = $hiBin;
126
            $this->_bins[$i]["mid"]  = ($hiBin + $loBin) / 2;
127
            if ($this->_type == self::HISTOGRAM_CUMMULATIVE) {
128
                $this->_bins[$i]["count"] = $cumm;
129
            } else {
130
                $this->_bins[$i]["count"] = 0;
131
            }
132
133
            for ($j = 0; $j < $ndata; $j++) {
134
                if (!empty($ignoreList) && in_array($j, $ignoreList)) {
135
                    continue;
136
                }
137
138
                if ($j == 0) {
139
                    $inRange = ($loBin <= $data[$j] && $hiBin >= $data[$j]);
140
                } else {
141
                    $inRange = ($loBin < $data[$j] && $hiBin >= $data[$j]);
142
                }
143
144
                if ($inRange) {
145
                    $this->_bins[$i]["count"]++;
146
                    if ($this->_type == self::HISTOGRAM_CUMMULATIVE) {
147
                        $cumm++;
148
                    }
149
150
                    $ignoreList[] = $j;
151
                }
152
            }
153
        }
154
        return true;
155
    }
156
157
    /**
158
     * Returns the statistics for the data set
159
     *
160
     * @access  public
161
     * @return  mixed   an associative array on success, a \PEAR_Error object otherwise
162
     */
163
    public function getDataStats()
164
    {
165
        if ($this->isCalculated()) {
166
            $this->_stats->setData($this->_data);
167
168
            return $this->_stats->calc($this->_statsMode);
169
        } else {
170
            throw new \PEAR_Exception("histogram has not been calculated");
171
        }
172
    }
173
174
    /**
175
     * Returns the statistics for the data set, filtered using the bin range
176
     *
177
     * @access  public
178
     * @return  mixed   an associative array on success, a \PEAR_Error object otherwise
179
     */
180
    public function getHistogramDataStats()
181
    {
182
        if ($this->isCalculated()) {
183
            $this->_stats->setData($this->_histogramData());
184
            return $this->_stats->calc($this->_statsMode);
185
        } else {
186
            throw new \PEAR_Exception("histogram has not been calculated");
187
        }
188
    }
189
190
    /**
191
     * Returns the bins and frequencies calculated using the given
192
     * bin mode and separator
193
     *
194
     * @access  public
195
     * @param   int $mode   one of HISTOGRAM_LO_BINS, HISTOGRAM_MID_BINS (default), or HISTOGRAM_HI_BINS
196
     * @param   string  $separator  the separator, default ", "
197
     * @return  mixed  a string on success, a \PEAR_Error object otherwise
198
     */
199
    public function toSeparated($mode = self::HISTOGRAM_MID_BINS, $separator = ", ")
200
    {
201
        $bins = $this->getBins($mode);
202
        if (\PEAR::isError($bins)) {
203
            return $bins;
204
        }
205
206
        $nbins = count($bins);
0 ignored issues
show
Unused Code introduced by
The assignment to $nbins is dead and can be removed.
Loading history...
207
        $out   = array("# bin{$separator}frequency");
208
        foreach ($bins as $bin => $freq) {
209
            $out[] = "{$bin}{$separator}{$freq}";
210
        }
211
212
        return implode("\n", $out) . "\n";
213
    }
214
215
    /**
216
     * Static method to check that an object is a Histogram instance
217
     *
218
     * @static
219
     * @access public
220
     * @param  object Histogram $hist An instance of the Histogram class
221
     * @return boolean TRUE on success, FALSE otherwise
222
     */
223
    public static function isValidHistogram(&$hist)
224
    {
225
        return (is_object($hist) && is_a($hist, '\HuasoFoundries\Histogram\Histogram'));
226
    }
227
228
    /**
229
     * Method to interrogate if a histogram has been calculated
230
     *
231
     * @access public
232
     * @return boolean TRUE if the histogram was calculated, FALSE otherwise
233
     * @see calculate()
234
     */
235
    public function isCalculated()
236
    {
237
        return !empty($this->_bins);
238
    }
239
240
    /**
241
     * Generates a plot using the appropriate printer object
242
     *
243
     * @param object $printer A Histogram_Printer_* object
244
     * @return string|PEAR_Error A string on success, a \PEAR_Error otherwise
0 ignored issues
show
Bug introduced by
The type HuasoFoundries\Histogram\PEAR_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
245
     */
246
    public function generatePlot(&$printer)
247
    {
248
        if (is_object($printer) && is_a($printer, '\HuasoFoundries\Histogram\Printer\Common')) {
249
            $printer->setHistogram($this);
250
            return $printer->generateOutput();
251
        } else {
252
            throw new \PEAR_Exception('Invalid object, expecting a \HuasoFoundries\Histogram\Printer\* instance');
253
        }
254
    }
255
256
    /**
257
     * Prints a simple ASCII representation of the histogram
258
     *
259
     * @deprecated
260
     * @access  public
261
     * @param   optional    int $mode   one of HISTOGRAM_LO_BINS, HISTOGRAM_MID_BINS, or HISTOGRAM_HI_BINS (default)
262
     * @return  mixed   a string on success, a \PEAR_Error object otherwise
263
     */
264
    public function printHistogram($mode = self::HISTOGRAM_HI_BINS)
265
    {
266
        if (!$this->isCalculated()) {
267
            throw new \PEAR_Exception("histogram has not been calculated");
268
        }
269
        $out = ($this->_type == self::HISTOGRAM_CUMMULATIVE) ? "Cummulative Frequency" : "Histogram";
270
        $out .= "\n\tNumber of bins: " . $this->_nbins . "\n";
271
        $out .= "\tPlot range: [" . $this->_rangeLow . ", " . $this->_rangeHigh . "]\n";
272
        $hdata = $this->_histogramData();
273
        $out .= "\tData range: [" . min($hdata) . ", " . max($hdata) . "]\n";
274
        $out .= "\tOriginal data range: [" . min($this->_data) . ", " . max($this->_data) . "]\n";
275
        $out .= "BIN (FREQUENCY) ASCII_BAR (%)\n";
276
        $fmt     = "%-4.3f (%-4d) |%s\n";
277
        $bins    = $this->_filterBins($mode);
278
        $maxfreq = max(array_values($bins));
279
        $total   = count($this->_data);
280
        foreach ($bins as $bin => $freq) {
281
            $out .= sprintf($fmt, $bin, $freq, $this->_bar($freq, $maxfreq, $total));
282
        }
283
284
        return $out;
285
    }
286
287
    /**
288
     * Prints a simple ASCII bar
289
     *
290
     * @access  private
291
     * @param   int $freq   the frequency
292
     * @param   int $maxfreq    the maximum frequency
293
     * @param   int $total  the total count
294
     * @return  string
295
     */
296
    public function _bar($freq, $maxfreq, $total)
297
    {
298
        $fact  = floatval(($maxfreq > 40) ? 40 / $maxfreq : 1);
299
        $niter = round($freq * $fact);
300
        $out   = "";
301
        for ($i = 0; $i < $niter; $i++) {
302
            $out .= "*";
303
        }
304
305
        return $out . sprintf(" (%.1f%%)", $freq / $total * 100);
306
    }
307
308
    /**
309
     * Returns a subset of the bins array by bin value type
310
     *
311
     * @access  private
312
     * @param   int $mode one of HISTOGRAM_MID_BINS, HISTOGRAM_LO_BINS or HISTOGRAM_HI_BINS
313
     * @return  array
314
     */
315
    public function _filterBins($mode)
316
    {
317
        $map = array(
318
            self::HISTOGRAM_MID_BINS => "mid",
319
            self::HISTOGRAM_LO_BINS  => "low",
320
            self::HISTOGRAM_HI_BINS  => "high",
321
        );
322
        $filtered = array();
323
        foreach ($this->_bins as $bin) {
324
            $filtered["{$bin[$map[$mode]]}"] = $bin["count"];
325
        }
326
327
        return $filtered;
328
    }
329
330
    /**
331
     * Returns an array of data contained within the range for the
332
     * histogram calculation. Overrides the empty implementation in
333
     * Math_AbstractHistogram::_histogramData()
334
     *
335
     * @access  private
336
     * @return  array
337
     */
338
    public function _histogramData()
339
    {
340
        $data = array();
341
        foreach ($this->_data as $val) {
342
            if ($val < $this->_rangeLow || $val > $this->_rangeHigh) {
343
                continue;
344
            } else {
345
                $data[] = $val;
346
            }
347
        }
348
349
        return $data;
350
    }
351
}
352
353
// vim: ts=4:sw=4:et:
354
// vim6: fdl=1:
355