├── mord ├── tests │ ├── __init__.py │ └── test_fit.py ├── datasets │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_base.py │ ├── descr │ │ └── copenhagen_housing_survey.rst │ ├── base.py │ └── data │ │ └── copenhagen_housing_survey.csv ├── __init__.py ├── utils.py ├── regression_based.py ├── multiclass.py └── threshold_based.py ├── doc ├── numpy_ext │ ├── __init__.py │ ├── __init__.pyc │ ├── docscrape.pyc │ ├── numpydoc.pyc │ ├── docscrape_sphinx.pyc │ ├── __pycache__ │ │ ├── __init__.cpython-34.pyc │ │ ├── docscrape.cpython-34.pyc │ │ ├── numpydoc.cpython-34.pyc │ │ └── docscrape_sphinx.cpython-34.pyc │ ├── numpydoc.py │ ├── docscrape_sphinx.py │ └── docscrape.py ├── ordinal_1.png ├── reference.rst ├── index.rst ├── Makefile ├── make.bat └── conf.py ├── .gitignore ├── MANIFEST.in ├── setup.cfg ├── setup.py ├── README.rst ├── .github └── workflows │ └── build.yml ├── .travis.yml ├── LICENSE.txt └── examples └── housing.py /mord/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/numpy_ext/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mord/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | doc/_build -------------------------------------------------------------------------------- /mord/datasets/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include mord/datasets *.csv *.rst 2 | -------------------------------------------------------------------------------- /doc/ordinal_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/ordinal_1.png -------------------------------------------------------------------------------- /doc/numpy_ext/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/__init__.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/docscrape.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/docscrape.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/numpydoc.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/numpydoc.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/docscrape_sphinx.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/docscrape_sphinx.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/__pycache__/docscrape.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/__pycache__/docscrape.cpython-34.pyc -------------------------------------------------------------------------------- /doc/numpy_ext/__pycache__/numpydoc.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/__pycache__/numpydoc.cpython-34.pyc -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = doc 3 | build-dir = doc/_build 4 | all_files = 1 5 | 6 | [upload_sphinx] 7 | upload-dir = doc/_build/html -------------------------------------------------------------------------------- /doc/numpy_ext/__pycache__/docscrape_sphinx.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabianp/mord/HEAD/doc/numpy_ext/__pycache__/docscrape_sphinx.cpython-34.pyc -------------------------------------------------------------------------------- /mord/__init__.py: -------------------------------------------------------------------------------- 1 | from .threshold_based import * 2 | from .regression_based import * 3 | from . import utils 4 | from . import threshold_based 5 | 6 | __version__ = '0.6' 7 | -------------------------------------------------------------------------------- /mord/datasets/tests/test_base.py: -------------------------------------------------------------------------------- 1 | from mord.datasets.base import load_housing 2 | 3 | 4 | def test_load_housing(): 5 | res = load_housing() 6 | assert res.data.shape == (1681, 3) 7 | assert res.target.size == 1681 8 | assert len(res.feature_names) == 3 9 | assert res.DESCR 10 | -------------------------------------------------------------------------------- /doc/reference.rst: -------------------------------------------------------------------------------- 1 | 2 | API reference 3 | ============= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | .. autoclass:: mord.LogisticAT 9 | 10 | .. autoclass:: mord.LogisticIT 11 | 12 | .. autoclass:: mord.LAD 13 | 14 | .. autoclass:: mord.MulticlassLogistic 15 | 16 | .. autoclass:: mord.OrdinalRidge 17 | -------------------------------------------------------------------------------- /mord/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class OverfittingCV: 4 | def __init__(self, n, n_iter): 5 | self.n = n 6 | self.n_iter = n_iter 7 | 8 | def __iter__(self): 9 | for i in range(self.n_iter): 10 | train = np.arange(self.n) 11 | test = train 12 | yield train, test 13 | 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='mord', 5 | version="0.6", 6 | description='Ordinal regression models', 7 | long_description=open('README.rst').read(), 8 | author='Fabian Pedregosa', 9 | author_email='f@bianp.net', 10 | url='https://pypi.python.org/pypi/mord', 11 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 12 | include_package_data=True, 13 | requires=['numpy', 'scipy', "sklearn"], 14 | ) 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/fabianp/mord.svg?branch=master 2 | :target: https://travis-ci.org/fabianp/mord 3 | 4 | mord: ordinal regression in Python 5 | ================================== 6 | 7 | Collection of Ordinal Regression algorithms in Python, following a scikit-learn compatible API. 8 | 9 | Docs: https://pythonhosted.org/mord/ 10 | 11 | Github repo: http://github.com/fabianp/mord 12 | 13 | 14 | References 15 | ---------- 16 | 17 | Pedregosa, Fabian, Francis Bach, and Alexandre Gramfort. "On the consistency of ordinal regression methods." The Journal of Machine Learning Research 18.1 (2017) `JMLR `_. 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | concurrency: 3 | group: build-${{ github.ref }} 4 | cancel-in-progress: true 5 | on: [push, workflow_dispatch] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ['3.8', '3.9', '3.10', '3.11'] 12 | name: Python ${{ matrix.python-version }} sample 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - run: python --version 20 | - run: pip install --upgrade pip pytest numpy pandas scipy scikit-learn 21 | - run: pytest 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | install: 6 | - sudo apt-get update 7 | # We do this conditionally because it saves us some downloading if the 8 | # version is the same. 9 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 10 | wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; 11 | else 12 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 13 | fi 14 | - bash miniconda.sh -b -p $HOME/miniconda 15 | - export PATH="$HOME/miniconda/bin:$PATH" 16 | - hash -r 17 | - conda config --set always_yes yes --set changeps1 no 18 | - conda update -q conda 19 | # Useful for debugging any issues with conda 20 | - conda info -a 21 | 22 | # Replace dep1 dep2 ... with your dependencies 23 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy nose scikit-learn=0.16 pandas 24 | - source activate test-environment 25 | script: 26 | - nosetests -v 27 | addons: 28 | apt: 29 | packages: 30 | - libopenblas-dev 31 | -------------------------------------------------------------------------------- /mord/datasets/descr/copenhagen_housing_survey.rst: -------------------------------------------------------------------------------- 1 | Copenhagen Housing Conditions Survey 2 | =========================== 3 | 4 | Notes 5 | ------ 6 | Data Set Characteristics: 7 | 8 | :Number of Instances: 1681 9 | 10 | :Number of Attributes: 3 categorical 11 | 12 | :Sat (attribute 1) is usually the target 13 | 14 | :Attribute Information (in order): 15 | - Sat Satisfaction of householders with their present housing circumstances, (High, Medium or Low, ordered factor). 16 | - Infl Perceived degree of influence householders have on the management of the property (High, Medium, Low). 17 | - Type Type of rental accommodation, (Tower, Atrium, Apartment, Terrace). 18 | - Cont Contact residents are afforded with other residents, (Low, High). 19 | 20 | :Missing Attribute Values: None 21 | 22 | :Creator: Venables, W. N. and Ripley, B. D. (2002) Modern Applied Statistics with S. Fourth edition. Springer. 23 | 24 | 25 | This is a copy of a dataset in the MASS package. 26 | https://cran.r-project.org/web/packages/MASS/index.html 27 | 28 | This dataset was taken from the MASS library which is maintained at the Comprehensive R Archive Network (CRAN) 29 | 30 | **References** 31 | 32 | - Madsen, M. (1976) Statistical analysis of multiple contingency tables. Two examples. Scand. J. Statist. 3, 97–106. 33 | - Cox, D. R. and Snell, E. J. (1984) Applied Statistics, Principles and Examples. Chapman & Hall. 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 3-Clause BSD License 2 | 3 | 4 | Copyright 2018 Fabian Pedregosa-Izquierdo and mord developer 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 | 17 | 18 | -------------------------------------------------------------------------------- /mord/regression_based.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn import linear_model, svm, metrics 3 | 4 | 5 | class OrdinalRidge(linear_model.Ridge): 6 | """ 7 | Overwrite Ridge from scikit-learn to use 8 | the (minus) squared error as score function. 9 | 10 | (see https://github.com/scikit-learn/scikit-learn/issues/3848 11 | on why this cannot be accomplished using a GridSearchCV object) 12 | """ 13 | 14 | def fit(self, X, y, **fit_params): 15 | self.unique_y_ = np.unique(y) 16 | super(linear_model.Ridge, self).fit(X, y, **fit_params) 17 | return self 18 | 19 | def predict(self, X): 20 | pred = np.round(super(linear_model.Ridge, self).predict(X)) 21 | pred = np.clip(pred, 0, self.unique_y_.max()) 22 | return pred 23 | 24 | def score(self, X, y): 25 | pred = self.predict(X) 26 | return - metrics.mean_squared_error(pred, y) 27 | 28 | 29 | if hasattr(svm, 'LinearSVR'): 30 | class LAD(svm.LinearSVR): 31 | """ 32 | Least Absolute Deviation 33 | """ 34 | 35 | def fit(self, X, y): 36 | self.epsilon = 0. 37 | self.unique_y_ = np.unique(y) 38 | svm.LinearSVR.fit(self, X, y) 39 | return self 40 | 41 | def predict(self, X): 42 | pred = np.round(super(svm.LinearSVR, self).predict(X)) 43 | pred = np.clip(pred, 0, self.unique_y_.max()) 44 | return pred 45 | 46 | def score(self, X, y): 47 | pred = self.predict(X) 48 | return - metrics.mean_absolute_error(pred, y) 49 | 50 | -------------------------------------------------------------------------------- /examples/housing.py: -------------------------------------------------------------------------------- 1 | import mord 2 | from mord.datasets.base import load_housing 3 | from sklearn import linear_model, metrics, preprocessing 4 | 5 | data = load_housing() 6 | features = data.data 7 | 8 | le = preprocessing.LabelEncoder() 9 | le.fit(data.target) 10 | data.target = le.transform(data.target) 11 | 12 | features.loc[features.Infl == 'Low', 'Infl'] = 1 13 | features.loc[features.Infl == 'Medium', 'Infl'] = 2 14 | features.loc[features.Infl == 'High', 'Infl'] = 3 15 | 16 | features.loc[features.Cont == 'Low', 'Cont'] = 1 17 | features.loc[features.Cont == 'Medium', 'Cont'] = 2 18 | features.loc[features.Cont == 'High', 'Cont'] = 3 19 | 20 | le = preprocessing.LabelEncoder() 21 | le.fit(features.loc[:,'Type']) 22 | features.loc[:,'type_encoded'] = le.transform(features.loc[:,'Type']) 23 | 24 | X, y = features.loc[:,('Infl', 'Cont', 'type_encoded')], data.target 25 | 26 | clf1 = linear_model.LogisticRegression( 27 | solver='lbfgs', 28 | multi_class='multinomial') 29 | clf1.fit(X, y) 30 | 31 | print('Mean Absolute Error of LogisticRegression: %s' % 32 | metrics.mean_absolute_error(clf1.predict(X), y)) 33 | 34 | clf2 = mord.LogisticAT(alpha=1.) 35 | clf2.fit(X, y) 36 | print('Mean Absolute Error of LogisticAT %s' % 37 | metrics.mean_absolute_error(clf2.predict(X), y)) 38 | 39 | clf3 = mord.LogisticIT(alpha=1.) 40 | clf3.fit(X, y) 41 | print('Mean Absolute Error of LogisticIT %s' % 42 | metrics.mean_absolute_error(clf3.predict(X), y)) 43 | 44 | clf4 = mord.LogisticSE(alpha=1.) 45 | clf4.fit(X, y) 46 | print('Mean Absolute Error of LogisticSE %s' % 47 | metrics.mean_absolute_error(clf4.predict(X), y)) 48 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. mord documentation master file, created by 2 | sphinx-quickstart on Tue Jan 6 09:55:06 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | mord: Ordinal Regression in Python 7 | =================================== 8 | 9 | mord is a Python package that implements some ordinal regression methods following the scikit-learn API. 10 | 11 | .. image:: ordinal_1.png 12 | :align: center 13 | 14 | 15 | What is ordinal regression ? 16 | ----------------------------- 17 | 18 | Ordinal Regression denotes a family of statistical learning methods in which the goal is to predict a variable that is discrete and ordered. For example, predicting the movie rating on a scale of 1 to 5 starts can be considered an ordinal regression task. 19 | 20 | In this package we provide different models for the ordinal regression task. We categorize them between :ref:`threshold-based`, :ref:`regression-based` and :ref:`classification-based`. 21 | 22 | 23 | .. _threshold-based: 24 | 25 | Threshold-based models 26 | ---------------------- 27 | Threshold-based loss functions partition the real like into `k` segments and then 28 | 29 | * :class:`mord.LogisticIT` 30 | * :class:`mord.LogisticAT` 31 | 32 | 33 | .. _regression-based: 34 | 35 | Regression-based models 36 | ----------------------- 37 | 38 | * :class:`mord.OrdinalRidge` 39 | * :class:`mord.LAD` 40 | 41 | .. _classification-based: 42 | 43 | Classification-based models 44 | --------------------------- 45 | 46 | * :class:`mord.MulticlassLogistic` 47 | 48 | 49 | 50 | Citing 51 | ====== 52 | 53 | If you find this software useful, please consider citing: 54 | 55 | `Fabian Pedregosa-Izquierdo. Feature extraction and supervised learning on fMRI: from practice to theory. PhD thesis. `_ 56 | 57 | Indices and tables 58 | ================== 59 | 60 | * :ref:`genindex` 61 | * :ref:`modindex` 62 | * :ref:`search` 63 | 64 | -------------------------------------------------------------------------------- /mord/datasets/base.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, join 2 | 3 | import numpy as np 4 | from sklearn.utils import Bunch 5 | 6 | def load_housing(): 7 | from pandas import read_csv 8 | """Load and return the Copenhagen housing survey dataset 9 | (ordinal classification). 10 | 11 | ============== ============== 12 | Samples total 1681 13 | Dimensionality 3 14 | Features categorical 15 | Targets ordered categorical 16 | ============== ============== 17 | 18 | Returns 19 | ------- 20 | data : Bunch 21 | Dictionary-like object, the interesting attributes are: 22 | 'data', the data to learn, 'target', the classification targets, 23 | and 'DESCR', the full description of the dataset. 24 | 25 | Examples 26 | -------- 27 | >>> from sklearn.datasets import load_housing 28 | >>> housing = load_housing() 29 | >>> print(housing.data.shape) 30 | (506, 13) 31 | """ 32 | module_path = dirname(__file__) 33 | print(module_path) 34 | 35 | fdescr_name = join(module_path, 'descr', 'copenhagen_housing_survey.rst') 36 | with open(fdescr_name) as f: 37 | descr_text = f.read() 38 | 39 | data_file_name = join(module_path, 'data', 'copenhagen_housing_survey.csv') 40 | data = read_csv(data_file_name) 41 | 42 | ''' 43 | Original data set is formatted as a frequency table, 44 | but it's more convenient to work with the data 45 | as having one row per observation, below duplicates 46 | each obs by index based on the number the frequency ('Freq') 47 | of appearance 48 | ''' 49 | 50 | # Pandas has deprecated ".ix", so it has to be replaced with "iloc" 51 | index = np.asarray(range(0, data.shape[0])).\ 52 | repeat(data['Freq'].values) 53 | data = data.iloc[index,:] 54 | features = ('Infl', 'Type', 'Cont') 55 | 56 | return Bunch(data=data.loc[:,features], 57 | target=data.loc[:,'Sat'], 58 | feature_names=features, 59 | DESCR=descr_text) 60 | -------------------------------------------------------------------------------- /mord/multiclass.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | 5 | ## some multiclass methods that I never got to get working 6 | 7 | def obj_multiclass(x0, X, y, alpha, n_class): 8 | n_samples, n_features = X.shape 9 | W = x0.reshape((n_features + 1, n_class-1)) 10 | Wk = - W.sum(1)[:, None] 11 | W = np.concatenate((W, Wk), axis=1) 12 | X = np.concatenate((X, np.ones((n_samples, 1))), axis=1) 13 | Y = np.zeros((n_samples, n_class)) 14 | Y[:] = - 1./(n_class - 1) 15 | for i in range(n_samples): 16 | Y[i, y[i]] = 1. 17 | 18 | L = np.abs(np.arange(n_class)[:, None] - np.arange(n_class)) 19 | obj = (L[y] * np.fmax(X.dot(W) - Y, 0)).sum() / float(n_samples) 20 | 21 | Wt = W[:n_features] 22 | penalty = alpha * np.trace(Wt.T.dot(Wt)) 23 | return obj + penalty 24 | 25 | 26 | def multiclass_fit(X, y, alpha, n_class, maxiter=100000): 27 | """ 28 | Multiclass classification with absolute error cost 29 | 30 | References 31 | ---------- 32 | Lee, Yoonkyung, Yi Lin, and Grace Wahba. "Multicategory support 33 | vector machines: Theory and application to the classification of 34 | microarray data and satellite radiance data." Journal of the 35 | American Statistical Association 99.465 (2004): 67-81. 36 | """ 37 | X = np.asarray(X) 38 | y = np.asarray(y) # XXX check its made of integers 39 | n_samples, n_features = X.shape 40 | 41 | x0 = np.random.randn((n_features + 1) * (n_class - 1)) 42 | options = {'maxiter' : maxiter} 43 | sol = optimize.minimize(obj_multiclass, x0, jac=False, 44 | args=(X, y, alpha, n_class), method='L-BFGS-B', 45 | options=options) 46 | if not sol.success: 47 | print(sol.message) 48 | W = sol.x.reshape((n_features + 1, n_class-1)) 49 | Wk = - W.sum(1)[:, None] 50 | W = np.concatenate((W, Wk), axis=1) 51 | return W 52 | 53 | def multiclass_predict(X, W): 54 | n_samples, n_features = X.shape 55 | X = np.concatenate((X, np.ones((n_samples, 1))), axis=1) 56 | XW = X.dot(W) 57 | return np.argmax(XW, axis=1) 58 | 59 | -------------------------------------------------------------------------------- /mord/datasets/data/copenhagen_housing_survey.csv: -------------------------------------------------------------------------------- 1 | "Sat","Infl","Type","Cont","Freq" 2 | "Low","Low","Tower","Low",21 3 | "Medium","Low","Tower","Low",21 4 | "High","Low","Tower","Low",28 5 | "Low","Medium","Tower","Low",34 6 | "Medium","Medium","Tower","Low",22 7 | "High","Medium","Tower","Low",36 8 | "Low","High","Tower","Low",10 9 | "Medium","High","Tower","Low",11 10 | "High","High","Tower","Low",36 11 | "Low","Low","Apartment","Low",61 12 | "Medium","Low","Apartment","Low",23 13 | "High","Low","Apartment","Low",17 14 | "Low","Medium","Apartment","Low",43 15 | "Medium","Medium","Apartment","Low",35 16 | "High","Medium","Apartment","Low",40 17 | "Low","High","Apartment","Low",26 18 | "Medium","High","Apartment","Low",18 19 | "High","High","Apartment","Low",54 20 | "Low","Low","Atrium","Low",13 21 | "Medium","Low","Atrium","Low",9 22 | "High","Low","Atrium","Low",10 23 | "Low","Medium","Atrium","Low",8 24 | "Medium","Medium","Atrium","Low",8 25 | "High","Medium","Atrium","Low",12 26 | "Low","High","Atrium","Low",6 27 | "Medium","High","Atrium","Low",7 28 | "High","High","Atrium","Low",9 29 | "Low","Low","Terrace","Low",18 30 | "Medium","Low","Terrace","Low",6 31 | "High","Low","Terrace","Low",7 32 | "Low","Medium","Terrace","Low",15 33 | "Medium","Medium","Terrace","Low",13 34 | "High","Medium","Terrace","Low",13 35 | "Low","High","Terrace","Low",7 36 | "Medium","High","Terrace","Low",5 37 | "High","High","Terrace","Low",11 38 | "Low","Low","Tower","High",14 39 | "Medium","Low","Tower","High",19 40 | "High","Low","Tower","High",37 41 | "Low","Medium","Tower","High",17 42 | "Medium","Medium","Tower","High",23 43 | "High","Medium","Tower","High",40 44 | "Low","High","Tower","High",3 45 | "Medium","High","Tower","High",5 46 | "High","High","Tower","High",23 47 | "Low","Low","Apartment","High",78 48 | "Medium","Low","Apartment","High",46 49 | "High","Low","Apartment","High",43 50 | "Low","Medium","Apartment","High",48 51 | "Medium","Medium","Apartment","High",45 52 | "High","Medium","Apartment","High",86 53 | "Low","High","Apartment","High",15 54 | "Medium","High","Apartment","High",25 55 | "High","High","Apartment","High",62 56 | "Low","Low","Atrium","High",20 57 | "Medium","Low","Atrium","High",23 58 | "High","Low","Atrium","High",20 59 | "Low","Medium","Atrium","High",10 60 | "Medium","Medium","Atrium","High",22 61 | "High","Medium","Atrium","High",24 62 | "Low","High","Atrium","High",7 63 | "Medium","High","Atrium","High",10 64 | "High","High","Atrium","High",21 65 | "Low","Low","Terrace","High",57 66 | "Medium","Low","Terrace","High",23 67 | "High","Low","Terrace","High",13 68 | "Low","Medium","Terrace","High",31 69 | "Medium","Medium","Terrace","High",21 70 | "High","Medium","Terrace","High",13 71 | "Low","High","Terrace","High",5 72 | "Medium","High","Terrace","High",6 73 | "High","High","Terrace","High",13 74 | -------------------------------------------------------------------------------- /mord/tests/test_fit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import stats, optimize, sparse 3 | import mord 4 | import functools 5 | import pytest 6 | 7 | np.random.seed(0) 8 | from sklearn import datasets, metrics, linear_model 9 | 10 | n_class = 5 11 | n_samples = 100 12 | n_dim = 10 13 | 14 | X = np.random.randn(n_samples, n_dim) 15 | w = np.random.randn(n_dim) 16 | y = X.dot(w) 17 | bins = stats.mstats.mquantiles(y, np.linspace(0, 1, n_class + 1)) 18 | y = np.digitize(y, bins[:-1]) 19 | y -= y.min() 20 | 21 | 22 | # import pylab as plt 23 | # plt.scatter(X[:, 0], X[:, 1], c=y) 24 | # plt.show() 25 | 26 | def test_1(): 27 | """ 28 | Test two model in overfit mode 29 | """ 30 | clf1 = mord.OrdinalRidge(alpha=0.) 31 | clf1.fit(X, y) 32 | 33 | clf2 = mord.LogisticAT(alpha=0.) 34 | clf2.fit(X, y) 35 | 36 | # the score is - absolute error, 0 is perfect 37 | # assert clf1.score(X, y) < clf2.score(X, y) 38 | 39 | clf3 = mord.LogisticSE(alpha=0.) 40 | clf3.fit(X, y) 41 | pred3 = clf3.predict(X) 42 | pred2 = clf2.predict(X) 43 | 44 | # check that it predicts better than the surrogate 45 | # for other loss 46 | assert np.abs(pred2 - y).mean() <= np.abs(pred3 - y).mean() 47 | # # the score is - absolute error, 0 is perfect 48 | # assert_almost_equal(clf.score(X, y), 0., places=2) 49 | # 50 | # clf = mord.LogisticIT(alpha=0.) 51 | # clf.fit(X, y) 52 | # # the score is classification error, 1 is perfect 53 | # assert_almost_equal(clf.score(X, y), 1., places=2) 54 | 55 | # test on sparse matrices 56 | X_sparse = sparse.csr_matrix(X) 57 | clf4 = mord.LogisticAT(alpha=0.) 58 | clf4.fit(X_sparse, y) 59 | pred4 = clf4.predict(X_sparse) 60 | assert metrics.mean_absolute_error(y, pred4) < 1. 61 | 62 | 63 | def test_grad(): 64 | x0 = np.random.randn(n_dim + n_class - 1) 65 | x0[n_dim + 1:] = np.abs(x0[n_dim + 1:]) 66 | 67 | loss_fd = np.diag(np.ones(n_class - 1)) + \ 68 | np.diag(np.ones(n_class - 2), k=-1) 69 | loss_fd = np.vstack((loss_fd, np.zeros(n_class - 1))) 70 | loss_fd[-1, -1] = 1 # border case 71 | 72 | L = np.eye(n_class - 1) - np.diag(np.ones(n_class - 2), k=-1) 73 | 74 | def fun(x, sample_weights=None): 75 | return mord.threshold_based.obj_margin( 76 | x, X, y, 100.0, n_class, loss_fd, L, sample_weights) 77 | 78 | def grad(x, sample_weights=None): 79 | return mord.threshold_based.grad_margin( 80 | x, X, y, 100.0, n_class, loss_fd, L, sample_weights) 81 | 82 | assert optimize.check_grad(fun, grad, x0) < 1e-4, 'unweighted' 83 | 84 | sample_weights = np.random.rand(n_samples) 85 | assert ( 86 | optimize.check_grad( 87 | functools.partial(fun, sample_weights=sample_weights), 88 | functools.partial(grad, sample_weights=sample_weights), 89 | x0) < 1e-4 90 | ), 'weighted' 91 | 92 | 93 | def test_binary_class(): 94 | Xc, yc = datasets.make_classification(n_classes=2, n_samples=1000) 95 | clf = linear_model.LogisticRegression(C=1e6) 96 | clf.fit(Xc[:500], yc[:500]) 97 | pred_lr = clf.predict(Xc[500:]) 98 | 99 | clf = mord.LogisticAT(alpha=1e-6) 100 | clf.fit(Xc[:500], yc[:500]) 101 | pred_at = clf.predict(Xc[500:]) 102 | assert np.abs(pred_lr - pred_at).mean() == pytest.approx(0) 103 | 104 | clf2 = mord.LogisticSE(alpha=1e-6) 105 | clf2.fit(Xc[:500], yc[:500]) 106 | pred_at = clf2.predict(Xc[500:]) 107 | assert np.abs(pred_lr - pred_at).mean() == pytest.approx(0) 108 | 109 | # def test_performance(): 110 | # clf1 = mord.LogisticAT() 111 | # clf1.fit(X, y) 112 | # assert_almost_equal(clf1.score(X, y) < ) 113 | 114 | 115 | def test_predict_proba_nonnegative(): 116 | """ 117 | Test that predict_proba() function outputs a tuple of non-negative values 118 | """ 119 | 120 | def check_for_negative_prob(proba): 121 | for p in np.ravel(proba): 122 | assert np.round(p,7) >= 0 123 | 124 | clf = mord.LogisticAT(alpha=0.) 125 | clf.fit(X, y) 126 | check_for_negative_prob(clf.predict_proba(X)) 127 | 128 | clf2 = mord.LogisticIT(alpha=0.) 129 | clf2.fit(X, y) 130 | check_for_negative_prob(clf2.predict_proba(X)) 131 | 132 | clf3 = mord.LogisticSE(alpha=0.) 133 | clf3.fit(X, y) 134 | check_for_negative_prob(clf3.predict_proba(X)) 135 | -------------------------------------------------------------------------------- /doc/numpy_ext/numpydoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | ======== 3 | numpydoc 4 | ======== 5 | 6 | Sphinx extension that handles docstrings in the Numpy standard format. [1] 7 | 8 | It will: 9 | 10 | - Convert Parameters etc. sections to field lists. 11 | - Convert See Also section to a See also entry. 12 | - Renumber references. 13 | - Extract the signature from the docstring, if it can't be determined 14 | otherwise. 15 | 16 | .. [1] http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard 17 | 18 | """ 19 | 20 | from __future__ import unicode_literals 21 | 22 | import sys # Only needed to check Python version 23 | import os 24 | import re 25 | import pydoc 26 | from .docscrape_sphinx import get_doc_object 27 | from .docscrape_sphinx import SphinxDocString 28 | import inspect 29 | 30 | 31 | def mangle_docstrings(app, what, name, obj, options, lines, 32 | reference_offset=[0]): 33 | 34 | cfg = dict(use_plots=app.config.numpydoc_use_plots, 35 | show_class_members=app.config.numpydoc_show_class_members) 36 | 37 | if what == 'module': 38 | # Strip top title 39 | title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', 40 | re.I | re.S) 41 | lines[:] = title_re.sub('', "\n".join(lines)).split("\n") 42 | else: 43 | doc = get_doc_object(obj, what, "\n".join(lines), config=cfg) 44 | if sys.version_info[0] < 3: 45 | lines[:] = unicode(doc).splitlines() 46 | else: 47 | lines[:] = str(doc).splitlines() 48 | 49 | if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ 50 | obj.__name__: 51 | if hasattr(obj, '__module__'): 52 | v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__)) 53 | else: 54 | v = dict(full_name=obj.__name__) 55 | lines += [u'', u'.. htmlonly::', ''] 56 | lines += [u' %s' % x for x in 57 | (app.config.numpydoc_edit_link % v).split("\n")] 58 | 59 | # replace reference numbers so that there are no duplicates 60 | references = [] 61 | for line in lines: 62 | line = line.strip() 63 | m = re.match(r'^.. \[([a-z0-9_.-])\]', line, re.I) 64 | if m: 65 | references.append(m.group(1)) 66 | 67 | # start renaming from the longest string, to avoid overwriting parts 68 | references.sort(key=lambda x: -len(x)) 69 | if references: 70 | for i, line in enumerate(lines): 71 | for r in references: 72 | if re.match(r'^\d+$', r): 73 | new_r = "R%d" % (reference_offset[0] + int(r)) 74 | else: 75 | new_r = u"%s%d" % (r, reference_offset[0]) 76 | lines[i] = lines[i].replace(u'[%s]_' % r, 77 | u'[%s]_' % new_r) 78 | lines[i] = lines[i].replace(u'.. [%s]' % r, 79 | u'.. [%s]' % new_r) 80 | 81 | reference_offset[0] += len(references) 82 | 83 | 84 | def mangle_signature(app, what, name, obj, 85 | options, sig, retann): 86 | # Do not try to inspect classes that don't define `__init__` 87 | if (inspect.isclass(obj) and 88 | (not hasattr(obj, '__init__') or 89 | 'initializes x; see ' in pydoc.getdoc(obj.__init__))): 90 | return '', '' 91 | 92 | if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): 93 | return 94 | if not hasattr(obj, '__doc__'): 95 | return 96 | 97 | doc = SphinxDocString(pydoc.getdoc(obj)) 98 | if doc['Signature']: 99 | sig = re.sub("^[^(]*", "", doc['Signature']) 100 | return sig, '' 101 | 102 | 103 | def setup(app, get_doc_object_=get_doc_object): 104 | global get_doc_object 105 | get_doc_object = get_doc_object_ 106 | 107 | if sys.version_info[0] < 3: 108 | app.connect(b'autodoc-process-docstring', mangle_docstrings) 109 | app.connect(b'autodoc-process-signature', mangle_signature) 110 | else: 111 | app.connect('autodoc-process-docstring', mangle_docstrings) 112 | app.connect('autodoc-process-signature', mangle_signature) 113 | app.add_config_value('numpydoc_edit_link', None, False) 114 | app.add_config_value('numpydoc_use_plots', None, False) 115 | app.add_config_value('numpydoc_show_class_members', True, True) 116 | 117 | # Extra mangling domains 118 | app.add_domain(NumpyPythonDomain) 119 | app.add_domain(NumpyCDomain) 120 | 121 | #----------------------------------------------------------------------------- 122 | # Docstring-mangling domains 123 | #----------------------------------------------------------------------------- 124 | 125 | try: 126 | import sphinx # lazy to avoid test dependency 127 | except ImportError: 128 | CDomain = PythonDomain = object 129 | else: 130 | from sphinx.domains.c import CDomain 131 | from sphinx.domains.python import PythonDomain 132 | 133 | 134 | class ManglingDomainBase(object): 135 | directive_mangling_map = {} 136 | 137 | def __init__(self, *a, **kw): 138 | super(ManglingDomainBase, self).__init__(*a, **kw) 139 | self.wrap_mangling_directives() 140 | 141 | def wrap_mangling_directives(self): 142 | for name, objtype in self.directive_mangling_map.items(): 143 | self.directives[name] = wrap_mangling_directive( 144 | self.directives[name], objtype) 145 | 146 | 147 | class NumpyPythonDomain(ManglingDomainBase, PythonDomain): 148 | name = 'np' 149 | directive_mangling_map = { 150 | 'function': 'function', 151 | 'class': 'class', 152 | 'exception': 'class', 153 | 'method': 'function', 154 | 'classmethod': 'function', 155 | 'staticmethod': 'function', 156 | 'attribute': 'attribute', 157 | } 158 | 159 | 160 | class NumpyCDomain(ManglingDomainBase, CDomain): 161 | name = 'np-c' 162 | directive_mangling_map = { 163 | 'function': 'function', 164 | 'member': 'attribute', 165 | 'macro': 'function', 166 | 'type': 'class', 167 | 'var': 'object', 168 | } 169 | 170 | 171 | def wrap_mangling_directive(base_directive, objtype): 172 | class directive(base_directive): 173 | def run(self): 174 | env = self.state.document.settings.env 175 | 176 | name = None 177 | if self.arguments: 178 | m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) 179 | name = m.group(2).strip() 180 | 181 | if not name: 182 | name = self.arguments[0] 183 | 184 | lines = list(self.content) 185 | mangle_docstrings(env.app, objtype, name, None, None, lines) 186 | # local import to avoid testing dependency 187 | from docutils.statemachine import ViewList 188 | self.content = ViewList(lines, self.content.parent) 189 | 190 | return base_directive.run(self) 191 | 192 | return directive 193 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mord.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mord.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/mord" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mord" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mord.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mord.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc/numpy_ext/docscrape_sphinx.py: -------------------------------------------------------------------------------- 1 | import re 2 | import inspect 3 | import textwrap 4 | import pydoc 5 | from .docscrape import NumpyDocString 6 | from .docscrape import FunctionDoc 7 | from .docscrape import ClassDoc 8 | 9 | 10 | class SphinxDocString(NumpyDocString): 11 | def __init__(self, docstring, config=None): 12 | config = {} if config is None else config 13 | self.use_plots = config.get('use_plots', False) 14 | NumpyDocString.__init__(self, docstring, config=config) 15 | 16 | # string conversion routines 17 | def _str_header(self, name, symbol='`'): 18 | return ['.. rubric:: ' + name, ''] 19 | 20 | def _str_field_list(self, name): 21 | return [':' + name + ':'] 22 | 23 | def _str_indent(self, doc, indent=4): 24 | out = [] 25 | for line in doc: 26 | out += [' ' * indent + line] 27 | return out 28 | 29 | def _str_signature(self): 30 | return [''] 31 | if self['Signature']: 32 | return ['``%s``' % self['Signature']] + [''] 33 | else: 34 | return [''] 35 | 36 | def _str_summary(self): 37 | return self['Summary'] + [''] 38 | 39 | def _str_extended_summary(self): 40 | return self['Extended Summary'] + [''] 41 | 42 | def _str_param_list(self, name): 43 | out = [] 44 | if self[name]: 45 | out += self._str_field_list(name) 46 | out += [''] 47 | for param, param_type, desc in self[name]: 48 | out += self._str_indent(['**%s** : %s' % (param.strip(), 49 | param_type)]) 50 | out += [''] 51 | out += self._str_indent(desc, 8) 52 | out += [''] 53 | return out 54 | 55 | @property 56 | def _obj(self): 57 | if hasattr(self, '_cls'): 58 | return self._cls 59 | elif hasattr(self, '_f'): 60 | return self._f 61 | return None 62 | 63 | def _str_member_list(self, name): 64 | """ 65 | Generate a member listing, autosummary:: table where possible, 66 | and a table where not. 67 | 68 | """ 69 | out = [] 70 | if self[name]: 71 | out += ['.. rubric:: %s' % name, ''] 72 | prefix = getattr(self, '_name', '') 73 | 74 | if prefix: 75 | prefix = '~%s.' % prefix 76 | 77 | autosum = [] 78 | others = [] 79 | for param, param_type, desc in self[name]: 80 | param = param.strip() 81 | if not self._obj or hasattr(self._obj, param): 82 | autosum += [" %s%s" % (prefix, param)] 83 | else: 84 | others.append((param, param_type, desc)) 85 | 86 | if autosum: 87 | # GAEL: Toctree commented out below because it creates 88 | # hundreds of sphinx warnings 89 | # out += ['.. autosummary::', ' :toctree:', ''] 90 | out += ['.. autosummary::', ''] 91 | out += autosum 92 | 93 | if others: 94 | maxlen_0 = max([len(x[0]) for x in others]) 95 | maxlen_1 = max([len(x[1]) for x in others]) 96 | hdr = "=" * maxlen_0 + " " + "=" * maxlen_1 + " " + "=" * 10 97 | fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1) 98 | n_indent = maxlen_0 + maxlen_1 + 4 99 | out += [hdr] 100 | for param, param_type, desc in others: 101 | out += [fmt % (param.strip(), param_type)] 102 | out += self._str_indent(desc, n_indent) 103 | out += [hdr] 104 | out += [''] 105 | return out 106 | 107 | def _str_section(self, name): 108 | out = [] 109 | if self[name]: 110 | out += self._str_header(name) 111 | out += [''] 112 | content = textwrap.dedent("\n".join(self[name])).split("\n") 113 | out += content 114 | out += [''] 115 | return out 116 | 117 | def _str_see_also(self, func_role): 118 | out = [] 119 | if self['See Also']: 120 | see_also = super(SphinxDocString, self)._str_see_also(func_role) 121 | out = ['.. seealso::', ''] 122 | out += self._str_indent(see_also[2:]) 123 | return out 124 | 125 | def _str_warnings(self): 126 | out = [] 127 | if self['Warnings']: 128 | out = ['.. warning::', ''] 129 | out += self._str_indent(self['Warnings']) 130 | return out 131 | 132 | def _str_index(self): 133 | idx = self['index'] 134 | out = [] 135 | if len(idx) == 0: 136 | return out 137 | 138 | out += ['.. index:: %s' % idx.get('default', '')] 139 | for section, references in idx.iteritems(): 140 | if section == 'default': 141 | continue 142 | elif section == 'refguide': 143 | out += [' single: %s' % (', '.join(references))] 144 | else: 145 | out += [' %s: %s' % (section, ','.join(references))] 146 | return out 147 | 148 | def _str_references(self): 149 | out = [] 150 | if self['References']: 151 | out += self._str_header('References') 152 | if isinstance(self['References'], str): 153 | self['References'] = [self['References']] 154 | out.extend(self['References']) 155 | out += [''] 156 | # Latex collects all references to a separate bibliography, 157 | # so we need to insert links to it 158 | import sphinx # local import to avoid test dependency 159 | if sphinx.__version__ >= "0.6": 160 | out += ['.. only:: latex', ''] 161 | else: 162 | out += ['.. latexonly::', ''] 163 | items = [] 164 | for line in self['References']: 165 | m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) 166 | if m: 167 | items.append(m.group(1)) 168 | out += [' ' + ", ".join(["[%s]_" % item for item in items]), ''] 169 | return out 170 | 171 | def _str_examples(self): 172 | examples_str = "\n".join(self['Examples']) 173 | 174 | if (self.use_plots and 'import matplotlib' in examples_str 175 | and 'plot::' not in examples_str): 176 | out = [] 177 | out += self._str_header('Examples') 178 | out += ['.. plot::', ''] 179 | out += self._str_indent(self['Examples']) 180 | out += [''] 181 | return out 182 | else: 183 | return self._str_section('Examples') 184 | 185 | def __str__(self, indent=0, func_role="obj"): 186 | out = [] 187 | out += self._str_signature() 188 | out += self._str_index() + [''] 189 | out += self._str_summary() 190 | out += self._str_extended_summary() 191 | for param_list in ('Parameters', 'Returns', 'Raises'): 192 | out += self._str_param_list(param_list) 193 | out += self._str_warnings() 194 | out += self._str_see_also(func_role) 195 | out += self._str_section('Notes') 196 | out += self._str_references() 197 | out += self._str_examples() 198 | for param_list in ('Attributes', 'Methods'): 199 | out += self._str_member_list(param_list) 200 | out = self._str_indent(out, indent) 201 | return '\n'.join(out) 202 | 203 | 204 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 205 | def __init__(self, obj, doc=None, config={}): 206 | self.use_plots = config.get('use_plots', False) 207 | FunctionDoc.__init__(self, obj, doc=doc, config=config) 208 | 209 | 210 | class SphinxClassDoc(SphinxDocString, ClassDoc): 211 | def __init__(self, obj, doc=None, func_doc=None, config={}): 212 | self.use_plots = config.get('use_plots', False) 213 | ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) 214 | 215 | 216 | class SphinxObjDoc(SphinxDocString): 217 | def __init__(self, obj, doc=None, config=None): 218 | self._f = obj 219 | SphinxDocString.__init__(self, doc, config=config) 220 | 221 | 222 | def get_doc_object(obj, what=None, doc=None, config={}): 223 | if what is None: 224 | if inspect.isclass(obj): 225 | what = 'class' 226 | elif inspect.ismodule(obj): 227 | what = 'module' 228 | elif callable(obj): 229 | what = 'function' 230 | else: 231 | what = 'object' 232 | if what == 'class': 233 | return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, 234 | config=config) 235 | elif what in ('function', 'method'): 236 | return SphinxFunctionDoc(obj, doc=doc, config=config) 237 | else: 238 | if doc is None: 239 | doc = pydoc.getdoc(obj) 240 | return SphinxObjDoc(obj, doc, config=config) 241 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # mord documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jan 6 09:55:06 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.mathjax', 36 | 'sphinx.ext.ifconfig', 37 | 'sphinx.ext.viewcode', 38 | 'numpy_ext.numpydoc' 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix of source filenames. 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'mord' 55 | copyright = u'2015, Fabian Pedregosa' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | import mord 63 | version = mord.__version__ 64 | # The full version, including alpha/beta/rc tags. 65 | release = mord.__version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | #language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = ['_build'] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | #default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | #add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | #add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | #show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = 'sphinx' 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | #modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | #keep_warnings = False 104 | 105 | 106 | # -- Options for HTML output ---------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | html_theme = 'sphinxdoc' 111 | 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Output file base name for HTML help builder. 189 | htmlhelp_basename = 'morddoc' 190 | 191 | 192 | # -- Options for LaTeX output --------------------------------------------- 193 | 194 | latex_elements = { 195 | # The paper size ('letterpaper' or 'a4paper'). 196 | #'papersize': 'letterpaper', 197 | 198 | # The font size ('10pt', '11pt' or '12pt'). 199 | #'pointsize': '10pt', 200 | 201 | # Additional stuff for the LaTeX preamble. 202 | #'preamble': '', 203 | } 204 | 205 | # Grouping the document tree into LaTeX files. List of tuples 206 | # (source start file, target name, title, 207 | # author, documentclass [howto, manual, or own class]). 208 | latex_documents = [ 209 | ('index', 'mord.tex', u'mord Documentation', 210 | u'Fabian Pedregosa', 'manual'), 211 | ] 212 | 213 | # The name of an image file (relative to this directory) to place at the top of 214 | # the title page. 215 | #latex_logo = None 216 | 217 | # For "manual" documents, if this is true, then toplevel headings are parts, 218 | # not chapters. 219 | #latex_use_parts = False 220 | 221 | # If true, show page references after internal links. 222 | #latex_show_pagerefs = False 223 | 224 | # If true, show URL addresses after external links. 225 | #latex_show_urls = False 226 | 227 | # Documents to append as an appendix to all manuals. 228 | #latex_appendices = [] 229 | 230 | # If false, no module index is generated. 231 | #latex_domain_indices = True 232 | 233 | 234 | # -- Options for manual page output --------------------------------------- 235 | 236 | # One entry per manual page. List of tuples 237 | # (source start file, name, description, authors, manual section). 238 | man_pages = [ 239 | ('index', 'mord', u'mord Documentation', 240 | [u'Fabian Pedregosa'], 1) 241 | ] 242 | 243 | # If true, show URL addresses after external links. 244 | #man_show_urls = False 245 | 246 | 247 | # -- Options for Texinfo output ------------------------------------------- 248 | 249 | # Grouping the document tree into Texinfo files. List of tuples 250 | # (source start file, target name, title, author, 251 | # dir menu entry, description, category) 252 | texinfo_documents = [ 253 | ('index', 'mord', u'mord Documentation', 254 | u'Fabian Pedregosa', 'mord', 'One line description of project.', 255 | 'Miscellaneous'), 256 | ] 257 | 258 | # Documents to append as an appendix to all manuals. 259 | #texinfo_appendices = [] 260 | 261 | # If false, no module index is generated. 262 | #texinfo_domain_indices = True 263 | 264 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 265 | #texinfo_show_urls = 'footnote' 266 | 267 | # If true, do not generate a @detailmenu in the "Top" node's menu. 268 | #texinfo_no_detailmenu = False 269 | -------------------------------------------------------------------------------- /mord/threshold_based.py: -------------------------------------------------------------------------------- 1 | """ 2 | some ordinal regression algorithms 3 | 4 | This implements the margin-based ordinal regression methods described 5 | in http://arxiv.org/abs/1408.2327 6 | """ 7 | import numpy as np 8 | from scipy import optimize 9 | from sklearn import base, metrics 10 | from sklearn.utils.validation import check_X_y 11 | 12 | 13 | def sigmoid(t): 14 | # sigmoid function, 1 / (1 + exp(-t)) 15 | # stable computation 16 | idx = t > 0 17 | out = np.zeros_like(t) 18 | out[idx] = 1. / (1 + np.exp(-t[idx])) 19 | exp_t = np.exp(t[~idx]) 20 | out[~idx] = exp_t / (1. + exp_t) 21 | return out 22 | 23 | 24 | def log_loss(Z): 25 | # stable computation of the logistic loss 26 | idx = Z > 0 27 | out = np.zeros_like(Z) 28 | out[idx] = np.log(1 + np.exp(-Z[idx])) 29 | out[~idx] = (-Z[~idx] + np.log(1 + np.exp(Z[~idx]))) 30 | return out 31 | 32 | 33 | def obj_margin(x0, X, y, alpha, n_class, weights, L, sample_weight): 34 | """ 35 | Objective function for the general margin-based formulation 36 | """ 37 | 38 | w = x0[:X.shape[1]] 39 | c = x0[X.shape[1]:] 40 | theta = L.dot(c) 41 | loss_fd = weights[y] 42 | 43 | Xw = X.dot(w) 44 | Alpha = theta[:, None] - Xw # (n_class - 1, n_samples) 45 | S = np.sign(np.arange(n_class - 1)[:, None] - y + 0.5) 46 | 47 | err = loss_fd.T * log_loss(S * Alpha) 48 | if sample_weight is not None: 49 | err *= sample_weight 50 | obj = np.sum(err) 51 | obj += alpha * 0.5 * (np.dot(w, w)) 52 | return obj 53 | 54 | 55 | def grad_margin(x0, X, y, alpha, n_class, weights, L, sample_weight): 56 | """ 57 | Gradient for the general margin-based formulation 58 | """ 59 | 60 | w = x0[:X.shape[1]] 61 | c = x0[X.shape[1]:] 62 | theta = L.dot(c) 63 | loss_fd = weights[y] 64 | 65 | Xw = X.dot(w) 66 | Alpha = theta[:, None] - Xw # (n_class - 1, n_samples) 67 | S = np.sign(np.arange(n_class - 1)[:, None] - y + 0.5) 68 | # Alpha[idx] *= -1 69 | # W[idx.T] *= -1 70 | 71 | Sigma = S * loss_fd.T * sigmoid(-S * Alpha) 72 | if sample_weight is not None: 73 | Sigma *= sample_weight 74 | 75 | grad_w = X.T.dot(Sigma.sum(0)) + alpha * w 76 | 77 | grad_theta = -Sigma.sum(1) 78 | grad_c = L.T.dot(grad_theta) 79 | return np.concatenate((grad_w, grad_c), axis=0) 80 | 81 | 82 | def threshold_fit(X, y, alpha, n_class, mode='AE', 83 | max_iter=1000, verbose=False, tol=1e-12, 84 | sample_weight=None): 85 | """ 86 | Solve the general threshold-based ordinal regression model 87 | using the logistic loss as surrogate of the 0-1 loss 88 | 89 | Parameters 90 | ---------- 91 | mode : string, one of {'AE', '0-1', 'SE'} 92 | 93 | """ 94 | 95 | X, y = check_X_y(X, y, accept_sparse='csr') 96 | unique_y = np.sort(np.unique(y)) 97 | if not np.all(unique_y == np.arange(unique_y.size)): 98 | raise ValueError( 99 | 'Values in y must be %s, instead got %s' 100 | % (np.arange(unique_y.size), unique_y)) 101 | 102 | n_samples, n_features = X.shape 103 | 104 | # convert from c to theta 105 | L = np.zeros((n_class - 1, n_class - 1)) 106 | L[np.tril_indices(n_class-1)] = 1. 107 | 108 | if mode == 'AE': 109 | # loss forward difference 110 | loss_fd = np.ones((n_class, n_class - 1)) 111 | elif mode == '0-1': 112 | loss_fd = np.diag(np.ones(n_class - 1)) + \ 113 | np.diag(np.ones(n_class - 2), k=-1) 114 | loss_fd = np.vstack((loss_fd, np.zeros(n_class - 1))) 115 | loss_fd[-1, -1] = 1 # border case 116 | elif mode == 'SE': 117 | a = np.arange(n_class-1) 118 | b = np.arange(n_class) 119 | loss_fd = np.abs((a - b[:, None])**2 - (a - b[:, None]+1)**2) 120 | else: 121 | raise NotImplementedError 122 | 123 | x0 = np.zeros(n_features + n_class - 1) 124 | x0[X.shape[1]:] = np.arange(n_class - 1) 125 | options = {'maxiter' : max_iter, 'disp': verbose} 126 | if n_class > 2: 127 | bounds = [(None, None)] * (n_features + 1) + \ 128 | [(0, None)] * (n_class - 2) 129 | else: 130 | bounds = None 131 | 132 | sol = optimize.minimize(obj_margin, x0, method='L-BFGS-B', 133 | jac=grad_margin, bounds=bounds, options=options, 134 | args=(X, y, alpha, n_class, loss_fd, L, sample_weight), 135 | tol=tol) 136 | if verbose and not sol.success: 137 | print(sol.message) 138 | 139 | w, c = sol.x[:X.shape[1]], sol.x[X.shape[1]:] 140 | theta = L.dot(c) 141 | return w, theta 142 | 143 | 144 | def threshold_predict(X, w, theta): 145 | """ 146 | Class numbers are assumed to be between 0 and k-1 147 | """ 148 | tmp = theta[:, None] - np.asarray(X.dot(w)) 149 | pred = np.sum(tmp < 0, axis=0).astype(int) 150 | return pred 151 | 152 | 153 | def threshold_proba(X, w, theta): 154 | """ 155 | Class numbers are assumed to be between 0 and k-1. Assumes 156 | the `sigmoid` link function is used. 157 | """ 158 | eta = theta[:, None] - np.asarray(X.dot(w), dtype=np.float64) 159 | prob = np.pad( 160 | sigmoid(eta).T, 161 | pad_width=((0, 0), (1, 1)), 162 | mode='constant', 163 | constant_values=(0, 1)) 164 | return np.diff(prob) 165 | 166 | 167 | class LogisticAT(base.BaseEstimator): 168 | """ 169 | Classifier that implements the ordinal logistic model (All-Threshold variant) 170 | 171 | Parameters 172 | ---------- 173 | alpha: float 174 | Regularization parameter. Zero is no regularization, higher values 175 | increate the squared l2 regularization. 176 | 177 | References 178 | ---------- 179 | J. D. M. Rennie and N. Srebro, "Loss Functions for Preference Levels : 180 | Regression with Discrete Ordered Labels," in Proceedings of the IJCAI 181 | Multidisciplinary Workshop on Advances in Preference Handling, 2005. 182 | """ 183 | def __init__(self, alpha=1., verbose=0, max_iter=1000): 184 | self.alpha = alpha 185 | self.verbose = verbose 186 | self.max_iter = max_iter 187 | 188 | def fit(self, X, y, sample_weight=None): 189 | _y = np.array(y).astype(int) 190 | if np.abs(_y - y).sum() > 0.1: 191 | raise ValueError('y must only contain integer values') 192 | self.classes_ = np.unique(y) 193 | self.n_class_ = self.classes_.max() - self.classes_.min() + 1 194 | y_tmp = y - y.min() # we need classes that start at zero 195 | self.coef_, self.theta_ = threshold_fit( 196 | X, y_tmp, self.alpha, self.n_class_, mode='AE', 197 | verbose=self.verbose, max_iter=self.max_iter, 198 | sample_weight=sample_weight) 199 | return self 200 | 201 | def predict(self, X): 202 | return threshold_predict(X, self.coef_, self.theta_) +\ 203 | self.classes_.min() 204 | 205 | def predict_proba(self, X): 206 | return threshold_proba(X, self.coef_, self.theta_) 207 | 208 | def score(self, X, y, sample_weight=None): 209 | pred = self.predict(X) 210 | return -metrics.mean_absolute_error( 211 | pred, 212 | y, 213 | sample_weight=sample_weight) 214 | 215 | 216 | class LogisticIT(base.BaseEstimator): 217 | """ 218 | Classifier that implements the ordinal logistic model 219 | (Immediate-Threshold variant). 220 | 221 | Contrary to the OrdinalLogistic model, this variant 222 | minimizes a convex surrogate of the 0-1 loss, hence 223 | the score associated with this object is the accuracy 224 | score, i.e. the same score used in multiclass 225 | classification methods (sklearn.metrics.accuracy_score). 226 | 227 | Parameters 228 | ---------- 229 | alpha: float 230 | Regularization parameter. Zero is no regularization, higher values 231 | increate the squared l2 regularization. 232 | 233 | References 234 | ---------- 235 | J. D. M. Rennie and N. Srebro, "Loss Functions for Preference Levels : 236 | Regression with Discrete Ordered Labels," in Proceedings of the IJCAI 237 | Multidisciplinary Workshop on Advances in Preference Handling, 2005. 238 | """ 239 | def __init__(self, alpha=1., verbose=0, max_iter=1000): 240 | self.alpha = alpha 241 | self.verbose = verbose 242 | self.max_iter = max_iter 243 | 244 | def fit(self, X, y, sample_weight=None): 245 | _y = np.array(y).astype(int) 246 | if np.abs(_y - y).sum() > 0.1: 247 | raise ValueError('y must only contain integer values') 248 | self.classes_ = np.unique(y) 249 | self.n_class_ = self.classes_.max() - self.classes_.min() + 1 250 | y_tmp = y - y.min() # we need classes that start at zero 251 | self.coef_, self.theta_ = threshold_fit( 252 | X, y_tmp, self.alpha, self.n_class_, 253 | mode='0-1', verbose=self.verbose, max_iter=self.max_iter, 254 | sample_weight=sample_weight) 255 | return self 256 | 257 | def predict(self, X): 258 | return threshold_predict(X, self.coef_, self.theta_) +\ 259 | self.classes_.min() 260 | 261 | def predict_proba(self, X): 262 | return threshold_proba(X, self.coef_, self.theta_) 263 | 264 | def score(self, X, y, sample_weight=None): 265 | pred = self.predict(X) 266 | return metrics.accuracy_score( 267 | pred, 268 | y, 269 | sample_weight=sample_weight) 270 | 271 | 272 | class LogisticSE(base.BaseEstimator): 273 | """ 274 | Classifier that implements the ordinal logistic model 275 | (Squared Error variant). 276 | 277 | Contrary to the OrdinalLogistic model, this variant 278 | minimizes a convex surrogate of the 0-1 (?) loss ... 279 | 280 | TODO: double check this description (XXX) 281 | 282 | Parameters 283 | ---------- 284 | alpha: float 285 | Regularization parameter. Zero is no regularization, higher values 286 | increase the squared l2 regularization. 287 | 288 | References 289 | ---------- 290 | J. D. M. Rennie and N. Srebro, "Loss Functions for Preference Levels : 291 | Regression with Discrete Ordered Labels," in Proceedings of the IJCAI 292 | Multidisciplinary Workshop on Advances in Preference Handling, 2005. 293 | """ 294 | def __init__(self, alpha=1., verbose=0, max_iter=100000): 295 | self.alpha = alpha 296 | self.verbose = verbose 297 | self.max_iter = max_iter 298 | 299 | def fit(self, X, y, sample_weight=None): 300 | _y = np.array(y).astype(int) 301 | if np.abs(_y - y).sum() > 1e-3: 302 | raise ValueError('y must only contain integer values') 303 | self.classes_ = np.unique(y) 304 | self.n_class_ = self.classes_.max() - self.classes_.min() + 1 305 | y_tmp = y - y.min() # we need classes that start at zero 306 | self.coef_, self.theta_ = threshold_fit( 307 | X, y_tmp, self.alpha, self.n_class_, 308 | mode='SE', verbose=self.verbose, max_iter=self.max_iter, 309 | sample_weight=sample_weight) 310 | return self 311 | 312 | def predict(self, X): 313 | return threshold_predict(X, self.coef_, self.theta_) +\ 314 | self.classes_.min() 315 | 316 | def predict_proba(self, X): 317 | return threshold_proba(X, self.coef_, self.theta_) 318 | 319 | def score(self, X, y, sample_weight=None): 320 | pred = self.predict(X) 321 | return -metrics.mean_squared_error( 322 | pred, 323 | y, 324 | sample_weight=sample_weight) 325 | -------------------------------------------------------------------------------- /doc/numpy_ext/docscrape.py: -------------------------------------------------------------------------------- 1 | """Extract reference documentation from the NumPy source tree. 2 | 3 | """ 4 | 5 | import inspect 6 | import textwrap 7 | import re 8 | import pydoc 9 | from warnings import warn 10 | # Try Python 2 first, otherwise load from Python 3 11 | try: 12 | from StringIO import StringIO 13 | except: 14 | from io import StringIO 15 | 16 | 17 | class Reader(object): 18 | """A line-based string reader. 19 | 20 | """ 21 | def __init__(self, data): 22 | """ 23 | Parameters 24 | ---------- 25 | data : str 26 | String with lines separated by '\n'. 27 | 28 | """ 29 | if isinstance(data, list): 30 | self._str = data 31 | else: 32 | self._str = data.split('\n') # store string as list of lines 33 | 34 | self.reset() 35 | 36 | def __getitem__(self, n): 37 | return self._str[n] 38 | 39 | def reset(self): 40 | self._l = 0 # current line nr 41 | 42 | def read(self): 43 | if not self.eof(): 44 | out = self[self._l] 45 | self._l += 1 46 | return out 47 | else: 48 | return '' 49 | 50 | def seek_next_non_empty_line(self): 51 | for l in self[self._l:]: 52 | if l.strip(): 53 | break 54 | else: 55 | self._l += 1 56 | 57 | def eof(self): 58 | return self._l >= len(self._str) 59 | 60 | def read_to_condition(self, condition_func): 61 | start = self._l 62 | for line in self[start:]: 63 | if condition_func(line): 64 | return self[start:self._l] 65 | self._l += 1 66 | if self.eof(): 67 | return self[start:self._l + 1] 68 | return [] 69 | 70 | def read_to_next_empty_line(self): 71 | self.seek_next_non_empty_line() 72 | 73 | def is_empty(line): 74 | return not line.strip() 75 | return self.read_to_condition(is_empty) 76 | 77 | def read_to_next_unindented_line(self): 78 | def is_unindented(line): 79 | return (line.strip() and (len(line.lstrip()) == len(line))) 80 | return self.read_to_condition(is_unindented) 81 | 82 | def peek(self, n=0): 83 | if self._l + n < len(self._str): 84 | return self[self._l + n] 85 | else: 86 | return '' 87 | 88 | def is_empty(self): 89 | return not ''.join(self._str).strip() 90 | 91 | 92 | class NumpyDocString(object): 93 | def __init__(self, docstring, config={}): 94 | docstring = textwrap.dedent(docstring).split('\n') 95 | 96 | self._doc = Reader(docstring) 97 | self._parsed_data = { 98 | 'Signature': '', 99 | 'Summary': [''], 100 | 'Extended Summary': [], 101 | 'Parameters': [], 102 | 'Returns': [], 103 | 'Raises': [], 104 | 'Warns': [], 105 | 'Other Parameters': [], 106 | 'Attributes': [], 107 | 'Methods': [], 108 | 'See Also': [], 109 | 'Notes': [], 110 | 'Warnings': [], 111 | 'References': '', 112 | 'Examples': '', 113 | 'index': {} 114 | } 115 | 116 | self._parse() 117 | 118 | def __getitem__(self, key): 119 | return self._parsed_data[key] 120 | 121 | def __setitem__(self, key, val): 122 | if key not in self._parsed_data: 123 | warn("Unknown section %s" % key) 124 | else: 125 | self._parsed_data[key] = val 126 | 127 | def _is_at_section(self): 128 | self._doc.seek_next_non_empty_line() 129 | 130 | if self._doc.eof(): 131 | return False 132 | 133 | l1 = self._doc.peek().strip() # e.g. Parameters 134 | 135 | if l1.startswith('.. index::'): 136 | return True 137 | 138 | l2 = self._doc.peek(1).strip() # ---------- or ========== 139 | return l2.startswith('-' * len(l1)) or l2.startswith('=' * len(l1)) 140 | 141 | def _strip(self, doc): 142 | i = 0 143 | j = 0 144 | for i, line in enumerate(doc): 145 | if line.strip(): 146 | break 147 | 148 | for j, line in enumerate(doc[::-1]): 149 | if line.strip(): 150 | break 151 | 152 | return doc[i:len(doc) - j] 153 | 154 | def _read_to_next_section(self): 155 | section = self._doc.read_to_next_empty_line() 156 | 157 | while not self._is_at_section() and not self._doc.eof(): 158 | if not self._doc.peek(-1).strip(): # previous line was empty 159 | section += [''] 160 | 161 | section += self._doc.read_to_next_empty_line() 162 | 163 | return section 164 | 165 | def _read_sections(self): 166 | while not self._doc.eof(): 167 | data = self._read_to_next_section() 168 | name = data[0].strip() 169 | 170 | if name.startswith('..'): # index section 171 | yield name, data[1:] 172 | elif len(data) < 2: 173 | yield StopIteration 174 | else: 175 | yield name, self._strip(data[2:]) 176 | 177 | def _parse_param_list(self, content): 178 | r = Reader(content) 179 | params = [] 180 | while not r.eof(): 181 | header = r.read().strip() 182 | if ' : ' in header: 183 | arg_name, arg_type = header.split(' : ')[:2] 184 | else: 185 | arg_name, arg_type = header, '' 186 | 187 | desc = r.read_to_next_unindented_line() 188 | desc = dedent_lines(desc) 189 | 190 | params.append((arg_name, arg_type, desc)) 191 | 192 | return params 193 | 194 | _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" 195 | r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) 196 | 197 | def _parse_see_also(self, content): 198 | """ 199 | func_name : Descriptive text 200 | continued text 201 | another_func_name : Descriptive text 202 | func_name1, func_name2, :meth:`func_name`, func_name3 203 | 204 | """ 205 | items = [] 206 | 207 | def parse_item_name(text): 208 | """Match ':role:`name`' or 'name'""" 209 | m = self._name_rgx.match(text) 210 | if m: 211 | g = m.groups() 212 | if g[1] is None: 213 | return g[3], None 214 | else: 215 | return g[2], g[1] 216 | raise ValueError("%s is not a item name" % text) 217 | 218 | def push_item(name, rest): 219 | if not name: 220 | return 221 | name, role = parse_item_name(name) 222 | items.append((name, list(rest), role)) 223 | del rest[:] 224 | 225 | current_func = None 226 | rest = [] 227 | 228 | for line in content: 229 | if not line.strip(): 230 | continue 231 | 232 | m = self._name_rgx.match(line) 233 | if m and line[m.end():].strip().startswith(':'): 234 | push_item(current_func, rest) 235 | current_func, line = line[:m.end()], line[m.end():] 236 | rest = [line.split(':', 1)[1].strip()] 237 | if not rest[0]: 238 | rest = [] 239 | elif not line.startswith(' '): 240 | push_item(current_func, rest) 241 | current_func = None 242 | if ',' in line: 243 | for func in line.split(','): 244 | push_item(func, []) 245 | elif line.strip(): 246 | current_func = line 247 | elif current_func is not None: 248 | rest.append(line.strip()) 249 | push_item(current_func, rest) 250 | return items 251 | 252 | def _parse_index(self, section, content): 253 | """ 254 | .. index: default 255 | :refguide: something, else, and more 256 | 257 | """ 258 | def strip_each_in(lst): 259 | return [s.strip() for s in lst] 260 | 261 | out = {} 262 | section = section.split('::') 263 | if len(section) > 1: 264 | out['default'] = strip_each_in(section[1].split(','))[0] 265 | for line in content: 266 | line = line.split(':') 267 | if len(line) > 2: 268 | out[line[1]] = strip_each_in(line[2].split(',')) 269 | return out 270 | 271 | def _parse_summary(self): 272 | """Grab signature (if given) and summary""" 273 | if self._is_at_section(): 274 | return 275 | 276 | summary = self._doc.read_to_next_empty_line() 277 | summary_str = " ".join([s.strip() for s in summary]).strip() 278 | if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): 279 | self['Signature'] = summary_str 280 | if not self._is_at_section(): 281 | self['Summary'] = self._doc.read_to_next_empty_line() 282 | else: 283 | self['Summary'] = summary 284 | 285 | if not self._is_at_section(): 286 | self['Extended Summary'] = self._read_to_next_section() 287 | 288 | def _parse(self): 289 | self._doc.reset() 290 | self._parse_summary() 291 | 292 | for (section, content) in self._read_sections(): 293 | if not section.startswith('..'): 294 | section = ' '.join([s.capitalize() 295 | for s in section.split(' ')]) 296 | if section in ('Parameters', 'Attributes', 'Methods', 297 | 'Returns', 'Raises', 'Warns'): 298 | self[section] = self._parse_param_list(content) 299 | elif section.startswith('.. index::'): 300 | self['index'] = self._parse_index(section, content) 301 | elif section == 'See Also': 302 | self['See Also'] = self._parse_see_also(content) 303 | else: 304 | self[section] = content 305 | 306 | # string conversion routines 307 | 308 | def _str_header(self, name, symbol='-'): 309 | return [name, len(name) * symbol] 310 | 311 | def _str_indent(self, doc, indent=4): 312 | out = [] 313 | for line in doc: 314 | out += [' ' * indent + line] 315 | return out 316 | 317 | def _str_signature(self): 318 | if self['Signature']: 319 | return [self['Signature'].replace('*', '\*')] + [''] 320 | else: 321 | return [''] 322 | 323 | def _str_summary(self): 324 | if self['Summary']: 325 | return self['Summary'] + [''] 326 | else: 327 | return [] 328 | 329 | def _str_extended_summary(self): 330 | if self['Extended Summary']: 331 | return self['Extended Summary'] + [''] 332 | else: 333 | return [] 334 | 335 | def _str_param_list(self, name): 336 | out = [] 337 | if self[name]: 338 | out += self._str_header(name) 339 | for param, param_type, desc in self[name]: 340 | out += ['%s : %s' % (param, param_type)] 341 | out += self._str_indent(desc) 342 | out += [''] 343 | return out 344 | 345 | def _str_section(self, name): 346 | out = [] 347 | if self[name]: 348 | out += self._str_header(name) 349 | out += self[name] 350 | out += [''] 351 | return out 352 | 353 | def _str_see_also(self, func_role): 354 | if not self['See Also']: 355 | return [] 356 | out = [] 357 | out += self._str_header("See Also") 358 | last_had_desc = True 359 | for func, desc, role in self['See Also']: 360 | if role: 361 | link = ':%s:`%s`' % (role, func) 362 | elif func_role: 363 | link = ':%s:`%s`' % (func_role, func) 364 | else: 365 | link = "`%s`_" % func 366 | if desc or last_had_desc: 367 | out += [''] 368 | out += [link] 369 | else: 370 | out[-1] += ", %s" % link 371 | if desc: 372 | out += self._str_indent([' '.join(desc)]) 373 | last_had_desc = True 374 | else: 375 | last_had_desc = False 376 | out += [''] 377 | return out 378 | 379 | def _str_index(self): 380 | idx = self['index'] 381 | out = [] 382 | out += ['.. index:: %s' % idx.get('default', '')] 383 | for section, references in idx.iteritems(): 384 | if section == 'default': 385 | continue 386 | out += [' :%s: %s' % (section, ', '.join(references))] 387 | return out 388 | 389 | def __str__(self, func_role=''): 390 | out = [] 391 | out += self._str_signature() 392 | out += self._str_summary() 393 | out += self._str_extended_summary() 394 | for param_list in ('Parameters', 'Returns', 'Raises'): 395 | out += self._str_param_list(param_list) 396 | out += self._str_section('Warnings') 397 | out += self._str_see_also(func_role) 398 | for s in ('Notes', 'References', 'Examples'): 399 | out += self._str_section(s) 400 | for param_list in ('Attributes', 'Methods'): 401 | out += self._str_param_list(param_list) 402 | out += self._str_index() 403 | return '\n'.join(out) 404 | 405 | 406 | def indent(str, indent=4): 407 | indent_str = ' ' * indent 408 | if str is None: 409 | return indent_str 410 | lines = str.split('\n') 411 | return '\n'.join(indent_str + l for l in lines) 412 | 413 | 414 | def dedent_lines(lines): 415 | """Deindent a list of lines maximally""" 416 | return textwrap.dedent("\n".join(lines)).split("\n") 417 | 418 | 419 | def header(text, style='-'): 420 | return text + '\n' + style * len(text) + '\n' 421 | 422 | 423 | class FunctionDoc(NumpyDocString): 424 | def __init__(self, func, role='func', doc=None, config={}): 425 | self._f = func 426 | self._role = role # e.g. "func" or "meth" 427 | 428 | if doc is None: 429 | if func is None: 430 | raise ValueError("No function or docstring given") 431 | doc = inspect.getdoc(func) or '' 432 | NumpyDocString.__init__(self, doc) 433 | 434 | if not self['Signature'] and func is not None: 435 | func, func_name = self.get_func() 436 | try: 437 | # try to read signature 438 | argspec = inspect.getargspec(func) 439 | argspec = inspect.formatargspec(*argspec) 440 | argspec = argspec.replace('*', '\*') 441 | signature = '%s%s' % (func_name, argspec) 442 | except TypeError as e: 443 | signature = '%s()' % func_name 444 | self['Signature'] = signature 445 | 446 | def get_func(self): 447 | func_name = getattr(self._f, '__name__', self.__class__.__name__) 448 | if inspect.isclass(self._f): 449 | func = getattr(self._f, '__call__', self._f.__init__) 450 | else: 451 | func = self._f 452 | return func, func_name 453 | 454 | def __str__(self): 455 | out = '' 456 | 457 | func, func_name = self.get_func() 458 | signature = self['Signature'].replace('*', '\*') 459 | 460 | roles = {'func': 'function', 461 | 'meth': 'method'} 462 | 463 | if self._role: 464 | if not roles.has_key(self._role): 465 | print("Warning: invalid role %s" % self._role) 466 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role, ''), 467 | func_name) 468 | 469 | out += super(FunctionDoc, self).__str__(func_role=self._role) 470 | return out 471 | 472 | 473 | class ClassDoc(NumpyDocString): 474 | def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, 475 | config=None): 476 | if not inspect.isclass(cls) and cls is not None: 477 | raise ValueError("Expected a class or None, but got %r" % cls) 478 | self._cls = cls 479 | 480 | if modulename and not modulename.endswith('.'): 481 | modulename += '.' 482 | self._mod = modulename 483 | 484 | if doc is None: 485 | if cls is None: 486 | raise ValueError("No class or documentation string given") 487 | doc = pydoc.getdoc(cls) 488 | 489 | NumpyDocString.__init__(self, doc) 490 | 491 | if config is not None and config.get('show_class_members', True): 492 | if not self['Methods']: 493 | self['Methods'] = [(name, '', '') 494 | for name in sorted(self.methods)] 495 | if not self['Attributes']: 496 | self['Attributes'] = [(name, '', '') 497 | for name in sorted(self.properties)] 498 | 499 | @property 500 | def methods(self): 501 | if self._cls is None: 502 | return [] 503 | return [name for name, func in inspect.getmembers(self._cls) 504 | if not name.startswith('_') and callable(func)] 505 | 506 | @property 507 | def properties(self): 508 | if self._cls is None: 509 | return [] 510 | return [name for name, func in inspect.getmembers(self._cls) 511 | if not name.startswith('_') and func is None] 512 | --------------------------------------------------------------------------------