///|
pub fn make_series(name~ : String, points~ : Array[SeriesPoint]) -> FieldSeries {
{ name, points }
}
///|
pub fn series_from_profile(
profile : ArrayView[ProfileSample],
name? : String = "profile",
) -> FieldSeries {
let points : Array[SeriesPoint] = []
for sample in profile {
points.push({
coordinate: sample.distance_m,
value: sample.value,
flag: Good,
})
}
{ name, points }
}
///|
pub fn series_values(
series : FieldSeries,
policy? : MissingPolicy = SkipMissing,
) -> Array[Double] {
let values : Array[Double] = []
for point in series.points {
if is_finite(point.value) && quality_is_usable(point.flag) {
values.push(point.value)
} else {
match policy {
SkipMissing => ()
PreserveMissing => values.push(0.0 / 0.0)
ReplaceWith(value~) => values.push(value)
}
}
}
values
}
///|
pub fn series_summary(series : FieldSeries) -> Summary {
summary(series_values(series))
}
///|
pub fn series_is_sorted(series : FieldSeries) -> Bool {
for i in 1.. FieldSeries {
let points = series.points.copy()
points.sort_by(fn(a, b) { a.coordinate.compare(b.coordinate) })
{ ..series, points, }
}
///|
pub fn series_at(series : FieldSeries, coordinate : Double) -> Double? {
for point in series.points {
if point.coordinate == coordinate {
return Some(point.value)
}
}
None
}
///|
pub fn series_interpolate(series : FieldSeries, coordinate : Double) -> Double? {
let ordered = if series_is_sorted(series) {
series
} else {
series_sort(series)
}
if ordered.points.length() == 0 {
None
} else {
for i in 1..= left.coordinate &&
coordinate <= right.coordinate &&
is_finite(left.value) &&
is_finite(right.value) {
let width = right.coordinate - left.coordinate
return Some(
if width == 0.0 {
left.value
} else {
lerp(
left.value,
right.value,
(coordinate - left.coordinate) / width,
)
},
)
}
}
None
}
}
///|
pub fn series_resample(
series : FieldSeries,
coordinates : ArrayView[Double],
) -> FieldSeries {
let points : Array[SeriesPoint] = []
for coordinate in coordinates {
match series_interpolate(series, coordinate) {
Some(value) => points.push({ coordinate, value, flag: Good })
None => points.push({ coordinate, value: 0.0 / 0.0, flag: Missing })
}
}
{ name: series.name, points }
}
///|
pub fn series_fill_linear(series : FieldSeries) -> FieldSeries {
let points = series.points.copy()
for i in 0.. points[i] = { ..points[i], value, flag: Suspect }
None => ()
}
}
}
{ ..series, points, }
}
///|
pub fn series_differences(series : FieldSeries) -> FieldSeries {
let points : Array[SeriesPoint] = []
if series.points.length() > 0 {
points.push({ ..series.points[0], value: 0.0 })
for i in 1.. Double {
let mut total = 0.0
for i in 1.. FieldSeries {
let points : Array[SeriesPoint] = []
let mut total = 0.0
for i in 0.. 0 {
let a = series.points[i - 1]
let b = series.points[i]
if is_finite(a.value) && is_finite(b.value) {
total += (b.coordinate - a.coordinate) * (a.value + b.value) / 2.0
}
}
points.push({ ..series.points[i], value: total })
}
{ name: "integral({series.name})", points }
}
///|
pub fn series_moving_average(series : FieldSeries, radius : Int) -> FieldSeries {
let values = moving_average(
series_values(series, policy=PreserveMissing),
radius,
)
let points : Array[SeriesPoint] = []
for i in 0.. OutlierReport {
outlier_report(series_values(series), threshold~)
}
///|
pub fn series_peaks(
series : FieldSeries,
prominence~ : Double,
) -> Array[SeriesPoint] {
let peaks : Array[SeriesPoint] = []
for i in 1..<(series.points.length() - 1) {
let point = series.points[i]
if is_finite(point.value) &&
point.value > series.points[i - 1].value &&
point.value >= series.points[i + 1].value &&
point.value -
Double::max(series.points[i - 1].value, series.points[i + 1].value) >=
prominence {
peaks.push(point)
}
}
peaks
}
///|
pub fn series_troughs(
series : FieldSeries,
prominence~ : Double,
) -> Array[SeriesPoint] {
let troughs : Array[SeriesPoint] = []
for i in 1..<(series.points.length() - 1) {
let point = series.points[i]
if is_finite(point.value) &&
point.value < series.points[i - 1].value &&
point.value <= series.points[i + 1].value &&
Double::min(series.points[i - 1].value, series.points[i + 1].value) -
point.value >=
prominence {
troughs.push(point)
}
}
troughs
}
///|
pub fn series_autocorrelation(series : FieldSeries, lag : Int) -> Double {
let values = series_values(series)
if lag < 0 || lag >= values.length() {
0.0
} else {
let center = summary(values).mean
let mut numerator = 0.0
let mut denominator = 0.0
for i in 0.. Double {
let x = series_values(a)
let y = series_values(b)
if lag < 0 || lag >= x.length() || lag >= y.length() {
0.0
} else {
covariance_summary(x[lag:], y[:y.length() - lag]).correlation
}
}
///|
pub fn series_clip(series : FieldSeries, range : NumericRange) -> FieldSeries {
let points : Array[SeriesPoint] = []
for point in series.points {
if range_contains(range, point.coordinate) {
points.push(point)
}
}
{ ..series, points, }
}
///|
pub fn series_shift(series : FieldSeries, offset : Double) -> FieldSeries {
let points : Array[SeriesPoint] = []
for point in series.points {
points.push({ ..point, coordinate: point.coordinate + offset })
}
{ ..series, points, }
}
///|
pub fn series_scale(series : FieldSeries, factor : Double) -> FieldSeries {
let points : Array[SeriesPoint] = []
for point in series.points {
points.push({ ..point, value: point.value * factor })
}
{ ..series, points, }
}