diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d2c2aa0..b954f22 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,8 @@ name: Tests -on: [push] +on: + push: + pull_request: jobs: build: @@ -11,22 +13,69 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 lingam --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 lingam --count --exit-zero --ignore=E203,E501,E741,C901 --max-line-length=127 --statistics + - name: Test with pytest run: | pytest -v --cov=lingam --cov-report=term-missing + + build-and-install: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + - name: Build sdist & wheel via pyproject.toml + run: | + python -m venv venv_b + source venv_b/bin/activate + python -m pip install --upgrade pip setuptools wheel build + python -m build + ls -la dist/ + echo "List sdist contents:" + tar -tf dist/*.tar.gz | head -n 50 + + - name: Test install from wheel + run: | + python -m venv venv_w + source venv_w/bin/activate + pip install --no-cache-dir dist/*.whl + python -c "import lingam; print(lingam.__version__)" + pip check + + - name: Test install from sdist + run: | + python -m venv venv_s + source venv_s/bin/activate + pip install --no-cache-dir dist/*.tar.gz + python -c "import lingam; print(lingam.__version__)" + pip check diff --git a/lingam/causal_based_simulator.py b/lingam/causal_based_simulator.py index eb0bb68..ebd0274 100644 --- a/lingam/causal_based_simulator.py +++ b/lingam/causal_based_simulator.py @@ -668,7 +668,7 @@ class CBSIUnobsCommonCauseLiNGAM(CBSILiNGAM): def _check_causal_graph(self, causal_graph, X): try: # fill nan with zeros - causal_graph = check_array(causal_graph, force_all_finite="allow-nan", copy=True) + causal_graph = check_array(causal_graph, ensure_all_finite="allow-nan", copy=True) n_features = X.shape[1] if causal_graph.shape != (n_features, n_features): diff --git a/lingam/direct_lingam.py b/lingam/direct_lingam.py index 7987bcb..ab72d83 100644 --- a/lingam/direct_lingam.py +++ b/lingam/direct_lingam.py @@ -252,13 +252,20 @@ def _search_causal_order_gpu(self, X, U): Returns the instance itself. mlist: causal ordering """ + Uc, _ = self._search_candidate(U) + if len(Uc) == 1: + return Uc[0] + cols = len(U) rows = len(X) arr = X[:, np.array(U)] from lingam_cuda import causal_order as causal_order_gpu mlist = causal_order_gpu(arr, rows, cols) - return U[np.argmax(mlist)] + M_list = np.asarray(mlist) + M_list = M_list[np.isin(U, Uc)] + + return Uc[int(np.argmax(M_list))] def _mutual_information(self, x1, x2, param): """Calculate the mutual informations.""" diff --git a/lingam/tools/__init__.py b/lingam/tools/__init__.py index 0a0f825..7284e93 100644 --- a/lingam/tools/__init__.py +++ b/lingam/tools/__init__.py @@ -84,7 +84,7 @@ def bootstrap_with_imputation( Elements which are not NaN are the imputation values. """ # check args - X = check_array(X, force_all_finite="allow-nan") + X = check_array(X, ensure_all_finite="allow-nan") n_sampling = check_scalar(n_sampling, "n_sampling", (numbers.Integral, np.integer), min_val=1) diff --git a/lingam/utils/__init__.py b/lingam/utils/__init__.py index 6020810..80b3d10 100644 --- a/lingam/utils/__init__.py +++ b/lingam/utils/__init__.py @@ -957,7 +957,7 @@ def evaluate_model_fit(adjacency_matrix, X, is_ordinal=None): """ # check inputs - adj = check_array(adjacency_matrix, force_all_finite="allow-nan") + adj = check_array(adjacency_matrix, ensure_all_finite="allow-nan") if adj.ndim != 2 or (adj.shape[0] != adj.shape[1]): raise ValueError("adj must be an square matrix.") @@ -1037,7 +1037,7 @@ def calculate_distance_from_root_nodes(adjacency_matrix, max_distance=None): adjacency_matrix, ensure_min_samples=2, ensure_min_features=2, - force_all_finite="allow-nan", + ensure_all_finite="allow-nan", ) if adjacency_matrix.shape[0] != adjacency_matrix.shape[1]: raise ValueError("adjacency_matrix must be an square matrix.") @@ -1103,7 +1103,7 @@ def calculate_total_effect(adjacency_matrix, from_index, to_index, is_continuous adjacency_matrix, ensure_min_samples=2, ensure_min_features=2, - force_all_finite="allow-nan", + ensure_all_finite="allow-nan", ) if adjacency_matrix.shape[0] != adjacency_matrix.shape[1]: raise ValueError( diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ac1ad15 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lingam" +description = "Python package for causal discovery based on LiNGAM." +readme = "README.md" +authors = [ + { name = "Takashi Ikeuchi" }, + { name = "Genya Haraoka" }, + { name = "Mayumi Ide" }, + { name = "Kouhei Nishikawa" }, + { name = "Yan Zeng" }, + { name = "Takashi Nicholas Maeda" }, + { name = "Wataru Kurebayashi" }, + { name = "Shohei Shimizu" } +] +license = { file = "LICENSE" } +requires-python = ">=3.9" +dependencies = [ + "numpy", + "scipy", + "scikit-learn", + "graphviz", + "statsmodels", + "networkx", + "pandas", + "pygam", + "matplotlib", + "psy", + "semopy", + "autograd" +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" +] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/cdt15/lingam" + +[tool.setuptools.packages.find] +exclude = ["tests"] + +[tool.setuptools.dynamic] +version = { attr = "lingam.__version__" } diff --git a/setup.py b/setup.py deleted file mode 100644 index e3b2f0b..0000000 --- a/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -import setuptools - -with open('README.md', 'r', encoding='utf-8') as fh: - README = fh.read() - -import lingam - -VERSION = lingam.__version__ - -setuptools.setup( - name='lingam', - version=VERSION, - author='T.Ikeuchi, G.Haraoka, M.Ide, Y.Zeng, T.N.Maeda, W.Kurebayashi, S.Shimizu', - description='LiNGAM Python Package', - long_description=README, - long_description_content_type='text/markdown', - install_requires=[ - 'numpy', - 'scipy', - 'scikit-learn', - 'graphviz', - 'statsmodels', - 'networkx', - 'pandas', - 'pygam', - 'matplotlib', - 'psy', - 'semopy', - 'autograd', - ], - url='https://github.com/cdt15/lingam', - packages=setuptools.find_packages(exclude=['tests']), - classifiers=[ - 'Programming Language :: Python :: 3', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - ], - python_requires='>=3.9', -) diff --git a/tests/test_bottom_up_parce_lingam.py b/tests/test_bottom_up_parce_lingam.py index 4a4a995..dc1861d 100644 --- a/tests/test_bottom_up_parce_lingam.py +++ b/tests/test_bottom_up_parce_lingam.py @@ -102,9 +102,8 @@ def test_fit_invalid_data(): raise AssertionError # Include non-numeric data - x0 = np.random.uniform(size=5) - x1 = np.array(['X', 'Y', 'X', 'Y', 'X']) - X = pd.DataFrame(np.array([x0, x1]).T, columns=['x0', 'x1']) + X = np.array([[1.2, 'X'], + [3.4, 'Y']], dtype=object) try: model = BottomUpParceLiNGAM() model.fit(X) diff --git a/tests/test_direct_lingam.py b/tests/test_direct_lingam.py index df5f2ae..f8555e4 100644 --- a/tests/test_direct_lingam.py +++ b/tests/test_direct_lingam.py @@ -56,9 +56,8 @@ def test_fit_invalid_data(): raise AssertionError # Include non-numeric data - x0 = np.random.uniform(size=5) - x1 = np.array(['X', 'Y', 'X', 'Y', 'X']) - X = pd.DataFrame(np.array([x0, x1]).T, columns=['x0', 'x1']) + X = np.array([[1.2, 'X'], + [3.4, 'Y']], dtype=object) try: model = DirectLiNGAM() model.fit(X) diff --git a/tests/test_high_dim_direct_lingam.py b/tests/test_high_dim_direct_lingam.py index 8177308..5313b5b 100644 --- a/tests/test_high_dim_direct_lingam.py +++ b/tests/test_high_dim_direct_lingam.py @@ -73,9 +73,8 @@ def test_fit_invalid_data(): raise AssertionError # Include non-numeric data - x0 = np.random.uniform(size=5) - x1 = np.array(['X', 'Y', 'X', 'Y', 'X']) - X = pd.DataFrame(np.array([x0, x1]).T, columns=['x0', 'x1']) + X = np.array([[1.2, 'X'], + [3.4, 'Y']], dtype=object) try: model = HighDimDirectLiNGAM() model.fit(X) diff --git a/tests/test_rcd.py b/tests/test_rcd.py index aca3423..c123bf3 100644 --- a/tests/test_rcd.py +++ b/tests/test_rcd.py @@ -107,9 +107,8 @@ def test_fit_invalid_data(): raise AssertionError # Include non-numeric data - x0 = np.random.uniform(size=5) - x1 = np.array(['X', 'Y', 'X', 'Y', 'X']) - X = pd.DataFrame(np.array([x0, x1]).T, columns=['x0', 'x1']) + X = np.array([[1.2, 'X'], + [3.4, 'Y']], dtype=object) try: model = RCD() model.fit(X)