Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Truncated distribution #36

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/kixi/stats/distribution.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,30 @@
:cljs (ISeqable
(-seq [this] (sampleable->seq this)))))

(deftype ^:no-doc Truncated
[distribution lower upper lower-cdf upper-cdf]
p/PRandomVariable
(sample-1 [this rng]

Check warning on line 613 in src/kixi/stats/distribution.cljc

View workflow job for this annotation

GitHub Actions / lint

unused binding this
(p/quantile distribution
(+ (* (rand-double rng) (- upper-cdf lower-cdf)) lower-cdf)))
(sample-n [this n rng]
(default-sample-n this n rng))
p/PBounded
(minimum [this] lower)

Check warning on line 619 in src/kixi/stats/distribution.cljc

View workflow job for this annotation

GitHub Actions / lint

unused binding this
(maximum [this] upper)

Check warning on line 620 in src/kixi/stats/distribution.cljc

View workflow job for this annotation

GitHub Actions / lint

unused binding this
p/PQuantile
(cdf [this x]

Check warning on line 622 in src/kixi/stats/distribution.cljc

View workflow job for this annotation

GitHub Actions / lint

unused binding this
(cond (>= x upper) 1.0
(< x lower) 0.0
:else (/ (- (p/cdf distribution x) lower-cdf)
(- upper-cdf lower-cdf))))
(quantile [this p]

Check warning on line 627 in src/kixi/stats/distribution.cljc

View workflow job for this annotation

GitHub Actions / lint

unused binding this
(p/quantile distribution (+ (* p (- upper-cdf lower-cdf)) lower-cdf)))
#?@(:clj (clojure.lang.Seqable
(seq [this] (sampleable->seq this)))
:cljs (ISeqable
(-seq [this] (sampleable->seq this)))))

;;;; Public API

(def minimum p/minimum)
Expand Down Expand Up @@ -795,6 +819,23 @@
(str "Scale (" scale ") and shape (" shape ") must be positive."))
(->Pareto scale shape))

(defn truncated
"Returns a distribution that is a truncated version
of the supplied `distribution` between the lower bound `lower`
and the upper bound `upper`.
Params: {:distribution, :lower ∈ ℝ, :upper ∈ ℝ, :lower < :upper}"
[{:keys [distribution lower upper]}]
(assert (and (satisfies? p/PRandomVariable distribution)
(satisfies? p/PQuantile distribution))
"distribution must satisfy PRandomVaraible and PQuantile.")
(assert (and (number? lower) (number? upper) (< lower upper))
(str "lower (" lower ") must be less than upper (" upper ")."))
(let [cdf-lower (cdf distribution lower)
cdf-upper (cdf distribution upper)]
(assert (< cdf-lower cdf-upper)
(str "lower (" lower ") and upper (" upper ") are beyond the extremes of the distribution."))
(->Truncated distribution lower upper cdf-lower cdf-upper)))

(defn draw
"Returns a single variate from the distribution.
An optional seed long will ensure deterministic results"
Expand Down
55 changes: 54 additions & 1 deletion test/kixi/stats/distribution_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[clojure.test.check.properties :as prop :refer [for-all]]
[kixi.stats.distribution :as sut]
#?(:clj [kixi.stats.core :as kixi])
[kixi.stats.math :refer [gamma exp equal]]
[kixi.stats.math :refer [abs gamma exp log equal]]

Check warning on line 7 in test/kixi/stats/distribution_test.cljc

View workflow job for this annotation

GitHub Actions / lint

#'kixi.stats.math/log is referred but never used
[clojure.test.check.generators :as gen]
[clojure.test.check]
[kixi.stats.test-helpers :refer #?(:clj [=ish numeric cdf' quantile']
Expand Down Expand Up @@ -52,6 +52,21 @@
(def gen-pos-real
(gen/double* {:infinite? false :NaN? false :min 0.1 :max 100}))

(def gen-small-real
(gen/double* {:infinite? false :NaN? false :min -10.0 :max 10}))

(def gen-two-ascending-pos-reals
"Returns two positive reals in ascending order."
(->> (gen/tuple gen-pos-real gen-pos-real)
(gen/such-that (fn [[a b]] (not= a b)))
(gen/fmap sort)))

(def gen-two-ascending-small-ints
"Returns two small ints in ascending order."
(->> (gen/tuple gen/small-integer gen/small-integer)
(gen/such-that (fn [[a b]] (not= a b)))
(gen/fmap sort)))

(def gen-dof
(gen/choose 3 1000))

Expand Down Expand Up @@ -338,6 +353,44 @@
(let [draw (sut/draw (sut/uniform {:a a :b b}) {:seed seed})]
(is (and (<= a draw) (< draw b))))))

