Completed
Push — master ( bc0154...042bac )
by Mark
32s queued 28s
created

Averages   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Test Coverage

Coverage 97.96%

Importance

Changes 0
Metric Value
wmc 38
eloc 98
dl 0
loc 250
ccs 96
cts 98
cp 0.9796
rs 9.36
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A filterArguments() 0 7 2
A mode() 0 13 2
A median() 0 20 3
B averageDeviations() 0 35 9
B average() 0 25 7
A averageA() 0 25 6
B modeCalc() 0 44 9
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7
8
class Averages extends AggregateBase
9
{
10
    /**
11
     * AVEDEV.
12
     *
13
     * Returns the average of the absolute deviations of data points from their mean.
14
     * AVEDEV is a measure of the variability in a data set.
15
     *
16
     * Excel Function:
17
     *        AVEDEV(value1[,value2[, ...]])
18
     *
19
     * @param mixed ...$args Data values
20
     *
21
     * @return float|string (string if result is an error)
22
     */
23 8
    public static function averageDeviations(...$args)
24
    {
25 8
        $aArgs = Functions::flattenArrayIndexed($args);
26
27
        // Return value
28 8
        $returnValue = 0.0;
29
30 8
        $aMean = self::average(...$args);
31 8
        if ($aMean === ExcelError::DIV0()) {
32 1
            return ExcelError::NAN();
33 7
        } elseif ($aMean === ExcelError::VALUE()) {
34 2
            return ExcelError::VALUE();
35
        }
36
37 7
        $aCount = 0;
38 7
        foreach ($aArgs as $k => $arg) {
39 7
            $arg = self::testAcceptedBoolean($arg, $k);
40
            // Is it a numeric value?
41
            // Strings containing numeric values are only counted if they are string literals (not cell values)
42
            //    and then only in MS Excel and in Open Office, not in Gnumeric
43 7
            if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
44
                return ExcelError::VALUE();
45
            }
46 7
            if (self::isAcceptedCountable($arg, $k)) {
47 7
                $returnValue += abs($arg - $aMean);
48 7
                ++$aCount;
49
            }
50
        }
51
52
        // Return
53 7
        if ($aCount === 0) {
54
            return ExcelError::DIV0();
55
        }
56
57 7
        return $returnValue / $aCount;
58
    }
59
60
    /**
61
     * AVERAGE.
62
     *
63
     * Returns the average (arithmetic mean) of the arguments
64
     *
65
     * Excel Function:
66
     *        AVERAGE(value1[,value2[, ...]])
67
     *
68
     * @param mixed ...$args Data values
69
     *
70
     * @return float|string (string if result is an error)
71
     */
72 80
    public static function average(...$args)
73
    {
74 80
        $returnValue = $aCount = 0;
75
76
        // Loop through arguments
77 80
        foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
78 76
            $arg = self::testAcceptedBoolean($arg, $k);
79
            // Is it a numeric value?
80
            // Strings containing numeric values are only counted if they are string literals (not cell values)
81
            //    and then only in MS Excel and in Open Office, not in Gnumeric
82 76
            if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
83 7
                return ExcelError::VALUE();
84
            }
85 76
            if (self::isAcceptedCountable($arg, $k)) {
86 73
                $returnValue += $arg;
87 73
                ++$aCount;
88
            }
89
        }
90
91
        // Return
92 80
        if ($aCount > 0) {
93 73
            return $returnValue / $aCount;
94
        }
95
96 8
        return ExcelError::DIV0();
97
    }
98
99
    /**
100
     * AVERAGEA.
101
     *
102
     * Returns the average of its arguments, including numbers, text, and logical values
103
     *
104
     * Excel Function:
105
     *        AVERAGEA(value1[,value2[, ...]])
106
     *
107
     * @param mixed ...$args Data values
108
     *
109
     * @return float|string (string if result is an error)
110
     */
111 7
    public static function averageA(...$args)
112
    {
113 7
        $returnValue = null;
114
115 7
        $aCount = 0;
116
        // Loop through arguments
117 7
        foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
118 6
            if (is_numeric($arg)) {
119
                // do nothing
120 4
            } elseif (is_bool($arg)) {
121 3
                $arg = (int) $arg;
122 1
            } elseif (!Functions::isMatrixValue($k)) {
123 1
                $arg = 0;
124
            } else {
125 1
                return ExcelError::VALUE();
126
            }
127 6
            $returnValue += $arg;
128 6
            ++$aCount;
129
        }
130
131 7
        if ($aCount > 0) {
132 6
            return $returnValue / $aCount;
133
        }
134
135 1
        return ExcelError::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 = ExcelError::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 = ExcelError::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
            }
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
                    'value' => $datum,
247
                    'frequency' => 1,
248
                    'index' => $index,
249
                ];
250
            }
251
        }
252
253 8
        if ($maxfreq <= 1) {
254 2
            return ExcelError::NA();
255
        }
256
257 7
        return $maxfreqdatum;
258
    }
259
}
260