diff --git a/poetry.lock b/poetry.lock index 37b1adf..f7dcf55 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,33 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "astunparse" @@ -15,6 +44,17 @@ files = [ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + [[package]] name = "build" version = "1.0.3" @@ -274,6 +314,23 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + [[package]] name = "crashtest" version = "0.4.1" @@ -339,6 +396,48 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "debugpy" +version = "1.8.1" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, + {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, + {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, + {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, + {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, + {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, + {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, + {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, + {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, + {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, + {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, + {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, + {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, + {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, + {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, + {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, + {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, + {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, + {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, + {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, + {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, + {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "distlib" version = "0.3.7" @@ -452,6 +551,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + [[package]] name = "fastjsonschema" version = "2.18.1" @@ -583,6 +696,78 @@ files = [ {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, ] +[[package]] +name = "ipykernel" +version = "6.29.4" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.4-py3-none-any.whl", hash = "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da"}, + {file = "ipykernel-6.29.4.tar.gz", hash = "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.12.3" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipython-8.12.3-py3-none-any.whl", hash = "sha256:b0340d46a933d27c657b211a329d0be23793c36595acf9e6ef4164bc01a1804c"}, + {file = "ipython-8.12.3.tar.gz", hash = "sha256:3910c4b54543c2ad73d06579aa771041b7d5707b033bd488669b4cf544e3b363"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + [[package]] name = "jaraco-classes" version = "3.3.0" @@ -601,6 +786,25 @@ more-itertools = "*" docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + [[package]] name = "jeepney" version = "0.8.0" @@ -633,6 +837,60 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + +[[package]] +name = "jupyter-client" +version = "8.6.2" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + [[package]] name = "keyring" version = "24.2.0" @@ -777,6 +1035,20 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mergedeep" version = "1.3.4" @@ -996,6 +1268,17 @@ files = [ {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + [[package]] name = "numba" version = "0.58.1" @@ -1146,6 +1429,21 @@ sql-other = ["SQLAlchemy (>=1.4.16)"] test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.6.3)"] +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + [[package]] name = "pathspec" version = "0.11.2" @@ -1189,6 +1487,17 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + [[package]] name = "pkginfo" version = "1.9.6" @@ -1315,6 +1624,48 @@ files = [ poetry = ">=1.6.0,<2.0.0" poetry-core = ">=1.7.0,<2.0.0" +[[package]] +name = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -1326,6 +1677,20 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycparser" version = "2.21" @@ -1447,6 +1812,29 @@ files = [ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.2" @@ -1532,6 +1920,106 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "pyzmq" +version = "26.0.3" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, + {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, + {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, + {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, + {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, + {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, + {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, + {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + [[package]] name = "rapidfuzz" version = "3.5.2" @@ -1744,6 +2232,25 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "statsmodels" version = "0.14.0" @@ -1833,6 +2340,41 @@ files = [ {file = "tomlkit-0.12.2.tar.gz", hash = "sha256:df32fab589a81f0d7dc525a4267b6d7a64ee99619cbd1eeb0fae32c1dd426977"}, ] +[[package]] +name = "tornado" +version = "6.4" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, + {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, + {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, + {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "trove-classifiers" version = "2023.10.18" @@ -1844,6 +2386,17 @@ files = [ {file = "trove_classifiers-2023.10.18-py3-none-any.whl", hash = "sha256:20a3da8e3cb65587cc9f5d5b837bf74edeb480bba9bd8cd4f03ab056d6b06c4c"}, ] +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + [[package]] name = "tzdata" version = "2023.3" @@ -1931,6 +2484,17 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [[package]] name = "wheel" version = "0.41.3" @@ -2046,5 +2610,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = ">=3.8,<4.0" -content-hash = "c1bdd7ba1dbf4d249e6aa0d336ebb5a70c8e35ff81235a94cc57055cb45bf884" +python-versions = ">=3.8,<3.12" +content-hash = "15bc27046ea2dae655b38615735a15b44dd8703685e9dd8974130b149a0e18eb" diff --git a/pyproject.toml b/pyproject.toml index dfec8b6..05ad9d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ repository="https://github.com/s3alfisc/wildboottest" [tool.poetry.dependencies] -python=">=3.8,<4.0" +python=">=3.8,<3.12" numba=">=0.57" numpy=">=1.18" pandas=">=1.4" @@ -30,6 +30,7 @@ pytest="^7.2.0" statsmodels=">=0.13" tabulate = "^0.9.0" poetry = "^1.4.2" +joblib = "^1.4.2" [tool.poetry.dev-dependencies] pytest = "^7.2.0" @@ -48,6 +49,7 @@ pymdown-extensions = ">=10.0" mkdocstrings-python-legacy = "^0.2.3" mkdocstrings = {version = "^0.19.0", extras = ["python"], optional = true } pymdown-extensions = ">=10.0" +ipykernel = "^6.29.4" [build-system] diff --git a/tests/sim_sample.dta b/tests/sim_sample.dta new file mode 100644 index 0000000..7cf9a8e Binary files /dev/null and b/tests/sim_sample.dta differ diff --git a/tests/test_mle.ipynb b/tests/test_mle.ipynb new file mode 100644 index 0000000..9c740a1 --- /dev/null +++ b/tests/test_mle.ipynb @@ -0,0 +1,1178 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Score Bootstrap based on Kline and Santos (2012)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ___ ____ ____ ____ ____ ®\n", + " /__ / ____/ / ____/ Stata 18.0\n", + "___/ / /___/ / /___/ MP—Parallel Edition\n", + "\n", + " Statistics and Data Science Copyright 1985-2023 StataCorp LLC\n", + " StataCorp\n", + " 4905 Lakeway Drive\n", + " College Station, Texas 77845 USA\n", + " 800-782-8272 https://www.stata.com\n", + " 979-696-4600 service@stata.com\n", + "\n", + "Stata license: Single-user 2-core perpetual\n", + "Serial number: 501806367191\n", + " Licensed to: Aleksandr Michuda\n", + " Cornell University\n", + "\n", + "Notes:\n", + " 1. Unicode is supported; see help unicode_advice.\n", + " 2. More than 2 billion observations are allowed; see help obs_advice.\n", + " 3. Maximum number of variables is set to 5,000 but can be increased;\n", + " see help set_maxvar.\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from wildboottest.wildboottest import wildboottest\n", + "from wildboottest.weights import draw_weights\n", + "from statsmodels.discrete.discrete_model import Probit\n", + "from numba import prange, jit\n", + "import statsmodels.api as sm\n", + "import pandas as pd\n", + "import stata_setup\n", + "stata_setup.config(path = \"/Applications/Stata\", \n", + " edition = \"mp\")\n", + "from pystata import stata\n", + "import sfi\n", + "from joblib import Parallel, delayed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stata Code from Paper" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + ". \n", + ". ****************************************************\n", + ". * Code for Table 2 of Kline and Santos (2011) *\n", + ". * This file computes Analytical Wald and LM tests *\n", + ". * along with Score bootstrapped tests. *\n", + ". * Some other results not reported in the paper *\n", + ". * are also included. * \n", + ". ****************************************************\n", + ". \n", + ". version 11.1\n", + "\n", + ". set seed 12345\n", + "\n", + ". \n", + ". **mata subroutine to compute recentered clustered outer products**\n", + ". **inputs a matrix S of scores and a cluster summing matrix L**\n", + ". **outputs clustered outer product matrix**\n", + ". **note this program assumes a balanced design**\n", + ". **and proper sort order**\n", + ". cap mata: mata drop clustOPG()\n", + "\n", + ". mata: \n", + "------------------------------------------------- mata (type end to exit) -----\n", + ": real matrix clustOPG(real matrix S,real matrix L){\n", + "> real scalar N\n", + "> real matrix Scent, Scentsum, OPGcent\n", + "> \n", + "> N=rows(S)\n", + "> Scent=S - J(N,1,1)*mean(S)\n", + "> Scentsum=L'*Scent\n", + "> OPGcent=Scentsum'*Scentsum\n", + "> return(OPGcent)\n", + "> }\n", + "\n", + ": end\n", + "-------------------------------------------------------------------------------\n", + "\n", + ". \n", + ". \n", + ". \n", + ". /* cap prog drop sim_probit\n", + "> prog def sim_probit, rclass\n", + "> syntax [, c(integer 5) m(integer 50) f(integer 0) r(integer 199)] */\n", + ". *Notes on syntax: \n", + ". *c - # of clusters\n", + ". *m - # of obs per cluster\n", + ". *f - controls whether to use mixture regressor\n", + ". *r - # of bootstrap reps per simulation\n", + ". \n", + ". local c 10\n", + "\n", + ". local m 100\n", + "\n", + ". local f 0\n", + "\n", + ". local r 1000\n", + "\n", + ". \n", + ". **Generate data**\n", + ". drop _all\n", + "\n", + ". set obs `c' //generate c clusters\n", + "Number of observations (_N) was 0, now 10.\n", + "\n", + ". \n", + ". gen x=invnorm(uniform()) //covariate\n", + "\n", + ". gen mix=uniform()>.1 //contamination probability\n", + "\n", + ". \n", + ". if `f'==1{\n", + ". gen D=(mix*invnorm(uniform()) + (1-mix)*(2+3*invnorm(uniform())))/4 /\n", + "> /regressor of interest\n", + ". }\n", + "\n", + ". else{\n", + ". gen D=(invnorm(uniform()))/4 //regressor of interest\n", + ". }\n", + "\n", + ". \n", + ". gen v=invnorm(uniform())\n", + "\n", + ". \n", + ". \n", + ". gen c=_n //generate cluster id\n", + "\n", + ". \n", + ". expand `m' //make m obs per cluster\n", + "(990 observations created)\n", + "\n", + ". \n", + ". replace x=(x+invnorm(uniform()))/4 //add some within cluster variation in x\n", + "(1,000 real changes made)\n", + "\n", + ". \n", + ". gen y=x+D+v/sqrt(2)+invnorm(uniform())/sqrt(2) //form outcome\n", + "\n", + ". replace y=y>0\n", + "(1,000 real changes made)\n", + "\n", + ". sort c\n", + "\n", + ". \n", + ". **Restricted**\n", + ". constraint 1 D=1\n", + "\n", + ". glm y x D, constraint(1) fam(bin) link(probit)\n", + "\n", + "Iteration 0: Log likelihood = -685.70942 \n", + "Iteration 1: Log likelihood = -685.44941 \n", + "Iteration 2: Log likelihood = -685.4494 \n", + "\n", + "Generalized linear models Number of obs = 1,000\n", + "Optimization : ML Residual df = 998\n", + " Scale parameter = 1\n", + "Deviance = 1370.898802 (1/df) Deviance = 1.373646\n", + "Pearson = 1097.020519 (1/df) Pearson = 1.099219\n", + "\n", + "Variance function: V(u) = u*(1-u) [Bernoulli]\n", + "Link function : g(u) = invnorm(u) [Probit]\n", + "\n", + " AIC = 1.374899\n", + "Log likelihood = -685.4494008 BIC = -5523.041\n", + "\n", + " ( 1) [y]D = 1\n", + "------------------------------------------------------------------------------\n", + " | OIM\n", + " y | Coefficient std. err. z P>|z| [95% conf. interval]\n", + "-------------+----------------------------------------------------------------\n", + " x | .9846371 .1474927 6.68 0.000 .6955568 1.273717\n", + " D | 1 (constrained)\n", + " _cons | .1066109 .040653 2.62 0.009 .0269325 .1862893\n", + "------------------------------------------------------------------------------\n", + "\n", + ". predict pr\n", + "(option mu assumed; predicted mean y)\n", + "\n", + ". predict xbr, xb\n", + "\n", + ". gen phir=normalden(xbr)\n", + "\n", + ". gen er=(y-pr)*phir/(pr*(1-pr)) //quasi-residual\n", + "\n", + ". gen wr=phir^2/(pr*(1-pr)) //weights for hessian\n", + "\n", + ". gen one=1\n", + "\n", + ". \n", + ". mat accum Hr=one x D [iw=wr], nocons //form hessian */\n", + "(obs=598.5860322)\n", + "\n", + ". \n", + ". \n", + ". **Unrestricted**\n", + ". probit y x D, cluster(c)\n", + "\n", + "Iteration 0: Log pseudolikelihood = -693.12918 \n", + "Iteration 1: Log pseudolikelihood = -665.3805 \n", + "Iteration 2: Log pseudolikelihood = -665.33497 \n", + "Iteration 3: Log pseudolikelihood = -665.33497 \n", + "\n", + "Probit regression Number of obs = 1,000\n", + " Wald chi2(2) = 7.41\n", + " Prob > chi2 = 0.0247\n", + "Log pseudolikelihood = -665.33497 Pseudo R2 = 0.0401\n", + "\n", + " (Std. err. adjusted for 10 clusters in c)\n", + "------------------------------------------------------------------------------\n", + " | Robust\n", + " y | Coefficient std. err. z P>|z| [95% conf. interval]\n", + "-------------+----------------------------------------------------------------\n", + " x | 1.057877 .3958503 2.67 0.008 .2820252 1.83373\n", + " D | .0942995 1.041973 0.09 0.928 -1.947931 2.13653\n", + " _cons | .0357662 .3079585 0.12 0.908 -.5678212 .6393537\n", + "------------------------------------------------------------------------------\n", + "\n", + ". global b=_b[D]\n", + "\n", + ". local stdof=sqrt((`m'*`c'-3)/(`m'*`c'-1)) //inverse stata DOF correction\n", + "\n", + ". global W=((_b[D]-1)/(_se[D]*`stdof'))^2 //save analytical Wald\n", + "\n", + ". \n", + ". predict pu\n", + "(option pr assumed; Pr(y))\n", + "\n", + ". predict xbu, xb\n", + "\n", + ". gen phiu=normalden(xbu)\n", + "\n", + ". gen eu=(y-pu)*phiu/(pu*(1-pu)) //quasi-residual\n", + "\n", + ". gen wu=phiu^2/(pu*(1-pu)) //weights for hessian\n", + "\n", + ". \n", + ". local dfk=(`c'/(`c'-1))\n", + "\n", + ". mata: L=I(`c')#J(`m',1,1) //matrix for summing across clusters\n", + "\n", + ". mata: X=st_data(.,(\"one\", \"x\", \"D\"))\n", + "\n", + ". mata: C=(0\\0\\1) //constraint matrix -- third coefficient is restricte\n", + "> d\n", + "\n", + ". \n", + ". mata: Hr=st_matrix(\"Hr\")\n", + "\n", + ". mata: Hrinv=invsym(Hr)\n", + "\n", + ". mata: Ar=C'*Hrinv //form A_n\n", + "\n", + ". \n", + ". \n", + ". **Prepare influence function wald\n", + ". mat accum Hu=one x D [iw=wu], nocons //form hessian\n", + "(obs=615.9930729)\n", + "\n", + ". mata: Hu=st_matrix(\"Hu\")\n", + "\n", + ". mata: Huinv=invsym(Hu)\n", + "\n", + ". mata: Au=C'*Huinv\n", + "\n", + ". mata: eu=st_data(.,\"eu\")\n", + "\n", + ". mata: OPGucent=clustOPG(X:*eu,L)\n", + "\n", + ". mata: Vinvu=invsym(Au*OPGucent*`dfk'*Au') //compute inverse of variance of in\n", + "> fluence function\n", + "\n", + ". \n", + ". \n", + ". save \"sim_sample.dta\", replace\n", + "file sim_sample.dta saved\n", + "\n", + ". \n", + ". mata: S = X:*eu\n", + "\n", + ". \n", + ". **Bootstrap**\n", + ". gen u=.\n", + "(1,000 missing values generated)\n", + "\n", + ". gen ystar=.\n", + "(1,000 missing values generated)\n", + "\n", + ". gen ernew=.\n", + "(1,000 missing values generated)\n", + "\n", + ". gen eunew=.\n", + "(1,000 missing values generated)\n", + "\n", + ". gen pos=.\n", + "(1,000 missing values generated)\n", + "\n", + ". \n", + ". \n", + ". mat Ts=J(1,6,.) //store bootstrap statistics\n", + "\n", + ". \n", + ". by c: replace u=uniform()\n", + "(1,000 real changes made)\n", + "\n", + ". by c: replace pos=u[1]<.5 //cluster level rademacher indicator\n", + "(1000 real changes made)\n", + "\n", + ". gen radem = 2*pos - 1\n", + "\n", + ". \n", + ". qui replace ernew=(2*pos-1)*er //weighted residual\n", + "\n", + ". qui replace eunew=(2*pos-1)*eu //weighted residual\n", + "\n", + ". \n", + ". *Score bootstrap LM -- Rademacher\n", + ". mata: er=st_data(.,\"ernew\")\n", + "\n", + ". mata: Sr=colsum(X:*er)\n", + "\n", + ". mata: OPGrcent=clustOPG(X:*er,L)\n", + "\n", + ". mata: lm=Sr*Ar'*invsym(Ar*(OPGrcent)*`dfk'*Ar')*Ar*Sr'\n", + "\n", + ". mata: st_numscalar(\"lm\",lm)\n", + "\n", + ". mat Ts[1,1]=lm //save bootstrap LM \n", + "\n", + ". \n", + ". \n", + ". *Score Wald -- Rademacher\n", + ". mata: eu=st_data(.,\"eunew\")\n", + "\n", + ". mata: Su=colsum(X:*eu)\n", + "\n", + ". mata: OPGucent=clustOPG(X:*eu,L)\n", + "\n", + ". mata: wald=Su*Au'*invsym(Au*OPGucent*`dfk'*Au')*Au*Su'\n", + "\n", + ". mata: st_numscalar(\"wald\",wald)\n", + "\n", + ". mat Ts[1,2]=wald //save bootstrap Wald\n", + "\n", + ". \n", + ". *Score bootstrap Wald combining restricted and unrestricted scores -- Rademac\n", + "> her\n", + ". mata: wald_r=Sr*Ar'*invsym(Au*OPGucent*`dfk'*Au')*Ar*Sr'\n", + "\n", + ". mata: st_numscalar(\"wald_r\",wald)\n", + "\n", + ". mat Ts[1,3]=wald //save bootstrap Wald\n", + "\n", + ". \n", + ". \n", + ". \n" + ] + } + ], + "source": [ + "%%stata\n", + "\n", + "****************************************************\n", + "* Code for Table 2 of Kline and Santos (2011) *\n", + "* This file computes Analytical Wald and LM tests *\n", + "* along with Score bootstrapped tests. \t *\n", + "* Some other results not reported in the paper *\n", + "* are also included.\t\t\t\t *\t\n", + "****************************************************\n", + "\n", + "version 11.1\n", + "set seed 12345\n", + "\n", + "**mata subroutine to compute recentered clustered outer products**\n", + "**inputs a matrix S of scores and a cluster summing matrix L**\n", + "**outputs clustered outer product matrix**\n", + "**note this program assumes a balanced design**\n", + "**and proper sort order**\n", + "cap mata: mata drop clustOPG()\n", + "mata: \n", + "real matrix clustOPG(real matrix S,real matrix L){\n", + "real scalar N\n", + "real matrix Scent, Scentsum, OPGcent\n", + "\n", + "N=rows(S)\n", + "Scent=S - J(N,1,1)*mean(S)\n", + "Scentsum=L'*Scent\n", + "OPGcent=Scentsum'*Scentsum\n", + "return(OPGcent)\n", + "}\n", + "end\n", + "\n", + "\n", + "\n", + "/* cap prog drop sim_probit\n", + "prog def sim_probit, rclass\n", + "syntax [, c(integer 5) m(integer 50) f(integer 0) r(integer 199)] */\n", + "*Notes on syntax: \n", + "*c - # of clusters\n", + "*m - # of obs per cluster\n", + "*f - controls whether to use mixture regressor\n", + "*r - # of bootstrap reps per simulation\n", + "\n", + "local c 10\n", + "local m 100\n", + "local f 0\n", + "local r 1000\n", + "\n", + "**Generate data**\n", + "drop _all\n", + "set obs `c' //generate c clusters\n", + "\n", + "gen x=invnorm(uniform()) //covariate\n", + "gen mix=uniform()>.1 //contamination probability\n", + "\n", + "if `f'==1{\n", + "\tgen D=(mix*invnorm(uniform()) + (1-mix)*(2+3*invnorm(uniform())))/4 //regressor of interest\n", + "}\n", + "else{\n", + "\tgen D=(invnorm(uniform()))/4 //regressor of interest\n", + "}\n", + "\n", + "gen v=invnorm(uniform())\n", + "\n", + "\n", + "gen c=_n\t//generate cluster id\n", + "\n", + "expand `m'\t//make m obs per cluster\n", + "\n", + "replace x=(x+invnorm(uniform()))/4 //add some within cluster variation in x\n", + "\n", + "gen y=x+D+v/sqrt(2)+invnorm(uniform())/sqrt(2) //form outcome\n", + "replace y=y>0\n", + "sort c\n", + "\n", + "**Restricted**\n", + "constraint 1 D=1\n", + "glm y x D, constraint(1) fam(bin) link(probit)\n", + "predict pr\n", + "predict xbr, xb\n", + "gen phir=normalden(xbr)\n", + "gen er=(y-pr)*phir/(pr*(1-pr)) //quasi-residual\n", + "gen wr=phir^2/(pr*(1-pr)) //weights for hessian\n", + "gen one=1\n", + "\n", + "mat accum Hr=one x D [iw=wr], nocons //form hessian */\n", + "\n", + "\n", + "**Unrestricted**\n", + "probit y x D, cluster(c)\n", + "global b=_b[D]\n", + "local stdof=sqrt((`m'*`c'-3)/(`m'*`c'-1)) //inverse stata DOF correction\n", + "global W=((_b[D]-1)/(_se[D]*`stdof'))^2 //save analytical Wald\n", + "\n", + "predict pu\n", + "predict xbu, xb\n", + "gen phiu=normalden(xbu)\n", + "gen eu=(y-pu)*phiu/(pu*(1-pu)) //quasi-residual\n", + "gen wu=phiu^2/(pu*(1-pu)) //weights for hessian\n", + "\n", + "local dfk=(`c'/(`c'-1))\n", + "mata: L=I(`c')#J(`m',1,1) //matrix for summing across clusters\n", + "mata: X=st_data(.,(\"one\", \"x\", \"D\"))\n", + "mata: C=(0\\0\\1)\t\t//constraint matrix -- third coefficient is restricted\n", + "\n", + "mata: Hr=st_matrix(\"Hr\")\n", + "mata: Hrinv=invsym(Hr)\n", + "mata: Ar=C'*Hrinv\t\t//form A_n\n", + "\n", + "\n", + "**Prepare influence function wald\n", + "mat accum Hu=one x D [iw=wu], nocons //form hessian\n", + "mata: Hu=st_matrix(\"Hu\")\n", + "mata: Huinv=invsym(Hu)\n", + "mata: Au=C'*Huinv\n", + "mata: eu=st_data(.,\"eu\")\n", + "mata: OPGucent=clustOPG(X:*eu,L)\n", + "mata: Vinvu=invsym(Au*OPGucent*`dfk'*Au') //compute inverse of variance of influence function\n", + "\n", + "\n", + "save \"sim_sample.dta\", replace\n", + "\n", + "mata: S = X:*eu\n", + "\n", + "**Bootstrap**\n", + "gen u=.\n", + "gen ystar=.\n", + "gen ernew=.\n", + "gen eunew=.\n", + "gen pos=.\n", + "\n", + "\n", + "mat Ts=J(1,6,.) //store bootstrap statistics\n", + "\n", + "by c: replace u=uniform()\n", + "by c: replace pos=u[1]<.5 //cluster level rademacher indicator\n", + "gen radem = 2*pos - 1\n", + "\n", + "qui replace ernew=(2*pos-1)*er //weighted residual\n", + "qui replace eunew=(2*pos-1)*eu //weighted residual\n", + "\n", + "*Score bootstrap LM -- Rademacher\n", + "mata: er=st_data(.,\"ernew\")\n", + "mata: Sr=colsum(X:*er)\n", + "mata: OPGrcent=clustOPG(X:*er,L)\n", + "mata: lm=Sr*Ar'*invsym(Ar*(OPGrcent)*`dfk'*Ar')*Ar*Sr'\n", + "mata: st_numscalar(\"lm\",lm)\n", + "mat Ts[1,1]=lm\t //save bootstrap LM \n", + "\n", + "\n", + "*Score Wald -- Rademacher\n", + "mata: eu=st_data(.,\"eunew\")\n", + "mata: Su=colsum(X:*eu)\n", + "mata: OPGucent=clustOPG(X:*eu,L)\n", + "mata: wald=Su*Au'*invsym(Au*OPGucent*`dfk'*Au')*Au*Su'\n", + "mata: st_numscalar(\"wald\",wald)\n", + "mat Ts[1,2]=wald\t //save bootstrap Wald\n", + "\n", + "*Score bootstrap Wald combining restricted and unrestricted scores -- Rademacher\n", + "mata: wald_r=Sr*Ar'*invsym(Au*OPGucent*`dfk'*Au')*Ar*Sr'\n", + "mata: st_numscalar(\"wald_r\",wald)\n", + "mat Ts[1,3]=wald\t //save bootstrap Wald\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "N = 1000\n", + "rng = np.random.default_rng(seed=1)\n", + "num_X = 2\n", + "\n", + "\n", + "# generates fake probit data for testing (Not used in favor of data generated from paper)\n", + "def probit_data(N, num_X):\n", + " # generate X and error\n", + " epsilon = rng.normal(size=N)\n", + " X = sm.add_constant(rng.normal(size=(N, num_X)))\n", + " beta = np.arange(1,num_X+2)\n", + " \n", + " true_y = X @ beta + epsilon\n", + " \n", + " y_star = np.where(true_y >0, 1,0)\n", + " \n", + " return y_star, true_y, X\n", + "\n", + "y_star, true_y, X = probit_data(1000, 3)\n", + "\n", + "clusters = rng.choice(range(100), size=1000)\n", + "\n", + "df = stata.pdataframe_from_data()\n", + "\n", + "p = Probit.from_formula(\"y ~ x + D\", data=df)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimization terminated successfully.\n", + " Current function value: 0.665335\n", + " Iterations 4\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
Probit Regression Results
Dep. Variable: y No. Observations: 1000
Model: Probit Df Residuals: 997
Method: MLE Df Model: 2
Date: Thu, 06 Jun 2024 Pseudo R-squ.: 0.04010
Time: 19:23:11 Log-Likelihood: -665.33
converged: True LL-Null: -693.13
Covariance Type: cluster LLR p-value: 8.494e-13
\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
coef std err z P>|z| [0.025 0.975]
Intercept 0.0358 0.308 0.116 0.908 -0.568 0.640
x 1.0579 0.396 2.670 0.008 0.281 1.835
D 0.0943 1.043 0.090 0.928 -1.950 2.139
" + ], + "text/latex": [ + "\\begin{center}\n", + "\\begin{tabular}{lclc}\n", + "\\toprule\n", + "\\textbf{Dep. Variable:} & y & \\textbf{ No. Observations: } & 1000 \\\\\n", + "\\textbf{Model:} & Probit & \\textbf{ Df Residuals: } & 997 \\\\\n", + "\\textbf{Method:} & MLE & \\textbf{ Df Model: } & 2 \\\\\n", + "\\textbf{Date:} & Thu, 06 Jun 2024 & \\textbf{ Pseudo R-squ.: } & 0.04010 \\\\\n", + "\\textbf{Time:} & 19:23:11 & \\textbf{ Log-Likelihood: } & -665.33 \\\\\n", + "\\textbf{converged:} & True & \\textbf{ LL-Null: } & -693.13 \\\\\n", + "\\textbf{Covariance Type:} & cluster & \\textbf{ LLR p-value: } & 8.494e-13 \\\\\n", + "\\bottomrule\n", + "\\end{tabular}\n", + "\\begin{tabular}{lcccccc}\n", + " & \\textbf{coef} & \\textbf{std err} & \\textbf{z} & \\textbf{P$> |$z$|$} & \\textbf{[0.025} & \\textbf{0.975]} \\\\\n", + "\\midrule\n", + "\\textbf{Intercept} & 0.0358 & 0.308 & 0.116 & 0.908 & -0.568 & 0.640 \\\\\n", + "\\textbf{x} & 1.0579 & 0.396 & 2.670 & 0.008 & 0.281 & 1.835 \\\\\n", + "\\textbf{D} & 0.0943 & 1.043 & 0.090 & 0.928 & -1.950 & 2.139 \\\\\n", + "\\bottomrule\n", + "\\end{tabular}\n", + "%\\caption{Probit Regression Results}\n", + "\\end{center}" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " Probit Regression Results \n", + "==============================================================================\n", + "Dep. Variable: y No. Observations: 1000\n", + "Model: Probit Df Residuals: 997\n", + "Method: MLE Df Model: 2\n", + "Date: Thu, 06 Jun 2024 Pseudo R-squ.: 0.04010\n", + "Time: 19:23:11 Log-Likelihood: -665.33\n", + "converged: True LL-Null: -693.13\n", + "Covariance Type: cluster LLR p-value: 8.494e-13\n", + "==============================================================================\n", + " coef std err z P>|z| [0.025 0.975]\n", + "------------------------------------------------------------------------------\n", + "Intercept 0.0358 0.308 0.116 0.908 -0.568 0.640\n", + "x 1.0579 0.396 2.670 0.008 0.281 1.835\n", + "D 0.0943 1.043 0.090 0.928 -1.950 2.139\n", + "==============================================================================\n", + "\"\"\"" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p.fit(cov_type='cluster', cov_kwds={'groups' : df['c']}).summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "L = np.array(sfi.Mata.get(\"L\"))\n", + "S = np.array(sfi.Mata.get(\"S\"))\n", + "OPGucent = np.array(sfi.Mata.get(\"OPGucent\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimization terminated successfully.\n", + " Current function value: 0.665335\n", + " Iterations 4\n" + ] + }, + { + "data": { + "text/plain": [ + "Intercept 0.856059\n", + "x 7.202267\n", + "D 0.662808\n", + "dtype: float64" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p.fit().tvalues" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Union, TypeVar\n", + "import numpy.typing as npt\n", + "from statsmodels.base.model import Model\n", + "\n", + "ArrayLike = Union[npt.ArrayLike, pd.DataFrame, pd.Series]\n", + "\n", + "class IncorrectModelException(Exception):\n", + " pass\n", + "\n", + "class ScoreWildBootstrap:\n", + " \n", + " def __init__(self,\n", + " mod: Model, \n", + " clusters=Union[None, ArrayLike]) -> None:\n", + " \"\"\"Score wild bootstrap as in Kline and Santos (2012). Perturbs the score of using wild bootstrap weights and calculates wald statistics.\n", + " Optionally with restricted score or unrestricted scores.\n", + "\n", + " Args:\n", + " mod (Model): A statsmodels model that has a `score_obs` and `hessian` method.\n", + " clusters (ArrayLike|None, optional): A dataframe or array of shape Nx1 that gives the cluster membership of each of the N observations. Defaults to Union[None, ArrayLike].\n", + " \"\"\" \n", + " \n", + " if not hasattr(mod, \"score_obs\"):\n", + " raise IncorrectModelException(f\"{mod} does not implement `score_obs`.\")\n", + " if not hasattr(mod, \"hessian\"):\n", + " raise IncorrectModelException(\"{mod} does not implement `hessian`.\")\n", + " \n", + " self.mod = mod\n", + " \n", + " self.fitted_mod = self.mod.fit(cov_type='cluster', cov_kwds = {'groups' : clusters})\n", + " \n", + " self.beta_hat = self.fitted_mod.params\n", + " \n", + " if clusters is not None:\n", + " self.cluster_ids, self.cluster_obs = np.unique(clusters, return_counts=True)\n", + " self.clusters = clusters\n", + " self.num_clusters = len(self.cluster_ids)\n", + " else:\n", + " self.clusters = np.ones(self.mod.endog.shape[0])\n", + " self.cluster_ids = np.array([1])\n", + " self.num_clusters = 1\n", + " \n", + " def get_analytical_wald(self) -> pd.Series:\n", + " \"\"\"Calculates cluster-robust analytical wald test for each parameter.\n", + " \"\"\" \n", + " return self.fitted_mod.tvalues**2\n", + "\n", + " def wald_test(self, W: ArrayLike, restricted_residuals: bool =False, \n", + " R: ArrayLike =None, r: Union[None, int, float]=None) -> Union[ArrayLike, float]:\n", + " \"\"\"Calculates Wald statistic, given perturbation weights, scores and hessians from the model.\n", + "\n", + " Args:\n", + " W (ArrayLike): Weight vector used to perturb observation score\n", + " restricted_residuals (bool, optional): Whether to construct the test with the restricted score. Defaults to False.\n", + " \\begin{aligned}\n", + " & T_{n, 1}^{\\star s} \\equiv\\left(R H_n^{-1} S_n^{\\star}\\left(\\hat{\\beta}_u\\right)\\right)^{\\prime}\\left(R H_n^{-1} \\sum_n^{\\star s}\\left(\\hat{\\beta}_u\\right) H_n^{-1} R^{\\prime}\\right)^{-1}\\left(R H_n^{-1} S_n^{\\star}\\left(\\hat{\\beta}_u\\right)\\right) \\\\\n", + " & T_{n, 2}^{\\star s} \\equiv\\left(R H_n^{-1} S_n^{\\star}\\left(\\hat{\\beta}_r\\right)\\right)^{\\prime}\\left(R H_n^{-1} \\sum_n^{\\star s}\\left(\\hat{\\beta}_u\\right) H_n^{-1} R^{\\prime}\\right)^{-1}\\left(R H_n^{-1} S_n^{\\star}\\left(\\hat{\\beta}_r\\right)\\right)\n", + " \\end{aligned}\n", + " See Equations 50 and 51 from Kline and Santos (2012).\n", + " R (ArrayLike, optional): Restriction matrix for test. Defaults to None.\n", + " r (Union[None, int, float], optional): test restriction. Defaults to None.\n", + "\n", + " Returns:\n", + " Union[ArrayLike, float]: A Wald Statistic based on the perturbed weights.\n", + " \"\"\" \n", + " \n", + " \n", + " hessian_inv = -np.linalg.inv(self.mod.hessian(self.beta_hat))\n", + " \n", + " if restricted_residuals: # if we use the restricted score in the test\n", + " if r is None:\n", + " raise ValueError(\"Must specify coefficient constraint to restrict residuals\")\n", + " score_beta = self.beta_hat.copy()\n", + " score_beta[np.where(R==1)[0]] = 1 \n", + " else:\n", + " score_beta = self.beta_hat\n", + " \n", + " # make array of weights divided by cluster membership\n", + " W_Jn = np.select([self.clusters == c for c in self.cluster_ids], W.flatten())[:,np.newaxis]\n", + " # divide weights by observations per clusters \n", + " perturbed_score_obs = self.mod.score_obs(self.beta_hat) * W_Jn\n", + " \n", + " # multiply by cluster membership\n", + " L = pd.get_dummies(self.clusters).astype(int).values\n", + " \n", + " perturbed_score_obs_mean = (((L/(L.sum(axis=0)-1)).T @ perturbed_score_obs)).mean(axis=0)\n", + " \n", + " demeaned_perturbed_score_obs = perturbed_score_obs - perturbed_score_obs_mean \n", + " \n", + " demeaned_perturbed_score_obs_L = L.T @ demeaned_perturbed_score_obs\n", + " \n", + " perturbed_score = (self.mod.score_obs(score_beta) * W_Jn).sum(axis=0)\n", + " \n", + " # equation 50 from the paper\n", + " bread = R @ hessian_inv @ perturbed_score\n", + " \n", + " opg_centered = demeaned_perturbed_score_obs_L.T @ demeaned_perturbed_score_obs_L\n", + " \n", + " meat = R @ hessian_inv @ opg_centered @ hessian_inv @ R.T\n", + " \n", + " if isinstance(bread, np.ndarray):\n", + " meat_inv = np.linalg.inv(meat)\n", + " return bread.T @ meat_inv @ bread\n", + " else:\n", + " return (bread)**2/meat \n", + " \n", + " def bootstrap(self, R: ArrayLike, \n", + " W: Union[ArrayLike,None]=None, \n", + " r: Union[int, float, None]=None, \n", + " n_jobs: int = -1, \n", + " rng: Union[np.random.Generator, None] = None, \n", + " seed: Union[int, None] = None, \n", + " bootstrap_num: int =100, \n", + " restricted_residuals: bool=False, \n", + " full_enumeration: bool=False) -> ArrayLike:\n", + " \"\"\"Bootstraps Wald Statistics by generating weights and continuously perturbing the score\n", + "\n", + " Args:\n", + " R (ArrayLike, optional): Restriction Matrix. Defaults to None.\n", + " r (Union[int, float], optional): restriction. Defaults to None.\n", + " W (ArrayLike, optional): Weights vector. If not specified, Rademacher weights will be generated. Defaults to None.\n", + " n_jobs (int, optional): Number of jobs to dispath for parallel processing. Defaults to -1, or all cores.\n", + " rng (Union[np.random.Generator, None], optional): random generator that can be used for reproducibility. Defaults to None.\n", + " seed (Union[int, None], optional): seed for random generator. Defaults to None.\n", + " bootstrap_num (int, optional): number of bootstraps to perform. Defaults to 100.\n", + " restricted_residuals (bool, optional): Whether to use restricted score in constructing Wald. Defaults to False.\n", + " full_enumeration (bool, optional): Whether to generate the full set of random weight possibilities. Defaults to False.\n", + "\n", + " Returns:\n", + " ArrayLike: A `bootstrap_num` x 1 matrix.\n", + " \"\"\" \n", + " \n", + " def bootstrapper(restricted_residuals, R, r, full_enumeration, rng, W):\n", + " if rng is None:\n", + " rng=np.random.RandomState(seed)\n", + "\n", + " if W is None:\n", + " W = draw_weights('rademacher',\n", + " full_enumeration=full_enumeration,\n", + " N_G_bootcluster=self.num_clusters,\n", + " boot_iter=1,\n", + " rng=rng)[0]\n", + " \n", + " return self.wald_test(W = W, restricted_residuals=restricted_residuals, R=R, r=r)\n", + " \n", + " walds = Parallel(n_jobs=n_jobs, backend='loky')(delayed(bootstrapper)(restricted_residuals=restricted_residuals, \n", + " R=R, r=r, full_enumeration=full_enumeration, rng=rng, W=W) for b in range(bootstrap_num))\n", + " \n", + " return np.array(walds)\n", + " \n", + " \n", + " def get_wald_boot(self, n_jobs: int =-1, \n", + " bootstrap_num: int =100, \n", + " restricted_residuals: bool=False, \n", + " full_enumeration: bool=False, \n", + " rng: Union[np.random.Generator, None] =None, \n", + " seed: Union[int, None]=None,\n", + " W= Union[ArrayLike,None]) -> ArrayLike:\n", + " \"\"\"Generates Wald Statistics for each parameter separately.\n", + "\n", + " Args:\n", + " n_jobs (int): Number of jobs to dispath for parallel processing. Defaults to -1, or all cores.\n", + " bootstrap_num (int, optional): number of bootstraps to perform. Defaults to 100.\n", + " restricted_residuals (bool, optional): Whether to use restricted score in constructing Wald. Defaults to False.\n", + " full_enumeration (bool, optional): Whether to generate the full set of random weight possibilities. Defaults to False.\n", + " rng (Union[np.random.Generator, None], optional): random generator that can be used for reproducibility. Defaults to None.\n", + " seed (Union[int, None], optional): seed for random generator. Defaults to None.\n", + " W ( Union[ArrayLike,None], optional): Weights vector. If not specified, Rademacher weights will be generated. Defaults to None.\n", + "\n", + " Returns:\n", + " ArrayLike: A `bootstrap_num` x k matrix where k is the number of parameters.\n", + " \"\"\" \n", + " \n", + " # generate restriction vectors for each variable for beta=0\n", + " R = np.identity(self.beta_hat.shape[0])\n", + " \n", + " wald_boot = np.zeros(shape=(bootstrap_num, self.beta_hat.shape[0]))\n", + " \n", + " for i, R_vec in enumerate(R):\n", + " wald_boot[:, i] = self.bootstrap(R = R_vec, r = 0, n_jobs=n_jobs,\n", + " rng=rng, seed=seed, bootstrap_num=bootstrap_num,\n", + " restricted_residuals=restricted_residuals,\n", + " full_enumeration=full_enumeration,\n", + " W=None)\n", + " \n", + " return wald_boot\n", + " \n", + " \n", + " def get_pvalue(self, pval_type: str='two-tailed', **kwargs) -> ArrayLike:\n", + " \"\"\"Generates p-values by comparing analytical wald statistics with boostrapped, perturbed Wald statistics\n", + "\n", + " Args:\n", + " pval_type (str, optional): The type of p-value to generate. Defaults to 'two-tailed'.\n", + " kwargs can are used for specifying other options for `get_wald_boot`.\n", + "\n", + " Returns:\n", + " ArrayLike: An array of p-values for each parameter\n", + " \"\"\" \n", + " \n", + " wald_boot = self.get_wald_boot(**kwargs)\n", + " \n", + " analytical_wald = self.get_analytical_wald()\n", + " \n", + " if pval_type == \"two-tailed\":\n", + " pvalue = np.where(wald_boot > analytical_wald.values, 1, 0).mean(axis=0)\n", + " elif pval_type == \"equal-tailed\":\n", + " pl = np.where(analytical_wald.values < wald_boot, 1,0).mean(axis=0)\n", + " ph = np.where(analytical_wald.values > wald_boot, 1,0).mean(axis=0)\n", + " pvalue = 2 * min(pl, ph)\n", + " elif pval_type == \">\":\n", + " pvalue = np.where(analytical_wald.values < wald_boot,1,0).mean(axis=0)\n", + " else:\n", + " pvalue = np.where(analytical_wald.values > wald_boot,1,0).mean(axis=0)\n", + " \n", + " return pvalue\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimization terminated successfully.\n", + " Current function value: 0.665335\n", + " Iterations 4\n" + ] + }, + { + "data": { + "text/plain": [ + "array([0.909, 0.028, 0.931])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sb = ScoreWildBootstrap(p, clusters=df['c'])\n", + "\n", + "ts = sb.get_pvalue(n_jobs=1, bootstrap_num=1000)\n", + "\n", + "ts" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "def score_obs(self, params):\n", + " X= self.exog\n", + " y = self.endog\n", + " return (X.T*(y - X @ params)).T\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "sm.OLS.score_obs = score_obs" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "# linear_mod = sm.OLS(true_y, np.column_stack([X, rng.normal(size=1000)]))\n", + "\n", + "linear_mod = sm.OLS.from_formula(\"y ~ x +D\", data=df)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "| param | statistic | p-value |\n", + "|:----------|------------:|----------:|\n", + "| Intercept | 32.953 | 0.000 |\n", + "| x | 7.820 | 0.000 |\n", + "| D | 0.632 | 0.526 |\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/lordflaron/Documents/wildboottest/.venv/lib/python3.9/site-packages/tabulate/__init__.py:107: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison\n", + " (len(row) >= 1 and row[0] == SEPARATING_LINE)\n", + "/Users/lordflaron/Documents/wildboottest/.venv/lib/python3.9/site-packages/tabulate/__init__.py:108: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison\n", + " or (len(row) >= 2 and row[1] == SEPARATING_LINE)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
statisticp-value
param
Intercept[32.95279531027586]0.000
x[7.820060460734793]0.000
D[0.6320615764783465]0.526
\n", + "
" + ], + "text/plain": [ + " statistic p-value\n", + "param \n", + "Intercept [32.95279531027586] 0.000\n", + "x [7.820060460734793] 0.000\n", + "D [0.6320615764783465] 0.526" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wildboottest(linear_mod, B=1000, weights_type='rademacher',\n", + " impose_null=True, bootstrap_type='11')" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0. , 0.001, 0.868])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sb = ScoreWildBootstrap(linear_mod, clusters=df['c'])\n", + "\n", + "ts = sb.get_pvalue(n_jobs=1, bootstrap_num=1000)\n", + "\n", + "ts" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tests/test_score.do b/tests/test_score.do new file mode 100644 index 0000000..aec619f --- /dev/null +++ b/tests/test_score.do @@ -0,0 +1,163 @@ + +**************************************************** +* Code for Table 2 of Kline and Santos (2011) * +* This file computes Analytical Wald and LM tests * +* along with Score bootstrapped tests. * +* Some other results not reported in the paper * +* are also included. * +**************************************************** + +version 11.1 +set seed 12345 + +**mata subroutine to compute recentered clustered outer products** +**inputs a matrix S of scores and a cluster summing matrix L** +**outputs clustered outer product matrix** +**note this program assumes a balanced design** +**and proper sort order** +cap mata: mata drop clustOPG() +mata: +real matrix clustOPG(real matrix S,real matrix L){ +real scalar N +real matrix Scent, Scentsum, OPGcent + +N=rows(S) +Scent=S - J(N,1,1)*mean(S) +Scentsum=L'*Scent +OPGcent=Scentsum'*Scentsum +return(OPGcent) +} +end + + + +/* cap prog drop sim_probit +prog def sim_probit, rclass +syntax [, c(integer 5) m(integer 50) f(integer 0) r(integer 199)] */ +*Notes on syntax: +*c - # of clusters +*m - # of obs per cluster +*f - controls whether to use mixture regressor +*r - # of bootstrap reps per simulation + +local c 10 +local m 100 +local f 0 +local r 1000 + +**Generate data** +drop _all +set obs `c' //generate c clusters + +gen x=invnorm(uniform()) //covariate +gen mix=uniform()>.1 //contamination probability + +if `f'==1{ + gen D=(mix*invnorm(uniform()) + (1-mix)*(2+3*invnorm(uniform())))/4 //regressor of interest +} +else{ + gen D=(invnorm(uniform()))/4 //regressor of interest +} + +gen v=invnorm(uniform()) + + +gen c=_n //generate cluster id + +expand `m' //make m obs per cluster + +replace x=(x+invnorm(uniform()))/4 //add some within cluster variation in x + +gen y=x+D+v/sqrt(2)+invnorm(uniform())/sqrt(2) //form outcome +replace y=y>0 +sort c + +**Restricted** +constraint 1 D=1 +glm y x D, constraint(1) fam(bin) link(probit) +predict pr +predict xbr, xb +gen phir=normalden(xbr) +gen er=(y-pr)*phir/(pr*(1-pr)) //quasi-residual +gen wr=phir^2/(pr*(1-pr)) //weights for hessian +gen one=1 + +mat accum Hr=one x D [iw=wr], nocons //form hessian */ + + +**Unrestricted** +probit y x D, cluster(c) +global b=_b[D] +local stdof=sqrt((`m'*`c'-3)/(`m'*`c'-1)) //inverse stata DOF correction +global W=((_b[D]-1)/(_se[D]*`stdof'))^2 //save analytical Wald + +predict pu +predict xbu, xb +gen phiu=normalden(xbu) +gen eu=(y-pu)*phiu/(pu*(1-pu)) //quasi-residual +gen wu=phiu^2/(pu*(1-pu)) //weights for hessian + +global dfk=(`c'/(`c'-1)) +mata: L=I(`c')#J(`m',1,1) //matrix for summing across clusters +mata: X=st_data(.,("one", "x", "D")) +mata: C=(0\0\1) //constraint matrix -- third coefficient is restricted + +mata: Hr=st_matrix("Hr") +mata: Hrinv=invsym(Hr) +mata: Ar=C'*Hrinv //form A_n + + +**Prepare influence function wald +mat accum Hu=one x D [iw=wu], nocons //form hessian +mata: Hu=st_matrix("Hu") +mata: Huinv=invsym(Hu) +mata: Au=C'*Huinv +mata: eu=st_data(.,"eu") +mata: OPGucent=clustOPG(X:*eu,L) +mata: Vinvu=invsym(Au*OPGucent*$dfk*Au') //compute inverse of variance of influence function + + +save "sim_sample.dta", replace + +mata: S = X:*eu + +**Bootstrap** +gen u=. +gen ystar=. +gen ernew=. +gen eunew=. +gen pos=. + + +mat Ts=J(1,6,.) //store bootstrap statistics + +by c: replace u=uniform() +by c: replace pos=u[1]<.5 //cluster level rademacher indicator +gen radem = 2*pos - 1 + +qui replace ernew=(2*pos-1)*er //weighted residual +qui replace eunew=(2*pos-1)*eu //weighted residual + +*Score bootstrap LM -- Rademacher +mata: er=st_data(.,"ernew") +mata: Sr=colsum(X:*er) +mata: OPGrcent=clustOPG(X:*er,L) +mata: lm=Sr*Ar'*invsym(Ar*(OPGrcent)*$dfk*Ar')*Ar*Sr' +mata: st_numscalar("lm",lm) +mat Ts[1,1]=lm //save bootstrap LM + + +*Score Wald -- Rademacher +mata: eu=st_data(.,"eunew") +mata: Su=colsum(X:*eu) +mata: OPGucent=clustOPG(X:*eu,L) +mata: wald=Su*Au'*invsym(Au*OPGucent*$dfk*Au')*Au*Su' +mata: st_numscalar("wald",wald) +mat Ts[1,2]=wald //save bootstrap Wald + +*Score bootstrap Wald combining restricted and unrestricted scores -- Rademacher +mata: wald_r=Sr*Ar'*invsym(Au*OPGucent*$dfk*Au')*Ar*Sr' +mata: st_numscalar("wald_r",wald) +mat Ts[1,3]=wald //save bootstrap Wald + +