///|
pub fn sum_finite(values : ArrayView[Double]) -> Double {
  let mut total = 0.0
  for value in values {
    if is_finite(value) {
      total += value
    }
  }
  total
}

///|
pub fn count_finite(values : ArrayView[Double]) -> Int {
  let mut count = 0
  for value in values {
    if is_finite(value) {
      count += 1
    }
  }
  count
}

///|
pub fn minimum_finite(values : ArrayView[Double]) -> Double {
  let mut found = false
  let mut result = 0.0
  for value in values {
    if is_finite(value) && (!found || value < result) {
      result = value
      found = true
    }
  }
  result
}

///|
pub fn maximum_finite(values : ArrayView[Double]) -> Double {
  let mut found = false
  let mut result = 0.0
  for value in values {
    if is_finite(value) && (!found || value > result) {
      result = value
      found = true
    }
  }
  result
}

///|
pub fn centered_values(
  values : ArrayView[Double],
  center : Double,
) -> Array[Double] {
  let output : Array[Double] = []
  for value in values {
    if is_finite(value) {
      output.push(value - center)
    }
  }
  output
}

///|
pub fn mean_absolute_deviation(
  values : ArrayView[Double],
  center? : Double,
) -> Double {
  let chosen = match center {
    Some(value) => value
    None => median(values)
  }
  let mut total = 0.0
  let mut count = 0
  for value in values {
    if is_finite(value) {
      total += (value - chosen).abs()
      count += 1
    }
  }
  if count == 0 {
    0.0
  } else {
    total / Double::from_int(count)
  }
}

///|
pub fn median_absolute_deviation(values : ArrayView[Double]) -> Double {
  let center = median(values)
  let deviations : Array[Double] = []
  for value in values {
    if is_finite(value) {
      deviations.push((value - center).abs())
    }
  }
  median(deviations)
}

///|
pub fn robust_z_score(value : Double, values : ArrayView[Double]) -> Double {
  let center = median(values)
  let scale = median_absolute_deviation(values) * 1.4826
  z_score(value, center, scale)
}

///|
pub fn outlier_report(
  values : ArrayView[Double],
  threshold? : Double = 3.5,
) -> OutlierReport {
  let center = median(values)
  let scale = median_absolute_deviation(values) * 1.4826
  let indices : Array[Int] = []
  if scale > 0.0 {
    for i in 0.. threshold {
        indices.push(i)
      }
    }
  }
  { indices, center, scale }
}

///|
pub fn covariance_summary(
  x : ArrayView[Double],
  y : ArrayView[Double],
) -> CovarianceSummary {
  let n = Int::min(x.length(), y.length())
  let mut count = 0
  let mut sx = 0.0
  let mut sy = 0.0
  for i in 0.. Regression {
  let n = Int::min(x.length(), y.length())
  let mut count = 0
  let mut sx = 0.0
  let mut sy = 0.0
  for i in 0.. Double {
  model.intercept + model.slope * x
}

///|
pub fn regression_residuals(
  model : Regression,
  x : ArrayView[Double],
  y : ArrayView[Double],
) -> Array[Double] {
  let output : Array[Double] = []
  let n = Int::min(x.length(), y.length())
  for i in 0.. Array[HistogramBin] {
  let output : Array[HistogramBin] = []
  if bins <= 0 {
    output
  } else {
    let s = summary(values)
    let selected = match range {
      Some(value) => value
      None => make_range(minimum=s.minimum, maximum=s.maximum)
    }
    let width = (selected.maximum - selected.minimum) / Double::from_int(bins)
    for i in 0.. 0.0 {
      for value in values {
        if is_finite(value) && range_contains(selected, value) {
          let index = Int::min(
            bins - 1,
            ((value - selected.minimum) / width).floor().to_int(),
          )
          output[index] = { ..output[index], count: output[index].count + 1 }
        }
      }
    }
    output
  }
}

///|
pub fn empirical_cdf(values : ArrayView[Double], query : Double) -> Double {
  let mut count = 0
  let mut total = 0
  for value in values {
    if is_finite(value) {
      total += 1
      if value <= query {
        count += 1
      }
    }
  }
  if total == 0 {
    0.0
  } else {
    Double::from_int(count) / Double::from_int(total)
  }
}

///|
pub fn rank_values(values : ArrayView[Double]) -> Array[Double] {
  let output : Array[Double] = []
  for value in values {
    let mut lower = 0
    let mut equal = 0
    if is_finite(value) {
      for other in values {
        if is_finite(other) {
          if other < value {
            lower += 1
          }
          if other == value {
            equal += 1
          }
        }
      }
      output.push(Double::from_int(lower) + Double::from_int(equal + 1) / 2.0)
    } else {
      output.push(0.0)
    }
  }
  output
}

///|
pub fn winsorize(
  values : ArrayView[Double],
  lower_fraction : Double,
  upper_fraction : Double,
) -> Array[Double] {
  let low = percentile(values, lower_fraction)
  let high = percentile(values, upper_fraction)
  let output : Array[Double] = []
  for value in values {
    if is_finite(value) {
      output.push(value.clamp(min=low, max=high))
    } else {
      output.push(value)
    }
  }
  output
}

///|
pub fn trim_values(
  values : ArrayView[Double],
  fraction : Double,
) -> Array[Double] {
  let low = percentile(values, fraction)
  let high = percentile(values, 1.0 - fraction)
  let output : Array[Double] = []
  for value in values {
    if is_finite(value) && value >= low && value <= high {
      output.push(value)
    }
  }
  output
}

///|
pub fn weighted_variance(
  values : ArrayView[Double],
  weights : ArrayView[Double],
) -> Double {
  let mean = weighted_mean(values, weights)
  let mut total = 0.0
  let mut weight_total = 0.0
  let n = Int::min(values.length(), weights.length())
  for i in 0.. 0.0 {
      let d = values[i] - mean
      total += weights[i] * d * d
      weight_total += weights[i]
    }
  }
  if weight_total == 0.0 {
    0.0
  } else {
    total / weight_total
  }
}

///|
pub fn normalize_min_max(
  values : ArrayView[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  let s = summary(values)
  let output : Array[Double] = []
  if s.maximum == s.minimum {
    for value in values {
      if is_finite(value) {
        output.push((lower + upper) / 2.0)
      } else {
        output.push(value)
      }
    }
  } else {
    for value in values {
      if is_finite(value) {
        output.push(
          lower +
          (value - s.minimum) * (upper - lower) / (s.maximum - s.minimum),
        )
      } else {
        output.push(value)
      }
    }
  }
  output
}