diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a51c03..08d8670 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,8 +55,7 @@ jobs: - name: Run tests run: | source .venv/bin/activate - pytest tests/ - pytest tests/ --ignore-glob='test_graph_vis.py' .--cov=./ --cov-report=xml + pytest tests/ --ignore=tests/test_graph_vis.py --cov-report=xml - name: Upload coverage data to coveralls.io run: | python -m pip install coveralls==2.2 diff --git a/examples/Animal_Science/model_data_path.py b/examples/Animal_Science/model_data_path.py index 18d4c1a..35e117e 100644 --- a/examples/Animal_Science/model_data_path.py +++ b/examples/Animal_Science/model_data_path.py @@ -12,6 +12,7 @@ def fit_model(): model = Lmer(formula='Weight ~ Time + (1|Litter) + (1|Pig) + (1|Time)', family="gaussian", data=df) + print(model.fit()) return model @@ -21,6 +22,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/Animal_Science/model_df.py b/examples/Animal_Science/model_df.py index 7e296d9..a9ec7c0 100644 --- a/examples/Animal_Science/model_df.py +++ b/examples/Animal_Science/model_df.py @@ -14,7 +14,8 @@ def fit_model(): df = pd.read_csv('/Users/emjun/Git/tisane/data.csv') # Make sure that the data path is correct - model = Lmer(formula='Weight ~ Time + (1|Litter) + (1|Pig) + (1|Time)', family="gaussian", data=df) + model = Lmer(formula='Weight ~ Time + (1|Pig) + (1|Time) + (1|Litter)', family="gaussian", data=df) + print(model.fit()) return model @@ -24,6 +25,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/Animal_Science/model_no_data.py b/examples/Animal_Science/model_no_data.py index 41ad872..22c9416 100644 --- a/examples/Animal_Science/model_no_data.py +++ b/examples/Animal_Science/model_no_data.py @@ -15,7 +15,8 @@ def fit_model(): # df = - model = Lmer(formula='Weight ~ Time + (1|Pig) + (1|Litter) + (1|Time)', family="gaussian", data=df) + model = Lmer(formula='Weight ~ Time + (1|Litter) + (1|Time) + (1|Pig)', family="gaussian", data=df) + print(model.fit()) return model @@ -25,6 +26,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/Exercise/tisane_generated_files/model_data_path.py b/examples/Exercise/tisane_generated_files/model_data_path.py index 9c3d3ac..e0b2f4a 100644 --- a/examples/Exercise/tisane_generated_files/model_data_path.py +++ b/examples/Exercise/tisane_generated_files/model_data_path.py @@ -13,6 +13,7 @@ def fit_model(): model = smf.glm(formula='endurance ~ age + exercise', data=df, family=sm.families.Gaussian(sm.families.links.identity())) + res = model.fit() print(res.summary()) return model @@ -26,6 +27,7 @@ def show_model_diagnostics(model): res = model.fit() plt.clf() plt.grid(True) + plt.axhline(y=0, color='r', linestyle='-') plt.plot(res.predict(linear=True), res.resid_pearson, 'o') plt.xlabel("Linear predictor") plt.ylabel("Residual") diff --git a/examples/Exercise/tisane_generated_files/model_df.py b/examples/Exercise/tisane_generated_files/model_df.py index 9208c00..1bffbc9 100644 --- a/examples/Exercise/tisane_generated_files/model_df.py +++ b/examples/Exercise/tisane_generated_files/model_df.py @@ -16,6 +16,7 @@ def fit_model(): model = smf.glm(formula='endurance ~ age + exercise', data=df, family=sm.families.Gaussian(sm.families.links.identity())) + res = model.fit() print(res.summary()) return model @@ -29,6 +30,7 @@ def show_model_diagnostics(model): res = model.fit() plt.clf() plt.grid(True) + plt.axhline(y=0, color='r', linestyle='-') plt.plot(res.predict(linear=True), res.resid_pearson, 'o') plt.xlabel("Linear predictor") plt.ylabel("Residual") diff --git a/examples/Exercise/tisane_generated_files/model_no_data.py b/examples/Exercise/tisane_generated_files/model_no_data.py index 7062258..8713162 100644 --- a/examples/Exercise/tisane_generated_files/model_no_data.py +++ b/examples/Exercise/tisane_generated_files/model_no_data.py @@ -17,6 +17,7 @@ def fit_model(): model = smf.glm(formula='endurance ~ age + exercise', data=df, family=sm.families.Gaussian(sm.families.links.identity())) + res = model.fit() print(res.summary()) return model @@ -30,6 +31,7 @@ def show_model_diagnostics(model): res = model.fit() plt.clf() plt.grid(True) + plt.axhline(y=0, color='r', linestyle='-') plt.plot(res.predict(linear=True), res.resid_pearson, 'o') plt.xlabel("Linear predictor") plt.ylabel("Residual") diff --git a/examples/Group_Exercise/tisane_generated_files/model_data_path.py b/examples/Group_Exercise/tisane_generated_files/model_data_path.py index 69bbfab..bdcf90d 100644 --- a/examples/Group_Exercise/tisane_generated_files/model_data_path.py +++ b/examples/Group_Exercise/tisane_generated_files/model_data_path.py @@ -12,6 +12,7 @@ def fit_model(): model = Lmer(formula='pounds_lost ~ motivation + treatment + (1|group)', family="gaussian", data=df) + print(model.fit()) return model @@ -21,6 +22,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/Group_Exercise/tisane_generated_files/model_df.py b/examples/Group_Exercise/tisane_generated_files/model_df.py index e80b343..8c27981 100644 --- a/examples/Group_Exercise/tisane_generated_files/model_df.py +++ b/examples/Group_Exercise/tisane_generated_files/model_df.py @@ -15,6 +15,7 @@ def fit_model(): model = Lmer(formula='pounds_lost ~ motivation + treatment + (1|group)', family="gaussian", data=df) + print(model.fit()) return model @@ -24,6 +25,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/Group_Exercise/tisane_generated_files/model_no_data.py b/examples/Group_Exercise/tisane_generated_files/model_no_data.py index 0f7a7d8..7bab9f6 100644 --- a/examples/Group_Exercise/tisane_generated_files/model_no_data.py +++ b/examples/Group_Exercise/tisane_generated_files/model_no_data.py @@ -16,6 +16,7 @@ def fit_model(): model = Lmer(formula='pounds_lost ~ motivation + treatment + (1|group)', family="gaussian", data=df) + print(model.fit()) return model @@ -25,6 +26,7 @@ def fit_model(): # Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html def show_model_diagnostics(model): + plt.axhline(y=0, color='r', linestyle='-') plt.scatter(model.fits, model.residuals) plt.title("Fitted values vs. Residuals") plt.xlabel("fitted values") diff --git a/examples/readme_dot_graph.png b/examples/readme_dot_graph.png index 9b1ad69..a1707db 100644 Binary files a/examples/readme_dot_graph.png and b/examples/readme_dot_graph.png differ diff --git a/examples/test_more_complex.png b/examples/test_more_complex.png index 729059d..830d297 100644 Binary files a/examples/test_more_complex.png and b/examples/test_more_complex.png differ diff --git a/examples/test_units.png b/examples/test_units.png index a3b579c..7f2e461 100644 Binary files a/examples/test_units.png and b/examples/test_units.png differ diff --git a/poetry.lock b/poetry.lock index 278277a..7eb7229 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,6 +30,32 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "apscheduler" +version = "3.8.1" +description = "In-process task scheduler with Cron-like capabilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.dependencies] +pytz = "*" +six = ">=1.4.0" +tzlocal = ">=2.0,<3.0.0 || >=4.0.0" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["pytest (<6)", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + [[package]] name = "argcomplete" version = "1.12.3" @@ -141,6 +167,14 @@ soupsieve = ">1.2" html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "bidict" +version = "0.21.4" +description = "The bidirectional mapping library for Python." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "black" version = "20.8b1" @@ -176,6 +210,37 @@ packaging = "*" six = ">=1.9.0" webencodings = "*" +[[package]] +name = "boba" +version = "1.1.2" +description = "Author and execute multiverse analysis" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Click = ">=6.0" +dataclasses = ">=0.6" +pandas = ">=1.0.1" + +[[package]] +name = "boba-visualizer" +version = "1.1.1" +description = "Visualize multiverse outcomes" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +apscheduler = ">=3.7.0" +boba = ">=1.1.1" +Click = ">=7.0" +flask = ">=1.1.1" +flask-socketio = ">=5.0.0" +pandas = ">=1.0.1" +scikit-learn = ">=0.24.1" +scipy = ">=1.4.1" + [[package]] name = "brotli" version = "1.0.9" @@ -330,6 +395,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "dataclasses" +version = "0.6" +description = "A backport of the dataclasses module for Python 3.6" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "debugpy" version = "1.4.3" @@ -417,6 +490,18 @@ python-versions = "*" brotli = "*" flask = "*" +[[package]] +name = "flask-socketio" +version = "5.1.1" +description = "Socket.IO integration for Flask applications" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Flask = ">=0.9" +python-socketio = ">=5.0.2" + [[package]] name = "future" version = "0.18.2" @@ -439,7 +524,7 @@ numpy = ">=1.7,<1.19.4 || >1.19.4" [[package]] name = "h5py" -version = "3.4.0" +version = "3.3.0" description = "Read and write HDF5 files from Python" category = "main" optional = false @@ -1261,6 +1346,34 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-engineio" +version = "4.3.0" +description = "Engine.IO server and client for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "5.5.0" +description = "Socket.IO server and client for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +bidict = ">=0.21.0" +python-engineio = ">=4.3.0" + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + [[package]] name = "pytz" version = "2021.1" @@ -1269,6 +1382,18 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + [[package]] name = "pywin32" version = "301" @@ -1392,6 +1517,26 @@ numpy = ["pandas"] pandas = ["numpy", "pandas"] test = ["pytest"] +[[package]] +name = "scikit-learn" +version = "1.0.1" +description = "A set of python modules for machine learning and data mining" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +joblib = ">=0.11" +numpy = ">=1.14.6" +scipy = ">=1.1.0" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=2.2.3)", "pandas (>=0.25.0)", "memory-profiler (>=0.57.0)"] +docs = ["matplotlib (>=2.2.3)", "scikit-image (>=0.14.5)", "pandas (>=0.25.0)", "seaborn (>=0.9.0)", "memory-profiler (>=0.57.0)", "sphinx (>=4.0.1)", "sphinx-gallery (>=0.7.0)", "numpydoc (>=1.0.0)", "Pillow (>=7.1.2)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=2.2.3)", "scikit-image (>=0.14.5)", "pandas (>=0.25.0)", "seaborn (>=0.9.0)"] +tests = ["matplotlib (>=2.2.3)", "scikit-image (>=0.14.5)", "pandas (>=0.25.0)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "flake8 (>=3.8.2)", "black (>=21.6b0)", "mypy (>=0.770)", "pyamg (>=4.0.0)"] + [[package]] name = "scipy" version = "1.6.1" @@ -1634,6 +1779,14 @@ python-versions = ">= 3.5" [package.extras] test = ["pytest", "pathlib2"] +[[package]] +name = "threadpoolctl" +version = "3.0.0" +description = "threadpoolctl" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "toml" version = "0.10.2" @@ -1695,7 +1848,7 @@ python-versions = ">=2" [[package]] name = "tzlocal" -version = "3.0" +version = "4.1" description = "tzinfo object for the local timezone" category = "main" optional = false @@ -1703,9 +1856,11 @@ python-versions = ">=3.6" [package.dependencies] "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +pytz-deprecation-shim = "*" tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] @@ -1791,7 +1946,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7.11" -content-hash = "68863d7ef6290b0f7cf8d36bbc34cc1a15c20ac4789ff238d3e1b31f824108b9" +content-hash = "404c4277ff12bfa10736f13bb1144d8a33f2649daefa26f2845ed7dac2f4d1f4" [metadata.files] alabaster = [ @@ -1810,6 +1965,10 @@ appnope = [ {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] +apscheduler = [ + {file = "APScheduler-3.8.1-py2.py3-none-any.whl", hash = "sha256:c22cb14b411a31435eb2c530dfbbec948ac63015b517087c7978adb61b574865"}, + {file = "APScheduler-3.8.1.tar.gz", hash = "sha256:5cf344ebcfbdaa48ae178c029c055cec7bc7a4a47c21e315e4d1f08bd35f2355"}, +] argcomplete = [ {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, @@ -1869,6 +2028,10 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, ] +bidict = [ + {file = "bidict-0.21.4-py3-none-any.whl", hash = "sha256:3ac67daa353ecf853a1df9d3e924f005e729227a60a8dbada31a4c31aba7f654"}, + {file = "bidict-0.21.4.tar.gz", hash = "sha256:42c84ffbe6f8de898af6073b4be9ea7ccedcd78d3474aa844c54e49d5a079f6f"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -1876,6 +2039,14 @@ bleach = [ {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, ] +boba = [ + {file = "boba-1.1.2-py3-none-any.whl", hash = "sha256:8a0231dde4603fa535577e33d5af597fa33cca714c9b7d406a8e6274a6db7e35"}, + {file = "boba-1.1.2.tar.gz", hash = "sha256:9c17023fb56ecfb18d48390972dbabe20407819544bc82651fe4f7adeb174299"}, +] +boba-visualizer = [ + {file = "boba-visualizer-1.1.1.tar.gz", hash = "sha256:a711731b2fd182803f399d1787d2450d19de582bf518a1c237f0ee3d57d13d3f"}, + {file = "boba_visualizer-1.1.1-py3-none-any.whl", hash = "sha256:6726c10c8b042e6a5cbd9936f32267a71bfee93fc2d023bd9fb564cbe1cecddc"}, +] brotli = [ {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, @@ -1883,13 +2054,6 @@ brotli = [ {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, - {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, @@ -1898,31 +2062,23 @@ brotli = [ {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, ] cached-property = [ @@ -2069,6 +2225,10 @@ dash-html-components = [ dash-table = [ {file = "dash_table-4.12.0.tar.gz", hash = "sha256:4c99689a887bfd035278b14ecd5db70706023389e53735d5e3cccc416facdbe4"}, ] +dataclasses = [ + {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, + {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, +] debugpy = [ {file = "debugpy-1.4.3-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:88b17d7c2130968f75bdc706a33f46a8a6bb90f09512ea3bd984659d446ee4f4"}, {file = "debugpy-1.4.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ded60b402f83df46dee3f25ae5851809937176afdafd3fdbaab60b633b77cad"}, @@ -2117,6 +2277,10 @@ flask-compress = [ {file = "Flask-Compress-1.10.1.tar.gz", hash = "sha256:28352387efbbe772cfb307570019f81957a13ff718d994a9125fa705efb73680"}, {file = "Flask_Compress-1.10.1-py3-none-any.whl", hash = "sha256:a6c2d1ff51771e9b39d7a612754f4cb4e8af20cebe16b02fd19d98d8dd6966e5"}, ] +flask-socketio = [ + {file = "Flask-SocketIO-5.1.1.tar.gz", hash = "sha256:1efdaacc7a26e94f2b197a80079b1058f6aa644a6094c0a322349e2b9c41f6b1"}, + {file = "Flask_SocketIO-5.1.1-py3-none-any.whl", hash = "sha256:07e1899e3b4851978b2ac8642080156c6294f8d0fc5212b4e4bcca713830306a"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] @@ -2125,16 +2289,16 @@ h5 = [ {file = "h5-0.4.1.tar.gz", hash = "sha256:1d159fb680f56260aa249a413a4d98dca12d3d9bce3b9fddb334df97d38e894b"}, ] h5py = [ - {file = "h5py-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aa511bd05a9174c3008becdc93bd5785e254d34a6ab5f0425e6b2fbbc88afa6d"}, - {file = "h5py-3.4.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:708ddff49af12c01d77e0f9782bb1a0364d96459ec0d1f85d90baea6d203764b"}, - {file = "h5py-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a545f09074546f73305e0db6d36aaf1fb6ea2fcf1add2ce306b9c7f78e55a"}, - {file = "h5py-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b0f002f5f341afe7d3d7e15198e80d9021da24a4d182d88068d79bfc91fba86"}, - {file = "h5py-3.4.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:46917f20021dde02865572a5fd2bb620945f7b7cd268bdc8e3f5720c32b38140"}, - {file = "h5py-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:8e809149f95d9a3a33b1279bfbf894c78635a5497e8d5ac37420fa5ec0cf4f29"}, - {file = "h5py-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8745e5159830d7975a9cf38690455f22601509cda04de29b7e88b3fbdc747611"}, - {file = "h5py-3.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb4ce46095e3b16c872aaf62adad33f40039fecae04674eb62c035386affcb91"}, - {file = "h5py-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:1edf33e722d47c6eb3878d51173b23dd848939f006f41b498bafceff87fb4cbd"}, - {file = "h5py-3.4.0.tar.gz", hash = "sha256:ee1c683d91ab010d5e85cb61e8f9e7ee0d8eab545bf3dd50a9618f1d0e8f615e"}, + {file = "h5py-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f3bba8ffddd1fd2bf06127c5ff7b73f022cc1c8b7164355ddc760dc3f8570136"}, + {file = "h5py-3.3.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baef1a2cdef287a83e7f95ce9e0f4d762a9852fe7117b471063442c78b973695"}, + {file = "h5py-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8e09b682e4059c8cd259ddcc34bee35d639b9170105efeeae6ad195e7c1cea7a"}, + {file = "h5py-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:89d7e10409b62fed81c571e35798763cb8375442b98f8ebfc52ba41ac019e081"}, + {file = "h5py-3.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7ca7d23ebbdd59a4be9b4820de52fe67adc74e6a44d5084881305461765aac47"}, + {file = "h5py-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e0ea3330bf136f8213e43db67448994046ce501585dddc7ea4e8ceef0ef1600c"}, + {file = "h5py-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:13355234c004ff8bd819f7d3420188aa1936b17d7f8470d622974a373421b7a5"}, + {file = "h5py-3.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09e78cefdef0b7566ab66366c5c7d9984c7b23142245bd51b82b744ad1eebf65"}, + {file = "h5py-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:5e2f22e66a3fb1815405cfe5711670450c973b8552507c535a546a23a468af3d"}, + {file = "h5py-3.3.0.tar.gz", hash = "sha256:e0dac887d779929778b3cfd13309a939359cc9e74756fc09af7c527a82797186"}, ] idna = [ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, @@ -2288,22 +2452,12 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2312,21 +2466,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2336,9 +2483,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2672,10 +2816,22 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-engineio = [ + {file = "python-engineio-4.3.0.tar.gz", hash = "sha256:fed35eeacfa21f53f1fc05ef0cadd65a50780364da3a2be7650eb92f928fdb11"}, + {file = "python_engineio-4.3.0-py3-none-any.whl", hash = "sha256:ad06a975f7e14cb3bb7137cbf70fd883804484d29acd58004d1db1e2a7fc0ad3"}, +] +python-socketio = [ + {file = "python-socketio-5.5.0.tar.gz", hash = "sha256:ce972ea1b82aa1811fa10d30cf0d5c251b9a1558c3d66829b6fe70854bcccf0b"}, + {file = "python_socketio-5.5.0-py3-none-any.whl", hash = "sha256:ca28a0ff0ca5dd05ec5ba4ee2572fe06b96d6f0bc7df384d8b50fbbc06986134"}, +] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, +] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, @@ -2732,24 +2888,32 @@ pyzmq = [ {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f89468059ebc519a7acde1ee50b779019535db8dcf9b8c162ef669257fef7a93"}, {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea12133df25e3a6918718fbb9a510c6ee5d3fdd5a346320421aac3882f4feeea"}, {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c532fd68b93998aab92356be280deec5de8f8fe59cd28763d2cc8a58747b7f"}, + {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f907c7359ce8bf7f7e63c82f75ad0223384105f5126f313400b7e8004d9b33c3"}, + {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:902319cfe23366595d3fa769b5b751e6ee6750a0a64c5d9f757d624b2ac3519e"}, {file = "pyzmq-22.3.0-cp310-cp310-win32.whl", hash = "sha256:67db33bea0a29d03e6eeec55a8190e033318cee3cbc732ba8fd939617cbf762d"}, {file = "pyzmq-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:7661fc1d5cb73481cf710a1418a4e1e301ed7d5d924f91c67ba84b2a1b89defd"}, {file = "pyzmq-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79244b9e97948eaf38695f4b8e6fc63b14b78cc37f403c6642ba555517ac1268"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab888624ed68930442a3f3b0b921ad7439c51ba122dbc8c386e6487a658e4a4e"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18cd854b423fce44951c3a4d3e686bac8f1243d954f579e120a1714096637cc0"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de8df0684398bd74ad160afdc2a118ca28384ac6f5e234eb0508858d8d2d9364"}, + {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:62bcade20813796c426409a3e7423862d50ff0639f5a2a95be4b85b09a618666"}, + {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ea5a79e808baef98c48c884effce05c31a0698c1057de8fc1c688891043c1ce1"}, {file = "pyzmq-22.3.0-cp36-cp36m-win32.whl", hash = "sha256:3c1895c95be92600233e476fe283f042e71cf8f0b938aabf21b7aafa62a8dac9"}, {file = "pyzmq-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:851977788b9caa8ed011f5f643d3ee8653af02c5fc723fa350db5125abf2be7b"}, {file = "pyzmq-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4ebed0977f92320f6686c96e9e8dd29eed199eb8d066936bac991afc37cbb70"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42abddebe2c6a35180ca549fadc7228d23c1e1f76167c5ebc8a936b5804ea2df"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1e41b32d6f7f9c26bc731a8b529ff592f31fc8b6ef2be9fa74abd05c8a342d7"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:be4e0f229cf3a71f9ecd633566bd6f80d9fa6afaaff5489492be63fe459ef98c"}, + {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08c4e315a76ef26eb833511ebf3fa87d182152adf43dedee8d79f998a2162a0b"}, + {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:badb868fff14cfd0e200eaa845887b1011146a7d26d579aaa7f966c203736b92"}, {file = "pyzmq-22.3.0-cp37-cp37m-win32.whl", hash = "sha256:7c58f598d9fcc52772b89a92d72bf8829c12d09746a6d2c724c5b30076c1f11d"}, {file = "pyzmq-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b97502c16a5ec611cd52410bdfaab264997c627a46b0f98d3f666227fd1ea2d"}, {file = "pyzmq-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d728b08448e5ac3e4d886b165385a262883c34b84a7fe1166277fe675e1c197a"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:480b9931bfb08bf8b094edd4836271d4d6b44150da051547d8c7113bf947a8b0"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7dc09198e4073e6015d9a8ea093fc348d4e59de49382476940c3dd9ae156fba8"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ca6cd58f62a2751728016d40082008d3b3412a7f28ddfb4a2f0d3c130f69e74"}, + {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:468bd59a588e276961a918a3060948ae68f6ff5a7fa10bb2f9160c18fe341067"}, + {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c88fa7410e9fc471e0858638f403739ee869924dd8e4ae26748496466e27ac59"}, {file = "pyzmq-22.3.0-cp38-cp38-win32.whl", hash = "sha256:c0f84360dcca3481e8674393bdf931f9f10470988f87311b19d23cda869bb6b7"}, {file = "pyzmq-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f762442bab706fd874064ca218b33a1d8e40d4938e96c24dafd9b12e28017f45"}, {file = "pyzmq-22.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:954e73c9cd4d6ae319f1c936ad159072b6d356a92dcbbabfd6e6204b9a79d356"}, @@ -2757,6 +2921,8 @@ pyzmq = [ {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:acebba1a23fb9d72b42471c3771b6f2f18dcd46df77482612054bd45c07dfa36"}, {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf98fd7a6c8aaa08dbc699ffae33fd71175696d78028281bc7b832b26f00ca57"}, {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d072f7dfbdb184f0786d63bda26e8a0882041b1e393fbe98940395f7fab4c5e2"}, + {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:53f4fd13976789ffafedd4d46f954c7bb01146121812b72b4ddca286034df966"}, + {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1b5d457acbadcf8b27561deeaa386b0217f47626b29672fa7bd31deb6e91e1b"}, {file = "pyzmq-22.3.0-cp39-cp39-win32.whl", hash = "sha256:e6a02cf7271ee94674a44f4e62aa061d2d049001c844657740e156596298b70b"}, {file = "pyzmq-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3dcb5548ead4f1123851a5ced467791f6986d68c656bc63bfff1bf9e36671e2"}, {file = "pyzmq-22.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a4c9886d61d386b2b493377d980f502186cd71d501fffdba52bd2a0880cef4f"}, @@ -2829,6 +2995,33 @@ rpy2 = [ {file = "rpy2-3.4.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a86bb6a47df7454cbf72a4d98ef1a2a8eb65efbe1082265ba60978d8d421239a"}, {file = "rpy2-3.4.5.tar.gz", hash = "sha256:5d31a5ea43f5a59f6dec30faca87edb01fc9b8affa0beae96a99be923bd7dab3"}, ] +scikit-learn = [ + {file = "scikit-learn-1.0.1.tar.gz", hash = "sha256:ac2ca9dbb754d61cfe1c83ba8483498ef951d29b93ec09d6f002847f210a99da"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:116e05fd990d9b363fc29bd3699ec2117d7da9088f6ca9a90173b240c5a063f1"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd78a2442c948536f677e2744917c37cff014559648102038822c23863741c27"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32d941f12fd7e245f01da2b82943c5ce6f1133fa5375eb80caa51457532b3e7e"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb7214103f6c36c1371dd8c166897e3528264a28f2e2e42573ba8c61ed4d7142"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:46248cc6a8b72490f723c73ff2e65e62633d14cafe9d2df3a7b3f87d332a6f7e"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fecb5102f0a36c16c1361ec519a7bb0260776ef40e17393a81f530569c916a7b"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-win32.whl", hash = "sha256:02aee3b257617da0ec98dee9572b10523dc00c25b68c195ddf100c1a93b1854b"}, + {file = "scikit_learn-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:538f3a85c4980c7572f3e754f0ba8489363976ef3e7f6a94e8f1af5ae45f6f6a"}, + {file = "scikit_learn-1.0.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:59b1d6df8724003fa16b7365a3b43449ee152aa6e488dd7a19f933640bb2d7fb"}, + {file = "scikit_learn-1.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:515b227f01f569145dc9f86e56f4cea9f00a613fc4d074bbfc0a92ca00bff467"}, + {file = "scikit_learn-1.0.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fc75f81571137b39f9b31766e15a0e525331637e7fe8f8000a3fbfba7da3add9"}, + {file = "scikit_learn-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:648f4dbfdd0a1b45bf6e2e4afe3f431774c55dee05e2d28f8394d6648296f373"}, + {file = "scikit_learn-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53bb7c605427ab187869d7a05cd3f524a3015a90e351c1788fc3a662e7f92b69"}, + {file = "scikit_learn-1.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a800665527c1a63f7395a0baae3c89b0d97b54d2c23769c1c9879061bb80bc19"}, + {file = "scikit_learn-1.0.1-cp38-cp38-win32.whl", hash = "sha256:ee59da47e18b703f6de17d5d51b16ce086c50969d5a83db5217f0ae9372de232"}, + {file = "scikit_learn-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:ebbe4275556d3c02707bd93ae8b96d9651acd4165126e0ae64b336afa2a6dcb1"}, + {file = "scikit_learn-1.0.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:11a57405c1c3514227d0c6a0bee561c94cd1284b41e236f7a1d76b3975f77593"}, + {file = "scikit_learn-1.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a51fdbc116974d9715957366df73e5ec6f0a7a2afa017864c2e5f5834e6f494d"}, + {file = "scikit_learn-1.0.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:944f47b2d881b9d24aee40d643bfdc4bd2b6dc3d25b62964411c6d8882f940a1"}, + {file = "scikit_learn-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc60e0371e521995a6af2ef3f5d911568506124c272889b318b8b6e497251231"}, + {file = "scikit_learn-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ce4e3ddb6e6e9dcdb3e5ac7f0575dbaf56f79ce2b2edee55192b12b52df5be"}, + {file = "scikit_learn-1.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:059c5be0c0365321ddbcac7abf0db806fad8ecb64ee6c7cbcd58313c7d61634d"}, + {file = "scikit_learn-1.0.1-cp39-cp39-win32.whl", hash = "sha256:c6b9510fd2e1642314efb7aa951a0d05d963f3523e01c30b2dadde2395ebe6b4"}, + {file = "scikit_learn-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c604a813df8e7d6dfca3ae0db0a8fd7e5dff4ea9d94081ab263c81bf0b61ab4b"}, +] scipy = [ {file = "scipy-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a15a1f3fc0abff33e792d6049161b7795909b40b97c6cc2934ed54384017ab76"}, {file = "scipy-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e79570979ccdc3d165456dd62041d9556fb9733b86b4b6d818af7a0afc15f092"}, @@ -2959,6 +3152,10 @@ testpath = [ {file = "testpath-0.5.0-py3-none-any.whl", hash = "sha256:8044f9a0bab6567fc644a3593164e872543bb44225b0e24846e2c89237937589"}, {file = "testpath-0.5.0.tar.gz", hash = "sha256:1acf7a0bcd3004ae8357409fc33751e16d37ccc650921da1094a86581ad1e417"}, ] +threadpoolctl = [ + {file = "threadpoolctl-3.0.0-py3-none-any.whl", hash = "sha256:4fade5b3b48ae4b1c30f200b28f39180371104fccc642e039e0f2435ec8cc211"}, + {file = "threadpoolctl-3.0.0.tar.gz", hash = "sha256:d03115321233d0be715f0d3a5ad1d6c065fe425ddc2d671ca8e45e9fd5d7a52a"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -3056,8 +3253,8 @@ tzdata = [ {file = "tzdata-2021.1.tar.gz", hash = "sha256:e19c7351f887522a1ac739d21041e592ddde6dd1b764fdefa8f7b2b3551d3d38"}, ] tzlocal = [ - {file = "tzlocal-3.0-py3-none-any.whl", hash = "sha256:c736f2540713deb5938d789ca7c3fc25391e9a20803f05b60ec64987cf086559"}, - {file = "tzlocal-3.0.tar.gz", hash = "sha256:f4e6e36db50499e0d92f79b67361041f048e2609d166e93456b50746dc4aef12"}, + {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, + {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, ] unidecode = [ {file = "Unidecode-1.3.2-py3-none-any.whl", hash = "sha256:215fe33c9d1c889fa823ccb66df91b02524eb8cc8c9c80f9c5b8129754d27829"}, diff --git a/pyproject.toml b/pyproject.toml index 3abd298..358244b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ patsy = "^0.5.1" coverage = "^5.5" pytest-cov = "^2.11.1" h5 = "^0.4.1" -h5py = "^3.3.0" +h5py = "3.3.0" pymer4 = "^0.7.5" pytest = "^6.2.4" jupyter-dash = "^0.4.0" @@ -33,6 +33,8 @@ sphinx-autoapi = "^1.8.4" numpydoc = "^1.1.0" toml = "^0.10.2" ipykernel = "^6.3.1" +boba = "^1.1.2" +boba-visualizer = "^1.1.1" [tool.poetry.dev-dependencies] diff --git a/tests/generated_scripts/main_only.py b/tests/generated_scripts/main_only.py index 551f91b..a3ba489 100644 --- a/tests/generated_scripts/main_only.py +++ b/tests/generated_scripts/main_only.py @@ -17,6 +17,7 @@ def fit_model(): model = smf.glm(formula='Dependent_variable ~ Measure_0 + Measure_1', data=df, family=sm.families.Gaussian(sm.families.links.identity())) + res = model.fit() print(res.summary()) return model @@ -30,6 +31,7 @@ def show_model_diagnostics(model): res = model.fit() plt.clf() plt.grid(True) + plt.axhline(y=0, color='r', linestyle='-') plt.plot(res.predict(linear=True), res.resid_pearson, 'o') plt.xlabel("Linear predictor") plt.ylabel("Residual") diff --git a/tests/input_json_files/main_interaction.json b/tests/input_json_files/main_interaction.json new file mode 100644 index 0000000..8b11e35 --- /dev/null +++ b/tests/input_json_files/main_interaction.json @@ -0,0 +1,45 @@ +{ + "input": { + "query": { + "DV": "Weight", "IVs": ["Time"] + }, + "generated family, link functions": { + "GammaFamily": [ + "LogLink", + "InverseLink", + "IdentityLink" + ], + "GaussianFamily": [ + "LogitLink", + "InverseLink", + "ProbitLink", + "PowerLink", + "IdentityLink", + "LogLink", + "NegativeBinomialLink", + "CLogLogLink" + ], + "InverseGaussianFamily": [ + "LogLink", + "InverseLink", + "InverseSquaredLink", + "IdentityLink" + ], + "PoissonFamily": [ + "SquarerootLink", + "LogLink", + "IdentityLink" + ], + "TweedieFamily": [ + "PowerLink", + "LogLink" + ] + }, + "generated interaction effects": [], + "generated main effects": [ + "Time", + "Species" + ], + "generated random effects": {} + } +} \ No newline at end of file diff --git a/tests/input_json_files/main_only.json b/tests/input_json_files/main_only.json new file mode 100644 index 0000000..8269544 --- /dev/null +++ b/tests/input_json_files/main_only.json @@ -0,0 +1,44 @@ +{ + "input": { + "query": { + "DV": "Weight", "IVs": ["Time"] + }, + "generated family, link functions": { + "GammaFamily": [ + "LogLink", + "InverseLink", + "IdentityLink" + ], + "GaussianFamily": [ + "LogitLink", + "InverseLink", + "ProbitLink", + "PowerLink", + "IdentityLink", + "LogLink", + "NegativeBinomialLink", + "CLogLogLink" + ], + "InverseGaussianFamily": [ + "LogLink", + "InverseLink", + "InverseSquaredLink", + "IdentityLink" + ], + "PoissonFamily": [ + "SquarerootLink", + "LogLink", + "IdentityLink" + ], + "TweedieFamily": [ + "PowerLink", + "LogLink" + ] + }, + "generated interaction effects": [], + "generated main effects": [ + "Time" + ], + "generated random effects": {} + } +} \ No newline at end of file diff --git a/tests/output_decision_json_files/decisions_main_only.json b/tests/output_decision_json_files/decisions_main_only.json new file mode 100644 index 0000000..25ea886 --- /dev/null +++ b/tests/output_decision_json_files/decisions_main_only.json @@ -0,0 +1,115 @@ +{ + "decisions": [ + { + "options": ["Weight"], + "var": "dependent_variable" + }, + { + "options": [ + [], + [ + "Time" + ] + ], + "var": "main_effects" + }, + { + "options": [ + [] + ], + "var": "interaction_effects" + }, + { + "options": [ + [] + ], + "var": "random_effects" + }, + { + "options": [ + [ + "Gamma", + "log()" + ], + [ + "Gamma", + "inverse_power()" + ], + [ + "Gamma", + "identity()" + ], + [ + "Gaussian", + "logit()" + ], + [ + "Gaussian", + "inverse_power()" + ], + [ + "Gaussian", + "probit()" + ], + [ + "Gaussian", + "Power()" + ], + [ + "Gaussian", + "identity()" + ], + [ + "Gaussian", + "log()" + ], + [ + "Gaussian", + "NegativeBinomial()" + ], + [ + "Gaussian", + "cloglog()" + ], + [ + "InverseGaussian", + "log()" + ], + [ + "InverseGaussian", + "inverse_power()" + ], + [ + "InverseGaussian", + "inverse_squared()" + ], + [ + "InverseGaussian", + "identity()" + ], + [ + "Poisson", + "Power(power=.5)" + ], + [ + "Poisson", + "log()" + ], + [ + "Poisson", + "identity()" + ], + [ + "Tweedie", + "Power()" + ], + [ + "Tweedie", + "log()" + ] + ], + "var": "family_link_pair" + } + ], + "graph": [] +} \ No newline at end of file diff --git a/tests/output_template_files/template_main_only.py b/tests/output_template_files/template_main_only.py new file mode 100644 index 0000000..8b1b63c --- /dev/null +++ b/tests/output_template_files/template_main_only.py @@ -0,0 +1,51 @@ + +# Tisane inferred the following statistical model based on this query: {} + +import pandas as pd +import statsmodels.api as sm +import statsmodels.formula.api as smf +import matplotlib.pyplot as plt # for visualizing residual plots to diagnose model fit + +# --- (BOBA_CONFIG) +{"decisions": [{"options": ["Weight"], "var": "dependent_variable"}, {"options": [[], ["Time"]], "var": "main_effects"}, {"options": [[]], "var": "interaction_effects"}, {"options": [[]], "var": "random_effects"}, {"options": [["Gamma", "log()"], ["Gamma", "inverse_power()"], ["Gamma", "identity()"], ["Gaussian", "logit()"], ["Gaussian", "inverse_power()"], ["Gaussian", "probit()"], ["Gaussian", "Power()"], ["Gaussian", "identity()"], ["Gaussian", "log()"], ["Gaussian", "NegativeBinomial()"], ["Gaussian", "cloglog()"], ["InverseGaussian", "log()"], ["InverseGaussian", "inverse_power()"], ["InverseGaussian", "inverse_squared()"], ["InverseGaussian", "identity()"], ["Poisson", "Power(power=.5)"], ["Poisson", "log()"], ["Poisson", "identity()"], ["Tweedie", "Power()"], ["Tweedie", "log()"]], "var": "family_link_pair"}], "graph": []} +# --- (END) + +def fit_model(): + + df = pd.read_csv('data.csv') + + + ivs = list() + ivs.append({{main_effects}}) + ivs.append({{interaction_effects}}) + ivs.append({{random_effects}}) + ivs_formula = "+".join(ivs) + dv_formula = "{{dependent_variable}} ~ " + formula = dv_formula + ivs_formula + + family = {{family_link_pair}}[0] + link = {{family_link_pair}}[1] + eval(f"model = smf.glm(formula=formula, data=df, family=sm.families.{family}(sm.families.links.{link}))") + res = model.fit() + print(res.summary()) + return model + + +# What should you look for in the plot? +# If there is systematic bias in how residuals are distributed, you may want to try a new link or family function. You may also want to reconsider your conceptual and statistical models. +# Read more here: https://sscc.wisc.edu/sscc/pubs/RegressionDiagnostics.html +def show_model_diagnostics(model): + + res = model.fit() + plt.clf() + plt.grid(True) + plt.axhline(y=0, color='r', linestyle='-') + plt.plot(res.predict(linear=True), res.resid_pearson, 'o') + plt.xlabel("Linear predictor") + plt.ylabel("Residual") + plt.show() + + +if __name__ == "__main__": + model = fit_model() + show_model_diagnostics(model) diff --git a/tests/test_generate_code_helpers.py b/tests/test_generate_code_helpers.py index 2886880..e9249af 100644 --- a/tests/test_generate_code_helpers.py +++ b/tests/test_generate_code_helpers.py @@ -35,12 +35,14 @@ ### HELPERS to reduce redundancy across test cases model_template = """ model = smf.glm(formula={formula}, data=df, family=sm.families.{family_name}(sm.families.links.{link_obj})) +""" + +model_fit_template = """ res = model.fit() print(res.summary()) return model """ - def absolute_path(p: str) -> str: return os.path.join(os.path.dirname(os.path.abspath(__file__)), p) @@ -613,7 +615,8 @@ def test_generate_statsmodels_model(self): link_obj = "Power(power=.5)" reference_code = model_template.format( formula=formula, family_name=family_name, link_obj=link_obj - ) + ) + model_fit_template + self.assertEqual(code, reference_code) # def test_generate_statsmodels_code(self): diff --git a/tests/test_generate_multiverse_code.py b/tests/test_generate_multiverse_code.py new file mode 100644 index 0000000..1f89822 --- /dev/null +++ b/tests/test_generate_multiverse_code.py @@ -0,0 +1,64 @@ +""" +Tests methods called to generate multiverse code +""" +from tisane.family import AbstractFamily, AbstractLink +from tisane.main import ( + check_design_ivs, + check_design_dv, + construct_statistical_model, + infer_family_functions, + infer_link_functions, + infer_main_effects_with_explanations, + infer_interaction_effects_with_explanations, + infer_random_effects_with_explanations, +) +from tisane.code_generator import ( + generate_statsmodels_formula, + generate_statsmodels_family, + generate_statsmodels_link, + generate_statsmodels_model, + generate_pymer4_formula, +) +import tisane as ts +import pandas as pd +from typing import Dict, Set +from pathlib import Path +import os +import unittest + +test_data_repo_name = "output_json_files/" +test_script_repo_name = "output_scripts/" +dir = os.path.dirname(__file__) +data_dir = os.path.join(dir, test_data_repo_name) +script_dir = os.path.join(dir, test_script_repo_name) + +### HELPERS to reduce redundancy across test cases +model_template = """ + model = smf.glm(formula={formula}, data=df, family=sm.families.{family_name}(sm.families.links.{link_obj})) + res = model.fit() + print(res.summary()) + return model +""" + + +def absolute_path(p: str) -> str: + return os.path.join(os.path.dirname(os.path.abspath(__file__)), p) + + +def get_family_link_paired_candidates( + design: ts.Design, +) -> Dict[AbstractFamily, Set[AbstractLink]]: + family_candidates = infer_family_functions(query=design) + family_link_paired = dict() + for f in family_candidates: + l = infer_link_functions(query=design, family=f) + # Add Family: Link options + assert f not in family_link_paired.keys() + family_link_paired[f] = l + + return family_link_paired + + +class GenerateMultiverseCodeHelpersTest(unittest.TestCase): + def test_generate_statsmodels_formula_template(self): + u0 = ts.Unit("Unit") \ No newline at end of file diff --git a/tests/test_generate_multiverse_code_helpers.py b/tests/test_generate_multiverse_code_helpers.py new file mode 100644 index 0000000..ee5f29c --- /dev/null +++ b/tests/test_generate_multiverse_code_helpers.py @@ -0,0 +1,180 @@ +## TODO: Check that the Family - link pairs make sense (this might be covered earlier in the pipeline) + +from tisane.multiverse_code_generator import construct_all_main_options, construct_all_interaction_options, construct_all_random_options, construct_all_family_link_options, generate_boba_config_from_decisions, generate_multiverse_decisions, generate_template_code, boba_config_start, boba_config_end + +import os +import unittest +import json + +test_input_repo_name = "input_json_files/" +test_output_decision_repo_name = "output_decision_json_files/" +test_output_template_repo_name = "output_template_files/" +dir = os.path.dirname(__file__) +input_dir = os.path.join(dir, test_input_repo_name) +output_decision_dir = os.path.join(dir, test_output_decision_repo_name) +output_template_dir = os.path.join(dir, test_output_template_repo_name) + +class MultiverseCodeHelpers(unittest.TestCase): + + def test_construct_all_formulae_main_only(self): + main_only_file = "main_only.json" + + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + combined_dict = json.loads(file_data) + + + def test_construct_all_main_options_main_only(self): + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + combined_dict = json.loads(file_data) + + main_options = list(construct_all_main_options(combined_dict=combined_dict)) + self.assertEqual(len(main_options), 2) + for o in main_options: + self.assertLessEqual(len(o), 1) + + if len(o) == 1: + self.assertIn("Time", o) + + def test_construct_all_interaction_options_main_only(self): + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + combined_dict = json.loads(file_data) + + interaction_options = list(construct_all_interaction_options(combined_dict=combined_dict)) + self.assertEqual(len(interaction_options), 1) + for o in interaction_options: + self.assertLessEqual(len(o), 0) + + def test_construct_all_random_options_main_only(self): + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + combined_dict = json.loads(file_data) + + random_options = list(construct_all_random_options(combined_dict=combined_dict)) + self.assertEqual(len(random_options), 1) + for o in random_options: + self.assertLessEqual(len(o), 0) + + def test_construct_family_link_pairs(self): + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + combined_dict = json.loads(file_data) + + family_link_options = list(construct_all_family_link_options(combined_dict=combined_dict)) + for o in family_link_options: + self.assertEqual(len(o), 2) + + family = o[0] + link = o[1] + self.assertIn(family, DataForTests.possible_families) + + def test_generate_multiverse_decisions_main_only(self): + input_filename = "main_only.json" + input_path = os.path.join(input_dir, input_filename) + + # Read in JSON file as a dict + with open(input_path, "r") as f: + file_data = f.read() + input_dict = json.loads(file_data) + + output_filename = "decisions_main_only.json" + decisions = generate_multiverse_decisions(combined_dict=input_dict) + + # TODO: Do we need a more extensive test here? + # for dec in decisions: + # var = dec["var"] + # if var == "main_effects": + # options = dec["options"] + # input_dict["input"]["generated main effects"] + # elif var == "interaction_effects": + # pass + # elif var == "random_effects": + # pass + # elif var == "family, link pairs": + # pass + + # TODO: Add more tests for interaction_only, main_interaction, .... + + def test_generate_multiverse_template_main_only(self): + decisions_filename = "decisions_main_only.json" + decisions_path = os.path.join(output_decision_dir, decisions_filename) + decisions = dict() + with open(decisions_path, "r") as f: + decisions = f.read() + decisions = json.loads(decisions) + + template_filename = "template_main_only.py" + output_path = os.path.join(output_template_dir, template_filename) + output_path = generate_template_code(template_path=output_path, decisions=decisions, data_path="data.csv", target="PYTHON", has_random_effects=False) + + def test_boba_config_from_decisions_main_only(self): + decisions_filename = "decisions_main_only.json" + decisions_path = os.path.join(output_decision_dir, decisions_filename) + + # Read in JSON file as a dict + with open(decisions_path, "r") as f: + file_data = f.read() + decisions_dict = json.loads(file_data) + + boba_config_decisions = generate_boba_config_from_decisions(decisions_dict) + self.assertIsInstance(boba_config_decisions, str) + self.assertIn(boba_config_start, boba_config_decisions) + self.assertIn(boba_config_end, boba_config_decisions) + decisions = boba_config_decisions.split(boba_config_start)[1] + decisions = decisions.split(boba_config_end)[0] + decisions = decisions.rstrip() # remove trailing white space + decisions = decisions.lstrip() # remove leading white space + self.assertEqual(decisions, json.dumps(decisions_dict)) + + def test_generate_combined_decisions_template_main_only(self): + pass + + def test_construct_all_formulae_interaction_only(self): + combined_dict = dict() + pass + + def test_construct_all_formulae_main_interaction(self): + combined_dict = dict() + pass + + def test_construct_all_formulae_main_random(self): + combined_dict = dict() + pass + + def test_construct_all_formulae_main_interaction_random(self): + combined_dict = dict() + pass + + def test_construct_all_family(self): + pass + + def test_construct_all_link(self): + pass + + def test_generate_multiverse_decisions(self): + pass + +class DataForTests: + possible_families = ["Gaussian", "InverseGaussian", "Gamma", "Tweedie", "Poisson", "Binomial", "NegativeBinomial"] \ No newline at end of file diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..980c112 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,54 @@ +""" +Test functions in main.py +""" +import tisane as ts + +import pandas as pd +import os +import unittest + +class TestDriver(unittest.TestCase): + def testMultiverseGeneration(self): + dir = os.path.relpath("examples/Animal_Science/") + df = pd.read_csv(os.path.join(dir, "pigs.csv")) + + ## Initialize variables with data + # Bind measures to units at the time of declaration + week = ts.SetUp("Time", order=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], cardinality=12) + pig = ts.Unit("Pig", cardinality=72) # 72 pigs + litter = ts.Unit("Litter", cardinality=21) # 21 litters + # Each pig has 1 instance of an ordinal Evit measure + vitamin_e = pig.ordinal( + "Evit", order=["Evit000", "Evit100", "Evit200"], number_of_instances=1 + ) + # Each pig has 1 instance of an ordinal Cu measure + copper = pig.ordinal("Cu", order=["Cu000", "Cu035", "Cu175"], number_of_instances=1) + # Each pig has for each value of week 1 instance of a numeric Weight measure + # Also: Each pig has 1 instance of a Weight measure corresponding to each week + weight = pig.numeric("Weight", number_of_instances=week) + # Each pig has for each value of week 1 instance of a numeric Feed consumption measure + feed = pig.numeric("Feed consumption", number_of_instances=week) + + ## Conceptual relationships + week.causes(weight) + + ## Data measurement relationships + # Pigs are nested within litters + pig.nests_within(litter) + + ## Specify and execute query + design = ts.Design(dv=weight, ivs=[week]).assign_data(df) + + ts.infer_all_models(design=design) + + + def test_multiverse_decisions_generation(self): + file = "decisions.json" + pass + + def test_multiverse_template_generation(self): + file = "template.py" + pass + + + diff --git a/tisane/__init__.py b/tisane/__init__.py index 5d10e7a..43e093d 100644 --- a/tisane/__init__.py +++ b/tisane/__init__.py @@ -1,6 +1,7 @@ from tisane.main import ( infer_model, infer_statistical_model_from_design, + infer_all_models ) from tisane.variable import Unit, SetUp, Exactly, AtMost diff --git a/tisane/code_generator.py b/tisane/code_generator.py index 5033b8b..f8e1cea 100644 --- a/tisane/code_generator.py +++ b/tisane/code_generator.py @@ -73,12 +73,18 @@ def show_model_diagnostics(model): pymer4_model_template = """ model = Lmer(formula={formula}, family=\"{family_name}\", data=df) +""" + +pymer4_model_fit_template = """ print(model.fit()) return model """ statsmodels_model_template = """ model = smf.glm(formula={formula}, data=df, family=sm.families.{family_name}(sm.families.links.{link_obj})) +""" + +statsmodels_model_fit_template = """ res = model.fit() print(res.summary()) return model @@ -111,6 +117,7 @@ def show_model_diagnostics(model): "load_data_from_dataframe_template": load_data_from_dataframe_template, "load_data_no_data_source": load_data_no_data_source, "model_template": pymer4_model_template, + "model_fit_template": pymer4_model_fit_template, "model_diagnostics_function_wrapper": model_diagnostics_function_wrapper, "model_diagnostics": pymer4_model_diagnostics, "main_function": main_function, @@ -123,6 +130,7 @@ def show_model_diagnostics(model): "load_data_from_dataframe_template": load_data_from_dataframe_template, "load_data_no_data_source": load_data_no_data_source, "model_template": statsmodels_model_template, + "model_fit_template": statsmodels_model_fit_template, "model_diagnostics_function_wrapper": model_diagnostics_function_wrapper, "model_diagnostics": statsmodels_model_diagnostics, "main_function": main_function, @@ -203,12 +211,12 @@ def generate_python_code(statistical_model: StatisticalModel): if statistical_model.has_random_effects(): return generate_pymer4_code(statistical_model=statistical_model) - else: - assert not statistical_model.has_random_effects() - return generate_statsmodels_code(statistical_model=statistical_model) + # else + assert not statistical_model.has_random_effects() + return generate_statsmodels_code(statistical_model=statistical_model) -def generate_pymer4_code(statistical_model: StatisticalModel): +def generate_pymer4_code(statistical_model: StatisticalModel, boba_template: bool = False): global pymer4_code_templates ### Specify preamble @@ -260,19 +268,29 @@ def generate_pymer4_code(statistical_model: StatisticalModel): ) -def generate_pymer4_model(statistical_model: StatisticalModel): +def generate_pymer4_model(statistical_model: StatisticalModel, boba_template: bool =False): global pymer4_code_templates - formula_code = generate_pymer4_formula(statistical_model=statistical_model) - family_code = generate_pymer4_family(statistical_model=statistical_model) - # link_code = generate_pymer4_link(statistical_model=statistical_model) - model_code = pymer4_code_templates["model_template"].format( - formula=formula_code, family_name=family_code - ) + if boba_template: + formula_code = "{{FORMULA}}" + family_code = "{{FAMILY}}" + model_code = ( + pymer4_code_templates["model_template"].format( + formula=formula_code, family_name=family_code) + + pymer4_code_templates["model_fit_template"] + ) + else: + formula_code = generate_pymer4_formula(statistical_model=statistical_model) + family_code = generate_pymer4_family(statistical_model=statistical_model) + # family_code = "\"" + generate_pymer4_family(statistical_model=statistical_model) + "\"" + model_code = ( + pymer4_code_templates["model_template"].format( + formula=formula_code, family_name=family_code) + + pymer4_code_templates["model_fit_template"] + ) return model_code - def generate_pymer4_formula(statistical_model: StatisticalModel): global pymer4_code_templates @@ -357,11 +375,7 @@ def generate_pymer4_family(statistical_model: StatisticalModel) -> str: return pymer4_family_name_to_functions[sm_family_name] -# def generate_pymer4_link(statistical_model=StatisticalModel) -> str: -# return str() - - -def generate_statsmodels_code(statistical_model: StatisticalModel): +def generate_statsmodels_code(statistical_model: StatisticalModel, boba_template: bool = False): global statsmodels_code_templates ### Specify preamble @@ -384,12 +398,7 @@ def generate_statsmodels_code(statistical_model: StatisticalModel): ].format(path=data_path) ### Generate model code - formula_code = generate_statsmodels_formula(statistical_model=statistical_model) - family_code = generate_statsmodels_family(statistical_model=statistical_model) - link_code = generate_statsmodels_link(statistical_model=statistical_model) - model_code = statsmodels_code_templates["model_template"].format( - formula=formula_code, family_name=family_code, link_obj=link_code - ) + model_code = generate_statsmodels_model(statistical_model=statistical_model, boba_template=boba_template) model_diagnostics_code = statsmodels_code_templates["model_diagnostics"] ### Put everything together @@ -416,19 +425,30 @@ def generate_statsmodels_code(statistical_model: StatisticalModel): ) -def generate_statsmodels_model(statistical_model: StatisticalModel): +def generate_statsmodels_model(statistical_model: StatisticalModel, boba_template: bool = False): global statsmodels_code_templates - formula_code = generate_statsmodels_formula(statistical_model=statistical_model) - family_code = generate_statsmodels_family(statistical_model=statistical_model) - link_code = generate_statsmodels_link(statistical_model=statistical_model) - model_code = statsmodels_code_templates["model_template"].format( - formula=formula_code, family_name=family_code, link_obj=link_code - ) + if boba_template: + formula_code = "{{FORMULA}}" + family_code = "{{FAMILY}}" + link_code = "{{LINK}}" + model_code = ( + statsmodels_code_templates["model_template"].format( + formula=formula_code, family_name=family_code, link_obj=link_code) + + statsmodels_code_templates["model_fit_template"] + ) + else: + formula_code = generate_statsmodels_formula(statistical_model=statistical_model) + family_code = generate_statsmodels_family(statistical_model=statistical_model) + link_code = generate_statsmodels_link(statistical_model=statistical_model) + model_code = ( + statsmodels_code_templates["model_template"].format( + formula=formula_code, family_name=family_code, link_obj=link_code) + + statsmodels_code_templates["model_fit_template"] + ) return model_code - def generate_statsmodels_formula(statistical_model: StatisticalModel): dv_code = "{dv} ~ " dv_code = dv_code.format(dv=statistical_model.dependent_variable.name) @@ -713,20 +733,4 @@ def generate_statsmodels_glmm_code(statistical_model: StatisticalModel, **kwargs + ")" ) - return model_code - - -# def generate_statsmodels_model_code(statistical_model: StatisticalModel, **kwargs): -# model_code = str() - -# has_fixed = len(statistical_model.fixed_ivs) > 0 -# has_interactions = len(statistical_model.interactions) > 0 -# has_random = len(statistical_model.random_ivs) > 0 -# has_data = statistical_model.data is not None # May not have data - -# # Does the statistical model have random effects (slope or intercept) that we should take into consideration? -# if has_random: -# return generate_statsmodels_glmm_code(statistical_model=statistical_model) -# else: -# # GLM: Fixed, interactions, no random; Other family -# return generate_statsmodels_glm_code(statistical_model=statistical_model) + return model_code \ No newline at end of file diff --git a/tisane/data.py b/tisane/data.py index b431912..67619df 100644 --- a/tisane/data.py +++ b/tisane/data.py @@ -63,6 +63,14 @@ def has_data_path(self) -> bool: return self.data_path is not None + # # Outputs data to a CSV file + # # @returns the CSV file's path + # def to_csv(self): + # output_path = "data.csv" + # self.dataset.to_csv(output_path) + + # reutrn output_path + class DataVector(object): name: str diff --git a/tisane/main.py b/tisane/main.py index c07b475..068730a 100644 --- a/tisane/main.py +++ b/tisane/main.py @@ -18,6 +18,7 @@ from tisane.design import Design from tisane.statistical_model import StatisticalModel from tisane.code_generator import * +from tisane.multiverse_code_generator import generate_multiverse_decisions, generate_template_code from tisane.gui.gui import TisaneGUI from enum import Enum @@ -170,7 +171,6 @@ def write_to_script(code: str, output_dir: str, output_filename: str): print("Writing out path") return path - # @param file is the path to the JSON file from which to construct the statistical model def construct_statistical_model( filename: typing.Union[Path], @@ -333,7 +333,6 @@ def infer_model(design: Design, jupyter: bool = False): return infer_statistical_model_from_design(design=design, jupyter=jupyter) # @returns statistical model that reflects the study design - def infer_statistical_model_from_design(design: Design, jupyter: bool = False): """Infer a stats model from design and launch the Tisane GUI. @@ -479,9 +478,113 @@ def generateCode( sm.assign_data(design.dataset) # Generate code from SM code = generate_code(sm) + # Write generated code out - path = write_to_script(code, destinationDir, "model.py") return path gui.start_app(input=path, jupyter=jupyter, generateCode=generateCode) + +# Infer a multiverse from the specification +def infer_multiverse(design: Design, jupyter: bool = False): + return infer_all_models(design=design, jupyter=jupyter) + +# Infer a multiverse from the specification +def infer_all_models(design: Design, jupyter: bool = False): + return infer_all_statistical_models_from_design(design=design, jupyter=jupyter) + +# Infer a multiverse from the specification +def infer_all_statistical_models_from_design(design: Design, jupyter: bool = False): + gr = design.graph + + ### Step 1: Initial conceptual checks + # Check that the IVs have a conceptual relationship (direct or transitive) with DV + check_design_ivs(design) + # Check that the DV does not cause one or more IVs + check_design_dv(design) + + ### Step 2: Candidate statistical model inference/generation + (main_effects_candidates, main_explanations) = infer_main_effects_with_explanations( + gr=gr, query=design + ) + ( + interaction_effects_candidates, + interaction_explanations, + ) = infer_interaction_effects_with_explanations( + gr=gr, query=design, main_effects=main_effects_candidates + ) + ( + random_effects_candidates, + random_explanations, + ) = infer_random_effects_with_explanations( + gr=gr, + query=design, + main_effects=main_effects_candidates, + interaction_effects=interaction_effects_candidates, + ) + family_candidates = infer_family_functions(query=design) + link_candidates = set() + family_link_paired = dict() + for f in family_candidates: + l = infer_link_functions(query=design, family=f) + # Add Family: Link options + assert f not in family_link_paired.keys() + family_link_paired[f] = l + + ( + intermediaries, + variable_to_intermediaries, + ) = find_all_associates_that_causes_or_associates_another(design.ivs, design.dv, gr) + intermediary_to_variable = { + intermediary: gr.get_variable(intermediary) for intermediary in intermediaries + } + associative_intermediaries = [ + intermediary + for intermediary, intermediary_variable in intermediary_to_variable.items() + if is_intermediary_associative(intermediary_variable, design.dv, gr) + ] + # Combine explanations + explanations = dict() + explanations.update(main_explanations) + explanations.update(interaction_explanations) + explanations.update(random_explanations) + + # Get combined dict + combined_dict = collect_model_candidates( + query=design, + main_effects_candidates=main_effects_candidates, + interaction_effects_candidates=interaction_effects_candidates, + random_effects_candidates=random_effects_candidates, + family_link_paired_candidates=family_link_paired, + ) + + # Add explanations + combined_dict["input"]["explanations"] = explanations + combined_dict["input"][ + "associative intermediary main effects" + ] = associative_intermediaries + + ### Step 3: Generate multiverse code + has_random_effects = False + # Are there any random effects? If so, update has_random_effects. + if len(random_effects_candidates) > 0: + has_random_effects = True + + # Generate the dicitonary representing the multiverse + decisions = generate_multiverse_decisions(combined_dict) + + # Output data somewhere to read in from template.py + data = design.get_data() + data_file = "data.csv" + data.to_csv(data_file) + + # Generate template file for the multiverse + # There is only one template file generated because Tisane enforces the inclusion of mixed effects if they are inferred. + template_file = "template.py" + # TODO: Don't call generate_code (like would for a single model) because + # (i) we are using boba to generate the combinatorial set and + # (ii) therefore are not constructing complete StatisticalModels, which is the input for generate_code/functions in code_generator.py + # Could imagine feeding a partial spec into code_generator and having it fill the rest out, but that seems like a different use case to design for... + generate_template_code(template_file, decisions, data_file, has_random_effects=has_random_effects) # Need to inject decisions into template file to use boba + + ### TODO: Step 4: Generate bash script/output for how to execute Boba? diff --git a/tisane/multiverse_code_generator.py b/tisane/multiverse_code_generator.py new file mode 100644 index 0000000..a1e138a --- /dev/null +++ b/tisane/multiverse_code_generator.py @@ -0,0 +1,333 @@ +from tisane.code_generator import * + +import os +from pathlib import Path +from itertools import chain, combinations +from typing import List, Any, Tuple, Dict, Union +import json +import pandas as pd +import statsmodels.api as sm +import statsmodels.formula.api as smf + +### GLOBALs +formula_generation_code = """ + ivs = list() + ivs.append({{main_effects}}) + ivs.append({{interaction_effects}}) + ivs.append({{random_effects}}) + ivs_formula = "+".join(ivs) + dv_formula = "{{dependent_variable}} ~ " + formula = dv_formula + ivs_formula +""" + +family_link_specification_code = """ + family = {{family_link_pair}}[0] + link = {{family_link_pair}}[1] +""" + +boba_config_start = "# --- (BOBA_CONFIG)" +boba_config_end = "# --- (END)" + +### Helper functions +def powerset(iterable, min_length=0, max_length=None): + "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" + s = list(iterable) + if max_length is not None: + return chain.from_iterable(combinations(s, r) for r in range(min_length, max_length+1)) + #else: + return chain.from_iterable(combinations(s, r) for r in range(min_length, len(s)+1)) + +# Write data to JSON file specified in @param output_path +def write_to_json(data: Dict, output_path: str, output_filename: str): + assert output_filename.endswith(".json") + path = Path(output_path, output_filename) + # Output dictionary to JSON + with open(path, "w+", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4, sort_keys=True) + + return path + +def write_to_path(code: str, output_path: os.PathLike): + # Output @param code to .py script + with open(output_path, "w+", encoding="utf-8") as f: + f.write(code) + print("Writing out path") + return output_path + +def extract_dependent_variable(combined_dict: Dict[str, Any]) -> List[str]: + input = combined_dict["input"] + query = input["query"] + dv = query["DV"] + + return [dv] + +def construct_all_main_options(combined_dict: Dict[str, Any]) -> List[str]: + input = combined_dict["input"] + main_effects = input["generated main effects"] + main_options = powerset(main_effects) + + return list(main_options) + +def construct_all_interaction_options(combined_dict: Dict[str, Any]) -> List[str]: + input = combined_dict["input"] + interaction_effects = input["generated interaction effects"] + interaction_options = powerset(interaction_effects) + + return list(interaction_options) + +def construct_all_random_options(combined_dict: Dict[str, Any]) -> List[str]: + input = combined_dict["input"] + random_effects = input["generated random effects"] + random_options = powerset(random_effects) + + return list(random_options) + +def construct_all_family_link_options(combined_dict: Dict[str, Any], has_random_effects: bool = False) -> List[List[str]]: + global pymer4_family_name_to_functions + + input = combined_dict["input"] + + family_link_options = list() + for family, links in input["generated family, link functions"].items(): + for l in links: + if has_random_effects: + # Use pymer4 + family_func = pymer4_family_name_to_functions[family] + + link_func = l + else: + # Use statsmodels + family_func = statsmodels_family_name_to_functions[family] + link_func = statsmodels_link_name_to_functions[l] + + family_link_options.append([family_func, link_func]) + + return family_link_options + +def generate_multiverse_decisions(combined_dict: Dict[str, Any]) -> Dict[str, Any]: + # Generate formulae decisions modularly + # Add dependent variable + dv_options = extract_dependent_variable(combined_dict=combined_dict) + dv_dict = dict() + dv_dict["var"] = "dependent_variable" + dv_dict["options"] = dv_options + + # Construct main options + main_options = construct_all_main_options(combined_dict=combined_dict) + main_dict = dict() + main_dict["var"] = "main_effects" + main_dict["options"] = main_options + + # Construct interaction options + interaction_options = construct_all_interaction_options(combined_dict=combined_dict) + interaction_dict = dict() + interaction_dict["var"] = "interaction_effects" + interaction_dict["options"] = interaction_options + + # Construct random options + random_options = construct_all_random_options(combined_dict=combined_dict) + random_dict = dict() + random_dict["var"] = "random_effects" + random_dict["options"] = random_options + + # Construct family and link pair decisions + family_link_options = construct_all_family_link_options(combined_dict) + family_link_dict = dict() + family_link_dict["var"] = "family_link_pair" + family_link_dict["options"] = family_link_options + + # Combine all the decisions + decisions_dict = dict() + decisions_dict["graph"] = list() + decisions_dict["decisions"] = list() + decisions_dict["decisions"].append(dv_dict) + decisions_dict["decisions"].append(main_dict) + decisions_dict["decisions"].append(interaction_dict) + decisions_dict["decisions"].append(random_dict) + decisions_dict["decisions"].append(family_link_dict) + # TODO: Add any bash commands? + # decisions_dict["before_execute"] = "cp ./code/" + + return decisions_dict + +# def generate_multiverse_decisions_to_json(combined_dict: Dict[str, Any], decisions_path: os.PathLike, decisions_file: os.PathLike) -> os.PathLike: +# decisions_dict = generate_multiverse_decisions(combined_dict=combined_dict, decisions_path=decisions_path, decisions_file=decisions_file) + +# # Write out JSON +# path = write_to_json(data=decisions_dict, output_path=decisions_path, output_filename=decisions_file) + +# return path + +# @param template_file is the output file where the code will be output +def generate_template_code(template_path: os.PathLike, decisions: Dict[str, Any], data_path: Union[os.PathLike, None], target: str = "PYTHON", has_random_effects: bool = False): + if target.upper() == "PYTHON": + code = generate_template_python_code(template_path, decisions, data_path, has_random_effects) + else: + assert(target.upper() == "R") + code = generate_template_r_code(template_path, decisions, data_path, has_random_effects) + + # Write generated code out + path = write_to_path(code, template_path) + return path + +# Model template after examples, such as https://github.com/uwdata/boba/blob/master/example/fertility/template.py +def generate_template_python_code(template_path: os.PathLike, decisions: Dict[str, Any], data_path: Union[os.PathLike, None], target: str = "PYTHON", has_random_effects: bool = False): + if has_random_effects: + return generate_template_pymer4_code(template_path, decisions, data_path) + #else: + return generate_template_statsmodels_code(template_path, decisions, data_path) + +def generate_template_r_code(template_path: os.PathLike, decisions: Dict[str, Any], data_path: Union[os.PathLike, None], target: str = "PYTHON", has_random_effects: bool = False): + # Output file is an R file + raise NotImplementedError + +def generate_template_pymer4_code(template_path: os.PathLike, decisions: Dict[str, Any], data_path: Union[os.PathLike, None]): + global pymer4_code_templates + + ### Specify preamble + preamble = pymer4_code_templates["preamble"] + + ### Generate Boba config from @param decisions dict + boba_config = generate_boba_config_from_decisions(decisions=decisions) + + ### Generate data code + if not data_path: + data_code = pymer4_code_templates["load_data_no_data_source"] + else: + data_code = pymer4_code_templates["load_data_from_csv_template"] + data_code = data_code.format(path=str(data_path)) + + ### Generate model code + model_code = generate_template_pymer4_model() + + ### Generate model diagnostics code for plotting residuals vs. fitted + model_diagnostics_code = pymer4_code_templates["model_diagnostics"] + + ### Put everything together + model_function_wrapper = pymer4_code_templates["model_function_wrapper"] + model_diagnostics_function_wrapper = pymer4_code_templates[ + "model_diagnostics_function_wrapper" + ] + main_function = pymer4_code_templates["main_function"] + + assert data_code is not None + # Return string to write out to script + return ( + preamble + + "\n" + + boba_config + + "\n" + + model_function_wrapper + + data_code + + "\n" + + model_code + + "\n" + + model_diagnostics_function_wrapper + + model_diagnostics_code + + "\n" + + main_function + ) + +def generate_boba_config_from_decisions(decisions: Dict[str, Any]) -> str: + global boba_config_start, boba_config_end + + return ( + boba_config_start + + "\n" + + json.dumps(decisions) + + "\n" + + boba_config_end + ) + +# @returns template code with Boba cnofig, modeling, and plotting residuals +def generate_template_statsmodels_code(template_path: os.PathLike, decisions: Dict[str, Any], data_path: Union[os.PathLike, None]): + global statsmodels_code_templates + + ### Specify preamble + preamble = statsmodels_code_templates["preamble"] + + ### Generate Boba config from @param decisions dict + boba_config = generate_boba_config_from_decisions(decisions=decisions) + + ### Generate data code + if not data_path: + data_code = statsmodels_code_templates["load_data_no_data_source"] + else: + data_code = statsmodels_code_templates["load_data_from_csv_template"] + data_code = data_code.format(path=str(data_path)) + + ### Generate model code + model_code = generate_template_statsmodels_model() + + ### Generate model diagnostics code for plotting residuals vs. fitted + model_diagnostics_code = statsmodels_code_templates["model_diagnostics"] + + ### Put everything together + model_function_wrapper = statsmodels_code_templates["model_function_wrapper"] + model_diagnostics_function_wrapper = statsmodels_code_templates[ + "model_diagnostics_function_wrapper" + ] + main_function = statsmodels_code_templates["main_function"] + + assert data_code is not None + # Return string to write out to script + return ( + preamble + + "\n" + + boba_config + + "\n" + + model_function_wrapper + + data_code + + "\n" + + model_code + + "\n" + + model_diagnostics_function_wrapper + + model_diagnostics_code + + "\n" + + main_function + ) + +# Add "eval" wrapper to execute model as a function while using Boba multiverse +def wrap_model_template(model_template: str) -> str: + wrapped_code = ( + " " # for within-method spacing + + "eval(f\"" + + model_template.strip() + + "\")") + + return wrapped_code + +def generate_template_pymer4_model(): + global pymer4_code_templates, formula_generation_code, family_link_specification_code + + formula_code = "formula" + family_code = "{family}" + + model_code = ( + formula_generation_code + + family_link_specification_code + + "\n" # for nice formatting + + wrap_model_template(pymer4_code_templates["model_template"].format( + formula=formula_code, family_name=family_code)) + + pymer4_code_templates["model_fit_template"] + ) + + return model_code + + +def generate_template_statsmodels_model(): + global statsmodels_code_templates, formula_generation_code, family_link_specification_code + + formula_code = "formula" + family_code = "{family}" + link_code = "{link}" + + model_code = ( + formula_generation_code + + family_link_specification_code + + wrap_model_template(statsmodels_code_templates["model_template"].format( + formula=formula_code, family_name=family_code, link_obj=link_code)) + + statsmodels_code_templates["model_fit_template"] + ) + + return model_code \ No newline at end of file diff --git a/tisane/variable.py b/tisane/variable.py index e1a31e0..a828983 100644 --- a/tisane/variable.py +++ b/tisane/variable.py @@ -1247,7 +1247,7 @@ def per(self, cardinality: AbstractVariable=None, number_of_instances: AbstractV ... number_of_instances=Exactly(3).per(cardinality=days)) """ - super(Exactly, self).per(cardinality=cardinality, number_of_instances=number_of_instances) + return super(Exactly, self).per(cardinality=cardinality, number_of_instances=number_of_instances) class AtMost(NumberValue): @@ -1315,7 +1315,7 @@ def per(self, cardinality: AbstractVariable=None, number_of_instances: AbstractV """ - super(AtMost, self).per(cardinality=cardinality, number_of_instances=number_of_instances) + return super(AtMost, self).per(cardinality=cardinality, number_of_instances=number_of_instances) """ Class for expressing Per relationships