(defn trunc-draw
[distribution lower upper seed]
(sut/draw (sut/truncated {:distribution distribution :lower lower :upper upper}) {:seed seed}))

(defspec truncated-does-not-exceed-bounds
test-opts
(for-all [seed gen/int

Check warning on line 362 in test/kixi/stats/distribution_test.cljc

View workflow job for this annotation

GitHub Actions / lint

#'clojure.test.check.generators/int is deprecated since 0.10.0
s gen-shape
r gen-rate
;; with the next line, the chi-squared test fails.
;; k (gen/fmap inc gen/nat)
k gen-small-n
[a b] (->> (gen/tuple gen/int gen/int)

Check warning on line 368 in test/kixi/stats/distribution_test.cljc

View workflow job for this annotation

GitHub Actions / lint

#'clojure.test.check.generators/int is deprecated since 0.10.0

Check warning on line 368 in test/kixi/stats/distribution_test.cljc

View workflow job for this annotation

GitHub Actions / lint

#'clojure.test.check.generators/int is deprecated since 0.10.0
(gen/such-that (fn [[a b]] (not= a b)))
(gen/fmap sort))
[f g] gen-two-ascending-small-ints
[n m] gen-two-ascending-pos-reals]
(let [scale (/ 1 r) shape (+ 1.0 s) v (inc r)]
;; sd must be > 0 and of the order of a to avoid extremes of the normal.
(is (<= a (trunc-draw (sut/normal {:mu 0 :sd (+ 1.0 (abs a)) }) a b seed) b))
;; v between 1 and 2 so the distribution is thicker tailed.
(is (<= f (trunc-draw (sut/t {:v v}) f g seed) g))
;; This commented out test fails if k = 63 seed = 41 n = 0.1 m = 0.3
;; (sut/quantile (sut/chi-squared {:k 63}) (sut/cdf (sut/chi-squared {:k 63}) 0.1)) => 0.004285954228546346
;; (sut/quantile (sut/chi-squared {:k 63}) (sut/cdf (sut/chi-squared {:k 63}) 0.3)) => 0.004285954228546346
;; This commented out test fails if k = 1 seed = 25 n = 71.0 m = 94.0
;; (sut/quantile (sut/chi-squared {:k 1}) (sut/cdf (sut/chi-squared {:k 1}) 71.0)) => 200.0
;; (sut/quantile (sut/chi-squared {:k 1}) (sut/cdf (sut/chi-squared {:k 1}) 94.0)) => 200.0
#_(is (<= n (trunc-draw (sut/chi-squared {:k k}) n m seed) m))
;; lower and upper must be positive since log-normal support is (0, Inf)
(is (<= n (trunc-draw (sut/log-normal {:mu 0 :sd (+ 1.0 n) }) n m seed) m))
(is (<= n (trunc-draw (sut/cauchy {:location a :scale n }) n m seed) m))
;; share must be > 1 for pareto means to be finite.
(is (<= (+ n scale)
(trunc-draw (sut/pareto {:scale scale :shape shape}) (+ n scale) (+ m scale) seed)
(+ m scale)))
)))

(defspec exponential-returns-positive-floats
test-opts
(for-all [seed gen/small-integer
Expand Down
Loading