Passed
Push — master ( 10b137...2bffcf )
by Adrien
10:25
created

Averages   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Test Coverage

Coverage 98.04%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 98
dl 0
loc 251
ccs 100
cts 102
cp 0.9804
rs 8.96
c 1
b 0
f 0
wmc 43

7 Methods

Rating   Name   Duplication   Size   Complexity  
B averageA() 0 26 11
B average() 0 25 7
B modeCalc() 0 44 9
A filterArguments() 0 7 2
A mode() 0 13 2
A median() 0 20 3
B averageDeviations() 0 35 9

How to fix   Complexity   

Complex Class

Complex classes like Averages often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Averages, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6
7
class Averages extends AggregateBase
8
{
9
    /**
10
     * AVEDEV.
11
     *
12
     * Returns the average of the absolute deviations of data points from their mean.
13
     * AVEDEV is a measure of the variability in a data set.
14
     *
15
     * Excel Function:
16
     *        AVEDEV(value1[,value2[, ...]])
17
     *
18
     * @param mixed ...$args Data values
19
     *
20
     * @return float|string (string if result is an error)
21
     */
22 8
    public static function averageDeviations(...$args)
23
    {
24 8
        $aArgs = Functions::flattenArrayIndexed($args);
25
26
        // Return value
27 8
        $returnValue = 0;
28
29 8
        $aMean = self::average(...$args);
30 8
        if ($aMean === Functions::DIV0()) {
31 1
            return Functions::NAN();
32 7
        } elseif ($aMean === Functions::VALUE()) {
33 1
            return Functions::VALUE();
34
        }
35
36 6
        $aCount = 0;
37 6
        foreach ($aArgs as $k => $arg) {
38 6
            $arg = self::testAcceptedBoolean($arg, $k);
39
            // Is it a numeric value?
40
            // Strings containing numeric values are only counted if they are string literals (not cell values)
41
            //    and then only in MS Excel and in Open Office, not in Gnumeric
42 6
            if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
43
                return Functions::VALUE();
44
            }
45 6
            if (self::isAcceptedCountable($arg, $k)) {
46 6
                $returnValue += abs($arg - $aMean);
47 6
                ++$aCount;
48
            }
49
        }
50
51
        // Return
52 6
        if ($aCount === 0) {
53
            return Functions::DIV0();
54
        }
55
56 6
        return $returnValue / $aCount;
57
    }
58
59
    /**
60
     * AVERAGE.
61
     *
62
     * Returns the average (arithmetic mean) of the arguments
63
     *
64
     * Excel Function:
65
     *        AVERAGE(value1[,value2[, ...]])
66
     *
67
     * @param mixed ...$args Data values
68
     *
69
     * @return float|string (string if result is an error)
70
     */
71 77
    public static function average(...$args)
72
    {
73 77
        $returnValue = $aCount = 0;
74
75
        // Loop through arguments
76 77
        foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
77 73
            $arg = self::testAcceptedBoolean($arg, $k);
78
            // Is it a numeric value?
79
            // Strings containing numeric values are only counted if they are string literals (not cell values)
80
            //    and then only in MS Excel and in Open Office, not in Gnumeric
81 73
            if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
82 2
                return Functions::VALUE();
83
            }
84 73
            if (self::isAcceptedCountable($arg, $k)) {
85 70
                $returnValue += $arg;
86 70
                ++$aCount;
87
            }
88
        }
89
90
        // Return
91 75
        if ($aCount > 0) {
92 68
            return $returnValue / $aCount;
93
        }
94
95 8
        return Functions::DIV0();
96
    }
97
98
    /**
99
     * AVERAGEA.
100
     *
101
     * Returns the average of its arguments, including numbers, text, and logical values
102
     *
103
     * Excel Function:
104
     *        AVERAGEA(value1[,value2[, ...]])
105
     *
106
     * @param mixed ...$args Data values
107
     *
108
     * @return float|string (string if result is an error)
109
     */
110 7
    public static function averageA(...$args)
111
    {
112 7
        $returnValue = null;
113
114 7
        $aCount = 0;
115
        // Loop through arguments
116 7
        foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
117 6
            if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) {
118
            } else {
119 6
                if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
120 6
                    if (is_bool($arg)) {
121 3
                        $arg = (int) $arg;
122 5
                    } elseif (is_string($arg)) {
123 1
                        $arg = 0;
124
                    }
125 6
                    $returnValue += $arg;
126 6
                    ++$aCount;
127
                }
