diff --git a/.gitignore b/.gitignore
index 1c4e86d..e41947a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
build/*
dist/*
+doc/build/*
*.egg-info
__pycache__
.ruff_cache
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..be9d2e1
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,24 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+buildapi:
+ sphinx-apidoc -efM ../squigglepy -o source/reference
+ @echo "Auto-generation of API documentation finished. " \
+ "The generated files are in 'api/'"
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 0000000..543c6b1
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 0000000..ebbe282
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = "Squigglepy"
+copyright = "2023, Peter Wildeford"
+author = "Peter Wildeford"
+
+# The short X.Y version
+version = ""
+# The full version, including alpha/beta/rc tags
+release = ""
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.imgmath",
+ "sphinx.ext.viewcode",
+ "numpydoc",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = "en"
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "pydata_sphinx_theme"
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = "Squigglepydoc"
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, "Squigglepy.tex", "Squigglepy Documentation", "Peter Wildeford", "manual"),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, "squigglepy", "Squigglepy Documentation", [author], 1)]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (
+ master_doc,
+ "Squigglepy",
+ "Squigglepy Documentation",
+ author,
+ "Squigglepy",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ["search.html"]
+
+
+# -- Extension configuration -------------------------------------------------
+
+numpydoc_class_members_toctree = False
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..3461183
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,51 @@
+Squigglepy: Implementation of Squiggle in Python
+================================================
+
+`Squiggle `__ is a "simple
+programming language for intuitive probabilistic estimation". It serves
+as its own standalone programming language with its own syntax, but it
+is implemented in JavaScript. I like the features of Squiggle and intend
+to use it frequently, but I also sometimes want to use similar
+functionalities in Python, especially alongside other Python statistical
+programming packages like Numpy, Pandas, and Matplotlib. The
+**squigglepy** package here implements many Squiggle-like
+functionalities in Python.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents
+
+ Installation
+ Usage
+ API Reference
+
+Disclaimers
+-----------
+
+This package is unofficial and supported by Peter Wildeford and Rethink
+Priorities. It is not affiliated with or associated with the Quantified
+Uncertainty Research Institute, which maintains the Squiggle language
+(in JavaScript).
+
+This package is also new and not yet in a stable production version, so
+you may encounter bugs and other errors. Please report those so they can
+be fixed. It’s also possible that future versions of the package may
+introduce breaking changes.
+
+This package is available under an MIT License.
+
+Acknowledgements
+----------------
+
+- The primary author of this package is Peter Wildeford. Agustín
+ Covarrubias and Bernardo Baron contributed several key features and
+ developments.
+- Thanks to Ozzie Gooen and the Quantified Uncertainty Research
+ Institute for creating and maintaining the original Squiggle
+ language.
+- Thanks to Dawn Drescher for helping me implement math between
+ distributions.
+- Thanks to Dawn Drescher for coming up with the idea to use ``~`` as a
+ shorthand for ``sample``, as well as helping me implement it.
+
+.. autosummary::
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
new file mode 100644
index 0000000..fb5e8d9
--- /dev/null
+++ b/doc/source/installation.rst
@@ -0,0 +1,12 @@
+Installation
+============
+
+.. code:: shell
+
+ pip install squigglepy
+
+For plotting support, you can also use the ``plots`` extra:
+
+.. code:: shell
+
+ pip install squigglepy[plots]
diff --git a/doc/source/reference/modules.rst b/doc/source/reference/modules.rst
new file mode 100644
index 0000000..57192bd
--- /dev/null
+++ b/doc/source/reference/modules.rst
@@ -0,0 +1,7 @@
+squigglepy
+==========
+
+.. toctree::
+ :maxdepth: 4
+
+ squigglepy
diff --git a/doc/source/reference/squigglepy.bayes.rst b/doc/source/reference/squigglepy.bayes.rst
new file mode 100644
index 0000000..8a445be
--- /dev/null
+++ b/doc/source/reference/squigglepy.bayes.rst
@@ -0,0 +1,7 @@
+squigglepy.bayes module
+=======================
+
+.. automodule:: squigglepy.bayes
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.correlation.rst b/doc/source/reference/squigglepy.correlation.rst
new file mode 100644
index 0000000..c99cf14
--- /dev/null
+++ b/doc/source/reference/squigglepy.correlation.rst
@@ -0,0 +1,7 @@
+squigglepy.correlation module
+=============================
+
+.. automodule:: squigglepy.correlation
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.distributions.rst b/doc/source/reference/squigglepy.distributions.rst
new file mode 100644
index 0000000..bfbdb38
--- /dev/null
+++ b/doc/source/reference/squigglepy.distributions.rst
@@ -0,0 +1,7 @@
+squigglepy.distributions module
+===============================
+
+.. automodule:: squigglepy.distributions
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.numbers.rst b/doc/source/reference/squigglepy.numbers.rst
new file mode 100644
index 0000000..524cbcd
--- /dev/null
+++ b/doc/source/reference/squigglepy.numbers.rst
@@ -0,0 +1,7 @@
+squigglepy.numbers module
+=========================
+
+.. automodule:: squigglepy.numbers
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.rng.rst b/doc/source/reference/squigglepy.rng.rst
new file mode 100644
index 0000000..84ec740
--- /dev/null
+++ b/doc/source/reference/squigglepy.rng.rst
@@ -0,0 +1,7 @@
+squigglepy.rng module
+=====================
+
+.. automodule:: squigglepy.rng
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.rst b/doc/source/reference/squigglepy.rst
new file mode 100644
index 0000000..b629fda
--- /dev/null
+++ b/doc/source/reference/squigglepy.rst
@@ -0,0 +1,22 @@
+squigglepy package
+==================
+
+.. automodule:: squigglepy
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+ :maxdepth: 4
+
+ squigglepy.bayes
+ squigglepy.correlation
+ squigglepy.distributions
+ squigglepy.numbers
+ squigglepy.rng
+ squigglepy.samplers
+ squigglepy.utils
+ squigglepy.version
diff --git a/doc/source/reference/squigglepy.samplers.rst b/doc/source/reference/squigglepy.samplers.rst
new file mode 100644
index 0000000..ae3e754
--- /dev/null
+++ b/doc/source/reference/squigglepy.samplers.rst
@@ -0,0 +1,7 @@
+squigglepy.samplers module
+==========================
+
+.. automodule:: squigglepy.samplers
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.utils.rst b/doc/source/reference/squigglepy.utils.rst
new file mode 100644
index 0000000..8af5f44
--- /dev/null
+++ b/doc/source/reference/squigglepy.utils.rst
@@ -0,0 +1,7 @@
+squigglepy.utils module
+=======================
+
+.. automodule:: squigglepy.utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/reference/squigglepy.version.rst b/doc/source/reference/squigglepy.version.rst
new file mode 100644
index 0000000..efe11f5
--- /dev/null
+++ b/doc/source/reference/squigglepy.version.rst
@@ -0,0 +1,7 @@
+squigglepy.version module
+=========================
+
+.. automodule:: squigglepy.version
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
new file mode 100644
index 0000000..3e03a70
--- /dev/null
+++ b/doc/source/usage.rst
@@ -0,0 +1,476 @@
+Examples
+========
+
+Piano tuners example
+~~~~~~~~~~~~~~~~~~~~
+
+Here’s the Squigglepy implementation of `the example from Squiggle
+Docs `__:
+
+.. code:: python
+
+ import squigglepy as sq
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from squigglepy.numbers import K, M
+ from pprint import pprint
+
+ pop_of_ny_2022 = sq.to(8.1*M, 8.4*M) # This means that you're 90% confident the value is between 8.1 and 8.4 Million.
+ pct_of_pop_w_pianos = sq.to(0.2, 1) * 0.01 # We assume there are almost no people with multiple pianos
+ pianos_per_piano_tuner = sq.to(2*K, 50*K)
+ piano_tuners_per_piano = 1 / pianos_per_piano_tuner
+ total_tuners_in_2022 = pop_of_ny_2022 * pct_of_pop_w_pianos * piano_tuners_per_piano
+ samples = total_tuners_in_2022 @ 1000 # Note: `@ 1000` is shorthand to get 1000 samples
+
+ # Get mean and SD
+ print('Mean: {}, SD: {}'.format(round(np.mean(samples), 2),
+ round(np.std(samples), 2)))
+
+ # Get percentiles
+ pprint(sq.get_percentiles(samples, digits=0))
+
+ # Histogram
+ plt.hist(samples, bins=200)
+ plt.show()
+
+ # Shorter histogram
+ total_tuners_in_2022.plot()
+
+And the version from the Squiggle doc that incorporates time:
+
+.. code:: python
+
+ import squigglepy as sq
+ from squigglepy.numbers import K, M
+
+ pop_of_ny_2022 = sq.to(8.1*M, 8.4*M)
+ pct_of_pop_w_pianos = sq.to(0.2, 1) * 0.01
+ pianos_per_piano_tuner = sq.to(2*K, 50*K)
+ piano_tuners_per_piano = 1 / pianos_per_piano_tuner
+
+ def pop_at_time(t): # t = Time in years after 2022
+ avg_yearly_pct_change = sq.to(-0.01, 0.05) # We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year
+ return pop_of_ny_2022 * ((avg_yearly_pct_change + 1) ** t)
+
+ def total_tuners_at_time(t):
+ return pop_at_time(t) * pct_of_pop_w_pianos * piano_tuners_per_piano
+
+ # Get total piano tuners at 2030
+ sq.get_percentiles(total_tuners_at_time(2030-2022) @ 1000)
+
+**WARNING:** Be careful about dividing by ``K``, ``M``, etc. ``1/2*K`` =
+500 in Python. Use ``1/(2*K)`` instead to get the expected outcome.
+
+**WARNING:** Be careful about using ``K`` to get sample counts. Use
+``sq.norm(2, 3) @ (2*K)``\ … ``sq.norm(2, 3) @ 2*K`` will return only
+two samples, multiplied by 1000.
+
+Distributions
+~~~~~~~~~~~~~
+
+.. code:: python
+
+ import squigglepy as sq
+
+ # Normal distribution
+ sq.norm(1, 3) # 90% interval from 1 to 3
+
+ # Distribution can be sampled with mean and sd too
+ sq.norm(mean=0, sd=1)
+
+ # Shorthand to get one sample
+ ~sq.norm(1, 3)
+
+ # Shorthand to get more than one sample
+ sq.norm(1, 3) @ 100
+
+ # Longhand version to get more than one sample
+ sq.sample(sq.norm(1, 3), n=100)
+
+ # Nice progress reporter
+ sq.sample(sq.norm(1, 3), n=1000, verbose=True)
+
+ # Other distributions exist
+ sq.lognorm(1, 10)
+ sq.tdist(1, 10, t=5)
+ sq.triangular(1, 2, 3)
+ sq.pert(1, 2, 3, lam=2)
+ sq.binomial(p=0.5, n=5)
+ sq.beta(a=1, b=2)
+ sq.bernoulli(p=0.5)
+ sq.poisson(10)
+ sq.chisquare(2)
+ sq.gamma(3, 2)
+ sq.pareto(1)
+ sq.exponential(scale=1)
+ sq.geometric(p=0.5)
+
+ # Discrete sampling
+ sq.discrete({'A': 0.1, 'B': 0.9})
+
+ # Can return integers
+ sq.discrete({0: 0.1, 1: 0.3, 2: 0.3, 3: 0.15, 4: 0.15})
+
+ # Alternate format (also can be used to return more complex objects)
+ sq.discrete([[0.1, 0],
+ [0.3, 1],
+ [0.3, 2],
+ [0.15, 3],
+ [0.15, 4]])
+
+ sq.discrete([0, 1, 2]) # No weights assumes equal weights
+
+ # You can mix distributions together
+ sq.mixture([sq.norm(1, 3),
+ sq.norm(4, 10),
+ sq.lognorm(1, 10)], # Distributions to mix
+ [0.3, 0.3, 0.4]) # These are the weights on each distribution
+
+ # This is equivalent to the above, just a different way of doing the notation
+ sq.mixture([[0.3, sq.norm(1,3)],
+ [0.3, sq.norm(4,10)],
+ [0.4, sq.lognorm(1,10)]])
+
+ # Make a zero-inflated distribution
+ # 60% chance of returning 0, 40% chance of sampling from `norm(1, 2)`.
+ sq.zero_inflated(0.6, sq.norm(1, 2))
+
+Additional features
+~~~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+ import squigglepy as sq
+
+ # You can add and subtract distributions
+ (sq.norm(1,3) + sq.norm(4,5)) @ 100
+ (sq.norm(1,3) - sq.norm(4,5)) @ 100
+ (sq.norm(1,3) * sq.norm(4,5)) @ 100
+ (sq.norm(1,3) / sq.norm(4,5)) @ 100
+
+ # You can also do math with numbers
+ ~((sq.norm(sd=5) + 2) * 2)
+ ~(-sq.lognorm(0.1, 1) * sq.pareto(1) / 10)
+
+ # You can change the CI from 90% (default) to 80%
+ sq.norm(1, 3, credibility=80)
+
+ # You can clip
+ sq.norm(0, 3, lclip=0, rclip=5) # Sample norm with a 90% CI from 0-3, but anything lower than 0 gets clipped to 0 and anything higher than 5 gets clipped to 5.
+
+ # You can also clip with a function, and use pipes
+ sq.norm(0, 3) >> sq.clip(0, 5)
+
+ # You can correlate continuous distributions
+ a, b = sq.uniform(-1, 1), sq.to(0, 3)
+ a, b = sq.correlate((a, b), 0.5) # Correlate a and b with a correlation of 0.5
+ # You can even pass your own correlation matrix!
+ a, b = sq.correlate((a, b), [[1, 0.5], [0.5, 1]])
+
+Example: Rolling a die
+^^^^^^^^^^^^^^^^^^^^^^
+
+An example of how to use distributions to build tools:
+
+.. code:: python
+
+ import squigglepy as sq
+
+ def roll_die(sides, n=1):
+ return sq.discrete(list(range(1, sides + 1))) @ n if sides > 0 else None
+
+ roll_die(sides=6, n=10)
+ # [2, 6, 5, 2, 6, 2, 3, 1, 5, 2]
+
+This is already included standard in the utils of this package. Use
+``sq.roll_die``.
+
+Bayesian inference
+~~~~~~~~~~~~~~~~~~
+
+1% of women at age forty who participate in routine screening have
+breast cancer. 80% of women with breast cancer will get positive
+mammographies. 9.6% of women without breast cancer will also get
+positive mammographies.
+
+A woman in this age group had a positive mammography in a routine
+screening. What is the probability that she actually has breast cancer?
+
+We can approximate the answer with a Bayesian network (uses rejection
+sampling):
+
+.. code:: python
+
+ import squigglepy as sq
+ from squigglepy import bayes
+ from squigglepy.numbers import M
+
+ def mammography(has_cancer):
+ return sq.event(0.8 if has_cancer else 0.096)
+
+ def define_event():
+ cancer = ~sq.bernoulli(0.01)
+ return({'mammography': mammography(cancer),
+ 'cancer': cancer})
+
+ bayes.bayesnet(define_event,
+ find=lambda e: e['cancer'],
+ conditional_on=lambda e: e['mammography'],
+ n=1*M)
+ # 0.07723995880535531
+
+Or if we have the information immediately on hand, we can directly
+calculate it. Though this doesn’t work for very complex stuff.
+
+.. code:: python
+
+ from squigglepy import bayes
+ bayes.simple_bayes(prior=0.01, likelihood_h=0.8, likelihood_not_h=0.096)
+ # 0.07763975155279504
+
+You can also make distributions and update them:
+
+.. code:: python
+
+ import matplotlib.pyplot as plt
+ import squigglepy as sq
+ from squigglepy import bayes
+ from squigglepy.numbers import K
+ import numpy as np
+
+ print('Prior')
+ prior = sq.norm(1,5)
+ prior_samples = prior @ (10*K)
+ plt.hist(prior_samples, bins = 200)
+ plt.show()
+ print(sq.get_percentiles(prior_samples))
+ print('Prior Mean: {} SD: {}'.format(np.mean(prior_samples), np.std(prior_samples)))
+ print('-')
+
+ print('Evidence')
+ evidence = sq.norm(2,3)
+ evidence_samples = evidence @ (10*K)
+ plt.hist(evidence_samples, bins = 200)
+ plt.show()
+ print(sq.get_percentiles(evidence_samples))
+ print('Evidence Mean: {} SD: {}'.format(np.mean(evidence_samples), np.std(evidence_samples)))
+ print('-')
+
+ print('Posterior')
+ posterior = bayes.update(prior, evidence)
+ posterior_samples = posterior @ (10*K)
+ plt.hist(posterior_samples, bins = 200)
+ plt.show()
+ print(sq.get_percentiles(posterior_samples))
+ print('Posterior Mean: {} SD: {}'.format(np.mean(posterior_samples), np.std(posterior_samples)))
+
+ print('Average')
+ average = bayes.average(prior, evidence)
+ average_samples = average @ (10*K)
+ plt.hist(average_samples, bins = 200)
+ plt.show()
+ print(sq.get_percentiles(average_samples))
+ print('Average Mean: {} SD: {}'.format(np.mean(average_samples), np.std(average_samples)))
+
+Example: Alarm net
+^^^^^^^^^^^^^^^^^^
+
+This is the alarm network from `Bayesian Artificial Intelligence -
+Section
+2.5.1 `__:
+
+ Assume your house has an alarm system against burglary.
+
+ You live in the seismically active area and the alarm system can get
+ occasionally set off by an earthquake.
+
+ You have two neighbors, Mary and John, who do not know each other. If
+ they hear the alarm they call you, but this is not guaranteed.
+
+ The chance of a burglary on a particular day is 0.1%. The chance of
+ an earthquake on a particular day is 0.2%.
+
+ The alarm will go off 95% of the time with both a burglary and an
+ earthquake, 94% of the time with just a burglary, 29% of the time
+ with just an earthquake, and 0.1% of the time with nothing (total
+ false alarm).
+
+ John will call you 90% of the time when the alarm goes off. But on 5%
+ of the days, John will just call to say “hi”. Mary will call you 70%
+ of the time when the alarm goes off. But on 1% of the days, Mary will
+ just call to say “hi”.
+
+.. code:: python
+
+ import squigglepy as sq
+ from squigglepy import bayes
+ from squigglepy.numbers import M
+
+ def p_alarm_goes_off(burglary, earthquake):
+ if burglary and earthquake:
+ return 0.95
+ elif burglary and not earthquake:
+ return 0.94
+ elif not burglary and earthquake:
+ return 0.29
+ elif not burglary and not earthquake:
+ return 0.001
+
+ def p_john_calls(alarm_goes_off):
+ return 0.9 if alarm_goes_off else 0.05
+
+ def p_mary_calls(alarm_goes_off):
+ return 0.7 if alarm_goes_off else 0.01
+
+ def define_event():
+ burglary_happens = sq.event(p=0.001)
+ earthquake_happens = sq.event(p=0.002)
+ alarm_goes_off = sq.event(p_alarm_goes_off(burglary_happens, earthquake_happens))
+ john_calls = sq.event(p_john_calls(alarm_goes_off))
+ mary_calls = sq.event(p_mary_calls(alarm_goes_off))
+ return {'burglary': burglary_happens,
+ 'earthquake': earthquake_happens,
+ 'alarm_goes_off': alarm_goes_off,
+ 'john_calls': john_calls,
+ 'mary_calls': mary_calls}
+
+ # What are the chances that both John and Mary call if an earthquake happens?
+ bayes.bayesnet(define_event,
+ n=1*M,
+ find=lambda e: (e['mary_calls'] and e['john_calls']),
+ conditional_on=lambda e: e['earthquake'])
+ # Result will be ~0.19, though it varies because it is based on a random sample.
+ # This also may take a minute to run.
+
+ # If both John and Mary call, what is the chance there's been a burglary?
+ bayes.bayesnet(define_event,
+ n=1*M,
+ find=lambda e: e['burglary'],
+ conditional_on=lambda e: (e['mary_calls'] and e['john_calls']))
+ # Result will be ~0.27, though it varies because it is based on a random sample.
+ # This will run quickly because there is a built-in cache.
+ # Use `cache=False` to not build a cache and `reload_cache=True` to recalculate the cache.
+
+Note that the amount of Bayesian analysis that squigglepy can do is
+pretty limited. For more complex bayesian analysis, consider
+`sorobn `__,
+`pomegranate `__,
+`bnlearn `__, or
+`pyMC `__.
+
+Example: A demonstration of the Monty Hall Problem
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code:: python
+
+ import squigglepy as sq
+ from squigglepy import bayes
+ from squigglepy.numbers import K, M, B, T
+
+
+ def monte_hall(door_picked, switch=False):
+ doors = ['A', 'B', 'C']
+ car_is_behind_door = ~sq.discrete(doors)
+ reveal_door = ~sq.discrete([d for d in doors if d != door_picked and d != car_is_behind_door])
+
+ if switch:
+ old_door_picked = door_picked
+ door_picked = [d for d in doors if d != old_door_picked and d != reveal_door][0]
+
+ won_car = (car_is_behind_door == door_picked)
+ return won_car
+
+
+ def define_event():
+ door = ~sq.discrete(['A', 'B', 'C'])
+ switch = sq.event(0.5)
+ return {'won': monte_hall(door_picked=door, switch=switch),
+ 'switched': switch}
+
+ RUNS = 10*K
+ r = bayes.bayesnet(define_event,
+ find=lambda e: e['won'],
+ conditional_on=lambda e: e['switched'],
+ verbose=True,
+ n=RUNS)
+ print('Win {}% of the time when switching'.format(int(r * 100)))
+
+ r = bayes.bayesnet(define_event,
+ find=lambda e: e['won'],
+ conditional_on=lambda e: not e['switched'],
+ verbose=True,
+ n=RUNS)
+ print('Win {}% of the time when not switching'.format(int(r * 100)))
+
+ # Win 66% of the time when switching
+ # Win 34% of the time when not switching
+
+Example: More complex coin/dice interactions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Imagine that I flip a coin. If heads, I take a random die out of my
+ blue bag. If tails, I take a random die out of my red bag. The blue
+ bag contains only 6-sided dice. The red bag contains a 4-sided die, a
+ 6-sided die, a 10-sided die, and a 20-sided die. I then roll the
+ random die I took. What is the chance that I roll a 6?
+
+.. code:: python
+
+ import squigglepy as sq
+ from squigglepy.numbers import K, M, B, T
+ from squigglepy import bayes
+
+ def define_event():
+ if sq.flip_coin() == 'heads': # Blue bag
+ return sq.roll_die(6)
+ else: # Red bag
+ return sq.discrete([4, 6, 10, 20]) >> sq.roll_die
+
+
+ bayes.bayesnet(define_event,
+ find=lambda e: e == 6,
+ verbose=True,
+ n=100*K)
+ # This run for me returned 0.12306 which is pretty close to the correct answer of 0.12292
+
+Kelly betting
+~~~~~~~~~~~~~
+
+You can use probability generated, combine with a bankroll to determine
+bet sizing using `Kelly
+criterion `__.
+
+For example, if you want to Kelly bet and you’ve…
+
+- determined that your price (your probability of the event in question
+ happening / the market in question resolving in your favor) is $0.70
+ (70%)
+- see that the market is pricing at $0.65
+- you have a bankroll of $1000 that you are willing to bet
+
+You should bet as follows:
+
+.. code:: python
+
+ import squigglepy as sq
+ kelly_data = sq.kelly(my_price=0.70, market_price=0.65, bankroll=1000)
+ kelly_data['kelly'] # What fraction of my bankroll should I bet on this?
+ # 0.143
+ kelly_data['target'] # How much money should be invested in this?
+ # 142.86
+ kelly_data['expected_roi'] # What is the expected ROI of this bet?
+ # 0.077
+
+More examples
+~~~~~~~~~~~~~
+
+You can see more examples of squigglepy in action
+`here `__.
+
+Run tests
+---------
+
+Use ``black .`` for formatting.
+
+Run
+``ruff check . && pytest && pip3 install . && python3 tests/integration.py``
diff --git a/poetry.lock b/poetry.lock
index 86c4e93..42f9236 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,5 +1,30 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+[[package]]
+name = "accessible-pygments"
+version = "0.0.4"
+description = "A collection of accessible pygments styles"
+optional = false
+python-versions = "*"
+files = [
+ {file = "accessible-pygments-0.0.4.tar.gz", hash = "sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e"},
+ {file = "accessible_pygments-0.0.4-py2.py3-none-any.whl", hash = "sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d"},
+]
+
+[package.dependencies]
+pygments = ">=1.5"
+
+[[package]]
+name = "alabaster"
+version = "0.7.13"
+description = "A configurable sidebar-enabled Sphinx theme"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"},
+ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
+]
+
[[package]]
name = "ansi2html"
version = "1.8.0"
@@ -33,6 +58,38 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+[[package]]
+name = "babel"
+version = "2.13.1"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"},
+ {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"},
+]
+
+[package.extras]
+dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.2"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.6.0"
+files = [
+ {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"},
+ {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
[[package]]
name = "black"
version = "23.3.0"
@@ -438,6 +495,17 @@ files = [
[package.extras]
graph = ["objgraph (>=1.7.2)"]
+[[package]]
+name = "docutils"
+version = "0.20.1"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"},
+ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"},
+]
+
[[package]]
name = "exceptiongroup"
version = "1.1.1"
@@ -602,6 +670,17 @@ files = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
[[package]]
name = "importlib-metadata"
version = "6.7.0"
@@ -1369,6 +1448,33 @@ files = [
[package.extras]
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
+[[package]]
+name = "pydata-sphinx-theme"
+version = "0.14.3"
+description = "Bootstrap-based Sphinx theme from the PyData community"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pydata_sphinx_theme-0.14.3-py3-none-any.whl", hash = "sha256:b7e40cd75a20449adfe2d7525be379b9fe92f6d31e5233e449fa34ddcd4398d9"},
+ {file = "pydata_sphinx_theme-0.14.3.tar.gz", hash = "sha256:bd474f347895f3fc5b6ce87390af64330ee54f11ebf9660d5bc3f87d532d4e5c"},
+]
+
+[package.dependencies]
+accessible-pygments = "*"
+Babel = "*"
+beautifulsoup4 = "*"
+docutils = "!=0.17.0"
+packaging = "*"
+pygments = ">=2.7"
+sphinx = ">=5.0"
+typing-extensions = "*"
+
+[package.extras]
+a11y = ["pytest-playwright"]
+dev = ["nox", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml"]
+doc = ["ablog (>=0.11.0rc2)", "colorama", "ipykernel", "ipyleaflet", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (<1.4)", "sphinxext-rediraffe", "xarray"]
+test = ["pytest", "pytest-cov", "pytest-regressions"]
+
[[package]]
name = "pygments"
version = "2.15.1"
@@ -1659,6 +1765,17 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+optional = false
+python-versions = "*"
+files = [
+ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+]
+
[[package]]
name = "sortedcontainers"
version = "2.4.0"
@@ -1670,6 +1787,156 @@ files = [
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
]
+[[package]]
+name = "soupsieve"
+version = "2.5"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"},
+ {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
+]
+
+[[package]]
+name = "sphinx"
+version = "7.2.6"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"},
+ {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=2.9"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.18.1,<0.21"
+imagesize = ">=1.3"
+importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""}
+Jinja2 = ">=3.0"
+packaging = ">=21.0"
+Pygments = ">=2.14"
+requests = ">=2.25.0"
+snowballstemmer = ">=2.0"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.9"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"]
+test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.7"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"},
+ {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"},
+]
+
+[package.dependencies]
+Sphinx = ">=5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.5"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"},
+ {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"},
+]
+
+[package.dependencies]
+Sphinx = ">=5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.4"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"},
+ {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"},
+]
+
+[package.dependencies]
+Sphinx = ">=5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.6"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"},
+ {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"},
+]
+
+[package.dependencies]
+Sphinx = ">=5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.9"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"},
+ {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"},
+]
+
+[package.dependencies]
+Sphinx = ">=5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
[[package]]
name = "tenacity"
version = "8.2.2"
@@ -1809,4 +2076,4 @@ plots = ["matplotlib"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
-content-hash = "5625f6f3eec6de2121f93d9cb0591c23402a9c232f824ce8353c50b7441e3a9d"
+content-hash = "f9f1beabf7d339e2173f854e8559f6bd9f1fbb28d711802c3d0d831a7b1fa2b5"
diff --git a/pyproject.toml b/pyproject.toml
index d4e93d2..b35de76 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,7 @@ pathos = "^0.3.0"
msgspec = "^0.15.1"
matplotlib = { version = "^3.7.1", optional = true }
pandas = { version = "^2.0.2", optional = true }
+pydata-sphinx-theme = "^0.14.3"
[tool.poetry.group.dev.dependencies]
diff --git a/squigglepy/bayes.py b/squigglepy/bayes.py
index 415f29a..e3fec17 100644
--- a/squigglepy/bayes.py
+++ b/squigglepy/bayes.py
@@ -1,3 +1,7 @@
+"""
+This modules includes functions for Bayesian inference.
+"""
+
import os
import time
import math
diff --git a/squigglepy/correlation.py b/squigglepy/correlation.py
index abd20b2..0108940 100644
--- a/squigglepy/correlation.py
+++ b/squigglepy/correlation.py
@@ -107,9 +107,10 @@ def correlate(
>>> solar_radiation, temperature = sq.gamma(300, 100), sq.to(22, 28)
>>> solar_radiation, temperature = sq.correlate((solar_radiation, temperature), 0.7)
>>> print(np.corrcoef(solar_radiation @ 1000, temperature @ 1000)[0, 1])
- 0.6975960649767123
+ 0.6975960649767123
Or you could pass a correlation matrix:
+
>>> funding_gap, cost_per_delivery, effect_size = (
sq.to(20_000, 80_000), sq.to(30, 80), sq.beta(2, 5)
)
@@ -118,9 +119,9 @@ def correlate(
[[1, 0.6, -0.5], [0.6, 1, -0.2], [-0.5, -0.2, 1]]
)
>>> print(np.corrcoef(funding_gap @ 1000, cost_per_delivery @ 1000, effect_size @ 1000))
- array([[ 1. , 0.580520 , -0.480149],
- [ 0.580962, 1. , -0.187831],
- [-0.480149, -0.187831 , 1. ]])
+ array([[ 1. , 0.580520 , -0.480149],
+ [ 0.580962, 1. , -0.187831],
+ [-0.480149, -0.187831 , 1. ]])
"""
if not isinstance(variables, tuple):
diff --git a/squigglepy/distributions.py b/squigglepy/distributions.py
index 413e749..7b01f7a 100644
--- a/squigglepy/distributions.py
+++ b/squigglepy/distributions.py
@@ -1,3 +1,7 @@
+"""
+A collection of probability distributions and functions to operate on them.
+"""
+
import operator
import math
import numpy as np
diff --git a/squigglepy/utils.py b/squigglepy/utils.py
index 03e02dc..fa6d9e2 100644
--- a/squigglepy/utils.py
+++ b/squigglepy/utils.py
@@ -912,25 +912,25 @@ def kelly(my_price, market_price, deference=0, bankroll=1, resolve_date=None, cu
-------
dict
A dict of values specifying:
- * ``my_price``
- * ``market_price``
- * ``deference``
- * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
- into account.
- * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
- * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
- ``market_price``.
- * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
- you should bet.
- * ``target`` : the target amount of money you should have invested
- * ``current``
- * ``delta`` : the amount of money you should invest given what you already
- have invested
- * ``max_gain`` : the amount of money you would gain if you win
- * ``modeled_gain`` : the expected value you would win given ``adj_price``
- * ``expected_roi`` : the expected return on investment
- * ``expected_arr`` : the expected ARR given ``resolve_date``
- * ``resolve_date``
+ * ``my_price``
+ * ``market_price``
+ * ``deference``
+ * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
+ into account.
+ * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
+ * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
+ ``market_price``.
+ * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
+ you should bet.
+ * ``target`` : the target amount of money you should have invested
+ * ``current``
+ * ``delta`` : the amount of money you should invest given what you already
+ have invested
+ * ``max_gain`` : the amount of money you would gain if you win
+ * ``modeled_gain`` : the expected value you would win given ``adj_price``
+ * ``expected_roi`` : the expected return on investment
+ * ``expected_arr`` : the expected ARR given ``resolve_date``
+ * ``resolve_date``
Examples
--------
@@ -1000,25 +1000,25 @@ def full_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0)
-------
dict
A dict of values specifying:
- * ``my_price``
- * ``market_price``
- * ``deference``
- * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
- into account.
- * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
- * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
- ``market_price``.
- * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
- you should bet.
- * ``target`` : the target amount of money you should have invested
- * ``current``
- * ``delta`` : the amount of money you should invest given what you already
- have invested
- * ``max_gain`` : the amount of money you would gain if you win
- * ``modeled_gain`` : the expected value you would win given ``adj_price``
- * ``expected_roi`` : the expected return on investment
- * ``expected_arr`` : the expected ARR given ``resolve_date``
- * ``resolve_date``
+ * ``my_price``
+ * ``market_price``
+ * ``deference``
+ * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
+ into account.
+ * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
+ * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
+ ``market_price``.
+ * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
+ you should bet.
+ * ``target`` : the target amount of money you should have invested
+ * ``current``
+ * ``delta`` : the amount of money you should invest given what you already
+ have invested
+ * ``max_gain`` : the amount of money you would gain if you win
+ * ``modeled_gain`` : the expected value you would win given ``adj_price``
+ * ``expected_roi`` : the expected return on investment
+ * ``expected_arr`` : the expected ARR given ``resolve_date``
+ * ``resolve_date``
Examples
--------
@@ -1062,25 +1062,25 @@ def half_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0)
-------
dict
A dict of values specifying:
- * ``my_price``
- * ``market_price``
- * ``deference``
- * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
- into account.
- * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
- * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
- ``market_price``.
- * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
- you should bet.
- * ``target`` : the target amount of money you should have invested
- * ``current``
- * ``delta`` : the amount of money you should invest given what you already
- have invested
- * ``max_gain`` : the amount of money you would gain if you win
- * ``modeled_gain`` : the expected value you would win given ``adj_price``
- * ``expected_roi`` : the expected return on investment
- * ``expected_arr`` : the expected ARR given ``resolve_date``
- * ``resolve_date``
+ * ``my_price``
+ * ``market_price``
+ * ``deference``
+ * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
+ into account.
+ * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
+ * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
+ ``market_price``.
+ * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
+ you should bet.
+ * ``target`` : the target amount of money you should have invested
+ * ``current``
+ * ``delta`` : the amount of money you should invest given what you already
+ have invested
+ * ``max_gain`` : the amount of money you would gain if you win
+ * ``modeled_gain`` : the expected value you would win given ``adj_price``
+ * ``expected_roi`` : the expected return on investment
+ * ``expected_arr`` : the expected ARR given ``resolve_date``
+ * ``resolve_date``
Examples
--------
@@ -1124,25 +1124,25 @@ def quarter_kelly(my_price, market_price, bankroll=1, resolve_date=None, current
-------
dict
A dict of values specifying:
- * ``my_price``
- * ``market_price``
- * ``deference``
- * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
- into account.
- * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
- * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
- ``market_price``.
- * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
- you should bet.
- * ``target`` : the target amount of money you should have invested
- * ``current``
- * ``delta`` : the amount of money you should invest given what you already
- have invested
- * ``max_gain`` : the amount of money you would gain if you win
- * ``modeled_gain`` : the expected value you would win given ``adj_price``
- * ``expected_roi`` : the expected return on investment
- * ``expected_arr`` : the expected ARR given ``resolve_date``
- * ``resolve_date``
+ * ``my_price``
+ * ``market_price``
+ * ``deference``
+ * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken
+ into account.
+ * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``.
+ * ``adj_delta_price`` : the absolute difference between ``adj_price`` and
+ ``market_price``.
+ * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll``
+ you should bet.
+ * ``target`` : the target amount of money you should have invested
+ * ``current``
+ * ``delta`` : the amount of money you should invest given what you already
+ have invested
+ * ``max_gain`` : the amount of money you would gain if you win
+ * ``modeled_gain`` : the expected value you would win given ``adj_price``
+ * ``expected_roi`` : the expected return on investment
+ * ``expected_arr`` : the expected ARR given ``resolve_date``
+ * ``resolve_date``
Examples
--------