diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 37cb382..3713442 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "macos-latest"] - python-version: ["3.8", "3.9"] + python-version: ["3.8", "3.9", "3.10"] name: ${{ matrix.os }} with Python ${{ matrix.python-version }} defaults: diff --git a/docs/installation.rst b/docs/installation.rst index f0927ed..a067783 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -16,4 +16,4 @@ If you want to use the most up-to-date version, you can install from the ``maste pip install git+https://github.com/neurostuff/PyMARE.git PyMARE requires Python >=3.6 and a number of packages. -For a complete list, please see the file ``setup.cfg``. +For a complete list, please see ``setup.cfg``. diff --git a/pymare/core.py b/pymare/core.py index 7fc7b60..7a94fcc 100644 --- a/pymare/core.py +++ b/pymare/core.py @@ -59,7 +59,6 @@ class Dataset: def __init__( self, y=None, v=None, X=None, n=None, data=None, X_names=None, add_intercept=True ): - if y is None and data is None: raise ValueError( "If no y values are provided, a pandas DataFrame " diff --git a/pymare/effectsize/base.py b/pymare/effectsize/base.py index 68d8642..4c2ce6b 100644 --- a/pymare/effectsize/base.py +++ b/pymare/effectsize/base.py @@ -90,7 +90,6 @@ class EffectSizeConverter(metaclass=ABCMeta): """Base class for effect size converters.""" def __init__(self, data=None, **kwargs): - kwargs = {k: v for k, v in kwargs.items() if v is not None} if data is not None: @@ -509,7 +508,6 @@ def compute_measure( # Select or infer converter class if comparison == "infer": - one_samp_inputs = {"m", "sd", "n", "r"} two_samp_inputs = {"m1", "m2", "sd1", "sd2", "n1", "n2"} diff --git a/pymare/effectsize/expressions.py b/pymare/effectsize/expressions.py index 6cc1eeb..f96cf9d 100644 --- a/pymare/effectsize/expressions.py +++ b/pymare/effectsize/expressions.py @@ -91,7 +91,6 @@ def df_search(sym, exprs, known, visited): results = [] for exp in exp_dict[sym]: - candidates = [] sym_names = set(s.name for s in exp.symbols) diff --git a/pymare/estimators/estimators.py b/pymare/estimators/estimators.py index 7a2b1a9..24faee3 100644 --- a/pymare/estimators/estimators.py +++ b/pymare/estimators/estimators.py @@ -1,5 +1,6 @@ """Meta-regression estimator classes.""" +import sys from abc import ABCMeta, abstractmethod from inspect import getfullargspec from warnings import warn @@ -553,8 +554,8 @@ class StanMetaRegression(BaseEstimator): Warning ------- - With changes to Stan in version 3, which requires Python 3.7, this class no longer works for - Python 3.7+. We will try to fix it in the future. + :obj:`~pymare.estimators.StanMetaRegression` uses Pystan 3, which requires Python 3.7. + Pystan 3 should not be used with PyMARE and Python 3.6 or earlier. """ _result_cls = BayesianMetaRegressionResults @@ -564,6 +565,13 @@ def __init__(self, **sampling_kwargs): self.model = None self.result_ = None + if sys.version_info < (3, 7): + raise RuntimeError( + "StanMetaRegression uses Pystan 3, which requires python 3.7 or higher. " + f"You are running Python {sys.version_info.major}.{sys.version_info.minor}. " + "Pystan 3 should not be used with PyMARE and Python 3.6 or earlier." + ) + def compile(self): """Compile the Stan model.""" # Note: we deliberately use a centered parameterization for the @@ -575,7 +583,7 @@ def compile(self): int N; int K; vector[N] y; - int id[N]; + array[N] int id; int C; matrix[K, C] X; vector[N] sigma; @@ -595,13 +603,11 @@ def compile(self): } """ try: - from pystan import StanModel + import stan except ImportError: - raise ImportError( - "Please install pystan or, if using Python 3.7+, switch to Python 3.6." - ) + raise ImportError("Please install pystan.") - self.model = StanModel(model_code=spec) + self.model = stan.build(spec, data=self.data) def fit(self, y, v, X, groups=None): """Run the Stan sampler and return results. @@ -645,9 +651,6 @@ def fit(self, y, v, X, groups=None): "shape {}.".format(y.shape) ) - if self.model is None: - self.compile() - N = y.shape[0] groups = groups or np.arange(1, N + 1, dtype=int) K = len(np.unique(groups)) @@ -662,7 +665,12 @@ def fit(self, y, v, X, groups=None): "sigma": v.ravel(), } - self.result_ = self.model.sampling(data=data, **self.sampling_kwargs) + self.data = data + + if self.model is None: + self.compile() + + self.result_ = self.model.sample(**self.sampling_kwargs) return self def summary(self, ci=95): diff --git a/pymare/results.py b/pymare/results.py index 31c7d3e..02a5069 100644 --- a/pymare/results.py +++ b/pymare/results.py @@ -332,7 +332,6 @@ def permutation_test(self, n_perm=1000): # Loop over parallel datasets for i in range(n_datasets): - y = self.dataset.y[:, i] y_perm = np.repeat(y[:, None], n_perm, axis=1) @@ -472,7 +471,6 @@ def permutation_test(self, n_perm=1000): # Loop over parallel datasets for i in range(n_datasets): - y = self.dataset.y[:, i] y_perm = np.repeat(y[:, None], n_perm, axis=1) diff --git a/pymare/tests/test_stan_estimators.py b/pymare/tests/test_stan_estimators.py index 71efc28..995ba47 100644 --- a/pymare/tests/test_stan_estimators.py +++ b/pymare/tests/test_stan_estimators.py @@ -1,15 +1,17 @@ """Tests for estimators that use stan.""" +import sys + import pytest from pymare.estimators import StanMetaRegression -@pytest.mark.skip(reason="StanMetaRegression won't work with Python 3.7+.") +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") def test_stan_estimator(dataset): """Run smoke test for StanMetaRegression.""" # no ground truth here, so we use sanity checks and rough bounds - est = StanMetaRegression(iter=3000).fit_dataset(dataset) + est = StanMetaRegression(num_samples=3000).fit_dataset(dataset) results = est.summary() assert "BayesianMetaRegressionResults" == results.__class__.__name__ summary = results.summary(["beta", "tau2"]) @@ -19,9 +21,17 @@ def test_stan_estimator(dataset): assert 3 < tau2 < 5 -@pytest.mark.skip(reason="StanMetaRegression won't work with Python 3.7+.") +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") def test_stan_2d_input_failure(dataset_2d): """Run smoke test for StanMetaRegression on 2D data.""" with pytest.raises(ValueError) as exc: - StanMetaRegression(iter=500).fit_dataset(dataset_2d) + StanMetaRegression(num_samples=500).fit_dataset(dataset_2d) assert str(exc.value).startswith("The StanMetaRegression") + + +def test_stan_python_36_failure(dataset): + """Run smoke test for StanMetaRegression with Python 3.6.""" + if sys.version_info < (3, 7): + # Raise error if StanMetaRegression is initialize with python 3.6 or lower + with pytest.raises(RuntimeError): + StanMetaRegression(num_samples=3000).fit_dataset(dataset) diff --git a/setup.cfg b/setup.cfg index 1d476e4..276eabe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ classifiers = Operating System :: OS Independent Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Topic :: Scientific/Engineering [options] @@ -37,7 +38,7 @@ python_requires = >= 3.8 install_requires = numpy>=1.8.0 pandas - scipy + scipy<1.13.0 # https://github.com/arviz-devs/arviz/issues/2336 sympy wrapt packages = find: @@ -74,6 +75,7 @@ stan = all = %(doc)s %(tests)s + %(stan)s [options.package_data] * =