Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 53 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# sampdo

`sampdo` is a lightweight statistical aggregation library for Go. It collects a large number of `float64` samples and efficiently computes minimum, maximum, mean, median, and arbitrary percentiles from a sorted view. For non-negative values it uses radix sort, which is faster than a comparison sort.

`sampdo` は Go 用の軽量な数値集計ライブラリです。大量の `float64` サンプルに対して、ソート済みの状態から最小値・最大値・平均値・中央値・任意のパーセンタイルを効率的に取得できます。非負数に対しては基数ソートを使い、比較ソートより高速に集計できます。

## 特徴
## Features / 特徴

- **ソートベースの集計**: サンプルを 1 回ソートするだけで、複数の統計量を取得できます。
- **基数ソート対応**: 非負数の場合、`slices.Sort` よりも高速な基数ソートを使用します。
- **NaN を排除**: `Append` 時に `NaN` を弾き、集計結果を安定させます。
- **イミュータブルなソート結果**: `Sorted()` 後に元のデータを変更しても、返された `*Sorted` は影響を受けません。
- **無限大に対応**: `+Inf`、`-Inf` を含むデータのパーセンタイル計算にも対応しています。
- **Sort-based aggregation / ソートベースの集計**: Compute several statistics from a single sort. サンプルを 1 回ソートするだけで複数の統計量を取得できます。
- **Radix sort for non-negative numbers / 非負数向け基数ソート**: Falls back to `slices.Sort` only when negative or `-0` values are present. 負数や `-0` がある場合のみ `slices.Sort` にフォールバックします。
- **NaN rejection / NaN を排除**: `Append` rejects `NaN` so results stay stable. `Append` は `NaN` を弾き、集計結果を安定させます。
- **Immutable sorted view / イミュータブルなソート結果**: Appending after `Sorted()` does not mutate the returned `*Sorted`. `Sorted()` 後に追加しても返された `*Sorted` は影響を受けません。
- **Infinity support / 無限大対応**: Handles `+Inf` and `-Inf` in percentile calculations. `+Inf`、`-Inf` を含むデータのパーセンタイル計算にも対応しています。

## インストール
## Installation / インストール

```bash
go get github.com/monitoring-forge/sampdo
```

## 使い方
## Usage / 使い方

```go
package main
Expand All @@ -31,7 +33,7 @@ import (
func main() {
s := sampdo.New()

// サンプルを追加(可変長引数)
// Add samples (variadic) / サンプルを追加(可変長引数)
if err := s.Append(8.0, 2.0, 10.0, 4.0, 6.0, 3.0, 7.0, 1.0, 9.0, 5.0); err != nil {
log.Fatal(err)
}
Expand All @@ -41,19 +43,19 @@ func main() {
log.Fatal(err)
}

fmt.Println("Count :", sorted.Count()) // サンプル数
fmt.Println("Min :", sorted.Min()) // 最小値
fmt.Println("Max :", sorted.Max()) // 最大値
fmt.Println("Mean :", sorted.Mean()) // 平均値
fmt.Println("Median :", sorted.Median()) // 中央値
fmt.Println("P75 :", sorted.Percentile(75)) // 75 パーセンタイル
fmt.Println("P90 :", sorted.Percentile(90)) // 90 パーセンタイル
fmt.Println("P95 :", sorted.Percentile(95)) // 95 パーセンタイル
fmt.Println("P99 :", sorted.Percentile(99)) // 99 パーセンタイル
fmt.Println("Count :", sorted.Count()) // number of samples / サンプル数
fmt.Println("Min :", sorted.Min()) // minimum value / 最小値
fmt.Println("Max :", sorted.Max()) // maximum value / 最大値
fmt.Println("Mean :", sorted.Mean()) // arithmetic mean / 平均値
fmt.Println("Median :", sorted.Median()) // median value / 中央値
fmt.Println("P75 :", sorted.Percentile(75)) // 75th percentile / 75 パーセンタイル
fmt.Println("P90 :", sorted.Percentile(90)) // 90th percentile / 90 パーセンタイル
fmt.Println("P95 :", sorted.Percentile(95)) // 95th percentile / 95 パーセンタイル
fmt.Println("P99 :", sorted.Percentile(99)) // 99th percentile / 99 パーセンタイル
}
```

### 出力例
### Example output / 出力例

```
Count : 10
Expand All @@ -67,76 +69,66 @@ P95 : 9.55
P99 : 9.91
```

## 出力される数字の意味
## What the numbers mean / 出力される数字の意味

