Histogram3D::setBinOptions()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace HuasoFoundries\Histogram;
3
4
/**
5
 * Class to generate 3D histograms from bi-dimensional numeric
6
 * arrays.
7
 *
8
 * Originally this class was part of NumPHP (Numeric PHP package)
9
 *
10
 * @author  Jesus M. Castagnetto <[email protected]>
11
 * @version 0.9.1beta
12
 * @access  public
13
 * @package Math_Histogram
14
 */
15
class Histogram3D extends AbstractHistogram
16
{
17
18
    /**
19
     * Constructor for Math_Histogram3D
20
     *
21
     * @access  public
22
     * @param   optional    int $type   one of HISTOGRAM_SIMPLE or HISTOGRAM_CUMMULATIVE
23
     * @param   optional    array $binOptions   an array of options for binning the data
24
     * @return  object  Math_Histogram3D
25
     *
26
     * @see setBinOptions()
27
     * @see Math_AbstractHistogram::setType()
28
     * @see Math_AbstractHistogram
29
     */
30
31
    public function __construct($type = self::HISTOGRAM_SIMPLE, $binOptions = "")
32
    {
33
        $this->setType($type);
34
        try {
35
            $this->setBinOptions($binOptions);
36
        } catch (\PEAR_Exception $e) {
37
            // Falling back to default options
38
        }
39
    }
40
41
    /**
42
     * Sets the binning options. Overrides parent's method.
43
     *
44
     * @access  public
45
     * @param   array $binOptions  an array of options for binning the data
46
     * @return  void
47
     */
48
49
    public function setBinOptions($binOptions)
50
    {
51
        if ($this->_validBinOptions($binOptions)) {
52
            return parent::setBinOptions($binOptions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setBinOptions($binOptions) returns the type true which is incompatible with the documented return type void.
Loading history...
53
        } else {
54
            throw new \PEAR_Exception("incorrect options array");
55
        }
56
    }
57
58
    /**
59
     * Sets the data to be processed. The data will be validated to
60
     * be a simple bi-dimensional numerical array
61
     *
62
     * @access  public
63
     * @param   array   $data   the numeric array
64
     * @return  mixed   boolean true on success, a \PEAR_Error object otherwise
65
     *
66
     * @see _clear()
67
     * @see Math_AbstractHistogram::getData()
68
     * @see Math_AbstractHistogram
69
     * @see getHistogramData()
70
     */
71
    public function setData($data)
72
    {
73
        $this->_clear();
74
        if (!$this->_validData($data)) {
75
            throw new \PEAR_Exception("array of numeric coordinates expected");
76
        }
77
78
        $this->_data       = $data;
79
        list($xMin, $xMax) = $this->_getMinMax('x');
0 ignored issues
show
Bug introduced by
'x' of type string is incompatible with the type array expected by parameter $elem of HuasoFoundries\Histogram\Histogram3D::_getMinMax(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
        list($xMin, $xMax) = $this->_getMinMax(/** @scrutinizer ignore-type */ 'x');
Loading history...
80
        list($yMin, $yMax) = $this->_getMinMax('y');
81
        if (is_null($this->_rangeLow)) {
82
            $this->_rangeLow = array('x' => $xMin, 'y' => $yMin);
83
        }
84
85
        if (is_null($this->_rangeHigh)) {
86
            $this->_rangeHigh = array('x' => $xMax, 'y' => $yMax);
87
        }
88
89
        if (is_null($this->_nbins)) {
90
            $this->_nbins = array('x' => 10, 'y' => 10);
91
        }
92
93
        return true;
94
    }
95
96
    /**
97
     * Calculates the histogram bins and frequencies
98
     *
99
     * @access  public
100
     * @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...
101
     * @return  mixed   boolean true on success, a \PEAR_Error object otherwise
102
     *
103
     * @see Math_Stats
104
     */
105
    public function calculate($statsMode = \HuasoFoundries\Math\Stats::STATS_BASIC)
106
    {
107
        $this->_bins      = array();
108
        $this->_stats     = array('x' => new \HuasoFoundries\Math\Stats(), 'y' => new \HuasoFoundries\Math\Stats());
0 ignored issues
show
Documentation Bug introduced by
It seems like array('x' => new HuasoFo...Foundries\Math\Stats()) of type array<string,HuasoFoundries\Math\Stats> is incompatible with the declared type object of property $_stats.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
109
        $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...
110
        $deltaX           = ($this->_rangeHigh['x'] - $this->_rangeLow['x']) / $this->_nbins['x'];
111
        $deltaY           = ($this->_rangeHigh['y'] - $this->_rangeLow['y']) / $this->_nbins['y'];
112
        $data             = $this->_histogramData();
113
        //$dataX = $this->_data['x'];
114
        //$dataY = $this->_data['y'];
115
        $dataX      = $data['x'];
116
        $dataY      = $data['y'];
117
        $ignoreList = array();
118
        $cumm       = 0;
119
        $nData      = count($dataX);
120
        for ($i = 0; $i < $this->_nbins['x']; $i++) {
121
            $loXBin = $this->_rangeLow['x'] + $i * $deltaX;
122
            $hiXBin = $loXBin + $deltaX;
123
            $xBin   = array('low' => $loXBin, 'high' => $hiXBin,
124
                'mid'             => ($hiXBin + $loXBin) / 2);
125
            for ($j = 0; $j < $this->_nbins['y']; $j++) {
126
                $loYBin = $this->_rangeLow['y'] + $j * $deltaY;
127
                $hiYBin = $loYBin + $deltaY;
128
                $yBin   = array('low' => $loYBin, 'high' => $hiYBin,
129
                    'mid'             => ($hiYBin + $loYBin) / 2);
130
                $bin  = array('x' => $xBin, 'y' => $yBin);
131
                $freq = 0;
132
                for ($k = 0; $k < $nData; $k++) {
133
                    if (!empty($ignoreList) && in_array($k, $ignoreList)) {
134
                        continue;
135
                    }
136
137
                    $valueX   = $dataX[$k];
138
                    $valueY   = $dataY[$k];
139
                    $inRangeX = $inRangeY = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $inRangeX is dead and can be removed.
Loading history...
140
                    if ($i == 0) {
141
                        $inRangeX = ($loXBin <= $valueX && $hiXBin >= $valueX);
142
                    } else {
143
                        $inRangeX = ($loXBin < $valueX && $hiXBin >= $valueX);
144
                    }
145
146
                    if ($j == 0) {
147
                        $inRangeY = ($loYBin <= $valueY && $hiYBin >= $valueY);
148
                    } else {
149
                        $inRangeY = ($loYBin < $valueY && $hiYBin >= $valueY);
150
                    }
151
152
                    if ($inRangeX && $inRangeY) {
153
                        $freq++;
154
                        $cumm++;
155
                        $ignoreList[] = $k;
156
                    }
157
                }
158
                if ($this->_type == self::HISTOGRAM_CUMMULATIVE) {
159
                    if ($freq > 0) {
160
                        $bin['count'] = $freq + $cumm - 1;
161
                    } else {
162
                        $bin['count'] = 0;
163
                    }
164
                } else {
165
                    $bin['count'] = $freq;
166
                }
167
                $bin['xbin']   = $i;
168
                $bin['ybin']   = $j;
169
                $this->_bins[] = $bin;
170
            }
171
        }
172
    }
173
174
    /**
175
     * Returns the statistics for the data set
176
     *
177
     * @access  public
178
     * @return  mixed   an associative array on success, a \PEAR_Error object otherwise
179
     */
180
    public function getDataStats()
181
    {
182
        if (empty($this->_bins)) {
183
            throw new \PEAR_Exception("histogram has not been calculated");
184
        }
185
186
        $this->_stats['x']->setData($this->_data['x']);
187
        $this->_stats['y']->setData($this->_data['y']);
188
        return array('x' => $this->_stats['x']->calc($this->_statsMode),
189
            'y'          => $this->_stats['y']->calc($this->_statsMode));
190
    }
191
192
    /**
193
     * Returns the statistics for the data set, filtered using the bin ranges
194
     *
195
     * @access  public
196
     * @return  mixed   an associative array on success, a \PEAR_Error object otherwise
197
     */
198
    public function getHistogramDataStats()
199
    {
200
        if (empty($this->_bins)) {
201
            throw new \PEAR_Exception("histogram has not been calculated");
202
        }
203
204
        $data = $this->_histogramData();
205
        $this->_stats['x']->setData($data['x']);
206
        $this->_stats['y']->setData($data['y']);
207
        return array('x' => $this->_stats['x']->calc($this->_statsMode),
208
            'y'          => $this->_stats['y']->calc($this->_statsMode));
209
    }
210
211
    /**
212
     * Returns the bins and frequencies calculated using the given
213
     * bin mode and separator
214
     *
215
     * @access  public
216
     * @param   int $mode   one of HISTOGRAM_LO_BINS, HISTOGRAM_MID_BINS (default), or HISTOGRAM_HI_BINS
217
     * @param   string  $separator  the separator, default ", "
218
     * @return  mixed  a string on success, a \PEAR_Error object otherwise
219
     */
220
    public function toSeparated($mode = self::HISTOGRAM_MID_BINS, $separator = ", ")
221
    {
222
        try {
223
            $bins = $this->getBins($mode);
224
        } catch (\PEAR_Exception $e) {
225
            return $bins;
226
        }
227
        
228
229
        $nbins = count($bins);
230
        $out   = array("# x_bin{$separator}y_bin{$separator}frequency");
231
        for ($i = 0; $i < $nbins; $i++) {
232
            $out[] = implode($separator, $bins[$i]);
233
        }
234
235
        return implode("\n", $out) . "\n";
236
    }
237
238
    /**
239
     * Returns the minimum and maximum of the given unidimensional numeric
240
     * array
241
     *
242
     * @access  private
243
     * @param   array   $elem
244
     * @return  array   of values: array(min, max)
245
     */
246
    public function _getMinMax($elem)
247
    {
248
        return array(min($this->_data[$elem]), max($this->_data[$elem]));
249
    }
250
251
    /**
252
     * Returns a subset of the bins array by bin value type
253
     *
254
     * @access  private
255
     * @param   int $mode one of HISTOGRAM_MID_BINS, HISTOGRAM_LO_BINS or HISTOGRAM_HI_BINS
256
     * @return  array
257
     */
258
    public function _filterBins($mode)
259
    {
260
        $map = array(
261
            self::HISTOGRAM_MID_BINS => "mid",
262
            self::HISTOGRAM_LO_BINS  => "low",
263
            self::HISTOGRAM_HI_BINS  => "high",
264
        );
265
        $filtered = array();
266
        foreach ($this->_bins as $bin) {
267
            $tmp['x']     = $bin['x'][$map[$mode]];
268
            $tmp['y']     = $bin['y'][$map[$mode]];
269
            $tmp['count'] = $bin['count'];
270
            $filtered[]   = $tmp;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tmp seems to be defined later in this foreach loop on line 267. Are you sure it is defined here?
Loading history...
271
        }
272
        return $filtered;
273
    }
274
275
    /**
276
     * Checks that the array of options passed is valid
277
     * Options array should have the form:
278
     *
279
     * $opt = array ('low'=>array('x'=>10, 'y'=>10),
280
     *               'high'=>array(...),
281
     *               'nbins'=>array(...));
282
     *
283
     * @access  private
284
     * @return  boolean
285
     */
286
    public function _validBinOptions($binOptions)
287
    {
288
        $barray = (is_array($binOptions)
289
            && is_array($binOptions['low'])
290
            && is_array($binOptions['high'])
291
            && is_array($binOptions['nbins']));
292
        if (!$barray) {
293
            return false;
294
        }
295
        $low   = $binOptions['low'];
296
        $high  = $binOptions['high'];
297
        $nbins = $binOptions['nbins'];
298
        $blow  = (isset($low['x']) && isset($low['y'])
299
            && is_numeric($low['x']) && is_numeric($low['y']));
300
        $bhigh = (isset($high['x']) && isset($high['y'])
301
            && is_numeric($high['x']) && is_numeric($high['y']));
302
        $bnbins = (isset($nbins['x']) && isset($nbins['y'])
303
            && is_numeric($nbins['x']) && is_numeric($nbins['y']));
304
        return ($blow && $bhigh && $bnbins);
305
    }
306
307
    /**
308
     * Checks that the data passed is bi-dimensional numeric array
309
     * of the form:
310
     *
311
     * $data = array ('x'=>array(...), 'y'=>array(...));
312
     *
313
     * It also checks that: count($data['x']) == count($data['y'])
314
     *
315
     * @access  private
316
     * @return  boolean
317
     */
318
    public function _validData($data)
319
    {
320
        if (is_array($data) && is_array($data['x']) && is_array($data['y'])) {
321
            $n = count($data['x']);
322
            if (count($data) == 2 && $n == count($data['y'])) {
323
                for ($i = 0; $i < $n; $i++) {
324
                    if (!is_numeric($data['x'][$i]) || !is_numeric($data['y'][$i])) {
325
                        return false;
326
                    }
327
                }
328
329
                // if everything checks out
330
                return true;
331
            } else {
332
                return false;
333
            }
334
        } else {
335
            return false;
336
        }
337
    }
338
339
    /**
340
     * Returns an array of data contained within the ranges for the
341
     * histogram calculation. Overrides the empty implementation in
342
     * Math_AbstractHistogram::_histogramData()
343
     *
344
     * @access  private
345
     * @return  array
346
     */
347
    public function _histogramData()
348
    {
349
        if ($this->_rangeLow['x'] == min($this->_data['x'])
350
            && $this->_rangeHigh['x'] == max($this->_data['x'])
351
            && $this->_rangeLow['y'] == min($this->_data['y'])
352
            && $this->_rangeHigh['y'] == max($this->_data['y'])) {
353
            return $this->_data;
354
        }
355
356
        $data  = array();
357
        $ndata = count($this->_data['x']);
358
        for ($i = 0; $i < $ndata; $i++) {
359
            $x        = $this->_data['x'][$i];
360
            $y        = $this->_data['y'][$i];
361
            $inRangeX = ($this->_rangeLow['x'] <= $x && $this->_rangeHigh['x'] >= $x);
362
            $inRangeY = ($this->_rangeLow['y'] <= $y && $this->_rangeHigh['y'] >= $y);
363
            if ($inRangeX && $inRangeY) {
364
                $data['x'][] = $x;
365
                $data['y'][] = $y;
366
            } else {
367
                continue;
368
            }
369
        }
370
        return $data;
371
    }
372
}
373
374
// vim: ts=4:sw=4:et:
375
// vim6: fdl=1:
376