fix
This commit is contained in:
+114
@@ -0,0 +1,114 @@
|
||||
package series
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CSVData struct {
|
||||
Columns []string
|
||||
Rows [][]string
|
||||
index map[string]int
|
||||
}
|
||||
|
||||
func ReadCSV(path string) (*CSVData, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
records, err := csv.NewReader(file).ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return &CSVData{Columns: []string{}, Rows: [][]string{}, index: map[string]int{}}, nil
|
||||
}
|
||||
|
||||
data := &CSVData{
|
||||
Columns: records[0],
|
||||
Rows: records[1:],
|
||||
index: make(map[string]int, len(records[0])),
|
||||
}
|
||||
for i, column := range data.Columns {
|
||||
data.index[strings.ToLower(strings.TrimSpace(column))] = i
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (d *CSVData) Index(name string) (int, error) {
|
||||
idx, ok := d.index[strings.ToLower(name)]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("missing column %q", name)
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func (d *CSVData) NumericColumns(names ...string) ([][]float64, error) {
|
||||
indices := make([]int, len(names))
|
||||
for i, name := range names {
|
||||
idx, err := d.Index(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indices[i] = idx
|
||||
}
|
||||
|
||||
values := make([][]float64, len(names))
|
||||
for i := range values {
|
||||
values[i] = make([]float64, 0, len(d.Rows))
|
||||
}
|
||||
|
||||
maxIndex := MaxInt(indices...)
|
||||
for _, row := range d.Rows {
|
||||
if len(row) <= maxIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
parsed := make([]float64, len(indices))
|
||||
valid := true
|
||||
for i, idx := range indices {
|
||||
value, err := ParseFloat(row[idx])
|
||||
if err != nil {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
parsed[i] = value
|
||||
}
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, value := range parsed {
|
||||
values[i] = append(values[i], value)
|
||||
}
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func ParseFloat(value string) (float64, error) {
|
||||
return strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
}
|
||||
|
||||
func MaxInt(values ...int) int {
|
||||
max := 0
|
||||
for _, value := range values {
|
||||
if value > max {
|
||||
max = value
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func MaxLastHalf(values []float64) float64 {
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
return slices.Max(values[len(values)/2:])
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package series
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/gonum/dsp/fourier"
|
||||
)
|
||||
|
||||
type SpectrumPoint struct {
|
||||
Frequency float64 `json:"frequency"`
|
||||
Amplitude float64 `json:"amplitude"`
|
||||
}
|
||||
|
||||
func AverageDelta(times []float64) float64 {
|
||||
if len(times) < 2 {
|
||||
return 0
|
||||
}
|
||||
total := 0.0
|
||||
count := 0
|
||||
for i := 1; i < len(times); i++ {
|
||||
delta := times[i] - times[i-1]
|
||||
if delta <= 0 {
|
||||
continue
|
||||
}
|
||||
total += delta
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
return total / float64(count)
|
||||
}
|
||||
|
||||
func SpectrumPoints(values []float64, dt float64) []SpectrumPoint {
|
||||
if len(values) < 2 || dt <= 0 {
|
||||
return []SpectrumPoint{}
|
||||
}
|
||||
|
||||
centered := Centered(values)
|
||||
fft := fourier.NewFFT(len(centered))
|
||||
coeffs := fft.Coefficients(nil, centered)
|
||||
limit := len(coeffs) / 2
|
||||
points := make([]SpectrumPoint, 0, limit+1)
|
||||
for i := 0; i <= limit; i++ {
|
||||
points = append(points, SpectrumPoint{
|
||||
Frequency: float64(i) / (float64(len(centered)) * dt),
|
||||
Amplitude: math.Hypot(real(coeffs[i]), imag(coeffs[i])) / float64(len(centered)),
|
||||
})
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func MainAngularFrequency(times, values []float64) float64 {
|
||||
if len(values) < 2 || len(times) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
dt := AverageDelta(times)
|
||||
if dt <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
centered := Centered(values)
|
||||
fft := fourier.NewFFT(len(centered))
|
||||
coeffs := fft.Coefficients(nil, centered)
|
||||
if len(coeffs) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
bestIndex := 1
|
||||
bestAmp := 0.0
|
||||
for i := 1; i < len(coeffs); i++ {
|
||||
amp := math.Hypot(real(coeffs[i]), imag(coeffs[i]))
|
||||
if amp > bestAmp {
|
||||
bestAmp = amp
|
||||
bestIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
frequencyHz := fft.Freq(bestIndex) / dt
|
||||
return 2 * math.Pi * frequencyHz
|
||||
}
|
||||
|
||||
func Centered(values []float64) []float64 {
|
||||
centered := make([]float64, len(values))
|
||||
mean := 0.0
|
||||
for _, value := range values {
|
||||
mean += value
|
||||
}
|
||||
mean /= float64(len(values))
|
||||
for i, value := range values {
|
||||
centered[i] = value - mean
|
||||
}
|
||||
return centered
|
||||
}
|
||||
Reference in New Issue
Block a user