| Method | English | 日本語 |
| --- | --- | --- |
| `Count()` | Number of appended samples. | 追加されたサンプルの個数。 |
| `Min()` | Smallest value; returns `0` when empty. | 最小値。空の場合は `0`。 |
| `Max()` | Largest value; returns `0` when empty. | 最大値。空の場合は `0`。 |
| `Mean()` | Arithmetic mean; sums in input order. Returns `0` when empty. | 算術平均。入力順に合計し、空の場合は `0`。 |
| `Median()` | 50th percentile; averages the two middle values for an even count. | 50 パーセンタイル。要素数が偶数の場合は中央 2 値の平均。 |
| `Percentile(p)` | Returns the `p`th percentile (0–100) using linear interpolation; returns `0` for out-of-range `p`. | `p` パーセンタイル(0〜100)を線形補間で返す。範囲外は `0`。 |

| メソッド | 説明 |
| --- | --- |
| `Count()` | 追加されたサンプルの個数を返します。 |
| `Min()` | 最小値を返します。サンプルが空の場合は `0` を返します。 |
| `Max()` | 最大値を返します。サンプルが空の場合は `0` を返します。 |
| `Mean()` | 平均値(算術平均)を返します。サンプルが空の場合は `0` を返します。入力された順序で合計を計算します。 |
| `Median()` | 中央値(50 パーセンタイル)を返します。要素数が偶数の場合は中央 2 値の平均を返します。 |
| `Percentile(p)` | `p` パーセンタイル(0〜100)を線形補間で返します。範囲外の `p` には `0` を返します。 |
### How percentiles are calculated / パーセンタイルの計算方法

### パーセンタイルの計算方法
`Percentile(p)` computes the position `k` with the formula below and linearly interpolates between the two surrounding points.

`Percentile(p)` は以下の式で位置を求め、隣接する 2 点間を線形補間します。
`Percentile(p)` は以下の式で位置 `k` を求め、隣接する 2 点間を線形補間します。

```
k = (p / 100) * (n - 1)
```

`n` is the sample count. For example, with 10 samples, P75 yields `k = 6.75`, so the result is interpolated 75% of the way between the 7th and 8th values.

`n` はサンプル数です。例えば 10 サンプルの P75 は `k = 6.75` となり、7 番目と 8 番目の値の間を 0.75 で補間します。

## オプション
## Options / オプション

Pass these options to `New`:

`New` に以下のオプションを渡せます。

```go
// 初期キャパシティを指定(デフォルトは 128)
// Set the initial capacity (default is 128) / 初期キャパシティを指定(デフォルトは 128)
s := sampdo.New(sampdo.WithInitialCapacity(1024))

// 常に slices.Sort を使う(非負数でも基数ソートを使わない)
// Always use slices.Sort, even for non-negative data / 常に slices.Sort を使う
s := sampdo.New(sampdo.WithPreferSlicesSort(true))
```

## 制限・注意事項
## Limitations and notes / 制限・注意事項

- **NaN は追加不可**: `Append` に `NaN` を含めるとエラーになります。
- **負のゼロは比較ソートにフォールバック**: `-0` を含めると負の数と同様に `slices.Sort` が使用されます。
- **空の集計はエラー**: 1 つも `Append` せずに `Sorted()` を呼ぶとエラーを返します。
- **パーセンタイルは 0〜100 のみ**: 範囲外の値に対しては `0` を返します。
- **`Sorted()` は 1 回だけ呼ぶ想定**: 同じ `*Sampdo` に対して `Sorted()` 後に `Append` して再集計できますが、ソート済みスライスをクローンするためコストが発生します。
- **NaN is not allowed / NaN は追加不可**: `Append` returns an error if any value is `NaN`. `NaN` を含めると `Append` はエラーを返します。
- **Negative zero falls back to comparison sort / 負のゼロは比較ソートにフォールバック**: Including `-0` causes the library to use `slices.Sort`. `-0` を含めると `slices.Sort` が使用されます。
- **Empty input is an error / 空の集計はエラー**: Calling `Sorted()` without any appended points returns an error. 1 つも `Append` せずに `Sorted()` を呼ぶとエラーになります。
- **Percentiles are limited to 0–100 / パーセンタイルは 0〜100 のみ**: Out-of-range percentiles return `0`. 範囲外の値に対しては `0` を返します。
- **Reuse after `Sorted()` has a cost / `Sorted()` 後の再利用にコスト**: You can keep appending after `Sorted()`, but the sorted slice is cloned to keep the previous `*Sorted` immutable. `Sorted()` 後に追加できますが、イミュータブル性を保つためソート済みスライスのクローンが発生します。

## ベンチマーク
## Benchmark / ベンチマーク

Run benchmarks with:

以下のコマンドでベンチマークを実行できます。

```bash
go test -bench BenchmarkSampdoAll -benchmem -benchtime=100ms -count 10 -run '^$' .
```

## ライセンス

MIT License

Copyright (c) 2026 Masahiro Nagano

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:
## License / ライセンス

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This project is licensed under the [MIT License](LICENSE).

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
AUTHORS 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.
本プロジェクトは [MIT ライセンス](LICENSE) の下で提供されています。

Loading