128
            }
129
        }
130
131 7
        if ($aCount > 0) {
132 6
            return $returnValue / $aCount;
133
        }
134
135 1
        return Functions::DIV0();
136
    }
137
138
    /**
139
     * MEDIAN.
140
     *
141
     * Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
142
     *
143
     * Excel Function:
144
     *        MEDIAN(value1[,value2[, ...]])
145
     *
146
     * @param mixed ...$args Data values
147
     *
148
     * @return float|string The result, or a string containing an error
149
     */
150 5
    public static function median(...$args)
151
    {
152 5
        $aArgs = Functions::flattenArray($args);
153
154 5
        $returnValue = Functions::NAN();
155
156 5
        $aArgs = self::filterArguments($aArgs);
157 5
        $valueCount = count($aArgs);
158 5
        if ($valueCount > 0) {
159 5
            sort($aArgs, SORT_NUMERIC);
160 5
            $valueCount = $valueCount / 2;
161 5
            if ($valueCount == floor($valueCount)) {
162 2
                $returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2;
163
            } else {
164 4
                $valueCount = floor($valueCount);
165 4
                $returnValue = $aArgs[$valueCount];
166
            }
167
        }
168
169 5
        return $returnValue;
170
    }
171
172
    /**
173
     * MODE.
174
     *
175
     * Returns the most frequently occurring, or repetitive, value in an array or range of data
176
     *
177
     * Excel Function:
178
     *        MODE(value1[,value2[, ...]])
179
     *
180
     * @param mixed ...$args Data values
181
     *
182
     * @return float|string The result, or a string containing an error
183
     */
184 9
    public static function mode(...$args)
185
    {
186 9
        $returnValue = Functions::NA();
187
188
        // Loop through arguments
189 9
        $aArgs = Functions::flattenArray($args);
190 9
        $aArgs = self::filterArguments($aArgs);
191
192 9
        if (!empty($aArgs)) {
193 8
            return self::modeCalc($aArgs);
194
        }
195
196 2
        return $returnValue;
197
    }
198
199 13
    protected static function filterArguments($args)
200
    {
201 13
        return array_filter(
202
            $args,
203 13
            function ($value) {
204
                // Is it a numeric value?
205 13
                return  (is_numeric($value)) && (!is_string($value));
206 13
            }
207
        );
208
    }
209
210
    //
211
    //    Special variant of array_count_values that isn't limited to strings and integers,
212
    //        but can work with floating point numbers as values
213
    //
214 8
    private static function modeCalc($data)
215
    {
216 8
        $frequencyArray = [];
217 8
        $index = 0;
218 8
        $maxfreq = 0;
219 8
        $maxfreqkey = '';
220 8
        $maxfreqdatum = '';
221 8
        foreach ($data as $datum) {
222 8
            $found = false;
223 8
            ++$index;
224 8
            foreach ($frequencyArray as $key => $value) {
225 8
                if ((string) $value['value'] == (string) $datum) {
226 7
                    ++$frequencyArray[$key]['frequency'];
227 7
                    $freq = $frequencyArray[$key]['frequency'];
228 7
                    if ($freq > $maxfreq) {
229 7
                        $maxfreq = $freq;
230 7
                        $maxfreqkey = $key;
231 7
                        $maxfreqdatum = $datum;
232 3
                    } elseif ($freq == $maxfreq) {
233 3
                        if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) {
234 1
                            $maxfreqkey = $key;
235 1
                            $maxfreqdatum = $datum;
236
                        }
237
                    }
238 7
                    $found = true;
239
240 7
                    break;
241
                }
242
            }
243
244 8
            if ($found === false) {
245 8
                $frequencyArray[] = [
246 8
                    'value' => $datum,
247 8
                    'frequency' => 1,
248 8
                    'index' => $index,
249
                ];
250
            }
251
        }
252
253 8
        if ($maxfreq <= 1) {
254 2
            return Functions::NA();
255
        }
256
257 7
        return $maxfreqdatum;
258
    }
259
}
260