diff --git a/.travis.yml b/.travis.yml index e9ba691..09680f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ version: ~> 1.0 language: python python: + - '3.8' - '3.7' - - '2.7' - '3.6' notifications: @@ -17,7 +17,7 @@ branches: env: global: - - CANONICAL_PYTHON="3.7" + - CANONICAL_PYTHON="3.8" - CANONICAL_MDTRAJ="release" - CODECLIMATE="" - TWINE_USERNAME="dwhswenson" @@ -25,7 +25,7 @@ env: - secure: "onsUSX6Yxt0HJApAsyUr5iFQW1ZngBiMxKmdiI5aN0TqnVSQ6819xx0YIxBjgburG/pFoW1F8bh8KYITV6TKK7RpcwdOspqpeO0X6L0DdBvNWQdl99QtJZcuMS3nPBiJ4guwrJCkQa/iKgZ7k27YukTw5pgKKDgC2d10P5SQiVFHNkX8lo4FzZIBnpA93Nq9G+j2pi14PpGtxaXhktqLOSvLvy/7BIWfzy69HDGjKZ38SMF4RFRnHiBI0G2uON6N5AuLovBWpRrmPmCTiX3p2lgxss8oUSKsA9OlikAoGlLyOjzMU2Fu3JqgoXPSyjDzkqO/Bavd1S03RYUeB5cRM9kBoORjCYl2dBCAmh7U41ZigvivYC2UZ/2p300f9YmolZ4vcj9tz4THXfF5/ZgIoARFIVk3fIQOHJCnXwa3gDcf0AEmsYOUOSKJKyYxruQhyjb2reFxUyA6bqtelV82qpMT53fSZ1/ZJjA+yJJgbeB9qkrMjvUf9AjlY8faqXyIafJsogFuvg1rg+FddMlc56zUWCEdc/pgbTesQuF8GCqje++7eaN68KiWBPwjDSX6cuNkeg86xx1E4VsRcXH6rQ9JXCsGs/HN4JRZ5J+ZH4YOHaUc5pTG82mTEZHGCjAN+BuhGfSFcgSoGO6oOHRGVjw47emOLvNpjS05B/zu/4c=" # AUTORELEASE_TOKEN - secure: "g+pFYJ62KN/gfnzrzQlfRTyDwvIpwQp4aQvwB4GVrWUISoOx3X8IlK5+S26Wb/BK4cCIGAKWs+dVgCr1Ag0v4TBhmi4CBlkZXQTNv3TcbGwyhmXYbO7kf03lpapsxKu5JkFlZqe0fniwocut6d2BiAuRQjvJ1B+xhEkl3t/ARarg0G/e+1geNyCopULD3tzcUDV4BFUw6cjfm4Sqn008m+7F8vgNOG+HitPtjggmZfFgoZVvS3nnZbFxGq99Uit8PST8Rdus3PJFNJlguf4XTpsdYZeW2CjFfd9P/v8hvfM3ATSCJ8Mzh8PuWJFP48Be6IvJAn6qhgJW7jSDK6bcCahS0BXBr0EqT9maC3jNXTQiem8WT990mUbK9tpbj6a9HPaD/E1XfmfC2he/EIRt/p14hNPB8rMIR0NRcA0Hy4mTn6AsfUgzwFSRZqjicvLlgwFokn32usZAkcNsW7KQE2+YVnUq6VYG/wpCd5w3x/4sws/HWTTR0Kg98pqt31s3oI0KCktFCuz2Ipxj4sNYJ8ExwqJ3v4zr0505YpYLnJCqy02nbwBcmHv8GrodmlGn2Y5Wid3BZrn5VYQIZVWVSVosfT7B5VpcFVdXRSY5VoKE8mTU7c98HmvASOygG6TZyQBkwXWsEBDoKMssK6QuCjaJpoQt1ZkvpMVsFzzoJkY=" - matrix: + jobs: - MDTRAJ="release" - MDTRAJ="dev" @@ -33,6 +33,8 @@ jobs: exclude: - env: MDTRAJ="dev" python: "3.6" + - env: MDTRAJ="dev" + python: "3.7" before_install: - echo "before install" @@ -58,7 +60,6 @@ after_success: - coveralls - python-codacy-coverage -r cov.xml - import: - - dwhswenson/autorelease:autorelease-travis.yml@v0.1.0 + - dwhswenson/autorelease:autorelease-travis.yml@v0.2.1 diff --git a/appveyor.yml b/appveyor.yml index d2eda24..9513a21 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,26 +14,24 @@ environment: PYTHONUNBUFFERED: 1 matrix: - - PYTHON: "C:\\Miniconda" - CONDA_PY: "27" - ARCH: '32' - - PYTHON: "C:\\Miniconda-x64" - CONDA_PY: "27" - ARCH: '64' - - PYTHON: "C:\\Miniconda36" - CONDA_PY: "36" # No MDTraj for py37/Win32 - ARCH: '32' + #- PYTHON: "C:\\Miniconda38-x64" + #CONDA_PY: "38" + #ARCH: '64' - PYTHON: "C:\\Miniconda37-x64" CONDA_PY: "37" ARCH: '64' + - PYTHON: "C:\\Miniconda36-x64" + CONDA_PY: "36" + ARCH: '64' build: false install: - # install python + # set up conda Python - SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - conda config --add channels conda-forge - - conda install -yq conda=4.5 conda-build=3.10 + # note: it may become necessary to pin versions of conda/conda-build + - conda install -yq conda conda-build test_script: #- "%CMD_IN_ENV% activate base" diff --git a/ci/conda-recipe/meta.yaml b/ci/conda-recipe/meta.yaml index 8ea4c9d..e5734db 100644 --- a/ci/conda-recipe/meta.yaml +++ b/ci/conda-recipe/meta.yaml @@ -1,7 +1,7 @@ package: name: contact_map # add ".dev0" for unreleased versions - version: "0.5.0" + version: "0.5.1" source: path: ../../ @@ -20,7 +20,7 @@ requirements: - mdtraj - numpy - scipy - - pandas<1.0 + - pandas test: requires: diff --git a/contact_map/contact_count.py b/contact_map/contact_count.py index c9beb2e..4e57732 100644 --- a/contact_map/contact_count.py +++ b/contact_map/contact_count.py @@ -13,6 +13,8 @@ else: HAS_MATPLOTLIB = True +# pandas 0.25 not available on py27; can drop this when we drop py27 +_PD_VERSION = tuple(int(x) for x in pd.__version__.split('.')[:2]) def _colorbar(with_colorbar, cmap_f, norm, min_val): if with_colorbar is False: @@ -25,6 +27,34 @@ def _colorbar(with_colorbar, cmap_f, norm, min_val): return cb +# TODO: remove following: this is a monkeypatch for a bug in pandas +# see: https://github.com/pandas-dev/pandas/issues/29814 +from pandas._libs.sparse import BlockIndex, IntIndex, SparseIndex +def _patch_from_spmatrix(cls, data): + length, ncol = data.shape + + if ncol != 1: + raise ValueError("'data' must have a single column, not '{}'".format(ncol)) + + # our sparse index classes require that the positions be strictly + # increasing. So we need to sort loc, and arr accordingly. + arr = data.data + #idx, _ = data.nonzero() + idx = data.indices + loc = np.argsort(idx) + arr = arr.take(loc) + idx.sort() + + zero = np.array(0, dtype=arr.dtype).item() + dtype = pd.SparseDtype(arr.dtype, zero) + index = IntIndex(length, idx) + + return cls._simple_new(arr, index, dtype) + +if _PD_VERSION >= (0, 25): + pd.core.arrays.SparseArray.from_spmatrix = classmethod(_patch_from_spmatrix) +# TODO: this is the end of what to remove when pandas is fixed + class ContactCount(object): """Return object when dealing with contacts (residue or atom). @@ -95,10 +125,22 @@ def df(self): Rows/columns correspond to indices and the values correspond to the count """ - mtx = self.sparse_matrix.tocoo() + mtx = self.sparse_matrix index = list(range(self.n_x)) columns = list(range(self.n_y)) - return pd.SparseDataFrame(mtx, index=index, columns=columns) + + if _PD_VERSION < (0, 25): # py27 only + mtx = mtx.tocoo() + return pd.SparseDataFrame(mtx, index=index, columns=columns) + + df = pd.DataFrame.sparse.from_spmatrix(mtx, index=index, + columns=columns) + # note: I think we can always use float here for dtype; but in + # principle maybe we need to inspect and get the internal type? + # Problem is, pandas technically stores a different dtype for each + # column. + df = df.astype(pd.SparseDtype("float", np.nan)) + return df def _check_number_of_pixels(self, figure): """ diff --git a/contact_map/tests/test_contact_count.py b/contact_map/tests/test_contact_count.py index ef91eb3..77f3a5d 100644 --- a/contact_map/tests/test_contact_count.py +++ b/contact_map/tests/test_contact_count.py @@ -89,12 +89,23 @@ def test_sparse_matrix(self): def test_df(self): atom_df = self.map.atom_contacts.df residue_df = self.map.residue_contacts.df - assert isinstance(atom_df, pd.SparseDataFrame) - assert isinstance(residue_df, pd.SparseDataFrame) - assert_array_equal(atom_df.to_dense().values, + # this block is for old pandas on py27 + pd_version = tuple(int(x) for x in pd.__version__.split('.')[:2]) + if pd_version < (0, 25): + assert isinstance(atom_df, pd.SparseDataFrame) + assert isinstance(residue_df, pd.SparseDataFrame) + assert_array_equal(atom_df.to_dense().values, + zero_to_nan(self.atom_matrix)) + assert_array_equal(residue_df.to_dense().values, + zero_to_nan(self.residue_matrix)) + return + + assert isinstance(atom_df, pd.DataFrame) + assert isinstance(residue_df, pd.DataFrame) + assert_array_equal(atom_df.sparse.to_dense().values, zero_to_nan(self.atom_matrix)) - assert_array_equal(residue_df.to_dense().values, + assert_array_equal(residue_df.sparse.to_dense().values, zero_to_nan(self.residue_matrix)) @pytest.mark.parametrize("obj_type", ['atom', 'res']) diff --git a/docs/conf.py b/docs/conf.py index 0edcb3c..89cddec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -101,7 +101,7 @@ # General information about the project. project = u'Contact Map Explorer' -copyright = u'2017-2019, David W.H. Swenson and Sander Roet' +copyright = u'2017-2020, David W.H. Swenson and Sander Roet' author = u'David W.H. Swenson and Sander Roet' # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/readthedocs.yml b/readthedocs.yml index dfbea26..c17a8f1 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,13 +1,13 @@ version: 2 -conda: - environment: docs/environment.yml +#conda: + #environment: docs/environment.yml python: install: - #- requirements: docs/rtd_requirements.txt + - requirements: docs/rtd_requirements.txt #- requirements: requirements.txt - #- requirements: docs/requirements.txt + - requirements: docs/requirements.txt - method: setuptools path: . #- method: pip diff --git a/requirements.txt b/requirements.txt index 2bc28c8..6cd0d95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ future numpy scipy -pandas<1.0 +pandas mdtraj diff --git a/setup.cfg b/setup.cfg index d6415dd..dfaa6e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = contact_map -version = 0.5.0 +version = 0.5.1 description = Contact maps based on MDTraj long_description = file: README.md long_description_content_type = text/markdown @@ -26,7 +26,7 @@ install_requires = numpy mdtraj scipy - pandas<1.0 + pandas packages = find: [bdist_wheel]