115 lines
2.0 KiB
Go
115 lines
2.0 KiB
Go
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:])
|
|
}
|