├── .github-write-test
├── .github
├── FUNDING.yml
└── workflows
│ └── go.yml
├── .gitignore
├── CHANGELOG.md
├── DOCUMENTATION.md
├── LICENSE
├── Makefile
├── README.md
├── correlation.go
├── correlation_test.go
├── cumulative_sum.go
├── cumulative_sum_test.go
├── data.go
├── data_test.go
├── describe.go
├── describe_test.go
├── deviation.go
├── deviation_test.go
├── distances.go
├── distances_test.go
├── doc.go
├── entropy.go
├── entropy_test.go
├── errors.go
├── errors_test.go
├── examples
├── README.md
├── main.go
└── methods.go
├── examples_test.go
├── geometric_distribution.go
├── geometric_distribution_test.go
├── go.mod
├── legacy.go
├── legacy_test.go
├── load.go
├── load_test.go
├── max.go
├── max_test.go
├── mean.go
├── mean_test.go
├── median.go
├── median_test.go
├── min.go
├── min_test.go
├── mode.go
├── mode_test.go
├── nist_test.go
├── norm.go
├── norm_test.go
├── outlier.go
├── outlier_test.go
├── percentile.go
├── percentile_test.go
├── quartile.go
├── quartile_test.go
├── ranksum.go
├── ranksum_test.go
├── regression.go
├── regression_test.go
├── round.go
├── round_test.go
├── sample.go
├── sample_test.go
├── sigmoid.go
├── sigmoid_test.go
├── softmax.go
├── softmax_test.go
├── sum.go
├── sum_test.go
├── test_utils_test.go
├── util.go
├── util_test.go
├── variance.go
└── variance_test.go
/.github-write-test:
--------------------------------------------------------------------------------
1 | test
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [montanaflynn]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
15 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a golang project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3 |
4 | name: Go
5 | on: [push, pull_request]
6 | jobs:
7 | test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 |
12 | - name: Set up Go
13 | uses: actions/setup-go@v3
14 | with:
15 | go-version: "stable"
16 |
17 | - name: Run test with coverage
18 | run: go test -race -coverprofile=coverage.txt -covermode=atomic
19 |
20 | - name: Upload coverage to Codecov
21 | uses: codecov/codecov-action@v3
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage.out
2 | coverage.txt
3 | release-notes.txt
4 | .directory
5 | .chglog
6 | .vscode
7 | .DS_Store
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [Unreleased]
3 |
4 |
5 |
6 | ## [v0.7.1] - 2023-05-11
7 | ### Add
8 | - Add describe functions ([#77](https://github.com/montanaflynn/stats/issues/77))
9 |
10 | ### Update
11 | - Update .gitignore
12 | - Update README.md, LICENSE and DOCUMENTATION.md files
13 | - Update github action go workflow to run on push
14 |
15 |
16 |
17 | ## [v0.7.0] - 2023-01-08
18 | ### Add
19 | - Add geometric distribution functions ([#75](https://github.com/montanaflynn/stats/issues/75))
20 | - Add GitHub action go workflow
21 |
22 | ### Remove
23 | - Remove travis CI config
24 |
25 | ### Update
26 | - Update changelog with v0.7.0 changes
27 | - Update changelog with v0.7.0 changes
28 | - Update github action go workflow
29 | - Update geometric distribution tests
30 |
31 |
32 |
33 | ## [v0.6.6] - 2021-04-26
34 | ### Add
35 | - Add support for string and io.Reader in LoadRawData (pr [#68](https://github.com/montanaflynn/stats/issues/68))
36 | - Add latest versions of Go to test against
37 |
38 | ### Update
39 | - Update changelog with v0.6.6 changes
40 |
41 | ### Use
42 | - Use math.Sqrt in StandardDeviation (PR [#64](https://github.com/montanaflynn/stats/issues/64))
43 |
44 |
45 |
46 | ## [v0.6.5] - 2021-02-21
47 | ### Add
48 | - Add Float64Data.Quartiles documentation
49 | - Add Quartiles method to Float64Data type (issue [#60](https://github.com/montanaflynn/stats/issues/60))
50 |
51 | ### Fix
52 | - Fix make release changelog command and add changelog history
53 |
54 | ### Update
55 | - Update changelog with v0.6.5 changes
56 | - Update changelog with v0.6.4 changes
57 | - Update README.md links to CHANGELOG.md and DOCUMENTATION.md
58 | - Update README.md and Makefile with new release commands
59 |
60 |
61 |
62 | ## [v0.6.4] - 2021-01-13
63 | ### Fix
64 | - Fix failing tests due to precision errors on arm64 ([#58](https://github.com/montanaflynn/stats/issues/58))
65 |
66 | ### Update
67 | - Update changelog with v0.6.4 changes
68 | - Update examples directory to include a README.md used for synopsis
69 | - Update go.mod to include go version where modules are enabled by default
70 | - Update changelog with v0.6.3 changes
71 |
72 |
73 |
74 | ## [v0.6.3] - 2020-02-18
75 | ### Add
76 | - Add creating and committing changelog to Makefile release directive
77 | - Add release-notes.txt and .chglog directory to .gitignore
78 |
79 | ### Update
80 | - Update exported tests to use import for better example documentation
81 | - Update documentation using godoc2md
82 | - Update changelog with v0.6.2 release
83 |
84 |
85 |
86 | ## [v0.6.2] - 2020-02-18
87 | ### Fix
88 | - Fix linting errcheck warnings in go benchmarks
89 |
90 | ### Update
91 | - Update Makefile release directive to use correct release name
92 |
93 |
94 |
95 | ## [v0.6.1] - 2020-02-18
96 | ### Add
97 | - Add StableSample function signature to readme
98 |
99 | ### Fix
100 | - Fix linting warnings for normal distribution functions formatting and tests
101 |
102 | ### Update
103 | - Update documentation links and rename DOC.md to DOCUMENTATION.md
104 | - Update README with link to pkg.go.dev reference and release section
105 | - Update Makefile with new changelog, docs, and release directives
106 | - Update DOC.md links to GitHub source code
107 | - Update doc.go comment and add DOC.md package reference file
108 | - Update changelog using git-chglog
109 |
110 |
111 |
112 | ## [v0.6.0] - 2020-02-17
113 | ### Add
114 | - Add Normal Distribution Functions ([#56](https://github.com/montanaflynn/stats/issues/56))
115 | - Add previous versions of Go to travis CI config
116 | - Add check for distinct values in Mode function ([#51](https://github.com/montanaflynn/stats/issues/51))
117 | - Add StableSample function ([#48](https://github.com/montanaflynn/stats/issues/48))
118 | - Add doc.go file to show description and usage on godoc.org
119 | - Add comments to new error and legacy error variables
120 | - Add ExampleRound function to tests
121 | - Add go.mod file for module support
122 | - Add Sigmoid, SoftMax and Entropy methods and tests
123 | - Add Entropy documentation, example and benchmarks
124 | - Add Entropy function ([#44](https://github.com/montanaflynn/stats/issues/44))
125 |
126 | ### Fix
127 | - Fix percentile when only one element ([#47](https://github.com/montanaflynn/stats/issues/47))
128 | - Fix AutoCorrelation name in comments and remove unneeded Sprintf
129 |
130 | ### Improve
131 | - Improve documentation section with command comments
132 |
133 | ### Remove
134 | - Remove very old versions of Go in travis CI config
135 | - Remove boolean comparison to get rid of gometalinter warning
136 |
137 | ### Update
138 | - Update license dates
139 | - Update Distance functions signatures to use Float64Data
140 | - Update Sigmoid examples
141 | - Update error names with backward compatibility
142 |
143 | ### Use
144 | - Use relative link to examples/main.go
145 | - Use a single var block for exported errors
146 |
147 |
148 |
149 | ## [v0.5.0] - 2019-01-16
150 | ### Add
151 | - Add Sigmoid and Softmax functions
152 |
153 | ### Fix
154 | - Fix syntax highlighting and add CumulativeSum func
155 |
156 |
157 |
158 | ## [v0.4.0] - 2019-01-14
159 | ### Add
160 | - Add goreport badge and documentation section to README.md
161 | - Add Examples to test files
162 | - Add AutoCorrelation and nist tests
163 | - Add String method to statsErr type
164 | - Add Y coordinate error for ExponentialRegression
165 | - Add syntax highlighting ([#43](https://github.com/montanaflynn/stats/issues/43))
166 | - Add CumulativeSum ([#40](https://github.com/montanaflynn/stats/issues/40))
167 | - Add more tests and rename distance files
168 | - Add coverage and benchmarks to azure pipeline
169 | - Add go tests to azure pipeline
170 |
171 | ### Change
172 | - Change travis tip alias to master
173 | - Change codecov to coveralls for code coverage
174 |
175 | ### Fix
176 | - Fix a few lint warnings
177 | - Fix example error
178 |
179 | ### Improve
180 | - Improve test coverage of distance functions
181 |
182 | ### Only
183 | - Only run travis on stable and tip versions
184 | - Only check code coverage on tip
185 |
186 | ### Remove
187 | - Remove azure CI pipeline
188 | - Remove unnecessary type conversions
189 |
190 | ### Return
191 | - Return EmptyInputErr instead of EmptyInput
192 |
193 | ### Set
194 | - Set up CI with Azure Pipelines
195 |
196 |
197 |
198 | ## [0.3.0] - 2017-12-02
199 | ### Add
200 | - Add Chebyshev, Manhattan, Euclidean and Minkowski distance functions ([#35](https://github.com/montanaflynn/stats/issues/35))
201 | - Add function for computing chebyshev distance. ([#34](https://github.com/montanaflynn/stats/issues/34))
202 | - Add support for time.Duration
203 | - Add LoadRawData to docs and examples
204 | - Add unit test for edge case that wasn't covered
205 | - Add unit tests for edge cases that weren't covered
206 | - Add pearson alias delegating to correlation
207 | - Add CovariancePopulation to Float64Data
208 | - Add pearson product-moment correlation coefficient
209 | - Add population covariance
210 | - Add random slice benchmarks
211 | - Add all applicable functions as methods to Float64Data type
212 | - Add MIT license badge
213 | - Add link to examples/methods.go
214 | - Add Protips for usage and documentation sections
215 | - Add tests for rounding up
216 | - Add webdoc target and remove linting from test target
217 | - Add example usage and consolidate contributing information
218 |
219 | ### Added
220 | - Added MedianAbsoluteDeviation
221 |
222 | ### Annotation
223 | - Annotation spelling error
224 |
225 | ### Auto
226 | - auto commit
227 | - auto commit
228 |
229 | ### Calculate
230 | - Calculate correlation with sdev and covp
231 |
232 | ### Clean
233 | - Clean up README.md and add info for offline docs
234 |
235 | ### Consolidated
236 | - Consolidated all error values.
237 |
238 | ### Fix
239 | - Fix Percentile logic
240 | - Fix InterQuartileRange method test
241 | - Fix zero percent bug and add test
242 | - Fix usage example output typos
243 |
244 | ### Improve
245 | - Improve bounds checking in Percentile
246 | - Improve error log messaging
247 |
248 | ### Imput
249 | - Imput -> Input
250 |
251 | ### Include
252 | - Include alternative way to set Float64Data in example
253 |
254 | ### Make
255 | - Make various changes to README.md
256 |
257 | ### Merge
258 | - Merge branch 'master' of github.com:montanaflynn/stats
259 | - Merge master
260 |
261 | ### Mode
262 | - Mode calculation fix and tests
263 |
264 | ### Realized
265 | - Realized the obvious efficiency gains of ignoring the unique numbers at the beginning of the slice. Benchmark joy ensued.
266 |
267 | ### Refactor
268 | - Refactor testing of Round()
269 | - Refactor setting Coordinate y field using Exp in place of Pow
270 | - Refactor Makefile and add docs target
271 |
272 | ### Remove
273 | - Remove deep links to types and functions
274 |
275 | ### Rename
276 | - Rename file from types to data
277 |
278 | ### Retrieve
279 | - Retrieve InterQuartileRange for the Float64Data.
280 |
281 | ### Split
282 | - Split up stats.go into separate files
283 |
284 | ### Support
285 | - Support more types on LoadRawData() ([#36](https://github.com/montanaflynn/stats/issues/36))
286 |
287 | ### Switch
288 | - Switch default and check targets
289 |
290 | ### Update
291 | - Update Readme
292 | - Update example methods and some text
293 | - Update README and include Float64Data type method examples
294 |
295 | ### Pull Requests
296 | - Merge pull request [#32](https://github.com/montanaflynn/stats/issues/32) from a-robinson/percentile
297 | - Merge pull request [#30](https://github.com/montanaflynn/stats/issues/30) from montanaflynn/fix-test
298 | - Merge pull request [#29](https://github.com/montanaflynn/stats/issues/29) from edupsousa/master
299 | - Merge pull request [#27](https://github.com/montanaflynn/stats/issues/27) from andrey-yantsen/fix-percentile-out-of-bounds
300 | - Merge pull request [#25](https://github.com/montanaflynn/stats/issues/25) from kazhuravlev/patch-1
301 | - Merge pull request [#22](https://github.com/montanaflynn/stats/issues/22) from JanBerktold/time-duration
302 | - Merge pull request [#24](https://github.com/montanaflynn/stats/issues/24) from alouche/master
303 | - Merge pull request [#21](https://github.com/montanaflynn/stats/issues/21) from brydavis/master
304 | - Merge pull request [#19](https://github.com/montanaflynn/stats/issues/19) from ginodeis/mode-bug
305 | - Merge pull request [#17](https://github.com/montanaflynn/stats/issues/17) from Kunde21/master
306 | - Merge pull request [#3](https://github.com/montanaflynn/stats/issues/3) from montanaflynn/master
307 | - Merge pull request [#2](https://github.com/montanaflynn/stats/issues/2) from montanaflynn/master
308 | - Merge pull request [#13](https://github.com/montanaflynn/stats/issues/13) from toashd/pearson
309 | - Merge pull request [#12](https://github.com/montanaflynn/stats/issues/12) from alixaxel/MAD
310 | - Merge pull request [#1](https://github.com/montanaflynn/stats/issues/1) from montanaflynn/master
311 | - Merge pull request [#11](https://github.com/montanaflynn/stats/issues/11) from Kunde21/modeMemReduce
312 | - Merge pull request [#10](https://github.com/montanaflynn/stats/issues/10) from Kunde21/ModeRewrite
313 |
314 |
315 |
316 | ## [0.2.0] - 2015-10-14
317 | ### Add
318 | - Add Makefile with gometalinter, testing, benchmarking and coverage report targets
319 | - Add comments describing functions and structs
320 | - Add Correlation func
321 | - Add Covariance func
322 | - Add tests for new function shortcuts
323 | - Add StandardDeviation function as a shortcut to StandardDeviationPopulation
324 | - Add Float64Data and Series types
325 |
326 | ### Change
327 | - Change Sample to return a standard []float64 type
328 |
329 | ### Fix
330 | - Fix broken link to Makefile
331 | - Fix broken link and simplify code coverage reporting command
332 | - Fix go vet warning about printf type placeholder
333 | - Fix failing codecov test coverage reporting
334 | - Fix link to CHANGELOG.md
335 |
336 | ### Fixed
337 | - Fixed typographical error, changed accomdate to accommodate in README.
338 |
339 | ### Include
340 | - Include Variance and StandardDeviation shortcuts
341 |
342 | ### Pass
343 | - Pass gometalinter
344 |
345 | ### Refactor
346 | - Refactor Variance function to be the same as population variance
347 |
348 | ### Release
349 | - Release version 0.2.0
350 |
351 | ### Remove
352 | - Remove unneeded do packages and update cover URL
353 | - Remove sudo from pip install
354 |
355 | ### Reorder
356 | - Reorder functions and sections
357 |
358 | ### Revert
359 | - Revert to legacy containers to preserve go1.1 testing
360 |
361 | ### Switch
362 | - Switch from legacy to container-based CI infrastructure
363 |
364 | ### Update
365 | - Update contributing instructions and mention Makefile
366 |
367 | ### Pull Requests
368 | - Merge pull request [#5](https://github.com/montanaflynn/stats/issues/5) from orthographic-pedant/spell_check/accommodate
369 |
370 |
371 |
372 | ## [0.1.0] - 2015-08-19
373 | ### Add
374 | - Add CONTRIBUTING.md
375 |
376 | ### Rename
377 | - Rename functions while preserving backwards compatibility
378 |
379 |
380 |
381 | ## 0.0.9 - 2015-08-18
382 | ### Add
383 | - Add HarmonicMean func
384 | - Add GeometricMean func
385 | - Add .gitignore to avoid commiting test coverage report
386 | - Add Outliers stuct and QuantileOutliers func
387 | - Add Interquartile Range, Midhinge and Trimean examples
388 | - Add Trimean
389 | - Add Midhinge
390 | - Add Inter Quartile Range
391 | - Add a unit test to check for an empty slice error
392 | - Add Quantiles struct and Quantile func
393 | - Add more tests and fix a typo
394 | - Add Golang 1.5 to build tests
395 | - Add a standard MIT license file
396 | - Add basic benchmarking
397 | - Add regression models
398 | - Add codecov token
399 | - Add codecov
400 | - Add check for slices with a single item
401 | - Add coverage tests
402 | - Add back previous Go versions to Travis CI
403 | - Add Travis CI
404 | - Add GoDoc badge
405 | - Add Percentile and Float64ToInt functions
406 | - Add another rounding test for whole numbers
407 | - Add build status badge
408 | - Add code coverage badge
409 | - Add test for NaN, achieving 100% code coverage
410 | - Add round function
411 | - Add standard deviation function
412 | - Add sum function
413 |
414 | ### Add
415 | - add tests for sample
416 | - add sample
417 |
418 | ### Added
419 | - Added sample and population variance and deviation functions
420 | - Added README
421 |
422 | ### Adjust
423 | - Adjust API ordering
424 |
425 | ### Avoid
426 | - Avoid unintended consequence of using sort
427 |
428 | ### Better
429 | - Better performing min/max
430 | - Better description
431 |
432 | ### Change
433 | - Change package path to potentially fix a bug in earlier versions of Go
434 |
435 | ### Clean
436 | - Clean up README and add some more information
437 | - Clean up test error
438 |
439 | ### Consistent
440 | - Consistent empty slice error messages
441 | - Consistent var naming
442 | - Consistent func declaration
443 |
444 | ### Convert
445 | - Convert ints to floats
446 |
447 | ### Duplicate
448 | - Duplicate packages for all versions
449 |
450 | ### Export
451 | - Export Coordinate struct fields
452 |
453 | ### First
454 | - First commit
455 |
456 | ### Fix
457 | - Fix copy pasta mistake testing the wrong function
458 | - Fix error message
459 | - Fix usage output and edit API doc section
460 | - Fix testing edgecase where map was in wrong order
461 | - Fix usage example
462 | - Fix usage examples
463 |
464 | ### Include
465 | - Include the Nearest Rank method of calculating percentiles
466 |
467 | ### More
468 | - More commenting
469 |
470 | ### Move
471 | - Move GoDoc link to top
472 |
473 | ### Redirect
474 | - Redirect kills newer versions of Go
475 |
476 | ### Refactor
477 | - Refactor code and error checking
478 |
479 | ### Remove
480 | - Remove unnecassary typecasting in sum func
481 | - Remove cover since it doesn't work for later versions of go
482 | - Remove golint and gocoveralls
483 |
484 | ### Rename
485 | - Rename StandardDev to StdDev
486 | - Rename StandardDev to StdDev
487 |
488 | ### Return
489 | - Return errors for all functions
490 |
491 | ### Run
492 | - Run go fmt to clean up formatting
493 |
494 | ### Simplify
495 | - Simplify min/max function
496 |
497 | ### Start
498 | - Start with minimal tests
499 |
500 | ### Switch
501 | - Switch wercker to travis and update todos
502 |
503 | ### Table
504 | - table testing style
505 |
506 | ### Update
507 | - Update README and move the example main.go into it's own file
508 | - Update TODO list
509 | - Update README
510 | - Update usage examples and todos
511 |
512 | ### Use
513 | - Use codecov the recommended way
514 | - Use correct string formatting types
515 |
516 | ### Pull Requests
517 | - Merge pull request [#4](https://github.com/montanaflynn/stats/issues/4) from saromanov/sample
518 |
519 |
520 | [Unreleased]: https://github.com/montanaflynn/stats/compare/v0.7.1...HEAD
521 | [v0.7.1]: https://github.com/montanaflynn/stats/compare/v0.7.0...v0.7.1
522 | [v0.7.0]: https://github.com/montanaflynn/stats/compare/v0.6.6...v0.7.0
523 | [v0.6.6]: https://github.com/montanaflynn/stats/compare/v0.6.5...v0.6.6
524 | [v0.6.5]: https://github.com/montanaflynn/stats/compare/v0.6.4...v0.6.5
525 | [v0.6.4]: https://github.com/montanaflynn/stats/compare/v0.6.3...v0.6.4
526 | [v0.6.3]: https://github.com/montanaflynn/stats/compare/v0.6.2...v0.6.3
527 | [v0.6.2]: https://github.com/montanaflynn/stats/compare/v0.6.1...v0.6.2
528 | [v0.6.1]: https://github.com/montanaflynn/stats/compare/v0.6.0...v0.6.1
529 | [v0.6.0]: https://github.com/montanaflynn/stats/compare/v0.5.0...v0.6.0
530 | [v0.5.0]: https://github.com/montanaflynn/stats/compare/v0.4.0...v0.5.0
531 | [v0.4.0]: https://github.com/montanaflynn/stats/compare/0.3.0...v0.4.0
532 | [0.3.0]: https://github.com/montanaflynn/stats/compare/0.2.0...0.3.0
533 | [0.2.0]: https://github.com/montanaflynn/stats/compare/0.1.0...0.2.0
534 | [0.1.0]: https://github.com/montanaflynn/stats/compare/0.0.9...0.1.0
535 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2023 Montana Flynn (https://montanaflynn.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all
2 |
3 | default: test lint
4 |
5 | format:
6 | go fmt .
7 |
8 | test:
9 | go test -race
10 |
11 | check: format test
12 |
13 | benchmark:
14 | go test -bench=. -benchmem
15 |
16 | coverage:
17 | go test -coverprofile=coverage.out
18 | go tool cover -html="coverage.out"
19 |
20 | lint: format
21 | golangci-lint run .
22 |
23 | docs:
24 | godoc2md github.com/montanaflynn/stats | sed -e s#src/target/##g > DOCUMENTATION.md
25 |
26 | release:
27 | git-chglog --output CHANGELOG.md --next-tag ${TAG}
28 | git add CHANGELOG.md
29 | git commit -m "Update changelog with ${TAG} changes"
30 | git tag ${TAG}
31 | git-chglog $(TAG) | tail -n +4 | gsed '1s/^/$(TAG)\n/gm' > release-notes.txt
32 | git push origin master ${TAG}
33 | hub release create --copy -F release-notes.txt ${TAG}
34 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stats - Golang Statistics Package
2 |
3 | [![][action-svg]][action-url] [![][codecov-svg]][codecov-url] [![][goreport-svg]][goreport-url] [![][godoc-svg]][godoc-url] [![][pkggodev-svg]][pkggodev-url] [![][license-svg]][license-url]
4 |
5 | A well tested and comprehensive Golang statistics library / package / module with no dependencies.
6 |
7 | If you have any suggestions, problems or bug reports please [create an issue](https://github.com/montanaflynn/stats/issues) and I'll do my best to accommodate you. In addition simply starring the repo would show your support for the project and be very much appreciated!
8 |
9 | ## Installation
10 |
11 | ```
12 | go get github.com/montanaflynn/stats
13 | ```
14 |
15 | ## Example Usage
16 |
17 | All the functions can be seen in [examples/main.go](examples/main.go) but here's a little taste:
18 |
19 | ```go
20 | // start with some source data to use
21 | data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8}
22 |
23 | // you could also use different types like this
24 | // data := stats.LoadRawData([]int{1, 2, 3, 4, 5})
25 | // data := stats.LoadRawData([]interface{}{1.1, "2", 3})
26 | // etc...
27 |
28 | median, _ := stats.Median(data)
29 | fmt.Println(median) // 3.65
30 |
31 | roundedMedian, _ := stats.Round(median, 0)
32 | fmt.Println(roundedMedian) // 4
33 | ```
34 |
35 | ## Documentation
36 |
37 | The entire API documentation is available on [GoDoc.org](http://godoc.org/github.com/montanaflynn/stats) or [pkg.go.dev](https://pkg.go.dev/github.com/montanaflynn/stats).
38 |
39 | You can also view docs offline with the following commands:
40 |
41 | ```
42 | # Command line
43 | godoc . # show all exported apis
44 | godoc . Median # show a single function
45 | godoc -ex . Round # show function with example
46 | godoc . Float64Data # show the type and methods
47 |
48 | # Local website
49 | godoc -http=:4444 # start the godoc server on port 4444
50 | open http://localhost:4444/pkg/github.com/montanaflynn/stats/
51 | ```
52 |
53 | The exported API is as follows:
54 |
55 | ```go
56 | var (
57 | ErrEmptyInput = statsError{"Input must not be empty."}
58 | ErrNaN = statsError{"Not a number."}
59 | ErrNegative = statsError{"Must not contain negative values."}
60 | ErrZero = statsError{"Must not contain zero values."}
61 | ErrBounds = statsError{"Input is outside of range."}
62 | ErrSize = statsError{"Must be the same length."}
63 | ErrInfValue = statsError{"Value is infinite."}
64 | ErrYCoord = statsError{"Y Value must be greater than zero."}
65 | )
66 |
67 | func Round(input float64, places int) (rounded float64, err error) {}
68 |
69 | type Float64Data []float64
70 |
71 | func LoadRawData(raw interface{}) (f Float64Data) {}
72 |
73 | func AutoCorrelation(data Float64Data, lags int) (float64, error) {}
74 | func ChebyshevDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {}
75 | func Correlation(data1, data2 Float64Data) (float64, error) {}
76 | func Covariance(data1, data2 Float64Data) (float64, error) {}
77 | func CovariancePopulation(data1, data2 Float64Data) (float64, error) {}
78 | func CumulativeSum(input Float64Data) ([]float64, error) {}
79 | func Describe(input Float64Data, allowNaN bool, percentiles *[]float64) (*Description, error) {}
80 | func DescribePercentileFunc(input Float64Data, allowNaN bool, percentiles *[]float64, percentileFunc func(Float64Data, float64) (float64, error)) (*Description, error) {}
81 | func Entropy(input Float64Data) (float64, error) {}
82 | func EuclideanDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {}
83 | func GeometricMean(input Float64Data) (float64, error) {}
84 | func HarmonicMean(input Float64Data) (float64, error) {}
85 | func InterQuartileRange(input Float64Data) (float64, error) {}
86 | func ManhattanDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {}
87 | func Max(input Float64Data) (max float64, err error) {}
88 | func Mean(input Float64Data) (float64, error) {}
89 | func Median(input Float64Data) (median float64, err error) {}
90 | func MedianAbsoluteDeviation(input Float64Data) (mad float64, err error) {}
91 | func MedianAbsoluteDeviationPopulation(input Float64Data) (mad float64, err error) {}
92 | func Midhinge(input Float64Data) (float64, error) {}
93 | func Min(input Float64Data) (min float64, err error) {}
94 | func MinkowskiDistance(dataPointX, dataPointY Float64Data, lambda float64) (distance float64, err error) {}
95 | func Mode(input Float64Data) (mode []float64, err error) {}
96 | func NormBoxMullerRvs(loc float64, scale float64, size int) []float64 {}
97 | func NormCdf(x float64, loc float64, scale float64) float64 {}
98 | func NormEntropy(loc float64, scale float64) float64 {}
99 | func NormFit(data []float64) [2]float64{}
100 | func NormInterval(alpha float64, loc float64, scale float64 ) [2]float64 {}
101 | func NormIsf(p float64, loc float64, scale float64) (x float64) {}
102 | func NormLogCdf(x float64, loc float64, scale float64) float64 {}
103 | func NormLogPdf(x float64, loc float64, scale float64) float64 {}
104 | func NormLogSf(x float64, loc float64, scale float64) float64 {}
105 | func NormMean(loc float64, scale float64) float64 {}
106 | func NormMedian(loc float64, scale float64) float64 {}
107 | func NormMoment(n int, loc float64, scale float64) float64 {}
108 | func NormPdf(x float64, loc float64, scale float64) float64 {}
109 | func NormPpf(p float64, loc float64, scale float64) (x float64) {}
110 | func NormPpfRvs(loc float64, scale float64, size int) []float64 {}
111 | func NormSf(x float64, loc float64, scale float64) float64 {}
112 | func NormStats(loc float64, scale float64, moments string) []float64 {}
113 | func NormStd(loc float64, scale float64) float64 {}
114 | func NormVar(loc float64, scale float64) float64 {}
115 | func Pearson(data1, data2 Float64Data) (float64, error) {}
116 | func Percentile(input Float64Data, percent float64) (percentile float64, err error) {}
117 | func PercentileNearestRank(input Float64Data, percent float64) (percentile float64, err error) {}
118 | func PopulationVariance(input Float64Data) (pvar float64, err error) {}
119 | func Sample(input Float64Data, takenum int, replacement bool) ([]float64, error) {}
120 | func SampleVariance(input Float64Data) (svar float64, err error) {}
121 | func Sigmoid(input Float64Data) ([]float64, error) {}
122 | func SoftMax(input Float64Data) ([]float64, error) {}
123 | func StableSample(input Float64Data, takenum int) ([]float64, error) {}
124 | func StandardDeviation(input Float64Data) (sdev float64, err error) {}
125 | func StandardDeviationPopulation(input Float64Data) (sdev float64, err error) {}
126 | func StandardDeviationSample(input Float64Data) (sdev float64, err error) {}
127 | func StdDevP(input Float64Data) (sdev float64, err error) {}
128 | func StdDevS(input Float64Data) (sdev float64, err error) {}
129 | func Sum(input Float64Data) (sum float64, err error) {}
130 | func Trimean(input Float64Data) (float64, error) {}
131 | func VarP(input Float64Data) (sdev float64, err error) {}
132 | func VarS(input Float64Data) (sdev float64, err error) {}
133 | func Variance(input Float64Data) (sdev float64, err error) {}
134 | func ProbGeom(a int, b int, p float64) (prob float64, err error) {}
135 | func ExpGeom(p float64) (exp float64, err error) {}
136 | func VarGeom(p float64) (exp float64, err error) {}
137 |
138 | type Coordinate struct {
139 | X, Y float64
140 | }
141 |
142 | type Series []Coordinate
143 |
144 | func ExponentialRegression(s Series) (regressions Series, err error) {}
145 | func LinearRegression(s Series) (regressions Series, err error) {}
146 | func LogarithmicRegression(s Series) (regressions Series, err error) {}
147 |
148 | type Outliers struct {
149 | Mild Float64Data
150 | Extreme Float64Data
151 | }
152 |
153 | type Quartiles struct {
154 | Q1 float64
155 | Q2 float64
156 | Q3 float64
157 | }
158 |
159 | func Quartile(input Float64Data) (Quartiles, error) {}
160 | func QuartileOutliers(input Float64Data) (Outliers, error) {}
161 | ```
162 |
163 | ## Contributing
164 |
165 | Pull request are always welcome no matter how big or small. I've included a [Makefile](https://github.com/montanaflynn/stats/blob/master/Makefile) that has a lot of helper targets for common actions such as linting, testing, code coverage reporting and more.
166 |
167 | 1. Fork the repo and clone your fork
168 | 2. Create new branch (`git checkout -b some-thing`)
169 | 3. Make the desired changes
170 | 4. Ensure tests pass (`go test -cover` or `make test`)
171 | 5. Run lint and fix problems (`go vet .` or `make lint`)
172 | 6. Commit changes (`git commit -am 'Did something'`)
173 | 7. Push branch (`git push origin some-thing`)
174 | 8. Submit pull request
175 |
176 | To make things as seamless as possible please also consider the following steps:
177 |
178 | - Update `examples/main.go` with a simple example of the new feature
179 | - Update `README.md` documentation section with any new exported API
180 | - Keep 100% code coverage (you can check with `make coverage`)
181 | - Squash commits into single units of work with `git rebase -i new-feature`
182 |
183 | ## Releasing
184 |
185 | This is not required by contributors and mostly here as a reminder to myself as the maintainer of this repo. To release a new version we should update the [CHANGELOG.md](/CHANGELOG.md) and [DOCUMENTATION.md](/DOCUMENTATION.md).
186 |
187 | First install the tools used to generate the markdown files and release:
188 |
189 | ```
190 | go install github.com/davecheney/godoc2md@latest
191 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
192 | brew tap git-chglog/git-chglog
193 | brew install gnu-sed hub git-chglog
194 | ```
195 |
196 | Then you can run these `make` directives:
197 |
198 | ```
199 | # Generate DOCUMENTATION.md
200 | make docs
201 | ```
202 |
203 | Then we can create a [CHANGELOG.md](/CHANGELOG.md) a new git tag and a github release:
204 |
205 | ```
206 | make release TAG=v0.x.x
207 | ```
208 |
209 | To authenticate `hub` for the release you will need to create a personal access token and use it as the password when it's requested.
210 |
211 | ## MIT License
212 |
213 | Copyright (c) 2014-2023 Montana Flynn (https://montanaflynn.com)
214 |
215 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
216 |
217 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
218 |
219 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORpublicS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
220 |
221 | [action-url]: https://github.com/montanaflynn/stats/actions
222 | [action-svg]: https://img.shields.io/github/actions/workflow/status/montanaflynn/stats/go.yml
223 |
224 | [codecov-url]: https://app.codecov.io/gh/montanaflynn/stats
225 | [codecov-svg]: https://img.shields.io/codecov/c/github/montanaflynn/stats?token=wnw8dActnH
226 |
227 | [goreport-url]: https://goreportcard.com/report/github.com/montanaflynn/stats
228 | [goreport-svg]: https://goreportcard.com/badge/github.com/montanaflynn/stats
229 |
230 | [godoc-url]: https://godoc.org/github.com/montanaflynn/stats
231 | [godoc-svg]: https://godoc.org/github.com/montanaflynn/stats?status.svg
232 |
233 | [pkggodev-url]: https://pkg.go.dev/github.com/montanaflynn/stats
234 | [pkggodev-svg]: https://gistcdn.githack.com/montanaflynn/b02f1d78d8c0de8435895d7e7cd0d473/raw/17f2a5a69f1323ecd42c00e0683655da96d9ecc8/badge.svg
235 |
236 | [license-url]: https://github.com/montanaflynn/stats/blob/master/LICENSE
237 | [license-svg]: https://img.shields.io/badge/license-MIT-blue.svg
238 |
--------------------------------------------------------------------------------
/correlation.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | // Correlation describes the degree of relationship between two sets of data
8 | func Correlation(data1, data2 Float64Data) (float64, error) {
9 |
10 | l1 := data1.Len()
11 | l2 := data2.Len()
12 |
13 | if l1 == 0 || l2 == 0 {
14 | return math.NaN(), EmptyInputErr
15 | }
16 |
17 | if l1 != l2 {
18 | return math.NaN(), SizeErr
19 | }
20 |
21 | sdev1, _ := StandardDeviationPopulation(data1)
22 | sdev2, _ := StandardDeviationPopulation(data2)
23 |
24 | if sdev1 == 0 || sdev2 == 0 {
25 | return 0, nil
26 | }
27 |
28 | covp, _ := CovariancePopulation(data1, data2)
29 | return covp / (sdev1 * sdev2), nil
30 | }
31 |
32 | // Pearson calculates the Pearson product-moment correlation coefficient between two variables
33 | func Pearson(data1, data2 Float64Data) (float64, error) {
34 | return Correlation(data1, data2)
35 | }
36 |
37 | // AutoCorrelation is the correlation of a signal with a delayed copy of itself as a function of delay
38 | func AutoCorrelation(data Float64Data, lags int) (float64, error) {
39 | if len(data) < 1 {
40 | return 0, EmptyInputErr
41 | }
42 |
43 | mean, _ := Mean(data)
44 |
45 | var result, q float64
46 |
47 | for i := 0; i < lags; i++ {
48 | v := (data[0] - mean) * (data[0] - mean)
49 | for i := 1; i < len(data); i++ {
50 | delta0 := data[i-1] - mean
51 | delta1 := data[i] - mean
52 | q += (delta0*delta1 - q) / float64(i+1)
53 | v += (delta1*delta1 - v) / float64(i+1)
54 | }
55 |
56 | result = q / v
57 | }
58 |
59 | return result, nil
60 | }
61 |
--------------------------------------------------------------------------------
/correlation_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleCorrelation() {
12 | s1 := []float64{1, 2, 3, 4, 5}
13 | s2 := []float64{1, 2, 3, 5, 6}
14 | a, _ := stats.Correlation(s1, s2)
15 | rounded, _ := stats.Round(a, 5)
16 | fmt.Println(rounded)
17 | // Output: 0.99124
18 | }
19 |
20 | func TestCorrelation(t *testing.T) {
21 | s1 := []float64{1, 2, 3, 4, 5}
22 | s2 := []float64{10, -51.2, 8}
23 | s3 := []float64{1, 2, 3, 5, 6}
24 | s4 := []float64{}
25 | s5 := []float64{0, 0, 0}
26 | testCases := []struct {
27 | name string
28 | input [][]float64
29 | output float64
30 | err error
31 | }{
32 | {"Empty Slice Error", [][]float64{s4, s4}, math.NaN(), stats.EmptyInputErr},
33 | {"Different Length Error", [][]float64{s1, s2}, math.NaN(), stats.SizeErr},
34 | {"Correlation Value", [][]float64{s1, s3}, 0.9912407071619302, nil},
35 | {"Same Input Value", [][]float64{s5, s5}, 0.00, nil},
36 | }
37 | for _, tc := range testCases {
38 | t.Run(tc.name, func(t *testing.T) {
39 | a, err := stats.Correlation(tc.input[0], tc.input[1])
40 | if err != nil {
41 | if err != tc.err {
42 | t.Errorf("Should have returned error %s", tc.err)
43 | }
44 | } else if !veryclose(a, tc.output) {
45 | t.Errorf("Result %.08f should be %.08f", a, tc.output)
46 | }
47 | a2, err2 := stats.Pearson(tc.input[0], tc.input[1])
48 | if err2 != nil {
49 | if err2 != tc.err {
50 | t.Errorf("Should have returned error %s", tc.err)
51 | }
52 | } else if !veryclose(a2, tc.output) {
53 | t.Errorf("Result %.08f should be %.08f", a2, tc.output)
54 | }
55 | })
56 | }
57 | }
58 |
59 | func ExampleAutoCorrelation() {
60 | s1 := []float64{1, 2, 3, 4, 5}
61 | a, _ := stats.AutoCorrelation(s1, 1)
62 | fmt.Println(a)
63 | // Output: 0.4
64 | }
65 |
66 | func TestAutoCorrelation(t *testing.T) {
67 | s1 := []float64{1, 2, 3, 4, 5}
68 | s2 := []float64{}
69 |
70 | a, err := stats.AutoCorrelation(s1, 1)
71 | if err != nil {
72 | t.Errorf("Should not have returned an error")
73 | }
74 | if a != 0.4 {
75 | t.Errorf("Should have returned 0.4")
76 | }
77 |
78 | _, err = stats.AutoCorrelation(s2, 1)
79 | if err != stats.EmptyInputErr {
80 | t.Errorf("Should have returned empty input error")
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/cumulative_sum.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // CumulativeSum calculates the cumulative sum of the input slice
4 | func CumulativeSum(input Float64Data) ([]float64, error) {
5 |
6 | if input.Len() == 0 {
7 | return Float64Data{}, EmptyInput
8 | }
9 |
10 | cumSum := make([]float64, input.Len())
11 |
12 | for i, val := range input {
13 | if i == 0 {
14 | cumSum[i] = val
15 | } else {
16 | cumSum[i] = cumSum[i-1] + val
17 | }
18 | }
19 |
20 | return cumSum, nil
21 | }
22 |
--------------------------------------------------------------------------------
/cumulative_sum_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleCumulativeSum() {
12 | data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8}
13 | csum, _ := stats.CumulativeSum(data)
14 | fmt.Println(csum)
15 | // Output: [1 3.1 6.300000000000001 11.123000000000001 15.223 21.023]
16 | }
17 |
18 | func TestCumulativeSum(t *testing.T) {
19 | for _, c := range []struct {
20 | in []float64
21 | out []float64
22 | }{
23 | {[]float64{1, 2, 3}, []float64{1, 3, 6}},
24 | {[]float64{1.0, 1.1, 1.2, 2.2}, []float64{1.0, 2.1, 3.3, 5.5}},
25 | {[]float64{-1, -1, 2, -3}, []float64{-1, -2, 0, -3}},
26 | } {
27 | got, err := stats.CumulativeSum(c.in)
28 | if err != nil {
29 | t.Errorf("Returned an error")
30 | }
31 | if !reflect.DeepEqual(c.out, got) {
32 | t.Errorf("CumulativeSum(%.1f) => %.1f != %.1f", c.in, got, c.out)
33 | }
34 | }
35 | _, err := stats.CumulativeSum([]float64{})
36 | if err == nil {
37 | t.Errorf("Empty slice should have returned an error")
38 | }
39 | }
40 |
41 | func BenchmarkCumulativeSumSmallFloatSlice(b *testing.B) {
42 | for i := 0; i < b.N; i++ {
43 | _, _ = stats.CumulativeSum(makeFloatSlice(5))
44 | }
45 | }
46 |
47 | func BenchmarkCumulativeSumLargeFloatSlice(b *testing.B) {
48 | lf := makeFloatSlice(100000)
49 | b.ResetTimer()
50 | for i := 0; i < b.N; i++ {
51 | _, _ = stats.CumulativeSum(lf)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/data.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // Float64Data is a named type for []float64 with helper methods
4 | type Float64Data []float64
5 |
6 | // Get item in slice
7 | func (f Float64Data) Get(i int) float64 { return f[i] }
8 |
9 | // Len returns length of slice
10 | func (f Float64Data) Len() int { return len(f) }
11 |
12 | // Less returns if one number is less than another
13 | func (f Float64Data) Less(i, j int) bool { return f[i] < f[j] }
14 |
15 | // Swap switches out two numbers in slice
16 | func (f Float64Data) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
17 |
18 | // Min returns the minimum number in the data
19 | func (f Float64Data) Min() (float64, error) { return Min(f) }
20 |
21 | // Max returns the maximum number in the data
22 | func (f Float64Data) Max() (float64, error) { return Max(f) }
23 |
24 | // Sum returns the total of all the numbers in the data
25 | func (f Float64Data) Sum() (float64, error) { return Sum(f) }
26 |
27 | // CumulativeSum returns the cumulative sum of the data
28 | func (f Float64Data) CumulativeSum() ([]float64, error) { return CumulativeSum(f) }
29 |
30 | // Mean returns the mean of the data
31 | func (f Float64Data) Mean() (float64, error) { return Mean(f) }
32 |
33 | // Median returns the median of the data
34 | func (f Float64Data) Median() (float64, error) { return Median(f) }
35 |
36 | // Mode returns the mode of the data
37 | func (f Float64Data) Mode() ([]float64, error) { return Mode(f) }
38 |
39 | // GeometricMean returns the median of the data
40 | func (f Float64Data) GeometricMean() (float64, error) { return GeometricMean(f) }
41 |
42 | // HarmonicMean returns the mode of the data
43 | func (f Float64Data) HarmonicMean() (float64, error) { return HarmonicMean(f) }
44 |
45 | // MedianAbsoluteDeviation the median of the absolute deviations from the dataset median
46 | func (f Float64Data) MedianAbsoluteDeviation() (float64, error) {
47 | return MedianAbsoluteDeviation(f)
48 | }
49 |
50 | // MedianAbsoluteDeviationPopulation finds the median of the absolute deviations from the population median
51 | func (f Float64Data) MedianAbsoluteDeviationPopulation() (float64, error) {
52 | return MedianAbsoluteDeviationPopulation(f)
53 | }
54 |
55 | // StandardDeviation the amount of variation in the dataset
56 | func (f Float64Data) StandardDeviation() (float64, error) {
57 | return StandardDeviation(f)
58 | }
59 |
60 | // StandardDeviationPopulation finds the amount of variation from the population
61 | func (f Float64Data) StandardDeviationPopulation() (float64, error) {
62 | return StandardDeviationPopulation(f)
63 | }
64 |
65 | // StandardDeviationSample finds the amount of variation from a sample
66 | func (f Float64Data) StandardDeviationSample() (float64, error) {
67 | return StandardDeviationSample(f)
68 | }
69 |
70 | // QuartileOutliers finds the mild and extreme outliers
71 | func (f Float64Data) QuartileOutliers() (Outliers, error) {
72 | return QuartileOutliers(f)
73 | }
74 |
75 | // Percentile finds the relative standing in a slice of floats
76 | func (f Float64Data) Percentile(p float64) (float64, error) {
77 | return Percentile(f, p)
78 | }
79 |
80 | // PercentileNearestRank finds the relative standing using the Nearest Rank method
81 | func (f Float64Data) PercentileNearestRank(p float64) (float64, error) {
82 | return PercentileNearestRank(f, p)
83 | }
84 |
85 | // Correlation describes the degree of relationship between two sets of data
86 | func (f Float64Data) Correlation(d Float64Data) (float64, error) {
87 | return Correlation(f, d)
88 | }
89 |
90 | // AutoCorrelation is the correlation of a signal with a delayed copy of itself as a function of delay
91 | func (f Float64Data) AutoCorrelation(lags int) (float64, error) {
92 | return AutoCorrelation(f, lags)
93 | }
94 |
95 | // Pearson calculates the Pearson product-moment correlation coefficient between two variables.
96 | func (f Float64Data) Pearson(d Float64Data) (float64, error) {
97 | return Pearson(f, d)
98 | }
99 |
100 | // Quartile returns the three quartile points from a slice of data
101 | func (f Float64Data) Quartile(d Float64Data) (Quartiles, error) {
102 | return Quartile(d)
103 | }
104 |
105 | // InterQuartileRange finds the range between Q1 and Q3
106 | func (f Float64Data) InterQuartileRange() (float64, error) {
107 | return InterQuartileRange(f)
108 | }
109 |
110 | // Midhinge finds the average of the first and third quartiles
111 | func (f Float64Data) Midhinge(d Float64Data) (float64, error) {
112 | return Midhinge(d)
113 | }
114 |
115 | // Trimean finds the average of the median and the midhinge
116 | func (f Float64Data) Trimean(d Float64Data) (float64, error) {
117 | return Trimean(d)
118 | }
119 |
120 | // Sample returns sample from input with replacement or without
121 | func (f Float64Data) Sample(n int, r bool) ([]float64, error) {
122 | return Sample(f, n, r)
123 | }
124 |
125 | // Variance the amount of variation in the dataset
126 | func (f Float64Data) Variance() (float64, error) {
127 | return Variance(f)
128 | }
129 |
130 | // PopulationVariance finds the amount of variance within a population
131 | func (f Float64Data) PopulationVariance() (float64, error) {
132 | return PopulationVariance(f)
133 | }
134 |
135 | // SampleVariance finds the amount of variance within a sample
136 | func (f Float64Data) SampleVariance() (float64, error) {
137 | return SampleVariance(f)
138 | }
139 |
140 | // Covariance is a measure of how much two sets of data change
141 | func (f Float64Data) Covariance(d Float64Data) (float64, error) {
142 | return Covariance(f, d)
143 | }
144 |
145 | // CovariancePopulation computes covariance for entire population between two variables
146 | func (f Float64Data) CovariancePopulation(d Float64Data) (float64, error) {
147 | return CovariancePopulation(f, d)
148 | }
149 |
150 | // Sigmoid returns the input values along the sigmoid or s-shaped curve
151 | func (f Float64Data) Sigmoid() ([]float64, error) {
152 | return Sigmoid(f)
153 | }
154 |
155 | // SoftMax returns the input values in the range of 0 to 1
156 | // with sum of all the probabilities being equal to one.
157 | func (f Float64Data) SoftMax() ([]float64, error) {
158 | return SoftMax(f)
159 | }
160 |
161 | // Entropy provides calculation of the entropy
162 | func (f Float64Data) Entropy() (float64, error) {
163 | return Entropy(f)
164 | }
165 |
166 | // Quartiles returns the three quartile points from instance of Float64Data
167 | func (f Float64Data) Quartiles() (Quartiles, error) {
168 | return Quartile(f)
169 | }
170 |
--------------------------------------------------------------------------------
/data_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "math/rand"
6 | "reflect"
7 | "runtime"
8 | "testing"
9 | "time"
10 |
11 | "github.com/montanaflynn/stats"
12 | )
13 |
14 | var data1 = stats.Float64Data{-10, -10.001, 5, 1.1, 2, 3, 4.20, 5}
15 | var data2 = stats.Float64Data{-9, -9.001, 4, .1, 1, 2, 3.20, 5}
16 |
17 | func getFunctionName(i interface{}) string {
18 | return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
19 | }
20 |
21 | func checkResult(result float64, err error, name string, f float64, t *testing.T) {
22 | if err != nil {
23 | t.Errorf("%s returned an error", name)
24 | }
25 | if !veryclose(result, f) {
26 | t.Errorf("%s() => %v != %v", name, result, f)
27 | }
28 | }
29 |
30 | // makeFloatSlice makes a slice of float64s
31 | func makeFloatSlice(c int) []float64 {
32 | lf := make([]float64, 0, c)
33 | for i := 0; i < c; i++ {
34 | f := float64(i * 100)
35 | lf = append(lf, f)
36 | }
37 | return lf
38 | }
39 |
40 | func makeRandFloatSlice(c int) []float64 {
41 | lf := make([]float64, 0, c)
42 | rand.Seed(time.Now().UTC().UnixNano())
43 | for i := 0; i < c; i++ {
44 | f := float64(i * 100)
45 | lf = append(lf, f)
46 | }
47 | return lf
48 | }
49 |
50 | func TestInterfaceMethods(t *testing.T) {
51 | // Test Get
52 | a := data1.Get(1)
53 | if a != -10.001 {
54 | t.Errorf("Get(2) => %.1f != %.1f", a, -10.001)
55 | }
56 |
57 | // Test Len
58 | l := data1.Len()
59 | if l != 8 {
60 | t.Errorf("Len() => %v != %v", l, 8)
61 | }
62 |
63 | // Test Less
64 | b := data1.Less(0, 5)
65 | if !b {
66 | t.Errorf("Less() => %v != %v", b, true)
67 | }
68 |
69 | // Test Swap
70 | data1.Swap(0, 2)
71 | if data1.Get(0) != 5 {
72 | t.Errorf("Len() => %v != %v", l, 8)
73 | }
74 | }
75 |
76 | func TestHelperMethods(t *testing.T) {
77 |
78 | // Test Min
79 | m, _ := data1.Min()
80 | if m != -10.001 {
81 | t.Errorf("Min() => %v != %v", m, -10.001)
82 | }
83 |
84 | // Test Max
85 | m, _ = data1.Max()
86 | if m != 5 {
87 | t.Errorf("Max() => %v != %v", m, 5)
88 | }
89 |
90 | // Test Sum
91 | m, _ = data1.Sum()
92 | if m != 0.2990000000000004 {
93 | t.Errorf("Sum() => %v != %v", m, 0.2990000000000004)
94 | }
95 |
96 | // Test CumulativeSum
97 | cs, _ := data1.CumulativeSum()
98 | want := []float64{5, -5.0009999999999994, -15.001, -13.901, -11.901, -8.901, -4.701, 0.2990000000000004}
99 | if !reflect.DeepEqual(cs, want) {
100 | t.Errorf("CumulativeSum() => %v != %v", cs, want)
101 | }
102 |
103 | // Test Mean
104 | m, _ = data1.Mean()
105 | if m != 0.03737500000000005 {
106 | t.Errorf("Mean() => %v != %v", m, 0.03737500000000005)
107 | }
108 |
109 | // Test GeometricMean
110 | m, _ = data1.GeometricMean()
111 | if m != 4.028070682618703 {
112 | t.Errorf("GeometricMean() => %v != %v", m, 4.028070682618703)
113 | }
114 |
115 | // Test HarmonicMean
116 | m, _ = data1.HarmonicMean()
117 | if !math.IsNaN(m) {
118 | t.Errorf("HarmonicMean() => %v != %v", m, math.NaN())
119 | }
120 |
121 | // Test Median
122 | m, _ = data1.Median()
123 | if m != 2.5 {
124 | t.Errorf("Median() => %v != %v", m, 2.5)
125 | }
126 |
127 | // Test Mode
128 | mo, _ := data1.Mode()
129 | if !reflect.DeepEqual(mo, []float64{5.0}) {
130 | t.Errorf("Mode() => %.1f != %.1f", mo, []float64{5.0})
131 | }
132 |
133 | // Test InterQuartileRange
134 | iqr, _ := data1.InterQuartileRange()
135 | if iqr != 9.05 {
136 | t.Errorf("InterQuartileRange() => %v != %v", iqr, 9.05)
137 | }
138 | }
139 |
140 | func assertFloat64(fn func() (float64, error), f float64, t *testing.T) {
141 | res, err := fn()
142 | checkResult(res, err, getFunctionName(fn), f, t)
143 | }
144 |
145 | func TestMedianAbsoluteDeviationMethods(t *testing.T) {
146 | assertFloat64(data1.MedianAbsoluteDeviation, 2.1, t)
147 | assertFloat64(data1.MedianAbsoluteDeviationPopulation, 2.1, t)
148 | }
149 |
150 | func TestStandardDeviationMethods(t *testing.T) {
151 | assertFloat64(data1.StandardDeviation, 5.935684731720091, t)
152 | assertFloat64(data1.StandardDeviationPopulation, 5.935684731720091, t)
153 | assertFloat64(data1.StandardDeviationSample, 6.345513892000508, t)
154 | }
155 |
156 | func TestVarianceMethods(t *testing.T) {
157 | assertFloat64(data1.Variance, 35.232353234375005, t)
158 | assertFloat64(data1.PopulationVariance, 35.232353234375005, t)
159 | assertFloat64(data1.SampleVariance, 40.26554655357143, t)
160 |
161 | }
162 |
163 | func assertPercentiles(fn func(i float64) (float64, error), i float64, f float64, t *testing.T) {
164 | res, err := fn(i)
165 | checkResult(res, err, getFunctionName(fn), f, t)
166 | }
167 |
168 | func TestPercentileMethods(t *testing.T) {
169 | assertPercentiles(data1.Percentile, 75, 4.2, t)
170 | assertPercentiles(data1.PercentileNearestRank, 75, 4.2, t)
171 |
172 | }
173 |
174 | func assertOtherDataMethods(fn func(d stats.Float64Data) (float64, error), d stats.Float64Data, f float64, t *testing.T) {
175 | res, err := fn(d)
176 | checkResult(res, err, getFunctionName(fn), f, t)
177 | }
178 |
179 | func TestOtherDataMethods(t *testing.T) {
180 | assertOtherDataMethods(data1.Correlation, data2, 0.20875473597605448, t)
181 | assertOtherDataMethods(data1.Pearson, data2, 0.20875473597605448, t)
182 | assertOtherDataMethods(data1.Midhinge, data2, -0.42500000000000004, t)
183 | assertOtherDataMethods(data1.Trimean, data2, 0.5375, t)
184 | assertOtherDataMethods(data1.Covariance, data2, 7.3814215535714265, t)
185 | assertOtherDataMethods(data1.CovariancePopulation, data2, 6.458743859374998, t)
186 | }
187 |
188 | func TestAutoCorrelationMethod(t *testing.T) {
189 | _, err := data1.AutoCorrelation(1)
190 | if err != nil {
191 | t.Error("stats.Float64Data.AutoCorrelation returned an error")
192 | }
193 | }
194 |
195 | func TestSampleMethod(t *testing.T) {
196 | // Test Sample method
197 | _, err := data1.Sample(5, true)
198 | if err != nil {
199 | t.Errorf("%s returned an error", getFunctionName(data1.Sample))
200 | }
201 | }
202 |
203 | func TestQuartileMethods(t *testing.T) {
204 | // Test QuartileOutliers method
205 | _, err := data1.QuartileOutliers()
206 | if err != nil {
207 | t.Errorf("%s returned an error", getFunctionName(data1.QuartileOutliers))
208 | }
209 |
210 | // Test Quartile method
211 | _, err = data1.Quartile(data2)
212 | if err != nil {
213 | t.Errorf("%s returned an error", getFunctionName(data1.Quartile))
214 | }
215 | }
216 |
217 | func TestSigmoidMethod(t *testing.T) {
218 | d := stats.LoadRawData([]float64{3.0, 1.0, 2.1})
219 | a := []float64{0.9525741268224334, 0.7310585786300049, 0.8909031788043871}
220 | s, _ := d.Sigmoid()
221 | if !reflect.DeepEqual(s, a) {
222 | t.Errorf("Sigmoid() => %g != %g", s, a)
223 | }
224 | }
225 |
226 | func TestSoftMaxMethod(t *testing.T) {
227 | d := stats.LoadRawData([]float64{3.0, 1.0, 0.2})
228 | a := []float64{0.8360188027814407, 0.11314284146556013, 0.05083835575299916}
229 | s, _ := d.SoftMax()
230 | if !reflect.DeepEqual(s, a) {
231 | t.Errorf("SoftMax() => %g != %g", s, a)
232 | }
233 | }
234 |
235 | func TestEntropyMethod(t *testing.T) {
236 | d := stats.LoadRawData([]float64{3.0, 1.0, 0.2})
237 | a := 0.7270013625470586
238 | e, _ := d.Entropy()
239 | if e != a {
240 | t.Errorf("Entropy() => %v != %v", e, a)
241 | }
242 | }
243 |
244 | // Here we show the regular way of doing it
245 | // with a plain old slice of float64s
246 | func BenchmarkRegularAPI(b *testing.B) {
247 | for i := 0; i < b.N; i++ {
248 | data := []float64{-10, -7, -3.11, 5, 1.1, 2, 3, 4.20, 5, 18}
249 | _, _ = stats.Min(data)
250 | _, _ = stats.Max(data)
251 | _, _ = stats.Sum(data)
252 | _, _ = stats.Mean(data)
253 | _, _ = stats.Median(data)
254 | _, _ = stats.Mode(data)
255 | }
256 | }
257 |
258 | // Here's where things get interesting
259 | // and we start to use the included
260 | // stats.Float64Data type and methods
261 | func BenchmarkMethodsAPI(b *testing.B) {
262 | for i := 0; i < b.N; i++ {
263 | data := stats.Float64Data{-10, -7, -3.11, 5, 1.1, 2, 3, 4.20, 5, 18}
264 | _, _ = data.Min()
265 | _, _ = data.Max()
266 | _, _ = data.Sum()
267 | _, _ = data.Mean()
268 | _, _ = data.Median()
269 | _, _ = data.Mode()
270 | }
271 | }
272 |
273 | func TestQuartilesMethods(t *testing.T) {
274 | _, err := data1.Quartiles()
275 | if err != nil {
276 | t.Errorf("%s returned an error", getFunctionName(data1.Quartiles))
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/describe.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "fmt"
4 |
5 | // Holds information about the dataset provided to Describe
6 | type Description struct {
7 | Count int
8 | Mean float64
9 | Std float64
10 | Max float64
11 | Min float64
12 | DescriptionPercentiles []descriptionPercentile
13 | AllowedNaN bool
14 | }
15 |
16 | // Specifies percentiles to be computed
17 | type descriptionPercentile struct {
18 | Percentile float64
19 | Value float64
20 | }
21 |
22 | // Describe generates descriptive statistics about a provided dataset, similar to python's pandas.describe()
23 | func Describe(input Float64Data, allowNaN bool, percentiles *[]float64) (*Description, error) {
24 | return DescribePercentileFunc(input, allowNaN, percentiles, Percentile)
25 | }
26 |
27 | // Describe generates descriptive statistics about a provided dataset, similar to python's pandas.describe()
28 | // Takes in a function to use for percentile calculation
29 | func DescribePercentileFunc(input Float64Data, allowNaN bool, percentiles *[]float64, percentileFunc func(Float64Data, float64) (float64, error)) (*Description, error) {
30 | var description Description
31 | description.AllowedNaN = allowNaN
32 | description.Count = input.Len()
33 |
34 | if description.Count == 0 && !allowNaN {
35 | return &description, ErrEmptyInput
36 | }
37 |
38 | // Disregard error, since it cannot be thrown if Count is > 0 and allowNaN is false, else NaN is accepted
39 | description.Std, _ = StandardDeviation(input)
40 | description.Max, _ = Max(input)
41 | description.Min, _ = Min(input)
42 | description.Mean, _ = Mean(input)
43 |
44 | if percentiles != nil {
45 | for _, percentile := range *percentiles {
46 | if value, err := percentileFunc(input, percentile); err == nil || allowNaN {
47 | description.DescriptionPercentiles = append(description.DescriptionPercentiles, descriptionPercentile{Percentile: percentile, Value: value})
48 | }
49 | }
50 | }
51 |
52 | return &description, nil
53 | }
54 |
55 | /*
56 | Represents the Description instance in a string format with specified number of decimals
57 |
58 | count 3
59 | mean 2.00
60 | std 0.82
61 | max 3.00
62 | min 1.00
63 | 25.00% NaN
64 | 50.00% 1.50
65 | 75.00% 2.50
66 | NaN OK true
67 | */
68 | func (d *Description) String(decimals int) string {
69 | var str string
70 |
71 | str += fmt.Sprintf("count\t%d\n", d.Count)
72 | str += fmt.Sprintf("mean\t%.*f\n", decimals, d.Mean)
73 | str += fmt.Sprintf("std\t%.*f\n", decimals, d.Std)
74 | str += fmt.Sprintf("max\t%.*f\n", decimals, d.Max)
75 | str += fmt.Sprintf("min\t%.*f\n", decimals, d.Min)
76 | for _, percentile := range d.DescriptionPercentiles {
77 | str += fmt.Sprintf("%.2f%%\t%.*f\n", percentile.Percentile, decimals, percentile.Value)
78 | }
79 | str += fmt.Sprintf("NaN OK\t%t", d.AllowedNaN)
80 | return str
81 | }
82 |
--------------------------------------------------------------------------------
/describe_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func TestDescribeValidDataset(t *testing.T) {
11 | _, err := stats.Describe([]float64{1.0, 2.0, 3.0}, false, &[]float64{25.0, 50.0, 75.0})
12 | if err != nil {
13 | t.Errorf("Returned an error")
14 | }
15 | }
16 |
17 | func TestDescribeEmptyDataset(t *testing.T) {
18 | _, err := stats.Describe([]float64{}, false, nil)
19 | if err != stats.ErrEmptyInput {
20 | t.Errorf("Did not return empty input error")
21 | }
22 | }
23 |
24 | func TestDescribeEmptyDatasetNaN(t *testing.T) {
25 | describe, err := stats.Describe([]float64{}, true, nil)
26 | if err != nil {
27 | t.Errorf("Returned an error")
28 | }
29 |
30 | if !math.IsNaN(describe.Max) || !math.IsNaN(describe.Mean) || !math.IsNaN(describe.Min) || !math.IsNaN(describe.Std) {
31 | t.Errorf("Was not NaN")
32 | }
33 | }
34 |
35 | func TestDescribeValidDatasetNaN(t *testing.T) {
36 | describe, err := stats.Describe([]float64{1.0, 2.0, 3.0}, true, &[]float64{25.0, 50.0, 75.0})
37 | if err != nil {
38 | t.Errorf("Returned an error")
39 | }
40 |
41 | if math.IsNaN(describe.Max) {
42 | t.Errorf("Was NaN")
43 | }
44 | }
45 |
46 | func TestDescribeValues(t *testing.T) {
47 | dataSet := []float64{1.0, 2.0, 3.0}
48 | percentiles := []float64{25.0, 50.0, 75.0}
49 | describe, _ := stats.Describe(dataSet, true, &percentiles)
50 | if describe.Count != len(dataSet) {
51 | t.Errorf("Count was not == length of dataset")
52 | }
53 | if len(describe.DescriptionPercentiles) != len(percentiles) {
54 | t.Errorf("Percentiles length was not == length of input percentiles")
55 | }
56 |
57 | max, _ := stats.Max(dataSet)
58 | if max != describe.Max {
59 | t.Errorf("Max was not equal to Max(dataset)")
60 | }
61 |
62 | min, _ := stats.Min(dataSet)
63 | if min != describe.Min {
64 | t.Errorf("Min was not equal to Min(dataset)")
65 | }
66 |
67 | mean, _ := stats.Mean(dataSet)
68 | if mean != describe.Mean {
69 | t.Errorf("Mean was not equal to Mean(dataset)")
70 | }
71 |
72 | std, _ := stats.StandardDeviation(dataSet)
73 | if std != describe.Std {
74 | t.Errorf("Std was not equal to StandardDeviation(dataset)")
75 | }
76 | }
77 |
78 | func TestDescribeString(t *testing.T) {
79 | describe, _ := stats.Describe([]float64{1.0, 2.0, 3.0}, true, &[]float64{25.0, 50.0, 75.0})
80 | if describe.String(2) != "count\t3\nmean\t2.00\nstd\t0.82\nmax\t3.00\nmin\t1.00\n25.00%\tNaN\n50.00%\t1.50\n75.00%\t2.50\nNaN OK\ttrue" {
81 | t.Errorf("String output is not correct")
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/deviation.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // MedianAbsoluteDeviation finds the median of the absolute deviations from the dataset median
6 | func MedianAbsoluteDeviation(input Float64Data) (mad float64, err error) {
7 | return MedianAbsoluteDeviationPopulation(input)
8 | }
9 |
10 | // MedianAbsoluteDeviationPopulation finds the median of the absolute deviations from the population median
11 | func MedianAbsoluteDeviationPopulation(input Float64Data) (mad float64, err error) {
12 | if input.Len() == 0 {
13 | return math.NaN(), EmptyInputErr
14 | }
15 |
16 | i := copyslice(input)
17 | m, _ := Median(i)
18 |
19 | for key, value := range i {
20 | i[key] = math.Abs(value - m)
21 | }
22 |
23 | return Median(i)
24 | }
25 |
26 | // StandardDeviation the amount of variation in the dataset
27 | func StandardDeviation(input Float64Data) (sdev float64, err error) {
28 | return StandardDeviationPopulation(input)
29 | }
30 |
31 | // StandardDeviationPopulation finds the amount of variation from the population
32 | func StandardDeviationPopulation(input Float64Data) (sdev float64, err error) {
33 |
34 | if input.Len() == 0 {
35 | return math.NaN(), EmptyInputErr
36 | }
37 |
38 | // Get the population variance
39 | vp, _ := PopulationVariance(input)
40 |
41 | // Return the population standard deviation
42 | return math.Sqrt(vp), nil
43 | }
44 |
45 | // StandardDeviationSample finds the amount of variation from a sample
46 | func StandardDeviationSample(input Float64Data) (sdev float64, err error) {
47 |
48 | if input.Len() == 0 {
49 | return math.NaN(), EmptyInputErr
50 | }
51 |
52 | // Get the sample variance
53 | vs, _ := SampleVariance(input)
54 |
55 | // Return the sample standard deviation
56 | return math.Sqrt(vs), nil
57 | }
58 |
--------------------------------------------------------------------------------
/deviation_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func TestMedianAbsoluteDeviation(t *testing.T) {
11 | _, err := stats.MedianAbsoluteDeviation([]float64{1, 2, 3})
12 | if err != nil {
13 | t.Errorf("Returned an error")
14 | }
15 | }
16 |
17 | func TestMedianAbsoluteDeviationPopulation(t *testing.T) {
18 | s, _ := stats.MedianAbsoluteDeviation([]float64{1, 2, 3})
19 | m, err := stats.Round(s, 2)
20 | if err != nil {
21 | t.Errorf("Returned an error")
22 | }
23 | if m != 1.00 {
24 | t.Errorf("%.10f != %.10f", m, 1.00)
25 | }
26 |
27 | s, _ = stats.MedianAbsoluteDeviation([]float64{-2, 0, 4, 5, 7})
28 | m, err = stats.Round(s, 2)
29 | if err != nil {
30 | t.Errorf("Returned an error")
31 | }
32 | if m != 3.00 {
33 | t.Errorf("%.10f != %.10f", m, 3.00)
34 | }
35 |
36 | m, _ = stats.MedianAbsoluteDeviation([]float64{})
37 | if !math.IsNaN(m) {
38 | t.Errorf("%.1f != %.1f", m, math.NaN())
39 | }
40 | }
41 |
42 | func TestStandardDeviation(t *testing.T) {
43 | _, err := stats.StandardDeviation([]float64{1, 2, 3})
44 | if err != nil {
45 | t.Errorf("Returned an error")
46 | }
47 | }
48 |
49 | func TestStandardDeviationPopulation(t *testing.T) {
50 | s, _ := stats.StandardDeviationPopulation([]float64{1, 2, 3})
51 | m, err := stats.Round(s, 2)
52 | if err != nil {
53 | t.Errorf("Returned an error")
54 | }
55 | if m != 0.82 {
56 | t.Errorf("%.10f != %.10f", m, 0.82)
57 | }
58 | s, _ = stats.StandardDeviationPopulation([]float64{-1, -2, -3.3})
59 | m, err = stats.Round(s, 2)
60 | if err != nil {
61 | t.Errorf("Returned an error")
62 | }
63 | if m != 0.94 {
64 | t.Errorf("%.10f != %.10f", m, 0.94)
65 | }
66 |
67 | m, _ = stats.StandardDeviationPopulation([]float64{})
68 | if !math.IsNaN(m) {
69 | t.Errorf("%.1f != %.1f", m, math.NaN())
70 | }
71 | }
72 |
73 | func TestStandardDeviationSample(t *testing.T) {
74 | s, _ := stats.StandardDeviationSample([]float64{1, 2, 3})
75 | m, err := stats.Round(s, 2)
76 | if err != nil {
77 | t.Errorf("Returned an error")
78 | }
79 | if m != 1.0 {
80 | t.Errorf("%.10f != %.10f", m, 1.0)
81 | }
82 | s, _ = stats.StandardDeviationSample([]float64{-1, -2, -3.3})
83 | m, err = stats.Round(s, 2)
84 | if err != nil {
85 | t.Errorf("Returned an error")
86 | }
87 | if m != 1.15 {
88 | t.Errorf("%.10f != %.10f", m, 1.15)
89 | }
90 |
91 | m, _ = stats.StandardDeviationSample([]float64{})
92 | if !math.IsNaN(m) {
93 | t.Errorf("%.1f != %.1f", m, math.NaN())
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/distances.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | // Validate data for distance calculation
8 | func validateData(dataPointX, dataPointY Float64Data) error {
9 | if len(dataPointX) == 0 || len(dataPointY) == 0 {
10 | return EmptyInputErr
11 | }
12 |
13 | if len(dataPointX) != len(dataPointY) {
14 | return SizeErr
15 | }
16 | return nil
17 | }
18 |
19 | // ChebyshevDistance computes the Chebyshev distance between two data sets
20 | func ChebyshevDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {
21 | err = validateData(dataPointX, dataPointY)
22 | if err != nil {
23 | return math.NaN(), err
24 | }
25 | var tempDistance float64
26 | for i := 0; i < len(dataPointY); i++ {
27 | tempDistance = math.Abs(dataPointX[i] - dataPointY[i])
28 | if distance < tempDistance {
29 | distance = tempDistance
30 | }
31 | }
32 | return distance, nil
33 | }
34 |
35 | // EuclideanDistance computes the Euclidean distance between two data sets
36 | func EuclideanDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {
37 |
38 | err = validateData(dataPointX, dataPointY)
39 | if err != nil {
40 | return math.NaN(), err
41 | }
42 | distance = 0
43 | for i := 0; i < len(dataPointX); i++ {
44 | distance = distance + ((dataPointX[i] - dataPointY[i]) * (dataPointX[i] - dataPointY[i]))
45 | }
46 | return math.Sqrt(distance), nil
47 | }
48 |
49 | // ManhattanDistance computes the Manhattan distance between two data sets
50 | func ManhattanDistance(dataPointX, dataPointY Float64Data) (distance float64, err error) {
51 | err = validateData(dataPointX, dataPointY)
52 | if err != nil {
53 | return math.NaN(), err
54 | }
55 | distance = 0
56 | for i := 0; i < len(dataPointX); i++ {
57 | distance = distance + math.Abs(dataPointX[i]-dataPointY[i])
58 | }
59 | return distance, nil
60 | }
61 |
62 | // MinkowskiDistance computes the Minkowski distance between two data sets
63 | //
64 | // Arguments:
65 | //
66 | // dataPointX: First set of data points
67 | // dataPointY: Second set of data points. Length of both data
68 | // sets must be equal.
69 | // lambda: aka p or city blocks; With lambda = 1
70 | // returned distance is manhattan distance and
71 | // lambda = 2; it is euclidean distance. Lambda
72 | // reaching to infinite - distance would be chebysev
73 | // distance.
74 | //
75 | // Return:
76 | //
77 | // Distance or error
78 | func MinkowskiDistance(dataPointX, dataPointY Float64Data, lambda float64) (distance float64, err error) {
79 | err = validateData(dataPointX, dataPointY)
80 | if err != nil {
81 | return math.NaN(), err
82 | }
83 | for i := 0; i < len(dataPointY); i++ {
84 | distance = distance + math.Pow(math.Abs(dataPointX[i]-dataPointY[i]), lambda)
85 | }
86 | distance = math.Pow(distance, 1/lambda)
87 | if math.IsInf(distance, 1) {
88 | return math.NaN(), InfValue
89 | }
90 | return distance, nil
91 | }
92 |
--------------------------------------------------------------------------------
/distances_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | type distanceFunctionType func(stats.Float64Data, stats.Float64Data) (float64, error)
11 |
12 | var minkowskiDistanceTestMatrix = []struct {
13 | dataPointX []float64
14 | dataPointY []float64
15 | lambda float64
16 | distance float64
17 | }{
18 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 1, 24},
19 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 2, 10.583005244258363},
20 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 99, 6},
21 | }
22 |
23 | var distanceTestMatrix = []struct {
24 | dataPointX []float64
25 | dataPointY []float64
26 | distance float64
27 | distanceFunction distanceFunctionType
28 | }{
29 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 6, stats.ChebyshevDistance},
30 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 24, stats.ManhattanDistance},
31 | {[]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, 10.583005244258363, stats.EuclideanDistance},
32 | }
33 |
34 | func TestDataSetDistances(t *testing.T) {
35 |
36 | // Test Minkowski Distance with different lambda values.
37 | for _, testData := range minkowskiDistanceTestMatrix {
38 | distance, err := stats.MinkowskiDistance(testData.dataPointX, testData.dataPointY, testData.lambda)
39 | if err != nil && distance != testData.distance {
40 | t.Errorf("Failed to compute Minkowski distance.")
41 | }
42 |
43 | _, err = stats.MinkowskiDistance([]float64{}, []float64{}, 3)
44 | if err == nil {
45 | t.Errorf("Empty slices should have resulted in an error")
46 | }
47 |
48 | _, err = stats.MinkowskiDistance([]float64{1, 2, 3}, []float64{1, 4}, 3)
49 | if err == nil {
50 | t.Errorf("Different length slices should have resulted in an error")
51 | }
52 |
53 | _, err = stats.MinkowskiDistance([]float64{999, 999, 999}, []float64{1, 1, 1}, 1000)
54 | if err == nil {
55 | t.Errorf("Infinite distance should have resulted in an error")
56 | }
57 | }
58 |
59 | // Compute distance with the help of all algorithms.
60 | for _, testSet := range distanceTestMatrix {
61 | distance, err := testSet.distanceFunction(testSet.dataPointX, testSet.dataPointY)
62 | if err != nil && testSet.distance != distance {
63 | t.Errorf("Failed to compute distance.")
64 | }
65 |
66 | _, err = testSet.distanceFunction([]float64{}, []float64{})
67 | if err == nil {
68 | t.Errorf("Empty slices should have resulted in an error")
69 | }
70 | }
71 | }
72 |
73 | func ExampleChebyshevDistance() {
74 | d1 := []float64{2, 3, 4, 5, 6, 7, 8}
75 | d2 := []float64{8, 7, 6, 5, 4, 3, 2}
76 | cd, _ := stats.ChebyshevDistance(d1, d2)
77 | fmt.Println(cd)
78 | // Output: 6
79 | }
80 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package stats is a well tested and comprehensive
3 | statistics library package with no dependencies.
4 |
5 | Example Usage:
6 |
7 | // start with some source data to use
8 | data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8}
9 |
10 | // you could also use different types like this
11 | // data := stats.LoadRawData([]int{1, 2, 3, 4, 5})
12 | // data := stats.LoadRawData([]interface{}{1.1, "2", 3})
13 | // etc...
14 |
15 | median, _ := stats.Median(data)
16 | fmt.Println(median) // 3.65
17 |
18 | roundedMedian, _ := stats.Round(median, 0)
19 | fmt.Println(roundedMedian) // 4
20 |
21 | MIT License Copyright (c) 2014-2020 Montana Flynn (https://montanaflynn.com)
22 | */
23 | package stats
24 |
--------------------------------------------------------------------------------
/entropy.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Entropy provides calculation of the entropy
6 | func Entropy(input Float64Data) (float64, error) {
7 | input, err := normalize(input)
8 | if err != nil {
9 | return math.NaN(), err
10 | }
11 | var result float64
12 | for i := 0; i < input.Len(); i++ {
13 | v := input.Get(i)
14 | if v == 0 {
15 | continue
16 | }
17 | result += (v * math.Log(v))
18 | }
19 | return -result, nil
20 | }
21 |
22 | func normalize(input Float64Data) (Float64Data, error) {
23 | sum, err := input.Sum()
24 | if err != nil {
25 | return Float64Data{}, err
26 | }
27 | for i := 0; i < input.Len(); i++ {
28 | input[i] = input[i] / sum
29 | }
30 | return input, nil
31 | }
32 |
--------------------------------------------------------------------------------
/entropy_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleEntropy() {
11 | d := []float64{1.1, 2.2, 3.3}
12 | e, _ := stats.Entropy(d)
13 | fmt.Println(e)
14 | // Output: 1.0114042647073518
15 | }
16 |
17 | func TestEntropy(t *testing.T) {
18 | for _, c := range []struct {
19 | in stats.Float64Data
20 | out float64
21 | }{
22 | {stats.Float64Data{4, 8, 5, 1}, 1.2110440167801229},
23 | {stats.Float64Data{0.8, 0.01, 0.4}, 0.6791185708986585},
24 | {stats.Float64Data{0.8, 1.1, 0, 5}, 0.7759393943707658},
25 | } {
26 | got, err := stats.Entropy(c.in)
27 | if err != nil {
28 | t.Errorf("Returned an error")
29 | }
30 | if !veryclose(got, c.out) {
31 | t.Errorf("Max(%.1f) => %.1f != %.1f", c.in, got, c.out)
32 | }
33 | }
34 | _, err := stats.Entropy([]float64{})
35 | if err == nil {
36 | t.Errorf("Empty slice didn't return an error")
37 | }
38 | }
39 |
40 | func BenchmarkEntropySmallFloatSlice(b *testing.B) {
41 | for i := 0; i < b.N; i++ {
42 | _, _ = stats.Entropy(makeFloatSlice(5))
43 | }
44 | }
45 |
46 | func BenchmarkEntropyLargeFloatSlice(b *testing.B) {
47 | lf := makeFloatSlice(100000)
48 | b.ResetTimer()
49 | for i := 0; i < b.N; i++ {
50 | _, _ = stats.Entropy(lf)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/errors.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | type statsError struct {
4 | err string
5 | }
6 |
7 | func (s statsError) Error() string {
8 | return s.err
9 | }
10 |
11 | func (s statsError) String() string {
12 | return s.err
13 | }
14 |
15 | // These are the package-wide error values.
16 | // All error identification should use these values.
17 | // https://github.com/golang/go/wiki/Errors#naming
18 | var (
19 | // ErrEmptyInput Input must not be empty
20 | ErrEmptyInput = statsError{"Input must not be empty."}
21 | // ErrNaN Not a number
22 | ErrNaN = statsError{"Not a number."}
23 | // ErrNegative Must not contain negative values
24 | ErrNegative = statsError{"Must not contain negative values."}
25 | // ErrZero Must not contain zero values
26 | ErrZero = statsError{"Must not contain zero values."}
27 | // ErrBounds Input is outside of range
28 | ErrBounds = statsError{"Input is outside of range."}
29 | // ErrSize Must be the same length
30 | ErrSize = statsError{"Must be the same length."}
31 | // ErrInfValue Value is infinite
32 | ErrInfValue = statsError{"Value is infinite."}
33 | // ErrYCoord Y Value must be greater than zero
34 | ErrYCoord = statsError{"Y Value must be greater than zero."}
35 | )
36 |
--------------------------------------------------------------------------------
/errors_test.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestError(t *testing.T) {
8 | err := statsError{"test error"}
9 | if err.Error() != "test error" {
10 | t.Errorf("Error method message didn't match")
11 | }
12 | if err.String() != "test error" {
13 | t.Errorf("String method message didn't match")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # examples
2 |
3 | The examples directory provides some examples of using the stats package.
--------------------------------------------------------------------------------
/examples/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func main() {
10 |
11 | // d := stats.LoadRawData([]interface{}{1.1, "2", 3.0, 4, "5"})
12 | d := stats.LoadRawData([]int{1, 2, 3, 4, 5})
13 |
14 | a, _ := stats.Min(d)
15 | fmt.Println(a)
16 | // Output: 1.1
17 |
18 | a, _ = stats.Max(d)
19 | fmt.Println(a)
20 | // Output: 5
21 |
22 | a, _ = stats.Sum([]float64{1.1, 2.2, 3.3})
23 | fmt.Println(a)
24 | // Output: 6.6
25 |
26 | cs, _ := stats.CumulativeSum([]float64{1.1, 2.2, 3.3})
27 | fmt.Println(cs) // [1.1 3.3000000000000003 6.6]
28 |
29 | a, _ = stats.Mean([]float64{1, 2, 3, 4, 5})
30 | fmt.Println(a)
31 | // Output: 3
32 |
33 | a, _ = stats.Median([]float64{1, 2, 3, 4, 5, 6, 7})
34 | fmt.Println(a)
35 | // Output: 4
36 |
37 | m, _ := stats.Mode([]float64{5, 5, 3, 3, 4, 2, 1})
38 | fmt.Println(m)
39 | // Output: [5 3]
40 |
41 | a, _ = stats.PopulationVariance([]float64{1, 2, 3, 4, 5})
42 | fmt.Println(a)
43 | // Output: 2
44 |
45 | a, _ = stats.SampleVariance([]float64{1, 2, 3, 4, 5})
46 | fmt.Println(a)
47 | // Output: 2.5
48 |
49 | a, _ = stats.MedianAbsoluteDeviationPopulation([]float64{1, 2, 3})
50 | fmt.Println(a)
51 | // Output: 1
52 |
53 | a, _ = stats.StandardDeviationPopulation([]float64{1, 2, 3})
54 | fmt.Println(a)
55 | // Output: 0.816496580927726
56 |
57 | a, _ = stats.StandardDeviationSample([]float64{1, 2, 3})
58 | fmt.Println(a)
59 | // Output: 1
60 |
61 | a, _ = stats.Percentile([]float64{1, 2, 3, 4, 5}, 75)
62 | fmt.Println(a)
63 | // Output: 4
64 |
65 | a, _ = stats.PercentileNearestRank([]float64{35, 20, 15, 40, 50}, 75)
66 | fmt.Println(a)
67 | // Output: 40
68 |
69 | c := []stats.Coordinate{
70 | {1, 2.3},
71 | {2, 3.3},
72 | {3, 3.7},
73 | {4, 4.3},
74 | {5, 5.3},
75 | }
76 |
77 | r, _ := stats.LinearRegression(c)
78 | fmt.Println(r)
79 | // Output: [{1 2.3800000000000026} {2 3.0800000000000014} {3 3.7800000000000002} {4 4.479999999999999} {5 5.179999999999998}]
80 |
81 | r, _ = stats.ExponentialRegression(c)
82 | fmt.Println(r)
83 | // Output: [{1 2.5150181024736638} {2 3.032084111136781} {3 3.6554544271334493} {4 4.406984298281804} {5 5.313022222665875}]
84 |
85 | r, _ = stats.LogarithmicRegression(c)
86 | fmt.Println(r)
87 | // Output: [{1 2.1520822363811702} {2 3.3305559222492214} {3 4.019918836568674} {4 4.509029608117273} {5 4.888413396683663}]
88 |
89 | s, _ := stats.Sample([]float64{0.1, 0.2, 0.3, 0.4}, 3, false)
90 | fmt.Println(s)
91 | // Output: [0.2,0.4,0.3]
92 |
93 | s, _ = stats.Sample([]float64{0.1, 0.2, 0.3, 0.4}, 10, true)
94 | fmt.Println(s)
95 | // Output: [0.2,0.2,0.4,0.1,0.2,0.4,0.3,0.2,0.2,0.1]
96 |
97 | q, _ := stats.Quartile([]float64{7, 15, 36, 39, 40, 41})
98 | fmt.Println(q)
99 | // Output: {15 37.5 40}
100 |
101 | iqr, _ := stats.InterQuartileRange([]float64{102, 104, 105, 107, 108, 109, 110, 112, 115, 116, 118})
102 | fmt.Println(iqr)
103 | // Output: 10
104 |
105 | mh, _ := stats.Midhinge([]float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13})
106 | fmt.Println(mh)
107 | // Output: 7.5
108 |
109 | tr, _ := stats.Trimean([]float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13})
110 | fmt.Println(tr)
111 | // Output: 7.25
112 |
113 | o, _ := stats.QuartileOutliers([]float64{-1000, 1, 3, 4, 4, 6, 6, 6, 6, 7, 8, 15, 18, 100})
114 | fmt.Printf("%+v\n", o)
115 | // Output: {Mild:[15 18] Extreme:[-1000 100]}
116 |
117 | gm, _ := stats.GeometricMean([]float64{10, 51.2, 8})
118 | fmt.Println(gm)
119 | // Output: 15.999999999999991
120 |
121 | hm, _ := stats.HarmonicMean([]float64{1, 2, 3, 4, 5})
122 | fmt.Println(hm)
123 | // Output: 2.18978102189781
124 |
125 | a, _ = stats.Round(2.18978102189781, 3)
126 | fmt.Println(a)
127 | // Output: 2.189
128 |
129 | e, _ := stats.ChebyshevDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
130 | fmt.Println(e)
131 | // Output: 6
132 |
133 | e, _ = stats.ManhattanDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
134 | fmt.Println(e)
135 | // Output: 24
136 |
137 | e, _ = stats.EuclideanDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
138 | fmt.Println(e)
139 | // Output: 10.583005244258363
140 |
141 | e, _ = stats.MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(1))
142 | fmt.Println(e)
143 | // Output: 24
144 |
145 | e, _ = stats.MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(2))
146 | fmt.Println(e)
147 | // Output: 10.583005244258363
148 |
149 | e, _ = stats.MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(99))
150 | fmt.Println(e)
151 | // Output: 6
152 |
153 | cor, _ := stats.Correlation([]float64{1, 2, 3, 4, 5}, []float64{1, 2, 3, 5, 6})
154 | fmt.Println(cor)
155 | // Output: 0.9912407071619302
156 |
157 | ac, _ := stats.AutoCorrelation([]float64{1, 2, 3, 4, 5}, 1)
158 | fmt.Println(ac)
159 | // Output: 0.4
160 |
161 | sig, _ := stats.Sigmoid([]float64{3.0, 1.0, 2.1})
162 | fmt.Println(sig)
163 | // Output: [0.9525741268224334 0.7310585786300049 0.8909031788043871]
164 |
165 | sm, _ := stats.SoftMax([]float64{3.0, 1.0, 0.2})
166 | fmt.Println(sm)
167 | // Output: [0.8360188027814407 0.11314284146556013 0.05083835575299916]
168 |
169 | e, _ = stats.Entropy([]float64{1.1, 2.2, 3.3})
170 | fmt.Println(e)
171 | // Output: 1.0114042647073518
172 |
173 | p := 0.5
174 | begin := 1
175 | end := 2
176 | chance, _ := stats.ProbGeom(begin, end, p)
177 | fmt.Println(chance)
178 | // Output: 0.25
179 |
180 | prob1 := 0.5
181 | exp, _ := stats.ExpGeom(prob1)
182 | fmt.Println(exp)
183 | // Output:
184 |
185 | prob2 := 0.5
186 | vari, _ := stats.VarGeom(prob2)
187 | fmt.Println(vari)
188 | // Output: 2
189 |
190 | description, _ := stats.Describe([]float64{1.0, 2.0, 3.0}, true, &[]float64{25.0, 50.0, 75.0})
191 | fmt.Println(description.String(2))
192 | }
193 |
--------------------------------------------------------------------------------
/examples/methods.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func main() {
10 |
11 | var d stats.Float64Data = []float64{1, 2, 3, 4, 4, 5}
12 |
13 | // you could also use arbitrary types like this
14 | // var d = stats.LoadRawData([]interface{}{1.1, "2", 3.0, 4, "5"})
15 |
16 | min, _ := d.Min()
17 | fmt.Println(min) // 1
18 |
19 | max, _ := d.Max()
20 | fmt.Println(max) // 5
21 |
22 | sum, _ := d.Sum()
23 | fmt.Println(sum) // 19
24 |
25 | // See https://godoc.org/github.com/montanaflynn/stats#Float64Data
26 | // or run godoc ./ Float64Data to view all available methods
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/examples_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | // import (
4 | // "fmt"
5 | // "testing"
6 |
7 | // "github.com/montanaflynn/stats"
8 | // )
9 |
10 | // func Example() {
11 | // // t.Parallel()
12 | // t.Run("LoadRawData", func(t *testing.T) {
13 | // // t.Parallel()
14 | // data := stats.LoadRawData([]interface{}{1.1, "2", 3})
15 | // fmt.Println(data)
16 | // // Output: 1.1, 2.0, 3.0, 4
17 | // })
18 | // }
19 |
20 | // // func Example() {
21 |
22 | // // // start with some source data to use
23 | // // data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8}
24 | // // // you could also use different types like this
25 | // // // data := stats.LoadRawData([]int{1, 2, 3, 4, 5})
26 | // // // data := stats.LoadRawData([]interface{}{1.1, "2", 3})
27 | // // // etc...
28 |
29 | // // median, _ := Median(data)
30 | // // fmt.Println(median)
31 | // // // Output: 3.65
32 |
33 | // // roundedMedian, _ := Round(median, 0)
34 | // // fmt.Println(roundedMedian)
35 | // // // Output: 4
36 |
37 | // // a, _ := Mean([]float64{1, 2, 3, 4, 5})
38 | // // fmt.Println(a)
39 | // // // Output: 3
40 |
41 | // // a, _ = Median([]float64{1, 2, 3, 4, 5, 6, 7})
42 | // // fmt.Println(a)
43 | // // // Output: 4
44 |
45 | // // m, _ := Mode([]float64{5, 5, 3, 3, 4, 2, 1})
46 | // // fmt.Println(m)
47 | // // // Output: [5 3]
48 |
49 | // // a, _ = PopulationVariance([]float64{1, 2, 3, 4, 5})
50 | // // fmt.Println(a)
51 | // // // Output: 2
52 |
53 | // // a, _ = SampleVariance([]float64{1, 2, 3, 4, 5})
54 | // // fmt.Println(a)
55 | // // // Output: 2.5
56 |
57 | // // a, _ = MedianAbsoluteDeviationPopulation([]float64{1, 2, 3})
58 | // // fmt.Println(a)
59 | // // // Output: 1
60 |
61 | // // a, _ = StandardDeviationPopulation([]float64{1, 2, 3})
62 | // // fmt.Println(a)
63 | // // // Output: 0.816496580927726
64 |
65 | // // a, _ = StandardDeviationSample([]float64{1, 2, 3})
66 | // // fmt.Println(a)
67 | // // // Output: 1
68 |
69 | // // a, _ = Percentile([]float64{1, 2, 3, 4, 5}, 75)
70 | // // fmt.Println(a)
71 | // // // Output: 4
72 |
73 | // // a, _ = PercentileNearestRank([]float64{35, 20, 15, 40, 50}, 75)
74 | // // fmt.Println(a)
75 | // // // Output: 40
76 |
77 | // // c := []Coordinate{
78 | // // {1, 2.3},
79 | // // {2, 3.3},
80 | // // {3, 3.7},
81 | // // {4, 4.3},
82 | // // {5, 5.3},
83 | // // }
84 |
85 | // // r, _ := LinearRegression(c)
86 | // // fmt.Println(r)
87 | // // // Output: [{1 2.3800000000000026} {2 3.0800000000000014} {3 3.7800000000000002} {4 4.479999999999999} {5 5.179999999999998}]
88 |
89 | // // r, _ = ExponentialRegression(c)
90 | // // fmt.Println(r)
91 | // // // Output: [{1 2.5150181024736638} {2 3.032084111136781} {3 3.6554544271334493} {4 4.406984298281804} {5 5.313022222665875}]
92 |
93 | // // r, _ = LogarithmicRegression(c)
94 | // // fmt.Println(r)
95 | // // // Output: [{1 2.1520822363811702} {2 3.3305559222492214} {3 4.019918836568674} {4 4.509029608117273} {5 4.888413396683663}]
96 |
97 | // // s, _ := Sample([]float64{0.1, 0.2, 0.3, 0.4}, 3, false)
98 | // // fmt.Println(s)
99 | // // // Output: [0.2,0.4,0.3]
100 |
101 | // // s, _ = Sample([]float64{0.1, 0.2, 0.3, 0.4}, 10, true)
102 | // // fmt.Println(s)
103 | // // // Output: [0.2,0.2,0.4,0.1,0.2,0.4,0.3,0.2,0.2,0.1]
104 |
105 | // // q, _ := Quartile([]float64{7, 15, 36, 39, 40, 41})
106 | // // fmt.Println(q)
107 | // // // Output: {15 37.5 40}
108 |
109 | // // iqr, _ := InterQuartileRange([]float64{102, 104, 105, 107, 108, 109, 110, 112, 115, 116, 118})
110 | // // fmt.Println(iqr)
111 | // // // Output: 10
112 |
113 | // // mh, _ := Midhinge([]float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13})
114 | // // fmt.Println(mh)
115 | // // // Output: 7.5
116 |
117 | // // tr, _ := Trimean([]float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13})
118 | // // fmt.Println(tr)
119 | // // // Output: 7.25
120 |
121 | // // o, _ := QuartileOutliers([]float64{-1000, 1, 3, 4, 4, 6, 6, 6, 6, 7, 8, 15, 18, 100})
122 | // // fmt.Printf("%+v\n", o)
123 | // // // Output: {Mild:[15 18] Extreme:[-1000 100]}
124 |
125 | // // gm, _ := GeometricMean([]float64{10, 51.2, 8})
126 | // // fmt.Println(gm)
127 | // // // Output: 15.999999999999991
128 |
129 | // // hm, _ := HarmonicMean([]float64{1, 2, 3, 4, 5})
130 | // // fmt.Println(hm)
131 | // // // Output: 2.18978102189781
132 |
133 | // // a, _ = Round(2.18978102189781, 3)
134 | // // fmt.Println(a)
135 | // // // Output: 2.189
136 |
137 | // // e, _ := ChebyshevDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
138 | // // fmt.Println(e)
139 | // // // Output: 6
140 |
141 | // // e, _ = ManhattanDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
142 | // // fmt.Println(e)
143 | // // // Output: 24
144 |
145 | // // e, _ = EuclideanDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2})
146 | // // fmt.Println(e)
147 | // // // Output: 10.583005244258363
148 |
149 | // // e, _ = MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(1))
150 | // // fmt.Println(e)
151 | // // // Output: 24
152 |
153 | // // e, _ = MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(2))
154 | // // fmt.Println(e)
155 | // // // Output: 10.583005244258363
156 |
157 | // // e, _ = MinkowskiDistance([]float64{2, 3, 4, 5, 6, 7, 8}, []float64{8, 7, 6, 5, 4, 3, 2}, float64(99))
158 | // // fmt.Println(e)
159 | // // // Output: 6
160 | // // }
161 |
--------------------------------------------------------------------------------
/geometric_distribution.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | // ProbGeom generates the probability for a geometric random variable
8 | // with parameter p to achieve success in the interval of [a, b] trials
9 | // See https://en.wikipedia.org/wiki/Geometric_distribution for more information
10 | func ProbGeom(a int, b int, p float64) (prob float64, err error) {
11 | if (a > b) || (a < 1) {
12 | return math.NaN(), ErrBounds
13 | }
14 |
15 | prob = 0
16 | q := 1 - p // probability of failure
17 |
18 | for k := a + 1; k <= b; k++ {
19 | prob = prob + p*math.Pow(q, float64(k-1))
20 | }
21 |
22 | return prob, nil
23 | }
24 |
25 | // ProbGeom generates the expectation or average number of trials
26 | // for a geometric random variable with parameter p
27 | func ExpGeom(p float64) (exp float64, err error) {
28 | if (p > 1) || (p < 0) {
29 | return math.NaN(), ErrNegative
30 | }
31 |
32 | return 1 / p, nil
33 | }
34 |
35 | // ProbGeom generates the variance for number for a
36 | // geometric random variable with parameter p
37 | func VarGeom(p float64) (exp float64, err error) {
38 | if (p > 1) || (p < 0) {
39 | return math.NaN(), ErrNegative
40 | }
41 | return (1 - p) / math.Pow(p, 2), nil
42 | }
43 |
--------------------------------------------------------------------------------
/geometric_distribution_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleProbGeom() {
12 | p := 0.5
13 | a := 1
14 | b := 2
15 | chance, _ := stats.ProbGeom(a, b, p)
16 | fmt.Println(chance)
17 | // Output: 0.25
18 | }
19 |
20 | func TestProbGeomLarge(t *testing.T) {
21 | p := 0.5
22 | a := 1
23 | b := 10000
24 | chance, err := stats.ProbGeom(a, b, p)
25 | if err != nil {
26 | t.Errorf("Returned an error")
27 | }
28 | if chance != 0.5 {
29 | t.Errorf("ProbGeom(%d, %d, %.01f) => %.1f != %.1f", a, b, p, chance, 0.5)
30 | }
31 | }
32 |
33 | func TestErrBoundsProbGeom(t *testing.T) {
34 | p := 0.5
35 | a := -1
36 | b := 4
37 | chance, err := stats.ProbGeom(a, b, p)
38 | if err == nil {
39 | t.Errorf("Did not return an error when expected")
40 | }
41 | if !math.IsNaN(chance) {
42 | t.Errorf("ProbGeom(%d, %d, %.01f) => %.1f != %.1f", a, b, p, chance, math.NaN())
43 | }
44 | }
45 |
46 | func ExampleExpGeom() {
47 | p := 0.5
48 | exp, _ := stats.ExpGeom(p)
49 | fmt.Println(exp)
50 | // Output: 2
51 | }
52 |
53 | func TestExpGeom(t *testing.T) {
54 | p := 0.5
55 | exp, err := stats.ExpGeom(p)
56 | if err != nil {
57 | t.Errorf("Returned an error when not expected")
58 | }
59 | if exp != 2.0 {
60 | t.Errorf("ExpGeom(%.01f) => %.1f != %.1f", p, exp, 2.0)
61 | }
62 | }
63 |
64 | func TestErrExpGeom(t *testing.T) {
65 | p := -1.0
66 | exp, err := stats.ExpGeom(p)
67 | if err == nil {
68 | t.Errorf("Did not return an error")
69 | }
70 | if !math.IsNaN(exp) {
71 | t.Errorf("ExpGeom(%.01f) => %.1f != %.1f", p, exp, math.NaN())
72 | }
73 | }
74 |
75 | func ExampleVarGeom() {
76 | p := 0.5
77 | vari, _ := stats.VarGeom(p)
78 | fmt.Println(vari)
79 | // Output: 2
80 | }
81 |
82 | func TestVarGeom(t *testing.T) {
83 | p := 0.25
84 | vari, err := stats.VarGeom(p)
85 | if err != nil {
86 | t.Errorf("Returned an error when not expected")
87 | }
88 | if vari != 12.0 {
89 | t.Errorf("VarGeom(%.01f) => %.1f != %.1f", p, vari, 12.0)
90 | }
91 | }
92 |
93 | func TestErrVarGeom(t *testing.T) {
94 | p := -1.0
95 | vari, err := stats.VarGeom(p)
96 | if err == nil {
97 | t.Errorf("Did not return an error")
98 | }
99 | if !math.IsNaN(vari) {
100 | t.Errorf("VarGeom(%.01f) => %.1f != %.1f", p, vari, math.NaN())
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/montanaflynn/stats
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/legacy.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // VarP is a shortcut to PopulationVariance
4 | func VarP(input Float64Data) (sdev float64, err error) {
5 | return PopulationVariance(input)
6 | }
7 |
8 | // VarS is a shortcut to SampleVariance
9 | func VarS(input Float64Data) (sdev float64, err error) {
10 | return SampleVariance(input)
11 | }
12 |
13 | // StdDevP is a shortcut to StandardDeviationPopulation
14 | func StdDevP(input Float64Data) (sdev float64, err error) {
15 | return StandardDeviationPopulation(input)
16 | }
17 |
18 | // StdDevS is a shortcut to StandardDeviationSample
19 | func StdDevS(input Float64Data) (sdev float64, err error) {
20 | return StandardDeviationSample(input)
21 | }
22 |
23 | // LinReg is a shortcut to LinearRegression
24 | func LinReg(s []Coordinate) (regressions []Coordinate, err error) {
25 | return LinearRegression(s)
26 | }
27 |
28 | // ExpReg is a shortcut to ExponentialRegression
29 | func ExpReg(s []Coordinate) (regressions []Coordinate, err error) {
30 | return ExponentialRegression(s)
31 | }
32 |
33 | // LogReg is a shortcut to LogarithmicRegression
34 | func LogReg(s []Coordinate) (regressions []Coordinate, err error) {
35 | return LogarithmicRegression(s)
36 | }
37 |
38 | // Legacy error names that didn't start with Err
39 | var (
40 | EmptyInputErr = ErrEmptyInput
41 | NaNErr = ErrNaN
42 | NegativeErr = ErrNegative
43 | ZeroErr = ErrZero
44 | BoundsErr = ErrBounds
45 | SizeErr = ErrSize
46 | InfValue = ErrInfValue
47 | YCoordErr = ErrYCoord
48 | EmptyInput = ErrEmptyInput
49 | )
50 |
--------------------------------------------------------------------------------
/legacy_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | // Create working sample data to test if the legacy
10 | // functions cause a runtime crash or return an error
11 | func TestLegacy(t *testing.T) {
12 |
13 | // Slice of data
14 | s := []float64{-10, -10.001, 5, 1.1, 2, 3, 4.20, 5}
15 |
16 | // Slice of coordinates
17 | d := []stats.Coordinate{
18 | {1, 2.3},
19 | {2, 3.3},
20 | {3, 3.7},
21 | {4, 4.3},
22 | {5, 5.3},
23 | }
24 |
25 | // VarP rename compatibility
26 | _, err := stats.VarP(s)
27 | if err != nil {
28 | t.Errorf("VarP not successfully returning PopulationVariance.")
29 | }
30 |
31 | // VarS rename compatibility
32 | _, err = stats.VarS(s)
33 | if err != nil {
34 | t.Errorf("VarS not successfully returning SampleVariance.")
35 | }
36 |
37 | // StdDevP rename compatibility
38 | _, err = stats.StdDevP(s)
39 | if err != nil {
40 | t.Errorf("StdDevP not successfully returning StandardDeviationPopulation.")
41 | }
42 |
43 | // StdDevS rename compatibility
44 | _, err = stats.StdDevS(s)
45 | if err != nil {
46 | t.Errorf("StdDevS not successfully returning StandardDeviationSample.")
47 | }
48 |
49 | // LinReg rename compatibility
50 | _, err = stats.LinReg(d)
51 | if err != nil {
52 | t.Errorf("LinReg not successfully returning LinearRegression.")
53 | }
54 |
55 | // ExpReg rename compatibility
56 | _, err = stats.ExpReg(d)
57 | if err != nil {
58 | t.Errorf("ExpReg not successfully returning ExponentialRegression.")
59 | }
60 |
61 | // LogReg rename compatibility
62 | _, err = stats.LogReg(d)
63 | if err != nil {
64 | t.Errorf("LogReg not successfully returning LogarithmicRegression.")
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/load.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "bufio"
5 | "io"
6 | "strconv"
7 | "strings"
8 | "time"
9 | )
10 |
11 | // LoadRawData parses and converts a slice of mixed data types to floats
12 | func LoadRawData(raw interface{}) (f Float64Data) {
13 | var r []interface{}
14 | var s Float64Data
15 |
16 | switch t := raw.(type) {
17 | case []interface{}:
18 | r = t
19 | case []uint:
20 | for _, v := range t {
21 | s = append(s, float64(v))
22 | }
23 | return s
24 | case []uint8:
25 | for _, v := range t {
26 | s = append(s, float64(v))
27 | }
28 | return s
29 | case []uint16:
30 | for _, v := range t {
31 | s = append(s, float64(v))
32 | }
33 | return s
34 | case []uint32:
35 | for _, v := range t {
36 | s = append(s, float64(v))
37 | }
38 | return s
39 | case []uint64:
40 | for _, v := range t {
41 | s = append(s, float64(v))
42 | }
43 | return s
44 | case []bool:
45 | for _, v := range t {
46 | if v {
47 | s = append(s, 1.0)
48 | } else {
49 | s = append(s, 0.0)
50 | }
51 | }
52 | return s
53 | case []float64:
54 | return Float64Data(t)
55 | case []int:
56 | for _, v := range t {
57 | s = append(s, float64(v))
58 | }
59 | return s
60 | case []int8:
61 | for _, v := range t {
62 | s = append(s, float64(v))
63 | }
64 | return s
65 | case []int16:
66 | for _, v := range t {
67 | s = append(s, float64(v))
68 | }
69 | return s
70 | case []int32:
71 | for _, v := range t {
72 | s = append(s, float64(v))
73 | }
74 | return s
75 | case []int64:
76 | for _, v := range t {
77 | s = append(s, float64(v))
78 | }
79 | return s
80 | case []string:
81 | for _, v := range t {
82 | r = append(r, v)
83 | }
84 | case []time.Duration:
85 | for _, v := range t {
86 | r = append(r, v)
87 | }
88 | case map[int]int:
89 | for i := 0; i < len(t); i++ {
90 | s = append(s, float64(t[i]))
91 | }
92 | return s
93 | case map[int]int8:
94 | for i := 0; i < len(t); i++ {
95 | s = append(s, float64(t[i]))
96 | }
97 | return s
98 | case map[int]int16:
99 | for i := 0; i < len(t); i++ {
100 | s = append(s, float64(t[i]))
101 | }
102 | return s
103 | case map[int]int32:
104 | for i := 0; i < len(t); i++ {
105 | s = append(s, float64(t[i]))
106 | }
107 | return s
108 | case map[int]int64:
109 | for i := 0; i < len(t); i++ {
110 | s = append(s, float64(t[i]))
111 | }
112 | return s
113 | case map[int]string:
114 | for i := 0; i < len(t); i++ {
115 | r = append(r, t[i])
116 | }
117 | case map[int]uint:
118 | for i := 0; i < len(t); i++ {
119 | s = append(s, float64(t[i]))
120 | }
121 | return s
122 | case map[int]uint8:
123 | for i := 0; i < len(t); i++ {
124 | s = append(s, float64(t[i]))
125 | }
126 | return s
127 | case map[int]uint16:
128 | for i := 0; i < len(t); i++ {
129 | s = append(s, float64(t[i]))
130 | }
131 | return s
132 | case map[int]uint32:
133 | for i := 0; i < len(t); i++ {
134 | s = append(s, float64(t[i]))
135 | }
136 | return s
137 | case map[int]uint64:
138 | for i := 0; i < len(t); i++ {
139 | s = append(s, float64(t[i]))
140 | }
141 | return s
142 | case map[int]bool:
143 | for i := 0; i < len(t); i++ {
144 | if t[i] {
145 | s = append(s, 1.0)
146 | } else {
147 | s = append(s, 0.0)
148 | }
149 | }
150 | return s
151 | case map[int]float64:
152 | for i := 0; i < len(t); i++ {
153 | s = append(s, t[i])
154 | }
155 | return s
156 | case map[int]time.Duration:
157 | for i := 0; i < len(t); i++ {
158 | r = append(r, t[i])
159 | }
160 | case string:
161 | for _, v := range strings.Fields(t) {
162 | r = append(r, v)
163 | }
164 | case io.Reader:
165 | scanner := bufio.NewScanner(t)
166 | for scanner.Scan() {
167 | l := scanner.Text()
168 | for _, v := range strings.Fields(l) {
169 | r = append(r, v)
170 | }
171 | }
172 | }
173 |
174 | for _, v := range r {
175 | switch t := v.(type) {
176 | case int:
177 | a := float64(t)
178 | f = append(f, a)
179 | case uint:
180 | f = append(f, float64(t))
181 | case float64:
182 | f = append(f, t)
183 | case string:
184 | fl, err := strconv.ParseFloat(t, 64)
185 | if err == nil {
186 | f = append(f, fl)
187 | }
188 | case bool:
189 | if t {
190 | f = append(f, 1.0)
191 | } else {
192 | f = append(f, 0.0)
193 | }
194 | case time.Duration:
195 | f = append(f, float64(t))
196 | }
197 | }
198 | return f
199 | }
200 |
--------------------------------------------------------------------------------
/load_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 | "time"
8 |
9 | "github.com/montanaflynn/stats"
10 | )
11 |
12 | func ExampleLoadRawData() {
13 | data := stats.LoadRawData([]interface{}{1.1, "2", 3})
14 | fmt.Println(data)
15 | // Output: [1.1 2 3]
16 | }
17 |
18 | var allTestData = []struct {
19 | actual interface{}
20 | expected stats.Float64Data
21 | }{
22 | {
23 | []interface{}{1.0, "2", 3.0, uint(4), "4.0", 5, time.Duration(6), time.Duration(-7)},
24 | stats.Float64Data{1.0, 2.0, 3.0, 4.0, 4.0, 5.0, 6.0, -7.0},
25 | },
26 | {
27 | []interface{}{"-345", "223", "-654.4", "194", "898.3"},
28 | stats.Float64Data{-345.0, 223.0, -654.4, 194.0, 898.3},
29 | },
30 | {
31 | []interface{}{7862, 4234, 9872.1, 8794},
32 | stats.Float64Data{7862.0, 4234.0, 9872.1, 8794.0},
33 | },
34 | {
35 | []interface{}{true, false, true, false, false},
36 | stats.Float64Data{1.0, 0.0, 1.0, 0.0, 0.0},
37 | },
38 | {
39 | []interface{}{14.3, 26, 17.7, "shoe"},
40 | stats.Float64Data{14.3, 26.0, 17.7},
41 | },
42 | {
43 | []bool{true, false, true, true, false},
44 | stats.Float64Data{1.0, 0.0, 1.0, 1.0, 0.0},
45 | },
46 | {
47 | []float64{10230.9823, 93432.9384, 23443.945, 12374.945},
48 | stats.Float64Data{10230.9823, 93432.9384, 23443.945, 12374.945},
49 | },
50 | {
51 | []time.Duration{-843, 923, -398, 1000},
52 | stats.Float64Data{-843.0, 923.0, -398.0, 1000.0},
53 | },
54 | {
55 | []string{"-843.2", "923", "hello", "-398", "1000.5"},
56 | stats.Float64Data{-843.2, 923.0, -398.0, 1000.5},
57 | },
58 | {
59 | []uint{34, 12, 65, 230, 30},
60 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 30.0},
61 | },
62 | {
63 | []uint8{34, 12, 65, 23, 255},
64 | stats.Float64Data{34.0, 12.0, 65.0, 23.0, 255.0},
65 | },
66 | {
67 | []uint16{34, 12, 65, 230, 65535},
68 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 65535.0},
69 | },
70 | {
71 | []uint32{34, 12, 65, 230, 4294967295},
72 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 4294967295.0},
73 | },
74 | {
75 | []uint64{34, 12, 65, 230, 18446744073709551615},
76 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 18446744073709552000.0},
77 | },
78 | {
79 | []int{-843, 923, -398, 1000},
80 | stats.Float64Data{-843.0, 923.0, -398.0, 1000.0},
81 | },
82 | {
83 | []int8{-43, 23, -128, 127},
84 | stats.Float64Data{-43.0, 23.0, -128.0, 127.0},
85 | },
86 | {
87 | []int16{-843, 923, -32768, 32767},
88 | stats.Float64Data{-843.0, 923.0, -32768.0, 32767.0},
89 | },
90 | {
91 | []int32{-843, 923, -2147483648, 2147483647},
92 | stats.Float64Data{-843.0, 923.0, -2147483648.0, 2147483647.0},
93 | },
94 | {
95 | []int64{-843, 923, -9223372036854775808, 9223372036854775807, 9223372036854775800},
96 | stats.Float64Data{-843.0, 923.0, -9223372036854776000.0, 9223372036854776000.0, 9223372036854776000.0},
97 | },
98 | {
99 | map[int]bool{0: true, 1: true, 2: false, 3: true, 4: false},
100 | stats.Float64Data{1.0, 1.0, 0.0, 1.0, 0.0},
101 | },
102 | {
103 | map[int]float64{0: 68.6, 1: 72.1, 2: -33.3, 3: -99.2},
104 | stats.Float64Data{68.6, 72.1, -33.3, -99.2},
105 | },
106 | {
107 | map[int]time.Duration{0: -843, 1: 923, 2: -398, 3: 1000},
108 | stats.Float64Data{-843.0, 923.0, -398.0, 1000.0},
109 | },
110 | {
111 | map[int]string{0: "456", 1: "758", 2: "-9874", 3: "-1981", 4: "68.6", 5: "72.1", 6: "-33.3", 7: "-99.2"},
112 | stats.Float64Data{456.0, 758.0, -9874.0, -1981.0, 68.6, 72.1, -33.3, -99.2},
113 | },
114 | {
115 | map[int]uint{0: 4567, 1: 7580, 2: 98742, 3: 19817},
116 | stats.Float64Data{4567.0, 7580.0, 98742.0, 19817.0},
117 | },
118 | {
119 | map[int]uint8{0: 34, 1: 12, 2: 65, 3: 23, 4: 255},
120 | stats.Float64Data{34.0, 12.0, 65.0, 23.0, 255.0},
121 | },
122 | {
123 | map[int]uint16{0: 34, 1: 12, 2: 65, 3: 230, 4: 65535},
124 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 65535.0},
125 | },
126 | {
127 | map[int]uint32{0: 34, 1: 12, 2: 65, 3: 230, 4: 4294967295},
128 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 4294967295.0},
129 | },
130 | {
131 | map[int]uint64{0: 34, 1: 12, 2: 65, 3: 230, 4: 18446744073709551615},
132 | stats.Float64Data{34.0, 12.0, 65.0, 230.0, 18446744073709552000.0},
133 | },
134 | {
135 | map[int]int{0: 456, 1: 758, 2: -9874, 3: -1981},
136 | stats.Float64Data{456.0, 758.0, -9874.0, -1981.0},
137 | },
138 | {
139 | map[int]int8{0: -43, 1: 23, 2: -128, 3: 127},
140 | stats.Float64Data{-43.0, 23.0, -128.0, 127.0},
141 | },
142 | {
143 | map[int]int16{0: -843, 1: 923, 2: -32768, 3: 32767},
144 | stats.Float64Data{-843.0, 923.0, -32768.0, 32767.0},
145 | },
146 | {
147 | map[int]int32{0: -843, 1: 923, 2: -2147483648, 3: 2147483647},
148 | stats.Float64Data{-843.0, 923.0, -2147483648.0, 2147483647.0},
149 | },
150 | {
151 | map[int]int64{0: -843, 1: 923, 2: -9223372036854775808, 3: 9223372036854775807, 4: 9223372036854775800},
152 | stats.Float64Data{-843.0, 923.0, -9223372036854776000.0, 9223372036854776000.0, 9223372036854776000.0},
153 | },
154 | {
155 | "1\n\n2 3.3\n 4.4",
156 | stats.Float64Data{1.0, 2, 3.3, 4.4},
157 | },
158 | {
159 | strings.NewReader("1\n\n2 3.3\n 4.4"),
160 | stats.Float64Data{1.0, 2, 3.3, 4.4},
161 | },
162 | }
163 |
164 | func equal(actual, expected stats.Float64Data) bool {
165 | if len(actual) != len(expected) {
166 | return false
167 | }
168 |
169 | for k, actualVal := range actual {
170 | if actualVal != expected[k] {
171 | return false
172 | }
173 | }
174 |
175 | return true
176 | }
177 |
178 | func TestLoadRawData(t *testing.T) {
179 | for _, data := range allTestData {
180 | actual := stats.LoadRawData(data.actual)
181 | if !equal(actual, data.expected) {
182 | t.Fatalf("Transform(%v). Expected [%v], Actual [%v]", data.actual, data.expected, actual)
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/max.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | // Max finds the highest number in a slice
8 | func Max(input Float64Data) (max float64, err error) {
9 |
10 | // Return an error if there are no numbers
11 | if input.Len() == 0 {
12 | return math.NaN(), EmptyInputErr
13 | }
14 |
15 | // Get the first value as the starting point
16 | max = input.Get(0)
17 |
18 | // Loop and replace higher values
19 | for i := 1; i < input.Len(); i++ {
20 | if input.Get(i) > max {
21 | max = input.Get(i)
22 | }
23 | }
24 |
25 | return max, nil
26 | }
27 |
--------------------------------------------------------------------------------
/max_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleMax() {
11 | d := []float64{1.1, 2.3, 3.2, 4.0, 4.01, 5.09}
12 | a, _ := stats.Max(d)
13 | fmt.Println(a)
14 | // Output: 5.09
15 | }
16 |
17 | func TestMax(t *testing.T) {
18 | for _, c := range []struct {
19 | in []float64
20 | out float64
21 | }{
22 | {[]float64{1, 2, 3, 4, 5}, 5.0},
23 | {[]float64{10.5, 3, 5, 7, 9}, 10.5},
24 | {[]float64{-20, -1, -5.5}, -1.0},
25 | {[]float64{-1.0}, -1.0},
26 | } {
27 | got, err := stats.Max(c.in)
28 | if err != nil {
29 | t.Errorf("Returned an error")
30 | }
31 | if got != c.out {
32 | t.Errorf("Max(%.1f) => %.1f != %.1f", c.in, got, c.out)
33 | }
34 | }
35 | _, err := stats.Max([]float64{})
36 | if err == nil {
37 | t.Errorf("Empty slice didn't return an error")
38 | }
39 | }
40 |
41 | func BenchmarkMaxSmallFloatSlice(b *testing.B) {
42 | for i := 0; i < b.N; i++ {
43 | _, _ = stats.Max(makeFloatSlice(5))
44 | }
45 | }
46 |
47 | func BenchmarkMaxLargeFloatSlice(b *testing.B) {
48 | lf := makeFloatSlice(100000)
49 | b.ResetTimer()
50 | for i := 0; i < b.N; i++ {
51 | _, _ = stats.Max(lf)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/mean.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Mean gets the average of a slice of numbers
6 | func Mean(input Float64Data) (float64, error) {
7 |
8 | if input.Len() == 0 {
9 | return math.NaN(), EmptyInputErr
10 | }
11 |
12 | sum, _ := input.Sum()
13 |
14 | return sum / float64(input.Len()), nil
15 | }
16 |
17 | // GeometricMean gets the geometric mean for a slice of numbers
18 | func GeometricMean(input Float64Data) (float64, error) {
19 |
20 | l := input.Len()
21 | if l == 0 {
22 | return math.NaN(), EmptyInputErr
23 | }
24 |
25 | // Get the product of all the numbers
26 | var p float64
27 | for _, n := range input {
28 | if p == 0 {
29 | p = n
30 | } else {
31 | p *= n
32 | }
33 | }
34 |
35 | // Calculate the geometric mean
36 | return math.Pow(p, 1/float64(l)), nil
37 | }
38 |
39 | // HarmonicMean gets the harmonic mean for a slice of numbers
40 | func HarmonicMean(input Float64Data) (float64, error) {
41 |
42 | l := input.Len()
43 | if l == 0 {
44 | return math.NaN(), EmptyInputErr
45 | }
46 |
47 | // Get the sum of all the numbers reciprocals and return an
48 | // error for values that cannot be included in harmonic mean
49 | var p float64
50 | for _, n := range input {
51 | if n < 0 {
52 | return math.NaN(), NegativeErr
53 | } else if n == 0 {
54 | return math.NaN(), ZeroErr
55 | }
56 | p += (1 / n)
57 | }
58 |
59 | return float64(l) / p, nil
60 | }
61 |
--------------------------------------------------------------------------------
/mean_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func TestMean(t *testing.T) {
10 | for _, c := range []struct {
11 | in []float64
12 | out float64
13 | }{
14 | {[]float64{1, 2, 3, 4, 5}, 3.0},
15 | {[]float64{1, 2, 3, 4, 5, 6}, 3.5},
16 | {[]float64{1}, 1.0},
17 | } {
18 | got, _ := stats.Mean(c.in)
19 | if got != c.out {
20 | t.Errorf("Mean(%.1f) => %.1f != %.1f", c.in, got, c.out)
21 | }
22 | }
23 | _, err := stats.Mean([]float64{})
24 | if err == nil {
25 | t.Errorf("Empty slice should have returned an error")
26 | }
27 | }
28 |
29 | func BenchmarkMeanSmallFloatSlice(b *testing.B) {
30 | for i := 0; i < b.N; i++ {
31 | _, _ = stats.Mean(makeFloatSlice(5))
32 | }
33 | }
34 |
35 | func BenchmarkMeanLargeFloatSlice(b *testing.B) {
36 | lf := makeFloatSlice(100000)
37 | b.ResetTimer()
38 | for i := 0; i < b.N; i++ {
39 | _, _ = stats.Mean(lf)
40 | }
41 | }
42 |
43 | func TestGeometricMean(t *testing.T) {
44 | s1 := []float64{2, 18}
45 | s2 := []float64{10, 51.2, 8}
46 | s3 := []float64{1, 3, 9, 27, 81}
47 |
48 | for _, c := range []struct {
49 | in []float64
50 | out float64
51 | }{
52 | {s1, 6},
53 | {s2, 16},
54 | {s3, 9},
55 | } {
56 | gm, err := stats.GeometricMean(c.in)
57 | if err != nil {
58 | t.Errorf("Should not have returned an error")
59 | }
60 |
61 | gm, _ = stats.Round(gm, 0)
62 | if gm != c.out {
63 | t.Errorf("Geometric Mean %v != %v", gm, c.out)
64 | }
65 | }
66 |
67 | _, err := stats.GeometricMean([]float64{})
68 | if err == nil {
69 | t.Errorf("Empty slice should have returned an error")
70 | }
71 | }
72 |
73 | func TestHarmonicMean(t *testing.T) {
74 | s1 := []float64{1, 2, 3, 4, 5}
75 | s2 := []float64{10, -51.2, 8}
76 | s3 := []float64{1, 0, 9, 27, 81}
77 |
78 | hm, err := stats.HarmonicMean(s1)
79 | if err != nil {
80 | t.Errorf("Should not have returned an error")
81 | }
82 |
83 | hm, _ = stats.Round(hm, 2)
84 | if hm != 2.19 {
85 | t.Errorf("Geometric Mean %v != %v", hm, 2.19)
86 | }
87 |
88 | _, err = stats.HarmonicMean(s2)
89 | if err == nil {
90 | t.Errorf("Should have returned a negative number error")
91 | }
92 |
93 | _, err = stats.HarmonicMean(s3)
94 | if err == nil {
95 | t.Errorf("Should have returned a zero number error")
96 | }
97 |
98 | _, err = stats.HarmonicMean([]float64{})
99 | if err == nil {
100 | t.Errorf("Empty slice should have returned an error")
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/median.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Median gets the median number in a slice of numbers
6 | func Median(input Float64Data) (median float64, err error) {
7 |
8 | // Start by sorting a copy of the slice
9 | c := sortedCopy(input)
10 |
11 | // No math is needed if there are no numbers
12 | // For even numbers we add the two middle numbers
13 | // and divide by two using the mean function above
14 | // For odd numbers we just use the middle number
15 | l := len(c)
16 | if l == 0 {
17 | return math.NaN(), EmptyInputErr
18 | } else if l%2 == 0 {
19 | median, _ = Mean(c[l/2-1 : l/2+1])
20 | } else {
21 | median = c[l/2]
22 | }
23 |
24 | return median, nil
25 | }
26 |
--------------------------------------------------------------------------------
/median_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleMedian() {
12 | data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8}
13 | median, _ := stats.Median(data)
14 | fmt.Println(median)
15 | // Output: 3.65
16 | }
17 |
18 | func TestMedian(t *testing.T) {
19 | for _, c := range []struct {
20 | in []float64
21 | out float64
22 | }{
23 | {[]float64{5, 3, 4, 2, 1}, 3.0},
24 | {[]float64{6, 3, 2, 4, 5, 1}, 3.5},
25 | {[]float64{1}, 1.0},
26 | } {
27 | got, _ := stats.Median(c.in)
28 | if got != c.out {
29 | t.Errorf("Median(%.1f) => %.1f != %.1f", c.in, got, c.out)
30 | }
31 | }
32 | _, err := stats.Median([]float64{})
33 | if err == nil {
34 | t.Errorf("Empty slice should have returned an error")
35 | }
36 | }
37 |
38 | func BenchmarkMedianSmallFloatSlice(b *testing.B) {
39 | for i := 0; i < b.N; i++ {
40 | _, _ = stats.Median(makeFloatSlice(5))
41 | }
42 | }
43 |
44 | func BenchmarkMedianLargeFloatSlice(b *testing.B) {
45 | lf := makeFloatSlice(100000)
46 | b.ResetTimer()
47 | for i := 0; i < b.N; i++ {
48 | _, _ = stats.Median(lf)
49 | }
50 | }
51 |
52 | func TestMedianSortSideEffects(t *testing.T) {
53 | s := []float64{0.1, 0.3, 0.2, 0.4, 0.5}
54 | a := []float64{0.1, 0.3, 0.2, 0.4, 0.5}
55 | _, _ = stats.Median(s)
56 | if !reflect.DeepEqual(s, a) {
57 | t.Errorf("%.1f != %.1f", s, a)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/min.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Min finds the lowest number in a set of data
6 | func Min(input Float64Data) (min float64, err error) {
7 |
8 | // Get the count of numbers in the slice
9 | l := input.Len()
10 |
11 | // Return an error if there are no numbers
12 | if l == 0 {
13 | return math.NaN(), EmptyInputErr
14 | }
15 |
16 | // Get the first value as the starting point
17 | min = input.Get(0)
18 |
19 | // Iterate until done checking for a lower value
20 | for i := 1; i < l; i++ {
21 | if input.Get(i) < min {
22 | min = input.Get(i)
23 | }
24 | }
25 | return min, nil
26 | }
27 |
--------------------------------------------------------------------------------
/min_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleMin() {
11 | d := stats.LoadRawData([]interface{}{1.1, "2", 3.0, 4, "5"})
12 | a, _ := stats.Min(d)
13 | fmt.Println(a)
14 | // Output: 1.1
15 | }
16 |
17 | func TestMin(t *testing.T) {
18 | for _, c := range []struct {
19 | in []float64
20 | out float64
21 | }{
22 | {[]float64{1.1, 2, 3, 4, 5}, 1.1},
23 | {[]float64{10.534, 3, 5, 7, 9}, 3.0},
24 | {[]float64{-5, 1, 5}, -5.0},
25 | {[]float64{5}, 5},
26 | } {
27 | got, err := stats.Min(c.in)
28 | if err != nil {
29 | t.Errorf("Returned an error")
30 | }
31 | if got != c.out {
32 | t.Errorf("Min(%.1f) => %.1f != %.1f", c.in, got, c.out)
33 | }
34 | }
35 | _, err := stats.Min([]float64{})
36 | if err == nil {
37 | t.Errorf("Empty slice didn't return an error")
38 | }
39 | }
40 |
41 | func BenchmarkMinSmallFloatSlice(b *testing.B) {
42 | testData := makeFloatSlice(5)
43 | for i := 0; i < b.N; i++ {
44 | _, _ = stats.Min(testData)
45 | }
46 | }
47 |
48 | func BenchmarkMinSmallRandFloatSlice(b *testing.B) {
49 | testData := makeRandFloatSlice(5)
50 | b.ResetTimer()
51 | for i := 0; i < b.N; i++ {
52 | _, _ = stats.Min(testData)
53 | }
54 | }
55 |
56 | func BenchmarkMinLargeFloatSlice(b *testing.B) {
57 | testData := makeFloatSlice(100000)
58 | b.ResetTimer()
59 | for i := 0; i < b.N; i++ {
60 | _, _ = stats.Min(testData)
61 | }
62 | }
63 |
64 | func BenchmarkMinLargeRandFloatSlice(b *testing.B) {
65 | testData := makeRandFloatSlice(100000)
66 | b.ResetTimer()
67 | for i := 0; i < b.N; i++ {
68 | _, _ = stats.Min(testData)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/mode.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // Mode gets the mode [most frequent value(s)] of a slice of float64s
4 | func Mode(input Float64Data) (mode []float64, err error) {
5 | // Return the input if there's only one number
6 | l := input.Len()
7 | if l == 1 {
8 | return input, nil
9 | } else if l == 0 {
10 | return nil, EmptyInputErr
11 | }
12 |
13 | c := sortedCopyDif(input)
14 | // Traverse sorted array,
15 | // tracking the longest repeating sequence
16 | mode = make([]float64, 5)
17 | cnt, maxCnt := 1, 1
18 | for i := 1; i < l; i++ {
19 | switch {
20 | case c[i] == c[i-1]:
21 | cnt++
22 | case cnt == maxCnt && maxCnt != 1:
23 | mode = append(mode, c[i-1])
24 | cnt = 1
25 | case cnt > maxCnt:
26 | mode = append(mode[:0], c[i-1])
27 | maxCnt, cnt = cnt, 1
28 | default:
29 | cnt = 1
30 | }
31 | }
32 | switch {
33 | case cnt == maxCnt:
34 | mode = append(mode, c[l-1])
35 | case cnt > maxCnt:
36 | mode = append(mode[:0], c[l-1])
37 | maxCnt = cnt
38 | }
39 |
40 | // Since length must be greater than 1,
41 | // check for slices of distinct values
42 | if maxCnt == 1 || len(mode)*maxCnt == l && maxCnt != l {
43 | return Float64Data{}, nil
44 | }
45 |
46 | return mode, nil
47 | }
48 |
--------------------------------------------------------------------------------
/mode_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func TestMode(t *testing.T) {
11 | for _, c := range []struct {
12 | in []float64
13 | out []float64
14 | }{
15 | {[]float64{2, 2, 2, 2}, []float64{2}},
16 | {[]float64{5, 3, 4, 2, 1}, []float64{}},
17 | {[]float64{5, 5, 3, 3, 4, 4, 2, 2, 1, 1}, []float64{}},
18 | {[]float64{5, 5, 3, 4, 2, 1}, []float64{5}},
19 | {[]float64{5, 5, 3, 3, 4, 2, 1}, []float64{3, 5}},
20 | {[]float64{1}, []float64{1}},
21 | {[]float64{-50, -46.325, -46.325, -.87, 1, 2.1122, 3.20, 5, 15, 15, 15.0001}, []float64{-46.325, 15}},
22 | {[]float64{1, 2, 3, 4, 4, 4, 4, 4, 5, 3, 6, 7, 5, 0, 8, 8, 7, 6, 9, 9}, []float64{4}},
23 | {[]float64{76, 76, 110, 76, 76, 76, 76, 119, 76, 76, 76, 76, 31, 31, 31, 31, 83, 83, 83, 78, 78, 78, 78, 78, 78, 78, 78}, []float64{76}},
24 | } {
25 | got, err := stats.Mode(c.in)
26 | if err != nil {
27 | t.Errorf("Returned an error")
28 | }
29 | if !reflect.DeepEqual(c.out, got) {
30 | t.Errorf("Mode(%.1f) => %.1f != %.1f", c.in, got, c.out)
31 | }
32 | }
33 | _, err := stats.Mode([]float64{})
34 | if err == nil {
35 | t.Errorf("Empty slice should have returned an error")
36 | }
37 | }
38 |
39 | func BenchmarkModeSmallFloatSlice(b *testing.B) {
40 | for i := 0; i < b.N; i++ {
41 | _, _ = stats.Mode(makeFloatSlice(5))
42 | }
43 | }
44 |
45 | func BenchmarkModeSmallRandFloatSlice(b *testing.B) {
46 | lf := makeRandFloatSlice(5)
47 | b.ResetTimer()
48 | for i := 0; i < b.N; i++ {
49 | _, _ = stats.Mode(lf)
50 | }
51 | }
52 |
53 | func BenchmarkModeLargeFloatSlice(b *testing.B) {
54 | lf := makeFloatSlice(100000)
55 | b.ResetTimer()
56 | for i := 0; i < b.N; i++ {
57 | _, _ = stats.Mode(lf)
58 | }
59 | }
60 |
61 | func BenchmarkModeLargeRandFloatSlice(b *testing.B) {
62 | lf := makeRandFloatSlice(100000)
63 | b.ResetTimer()
64 | for i := 0; i < b.N; i++ {
65 | _, _ = stats.Mode(lf)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/nist_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | var (
11 | lew = stats.Float64Data{
12 | -213, -564, -35, -15, 141, 115, -420, -360, 203, -338, -431, 194,
13 | -220, -513, 154, -125, -559, 92, -21, -579, -52, 99, -543, -175,
14 | 162, -457, -346, 204, -300, -474, 164, -107, -572, -8, 83, -541,
15 | -224, 180, -420, -374, 201, -236, -531, 83, 27, -564, -112, 131,
16 | -507, -254, 199, -311, -495, 143, -46, -579, -90, 136, -472, -338,
17 | 202, -287, -477, 169, -124, -568, 17, 48, -568, -135, 162, -430,
18 | -422, 172, -74, -577, -13, 92, -534, -243, 194, -355, -465, 156,
19 | -81, -578, -64, 139, -449, -384, 193, -198, -538, 110, -44, -577,
20 | -6, 66, -552, -164, 161, -460, -344, 205, -281, -504, 134, -28,
21 | -576, -118, 156, -437, -381, 200, -220, -540, 83, 11, -568, -160,
22 | 172, -414, -408, 188, -125, -572, -32, 139, -492, -321, 205, -262,
23 | -504, 142, -83, -574, 0, 48, -571, -106, 137, -501, -266, 190,
24 | -391, -406, 194, -186, -553, 83, -13, -577, -49, 103, -515, -280,
25 | 201, 300, -506, 131, -45, -578, -80, 138, -462, -361, 201, -211,
26 | -554, 32, 74, -533, -235, 187, -372, -442, 182, -147, -566, 25,
27 | 68, -535, -244, 194, -351, -463, 174, -125, -570, 15, 72, -550,
28 | -190, 172, -424, -385, 198, -218, -536, 96}
29 |
30 | lottery = stats.Float64Data{
31 | 162, 671, 933, 414, 788, 730, 817, 33, 536, 875, 670, 236, 473, 167,
32 | 877, 980, 316, 950, 456, 92, 517, 557, 956, 954, 104, 178, 794, 278,
33 | 147, 773, 437, 435, 502, 610, 582, 780, 689, 562, 964, 791, 28, 97,
34 | 848, 281, 858, 538, 660, 972, 671, 613, 867, 448, 738, 966, 139, 636,
35 | 847, 659, 754, 243, 122, 455, 195, 968, 793, 59, 730, 361, 574, 522,
36 | 97, 762, 431, 158, 429, 414, 22, 629, 788, 999, 187, 215, 810, 782,
37 | 47, 34, 108, 986, 25, 644, 829, 630, 315, 567, 919, 331, 207, 412,
38 | 242, 607, 668, 944, 749, 168, 864, 442, 533, 805, 372, 63, 458, 777,
39 | 416, 340, 436, 140, 919, 350, 510, 572, 905, 900, 85, 389, 473, 758,
40 | 444, 169, 625, 692, 140, 897, 672, 288, 312, 860, 724, 226, 884, 508,
41 | 976, 741, 476, 417, 831, 15, 318, 432, 241, 114, 799, 955, 833, 358,
42 | 935, 146, 630, 830, 440, 642, 356, 373, 271, 715, 367, 393, 190, 669,
43 | 8, 861, 108, 795, 269, 590, 326, 866, 64, 523, 862, 840, 219, 382,
44 | 998, 4, 628, 305, 747, 247, 34, 747, 729, 645, 856, 974, 24, 568, 24,
45 | 694, 608, 480, 410, 729, 947, 293, 53, 930, 223, 203, 677, 227, 62,
46 | 455, 387, 318, 562, 242, 428, 968}
47 |
48 | mavro = stats.Float64Data{
49 | 2.00180, 2.00170, 2.00180, 2.00190, 2.00180, 2.00170, 2.00150,
50 | 2.00140, 2.00150, 2.00150, 2.00170, 2.00180, 2.00180, 2.00190,
51 | 2.00190, 2.00210, 2.00200, 2.00160, 2.00140, 2.00130, 2.00130,
52 | 2.00150, 2.00150, 2.00160, 2.00150, 2.00140, 2.00130, 2.00140,
53 | 2.00150, 2.00140, 2.00150, 2.00160, 2.00150, 2.00160, 2.00190,
54 | 2.00200, 2.00200, 2.00210, 2.00220, 2.00230, 2.00240, 2.00250,
55 | 2.00270, 2.00260, 2.00260, 2.00260, 2.00270, 2.00260, 2.00250,
56 | 2.00240}
57 |
58 | michelson = stats.Float64Data{
59 | 299.85, 299.74, 299.90, 300.07, 299.93, 299.85, 299.95, 299.98,
60 | 299.98, 299.88, 300.00, 299.98, 299.93, 299.65, 299.76, 299.81,
61 | 300.00, 300.00, 299.96, 299.96, 299.96, 299.94, 299.96, 299.94,
62 | 299.88, 299.80, 299.85, 299.88, 299.90, 299.84, 299.83, 299.79,
63 | 299.81, 299.88, 299.88, 299.83, 299.80, 299.79, 299.76, 299.80,
64 | 299.88, 299.88, 299.88, 299.86, 299.72, 299.72, 299.62, 299.86,
65 | 299.97, 299.95, 299.88, 299.91, 299.85, 299.87, 299.84, 299.84,
66 | 299.85, 299.84, 299.84, 299.84, 299.89, 299.81, 299.81, 299.82,
67 | 299.80, 299.77, 299.76, 299.74, 299.75, 299.76, 299.91, 299.92,
68 | 299.89, 299.86, 299.88, 299.72, 299.84, 299.85, 299.85, 299.78,
69 | 299.89, 299.84, 299.78, 299.81, 299.76, 299.81, 299.79, 299.81,
70 | 299.82, 299.85, 299.87, 299.87, 299.81, 299.74, 299.81, 299.94,
71 | 299.95, 299.80, 299.81, 299.87}
72 |
73 | pidigits = stats.Float64Data{
74 | 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8,
75 | 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 9, 5, 0, 2,
76 | 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 5, 8, 2, 0, 9,
77 | 7, 4, 9, 4, 4, 5, 9, 2, 3, 0, 7, 8, 1, 6, 4, 0, 6, 2, 8, 6, 2, 0,
78 | 8, 9, 9, 8, 6, 2, 8, 0, 3, 4, 8, 2, 5, 3, 4, 2, 1, 1, 7, 0, 6, 7,
79 | 9, 8, 2, 1, 4, 8, 0, 8, 6, 5, 1, 3, 2, 8, 2, 3, 0, 6, 6, 4, 7, 0,
80 | 9, 3, 8, 4, 4, 6, 0, 9, 5, 5, 0, 5, 8, 2, 2, 3, 1, 7, 2, 5, 3, 5,
81 | 9, 4, 0, 8, 1, 2, 8, 4, 8, 1, 1, 1, 7, 4, 5, 0, 2, 8, 4, 1, 0, 2,
82 | 7, 0, 1, 9, 3, 8, 5, 2, 1, 1, 0, 5, 5, 5, 9, 6, 4, 4, 6, 2, 2, 9,
83 | 4, 8, 9, 5, 4, 9, 3, 0, 3, 8, 1, 9, 6, 4, 4, 2, 8, 8, 1, 0, 9, 7,
84 | 5, 6, 6, 5, 9, 3, 3, 4, 4, 6, 1, 2, 8, 4, 7, 5, 6, 4, 8, 2, 3, 3,
85 | 7, 8, 6, 7, 8, 3, 1, 6, 5, 2, 7, 1, 2, 0, 1, 9, 0, 9, 1, 4, 5, 6,
86 | 4, 8, 5, 6, 6, 9, 2, 3, 4, 6, 0, 3, 4, 8, 6, 1, 0, 4, 5, 4, 3, 2,
87 | 6, 6, 4, 8, 2, 1, 3, 3, 9, 3, 6, 0, 7, 2, 6, 0, 2, 4, 9, 1, 4, 1,
88 | 2, 7, 3, 7, 2, 4, 5, 8, 7, 0, 0, 6, 6, 0, 6, 3, 1, 5, 5, 8, 8, 1,
89 | 7, 4, 8, 8, 1, 5, 2, 0, 9, 2, 0, 9, 6, 2, 8, 2, 9, 2, 5, 4, 0, 9,
90 | 1, 7, 1, 5, 3, 6, 4, 3, 6, 7, 8, 9, 2, 5, 9, 0, 3, 6, 0, 0, 1, 1,
91 | 3, 3, 0, 5, 3, 0, 5, 4, 8, 8, 2, 0, 4, 6, 6, 5, 2, 1, 3, 8, 4, 1,
92 | 4, 6, 9, 5, 1, 9, 4, 1, 5, 1, 1, 6, 0, 9, 4, 3, 3, 0, 5, 7, 2, 7,
93 | 0, 3, 6, 5, 7, 5, 9, 5, 9, 1, 9, 5, 3, 0, 9, 2, 1, 8, 6, 1, 1, 7,
94 | 3, 8, 1, 9, 3, 2, 6, 1, 1, 7, 9, 3, 1, 0, 5, 1, 1, 8, 5, 4, 8, 0,
95 | 7, 4, 4, 6, 2, 3, 7, 9, 9, 6, 2, 7, 4, 9, 5, 6, 7, 3, 5, 1, 8, 8,
96 | 5, 7, 5, 2, 7, 2, 4, 8, 9, 1, 2, 2, 7, 9, 3, 8, 1, 8, 3, 0, 1, 1,
97 | 9, 4, 9, 1, 2, 9, 8, 3, 3, 6, 7, 3, 3, 6, 2, 4, 4, 0, 6, 5, 6, 6,
98 | 4, 3, 0, 8, 6, 0, 2, 1, 3, 9, 4, 9, 4, 6, 3, 9, 5, 2, 2, 4, 7, 3,
99 | 7, 1, 9, 0, 7, 0, 2, 1, 7, 9, 8, 6, 0, 9, 4, 3, 7, 0, 2, 7, 7, 0,
100 | 5, 3, 9, 2, 1, 7, 1, 7, 6, 2, 9, 3, 1, 7, 6, 7, 5, 2, 3, 8, 4, 6,
101 | 7, 4, 8, 1, 8, 4, 6, 7, 6, 6, 9, 4, 0, 5, 1, 3, 2, 0, 0, 0, 5, 6,
102 | 8, 1, 2, 7, 1, 4, 5, 2, 6, 3, 5, 6, 0, 8, 2, 7, 7, 8, 5, 7, 7, 1,
103 | 3, 4, 2, 7, 5, 7, 7, 8, 9, 6, 0, 9, 1, 7, 3, 6, 3, 7, 1, 7, 8, 7,
104 | 2, 1, 4, 6, 8, 4, 4, 0, 9, 0, 1, 2, 2, 4, 9, 5, 3, 4, 3, 0, 1, 4,
105 | 6, 5, 4, 9, 5, 8, 5, 3, 7, 1, 0, 5, 0, 7, 9, 2, 2, 7, 9, 6, 8, 9,
106 | 2, 5, 8, 9, 2, 3, 5, 4, 2, 0, 1, 9, 9, 5, 6, 1, 1, 2, 1, 2, 9, 0,
107 | 2, 1, 9, 6, 0, 8, 6, 4, 0, 3, 4, 4, 1, 8, 1, 5, 9, 8, 1, 3, 6, 2,
108 | 9, 7, 7, 4, 7, 7, 1, 3, 0, 9, 9, 6, 0, 5, 1, 8, 7, 0, 7, 2, 1, 1,
109 | 3, 4, 9, 9, 9, 9, 9, 9, 8, 3, 7, 2, 9, 7, 8, 0, 4, 9, 9, 5, 1, 0,
110 | 5, 9, 7, 3, 1, 7, 3, 2, 8, 1, 6, 0, 9, 6, 3, 1, 8, 5, 9, 5, 0, 2,
111 | 4, 4, 5, 9, 4, 5, 5, 3, 4, 6, 9, 0, 8, 3, 0, 2, 6, 4, 2, 5, 2, 2,
112 | 3, 0, 8, 2, 5, 3, 3, 4, 4, 6, 8, 5, 0, 3, 5, 2, 6, 1, 9, 3, 1, 1,
113 | 8, 8, 1, 7, 1, 0, 1, 0, 0, 0, 3, 1, 3, 7, 8, 3, 8, 7, 5, 2, 8, 8,
114 | 6, 5, 8, 7, 5, 3, 3, 2, 0, 8, 3, 8, 1, 4, 2, 0, 6, 1, 7, 1, 7, 7,
115 | 6, 6, 9, 1, 4, 7, 3, 0, 3, 5, 9, 8, 2, 5, 3, 4, 9, 0, 4, 2, 8, 7,
116 | 5, 5, 4, 6, 8, 7, 3, 1, 1, 5, 9, 5, 6, 2, 8, 6, 3, 8, 8, 2, 3, 5,
117 | 3, 7, 8, 7, 5, 9, 3, 7, 5, 1, 9, 5, 7, 7, 8, 1, 8, 5, 7, 7, 3, 0,
118 | 5, 3, 2, 1, 7, 1, 2, 2, 6, 8, 0, 6, 6, 1, 3, 0, 0, 1, 9, 2, 7, 8,
119 | 7, 6, 6, 1, 1, 1, 9, 5, 9, 0, 9, 2, 1, 6, 4, 2, 0, 1, 9, 8, 9, 3,
120 | 8, 0, 9, 5, 2, 5, 7, 2, 0, 1, 0, 6, 5, 4, 8, 5, 8, 6, 3, 2, 7, 8,
121 | 8, 6, 5, 9, 3, 6, 1, 5, 3, 3, 8, 1, 8, 2, 7, 9, 6, 8, 2, 3, 0, 3,
122 | 0, 1, 9, 5, 2, 0, 3, 5, 3, 0, 1, 8, 5, 2, 9, 6, 8, 9, 9, 5, 7, 7,
123 | 3, 6, 2, 2, 5, 9, 9, 4, 1, 3, 8, 9, 1, 2, 4, 9, 7, 2, 1, 7, 7, 5,
124 | 2, 8, 3, 4, 7, 9, 1, 3, 1, 5, 1, 5, 5, 7, 4, 8, 5, 7, 2, 4, 2, 4,
125 | 5, 4, 1, 5, 0, 6, 9, 5, 9, 5, 0, 8, 2, 9, 5, 3, 3, 1, 1, 6, 8, 6,
126 | 1, 7, 2, 7, 8, 5, 5, 8, 8, 9, 0, 7, 5, 0, 9, 8, 3, 8, 1, 7, 5, 4,
127 | 6, 3, 7, 4, 6, 4, 9, 3, 9, 3, 1, 9, 2, 5, 5, 0, 6, 0, 4, 0, 0, 9,
128 | 2, 7, 7, 0, 1, 6, 7, 1, 1, 3, 9, 0, 0, 9, 8, 4, 8, 8, 2, 4, 0, 1,
129 | 2, 8, 5, 8, 3, 6, 1, 6, 0, 3, 5, 6, 3, 7, 0, 7, 6, 6, 0, 1, 0, 4,
130 | 7, 1, 0, 1, 8, 1, 9, 4, 2, 9, 5, 5, 5, 9, 6, 1, 9, 8, 9, 4, 6, 7,
131 | 6, 7, 8, 3, 7, 4, 4, 9, 4, 4, 8, 2, 5, 5, 3, 7, 9, 7, 7, 4, 7, 2,
132 | 6, 8, 4, 7, 1, 0, 4, 0, 4, 7, 5, 3, 4, 6, 4, 6, 2, 0, 8, 0, 4, 6,
133 | 6, 8, 4, 2, 5, 9, 0, 6, 9, 4, 9, 1, 2, 9, 3, 3, 1, 3, 6, 7, 7, 0,
134 | 2, 8, 9, 8, 9, 1, 5, 2, 1, 0, 4, 7, 5, 2, 1, 6, 2, 0, 5, 6, 9, 6,
135 | 6, 0, 2, 4, 0, 5, 8, 0, 3, 8, 1, 5, 0, 1, 9, 3, 5, 1, 1, 2, 5, 3,
136 | 3, 8, 2, 4, 3, 0, 0, 3, 5, 5, 8, 7, 6, 4, 0, 2, 4, 7, 4, 9, 6, 4,
137 | 7, 3, 2, 6, 3, 9, 1, 4, 1, 9, 9, 2, 7, 2, 6, 0, 4, 2, 6, 9, 9, 2,
138 | 2, 7, 9, 6, 7, 8, 2, 3, 5, 4, 7, 8, 1, 6, 3, 6, 0, 0, 9, 3, 4, 1,
139 | 7, 2, 1, 6, 4, 1, 2, 1, 9, 9, 2, 4, 5, 8, 6, 3, 1, 5, 0, 3, 0, 2,
140 | 8, 6, 1, 8, 2, 9, 7, 4, 5, 5, 5, 7, 0, 6, 7, 4, 9, 8, 3, 8, 5, 0,
141 | 5, 4, 9, 4, 5, 8, 8, 5, 8, 6, 9, 2, 6, 9, 9, 5, 6, 9, 0, 9, 2, 7,
142 | 2, 1, 0, 7, 9, 7, 5, 0, 9, 3, 0, 2, 9, 5, 5, 3, 2, 1, 1, 6, 5, 3,
143 | 4, 4, 9, 8, 7, 2, 0, 2, 7, 5, 5, 9, 6, 0, 2, 3, 6, 4, 8, 0, 6, 6,
144 | 5, 4, 9, 9, 1, 1, 9, 8, 8, 1, 8, 3, 4, 7, 9, 7, 7, 5, 3, 5, 6, 6,
145 | 3, 6, 9, 8, 0, 7, 4, 2, 6, 5, 4, 2, 5, 2, 7, 8, 6, 2, 5, 5, 1, 8,
146 | 1, 8, 4, 1, 7, 5, 7, 4, 6, 7, 2, 8, 9, 0, 9, 7, 7, 7, 7, 2, 7, 9,
147 | 3, 8, 0, 0, 0, 8, 1, 6, 4, 7, 0, 6, 0, 0, 1, 6, 1, 4, 5, 2, 4, 9,
148 | 1, 9, 2, 1, 7, 3, 2, 1, 7, 2, 1, 4, 7, 7, 2, 3, 5, 0, 1, 4, 1, 4,
149 | 4, 1, 9, 7, 3, 5, 6, 8, 5, 4, 8, 1, 6, 1, 3, 6, 1, 1, 5, 7, 3, 5,
150 | 2, 5, 5, 2, 1, 3, 3, 4, 7, 5, 7, 4, 1, 8, 4, 9, 4, 6, 8, 4, 3, 8,
151 | 5, 2, 3, 3, 2, 3, 9, 0, 7, 3, 9, 4, 1, 4, 3, 3, 3, 4, 5, 4, 7, 7,
152 | 6, 2, 4, 1, 6, 8, 6, 2, 5, 1, 8, 9, 8, 3, 5, 6, 9, 4, 8, 5, 5, 6,
153 | 2, 0, 9, 9, 2, 1, 9, 2, 2, 2, 1, 8, 4, 2, 7, 2, 5, 5, 0, 2, 5, 4,
154 | 2, 5, 6, 8, 8, 7, 6, 7, 1, 7, 9, 0, 4, 9, 4, 6, 0, 1, 6, 5, 3, 4,
155 | 6, 6, 8, 0, 4, 9, 8, 8, 6, 2, 7, 2, 3, 2, 7, 9, 1, 7, 8, 6, 0, 8,
156 | 5, 7, 8, 4, 3, 8, 3, 8, 2, 7, 9, 6, 7, 9, 7, 6, 6, 8, 1, 4, 5, 4,
157 | 1, 0, 0, 9, 5, 3, 8, 8, 3, 7, 8, 6, 3, 6, 0, 9, 5, 0, 6, 8, 0, 0,
158 | 6, 4, 2, 2, 5, 1, 2, 5, 2, 0, 5, 1, 1, 7, 3, 9, 2, 9, 8, 4, 8, 9,
159 | 6, 0, 8, 4, 1, 2, 8, 4, 8, 8, 6, 2, 6, 9, 4, 5, 6, 0, 4, 2, 4, 1,
160 | 9, 6, 5, 2, 8, 5, 0, 2, 2, 2, 1, 0, 6, 6, 1, 1, 8, 6, 3, 0, 6, 7,
161 | 4, 4, 2, 7, 8, 6, 2, 2, 0, 3, 9, 1, 9, 4, 9, 4, 5, 0, 4, 7, 1, 2,
162 | 3, 7, 1, 3, 7, 8, 6, 9, 6, 0, 9, 5, 6, 3, 6, 4, 3, 7, 1, 9, 1, 7,
163 | 2, 8, 7, 4, 6, 7, 7, 6, 4, 6, 5, 7, 5, 7, 3, 9, 6, 2, 4, 1, 3, 8,
164 | 9, 0, 8, 6, 5, 8, 3, 2, 6, 4, 5, 9, 9, 5, 8, 1, 3, 3, 9, 0, 4, 7,
165 | 8, 0, 2, 7, 5, 9, 0, 0, 9, 9, 4, 6, 5, 7, 6, 4, 0, 7, 8, 9, 5, 1,
166 | 2, 6, 9, 4, 6, 8, 3, 9, 8, 3, 5, 2, 5, 9, 5, 7, 0, 9, 8, 2, 5, 8,
167 | 2, 2, 6, 2, 0, 5, 2, 2, 4, 8, 9, 4, 0, 7, 7, 2, 6, 7, 1, 9, 4, 7,
168 | 8, 2, 6, 8, 4, 8, 2, 6, 0, 1, 4, 7, 6, 9, 9, 0, 9, 0, 2, 6, 4, 0,
169 | 1, 3, 6, 3, 9, 4, 4, 3, 7, 4, 5, 5, 3, 0, 5, 0, 6, 8, 2, 0, 3, 4,
170 | 9, 6, 2, 5, 2, 4, 5, 1, 7, 4, 9, 3, 9, 9, 6, 5, 1, 4, 3, 1, 4, 2,
171 | 9, 8, 0, 9, 1, 9, 0, 6, 5, 9, 2, 5, 0, 9, 3, 7, 2, 2, 1, 6, 9, 6,
172 | 4, 6, 1, 5, 1, 5, 7, 0, 9, 8, 5, 8, 3, 8, 7, 4, 1, 0, 5, 9, 7, 8,
173 | 8, 5, 9, 5, 9, 7, 7, 2, 9, 7, 5, 4, 9, 8, 9, 3, 0, 1, 6, 1, 7, 5,
174 | 3, 9, 2, 8, 4, 6, 8, 1, 3, 8, 2, 6, 8, 6, 8, 3, 8, 6, 8, 9, 4, 2,
175 | 7, 7, 4, 1, 5, 5, 9, 9, 1, 8, 5, 5, 9, 2, 5, 2, 4, 5, 9, 5, 3, 9,
176 | 5, 9, 4, 3, 1, 0, 4, 9, 9, 7, 2, 5, 2, 4, 6, 8, 0, 8, 4, 5, 9, 8,
177 | 7, 2, 7, 3, 6, 4, 4, 6, 9, 5, 8, 4, 8, 6, 5, 3, 8, 3, 6, 7, 3, 6,
178 | 2, 2, 2, 6, 2, 6, 0, 9, 9, 1, 2, 4, 6, 0, 8, 0, 5, 1, 2, 4, 3, 8,
179 | 8, 4, 3, 9, 0, 4, 5, 1, 2, 4, 4, 1, 3, 6, 5, 4, 9, 7, 6, 2, 7, 8,
180 | 0, 7, 9, 7, 7, 1, 5, 6, 9, 1, 4, 3, 5, 9, 9, 7, 7, 0, 0, 1, 2, 9,
181 | 6, 1, 6, 0, 8, 9, 4, 4, 1, 6, 9, 4, 8, 6, 8, 5, 5, 5, 8, 4, 8, 4,
182 | 0, 6, 3, 5, 3, 4, 2, 2, 0, 7, 2, 2, 2, 5, 8, 2, 8, 4, 8, 8, 6, 4,
183 | 8, 1, 5, 8, 4, 5, 6, 0, 2, 8, 5, 0, 6, 0, 1, 6, 8, 4, 2, 7, 3, 9,
184 | 4, 5, 2, 2, 6, 7, 4, 6, 7, 6, 7, 8, 8, 9, 5, 2, 5, 2, 1, 3, 8, 5,
185 | 2, 2, 5, 4, 9, 9, 5, 4, 6, 6, 6, 7, 2, 7, 8, 2, 3, 9, 8, 6, 4, 5,
186 | 6, 5, 9, 6, 1, 1, 6, 3, 5, 4, 8, 8, 6, 2, 3, 0, 5, 7, 7, 4, 5, 6,
187 | 4, 9, 8, 0, 3, 5, 5, 9, 3, 6, 3, 4, 5, 6, 8, 1, 7, 4, 3, 2, 4, 1,
188 | 1, 2, 5, 1, 5, 0, 7, 6, 0, 6, 9, 4, 7, 9, 4, 5, 1, 0, 9, 6, 5, 9,
189 | 6, 0, 9, 4, 0, 2, 5, 2, 2, 8, 8, 7, 9, 7, 1, 0, 8, 9, 3, 1, 4, 5,
190 | 6, 6, 9, 1, 3, 6, 8, 6, 7, 2, 2, 8, 7, 4, 8, 9, 4, 0, 5, 6, 0, 1,
191 | 0, 1, 5, 0, 3, 3, 0, 8, 6, 1, 7, 9, 2, 8, 6, 8, 0, 9, 2, 0, 8, 7,
192 | 4, 7, 6, 0, 9, 1, 7, 8, 2, 4, 9, 3, 8, 5, 8, 9, 0, 0, 9, 7, 1, 4,
193 | 9, 0, 9, 6, 7, 5, 9, 8, 5, 2, 6, 1, 3, 6, 5, 5, 4, 9, 7, 8, 1, 8,
194 | 9, 3, 1, 2, 9, 7, 8, 4, 8, 2, 1, 6, 8, 2, 9, 9, 8, 9, 4, 8, 7, 2,
195 | 2, 6, 5, 8, 8, 0, 4, 8, 5, 7, 5, 6, 4, 0, 1, 4, 2, 7, 0, 4, 7, 7,
196 | 5, 5, 5, 1, 3, 2, 3, 7, 9, 6, 4, 1, 4, 5, 1, 5, 2, 3, 7, 4, 6, 2,
197 | 3, 4, 3, 6, 4, 5, 4, 2, 8, 5, 8, 4, 4, 4, 7, 9, 5, 2, 6, 5, 8, 6,
198 | 7, 8, 2, 1, 0, 5, 1, 1, 4, 1, 3, 5, 4, 7, 3, 5, 7, 3, 9, 5, 2, 3,
199 | 1, 1, 3, 4, 2, 7, 1, 6, 6, 1, 0, 2, 1, 3, 5, 9, 6, 9, 5, 3, 6, 2,
200 | 3, 1, 4, 4, 2, 9, 5, 2, 4, 8, 4, 9, 3, 7, 1, 8, 7, 1, 1, 0, 1, 4,
201 | 5, 7, 6, 5, 4, 0, 3, 5, 9, 0, 2, 7, 9, 9, 3, 4, 4, 0, 3, 7, 4, 2,
202 | 0, 0, 7, 3, 1, 0, 5, 7, 8, 5, 3, 9, 0, 6, 2, 1, 9, 8, 3, 8, 7, 4,
203 | 4, 7, 8, 0, 8, 4, 7, 8, 4, 8, 9, 6, 8, 3, 3, 2, 1, 4, 4, 5, 7, 1,
204 | 3, 8, 6, 8, 7, 5, 1, 9, 4, 3, 5, 0, 6, 4, 3, 0, 2, 1, 8, 4, 5, 3,
205 | 1, 9, 1, 0, 4, 8, 4, 8, 1, 0, 0, 5, 3, 7, 0, 6, 1, 4, 6, 8, 0, 6,
206 | 7, 4, 9, 1, 9, 2, 7, 8, 1, 9, 1, 1, 9, 7, 9, 3, 9, 9, 5, 2, 0, 6,
207 | 1, 4, 1, 9, 6, 6, 3, 4, 2, 8, 7, 5, 4, 4, 4, 0, 6, 4, 3, 7, 4, 5,
208 | 1, 2, 3, 7, 1, 8, 1, 9, 2, 1, 7, 9, 9, 9, 8, 3, 9, 1, 0, 1, 5, 9,
209 | 1, 9, 5, 6, 1, 8, 1, 4, 6, 7, 5, 1, 4, 2, 6, 9, 1, 2, 3, 9, 7, 4,
210 | 8, 9, 4, 0, 9, 0, 7, 1, 8, 6, 4, 9, 4, 2, 3, 1, 9, 6, 1, 5, 6, 7,
211 | 9, 4, 5, 2, 0, 8, 0, 9, 5, 1, 4, 6, 5, 5, 0, 2, 2, 5, 2, 3, 1, 6,
212 | 0, 3, 8, 8, 1, 9, 3, 0, 1, 4, 2, 0, 9, 3, 7, 6, 2, 1, 3, 7, 8, 5,
213 | 5, 9, 5, 6, 6, 3, 8, 9, 3, 7, 7, 8, 7, 0, 8, 3, 0, 3, 9, 0, 6, 9,
214 | 7, 9, 2, 0, 7, 7, 3, 4, 6, 7, 2, 2, 1, 8, 2, 5, 6, 2, 5, 9, 9, 6,
215 | 6, 1, 5, 0, 1, 4, 2, 1, 5, 0, 3, 0, 6, 8, 0, 3, 8, 4, 4, 7, 7, 3,
216 | 4, 5, 4, 9, 2, 0, 2, 6, 0, 5, 4, 1, 4, 6, 6, 5, 9, 2, 5, 2, 0, 1,
217 | 4, 9, 7, 4, 4, 2, 8, 5, 0, 7, 3, 2, 5, 1, 8, 6, 6, 6, 0, 0, 2, 1,
218 | 3, 2, 4, 3, 4, 0, 8, 8, 1, 9, 0, 7, 1, 0, 4, 8, 6, 3, 3, 1, 7, 3,
219 | 4, 6, 4, 9, 6, 5, 1, 4, 5, 3, 9, 0, 5, 7, 9, 6, 2, 6, 8, 5, 6, 1,
220 | 0, 0, 5, 5, 0, 8, 1, 0, 6, 6, 5, 8, 7, 9, 6, 9, 9, 8, 1, 6, 3, 5,
221 | 7, 4, 7, 3, 6, 3, 8, 4, 0, 5, 2, 5, 7, 1, 4, 5, 9, 1, 0, 2, 8, 9,
222 | 7, 0, 6, 4, 1, 4, 0, 1, 1, 0, 9, 7, 1, 2, 0, 6, 2, 8, 0, 4, 3, 9,
223 | 0, 3, 9, 7, 5, 9, 5, 1, 5, 6, 7, 7, 1, 5, 7, 7, 0, 0, 4, 2, 0, 3,
224 | 3, 7, 8, 6, 9, 9, 3, 6, 0, 0, 7, 2, 3, 0, 5, 5, 8, 7, 6, 3, 1, 7,
225 | 6, 3, 5, 9, 4, 2, 1, 8, 7, 3, 1, 2, 5, 1, 4, 7, 1, 2, 0, 5, 3, 2,
226 | 9, 2, 8, 1, 9, 1, 8, 2, 6, 1, 8, 6, 1, 2, 5, 8, 6, 7, 3, 2, 1, 5,
227 | 7, 9, 1, 9, 8, 4, 1, 4, 8, 4, 8, 8, 2, 9, 1, 6, 4, 4, 7, 0, 6, 0,
228 | 9, 5, 7, 5, 2, 7, 0, 6, 9, 5, 7, 2, 2, 0, 9, 1, 7, 5, 6, 7, 1, 1,
229 | 6, 7, 2, 2, 9, 1, 0, 9, 8, 1, 6, 9, 0, 9, 1, 5, 2, 8, 0, 1, 7, 3,
230 | 5, 0, 6, 7, 1, 2, 7, 4, 8, 5, 8, 3, 2, 2, 2, 8, 7, 1, 8, 3, 5, 2,
231 | 0, 9, 3, 5, 3, 9, 6, 5, 7, 2, 5, 1, 2, 1, 0, 8, 3, 5, 7, 9, 1, 5,
232 | 1, 3, 6, 9, 8, 8, 2, 0, 9, 1, 4, 4, 4, 2, 1, 0, 0, 6, 7, 5, 1, 0,
233 | 3, 3, 4, 6, 7, 1, 1, 0, 3, 1, 4, 1, 2, 6, 7, 1, 1, 1, 3, 6, 9, 9,
234 | 0, 8, 6, 5, 8, 5, 1, 6, 3, 9, 8, 3, 1, 5, 0, 1, 9, 7, 0, 1, 6, 5,
235 | 1, 5, 1, 1, 6, 8, 5, 1, 7, 1, 4, 3, 7, 6, 5, 7, 6, 1, 8, 3, 5, 1,
236 | 5, 5, 6, 5, 0, 8, 8, 4, 9, 0, 9, 9, 8, 9, 8, 5, 9, 9, 8, 2, 3, 8,
237 | 7, 3, 4, 5, 5, 2, 8, 3, 3, 1, 6, 3, 5, 5, 0, 7, 6, 4, 7, 9, 1, 8,
238 | 5, 3, 5, 8, 9, 3, 2, 2, 6, 1, 8, 5, 4, 8, 9, 6, 3, 2, 1, 3, 2, 9,
239 | 3, 3, 0, 8, 9, 8, 5, 7, 0, 6, 4, 2, 0, 4, 6, 7, 5, 2, 5, 9, 0, 7,
240 | 0, 9, 1, 5, 4, 8, 1, 4, 1, 6, 5, 4, 9, 8, 5, 9, 4, 6, 1, 6, 3, 7,
241 | 1, 8, 0, 2, 7, 0, 9, 8, 1, 9, 9, 4, 3, 0, 9, 9, 2, 4, 4, 8, 8, 9,
242 | 5, 7, 5, 7, 1, 2, 8, 2, 8, 9, 0, 5, 9, 2, 3, 2, 3, 3, 2, 6, 0, 9,
243 | 7, 2, 9, 9, 7, 1, 2, 0, 8, 4, 4, 3, 3, 5, 7, 3, 2, 6, 5, 4, 8, 9,
244 | 3, 8, 2, 3, 9, 1, 1, 9, 3, 2, 5, 9, 7, 4, 6, 3, 6, 6, 7, 3, 0, 5,
245 | 8, 3, 6, 0, 4, 1, 4, 2, 8, 1, 3, 8, 8, 3, 0, 3, 2, 0, 3, 8, 2, 4,
246 | 9, 0, 3, 7, 5, 8, 9, 8, 5, 2, 4, 3, 7, 4, 4, 1, 7, 0, 2, 9, 1, 3,
247 | 2, 7, 6, 5, 6, 1, 8, 0, 9, 3, 7, 7, 3, 4, 4, 4, 0, 3, 0, 7, 0, 7,
248 | 4, 6, 9, 2, 1, 1, 2, 0, 1, 9, 1, 3, 0, 2, 0, 3, 3, 0, 3, 8, 0, 1,
249 | 9, 7, 6, 2, 1, 1, 0, 1, 1, 0, 0, 4, 4, 9, 2, 9, 3, 2, 1, 5, 1, 6,
250 | 0, 8, 4, 2, 4, 4, 4, 8, 5, 9, 6, 3, 7, 6, 6, 9, 8, 3, 8, 9, 5, 2,
251 | 2, 8, 6, 8, 4, 7, 8, 3, 1, 2, 3, 5, 5, 2, 6, 5, 8, 2, 1, 3, 1, 4,
252 | 4, 9, 5, 7, 6, 8, 5, 7, 2, 6, 2, 4, 3, 3, 4, 4, 1, 8, 9, 3, 0, 3,
253 | 9, 6, 8, 6, 4, 2, 6, 2, 4, 3, 4, 1, 0, 7, 7, 3, 2, 2, 6, 9, 7, 8,
254 | 0, 2, 8, 0, 7, 3, 1, 8, 9, 1, 5, 4, 4, 1, 1, 0, 1, 0, 4, 4, 6, 8,
255 | 2, 3, 2, 5, 2, 7, 1, 6, 2, 0, 1, 0, 5, 2, 6, 5, 2, 2, 7, 2, 1, 1,
256 | 1, 6, 6, 0, 3, 9, 6, 6, 6, 5, 5, 7, 3, 0, 9, 2, 5, 4, 7, 1, 1, 0,
257 | 5, 5, 7, 8, 5, 3, 7, 6, 3, 4, 6, 6, 8, 2, 0, 6, 5, 3, 1, 0, 9, 8,
258 | 9, 6, 5, 2, 6, 9, 1, 8, 6, 2, 0, 5, 6, 4, 7, 6, 9, 3, 1, 2, 5, 7,
259 | 0, 5, 8, 6, 3, 5, 6, 6, 2, 0, 1, 8, 5, 5, 8, 1, 0, 0, 7, 2, 9, 3,
260 | 6, 0, 6, 5, 9, 8, 7, 6, 4, 8, 6, 1, 1, 7, 9, 1, 0, 4, 5, 3, 3, 4,
261 | 8, 8, 5, 0, 3, 4, 6, 1, 1, 3, 6, 5, 7, 6, 8, 6, 7, 5, 3, 2, 4, 9,
262 | 4, 4, 1, 6, 6, 8, 0, 3, 9, 6, 2, 6, 5, 7, 9, 7, 8, 7, 7, 1, 8, 5,
263 | 5, 6, 0, 8, 4, 5, 5, 2, 9, 6, 5, 4, 1, 2, 6, 6, 5, 4, 0, 8, 5, 3,
264 | 0, 6, 1, 4, 3, 4, 4, 4, 3, 1, 8, 5, 8, 6, 7, 6, 9, 7, 5, 1, 4, 5,
265 | 6, 6, 1, 4, 0, 6, 8, 0, 0, 7, 0, 0, 2, 3, 7, 8, 7, 7, 6, 5, 9, 1,
266 | 3, 4, 4, 0, 1, 7, 1, 2, 7, 4, 9, 4, 7, 0, 4, 2, 0, 5, 6, 2, 2, 3,
267 | 0, 5, 3, 8, 9, 9, 4, 5, 6, 1, 3, 1, 4, 0, 7, 1, 1, 2, 7, 0, 0, 0,
268 | 4, 0, 7, 8, 5, 4, 7, 3, 3, 2, 6, 9, 9, 3, 9, 0, 8, 1, 4, 5, 4, 6,
269 | 6, 4, 6, 4, 5, 8, 8, 0, 7, 9, 7, 2, 7, 0, 8, 2, 6, 6, 8, 3, 0, 6,
270 | 3, 4, 3, 2, 8, 5, 8, 7, 8, 5, 6, 9, 8, 3, 0, 5, 2, 3, 5, 8, 0, 8,
271 | 9, 3, 3, 0, 6, 5, 7, 5, 7, 4, 0, 6, 7, 9, 5, 4, 5, 7, 1, 6, 3, 7,
272 | 7, 5, 2, 5, 4, 2, 0, 2, 1, 1, 4, 9, 5, 5, 7, 6, 1, 5, 8, 1, 4, 0,
273 | 0, 2, 5, 0, 1, 2, 6, 2, 2, 8, 5, 9, 4, 1, 3, 0, 2, 1, 6, 4, 7, 1,
274 | 5, 5, 0, 9, 7, 9, 2, 5, 9, 2, 3, 0, 9, 9, 0, 7, 9, 6, 5, 4, 7, 3,
275 | 7, 6, 1, 2, 5, 5, 1, 7, 6, 5, 6, 7, 5, 1, 3, 5, 7, 5, 1, 7, 8, 2,
276 | 9, 6, 6, 6, 4, 5, 4, 7, 7, 9, 1, 7, 4, 5, 0, 1, 1, 2, 9, 9, 6, 1,
277 | 4, 8, 9, 0, 3, 0, 4, 6, 3, 9, 9, 4, 7, 1, 3, 2, 9, 6, 2, 1, 0, 7,
278 | 3, 4, 0, 4, 3, 7, 5, 1, 8, 9, 5, 7, 3, 5, 9, 6, 1, 4, 5, 8, 9, 0,
279 | 1, 9, 3, 8, 9, 7, 1, 3, 1, 1, 1, 7, 9, 0, 4, 2, 9, 7, 8, 2, 8, 5,
280 | 6, 4, 7, 5, 0, 3, 2, 0, 3, 1, 9, 8, 6, 9, 1, 5, 1, 4, 0, 2, 8, 7,
281 | 0, 8, 0, 8, 5, 9, 9, 0, 4, 8, 0, 1, 0, 9, 4, 1, 2, 1, 4, 7, 2, 2,
282 | 1, 3, 1, 7, 9, 4, 7, 6, 4, 7, 7, 7, 2, 6, 2, 2, 4, 1, 4, 2, 5, 4,
283 | 8, 5, 4, 5, 4, 0, 3, 3, 2, 1, 5, 7, 1, 8, 5, 3, 0, 6, 1, 4, 2, 2,
284 | 8, 8, 1, 3, 7, 5, 8, 5, 0, 4, 3, 0, 6, 3, 3, 2, 1, 7, 5, 1, 8, 2,
285 | 9, 7, 9, 8, 6, 6, 2, 2, 3, 7, 1, 7, 2, 1, 5, 9, 1, 6, 0, 7, 7, 1,
286 | 6, 6, 9, 2, 5, 4, 7, 4, 8, 7, 3, 8, 9, 8, 6, 6, 5, 4, 9, 4, 9, 4,
287 | 5, 0, 1, 1, 4, 6, 5, 4, 0, 6, 2, 8, 4, 3, 3, 6, 6, 3, 9, 3, 7, 9,
288 | 0, 0, 3, 9, 7, 6, 9, 2, 6, 5, 6, 7, 2, 1, 4, 6, 3, 8, 5, 3, 0, 6,
289 | 7, 3, 6, 0, 9, 6, 5, 7, 1, 2, 0, 9, 1, 8, 0, 7, 6, 3, 8, 3, 2, 7,
290 | 1, 6, 6, 4, 1, 6, 2, 7, 4, 8, 8, 8, 8, 0, 0, 7, 8, 6, 9, 2, 5, 6,
291 | 0, 2, 9, 0, 2, 2, 8, 4, 7, 2, 1, 0, 4, 0, 3, 1, 7, 2, 1, 1, 8, 6,
292 | 0, 8, 2, 0, 4, 1, 9, 0, 0, 0, 4, 2, 2, 9, 6, 6, 1, 7, 1, 1, 9, 6,
293 | 3, 7, 7, 9, 2, 1, 3, 3, 7, 5, 7, 5, 1, 1, 4, 9, 5, 9, 5, 0, 1, 5,
294 | 6, 6, 0, 4, 9, 6, 3, 1, 8, 6, 2, 9, 4, 7, 2, 6, 5, 4, 7, 3, 6, 4,
295 | 2, 5, 2, 3, 0, 8, 1, 7, 7, 0, 3, 6, 7, 5, 1, 5, 9, 0, 6, 7, 3, 5,
296 | 0, 2, 3, 5, 0, 7, 2, 8, 3, 5, 4, 0, 5, 6, 7, 0, 4, 0, 3, 8, 6, 7,
297 | 4, 3, 5, 1, 3, 6, 2, 2, 2, 2, 4, 7, 7, 1, 5, 8, 9, 1, 5, 0, 4, 9,
298 | 5, 3, 0, 9, 8, 4, 4, 4, 8, 9, 3, 3, 3, 0, 9, 6, 3, 4, 0, 8, 7, 8,
299 | 0, 7, 6, 9, 3, 2, 5, 9, 9, 3, 9, 7, 8, 0, 5, 4, 1, 9, 3, 4, 1, 4,
300 | 4, 7, 3, 7, 7, 4, 4, 1, 8, 4, 2, 6, 3, 1, 2, 9, 8, 6, 0, 8, 0, 9,
301 | 9, 8, 8, 8, 6, 8, 7, 4, 1, 3, 2, 6, 0, 4, 7, 2}
302 | numacc1 = stats.Float64Data{10000001, 10000003, 10000002}
303 | numacc2 = make(stats.Float64Data, 1001)
304 | numacc3 = make(stats.Float64Data, 1001)
305 | numacc4 = make(stats.Float64Data, 1001)
306 | )
307 |
308 | func init() {
309 | numacc2[0] = 1.2
310 | numacc3[0] = 1000000.2
311 | numacc4[0] = 10000000.2
312 | for i := 1; i < 1000; i += 2 {
313 | numacc2[i] = 1.1
314 | numacc2[i+1] = 1.3
315 | numacc3[i] = 1000000.1
316 | numacc3[i+1] = 1000000.3
317 | numacc4[i] = 10000000.1
318 | numacc4[i+1] = 10000000.3
319 | }
320 | }
321 |
322 | func TestLewData(t *testing.T) {
323 | r, e := stats.Mean(lew)
324 | test("Lew Mean", r, -177.435000000000, 1e-15, e, t)
325 |
326 | r, e = stats.StandardDeviationSample(lew)
327 | test("Lew Standard Deviation", r, 277.332168044316, 1e-15, e, t)
328 |
329 | r, e = stats.AutoCorrelation(lew, 1)
330 | test("Lew AutoCorrelate1", r, -0.307304800605679, 1e-14, e, t)
331 | }
332 |
333 | func TestLotteryData(t *testing.T) {
334 | r, e := stats.Mean(lottery)
335 | test("Lottery Mean", r, 518.958715596330, 1e-15, e, t)
336 |
337 | r, e = stats.StandardDeviationSample(lottery)
338 | test("Lottery Standard Deviation", r, 291.699727470969, 1e-15, e, t)
339 |
340 | r, e = stats.AutoCorrelation(lottery, 1)
341 | test("Lottery AutoCorrelate1", r, -0.120948622967393, 1e-14, e, t)
342 | }
343 |
344 | func TestMavroData(t *testing.T) {
345 | r, e := stats.Mean(mavro)
346 | test("Mavro Mean", r, 2.00185600000000, 1e-15, e, t)
347 |
348 | r, e = stats.StandardDeviationSample(mavro)
349 | test("Mavro Standard Deviation", r, 0.000429123454003053, 1e-13, e, t)
350 |
351 | r, e = stats.AutoCorrelation(mavro, 1)
352 | test("Mavro AutoCorrelate1", r, 0.937989183438248, 1e-13, e, t)
353 | }
354 |
355 | func TestMichelsonData(t *testing.T) {
356 | r, e := stats.Mean(michelson)
357 | test("Michelson Mean", r, 299.852400000000, 1e-15, e, t)
358 |
359 | r, e = stats.StandardDeviationSample(michelson)
360 | test("Michelson Standard Deviation", r, 0.0790105478190518, 1e-13, e, t)
361 |
362 | r, e = stats.AutoCorrelation(michelson, 1)
363 | test("Michelson AutoCorrelate1", r, 0.535199668621283, 1e-13, e, t)
364 | }
365 |
366 | func TestPidigitsData(t *testing.T) {
367 | r, e := stats.Mean(pidigits)
368 | test("Pidigits Mean", r, 4.53480000000000, 1e-14, e, t)
369 |
370 | r, e = stats.StandardDeviationSample(pidigits)
371 | test("Pidigits Standard Deviation", r, 2.86733906028871, 1e-14, e, t)
372 |
373 | r, e = stats.AutoCorrelation(pidigits, 1)
374 | test("Pidigits AutoCorrelate1", r, -0.00355099287237972, 1e-13, e, t)
375 | }
376 |
377 | func TestNumacc1Data(t *testing.T) {
378 | r, e := stats.Mean(numacc1)
379 | test("numacc1 Mean", r, 10000002.0, 1e-14, e, t)
380 |
381 | r, e = stats.StandardDeviationSample(numacc1)
382 | test("numacc1 Standard Deviation", r, 1.0, 1e-13, e, t)
383 |
384 | r, e = stats.AutoCorrelation(numacc1, 1)
385 | test("Lew AutoCorrelateNumacc1", r, -0.5, 1e-15, e, t)
386 |
387 | }
388 |
389 | func TestNumacc2Data(t *testing.T) {
390 | r, e := stats.Mean(numacc2)
391 | test("numacc2 Mean", r, 1.2, 1e-10, e, t)
392 |
393 | r, e = stats.StandardDeviationSample(numacc2)
394 | test("numacc2 Standard Deviation", r, 0.1, 1e-10, e, t)
395 |
396 | r, e = stats.AutoCorrelation(numacc2, 1)
397 | test("Lew AutoCorrelateNumacc2", r, -0.999, 1e-10, e, t)
398 | }
399 |
400 | func TestNumacc3Data(t *testing.T) {
401 | r, e := stats.Mean(numacc3)
402 | test("numacc3 Mean", r, 1000000.2, 1e-15, e, t)
403 |
404 | r, e = stats.StandardDeviationSample(numacc3)
405 | test("numacc3 Standard Deviation", r, 0.1, 1e-9, e, t)
406 |
407 | r, e = stats.AutoCorrelation(numacc3, 1)
408 | test("Lew AutoCorrelateNumacc3", r, -0.999, 1e-10, e, t)
409 | }
410 |
411 | func TestNumacc4Data(t *testing.T) {
412 | r, e := stats.Mean(numacc4)
413 | test("numacc4 Mean", r, 10000000.2, 1e-10, e, t)
414 |
415 | r, e = stats.StandardDeviationSample(numacc4)
416 | test("numacc4 Standard Deviation", r, 0.1, 1e-7, e, t)
417 |
418 | r, e = stats.AutoCorrelation(numacc4, 1)
419 | test("Lew AutoCorrelateNumacc4", r, -0.999, 1e-7, e, t)
420 | }
421 |
422 | func bench(d stats.Float64Data) {
423 | _, _ = stats.Mean(d)
424 | _, _ = stats.StdDevS(d)
425 | _, _ = stats.AutoCorrelation(d, 1)
426 | }
427 |
428 | func BenchmarkNistLew(b *testing.B) {
429 | for i := 0; i < b.N; i++ {
430 | bench(lew)
431 | }
432 | }
433 |
434 | func BenchmarkNistLottery(b *testing.B) {
435 | for i := 0; i < b.N; i++ {
436 | bench(lottery)
437 | }
438 | }
439 |
440 | func BenchmarkNistMavro(b *testing.B) {
441 | for i := 0; i < b.N; i++ {
442 | bench(mavro)
443 | }
444 | }
445 |
446 | func BenchmarkNistMichelson(b *testing.B) {
447 | for i := 0; i < b.N; i++ {
448 | bench(michelson)
449 | }
450 | }
451 |
452 | func BenchmarkNistPidigits(b *testing.B) {
453 | for i := 0; i < b.N; i++ {
454 | bench(pidigits)
455 | }
456 | }
457 |
458 | func BenchmarkNistNumacc1(b *testing.B) {
459 | for i := 0; i < b.N; i++ {
460 | bench(numacc1)
461 | }
462 | }
463 |
464 | func BenchmarkNistNumacc2(b *testing.B) {
465 | for i := 0; i < b.N; i++ {
466 | bench(numacc2)
467 | }
468 | }
469 |
470 | func BenchmarkNistNumacc3(b *testing.B) {
471 | for i := 0; i < b.N; i++ {
472 | bench(numacc3)
473 | }
474 | }
475 |
476 | func BenchmarkNistNumacc4(b *testing.B) {
477 | for i := 0; i < b.N; i++ {
478 | bench(numacc4)
479 | }
480 | }
481 |
482 | func BenchmarkNistAll(b *testing.B) {
483 | for i := 0; i < b.N; i++ {
484 | bench(lew)
485 | bench(lottery)
486 | bench(mavro)
487 | bench(michelson)
488 | bench(pidigits)
489 | bench(numacc1)
490 | bench(numacc2)
491 | bench(numacc3)
492 | bench(numacc4)
493 | }
494 | }
495 |
496 | func test(d string, r, a, v float64, e error, t *testing.T) {
497 | if e != nil {
498 | t.Error(e)
499 | }
500 |
501 | var failure bool
502 | if math.IsNaN(r) || math.IsNaN(a) {
503 | failure = math.IsNaN(r) != math.IsNaN(a)
504 | } else if math.IsInf(r, 0) || math.IsInf(a, 0) {
505 | failure = math.IsInf(r, 0) != math.IsInf(a, 0)
506 | } else if a != 0 {
507 | failure = math.Abs(r-a)/math.Abs(a) > v
508 | } else {
509 | failure = math.Abs(r) > v
510 | }
511 |
512 | if failure {
513 | t.Errorf("%s => %v != %v", d, r, a)
514 | }
515 | }
516 |
--------------------------------------------------------------------------------
/norm.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | "math/rand"
6 | "strings"
7 | "time"
8 | )
9 |
10 | // NormPpfRvs generates random variates using the Point Percentile Function.
11 | // For more information please visit: https://demonstrations.wolfram.com/TheMethodOfInverseTransforms/
12 | func NormPpfRvs(loc float64, scale float64, size int) []float64 {
13 | rand.Seed(time.Now().UnixNano())
14 | var toReturn []float64
15 | for i := 0; i < size; i++ {
16 | toReturn = append(toReturn, NormPpf(rand.Float64(), loc, scale))
17 | }
18 | return toReturn
19 | }
20 |
21 | // NormBoxMullerRvs generates random variates using the Box–Muller transform.
22 | // For more information please visit: http://mathworld.wolfram.com/Box-MullerTransformation.html
23 | func NormBoxMullerRvs(loc float64, scale float64, size int) []float64 {
24 | rand.Seed(time.Now().UnixNano())
25 | var toReturn []float64
26 | for i := 0; i < int(float64(size/2)+float64(size%2)); i++ {
27 | // u1 and u2 are uniformly distributed random numbers between 0 and 1.
28 | u1 := rand.Float64()
29 | u2 := rand.Float64()
30 | // x1 and x2 are normally distributed random numbers.
31 | x1 := loc + (scale * (math.Sqrt(-2*math.Log(u1)) * math.Cos(2*math.Pi*u2)))
32 | toReturn = append(toReturn, x1)
33 | if (i+1)*2 <= size {
34 | x2 := loc + (scale * (math.Sqrt(-2*math.Log(u1)) * math.Sin(2*math.Pi*u2)))
35 | toReturn = append(toReturn, x2)
36 | }
37 | }
38 | return toReturn
39 | }
40 |
41 | // NormPdf is the probability density function.
42 | func NormPdf(x float64, loc float64, scale float64) float64 {
43 | return (math.Pow(math.E, -(math.Pow(x-loc, 2))/(2*math.Pow(scale, 2)))) / (scale * math.Sqrt(2*math.Pi))
44 | }
45 |
46 | // NormLogPdf is the log of the probability density function.
47 | func NormLogPdf(x float64, loc float64, scale float64) float64 {
48 | return math.Log((math.Pow(math.E, -(math.Pow(x-loc, 2))/(2*math.Pow(scale, 2)))) / (scale * math.Sqrt(2*math.Pi)))
49 | }
50 |
51 | // NormCdf is the cumulative distribution function.
52 | func NormCdf(x float64, loc float64, scale float64) float64 {
53 | return 0.5 * (1 + math.Erf((x-loc)/(scale*math.Sqrt(2))))
54 | }
55 |
56 | // NormLogCdf is the log of the cumulative distribution function.
57 | func NormLogCdf(x float64, loc float64, scale float64) float64 {
58 | return math.Log(0.5 * (1 + math.Erf((x-loc)/(scale*math.Sqrt(2)))))
59 | }
60 |
61 | // NormSf is the survival function (also defined as 1 - cdf, but sf is sometimes more accurate).
62 | func NormSf(x float64, loc float64, scale float64) float64 {
63 | return 1 - 0.5*(1+math.Erf((x-loc)/(scale*math.Sqrt(2))))
64 | }
65 |
66 | // NormLogSf is the log of the survival function.
67 | func NormLogSf(x float64, loc float64, scale float64) float64 {
68 | return math.Log(1 - 0.5*(1+math.Erf((x-loc)/(scale*math.Sqrt(2)))))
69 | }
70 |
71 | // NormPpf is the point percentile function.
72 | // This is based on Peter John Acklam's inverse normal CDF.
73 | // algorithm: http://home.online.no/~pjacklam/notes/invnorm/ (no longer visible).
74 | // For more information please visit: https://stackedboxes.org/2017/05/01/acklams-normal-quantile-function/
75 | func NormPpf(p float64, loc float64, scale float64) (x float64) {
76 | const (
77 | a1 = -3.969683028665376e+01
78 | a2 = 2.209460984245205e+02
79 | a3 = -2.759285104469687e+02
80 | a4 = 1.383577518672690e+02
81 | a5 = -3.066479806614716e+01
82 | a6 = 2.506628277459239e+00
83 |
84 | b1 = -5.447609879822406e+01
85 | b2 = 1.615858368580409e+02
86 | b3 = -1.556989798598866e+02
87 | b4 = 6.680131188771972e+01
88 | b5 = -1.328068155288572e+01
89 |
90 | c1 = -7.784894002430293e-03
91 | c2 = -3.223964580411365e-01
92 | c3 = -2.400758277161838e+00
93 | c4 = -2.549732539343734e+00
94 | c5 = 4.374664141464968e+00
95 | c6 = 2.938163982698783e+00
96 |
97 | d1 = 7.784695709041462e-03
98 | d2 = 3.224671290700398e-01
99 | d3 = 2.445134137142996e+00
100 | d4 = 3.754408661907416e+00
101 |
102 | plow = 0.02425
103 | phigh = 1 - plow
104 | )
105 |
106 | if p < 0 || p > 1 {
107 | return math.NaN()
108 | } else if p == 0 {
109 | return -math.Inf(0)
110 | } else if p == 1 {
111 | return math.Inf(0)
112 | }
113 |
114 | if p < plow {
115 | q := math.Sqrt(-2 * math.Log(p))
116 | x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) /
117 | ((((d1*q+d2)*q+d3)*q+d4)*q + 1)
118 | } else if phigh < p {
119 | q := math.Sqrt(-2 * math.Log(1-p))
120 | x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) /
121 | ((((d1*q+d2)*q+d3)*q+d4)*q + 1)
122 | } else {
123 | q := p - 0.5
124 | r := q * q
125 | x = (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r + a6) * q /
126 | (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r + 1)
127 | }
128 |
129 | e := 0.5*math.Erfc(-x/math.Sqrt2) - p
130 | u := e * math.Sqrt(2*math.Pi) * math.Exp(x*x/2)
131 | x = x - u/(1+x*u/2)
132 |
133 | return x*scale + loc
134 | }
135 |
136 | // NormIsf is the inverse survival function (inverse of sf).
137 | func NormIsf(p float64, loc float64, scale float64) (x float64) {
138 | if -NormPpf(p, loc, scale) == 0 {
139 | return 0
140 | }
141 | return -NormPpf(p, loc, scale)
142 | }
143 |
144 | // NormMoment approximates the non-central (raw) moment of order n.
145 | // For more information please visit: https://math.stackexchange.com/questions/1945448/methods-for-finding-raw-moments-of-the-normal-distribution
146 | func NormMoment(n int, loc float64, scale float64) float64 {
147 | toReturn := 0.0
148 | for i := 0; i < n+1; i++ {
149 | if (n-i)%2 == 0 {
150 | toReturn += float64(Ncr(n, i)) * (math.Pow(loc, float64(i))) * (math.Pow(scale, float64(n-i))) *
151 | (float64(factorial(n-i)) / ((math.Pow(2.0, float64((n-i)/2))) *
152 | float64(factorial((n-i)/2))))
153 | }
154 | }
155 | return toReturn
156 | }
157 |
158 | // NormStats returns the mean, variance, skew, and/or kurtosis.
159 | // Mean(‘m’), variance(‘v’), skew(‘s’), and/or kurtosis(‘k’).
160 | // Takes string containing any of 'mvsk'.
161 | // Returns array of m v s k in that order.
162 | func NormStats(loc float64, scale float64, moments string) []float64 {
163 | var toReturn []float64
164 | if strings.ContainsAny(moments, "m") {
165 | toReturn = append(toReturn, loc)
166 | }
167 | if strings.ContainsAny(moments, "v") {
168 | toReturn = append(toReturn, math.Pow(scale, 2))
169 | }
170 | if strings.ContainsAny(moments, "s") {
171 | toReturn = append(toReturn, 0.0)
172 | }
173 | if strings.ContainsAny(moments, "k") {
174 | toReturn = append(toReturn, 0.0)
175 | }
176 | return toReturn
177 | }
178 |
179 | // NormEntropy is the differential entropy of the RV.
180 | func NormEntropy(loc float64, scale float64) float64 {
181 | return math.Log(scale * math.Sqrt(2*math.Pi*math.E))
182 | }
183 |
184 | // NormFit returns the maximum likelihood estimators for the Normal Distribution.
185 | // Takes array of float64 values.
186 | // Returns array of Mean followed by Standard Deviation.
187 | func NormFit(data []float64) [2]float64 {
188 | sum := 0.00
189 | for i := 0; i < len(data); i++ {
190 | sum += data[i]
191 | }
192 | mean := sum / float64(len(data))
193 | stdNumerator := 0.00
194 | for i := 0; i < len(data); i++ {
195 | stdNumerator += math.Pow(data[i]-mean, 2)
196 | }
197 | return [2]float64{mean, math.Sqrt((stdNumerator) / (float64(len(data))))}
198 | }
199 |
200 | // NormMedian is the median of the distribution.
201 | func NormMedian(loc float64, scale float64) float64 {
202 | return loc
203 | }
204 |
205 | // NormMean is the mean/expected value of the distribution.
206 | func NormMean(loc float64, scale float64) float64 {
207 | return loc
208 | }
209 |
210 | // NormVar is the variance of the distribution.
211 | func NormVar(loc float64, scale float64) float64 {
212 | return math.Pow(scale, 2)
213 | }
214 |
215 | // NormStd is the standard deviation of the distribution.
216 | func NormStd(loc float64, scale float64) float64 {
217 | return scale
218 | }
219 |
220 | // NormInterval finds endpoints of the range that contains alpha percent of the distribution.
221 | func NormInterval(alpha float64, loc float64, scale float64) [2]float64 {
222 | q1 := (1.0 - alpha) / 2
223 | q2 := (1.0 + alpha) / 2
224 | a := NormPpf(q1, loc, scale)
225 | b := NormPpf(q2, loc, scale)
226 | return [2]float64{a, b}
227 | }
228 |
229 | // factorial is the naive factorial algorithm.
230 | func factorial(x int) int {
231 | if x == 0 {
232 | return 1
233 | }
234 | return x * factorial(x-1)
235 | }
236 |
237 | // Ncr is an N choose R algorithm.
238 | // Aaron Cannon's algorithm.
239 | func Ncr(n, r int) int {
240 | if n <= 1 || r == 0 || n == r {
241 | return 1
242 | }
243 | if newR := n - r; newR < r {
244 | r = newR
245 | }
246 | if r == 1 {
247 | return n
248 | }
249 | ret := int(n - r + 1)
250 | for i, j := ret+1, int(2); j <= r; i, j = i+1, j+1 {
251 | ret = ret * i / j
252 | }
253 | return ret
254 | }
255 |
--------------------------------------------------------------------------------
/norm_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func TestNormPpf(t *testing.T) {
12 | if stats.NormPpf(0.5, 0, 1) != 0 {
13 | t.Error("Input 0.5, Expected 0")
14 | }
15 | if !veryclose(stats.NormPpf(0.1, 0, 1), -1.2815515655446004) {
16 | t.Error("Input 0.1, Expected -1.2815515655446004")
17 | }
18 | if stats.NormPpf(0.002423, 0, 1) != -2.817096255323953 {
19 | t.Error("Input 0.002423, Expected -2.817096255323953")
20 | }
21 | if !close(stats.NormPpf(1-0.002423, 0, 1), 2.817096255323956) {
22 | t.Error("Input 1 - 0.002423, Expected 2.817096255323956")
23 | }
24 |
25 | if !math.IsNaN(stats.NormPpf(1.1, 0, 1)) {
26 | t.Error("Input 1.1, Expected NaN")
27 | }
28 | if !math.IsNaN(stats.NormPpf(-1.1, 0, 1)) {
29 | t.Error("Input -0.1, Expected Nan")
30 | }
31 | if stats.NormPpf(0, 0, 1) != -math.Inf(1) {
32 | t.Error("Input 0, Expected -Inf")
33 | }
34 | if stats.NormPpf(1, 0, 1) != math.Inf(1) {
35 | t.Error("Input 1, Expected Inf")
36 | }
37 | }
38 |
39 | func TestNormCdf(t *testing.T) {
40 | if stats.NormCdf(0, 0, 1) != 0.5 {
41 | t.Error("Input 0, Expected 0.5")
42 | }
43 | if stats.NormCdf(0.5, 0, 1) != 0.6914624612740131 {
44 | t.Error("Input 0.5, Expected 0.6914624612740131")
45 | }
46 | if stats.NormCdf(-0.5, 0, 1) != 0.3085375387259869 {
47 | t.Error("Input -0.5, Expected 0.3085375387259869")
48 | }
49 | }
50 |
51 | func TestNormPdf(t *testing.T) {
52 | if stats.NormPdf(0.5, 0, 1) != 0.35206532676429947 {
53 | t.Error("Input 0.5, Expected 0.35206532676429947")
54 | }
55 | if stats.NormPdf(0, 0, 1) != 0.3989422804014327 {
56 | t.Error("Input 0, Expected 0.3989422804014327")
57 | }
58 | if stats.NormPdf(-0.5, 0, 1) != 0.35206532676429947 {
59 | t.Error("Input -0.5, Expected 0.35206532676429947")
60 | }
61 | }
62 |
63 | func TestNormLogPdf(t *testing.T) {
64 | if stats.NormLogPdf(0, 0, 1) != -0.9189385332046727 {
65 | t.Error("Input 0, Expected -0.9189385332046727")
66 | }
67 | if stats.NormPdf(0, 0, 1) != 0.3989422804014327 {
68 | t.Error("Input 0, Expected 0.3989422804014327")
69 | }
70 | if stats.NormPdf(-0.5, 0, 1) != 0.35206532676429947 {
71 | t.Error("Input -0.5, Expected 0.35206532676429947")
72 | }
73 | }
74 |
75 | func TestNormLogCdf(t *testing.T) {
76 | if stats.NormLogCdf(0.5, 0, 1) != -0.36894641528865635 {
77 | t.Error("Input 0.5, Expected -0.36894641528865635")
78 | }
79 | }
80 |
81 | func TestNormIsf(t *testing.T) {
82 | if stats.NormIsf(0.5, 0, 1) != 0 {
83 | t.Error("Input 0.5, Expected 0")
84 | }
85 | if !veryclose(stats.NormIsf(0.1, 0, 1), 1.2815515655446004) {
86 | t.Error("Input 0.1, Expected 1.2815515655446004")
87 | }
88 | }
89 |
90 | func TestNormSf(t *testing.T) {
91 | if stats.NormSf(0.5, 0, 1) != 0.3085375387259869 {
92 | t.Error("Input 0.5, Expected 0.3085375387259869")
93 | }
94 | }
95 |
96 | func TestNormLogSf(t *testing.T) {
97 | if stats.NormLogSf(0.5, 0, 1) != -1.1759117615936185 {
98 | t.Error("Input 0.5, Expected -1.1759117615936185")
99 | }
100 | }
101 |
102 | func TestNormMoment(t *testing.T) {
103 | if stats.NormMoment(4, 0, 1) != 3 {
104 | t.Error("Input 3, Expected 3")
105 | }
106 | if stats.NormMoment(4, 0, 1) != 3 {
107 | t.Error("Input 3, Expected 3")
108 | }
109 | }
110 |
111 | func TestNormStats(t *testing.T) {
112 | if !reflect.DeepEqual(stats.NormStats(0, 1, "m"), []float64{0}) {
113 | t.Error("Input 'm' , Expected 0")
114 | }
115 | if !reflect.DeepEqual(stats.NormStats(0, 1, "v"), []float64{1}) {
116 | t.Error("Input 'v' , Expected 1")
117 | }
118 | if !reflect.DeepEqual(stats.NormStats(0, 1, "s"), []float64{0}) {
119 | t.Error("Input 's' , Expected 0")
120 | }
121 | if !reflect.DeepEqual(stats.NormStats(0, 1, "k"), []float64{0}) {
122 | t.Error("Input 'k' , Expected 0")
123 | }
124 | }
125 |
126 | func TestNormEntropy(t *testing.T) {
127 | if stats.NormEntropy(0, 1) != 1.4189385332046727 {
128 | t.Error("Input ( 0 , 1 ), Expected 1.4189385332046727")
129 | }
130 | }
131 |
132 | func TestNormFit(t *testing.T) {
133 | if !reflect.DeepEqual(stats.NormFit([]float64{0, 2, 3, 4}), [2]float64{2.25, 1.479019945774904}) {
134 | t.Error("Input (0,2,3,4), Expected {2.25, 1.479019945774904}")
135 | }
136 | }
137 |
138 | func TestNormInterval(t *testing.T) {
139 | if !reflect.DeepEqual(stats.NormInterval(0.5, 0, 1), [2]float64{-0.6744897501960818, 0.674489750196082}) {
140 | t.Error("Input (50 % ), Expected {-0.6744897501960818, 0.674489750196082}")
141 | }
142 | }
143 |
144 | func TestNormMean(t *testing.T) {
145 | if stats.NormMean(0, 1) != 0 {
146 | t.Error("Input (0, 1), Expected 0")
147 | }
148 | }
149 |
150 | func TestNormMedian(t *testing.T) {
151 | if stats.NormMedian(0, 1) != 0 {
152 | t.Error("Input (0, 1), Expected 0")
153 | }
154 | }
155 |
156 | func TestNormVar(t *testing.T) {
157 | if stats.NormVar(0, 1) != 1 {
158 | t.Error("Input (0, 1), Expected 1")
159 | }
160 | }
161 |
162 | func TestNormStd(t *testing.T) {
163 | if stats.NormStd(0, 1) != 1 {
164 | t.Error("Input (0, 1), Expected 1")
165 | }
166 | }
167 |
168 | func TestNormPpfRvs(t *testing.T) {
169 | if len(stats.NormPpfRvs(0, 1, 101)) != 101 {
170 | t.Error("Input size=101, Expected 101")
171 | }
172 | }
173 |
174 | func TestNormBoxMullerRvs(t *testing.T) {
175 | if len(stats.NormBoxMullerRvs(0, 1, 101)) != 101 {
176 | t.Error("Input size=101, Expected 101")
177 | }
178 | }
179 |
180 | func TestNcr(t *testing.T) {
181 | if stats.Ncr(4, 1) != 4 {
182 | t.Error("Input 4 choose 1, Expected 4")
183 | }
184 | if stats.Ncr(4, 3) != 4 {
185 | t.Error("Input 4 choose 3, Expected 4")
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/outlier.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // Outliers holds mild and extreme outliers found in data
4 | type Outliers struct {
5 | Mild Float64Data
6 | Extreme Float64Data
7 | }
8 |
9 | // QuartileOutliers finds the mild and extreme outliers
10 | func QuartileOutliers(input Float64Data) (Outliers, error) {
11 | if input.Len() == 0 {
12 | return Outliers{}, EmptyInputErr
13 | }
14 |
15 | // Start by sorting a copy of the slice
16 | copy := sortedCopy(input)
17 |
18 | // Calculate the quartiles and interquartile range
19 | qs, _ := Quartile(copy)
20 | iqr, _ := InterQuartileRange(copy)
21 |
22 | // Calculate the lower and upper inner and outer fences
23 | lif := qs.Q1 - (1.5 * iqr)
24 | uif := qs.Q3 + (1.5 * iqr)
25 | lof := qs.Q1 - (3 * iqr)
26 | uof := qs.Q3 + (3 * iqr)
27 |
28 | // Find the data points that are outside of the
29 | // inner and upper fences and add them to mild
30 | // and extreme outlier slices
31 | var mild Float64Data
32 | var extreme Float64Data
33 | for _, v := range copy {
34 |
35 | if v < lof || v > uof {
36 | extreme = append(extreme, v)
37 | } else if v < lif || v > uif {
38 | mild = append(mild, v)
39 | }
40 | }
41 |
42 | // Wrap them into our struct
43 | return Outliers{mild, extreme}, nil
44 | }
45 |
--------------------------------------------------------------------------------
/outlier_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func TestQuartileOutliers(t *testing.T) {
10 | s1 := []float64{-1000, 1, 3, 4, 4, 6, 6, 6, 6, 7, 8, 15, 18, 100}
11 | o, _ := stats.QuartileOutliers(s1)
12 |
13 | if o.Mild[0] != 15 {
14 | t.Errorf("First Mild Outlier %v != 15", o.Mild[0])
15 | }
16 |
17 | if o.Mild[1] != 18 {
18 | t.Errorf("Second Mild Outlier %v != 18", o.Mild[1])
19 | }
20 |
21 | if o.Extreme[0] != -1000 {
22 | t.Errorf("First Extreme Outlier %v != -1000", o.Extreme[0])
23 | }
24 |
25 | if o.Extreme[1] != 100 {
26 | t.Errorf("Second Extreme Outlier %v != 100", o.Extreme[1])
27 | }
28 |
29 | _, err := stats.QuartileOutliers([]float64{})
30 | if err == nil {
31 | t.Errorf("Empty slice should have returned an error")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/percentile.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | // Percentile finds the relative standing in a slice of floats
8 | func Percentile(input Float64Data, percent float64) (percentile float64, err error) {
9 | length := input.Len()
10 | if length == 0 {
11 | return math.NaN(), EmptyInputErr
12 | }
13 |
14 | if length == 1 {
15 | return input[0], nil
16 | }
17 |
18 | if percent <= 0 || percent > 100 {
19 | return math.NaN(), BoundsErr
20 | }
21 |
22 | // Start by sorting a copy of the slice
23 | c := sortedCopy(input)
24 |
25 | // Multiply percent by length of input
26 | index := (percent / 100) * float64(len(c))
27 |
28 | // Check if the index is a whole number
29 | if index == float64(int64(index)) {
30 |
31 | // Convert float to int
32 | i := int(index)
33 |
34 | // Find the value at the index
35 | percentile = c[i-1]
36 |
37 | } else if index > 1 {
38 |
39 | // Convert float to int via truncation
40 | i := int(index)
41 |
42 | // Find the average of the index and following values
43 | percentile, _ = Mean(Float64Data{c[i-1], c[i]})
44 |
45 | } else {
46 | return math.NaN(), BoundsErr
47 | }
48 |
49 | return percentile, nil
50 |
51 | }
52 |
53 | // PercentileNearestRank finds the relative standing in a slice of floats using the Nearest Rank method
54 | func PercentileNearestRank(input Float64Data, percent float64) (percentile float64, err error) {
55 |
56 | // Find the length of items in the slice
57 | il := input.Len()
58 |
59 | // Return an error for empty slices
60 | if il == 0 {
61 | return math.NaN(), EmptyInputErr
62 | }
63 |
64 | // Return error for less than 0 or greater than 100 percentages
65 | if percent < 0 || percent > 100 {
66 | return math.NaN(), BoundsErr
67 | }
68 |
69 | // Start by sorting a copy of the slice
70 | c := sortedCopy(input)
71 |
72 | // Return the last item
73 | if percent == 100.0 {
74 | return c[il-1], nil
75 | }
76 |
77 | // Find ordinal ranking
78 | or := int(math.Ceil(float64(il) * percent / 100))
79 |
80 | // Return the item that is in the place of the ordinal rank
81 | if or == 0 {
82 | return c[0], nil
83 | }
84 | return c[or-1], nil
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/percentile_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func TestPercentile(t *testing.T) {
11 | m, _ := stats.Percentile([]float64{43, 54, 56, 61, 62, 66}, 90)
12 | if m != 64.0 {
13 | t.Errorf("%.1f != %.1f", m, 64.0)
14 | }
15 | m, _ = stats.Percentile([]float64{43}, 90)
16 | if m != 43.0 {
17 | t.Errorf("%.1f != %.1f", m, 43.0)
18 | }
19 | m, _ = stats.Percentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 50)
20 | if m != 5.0 {
21 | t.Errorf("%.1f != %.1f", m, 5.0)
22 | }
23 | m, _ = stats.Percentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 99.9)
24 | if m != 9.5 {
25 | t.Errorf("%.1f != %.1f", m, 9.5)
26 | }
27 | m, _ = stats.Percentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 100)
28 | if m != 10.0 {
29 | t.Errorf("%.1f != %.1f", m, 10.0)
30 | }
31 | _, err := stats.Percentile([]float64{}, 99.9)
32 | if err != stats.EmptyInputErr {
33 | t.Errorf("Empty slice didn't return expected error; got %v", err)
34 | }
35 | _, err = stats.Percentile([]float64{1, 2, 3, 4, 5}, 0)
36 | if err != stats.BoundsErr {
37 | t.Errorf("Zero percent didn't return expected error; got %v", err)
38 | }
39 | _, err = stats.Percentile([]float64{1, 2, 3, 4, 5}, 0.13)
40 | if err != stats.BoundsErr {
41 | t.Errorf("Too low percent didn't return expected error; got %v", err)
42 | }
43 | _, err = stats.Percentile([]float64{1, 2, 3, 4, 5}, 101)
44 | if err != stats.BoundsErr {
45 | t.Errorf("Too high percent didn't return expected error; got %v", err)
46 | }
47 | }
48 |
49 | func TestPercentileSortSideEffects(t *testing.T) {
50 | s := []float64{43, 54, 56, 44, 62, 66}
51 | a := []float64{43, 54, 56, 44, 62, 66}
52 | _, _ = stats.Percentile(s, 90)
53 | if !reflect.DeepEqual(s, a) {
54 | t.Errorf("%.1f != %.1f", s, a)
55 | }
56 | }
57 |
58 | func BenchmarkPercentileSmallFloatSlice(b *testing.B) {
59 | for i := 0; i < b.N; i++ {
60 | _, _ = stats.Percentile(makeFloatSlice(5), 50)
61 | }
62 | }
63 |
64 | func BenchmarkPercentileLargeFloatSlice(b *testing.B) {
65 | lf := makeFloatSlice(100000)
66 | b.ResetTimer()
67 | for i := 0; i < b.N; i++ {
68 | _, _ = stats.Percentile(lf, 50)
69 | }
70 | }
71 |
72 | func TestPercentileNearestRank(t *testing.T) {
73 | f1 := []float64{35, 20, 15, 40, 50}
74 | f2 := []float64{20, 6, 7, 8, 8, 10, 13, 15, 16, 3}
75 | f3 := makeFloatSlice(101)
76 |
77 | for _, c := range []struct {
78 | sample []float64
79 | percent float64
80 | result float64
81 | }{
82 | {f1, 30, 20},
83 | {f1, 40, 20},
84 | {f1, 50, 35},
85 | {f1, 75, 40},
86 | {f1, 95, 50},
87 | {f1, 99, 50},
88 | {f1, 99.9, 50},
89 | {f1, 100, 50},
90 | {f2, 25, 7},
91 | {f2, 50, 8},
92 | {f2, 75, 15},
93 | {f2, 100, 20},
94 | {f3, 1, 100},
95 | {f3, 99, 9900},
96 | {f3, 100, 10000},
97 | {f3, 0, 0},
98 | } {
99 | got, err := stats.PercentileNearestRank(c.sample, c.percent)
100 | if err != nil {
101 | t.Errorf("Should not have returned an error")
102 | }
103 | if got != c.result {
104 | t.Errorf("%v != %v", got, c.result)
105 | }
106 | }
107 |
108 | _, err := stats.PercentileNearestRank([]float64{}, 50)
109 | if err == nil {
110 | t.Errorf("Should have returned an empty slice error")
111 | }
112 |
113 | _, err = stats.PercentileNearestRank([]float64{1, 2, 3, 4, 5}, -0.01)
114 | if err == nil {
115 | t.Errorf("Should have returned an percentage must be above 0 error")
116 | }
117 |
118 | _, err = stats.PercentileNearestRank([]float64{1, 2, 3, 4, 5}, 110)
119 | if err == nil {
120 | t.Errorf("Should have returned an percentage must not be above 100 error")
121 | }
122 |
123 | }
124 |
125 | func BenchmarkPercentileNearestRankSmallFloatSlice(b *testing.B) {
126 | for i := 0; i < b.N; i++ {
127 | _, _ = stats.PercentileNearestRank(makeFloatSlice(5), 50)
128 | }
129 | }
130 |
131 | func BenchmarkPercentileNearestRankLargeFloatSlice(b *testing.B) {
132 | lf := makeFloatSlice(100000)
133 | b.ResetTimer()
134 | for i := 0; i < b.N; i++ {
135 | _, _ = stats.PercentileNearestRank(lf, 50)
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/quartile.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Quartiles holds the three quartile points
6 | type Quartiles struct {
7 | Q1 float64
8 | Q2 float64
9 | Q3 float64
10 | }
11 |
12 | // Quartile returns the three quartile points from a slice of data
13 | func Quartile(input Float64Data) (Quartiles, error) {
14 |
15 | il := input.Len()
16 | if il == 0 {
17 | return Quartiles{}, EmptyInputErr
18 | }
19 |
20 | // Start by sorting a copy of the slice
21 | copy := sortedCopy(input)
22 |
23 | // Find the cutoff places depeding on if
24 | // the input slice length is even or odd
25 | var c1 int
26 | var c2 int
27 | if il%2 == 0 {
28 | c1 = il / 2
29 | c2 = il / 2
30 | } else {
31 | c1 = (il - 1) / 2
32 | c2 = c1 + 1
33 | }
34 |
35 | // Find the Medians with the cutoff points
36 | Q1, _ := Median(copy[:c1])
37 | Q2, _ := Median(copy)
38 | Q3, _ := Median(copy[c2:])
39 |
40 | return Quartiles{Q1, Q2, Q3}, nil
41 |
42 | }
43 |
44 | // InterQuartileRange finds the range between Q1 and Q3
45 | func InterQuartileRange(input Float64Data) (float64, error) {
46 | if input.Len() == 0 {
47 | return math.NaN(), EmptyInputErr
48 | }
49 | qs, _ := Quartile(input)
50 | iqr := qs.Q3 - qs.Q1
51 | return iqr, nil
52 | }
53 |
54 | // Midhinge finds the average of the first and third quartiles
55 | func Midhinge(input Float64Data) (float64, error) {
56 | if input.Len() == 0 {
57 | return math.NaN(), EmptyInputErr
58 | }
59 | qs, _ := Quartile(input)
60 | mh := (qs.Q1 + qs.Q3) / 2
61 | return mh, nil
62 | }
63 |
64 | // Trimean finds the average of the median and the midhinge
65 | func Trimean(input Float64Data) (float64, error) {
66 | if input.Len() == 0 {
67 | return math.NaN(), EmptyInputErr
68 | }
69 |
70 | c := sortedCopy(input)
71 | q, _ := Quartile(c)
72 |
73 | return (q.Q1 + (q.Q2 * 2) + q.Q3) / 4, nil
74 | }
75 |
--------------------------------------------------------------------------------
/quartile_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func TestQuartile(t *testing.T) {
10 | s1 := []float64{6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49}
11 | s2 := []float64{7, 15, 36, 39, 40, 41}
12 |
13 | for _, c := range []struct {
14 | in []float64
15 | Q1 float64
16 | Q2 float64
17 | Q3 float64
18 | }{
19 | {s1, 15, 40, 43},
20 | {s2, 15, 37.5, 40},
21 | } {
22 | quartiles, err := stats.Quartile(c.in)
23 | if err != nil {
24 | t.Errorf("Should not have returned an error")
25 | }
26 |
27 | if quartiles.Q1 != c.Q1 {
28 | t.Errorf("Q1 %v != %v", quartiles.Q1, c.Q1)
29 | }
30 | if quartiles.Q2 != c.Q2 {
31 | t.Errorf("Q2 %v != %v", quartiles.Q2, c.Q2)
32 | }
33 | if quartiles.Q3 != c.Q3 {
34 | t.Errorf("Q3 %v != %v", quartiles.Q3, c.Q3)
35 | }
36 | }
37 |
38 | _, err := stats.Quartile([]float64{})
39 | if err == nil {
40 | t.Errorf("Empty slice should have returned an error")
41 | }
42 | }
43 |
44 | func TestInterQuartileRange(t *testing.T) {
45 | s1 := []float64{102, 104, 105, 107, 108, 109, 110, 112, 115, 116, 118}
46 | iqr, _ := stats.InterQuartileRange(s1)
47 |
48 | if iqr != 10 {
49 | t.Errorf("IQR %v != 10", iqr)
50 | }
51 |
52 | _, err := stats.InterQuartileRange([]float64{})
53 | if err == nil {
54 | t.Errorf("Empty slice should have returned an error")
55 | }
56 | }
57 |
58 | func TestMidhinge(t *testing.T) {
59 | s1 := []float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13}
60 | mh, _ := stats.Midhinge(s1)
61 |
62 | if mh != 7.5 {
63 | t.Errorf("Midhinge %v != 7.5", mh)
64 | }
65 |
66 | _, err := stats.Midhinge([]float64{})
67 | if err == nil {
68 | t.Errorf("Empty slice should have returned an error")
69 | }
70 | }
71 |
72 | func TestTrimean(t *testing.T) {
73 | s1 := []float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13}
74 | tr, _ := stats.Trimean(s1)
75 |
76 | if tr != 7.25 {
77 | t.Errorf("Trimean %v != 7.25", tr)
78 | }
79 |
80 | _, err := stats.Trimean([]float64{})
81 | if err == nil {
82 | t.Errorf("Empty slice should have returned an error")
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/ranksum.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | // import "math"
4 | //
5 | // // WilcoxonRankSum tests the null hypothesis that two sets
6 | // // of data are drawn from the same distribution. It does
7 | // // not handle ties between measurements in x and y.
8 | // //
9 | // // Parameters:
10 | // // data1 Float64Data: First set of data points.
11 | // // data2 Float64Data: Second set of data points.
12 | // // Length of both data samples must be equal.
13 | // //
14 | // // Return:
15 | // // statistic float64: The test statistic under the
16 | // // large-sample approximation that the
17 | // // rank sum statistic is normally distributed.
18 | // // pvalue float64: The two-sided p-value of the test
19 | // // err error: Any error from the input data parameters
20 | // //
21 | // // https://en.wikipedia.org/wiki/Wilcoxon_rank-sum_test
22 | // func WilcoxonRankSum(data1, data2 Float64Data) (float64, float64, error) {
23 | //
24 | // l1 := data1.Len()
25 | // l2 := data2.Len()
26 | //
27 | // if l1 == 0 || l2 == 0 {
28 | // return math.NaN(), math.NaN(), EmptyInputErr
29 | // }
30 | //
31 | // if l1 != l2 {
32 | // return math.NaN(), math.NaN(), SizeErr
33 | // }
34 | //
35 | // alldata := Float64Data{}
36 | // alldata = append(alldata, data1...)
37 | // alldata = append(alldata, data2...)
38 | //
39 | // // ranked :=
40 | //
41 | // return 0.0, 0.0, nil
42 | // }
43 | //
44 | // // x, y = map(np.asarray, (x, y))
45 | // // n1 = len(x)
46 | // // n2 = len(y)
47 | // // alldata = np.concatenate((x, y))
48 | // // ranked = rankdata(alldata)
49 | // // x = ranked[:n1]
50 | // // s = np.sum(x, axis=0)
51 | // // expected = n1 * (n1+n2+1) / 2.0
52 | // // z = (s - expected) / np.sqrt(n1*n2*(n1+n2+1)/12.0)
53 | // // prob = 2 * distributions.norm.sf(abs(z))
54 | // //
55 | // // return RanksumsResult(z, prob)
56 | //
57 | // // def rankdata(a, method='average'):
58 | // // """
59 | // // Assign ranks to data, dealing with ties appropriately.
60 | // // Ranks begin at 1. The `method` argument controls how ranks are assigned
61 | // // to equal values. See [1]_ for further discussion of ranking methods.
62 | // // Parameters
63 | // // ----------
64 | // // a : array_like
65 | // // The array of values to be ranked. The array is first flattened.
66 | // // method : str, optional
67 | // // The method used to assign ranks to tied elements.
68 | // // The options are 'average', 'min', 'max', 'dense' and 'ordinal'.
69 | // // 'average':
70 | // // The average of the ranks that would have been assigned to
71 | // // all the tied values is assigned to each value.
72 | // // 'min':
73 | // // The minimum of the ranks that would have been assigned to all
74 | // // the tied values is assigned to each value. (This is also
75 | // // referred to as "competition" ranking.)
76 | // // 'max':
77 | // // The maximum of the ranks that would have been assigned to all
78 | // // the tied values is assigned to each value.
79 | // // 'dense':
80 | // // Like 'min', but the rank of the next highest element is assigned
81 | // // the rank immediately after those assigned to the tied elements.
82 | // // 'ordinal':
83 | // // All values are given a distinct rank, corresponding to the order
84 | // // that the values occur in `a`.
85 | // // The default is 'average'.
86 | // // Returns
87 | // // -------
88 | // // ranks : ndarray
89 | // // An array of length equal to the size of `a`, containing rank
90 | // // scores.
91 | // // References
92 | // // ----------
93 | // // .. [1] "Ranking", https://en.wikipedia.org/wiki/Ranking
94 | // // Examples
95 | // // --------
96 | // // >>> from scipy.stats import rankdata
97 | // // >>> rankdata([0, 2, 3, 2])
98 | // // array([ 1. , 2.5, 4. , 2.5])
99 | // // """
100 | // //
101 | // // arr = np.ravel(np.asarray(a))
102 | // // algo = 'quicksort'
103 | // // sorter = np.argsort(arr, kind=algo)
104 | // //
105 | // // inv = np.empty(sorter.size, dtype=np.intp)
106 | // // inv[sorter] = np.arange(sorter.size, dtype=np.intp)
107 | // //
108 | // //
109 | // // arr = arr[sorter]
110 | // // obs = np.r_[True, arr[1:] != arr[:-1]]
111 | // // dense = obs.cumsum()[inv]
112 | // //
113 | // //
114 | // // # cumulative counts of each unique value
115 | // // count = np.r_[np.nonzero(obs)[0], len(obs)]
116 | // //
117 | // // # average method
118 | // // return .5 * (count[dense] + count[dense - 1] + 1)
119 | //
120 | // type rankable interface {
121 | // Len() int
122 | // RankEqual(int, int) bool
123 | // }
124 | //
125 | // func StandardRank(d rankable) []float64 {
126 | // r := make([]float64, d.Len())
127 | // var k int
128 | // for i := range r {
129 | // if i == 0 || !d.RankEqual(i, i-1) {
130 | // k = i + 1
131 | // }
132 | // r[i] = float64(k)
133 | // }
134 | // return r
135 | // }
136 | //
137 | // func ModifiedRank(d rankable) []float64 {
138 | // r := make([]float64, d.Len())
139 | // for i := range r {
140 | // k := i + 1
141 | // for j := i + 1; j < len(r) && d.RankEqual(i, j); j++ {
142 | // k = j + 1
143 | // }
144 | // r[i] = float64(k)
145 | // }
146 | // return r
147 | // }
148 | //
149 | // func DenseRank(d rankable) []float64 {
150 | // r := make([]float64, d.Len())
151 | // var k int
152 | // for i := range r {
153 | // if i == 0 || !d.RankEqual(i, i-1) {
154 | // k++
155 | // }
156 | // r[i] = float64(k)
157 | // }
158 | // return r
159 | // }
160 | //
161 | // func OrdinalRank(d rankable) []float64 {
162 | // r := make([]float64, d.Len())
163 | // for i := range r {
164 | // r[i] = float64(i + 1)
165 | // }
166 | // return r
167 | // }
168 | //
169 | // func FractionalRank(d rankable) []float64 {
170 | // r := make([]float64, d.Len())
171 | // for i := 0; i < len(r); {
172 | // var j int
173 | // f := float64(i + 1)
174 | // for j = i + 1; j < len(r) && d.RankEqual(i, j); j++ {
175 | // f += float64(j + 1)
176 | // }
177 | // f /= float64(j - i)
178 | // for ; i < j; i++ {
179 | // r[i] = f
180 | // }
181 | // }
182 | // return r
183 | // }
184 |
--------------------------------------------------------------------------------
/ranksum_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | // import (
4 | // "testing"
5 | // )
6 | //
7 | // // >>> y1=[125,115,130,140,140,115,140,125,140,135]
8 | // // >>> y2=[110,122,125,120,140,124,123,137,135,145]
9 | // // >>> ss.wilcoxon(y1, y2)
10 | // // (18.0, 0.5936305914425295)
11 | //
12 | // // func ExampleWilcoxonRankSum() {
13 | // // t, p, err := WilcoxonRankSum([]float64{3.0, 1.0, 0.2}, []float64{3.1, 1.2, 1.2})
14 | // // fmt.Println(t, p, err)
15 | // // // Output: 18.0, 0.5936305914425295, nil
16 | // //
17 | // // }
18 | //
19 | // func TestRanked(t *testing.T) {
20 | //
21 | // var data = []float64{0.1, 3.2, 3.2}
22 | //
23 | // StandardRank(data)
24 | // // show := func(name string, fn func(rankable) []float64) {
25 | // // fmt.Println(name, "Ranking:")
26 | // // r := fn(data)
27 | // // for i, d := range data {
28 | // // fmt.Printf("%4v\n", r[i])
29 | // // }
30 | // // }
31 | // //
32 | // // sort.Sort(data)
33 | // // show("Standard", StandardRank)
34 | // // show("\nModified", ModifiedRank)
35 | // // show("\nDense", DenseRank)
36 | // // show("\nOrdinal", OrdinalRank)
37 | // // show("\nFractional", FractionalRank)
38 | //
39 | // }
40 |
--------------------------------------------------------------------------------
/regression.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Series is a container for a series of data
6 | type Series []Coordinate
7 |
8 | // Coordinate holds the data in a series
9 | type Coordinate struct {
10 | X, Y float64
11 | }
12 |
13 | // LinearRegression finds the least squares linear regression on data series
14 | func LinearRegression(s Series) (regressions Series, err error) {
15 |
16 | if len(s) == 0 {
17 | return nil, EmptyInputErr
18 | }
19 |
20 | // Placeholder for the math to be done
21 | var sum [5]float64
22 |
23 | // Loop over data keeping index in place
24 | i := 0
25 | for ; i < len(s); i++ {
26 | sum[0] += s[i].X
27 | sum[1] += s[i].Y
28 | sum[2] += s[i].X * s[i].X
29 | sum[3] += s[i].X * s[i].Y
30 | sum[4] += s[i].Y * s[i].Y
31 | }
32 |
33 | // Find gradient and intercept
34 | f := float64(i)
35 | gradient := (f*sum[3] - sum[0]*sum[1]) / (f*sum[2] - sum[0]*sum[0])
36 | intercept := (sum[1] / f) - (gradient * sum[0] / f)
37 |
38 | // Create the new regression series
39 | for j := 0; j < len(s); j++ {
40 | regressions = append(regressions, Coordinate{
41 | X: s[j].X,
42 | Y: s[j].X*gradient + intercept,
43 | })
44 | }
45 |
46 | return regressions, nil
47 | }
48 |
49 | // ExponentialRegression returns an exponential regression on data series
50 | func ExponentialRegression(s Series) (regressions Series, err error) {
51 |
52 | if len(s) == 0 {
53 | return nil, EmptyInputErr
54 | }
55 |
56 | var sum [6]float64
57 |
58 | for i := 0; i < len(s); i++ {
59 | if s[i].Y < 0 {
60 | return nil, YCoordErr
61 | }
62 | sum[0] += s[i].X
63 | sum[1] += s[i].Y
64 | sum[2] += s[i].X * s[i].X * s[i].Y
65 | sum[3] += s[i].Y * math.Log(s[i].Y)
66 | sum[4] += s[i].X * s[i].Y * math.Log(s[i].Y)
67 | sum[5] += s[i].X * s[i].Y
68 | }
69 |
70 | denominator := (sum[1]*sum[2] - sum[5]*sum[5])
71 | a := math.Pow(math.E, (sum[2]*sum[3]-sum[5]*sum[4])/denominator)
72 | b := (sum[1]*sum[4] - sum[5]*sum[3]) / denominator
73 |
74 | for j := 0; j < len(s); j++ {
75 | regressions = append(regressions, Coordinate{
76 | X: s[j].X,
77 | Y: a * math.Exp(b*s[j].X),
78 | })
79 | }
80 |
81 | return regressions, nil
82 | }
83 |
84 | // LogarithmicRegression returns an logarithmic regression on data series
85 | func LogarithmicRegression(s Series) (regressions Series, err error) {
86 |
87 | if len(s) == 0 {
88 | return nil, EmptyInputErr
89 | }
90 |
91 | var sum [4]float64
92 |
93 | i := 0
94 | for ; i < len(s); i++ {
95 | sum[0] += math.Log(s[i].X)
96 | sum[1] += s[i].Y * math.Log(s[i].X)
97 | sum[2] += s[i].Y
98 | sum[3] += math.Pow(math.Log(s[i].X), 2)
99 | }
100 |
101 | f := float64(i)
102 | a := (f*sum[1] - sum[2]*sum[0]) / (f*sum[3] - sum[0]*sum[0])
103 | b := (sum[2] - a*sum[0]) / f
104 |
105 | for j := 0; j < len(s); j++ {
106 | regressions = append(regressions, Coordinate{
107 | X: s[j].X,
108 | Y: b + a*math.Log(s[j].X),
109 | })
110 | }
111 |
112 | return regressions, nil
113 | }
114 |
--------------------------------------------------------------------------------
/regression_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleLinearRegression() {
11 | data := []stats.Coordinate{
12 | {1, 2.3},
13 | {2, 3.3},
14 | {3, 3.7},
15 | }
16 |
17 | r, _ := stats.LinearRegression(data)
18 | fmt.Println(r)
19 | // Output: [{1 2.400000000000001} {2 3.1} {3 3.7999999999999994}]
20 | }
21 |
22 | func TestLinearRegression(t *testing.T) {
23 | data := []stats.Coordinate{
24 | {1, 2.3},
25 | {2, 3.3},
26 | {3, 3.7},
27 | {4, 4.3},
28 | {5, 5.3},
29 | }
30 |
31 | r, _ := stats.LinearRegression(data)
32 | a := 2.3800000000000026
33 | if !close(r[0].Y, a) {
34 | t.Errorf("%v != %v", r[0].Y, a)
35 | }
36 | a = 3.0800000000000014
37 | if !veryclose(r[1].Y, a) {
38 | t.Errorf("%v != %v", r[1].Y, a)
39 | }
40 | a = 3.7800000000000002
41 | if r[2].Y != a {
42 | t.Errorf("%v != %v", r[2].Y, a)
43 | }
44 | a = 4.479999999999999
45 | if !veryclose(r[3].Y, a) {
46 | t.Errorf("%v != %v", r[3].Y, a)
47 | }
48 | a = 5.179999999999998
49 | if !veryclose(r[4].Y, a) {
50 | t.Errorf("%v != %v", r[4].Y, a)
51 | }
52 |
53 | _, err := stats.LinearRegression([]stats.Coordinate{})
54 | if err == nil {
55 | t.Errorf("Empty slice should have returned an error")
56 | }
57 | }
58 |
59 | func TestExponentialRegression(t *testing.T) {
60 | data := []stats.Coordinate{
61 | {1, 2.3},
62 | {2, 3.3},
63 | {3, 3.7},
64 | {4, 4.3},
65 | {5, 5.3},
66 | }
67 |
68 | r, _ := stats.ExponentialRegression(data)
69 | a, _ := stats.Round(r[0].Y, 3)
70 | if a != 2.515 {
71 | t.Errorf("%v != %v", r[0].Y, 2.515)
72 | }
73 | a, _ = stats.Round(r[1].Y, 3)
74 | if a != 3.032 {
75 | t.Errorf("%v != %v", r[1].Y, 3.032)
76 | }
77 | a, _ = stats.Round(r[2].Y, 3)
78 | if a != 3.655 {
79 | t.Errorf("%v != %v", r[2].Y, 3.655)
80 | }
81 | a, _ = stats.Round(r[3].Y, 3)
82 | if a != 4.407 {
83 | t.Errorf("%v != %v", r[3].Y, 4.407)
84 | }
85 | a, _ = stats.Round(r[4].Y, 3)
86 | if a != 5.313 {
87 | t.Errorf("%v != %v", r[4].Y, 5.313)
88 | }
89 |
90 | _, err := stats.ExponentialRegression([]stats.Coordinate{})
91 | if err == nil {
92 | t.Errorf("Empty slice should have returned an error")
93 | }
94 | }
95 |
96 | func TestExponentialRegressionYCoordErr(t *testing.T) {
97 | c := []stats.Coordinate{{1, -5}, {4, 25}, {6, 5}}
98 | _, err := stats.ExponentialRegression(c)
99 | if err != stats.YCoordErr {
100 | t.Errorf(err.Error())
101 | }
102 | }
103 |
104 | func TestLogarithmicRegression(t *testing.T) {
105 | data := []stats.Coordinate{
106 | {1, 2.3},
107 | {2, 3.3},
108 | {3, 3.7},
109 | {4, 4.3},
110 | {5, 5.3},
111 | }
112 |
113 | r, _ := stats.LogarithmicRegression(data)
114 | a := 2.1520822363811702
115 | if !close(r[0].Y, a) {
116 | t.Errorf("%v != %v", r[0].Y, a)
117 | }
118 | a = 3.3305559222492214
119 | if !veryclose(r[1].Y, a) {
120 | t.Errorf("%v != %v", r[1].Y, a)
121 | }
122 | a = 4.019918836568674
123 | if !veryclose(r[2].Y, a) {
124 | t.Errorf("%v != %v", r[2].Y, a)
125 | }
126 | a = 4.509029608117273
127 | if !veryclose(r[3].Y, a) {
128 | t.Errorf("%v != %v", r[3].Y, a)
129 | }
130 | a = 4.888413396683663
131 | if !veryclose(r[4].Y, a) {
132 | t.Errorf("%v != %v", r[4].Y, a)
133 | }
134 |
135 | _, err := stats.LogarithmicRegression([]stats.Coordinate{})
136 | if err == nil {
137 | t.Errorf("Empty slice should have returned an error")
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/round.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Round a float to a specific decimal place or precision
6 | func Round(input float64, places int) (rounded float64, err error) {
7 |
8 | // If the float is not a number
9 | if math.IsNaN(input) {
10 | return math.NaN(), NaNErr
11 | }
12 |
13 | // Find out the actual sign and correct the input for later
14 | sign := 1.0
15 | if input < 0 {
16 | sign = -1
17 | input *= -1
18 | }
19 |
20 | // Use the places arg to get the amount of precision wanted
21 | precision := math.Pow(10, float64(places))
22 |
23 | // Find the decimal place we are looking to round
24 | digit := input * precision
25 |
26 | // Get the actual decimal number as a fraction to be compared
27 | _, decimal := math.Modf(digit)
28 |
29 | // If the decimal is less than .5 we round down otherwise up
30 | if decimal >= 0.5 {
31 | rounded = math.Ceil(digit)
32 | } else {
33 | rounded = math.Floor(digit)
34 | }
35 |
36 | // Finally we do the math to actually create a rounded number
37 | return rounded / precision * sign, nil
38 | }
39 |
--------------------------------------------------------------------------------
/round_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleRound() {
12 | rounded, _ := stats.Round(1.534424, 1)
13 | fmt.Println(rounded)
14 | // Output: 1.5
15 | }
16 |
17 | func TestRound(t *testing.T) {
18 | for _, c := range []struct {
19 | number float64
20 | decimals int
21 | result float64
22 | }{
23 | {0.1111, 1, 0.1},
24 | {-0.1111, 2, -0.11},
25 | {5.3253, 3, 5.325},
26 | {5.3258, 3, 5.326},
27 | {5.3253, 0, 5.0},
28 | {5.55, 1, 5.6},
29 | } {
30 | m, err := stats.Round(c.number, c.decimals)
31 | if err != nil {
32 | t.Errorf("Returned an error")
33 | }
34 | if m != c.result {
35 | t.Errorf("%.1f != %.1f", m, c.result)
36 | }
37 |
38 | }
39 | _, err := stats.Round(math.NaN(), 2)
40 | if err == nil {
41 | t.Errorf("Round should error on NaN")
42 | }
43 | }
44 |
45 | func BenchmarkRound(b *testing.B) {
46 | for i := 0; i < b.N; i++ {
47 | _, _ = stats.Round(0.1111, 1)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/sample.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "math/rand"
5 | "sort"
6 | )
7 |
8 | // Sample returns sample from input with replacement or without
9 | func Sample(input Float64Data, takenum int, replacement bool) ([]float64, error) {
10 |
11 | if input.Len() == 0 {
12 | return nil, EmptyInputErr
13 | }
14 |
15 | length := input.Len()
16 | if replacement {
17 |
18 | result := Float64Data{}
19 | rand.Seed(unixnano())
20 |
21 | // In every step, randomly take the num for
22 | for i := 0; i < takenum; i++ {
23 | idx := rand.Intn(length)
24 | result = append(result, input[idx])
25 | }
26 |
27 | return result, nil
28 |
29 | } else if !replacement && takenum <= length {
30 |
31 | rand.Seed(unixnano())
32 |
33 | // Get permutation of number of indexies
34 | perm := rand.Perm(length)
35 | result := Float64Data{}
36 |
37 | // Get element of input by permutated index
38 | for _, idx := range perm[0:takenum] {
39 | result = append(result, input[idx])
40 | }
41 |
42 | return result, nil
43 |
44 | }
45 |
46 | return nil, BoundsErr
47 | }
48 |
49 | // StableSample like stable sort, it returns samples from input while keeps the order of original data.
50 | func StableSample(input Float64Data, takenum int) ([]float64, error) {
51 | if input.Len() == 0 {
52 | return nil, EmptyInputErr
53 | }
54 |
55 | length := input.Len()
56 |
57 | if takenum <= length {
58 |
59 | rand.Seed(unixnano())
60 |
61 | perm := rand.Perm(length)
62 | perm = perm[0:takenum]
63 | // Sort perm before applying
64 | sort.Ints(perm)
65 | result := Float64Data{}
66 |
67 | for _, idx := range perm {
68 | result = append(result, input[idx])
69 | }
70 |
71 | return result, nil
72 |
73 | }
74 |
75 | return nil, BoundsErr
76 | }
77 |
--------------------------------------------------------------------------------
/sample_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/montanaflynn/stats"
7 | )
8 |
9 | func TestSample(t *testing.T) {
10 | _, err := stats.Sample([]float64{}, 10, false)
11 | if err == nil {
12 | t.Errorf("should return an error")
13 | }
14 |
15 | _, err = stats.Sample([]float64{0.1, 0.2}, 10, false)
16 | if err == nil {
17 | t.Errorf("should return an error")
18 | }
19 | }
20 |
21 | func TestSampleWithoutReplacement(t *testing.T) {
22 | arr := []float64{0.1, 0.2, 0.3, 0.4, 0.5}
23 | result, _ := stats.Sample(arr, 5, false)
24 | checks := map[float64]bool{}
25 | for _, res := range result {
26 | _, ok := checks[res]
27 | if ok {
28 | t.Errorf("%v already seen", res)
29 | }
30 | checks[res] = true
31 | }
32 | }
33 |
34 | func TestSampleWithReplacement(t *testing.T) {
35 | arr := []float64{0.1, 0.2, 0.3, 0.4, 0.5}
36 | numsamples := 100
37 | result, _ := stats.Sample(arr, numsamples, true)
38 | if len(result) != numsamples {
39 | t.Errorf("%v != %v", len(result), numsamples)
40 | }
41 | }
42 |
43 | func TestStableSample(t *testing.T) {
44 | _, err := stats.StableSample(stats.Float64Data{}, 10)
45 | if err != stats.EmptyInputErr {
46 | t.Errorf("should return EmptyInputError when sampling an empty data")
47 | }
48 | _, err = stats.StableSample(stats.Float64Data{1.0, 2.0}, 10)
49 | if err != stats.BoundsErr {
50 | t.Errorf("should return BoundsErr when sampling size exceeds the maximum element size of data")
51 | }
52 | arr := []float64{1.0, 3.0, 2.0, -1.0, 5.0}
53 | locations := map[float64]int{
54 | 1.0: 0,
55 | 3.0: 1,
56 | 2.0: 2,
57 | -1.0: 3,
58 | 5.0: 4,
59 | }
60 | ret, _ := stats.StableSample(arr, 3)
61 | if len(ret) != 3 {
62 | t.Errorf("returned wrong sample size")
63 | }
64 | for i := 1; i < 3; i++ {
65 | if locations[ret[i]] < locations[ret[i-1]] {
66 | t.Errorf("doesn't keep order")
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/sigmoid.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Sigmoid returns the input values in the range of -1 to 1
6 | // along the sigmoid or s-shaped curve, commonly used in
7 | // machine learning while training neural networks as an
8 | // activation function.
9 | func Sigmoid(input Float64Data) ([]float64, error) {
10 | if input.Len() == 0 {
11 | return Float64Data{}, EmptyInput
12 | }
13 | s := make([]float64, len(input))
14 | for i, v := range input {
15 | s[i] = 1 / (1 + math.Exp(-v))
16 | }
17 | return s, nil
18 | }
19 |
--------------------------------------------------------------------------------
/sigmoid_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleSigmoid() {
11 | s, _ := stats.Sigmoid([]float64{3.0, 1.0, 2.1})
12 | fmt.Println(s)
13 | // Output: [0.9525741268224334 0.7310585786300049 0.8909031788043871]
14 | }
15 |
16 | func TestSigmoidEmptyInput(t *testing.T) {
17 | _, err := stats.Sigmoid([]float64{})
18 | if err != stats.EmptyInputErr {
19 | t.Errorf("Should have returned empty input error")
20 | }
21 | }
22 |
23 | func TestSigmoid(t *testing.T) {
24 | sm, err := stats.Sigmoid([]float64{-0.54761371, 17.04850603, 4.86054302})
25 | if err != nil {
26 | t.Error(err)
27 | }
28 |
29 | a := 0.3664182235138545
30 | if sm[0] != a {
31 | t.Errorf("%v != %v", sm[0], a)
32 | }
33 |
34 | a = 0.9999999605608187
35 | if sm[1] != a {
36 | t.Errorf("%v != %v", sm[1], a)
37 | }
38 |
39 | a = 0.9923132671908277
40 | if sm[2] != a {
41 | t.Errorf("%v != %v", sm[2], a)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/softmax.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // SoftMax returns the input values in the range of 0 to 1
6 | // with sum of all the probabilities being equal to one. It
7 | // is commonly used in machine learning neural networks.
8 | func SoftMax(input Float64Data) ([]float64, error) {
9 | if input.Len() == 0 {
10 | return Float64Data{}, EmptyInput
11 | }
12 |
13 | s := 0.0
14 | c, _ := Max(input)
15 | for _, e := range input {
16 | s += math.Exp(e - c)
17 | }
18 |
19 | sm := make([]float64, len(input))
20 | for i, v := range input {
21 | sm[i] = math.Exp(v-c) / s
22 | }
23 |
24 | return sm, nil
25 | }
26 |
--------------------------------------------------------------------------------
/softmax_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func ExampleSoftMax() {
11 | sm, _ := stats.SoftMax([]float64{3.0, 1.0, 0.2})
12 | fmt.Println(sm)
13 | // Output: [0.8360188027814407 0.11314284146556013 0.05083835575299916]
14 | }
15 |
16 | func TestSoftMaxEmptyInput(t *testing.T) {
17 | _, err := stats.SoftMax([]float64{})
18 | if err != stats.EmptyInputErr {
19 | t.Errorf("Should have returned empty input error")
20 | }
21 | }
22 |
23 | func TestSoftMax(t *testing.T) {
24 | sm, err := stats.SoftMax([]float64{3.0, 1.0, 0.2})
25 | if err != nil {
26 | t.Error(err)
27 | }
28 |
29 | a := 0.8360188027814407
30 | if sm[0] != a {
31 | t.Errorf("%v != %v", sm[0], a)
32 | }
33 |
34 | a = 0.11314284146556013
35 | if sm[1] != a {
36 | t.Errorf("%v != %v", sm[1], a)
37 | }
38 |
39 | a = 0.05083835575299916
40 | if sm[2] != a {
41 | t.Errorf("%v != %v", sm[1], a)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/sum.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // Sum adds all the numbers of a slice together
6 | func Sum(input Float64Data) (sum float64, err error) {
7 |
8 | if input.Len() == 0 {
9 | return math.NaN(), EmptyInputErr
10 | }
11 |
12 | // Add em up
13 | for _, n := range input {
14 | sum += n
15 | }
16 |
17 | return sum, nil
18 | }
19 |
--------------------------------------------------------------------------------
/sum_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/montanaflynn/stats"
9 | )
10 |
11 | func ExampleSum() {
12 | d := []float64{1.1, 2.2, 3.3}
13 | a, _ := stats.Sum(d)
14 | fmt.Println(a)
15 | // Output: 6.6
16 | }
17 |
18 | func TestSum(t *testing.T) {
19 | for _, c := range []struct {
20 | in []float64
21 | out float64
22 | }{
23 | {[]float64{1, 2, 3}, 6},
24 | {[]float64{1.0, 1.1, 1.2, 2.2}, 5.5},
25 | {[]float64{1, -1, 2, -3}, -1},
26 | } {
27 | got, err := stats.Sum(c.in)
28 | if err != nil {
29 | t.Errorf("Returned an error")
30 | }
31 | if !reflect.DeepEqual(c.out, got) {
32 | t.Errorf("Sum(%.1f) => %.1f != %.1f", c.in, got, c.out)
33 | }
34 | }
35 | _, err := stats.Sum([]float64{})
36 | if err == nil {
37 | t.Errorf("Empty slice should have returned an error")
38 | }
39 | }
40 |
41 | func BenchmarkSumSmallFloatSlice(b *testing.B) {
42 | for i := 0; i < b.N; i++ {
43 | _, _ = stats.Sum(makeFloatSlice(5))
44 | }
45 | }
46 |
47 | func BenchmarkSumLargeFloatSlice(b *testing.B) {
48 | lf := makeFloatSlice(100000)
49 | b.ResetTimer()
50 | for i := 0; i < b.N; i++ {
51 | _, _ = stats.Sum(lf)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test_utils_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | // Approximate float comparisons
4 | // Taken from the standard library's math/all_test.go
5 | func tolerance(a, b, e float64) bool {
6 | // Multiplying by e here can underflow denormal values to zero.
7 | // Check a==b so that at least if a and b are small and identical
8 | // we say they match.
9 | if a == b {
10 | return true
11 | }
12 | d := a - b
13 | if d < 0 {
14 | d = -d
15 | }
16 |
17 | // note: b is correct (expected) value, a is actual value.
18 | // make error tolerance a fraction of b, not a.
19 | if b != 0 {
20 | e = e * b
21 | if e < 0 {
22 | e = -e
23 | }
24 | }
25 | return d < e
26 | }
27 | func close(a, b float64) bool { return tolerance(a, b, 1e-14) }
28 | func veryclose(a, b float64) bool { return tolerance(a, b, 4e-16) }
29 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "sort"
5 | "time"
6 | )
7 |
8 | // float64ToInt rounds a float64 to an int
9 | func float64ToInt(input float64) (output int) {
10 | r, _ := Round(input, 0)
11 | return int(r)
12 | }
13 |
14 | // unixnano returns nanoseconds from UTC epoch
15 | func unixnano() int64 {
16 | return time.Now().UTC().UnixNano()
17 | }
18 |
19 | // copyslice copies a slice of float64s
20 | func copyslice(input Float64Data) Float64Data {
21 | s := make(Float64Data, input.Len())
22 | copy(s, input)
23 | return s
24 | }
25 |
26 | // sortedCopy returns a sorted copy of float64s
27 | func sortedCopy(input Float64Data) (copy Float64Data) {
28 | copy = copyslice(input)
29 | sort.Float64s(copy)
30 | return
31 | }
32 |
33 | // sortedCopyDif returns a sorted copy of float64s
34 | // only if the original data isn't sorted.
35 | // Only use this if returned slice won't be manipulated!
36 | func sortedCopyDif(input Float64Data) (copy Float64Data) {
37 | if sort.Float64sAreSorted(input) {
38 | return input
39 | }
40 | copy = copyslice(input)
41 | sort.Float64s(copy)
42 | return
43 | }
44 |
--------------------------------------------------------------------------------
/util_test.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestFloat64ToInt(t *testing.T) {
8 | m := float64ToInt(234.0234)
9 | if m != 234 {
10 | t.Errorf("%x != %x", m, 234)
11 | }
12 | m = float64ToInt(-234.0234)
13 | if m != -234 {
14 | t.Errorf("%x != %x", m, -234)
15 | }
16 | m = float64ToInt(1)
17 | if m != 1 {
18 | t.Errorf("%x != %x", m, 1)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/variance.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import "math"
4 |
5 | // _variance finds the variance for both population and sample data
6 | func _variance(input Float64Data, sample int) (variance float64, err error) {
7 |
8 | if input.Len() == 0 {
9 | return math.NaN(), EmptyInputErr
10 | }
11 |
12 | // Sum the square of the mean subtracted from each number
13 | m, _ := Mean(input)
14 |
15 | for _, n := range input {
16 | variance += (n - m) * (n - m)
17 | }
18 |
19 | // When getting the mean of the squared differences
20 | // "sample" will allow us to know if it's a sample
21 | // or population and wether to subtract by one or not
22 | return variance / float64((input.Len() - (1 * sample))), nil
23 | }
24 |
25 | // Variance the amount of variation in the dataset
26 | func Variance(input Float64Data) (sdev float64, err error) {
27 | return PopulationVariance(input)
28 | }
29 |
30 | // PopulationVariance finds the amount of variance within a population
31 | func PopulationVariance(input Float64Data) (pvar float64, err error) {
32 |
33 | v, err := _variance(input, 0)
34 | if err != nil {
35 | return math.NaN(), err
36 | }
37 |
38 | return v, nil
39 | }
40 |
41 | // SampleVariance finds the amount of variance within a sample
42 | func SampleVariance(input Float64Data) (svar float64, err error) {
43 |
44 | v, err := _variance(input, 1)
45 | if err != nil {
46 | return math.NaN(), err
47 | }
48 |
49 | return v, nil
50 | }
51 |
52 | // Covariance is a measure of how much two sets of data change
53 | func Covariance(data1, data2 Float64Data) (float64, error) {
54 |
55 | l1 := data1.Len()
56 | l2 := data2.Len()
57 |
58 | if l1 == 0 || l2 == 0 {
59 | return math.NaN(), EmptyInputErr
60 | }
61 |
62 | if l1 != l2 {
63 | return math.NaN(), SizeErr
64 | }
65 |
66 | m1, _ := Mean(data1)
67 | m2, _ := Mean(data2)
68 |
69 | // Calculate sum of squares
70 | var ss float64
71 | for i := 0; i < l1; i++ {
72 | delta1 := (data1.Get(i) - m1)
73 | delta2 := (data2.Get(i) - m2)
74 | ss += (delta1*delta2 - ss) / float64(i+1)
75 | }
76 |
77 | return ss * float64(l1) / float64(l1-1), nil
78 | }
79 |
80 | // CovariancePopulation computes covariance for entire population between two variables.
81 | func CovariancePopulation(data1, data2 Float64Data) (float64, error) {
82 |
83 | l1 := data1.Len()
84 | l2 := data2.Len()
85 |
86 | if l1 == 0 || l2 == 0 {
87 | return math.NaN(), EmptyInputErr
88 | }
89 |
90 | if l1 != l2 {
91 | return math.NaN(), SizeErr
92 | }
93 |
94 | m1, _ := Mean(data1)
95 | m2, _ := Mean(data2)
96 |
97 | var s float64
98 | for i := 0; i < l1; i++ {
99 | delta1 := (data1.Get(i) - m1)
100 | delta2 := (data2.Get(i) - m2)
101 | s += delta1 * delta2
102 | }
103 |
104 | return s / float64(l1), nil
105 | }
106 |
--------------------------------------------------------------------------------
/variance_test.go:
--------------------------------------------------------------------------------
1 | package stats_test
2 |
3 | import (
4 | "math"
5 | "testing"
6 |
7 | "github.com/montanaflynn/stats"
8 | )
9 |
10 | func TestVariance(t *testing.T) {
11 | _, err := stats.Variance([]float64{1, 2, 3})
12 | if err != nil {
13 | t.Errorf("Returned an error")
14 | }
15 | }
16 |
17 | func TestPopulationVariance(t *testing.T) {
18 | e, err := stats.PopulationVariance([]float64{})
19 | if !math.IsNaN(e) {
20 | t.Errorf("%.1f != %.1f", e, math.NaN())
21 | }
22 | if err != stats.EmptyInputErr {
23 | t.Errorf("%v != %v", err, stats.EmptyInputErr)
24 | }
25 |
26 | pv, _ := stats.PopulationVariance([]float64{1, 2, 3})
27 | a, err := stats.Round(pv, 1)
28 | if err != nil {
29 | t.Errorf("Returned an error")
30 | }
31 | if a != 0.7 {
32 | t.Errorf("%.1f != %.1f", a, 0.7)
33 | }
34 | }
35 |
36 | func TestSampleVariance(t *testing.T) {
37 | m, err := stats.SampleVariance([]float64{})
38 | if !math.IsNaN(m) {
39 | t.Errorf("%.1f != %.1f", m, math.NaN())
40 | }
41 | if err != stats.EmptyInputErr {
42 | t.Errorf("%v != %v", err, stats.EmptyInputErr)
43 | }
44 | m, _ = stats.SampleVariance([]float64{1, 2, 3})
45 | if m != 1.0 {
46 | t.Errorf("%.1f != %.1f", m, 1.0)
47 | }
48 | }
49 |
50 | func TestCovariance(t *testing.T) {
51 | s1 := []float64{1, 2, 3, 4, 5}
52 | s2 := []float64{10, -51.2, 8}
53 | s3 := []float64{1, 2, 3, 5, 6}
54 | s4 := []float64{}
55 |
56 | _, err := stats.Covariance(s1, s2)
57 | if err == nil {
58 | t.Errorf("Mismatched slice lengths should have returned an error")
59 | }
60 |
61 | a, err := stats.Covariance(s1, s3)
62 | if err != nil {
63 | t.Errorf("Should not have returned an error")
64 | }
65 |
66 | if a != 3.2499999999999996 {
67 | t.Errorf("Covariance %v != %v", a, 3.2499999999999996)
68 | }
69 |
70 | _, err = stats.Covariance(s1, s4)
71 | if err == nil {
72 | t.Errorf("Empty slice should have returned an error")
73 | }
74 | }
75 |
76 | func TestCovariancePopulation(t *testing.T) {
77 | s1 := []float64{1, 2, 3.5, 3.7, 8, 12}
78 | s2 := []float64{10, -51.2, 8}
79 | s3 := []float64{0.5, 1, 2.1, 3.4, 3.4, 4}
80 | s4 := []float64{}
81 |
82 | _, err := stats.CovariancePopulation(s1, s2)
83 | if err == nil {
84 | t.Errorf("Mismatched slice lengths should have returned an error")
85 | }
86 |
87 | a, err := stats.CovariancePopulation(s1, s3)
88 | if err != nil {
89 | t.Errorf("Should not have returned an error")
90 | }
91 |
92 | if a != 4.191666666666666 {
93 | t.Errorf("CovariancePopulation %v != %v", a, 4.191666666666666)
94 | }
95 |
96 | _, err = stats.CovariancePopulation(s1, s4)
97 | if err == nil {
98 | t.Errorf("Empty slice should have returned an error")
99 | }
100 | }
101 |
--------------------------------------------------------------------------------