All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.iheart.thomas.analysis.bayesian.fit.LogNormalFit.scala Maven / Gradle / Ivy

The newest version!
package com.iheart.thomas.analysis
package bayesian.fit

import cats.MonadError
import FitAssessmentAlg.BayesianAssessmentAlg
import DistributionSpec.{Normal, Uniform}

import com.stripe.rainier.compute.Real
import com.stripe.rainier.core.{LogNormal, Model}
import com.stripe.rainier.sampler.{RNG, SamplerConfig}
import org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest
import cats.implicits._
import com.iheart.thomas.analysis.bayesian.Variable
import java.time.Instant

/** https://stats.stackexchange.com/questions/30369/priors-for-log-normal-models
  *
  * @param name
  * @param locationPrior
  * @param scaleLnPrior
  */
case class LogNormalFit(
    locationPrior: Normal,
    scaleLnPrior: Uniform)

object LogNormalFit {
  type Measurements = List[Double]
  implicit def logNormalInstances[F[_]](
      implicit
      sampler: SamplerConfig = SamplerConfig.default,
      rng: RNG = RNG.default,
      K: Measurable[F, Measurements, LogNormalFit],
      F: MonadError[F, Throwable]
    ): FitAssessmentAlg[F, LogNormalFit] with UpdatableKPI[F, LogNormalFit] =
    new BayesianAssessmentAlg[F, LogNormalFit, Measurements]
      with UpdatableKPI[F, LogNormalFit] {

      private def fitModel(
          logNormal: LogNormalFit,
          data: List[Double]
        ): Variable[(Real, Real)] = {
        val location = logNormal.locationPrior.distribution.latent
        val scale = logNormal.scaleLnPrior.distribution.latent.exp
        val g = Model.observe(data, LogNormal(location, scale))
        Variable((location, scale), g)
      }

      def sampleIndicator(
          logNormalDistribution: LogNormalFit,
          data: List[Double]
        ): Indicator = {
        fitModel(logNormalDistribution, data).map { case (location, scale) =>
          (location + scale.pow(2d) / 2d).exp
        }
      }

      def updateFromData(
          k: LogNormalFit,
          start: Instant,
          end: Instant
        ): F[(LogNormalFit, Double)] =
        K.measureHistory(k, start, end).map { data =>
          val model = fitModel(k, data)

          val (locationSample, scaleSample) =
            model.predict().foldLeft((List.empty[Double], List.empty[Double])) {
              (memo, p) =>
                (p._1 :: memo._1, p._2 :: memo._2)
            }

          val updated = k.copy(
            locationPrior = Normal.fit(locationSample),
            scaleLnPrior = Uniform(
              Math.log(
                scaleSample.minimumOption.map(_ / 10).getOrElse(k.scaleLnPrior.from)
              ),
              Math.log(
                scaleSample.maximumOption.map(_ * 2).getOrElse(k.scaleLnPrior.to)
              )
            )
          )

          val ksTest = new KolmogorovSmirnovTest()
          val gd = new org.apache.commons.math3.distribution.LogNormalDistribution(
            updated.locationPrior.location,
            scaleSample.sum / scaleSample.size.toDouble
          )
          val ksStatistics = ksTest.kolmogorovSmirnovStatistic(gd, data.toArray)

          (updated, ksStatistics)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy