├── .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 | --------------------------------------------------------------------------------