montanaflynn/stats: A Dependency-Free Statistics Library for Go
A well tested and comprehensive Golang statistics library package with no dependencies.
At a glance
- What is it?
- The package collects descriptive statistics, distance metrics and a normal-distribution toolkit behind a single Float64Data type. It is a good fit for services that need medians and percentiles without pulling in a numerical stack, and a poor fit for anything that needs regression, hypothesis testing or streaming input.
- Who is it for?
- Adopt it if you are writing Go and need descriptive statistics, distance metrics or normal-distribution helpers on in-memory float64 slices, and you want that without adding a dependency tree. Do not adopt it if your data arrives as a stream, if you need regression or hypothesis testing, or if you cannot hold the full slice in memory.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap this package fills in a Go service
Go's standard library gives you math and sort and not much else for data work. If a service needs the median of a batch of latencies, a percentile for a latency budget, or the correlation between two series, the usual answer is to reach for a numerical library that brings its own matrix type, its own build tags and its own release cadence. The README describes this package as a statistics library with no dependencies, and that is the whole proposition. You get functions that take a slice of float64 and return a float64 or an error. Nothing else comes along for the ride.
The audience is therefore narrow and identifiable. It is a backend engineer who wants a median in a health-check handler, an analyst writing a one-off Go program to summarise a CSV, or a team that needs Euclidean and Manhattan distance for a small nearest-neighbour step. It is not aimed at people doing statistical modelling. There is no regression, no hypothesis test, no distribution fitting beyond the normal case, and no dataframe abstraction. The library computes numbers over slices you already hold in memory, and that constraint shapes everything below.
Float64Data, LoadRawData and the error variables
The central type is Float64Data, defined as a slice of float64. Every function in the exported API takes that type, which means the package is consistent to call and easy to reason about: you convert once at the boundary of your program and pass the same value around. The conversion helper is LoadRawData, which takes an interface{} and returns a Float64Data. The README shows it accepting []int and []interface{} containing mixed values such as 1.1 and the string "2", which suggests it coerces what it can and skips what it cannot. The README does not document the coercion rules, so if you feed it strings or nested values you should test the behaviour yourself rather than assume.
Errors are package-level variables rather than types you construct: ErrEmptyInput, ErrNaN, ErrNegative, ErrZero, ErrBounds, ErrSize, ErrInfValue and ErrYCoord. The messages are short and literal, for example "Input must not be empty." and "Must be the same length." That last one, ErrSize, is what you will see most often, because the two-series functions such as Correlation, Covariance and the distance metrics require both inputs to be the same length. Comparing with errors.Is is the natural way to branch on these, and the fixed set means you can write a switch over the handful of conditions instead of parsing strings.
What the exported API actually covers
The function list groups into recognisable clusters. Central tendency and spread: Mean, Median, Mode, Min, Max, GeometricMean, HarmonicMean, Midhinge, Percentile, PercentileNearestRank, PercentileWeighted, PercentileOfScore, InterQuartileRange, MedianAbsoluteDeviation and its population variant, CoefficientOfVariation, Kurtosis, Entropy. Cumulative and windowed operations: CumulativeSum, CumulativeMax, CumulativeMin, CumulativeProduct, Diff, PercentChange, MovingAverage, MovingMedian, MovingMax, MovingMin, MovingSum, MovingStdDev, EWMA. Relationships and distance: Correlation, Pearson, KendallTau, AutoCorrelation, Covariance and CovariancePopulation, ChebyshevDistance, EuclideanDistance, ManhattanDistance, MinkowskiDistance. Distribution and interpolation: Histogram, Interp, Clip, ArgMin, ArgMax, Round, Describe and DescribePercentileFunc, plus a large Norm family covering Pdf, Cdf, Sf, Isf, Ppf, LogPdf, LogCdf, LogSf, Entropy, Fit, Interval, Moment, Stats, Mean, Median, Std, Var, Sample, PpfRvs and BoxMullerRvs.
Two things stand out. First, several estimators exist in sample and population pairs, and the names are the only signal: MedianAbsoluteDeviation against MedianAbsoluteDeviationPopulation, Covariance against CovariancePopulation. Picking the wrong one changes your number, and the README does not explain the distinction, so you need to know which you want before you call either. Second, Describe returns a *Description struct and accepts an allowNaN flag plus a pointer to a slice of percentiles, with DescribePercentileFunc letting you swap in your own percentile implementation. That is the one place where the API shows a deliberate extension point rather than a fixed function, and it is worth using if your domain has a house definition of percentile.
Installation and the first call
Installation is the standard module fetch:
go get github.com/montanaflynn/stats
The README's example builds a slice, calls Median, then rounds the result:
data := []float64{1.0, 2.1, 3.2, 4.823, 4.1, 5.8} median, _ := stats.Median(data) roundedMedian, _ := stats.Round(median, 0)
The comment in the README states the median prints as 3.65 and the rounded value as 4. Note the discarded errors in the example. In production code you should not do that: Median returns an error, and ErrEmptyInput is a real case when a batch is empty. The same applies to Round, which takes a place count and returns both a value and an error.
For offline reference the README gives two commands. From a checkout you can run:
go doc -all . go doc Median go doc Float64Data
And for a browsable local site, after installing the pkgsite tool with go install golang.org/x/pkgsite/cmd/pkgsite@latest, you start it with pkgsite -http=:4444 and open http://localhost:4444/github.com/montanaflynn/stats. That is more useful than it sounds for a package of this size, because the exported list is long enough that reading it in a terminal is tedious.
Where the package stops being the right tool
The functions are all in-memory and slice-based. There is no streaming interface, no accumulator type, no incremental update. If you want a running median over an unbounded event stream, this library will not give it to you: MovingMedian takes a window and a slice you already have, not a channel. For high-volume telemetry you would be buffering everything before you can compute anything, and the memory profile scales with the batch size.
The statistical surface is also descriptive rather than inferential. There is no linear regression, no t-test, no ANOVA, no chi-square, no confidence interval helper outside the normal family, and no general distribution interface. NormFit returns a two-element array of location and scale, but the fitting is limited to the normal. If your question is "does this change matter" rather than "what does this data look like", you are outside the package's scope and should say so early rather than bending Percentile into a job it was not built for.
A third boundary is documentation depth. The README points at pkg.go.dev for the full API, so the contract for each function lives in its godoc comment, which is not reproduced in the material available here. For functions with sample and population variants, or with a lambda parameter as in MinkowskiDistance, that godoc is the only authoritative description of the formula used. Treat the function names as a map, not as a specification.
How this differs from gonum/stat
The obvious alternative in the Go ecosystem is gonum, whose stat subpackage covers much of the same descriptive ground and sits inside a larger numerical stack that includes matrices, optimisation and graph algorithms. The difference in approach is structural. gonum is designed so its pieces compose: you have a mat.VecDense or a mat.Dense and the statistics functions operate on those, which means a pipeline that starts with linear algebra can end with a covariance without converting types. This package has one type, Float64Data, which is a plain slice, and nothing else. There is no matrix type to interoperate with, and no plan to add one.
That makes the trade explicit. If you need regression, matrix decomposition or a distribution interface, gonum is the library that has them, and you accept its dependency graph and its learning curve for the type system. If all you need is a median, an interquartile range and a Euclidean distance in a service that should not grow its module graph, this package is the smaller commitment. The two are not substitutes at the same scope; they overlap in the middle and diverge at both ends.
Maintenance, releases and the MIT licence
The repository is not archived, the default branch is master, and the recent release list shows v0.12.6 on 2026-09-09, v0.12.5 on 2026-08-28 and v0.12.4 on 2026-08-17. Three releases inside roughly a month is a fairly active patch cadence, and the version numbers staying in the 0.x range is worth noting: there is no 1.0, so the maintainer has not declared the API stable. In practice that means a minor bump can change behaviour, and you should pin a version in go.mod rather than tracking master.
The licence is MIT, which is permissive and permits use in closed-source products provided the copyright notice and permission notice are retained. That is a summary of what MIT generally means, not legal advice; read the LICENSE file in the repository and let your own process decide. There is no CLA or dual-licensing arrangement visible in the material, and no dependency tree to audit, which is the practical advantage of a package that vendors nothing.
Upgrade cost is low by construction. The exported functions are free-standing, the input type is a slice, and there are no interfaces you implement or structs you embed, so a version bump is unlikely to force changes across your codebase. The realistic risk is behavioural: a change to a percentile or covariance formula between 0.x releases would be invisible in your build and visible only in your numbers. If you depend on one of the sample-versus-population variants, pin the version and keep a test with a known input and expected output so a silent change fails your build rather than your report.
Editorial conclusion
Adopt it if you are writing Go and need descriptive statistics, distance metrics or normal-distribution helpers on in-memory float64 slices, and you want that without adding a dependency tree. Do not adopt it if your data arrives as a stream, if you need regression or hypothesis testing, or if you cannot hold the full slice in memory. Before you commit, read the exported API list on pkg.go.dev and confirm that the specific estimator you need is present, because the package mixes sample and population variants and the naming is the only thing that tells them apart.
Community notes