Issues (64)

src/Histogram/Histogram4D.php (8 issues)

1
<?php
2
namespace HuasoFoundries\Histogram;
3
4
/**
5
 * Class to generate 4D histograms from tri-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 Histogram4D extends AbstractHistogram
16
{
17
18
    /**
19
     * Constructor for Math_Histogram4D
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
    public function __construct($type = self::HISTOGRAM_SIMPLE, $binOptions = "")
31
    {
32
        $this->setType($type);
33
        try {
34
            $this->setBinOptions($binOptions);
35
        } catch (\PEAR_Exception $e) {
36
            // Falling back to default options
37
        }
38
    }
39
40
    /**
41
     * Sets the binning options. Overrides parent's method.
42
     *
43
     * @access  public
44
     * @param   array $binOptions  an array of options for binning the data
45
     * @return  void
46
     */
47
    public function setBinOptions($binOptions)
48
    {
49
        if ($this->_validBinOptions($binOptions)) {
50
            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...
51
        } else {
52
            throw new \PEAR_Exception("incorrect options array");
53
        }
54
    }
55
56
    /**
57
     * Sets the data to be processed. The data will be validated to
58
     * be a simple tri-dimensional numerical array
59
     *
60
     * @access  public
61
     * @param   array   $data   the numeric array
62
     * @return  mixed   boolean true on success, a \PEAR_Error object otherwise
63
     *
64
     * @see _clear()
65
     * @see Math_AbstractHistogram::getData()
66
     * @see Math_AbstractHistogram
67
     * @see getHistogramData()
68
     */
69
    public function setData($data)
70
    {
71
        $this->_clear();
72
        if (!$this->_validData($data)) {
73
            throw new \PEAR_Exception("array of numeric coordinates expected");
74
        }
75
76
        $this->_data       = $data;
77
        list($xMin, $xMax) = $this->_getMinMax('x');
0 ignored issues
show
'x' of type string is incompatible with the type array expected by parameter $elem of HuasoFoundries\Histogram\Histogram4D::_getMinMax(). ( Ignorable by Annotation )

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

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