├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CHANGES.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── _static │ └── .gitignore ├── changes.rst ├── conf.py ├── contrib.rst ├── docs_requirements.txt ├── estimate.rst ├── forecast.rst ├── index.rst ├── license.rst └── simulate.rst ├── notebooks ├── airline.ipynb ├── notes.txt ├── petroleum-worksheet.ipynb ├── petroleum.ipynb └── utils.py ├── pydse ├── __init__.py ├── _version.py ├── arma.py ├── data │ ├── __init__.py │ ├── international-airline-passengers.csv │ ├── m1-us-1959119922.csv │ ├── monthly-cpi-canada-19501973.csv │ ├── monthly-sales-of-product-a-for-a.csv │ ├── monthly-sales-of-tasty-cola.csv │ ├── sales-of-shampoo-over-a-three-ye.csv │ └── us-monthly-sales-of-petroleum-an.csv ├── stats.py └── utils.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── install.sh ├── test_arma.R ├── test_arma.py ├── test_data.py ├── test_stats.py └── test_utils.py └── versioneer.py /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | source = pydse 5 | omit = */_version.py 6 | 7 | [report] 8 | # Regexes for lines to exclude from consideration 9 | exclude_lines = 10 | # Have to re-enable the standard pragma 11 | pragma: no cover 12 | 13 | # Don't complain about missing debug-only code: 14 | def __repr__ 15 | if self\.debug 16 | 17 | # Don't complain if tests don't hit defensive assertion code: 18 | raise AssertionError 19 | raise NotImplementedError 20 | 21 | # Don't complain if non-runnable code isn't run: 22 | if 0: 23 | if __name__ == .__main__.: 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | *.orig 7 | *.log 8 | *.pot 9 | __pycache__/* 10 | .cache/* 11 | 12 | # Project files 13 | .ropeproject 14 | .project 15 | .pydevproject 16 | .settings 17 | .idea 18 | 19 | # Package files 20 | *.egg 21 | .installed.cfg 22 | *.egg-info 23 | 24 | # Unittest and coverage 25 | htmlcov/* 26 | .coverage 27 | .tox 28 | junit.xml 29 | coverage.xml 30 | 31 | # Build and docs folder/files 32 | build/* 33 | dist/* 34 | sdist/* 35 | docs/_rst/* 36 | docs/_build/* 37 | cover/* 38 | MANIFEST 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | virtualenv: 3 | system_site_packages: true 4 | env: 5 | matrix: 6 | - DISTRIB="conda" PYTHON_VERSION="2.7" NUMPY_VERSION="1.7.0" 7 | SCIPY_VERSION="0.11.0" COVERAGE="true" 8 | - DISTRIB="conda" PYTHON_VERSION="3.4" 9 | NUMPY_VERSION="1.9.1" SCIPY_VERSION="0.14.0" 10 | install: 11 | - sudo apt-get -qq install python-dev 12 | - source tests/install.sh 13 | - pip install -vq -r requirements.txt 14 | script: 15 | - python setup.py test 16 | after_success: 17 | - if [[ "$COVERAGE" == "true" ]]; then coveralls || echo "failed"; fi 18 | cache: 19 | - apt 20 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Developers 3 | ========== 4 | 5 | * Florian Wilhelm 6 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Release Notes 3 | ============= 4 | 5 | Version 0.2.1, 2015-10-27 6 | ========================= 7 | 8 | - Fix unit tests due to changes in Pandas 9 | 10 | Version 0.2, 2014-12-23 11 | ======================= 12 | 13 | - Update to PyScaffold 1.3.1 14 | - Minor fixes in docs 15 | - Added exercises and presentation 16 | - Added plot_forecast function 17 | 18 | Version 0.1, 2014-07-11 19 | ======================= 20 | 21 | - First release 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Blue Yonder. 2 | All rights reserved. 3 | 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | a. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | b. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | c. Neither the name of the PyDSE developers nor the names of 14 | its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written 16 | permission. 17 | 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | DAMAGE. 30 | 31 | ------------------------------------------------------------------------------- 32 | 33 | The time series data provided by PyDSE in the pydse/data folder are subject 34 | to the "default open license" of DataMarket (http://datamarket.com/): 35 | 36 | Licence summary 37 | You may copy and redistribute the data. You may make derivative works from the 38 | data. You may use the data for commercial purposes. You may not sublicence the 39 | data when redistributing it. You may not redistribute the data under a 40 | different license. Source attribution on any use of this data: Must refer 41 | source. 42 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include *.txt 3 | include *.rst 4 | include tox.ini 5 | include pydse/data/*.csv 6 | recursive-include pydse *.py *.c *.h *.pxd *.pyx 7 | recursive-include docs *.rst *.py 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | PyDSE 3 | ===== 4 | 5 | .. image:: https://travis-ci.org/blue-yonder/pydse.svg?branch=master 6 | :target: https://travis-ci.org/blue-yonder/pydse 7 | .. image:: https://coveralls.io/repos/blue-yonder/pydse/badge.png 8 | :target: https://coveralls.io/r/blue-yonder/pydse 9 | .. image:: https://requires.io/github/blue-yonder/pydse/requirements.png?branch=master 10 | :target: https://requires.io/github/blue-yonder/pydse/requirements/?branch=master 11 | :alt: Requirements Status 12 | 13 | Toolset for Dynamic System Estimation for time series inspired by 14 | `DSE `_. 15 | It is in a beta state and only includes ARMA models right now. 16 | Documentation is available under http://pydse.readthedocs.org/. 17 | 18 | 19 | Installation 20 | ============ 21 | 22 | To install in your home directory, use:: 23 | 24 | python setup.py install --user 25 | 26 | If your are using `virtualenv `_, 27 | just use `pip `_:: 28 | 29 | pip install pydse 30 | -------------------------------------------------------------------------------- /docs/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/pydse.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pydse.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/pydse" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $HOME/.local/share/devhelp/pydse" 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 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # Empty directory 2 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is execfile()d with the current directory set to its containing dir. 4 | # 5 | # Note that not all possible configuration values are present in this 6 | # autogenerated file. 7 | # 8 | # All configuration values have a default; values that are commented out 9 | # serve to show the default. 10 | 11 | import sys 12 | import os 13 | import inspect 14 | from sphinx import apidoc 15 | 16 | 17 | __location__ = os.path.join(os.getcwd(), os.path.dirname( 18 | inspect.getfile(inspect.currentframe()))) 19 | 20 | output_dir = os.path.join(__location__, "../docs/_rst") 21 | module_dir = os.path.join(__location__, "../pydse") 22 | cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" 23 | cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) 24 | apidoc.main(cmd_line.split(" ")) 25 | 26 | # If extensions (or modules to document with autodoc) are in another directory, 27 | # add these directories to sys.path here. If the directory is relative to the 28 | # documentation root, use os.path.abspath to make it absolute, like shown here. 29 | # sys.path.insert(0, os.path.abspath('.')) 30 | 31 | # -- General configuration ----------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # needs_sphinx = '1.0' 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be extensions 37 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 38 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 39 | 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'sphinx.ext.coverage', 40 | 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.pngmath', 41 | 'matplotlib.sphinxext.only_directives', 42 | 'matplotlib.sphinxext.plot_directive', 'IPython.sphinxext.ipython_directive'] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | # source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = u'PyDSE' 58 | copyright = u'2014, Blue Yonder' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '' # Is set by calling `setup.py docs` 66 | # The full version, including alpha/beta/rc tags. 67 | release = '' # Is set by calling `setup.py docs` 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | # today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | # today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ['_build'] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all documents. 84 | # default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | # add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | # add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | # show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | # modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | # keep_warnings = False 105 | 106 | 107 | # -- Options for HTML output --------------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'default' 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 | try: 124 | from pydse import __version__ as version 125 | except ImportError: 126 | pass 127 | else: 128 | release = version 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | # html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | # html_logo = "" 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | # html_favicon = None 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 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 = 'pydse-doc' 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, author, documentclass [howto/manual]). 207 | latex_documents = [ 208 | ('index', 'user_guide.tex', u'PyDSE Documentation', 209 | u'Blue Yonder', 'manual'), 210 | ] 211 | 212 | # The name of an image file (relative to this directory) to place at the top of 213 | # the title page. 214 | # latex_logo = "" 215 | 216 | # For "manual" documents, if this is true, then toplevel headings are parts, 217 | # not chapters. 218 | # latex_use_parts = False 219 | 220 | # If true, show page references after internal links. 221 | # latex_show_pagerefs = False 222 | 223 | # If true, show URL addresses after external links. 224 | # latex_show_urls = False 225 | 226 | # Documents to append as an appendix to all manuals. 227 | # latex_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | # latex_domain_indices = True 231 | 232 | # -- External mapping ------------------------------------------------------------ 233 | python_version = '.'.join(map(str, sys.version_info[0:2])) 234 | intersphinx_mapping = { 235 | 'sphinx': ('http://sphinx.pocoo.org', None), 236 | 'python': ('http://docs.python.org/' + python_version, None), 237 | 'matplotlib': ('http://matplotlib.sourceforge.net', None), 238 | 'numpy': ('http://docs.scipy.org/doc/numpy', None), 239 | 'sklearn': ('http://scikit-learn.org/stable', None), 240 | 'pandas': ('http://pandas.pydata.org/pandas-docs/stable', None), 241 | 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), 242 | } 243 | 244 | -------------------------------------------------------------------------------- /docs/contrib.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contribution 3 | ============ 4 | 5 | PyDSE is developed by currently one developer in his spare time out of pure 6 | interest in ARMA models. You are very welcome to join in this effort if you 7 | would like to contribute. 8 | 9 | Bug Reports 10 | =========== 11 | 12 | If you experience bugs or in general issues with PyDSE, please file a bug 13 | report to our `Bug Tracker `_. 14 | 15 | 16 | Code 17 | ==== 18 | 19 | If you would like to contribute to PyDSE, fork the `main repository 20 | `_ on GitHub, then submit a 21 | “pull request” (PR): 22 | 23 | #. `Create an account `_ on GitHub if you do 24 | not already have one. 25 | #. Fork the project repository: click on the *Fork* button near the top of the 26 | page. This creates a copy of the code under your account on the GitHub server. 27 | #. Clone this copy to your local disk:: 28 | 29 | git clone git@github.com:YourLogin/pydse.git 30 | 31 | #. Create a branch to hold your changes:: 32 | 33 | git checkout -b my-feature 34 | 35 | and start making changes. Never work in the master branch! 36 | 37 | #. Work on this copy, on your computer, using `Git `_ to 38 | do the version control. When you’re done editing, do:: 39 | 40 | git add modified_files 41 | git commit 42 | 43 | to record your changes in Git, then push them to GitHub with:: 44 | 45 | git push -u origin my-feature 46 | 47 | #. Go to the web page of your PyDSE fork, and click 48 | "Create pull request" to send your changes to the maintainers for review. 49 | Find more detailed information `here 50 | `_. 51 | -------------------------------------------------------------------------------- /docs/docs_requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | matplotlib 3 | patsy 4 | IPython 5 | statsmodels 6 | -------------------------------------------------------------------------------- /docs/estimate.rst: -------------------------------------------------------------------------------- 1 | .. _estimation-of-parameters: 2 | 3 | ======================== 4 | Estimation of Parameters 5 | ======================== 6 | 7 | In this section we estimate the parameters for the time series of the monthly 8 | passengers of an international airline. 9 | 10 | .. plot:: 11 | :include-source: 12 | :context: 13 | 14 | import numpy as np 15 | from pydse import data 16 | 17 | df = data.airline_passengers() 18 | df.plot() 19 | 20 | .. plot:: 21 | :include-source: 22 | :context: 23 | :nofigs: 24 | 25 | plt.close() 26 | np.random.seed(0) 27 | 28 | Obviously, there is a strong trend in the data. Since ARMA can handle only 29 | stationary time series we have to remove it. In order to do that, we would like 30 | to smooth the time series. We see that there is 12 month seasonality, and 31 | therefore taking 3 years as a window for a smoothing function should be alright. 32 | An option would be a rolling mean: 33 | 34 | .. plot:: 35 | :include-source: 36 | :context: 37 | 38 | from pandas.stats.moments import rolling_mean 39 | df['Trend'] = rolling_mean(df['Passengers'], window=36, min_periods=1) 40 | df.plot() 41 | 42 | .. plot:: 43 | :context: 44 | 45 | plt.close() 46 | 47 | Our first guess is now to remove the trend by subtracting the *Trend* from our 48 | time series: 49 | 50 | .. plot:: 51 | :include-source: 52 | :context: 53 | 54 | residual = df['Passengers'] - df['Trend'] 55 | residual.plot() 56 | 57 | .. plot:: 58 | :context: 59 | 60 | plt.close() 61 | 62 | Obviously the trend is removed but the variance does not seem to be stationary, 63 | i.e. there is heteroscedasticity. Since the variance seems to be related with 64 | the absolut value of the time series we use another ansatz: 65 | 66 | .. plot:: 67 | :include-source: 68 | :context: 69 | 70 | residual = df['Passengers'] / df['Trend'] 71 | residual.plot() 72 | 73 | .. plot:: 74 | :context: 75 | 76 | plt.close() 77 | 78 | This time the series looks like a stationary process. Again, we look at the 79 | ACF and PACF plots. 80 | 81 | .. plot:: 82 | :include-source: 83 | :context: 84 | 85 | from statsmodels.graphics.tsaplots import plot_pacf, plot_acf 86 | plot_acf(residual, lags=15) 87 | 88 | .. plot:: 89 | :context: 90 | 91 | plt.close() 92 | 93 | .. plot:: 94 | :include-source: 95 | :context: 96 | 97 | plot_pacf(residual, lags=15) 98 | 99 | .. plot:: 100 | :context: 101 | 102 | plt.close() 103 | 104 | These plots show us the strong seasonality of 12 months. Due to this plots, we 105 | want to estimate an ARMA model where the *AR* term has only lag of 12 and the 106 | *MA* has lags 1 and 13. All other lags (except of 0 of course) should be equal 107 | to zero. 108 | 109 | .. plot:: 110 | :include-source: 111 | :context: 112 | :nofigs: 113 | 114 | from pydse.arma import ARMA 115 | 116 | AR = (np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01]), 117 | np.array([13, 1, 1])) 118 | MA = (np.array([1, 0.01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01]), 119 | np.array([14, 1, 1])) 120 | arma = ARMA(A=AR, B=MA, rand_state=0) 121 | arma.fix_constants() 122 | 123 | The :obj:`~.arma.ARMA.fix_constants` function determines the constants of our 124 | model. Every parameter that has less or equal than one decimal place is 125 | considered constant. Now the only remaining parameters are the ones that we set 126 | to *0.01*. In order to estimate those we call :obj:`~.arma.ARMA.est_params` 127 | with our residual time series: 128 | 129 | .. plot:: 130 | :include-source: 131 | :context: 132 | 133 | arma.est_params(residual) 134 | 135 | The output of this command tells us if our opimization method converged. 136 | We can now take a look if our estimated ARMA process produces a similar time 137 | series than residual. To quantify this similarity, we should take a look at the 138 | Mean Absolute Deviation (MAD) where we are in this case only interested in 139 | predictions starting from month 20 since it takes a while for ARMA to adjust . 140 | 141 | .. plot:: 142 | :include-source: 143 | :context: 144 | 145 | import pandas as pd 146 | result = pd.DataFrame({'pred': arma.forecast(residual)[:, 0], 147 | 'truth': residual.values}) 148 | MAD = np.mean(np.abs(result['pred'][20:] - result['truth'][20:])) 149 | result.plot(title="AR lags: 12; MA lags: 1, 13; MAD: {}".format(MAD)) 150 | 151 | 152 | .. plot:: 153 | :context: 154 | 155 | plt.close() 156 | 157 | Instead of guessing the possible parameters by looking at the ACF and PACF 158 | plots, we can also use the :obj:`~.arma.minic` function. This function takes 159 | a set of possible AR and MA lags to consider, calculates for each combination 160 | some information criterion and chooses the most likely. 161 | Let's say we are quite unsure how to interpret ACF and PACF plots and we just 162 | use our gut feeling that lag 1 and maybe lag 11, 12 as well as 13 could be 163 | useful as AR and MA lags. We just provide those guesses to :obj:`~.arma.minic` 164 | and get the best AR and MA lags. Then, we apply the :obj:`~.utils.make_lag_arr` 165 | function to generate one dimensional lag matrices that we use as inputs for 166 | our ARMA model as before. There we go: 167 | 168 | .. plot:: 169 | :include-source: 170 | :context: 171 | 172 | from pydse.arma import minic 173 | from pydse.utils import make_lag_arr 174 | 175 | best_ar_lags, best_ma_lags = minic([1, 11, 12, 13], [1, 11, 12, 13], residual) 176 | arma = ARMA(A=make_lag_arr(best_ar_lags), 177 | B=make_lag_arr(best_ma_lags), 178 | rand_state=0) 179 | arma.fix_constants() 180 | arma.est_params(residual) 181 | result = pd.DataFrame({'pred': arma.forecast(residual)[:, 0], 182 | 'truth': residual.values}) 183 | MAD = np.mean(np.abs(result['pred'][20:] - result['truth'][20:])) 184 | result.plot(title="AR lags: {}; MA lags: {}; MAD: {}".format( 185 | ", ".join(map(str, best_ar_lags)), ", ".join(map(str, best_ma_lags)), MAD)) 186 | 187 | .. plot:: 188 | :context: 189 | 190 | plt.close() 191 | 192 | Finally, we will apply the necessary back transformation to our time series: 193 | 194 | .. plot:: 195 | :include-source: 196 | :context: 197 | 198 | df['Prediction'] = result['pred'].values * df['Trend'].values 199 | del df['Trend'] 200 | df.plot() 201 | 202 | .. plot:: 203 | :context: 204 | 205 | plt.close() 206 | -------------------------------------------------------------------------------- /docs/forecast.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Forecast with Horizon 3 | ===================== 4 | 5 | In chapter :ref:`Estimation of Parameters ` only 6 | one-step ahead predictions were applied. So in our example of monthly data, 7 | all data up to the last month was used to predict the number of passengers in 8 | the current month. This is possible up to the last date with the known truth. 9 | 10 | In order to do forecasts beyond the period where all data is available, we can 11 | provide a ``horizon`` to the :obj:`~.ARMA.forecast` function. The horizon 12 | specifies the number of one-step ahead predictions that should be done after 13 | the last known data point. 14 | 15 | To illustrate this, we take the same example as 16 | in the last chapter and remove the last two years from the data: 17 | 18 | .. plot:: 19 | :include-source: 20 | :context: 21 | 22 | from pydse import data 23 | from pydse.arma import ARMA 24 | from pydse.utils import make_lag_arr 25 | import pandas as pd 26 | from pandas.stats.moments import rolling_mean 27 | 28 | df = data.airline_passengers() 29 | df['Trend'] = rolling_mean(df['Passengers'], window=36, min_periods=1) 30 | residual_all = df['Passengers'] / df['Trend'] 31 | residual_known = residual_all[:-24] 32 | pd.DataFrame({'future': residual_all, 'known': residual_known}).plot() 33 | 34 | .. plot:: 35 | :context: 36 | 37 | plt.close() 38 | 39 | Now, we fit an ARMA model with the *known* data and forecast the following two 40 | years after the known time period: 41 | 42 | .. plot:: 43 | :include-source: 44 | :context: 45 | 46 | arma = ARMA(A=make_lag_arr([1, 12, 13]), B=make_lag_arr([12]), rand_state=0) 47 | arma.fix_constants() 48 | arma.est_params(residual_known) 49 | pred = arma.forecast(residual_known, horizon=24) 50 | result = pd.DataFrame({'pred': pred[:, 0], 'truth': residual_all.values}) 51 | result.plot() 52 | 53 | .. plot:: 54 | :context: 55 | 56 | plt.close() 57 | 58 | By eye, it can be seen that our predictions are still quite accurate but not as 59 | good as using one-step ahead predictions with data up to the previous month. 60 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | PyDSE 3 | ===== 4 | 5 | Toolset for Dynamic System Estimation for time series inspired by 6 | `DSE `_. 7 | It is in a beta state and only includes ARMA models right now. 8 | 9 | 10 | Contents 11 | ======== 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | Create and Simulate 17 | Estimation of Parameters 18 | Forecast with Horizon 19 | Contribution 20 | License 21 | Changelog 22 | Module Reference <_rst/modules> 23 | 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | License 3 | ======= 4 | 5 | .. literalinclude:: ../LICENSE.txt 6 | -------------------------------------------------------------------------------- /docs/simulate.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Create and Simulate 3 | =================== 4 | 5 | The definition of an ARMA model is: 6 | 7 | .. math:: 8 | 9 | A(L)y_t = B(L)e_t + C(L)u_t 10 | 11 | where :math:`L` is the *lag* operator, :math:`y_t` a :math:`p`-dimensional 12 | vector of observed output variables, :math:`e_t` a :math:`p`-dimensional vector 13 | of white noise and :math:`u_t` a :math:`m`-dimensional vector of input variables. 14 | Since :math:`A, B` and :math:`C` are matrices in the lag shift operator, we have 15 | :math:`A(L)` is a :math:`a \times\, p \times\, p` tensor to define auto-regression, 16 | :math:`B(L)` is a :math:`b \times\, p \times\, p` tensor to moving-average and 17 | :math:`C(L)` is a :math:`c \times\, p \times\, m` tensor to account for the input 18 | variables. 19 | 20 | We create a simple ARMA model for a two dimensional output vector with matrices: 21 | 22 | .. math:: 23 | 24 | A(L) = \left( \begin{array}{cc} 25 | 1+0.5L_1+0.3L_2 & 0+0.2L_1+0.1L_2\\ 26 | 0+0.2L_1+0.05L_2 & 1+0.5L_1+0.3L_2\end{array} \right), 27 | 28 | .. math:: 29 | 30 | B(L) =\left( \begin{array}{cc} 31 | 1+0.2L_1 & 0+0.1L_1\\ 32 | 0+0.0L_1 & 1+0.3L_1\end{array} \right) 33 | 34 | In order to set this matrix we just write the entries left to right, up to down 35 | into an array and define the shape of this array in a second array: 36 | 37 | .. plot:: 38 | :include-source: 39 | :context: 40 | :nofigs: 41 | 42 | import pandas as pd 43 | import numpy as np 44 | import matplotlib.pylab as plt 45 | from pydse.arma import ARMA 46 | 47 | AR = (np.array([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3]), np.array([3, 2, 2])) 48 | MA = (np.array([1, .2, 0, .1, 0, 0, 1, .3]), np.array([2, 2, 2])) 49 | arma = ARMA(A=AR, B=MA, rand_state=0) 50 | 51 | Note that we set the random state to seed 0 to get the same results. 52 | Then by simulating we get: 53 | 54 | .. plot:: 55 | :include-source: 56 | :context: 57 | 58 | sim_data = arma.simulate(sampleT=100) 59 | sim_index = pd.date_range('1/1/2011', periods=sim_data.shape[0], freq='d') 60 | df = pd.DataFrame(data=sim_data, index=sim_index) 61 | df.plot() 62 | 63 | .. plot:: 64 | :context: 65 | 66 | plt.close() 67 | 68 | Let's create a simpler ARMA model with scalar output variable. 69 | 70 | .. plot:: 71 | :include-source: 72 | :context: 73 | 74 | AR = (np.array([1, .5, .3]), np.array([3, 1, 1])) 75 | MA = (np.array([1, .2]), np.array([2, 1, 1])) 76 | arma = ARMA(A=AR, B=MA, rand_state=0) 77 | 78 | Quite often you wanna check the `autocorrelation function `__ 79 | and `partial autocorrelation function `__: 80 | 81 | .. plot:: 82 | :include-source: 83 | :context: 84 | 85 | from statsmodels.graphics.tsaplots import plot_pacf, plot_acf 86 | 87 | sim_data = arma.simulate(sampleT=3000) 88 | sim_index = pd.date_range('1/1/2011', periods=sim_data.shape[0], freq='d') 89 | df = pd.DataFrame(data=sim_data, index=sim_index) 90 | plot_acf(df[0], lags=10) 91 | plot_pacf(df[0], lags=10) 92 | plt.show() 93 | 94 | Find a good introduction to ARMA on the `Decision 411 `__ 95 | course page. 96 | -------------------------------------------------------------------------------- /notebooks/notes.txt: -------------------------------------------------------------------------------- 1 | ========= 2 | Exercises 3 | ========= 4 | 5 | 6 | For the petroleum-worksheet this time schedule should roughly hold: 7 | 8 | Minutes Task 9 | 15 Introduction + System Setup 10 | 10 Preprocess time series 11 | 20 Fitting the ARMA model 12 | 20 minic (runtime is the major time consumer here) 13 | 10 Backtrasnformation + first conclusion 14 | 30 Vehicles 15 | 15 Horizon 16 | = 120 17 | 18 | The presentation can be created with: 19 | ipython nbconvert --to slides notebooks/petroleum.ipynb 20 | 21 | Removal of ugly "pass" lines with: 22 | sed -i '/pass<\/span>/d' petroleum.slides.html 23 | 24 | -------------------------------------------------------------------------------- /notebooks/petroleum-worksheet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:2339c3adcadcbd4b7436fc8140e4c947581d5e8b865757ba6801b9e008feac21" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "import pandas as pd\n", 16 | "import numpy as np\n", 17 | "import matplotlib.pyplot as plt\n", 18 | "%matplotlib inline\n", 19 | "\n", 20 | "from pydse.arma import ARMA\n", 21 | "from pydse import data" 22 | ], 23 | "language": "python", 24 | "metadata": {}, 25 | "outputs": [], 26 | "prompt_number": 1 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "## Petroleum for Chemicals" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "collapsed": false, 38 | "input": [ 39 | "chemicals = data.sales_petroleum()[['Chemicals']]\n", 40 | "chemicals.plot()\n", 41 | "pass" 42 | ], 43 | "language": "python", 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "metadata": {}, 48 | "output_type": "display_data", 49 | "png": "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\n8P+Xtvz2Fzjp9gAAAABJRU5ErkJggg==\n", 50 | "text": [ 51 | "" 52 | ] 53 | } 54 | ], 55 | "prompt_number": 2 56 | }, 57 | { 58 | "cell_type": "code", 59 | "collapsed": false, 60 | "input": [ 61 | "from pandas.stats.moments import rolling_mean, ewma\n", 62 | "from utils import ols" 63 | ], 64 | "language": "python", 65 | "metadata": {}, 66 | "outputs": [], 67 | "prompt_number": 3 68 | }, 69 | { 70 | "cell_type": "code", 71 | "collapsed": false, 72 | "input": [ 73 | "# Task: Analyze the trend and make the new time series stationary\n", 74 | "# See also:\n", 75 | "# * http://pandas.pydata.org/pandas-docs/version/0.15.1/generated/pandas.stats.moments.rolling_mean.html\n", 76 | "# * http://pandas.pydata.org/pandas-docs/stable/generated/pandas.stats.moments.ewma.html" 77 | ], 78 | "language": "python", 79 | "metadata": {}, 80 | "outputs": [], 81 | "prompt_number": 4 82 | }, 83 | { 84 | "cell_type": "code", 85 | "collapsed": false, 86 | "input": [ 87 | "from statsmodels.graphics.tsaplots import plot_pacf, plot_acf" 88 | ], 89 | "language": "python", 90 | "metadata": {}, 91 | "outputs": [], 92 | "prompt_number": 5 93 | }, 94 | { 95 | "cell_type": "code", 96 | "collapsed": false, 97 | "input": [ 98 | "# Task: Plot autocorrelation and partial autocorrelation\n", 99 | "# See also:\n", 100 | "# * http://statsmodels.sourceforge.net/stable/generated/statsmodels.graphics.tsaplots.plot_pacf.html\n", 101 | "# * http://statsmodels.sourceforge.net/stable/generated/statsmodels.graphics.tsaplots.plot_acf.html" 102 | ], 103 | "language": "python", 104 | "metadata": {}, 105 | "outputs": [], 106 | "prompt_number": 6 107 | }, 108 | { 109 | "cell_type": "code", 110 | "collapsed": false, 111 | "input": [ 112 | "from pydse.arma import ARMA\n", 113 | "from pydse.utils import make_lag_arr\n", 114 | "\n", 115 | "AR = (np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01]),\n", 116 | " np.array([13, 1, 1]))\n", 117 | "# = make_lag_arr([12])\n", 118 | "MA = (np.array([1, 0.01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01]),\n", 119 | " np.array([14, 1, 1]))\n", 120 | "# = make_lag_arr([1, 13])\n", 121 | "arma = ARMA(A=AR, B=MA, rand_state=0)\n", 122 | "arma.fix_constants()" 123 | ], 124 | "language": "python", 125 | "metadata": {}, 126 | "outputs": [], 127 | "prompt_number": 7 128 | }, 129 | { 130 | "cell_type": "code", 131 | "collapsed": false, 132 | "input": [ 133 | "# Task: Try different values for AR and MA based on the observation\n", 134 | "# in the ACF and PACF plots.\n", 135 | "# arma.est_params(residual_data)\n", 136 | "# arma.forecast(residual_data)" 137 | ], 138 | "language": "python", 139 | "metadata": {}, 140 | "outputs": [], 141 | "prompt_number": 8 142 | }, 143 | { 144 | "cell_type": "code", 145 | "collapsed": false, 146 | "input": [ 147 | "from pydse.arma import minic" 148 | ], 149 | "language": "python", 150 | "metadata": {}, 151 | "outputs": [], 152 | "prompt_number": 9 153 | }, 154 | { 155 | "cell_type": "code", 156 | "collapsed": false, 157 | "input": [ 158 | "# Task: Find the best parameters by using an exhaustive search over a set of possible parameters\n", 159 | "\n", 160 | "# best_ar_lags, best_ma_lags = minic([1, ..], [1, ..], residual)\n", 161 | "# arma = ..." 162 | ], 163 | "language": "python", 164 | "metadata": {}, 165 | "outputs": [], 166 | "prompt_number": 10 167 | }, 168 | { 169 | "cell_type": "code", 170 | "collapsed": false, 171 | "input": [ 172 | "# Task: Re-add the trend to the prediction to compare it with the original truth" 173 | ], 174 | "language": "python", 175 | "metadata": {}, 176 | "outputs": [], 177 | "prompt_number": 11 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "## Petroleum for Vehicles" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "collapsed": false, 189 | "input": [ 190 | "vehicles = data.sales_petroleum()[['Vehicles']]\n", 191 | "vehicles.plot()\n", 192 | "pass" 193 | ], 194 | "language": "python", 195 | "metadata": {}, 196 | "outputs": [ 197 | { 198 | "metadata": {}, 199 | "output_type": "display_data", 200 | "png": "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\nzZSGQWajkaMAAAAASUVORK5CYII=\n", 201 | "text": [ 202 | "" 203 | ] 204 | } 205 | ], 206 | "prompt_number": 12 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "metadata": {}, 211 | "source": [ 212 | "* **Tasks:**\n", 213 | " * Analyse the trend in the data\n", 214 | " * Transform the time series so that it is stationary\n", 215 | " * Plot (partial) autocorrelation to determine possible values for the lag\n", 216 | " * Train/Fit an ARMA model and find matching/good/optimal parameters " 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "collapsed": false, 222 | "input": [], 223 | "language": "python", 224 | "metadata": {}, 225 | "outputs": [], 226 | "prompt_number": 12 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "### Forecasting with a horizon" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "collapsed": false, 238 | "input": [ 239 | "# Task: Instead of predicting only step ahead, predict longer timespan with arma.forecast(, horizon=) " 240 | ], 241 | "language": "python", 242 | "metadata": {}, 243 | "outputs": [], 244 | "prompt_number": 13 245 | }, 246 | { 247 | "cell_type": "code", 248 | "collapsed": false, 249 | "input": [], 250 | "language": "python", 251 | "metadata": {}, 252 | "outputs": [], 253 | "prompt_number": 13 254 | } 255 | ], 256 | "metadata": {} 257 | } 258 | ] 259 | } -------------------------------------------------------------------------------- /notebooks/utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | Helper functions used in the example notebooks. 5 | """ 6 | 7 | import pandas as pd 8 | from pandas.stats.ols import OLS 9 | 10 | __author__ = "Uwe L. Korn" 11 | __copyright__ = "Blue Yonder" 12 | __license__ = "new BSD" 13 | 14 | 15 | def ols(series): 16 | """ 17 | Approximate a series by ordinary least squares regression. 18 | 19 | :param series: Pandas Series to be approximated 20 | :return: NumPy array of the same size 21 | """ 22 | x = series.index.values.astype(float) 23 | y = series.values.astype(float) 24 | model = OLS(y=pd.Series(y), x=pd.Series(x)) 25 | a = model.beta['x'] 26 | b = model.beta['intercept'] 27 | return a*x + b 28 | -------------------------------------------------------------------------------- /pydse/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import get_versions 2 | 3 | __version__ = get_versions()['version'] 4 | del get_versions 5 | -------------------------------------------------------------------------------- /pydse/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file helps to compute a version number in source trees obtained from 3 | # git-archive tarball (such as those provided by githubs download-from-tag 4 | # feature). Distribution tarballs (built by setup.py sdist) and build 5 | # directories (produced by setup.py build) will contain a much shorter file 6 | # that just contains the computed version number. 7 | 8 | # This file is released into the public domain. Generated by 9 | # versioneer-0.12 (https://github.com/warner/python-versioneer) 10 | 11 | # these strings will be replaced by git during git-archive 12 | git_refnames = "$Format:%d$" 13 | git_full = "$Format:%H$" 14 | 15 | # these strings are filled in when 'setup.py versioneer' creates _version.py 16 | tag_prefix = "v" 17 | parentdir_prefix = "pydse-" 18 | versionfile_source = "pydse/_version.py" 19 | 20 | import os, sys, re, subprocess, errno 21 | 22 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 23 | assert isinstance(commands, list) 24 | p = None 25 | for c in commands: 26 | try: 27 | # remember shell=False, so use git.cmd on windows, not just git 28 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 29 | stderr=(subprocess.PIPE if hide_stderr 30 | else None)) 31 | break 32 | except EnvironmentError: 33 | e = sys.exc_info()[1] 34 | if e.errno == errno.ENOENT: 35 | continue 36 | if verbose: 37 | print("unable to run %s" % args[0]) 38 | print(e) 39 | return None 40 | else: 41 | if verbose: 42 | print("unable to find command, tried %s" % (commands,)) 43 | return None 44 | stdout = p.communicate()[0].strip() 45 | if sys.version >= '3': 46 | stdout = stdout.decode() 47 | if p.returncode != 0: 48 | if verbose: 49 | print("unable to run %s (error)" % args[0]) 50 | return None 51 | return stdout 52 | 53 | 54 | def versions_from_parentdir(parentdir_prefix, root, verbose=False): 55 | # Source tarballs conventionally unpack into a directory that includes 56 | # both the project name and a version string. 57 | dirname = os.path.basename(root) 58 | if not dirname.startswith(parentdir_prefix): 59 | if verbose: 60 | print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % 61 | (root, dirname, parentdir_prefix)) 62 | return None 63 | return {"version": dirname[len(parentdir_prefix):], "full": ""} 64 | 65 | def git_get_keywords(versionfile_abs): 66 | # the code embedded in _version.py can just fetch the value of these 67 | # keywords. When used from setup.py, we don't want to import _version.py, 68 | # so we do it with a regexp instead. This function is not used from 69 | # _version.py. 70 | keywords = {} 71 | try: 72 | f = open(versionfile_abs,"r") 73 | for line in f.readlines(): 74 | if line.strip().startswith("git_refnames ="): 75 | mo = re.search(r'=\s*"(.*)"', line) 76 | if mo: 77 | keywords["refnames"] = mo.group(1) 78 | if line.strip().startswith("git_full ="): 79 | mo = re.search(r'=\s*"(.*)"', line) 80 | if mo: 81 | keywords["full"] = mo.group(1) 82 | f.close() 83 | except EnvironmentError: 84 | pass 85 | return keywords 86 | 87 | def git_versions_from_keywords(keywords, tag_prefix, verbose=False): 88 | if not keywords: 89 | return {} # keyword-finding function failed to find keywords 90 | refnames = keywords["refnames"].strip() 91 | if refnames.startswith("$Format"): 92 | if verbose: 93 | print("keywords are unexpanded, not using") 94 | return {} # unexpanded, so not in an unpacked git-archive tarball 95 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 96 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 97 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 98 | TAG = "tag: " 99 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 100 | if not tags: 101 | # Either we're using git < 1.8.3, or there really are no tags. We use 102 | # a heuristic: assume all version tags have a digit. The old git %d 103 | # expansion behaves like git log --decorate=short and strips out the 104 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 105 | # between branches and tags. By ignoring refnames without digits, we 106 | # filter out many common branch names like "release" and 107 | # "stabilization", as well as "HEAD" and "master". 108 | tags = set([r for r in refs if re.search(r'\d', r)]) 109 | if verbose: 110 | print("discarding '%s', no digits" % ",".join(refs-tags)) 111 | if verbose: 112 | print("likely tags: %s" % ",".join(sorted(tags))) 113 | for ref in sorted(tags): 114 | # sorting will prefer e.g. "2.0" over "2.0rc1" 115 | if ref.startswith(tag_prefix): 116 | r = ref[len(tag_prefix):] 117 | if verbose: 118 | print("picking %s" % r) 119 | return { "version": r, 120 | "full": keywords["full"].strip() } 121 | # no suitable tags, so we use the full revision id 122 | if verbose: 123 | print("no suitable tags, using full revision id") 124 | return { "version": keywords["full"].strip(), 125 | "full": keywords["full"].strip() } 126 | 127 | 128 | def git_versions_from_vcs(tag_prefix, root, verbose=False): 129 | # this runs 'git' from the root of the source tree. This only gets called 130 | # if the git-archive 'subst' keywords were *not* expanded, and 131 | # _version.py hasn't already been rewritten with a short version string, 132 | # meaning we're inside a checked out source tree. 133 | 134 | if not os.path.exists(os.path.join(root, ".git")): 135 | if verbose: 136 | print("no .git in %s" % root) 137 | return {} 138 | 139 | GITS = ["git"] 140 | if sys.platform == "win32": 141 | GITS = ["git.cmd", "git.exe"] 142 | stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], 143 | cwd=root) 144 | if stdout is None: 145 | return {} 146 | if not stdout.startswith(tag_prefix): 147 | if verbose: 148 | print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) 149 | return {} 150 | tag = stdout[len(tag_prefix):] 151 | stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 152 | if stdout is None: 153 | return {} 154 | full = stdout.strip() 155 | if tag.endswith("-dirty"): 156 | full += "-dirty" 157 | return {"version": tag, "full": full} 158 | 159 | 160 | def get_versions(default={"version": "unknown", "full": ""}, verbose=False): 161 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 162 | # __file__, we can work backwards from there to the root. Some 163 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 164 | # case we can only use expanded keywords. 165 | 166 | keywords = { "refnames": git_refnames, "full": git_full } 167 | ver = git_versions_from_keywords(keywords, tag_prefix, verbose) 168 | if ver: 169 | return rep_by_pep440(ver) 170 | 171 | try: 172 | root = os.path.abspath(__file__) 173 | # versionfile_source is the relative path from the top of the source 174 | # tree (where the .git directory might live) to this file. Invert 175 | # this to find the root from __file__. 176 | for i in range(len(versionfile_source.split('/'))): 177 | root = os.path.dirname(root) 178 | except NameError: 179 | return default 180 | 181 | return rep_by_pep440( 182 | git_versions_from_vcs(tag_prefix, root, verbose) 183 | or versions_from_parentdir(parentdir_prefix, root, verbose) 184 | or default) 185 | 186 | 187 | def git2pep440(ver_str): 188 | dash_count = ver_str.count('-') 189 | if dash_count == 0: 190 | return ver_str 191 | elif dash_count == 1: 192 | return ver_str.split('-')[0] + ".post.dev1.pre" 193 | elif dash_count == 2: 194 | tag, commits, _ = ver_str.split('-') 195 | return ".post.dev".join([tag, commits]) 196 | elif dash_count == 3: 197 | tag, commits, _, _ = ver_str.split('-') 198 | commits = str(int(commits) + 1) 199 | return ".post.dev".join([tag, commits]) + ".pre" 200 | else: 201 | raise RuntimeError("Invalid version string") 202 | 203 | 204 | def rep_by_pep440(ver): 205 | if ver["full"]: # only if versions_from_parentdir was not used 206 | ver["version"] = git2pep440(ver["version"]) 207 | else: 208 | ver["version"] = ver["version"].split('-')[0] 209 | return ver 210 | -------------------------------------------------------------------------------- /pydse/arma.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | All functionality related to ARMA models. 5 | """ 6 | 7 | from __future__ import division, print_function, absolute_import,\ 8 | unicode_literals 9 | 10 | import re 11 | import logging 12 | import operator 13 | import itertools 14 | 15 | import six 16 | import numpy as np 17 | import pandas as pd 18 | from numpy import linalg 19 | from scipy import optimize 20 | from six.moves import xrange 21 | 22 | from . import utils 23 | from . import stats 24 | from .utils import UnicodeMixin 25 | 26 | __author__ = "Florian Wilhelm" 27 | __copyright__ = "Blue Yonder" 28 | __license__ = "new BSD" 29 | 30 | _logger = logging.getLogger(__name__) 31 | 32 | 33 | class ARMAError(Exception, UnicodeMixin): 34 | def __unicode__(self): 35 | return self.message 36 | 37 | 38 | class ARMA(UnicodeMixin): 39 | """ 40 | A(L)y(t) = B(L)e(t) + C(L)u(t) - TREND(t) 41 | 42 | * L: Lag/Shift operator, 43 | * A: (axpxp) tensor to define auto-regression, 44 | * B: (bxpxp) tensor to define moving-average, 45 | * C: (cxpxm) tensor for external input, 46 | * e: (txp) matrix of unobserved disturbance (white noise), 47 | * y: (txp) matrix of observed output variables, 48 | * u: (mxt) matrix of input variables, 49 | * TREND: (txp) matrix like y or a p-dim vector. 50 | 51 | If B is net set, fall back to VAR, i.e. B(L) = I. 52 | """ 53 | def __init__(self, A, B=None, C=None, TREND=None, rand_state=None): 54 | self.A = np.asarray(A[0]).reshape(A[1], order='F') 55 | if B is not None: 56 | self.B = np.asarray(B[0]).reshape(B[1], order='F') 57 | else: 58 | # Set B(L) = I 59 | shape = A[1][1:] 60 | self.B = np.empty(shape=np.hstack(([1], shape))) 61 | self.B[0] = np.eye(*shape) 62 | if C is not None: 63 | self.C = np.asarray(C[0]).reshape(C[1], order='F') 64 | else: 65 | self.C = np.empty((0, 0, 0)) 66 | if TREND is not None: 67 | self.TREND = np.asarray(TREND) 68 | else: 69 | self.TREND = None 70 | self._check_consistency() 71 | 72 | self.Aconst = np.zeros(self.A.shape, dtype=np.bool) 73 | self.Bconst = np.zeros(self.B.shape, dtype=np.bool) 74 | self.Cconst = np.zeros(self.C.shape, dtype=np.bool) 75 | 76 | if rand_state is None: 77 | self.rand = np.random.RandomState() 78 | elif isinstance(rand_state, np.random.RandomState): 79 | self.rand = rand_state 80 | else: 81 | self.rand = np.random.RandomState(rand_state) 82 | 83 | def _get_num_non_consts(self): 84 | a = np.sum(~self.Aconst) 85 | b = np.sum(~self.Bconst) 86 | c = np.sum(~self.Cconst) 87 | return a, b, c 88 | 89 | @property 90 | def non_consts(self): 91 | """ 92 | Parameters of the ARMA model that are non-constant. 93 | 94 | :return: array 95 | """ 96 | a = self.A[~self.Aconst] 97 | b = self.B[~self.Bconst] 98 | c = self.C[~self.Cconst] 99 | return np.hstack([a, b, c]) 100 | 101 | @non_consts.setter 102 | def non_consts(self, values): 103 | """ 104 | Set the parameters of the ARMA model that are non-constant. 105 | 106 | :param values: array 107 | """ 108 | parts = np.cumsum(self._get_num_non_consts()) 109 | if values.size != parts[2]: 110 | raise ARMAError("Number of values does not equal number " 111 | "of non-constants") 112 | self.A[~self.Aconst] = values[:parts[0]] 113 | self.B[~self.Bconst] = values[parts[0]:parts[1]] 114 | self.C[~self.Cconst] = values[parts[1]:parts[2]] 115 | 116 | def _check_consistency(self): 117 | A, B, C, TREND = self.A, self.B, self.C, self.TREND 118 | if A is None: 119 | raise ARMAError("A needs to be set for an ARMA model") 120 | n = A.shape[1] 121 | if n != A.shape[2] or A.ndim > 3: 122 | raise ARMAError("A needs to be of shape (a, p, p)") 123 | if n != B.shape[1] or (n != B.shape[2] or B.ndim > 3): 124 | raise ARMAError("B needs to be of shape (b, p, p) with A being " 125 | "of shape (a, p, p)") 126 | if C.size != 0 and (n != C.shape[1] or C.ndim > 3): 127 | raise ARMAError("C needs to be of shape (c, p, m) with A being " 128 | "of shape (a, p, p)") 129 | if TREND is not None: 130 | if len(TREND.shape) > 2: 131 | raise ARMAError("TREND needs to of shape (t, p) with A being " 132 | "of shape (a, p, p)") 133 | elif len(TREND.shape) == 2 and n != TREND.shape[1]: 134 | raise ARMAError("TREND needs to of shape (t, p) with A being " 135 | "of shape (a, p, p)") 136 | elif len(TREND.shape) == 1 and n != TREND.shape[0]: 137 | raise ARMAError("TREND needs to of shape (t, p) with A being " 138 | "of shape (a, p, p)") 139 | 140 | def _get_noise(self, samples, p, lags): 141 | w0 = self.rand.normal(size=lags * p).reshape((lags, p)) 142 | w = self.rand.normal(size=samples * p).reshape((samples, p)) 143 | return w0, w 144 | 145 | def _prep_trend(self, dim_t, dim_p, t0=0): 146 | trend = self.TREND 147 | if trend is not None: 148 | if trend.ndim == 2: 149 | assert trend.shape[1] == dim_p 150 | if not trend.shape[0] >= t0+dim_t: 151 | raise ARMAError("TREND needs to be available until " 152 | "t={}".format(t0+dim_t-1)) 153 | trend = trend[t0:t0+dim_t, :] 154 | return trend 155 | else: 156 | return np.tile(trend, (dim_t, 1)) 157 | else: 158 | return np.zeros((dim_t, dim_p)) 159 | 160 | def simulate(self, y0=None, u0=None, u=None, sampleT=100, noise=None): 161 | """ 162 | Simulate an ARMA model. 163 | 164 | :param y0: lagged values of y prior to t=0 in reversed order 165 | :param u0: lagged values of u prior to t=0 in reversed order 166 | :param u: external input time series 167 | :param sampleT: length of the sample to simulate 168 | :param noise: tuple (w0, w) of a random noise time series. w0 are the 169 | lagged values of w prior to t=0 in reversed order. By default a normal 170 | distribution for the white noise is assumed. 171 | :return: simulated time series as array 172 | """ 173 | p = self.A.shape[1] 174 | a, b = self.A.shape[0], self.B.shape[0] 175 | c, m = self.C.shape[0], self.C.shape[2] 176 | y0 = utils.atleast_2d(y0) if y0 is not None else np.zeros((a, p)) 177 | u = utils.atleast_2d(u) if u0 is not None else np.zeros((c, m)) 178 | u0 = utils.atleast_2d(u0) if u0 is not None else np.zeros((c, m)) 179 | if noise is None: 180 | noise = self._get_noise(sampleT, p, b) 181 | w0, w = noise 182 | assert y0.shape[0] >= a 183 | assert w0.shape[0] >= b 184 | assert u0.shape[0] >= c 185 | 186 | # diagonalize with respect to matrix of A's leading coefficients 187 | A0inv = linalg.inv(self.A[0, :, :]) 188 | A = np.tensordot(self.A, A0inv, axes=1) 189 | B = np.tensordot(self.B, A0inv, axes=1) 190 | if c != 0: 191 | C = np.einsum('ijk,kl', self.C, A0inv) 192 | else: 193 | C = np.zeros((c, p, m)) 194 | 195 | # prepend start values to the series 196 | y = self._prep_trend(sampleT, p) 197 | y = np.vstack((y0[a::-1, ...], y)) 198 | w = np.vstack((w0[b::-1, ...], w)) 199 | u = np.vstack((u0[c::-1, ...], u)) 200 | 201 | # perform simulation by multiplying the lagged matrices to the vectors 202 | # and summing over the different lags 203 | for t in xrange(a, sampleT+a): 204 | y[t, :] -= np.einsum('ikj, ij', A[1:, ...], y[t-1:t-a:-1, :]) 205 | if b != 0: 206 | y[t, :] += np.einsum('ikj, ij', B, w[t-a+b:t-a:-1, :]) 207 | if c != 0: 208 | y[t, :] += np.einsum('ikj, ij', C, u[t-a+b:t-a:-1, :]) 209 | return y[a:] 210 | 211 | def forecast(self, y, horizon=0, u=None): 212 | """ 213 | Calculate an one-step-ahead forecast. 214 | 215 | :param y: output time series 216 | :param horizon: number of predictions after y[T_max] 217 | :param u: external input time series 218 | :return: predicted time series as array 219 | """ 220 | p = self.A.shape[1] 221 | a, b = self.A.shape[0], self.B.shape[0] 222 | c, m = self.C.shape[0], self.C.shape[2] 223 | u = u if u is not None else np.zeros((c, m)) 224 | y = utils.atleast_2d(y) 225 | 226 | sampleT = y.shape[0] 227 | predictT = sampleT + horizon 228 | 229 | # diagonalize with respect to matrix of B's leading coefficients 230 | B0inv = linalg.inv(self.B[0, :, :]) 231 | A = np.tensordot(self.A, B0inv, axes=1) 232 | B = np.tensordot(self.B, B0inv, axes=1) 233 | if c != 0: 234 | C = np.einsum('ijk,kl', self.C, B0inv) 235 | else: 236 | C = np.zeros((c, p, m)) 237 | 238 | # calculate directly the residual ... 239 | res = -np.dot(self._prep_trend(sampleT, p)[:sampleT, ...], B0inv) 240 | # and perform prediction 241 | for t in xrange(sampleT): 242 | la, lb, lc = min(a-1, t), min(b-1, t), min(c-1, t) 243 | ba, bb, bc = max(0, t-la), max(0, t-lb), max(0, t-lc) 244 | res[t, :] += np.einsum('ikj,ij', A[la::-1, ...], y[ba:t+1, :]) 245 | if b != 0: 246 | res[t, :] -= np.einsum('ikj,ij', B[lb:0:-1, ...], res[bb:t, :]) 247 | if c != 0: 248 | res[t, :] -= np.einsum('ikj,ij', C[lc::-1, ...], u[bc:t+1, :]) 249 | 250 | pred = np.zeros((predictT, p)) 251 | pred[:sampleT, :] = y[:sampleT, :] - np.dot(res, B[0, :, :]) 252 | 253 | if predictT > sampleT: 254 | A0inv = linalg.inv(self.A[0, :, :]) 255 | A = np.tensordot(self.A, A0inv, axes=1) 256 | B = np.tensordot(self.B, A0inv, axes=1) 257 | if c != 0: 258 | C = np.einsum('ijk,kl', self.C, A0inv) 259 | else: 260 | C = np.zeros((c, p, m)) 261 | pred[sampleT:, :] = np.dot(self._prep_trend(horizon, p, sampleT), 262 | A0inv) 263 | # perform prediction for horizon period 264 | for t in xrange(sampleT, predictT): 265 | for l in xrange(1, a): 266 | if t - l < sampleT: 267 | pred[t, :] -= np.dot(A[l, :, :], y[t - l, :]) 268 | else: 269 | pred[t, :] -= np.dot(A[l, :, :], pred[t - l, :]) 270 | 271 | for l in xrange(b): 272 | if t - l < sampleT: 273 | pred[t, :] += np.dot(B[l, :, :], res[t - l, :]) 274 | 275 | for l in xrange(c): 276 | pred[t, :] += np.dot(C[l, :, :], u[t - l, :]) 277 | 278 | return pred 279 | 280 | def fix_constants(self, fuzz=1e-5, prec=1): 281 | """ 282 | Fix some coefficients as constants depending on their value. 283 | 284 | Coefficient with a absolute difference of ``fuzz`` to a value of 285 | precision ``prec`` are considered constants. 286 | 287 | For example: 288 | 289 | * 1.1 is constant since abs(1.1 - round(1.1, prec)) < fuzz 290 | * 0.01 is non constant since abs(0.01 - round(0.01, prec)) > fuzz 291 | """ 292 | @np.vectorize 293 | def is_const(x): 294 | return abs(x - round(x, prec)) < fuzz 295 | 296 | def set_const(M, Mconst): 297 | M_mask = is_const(M) 298 | Mconst[M_mask] = True 299 | Mconst[~M_mask] = False 300 | 301 | set_const(self.A, self.Aconst) 302 | set_const(self.B, self.Bconst) 303 | if self.C.size != 0: 304 | set_const(self.C, self.Cconst) 305 | 306 | def est_params(self, y): 307 | """ 308 | Maximum likelihood estimation of the ARMA model's coefficients. 309 | 310 | :param y: output series 311 | :return: optimization result (:obj:`~scipy.optimize.OptimizeResult`) 312 | """ 313 | y = utils.atleast_2d(y) 314 | 315 | def cost_function(x): 316 | self.non_consts = x 317 | pred = self.forecast(y=y) 318 | return stats.negloglike(pred, y) 319 | 320 | x0 = self.non_consts 321 | return optimize.minimize(cost_function, x0) 322 | 323 | def _lag_matrix_to_str(self, matrix): 324 | # creates a string from a lag array 325 | def join_with_lag(arr): 326 | poly = str(arr[0]) 327 | for i, val in enumerate(arr[1:], start=1): 328 | if val != 0.: 329 | poly += '{:+.3}L{}'.format(val, i) 330 | return poly 331 | 332 | res_str = '' 333 | _, j_max, k_max = matrix.shape 334 | mat_str = np.empty((j_max, k_max), dtype=object) 335 | for j, k in itertools.product(xrange(j_max), xrange(k_max)): 336 | mat_str[j, k] = join_with_lag(matrix[:, j, k]) 337 | # determine width for each column and set columns to that width 338 | col_widths = [max(map(len, mat_str[:, k])) for k in xrange(k_max)] 339 | for k in xrange(k_max): 340 | fmt = np.vectorize(lambda x: '{:<{}}'.format(x, col_widths[k])) 341 | mat_str[:, k] = fmt(mat_str[:, k]) 342 | for j in xrange(j_max): 343 | res_str += ' '.join(mat_str[j, :]) + '\n' 344 | return res_str 345 | 346 | def __unicode__(self): 347 | desc = '' 348 | TREND = self.TREND 349 | if TREND is not None: 350 | desc += 'TREND=\n' 351 | if TREND.ndim == 1: 352 | TREND = TREND[np.newaxis, :] 353 | arr_str = np.array_str(np.transpose(TREND)) + '\n'*2 354 | arr_str = re.sub(r' *\[+', '', arr_str) 355 | arr_str = re.sub(r' *\]+', '', arr_str) 356 | desc += arr_str 357 | for mat_name in ('A', 'B', 'C'): 358 | matrix = getattr(self, mat_name) 359 | if matrix.shape[0] != 0: 360 | desc += '{}(L) =\n'.format(mat_name) 361 | desc += self._lag_matrix_to_str(matrix) + '\n' 362 | return desc 363 | 364 | def plot_forecast(self, all_y, horizon=0, u=None): 365 | """ 366 | Calculate an one-step-ahead forecast and plot prediction and truth. 367 | 368 | :param y: output time series 369 | :param horizon: number of predictions after y[T_max] 370 | :param u: external input time series 371 | :return: predicted time series as array 372 | """ 373 | def get_lags_idx(arr): 374 | # First entry is always 1 and not part of the used lags 375 | return [str(i) for i, v in enumerate(arr.flatten()) if v != 0][1:] 376 | 377 | if horizon > 0: 378 | y = all_y[:-horizon] 379 | df = pd.DataFrame({ 380 | 'Future': all_y[horizon:], 381 | 'Known Truth': y 382 | }) 383 | else: 384 | y = all_y 385 | df = pd.DataFrame({'Truth': all_y}) 386 | 387 | prediction = self.forecast(y, horizon, u) 388 | df['Prediction'] = prediction[:, 0] 389 | 390 | MAD = np.mean(np.abs(prediction[:, 0] - all_y)[20:]) 391 | df.plot(title="AR lags: {}; MA lags: {}; MAD: {}".format( 392 | ", ".join(get_lags_idx(self.A)), 393 | ", ".join(get_lags_idx(self.B)), MAD)) 394 | 395 | return prediction 396 | 397 | 398 | def minic(ar_lags, ma_lags, y, crit='BIC'): 399 | """ 400 | Minimum information criterion method to fit ARMA. 401 | 402 | Use the Akaike information criterion (AIC) or 403 | Bayesian information criterion (BIC) to determine the 404 | most promising AR and MA lags for an ARMA model. 405 | 406 | This method only works for scalar time series, i.e. 407 | dim(y[0]) = 1. 408 | 409 | :param ar_lags: list of AR lags to consider 410 | :param ma_lags: list of MA lags to consider 411 | :param y: target vector or scalar time series 412 | :param crit: information criterion ('BIC' or 'AIC') 413 | :return: tuple of AR lags and MA lags 414 | """ 415 | assert y.ndim == 1 416 | all_ar_lags = list(utils.powerset(sorted(ar_lags))) 417 | all_ma_lags = list(utils.powerset(sorted(ma_lags))) 418 | lags = itertools.product(all_ar_lags, all_ma_lags) 419 | next(lags) # drop case with no AR and MA lags 420 | metric = dict() # metric 421 | for ar_lags, ma_lags in lags: 422 | arma = ARMA(A=utils.make_lag_arr(ar_lags), 423 | B=utils.make_lag_arr(ma_lags)) 424 | arma.fix_constants() 425 | ret_val = arma.est_params(y) 426 | if ret_val['success']: 427 | k = len(ar_lags) + len(ma_lags) 428 | nloglike = ret_val['fun'] 429 | if crit == 'BIC': 430 | metric[(ar_lags, ma_lags)] = stats.bic(nloglike, k, len(y)) 431 | elif crit == 'AIC': 432 | metric[(ar_lags, ma_lags)] = stats.aic(nloglike, k) 433 | else: 434 | raise RuntimeError("Unknown method") 435 | else: 436 | metric[(ar_lags, ma_lags)] = np.inf 437 | return min(six.iteritems(metric), key=operator.itemgetter(1))[0] 438 | -------------------------------------------------------------------------------- /pydse/data/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | Example data files taken from `DataMarket `_. 5 | Data is under the `default open license 6 | `_: 7 | """ 8 | 9 | from __future__ import division, print_function, absolute_import 10 | 11 | import os 12 | import inspect 13 | from datetime import datetime 14 | import pandas as pd 15 | 16 | __author__ = "Florian Wilhelm" 17 | __copyright__ = "Blue Yonder" 18 | __license__ = "new BSD" 19 | 20 | __location__ = os.path.join( 21 | os.getcwd(), os.path.dirname(inspect.getfile(inspect.currentframe()))) 22 | 23 | 24 | def _get_df_from_file(filename): 25 | def parse_dates(date_str): 26 | for date_format in ['%y-%m', '%Y-%m']: 27 | try: 28 | return datetime.strptime(date_str, date_format) 29 | except ValueError: 30 | pass 31 | else: 32 | return ValueError("Could not parse the date {}".format(date_str)) 33 | 34 | path = os.path.join(__location__, filename) 35 | return pd.read_csv(path, sep=";", parse_dates=True, index_col=0, 36 | date_parser=parse_dates) 37 | 38 | 39 | def airline_passengers(): 40 | """ 41 | Monthly totals of international airline passengers in thousands, 42 | Jan 1949 - Dec 1960 43 | """ 44 | return _get_df_from_file("international-airline-passengers.csv") 45 | 46 | 47 | def m1_us(): 48 | """ 49 | Monthly M1 U.S., Jan 1959 - Feb 1992 50 | """ 51 | return _get_df_from_file("m1-us-1959119922.csv") 52 | 53 | 54 | def cpi_canada(): 55 | """ 56 | Monthly CPI, Canada, Jan 1950 - Dec 1973 57 | """ 58 | return _get_df_from_file("monthly-cpi-canada-19501973.csv") 59 | 60 | 61 | def sales_product(): 62 | """ 63 | Monthly sales of a plastic manufacturer's product, Jan 2001 - May 2012 64 | """ 65 | return _get_df_from_file("monthly-sales-of-product-a-for-a.csv") 66 | 67 | 68 | def sales_cola(): 69 | """ 70 | Monthly sales of Tasty Cola, Jan 2001 - Mar 2012 71 | """ 72 | return _get_df_from_file("monthly-sales-of-tasty-cola.csv") 73 | 74 | 75 | def sales_shampoo(): 76 | """ 77 | Monthly sales of shampoo, Jan 2001 - Mar 2012 78 | """ 79 | return _get_df_from_file("sales-of-shampoo-over-a-three-ye.csv") 80 | 81 | 82 | def sales_petroleum(): 83 | """ 84 | Monthly sales of petroleum and related products in the U.S., 85 | Jan 1971 - Dec 1991 86 | """ 87 | return _get_df_from_file("us-monthly-sales-of-petroleum-an.csv") 88 | -------------------------------------------------------------------------------- /pydse/data/international-airline-passengers.csv: -------------------------------------------------------------------------------- 1 | "Month";"Passengers" 2 | "1949-01";112 3 | "1949-02";118 4 | "1949-03";132 5 | "1949-04";129 6 | "1949-05";121 7 | "1949-06";135 8 | "1949-07";148 9 | "1949-08";148 10 | "1949-09";136 11 | "1949-10";119 12 | "1949-11";104 13 | "1949-12";118 14 | "1950-01";115 15 | "1950-02";126 16 | "1950-03";141 17 | "1950-04";135 18 | "1950-05";125 19 | "1950-06";149 20 | "1950-07";170 21 | "1950-08";170 22 | "1950-09";158 23 | "1950-10";133 24 | "1950-11";114 25 | "1950-12";140 26 | "1951-01";145 27 | "1951-02";150 28 | "1951-03";178 29 | "1951-04";163 30 | "1951-05";172 31 | "1951-06";178 32 | "1951-07";199 33 | "1951-08";199 34 | "1951-09";184 35 | "1951-10";162 36 | "1951-11";146 37 | "1951-12";166 38 | "1952-01";171 39 | "1952-02";180 40 | "1952-03";193 41 | "1952-04";181 42 | "1952-05";183 43 | "1952-06";218 44 | "1952-07";230 45 | "1952-08";242 46 | "1952-09";209 47 | "1952-10";191 48 | "1952-11";172 49 | "1952-12";194 50 | "1953-01";196 51 | "1953-02";196 52 | "1953-03";236 53 | "1953-04";235 54 | "1953-05";229 55 | "1953-06";243 56 | "1953-07";264 57 | "1953-08";272 58 | "1953-09";237 59 | "1953-10";211 60 | "1953-11";180 61 | "1953-12";201 62 | "1954-01";204 63 | "1954-02";188 64 | "1954-03";235 65 | "1954-04";227 66 | "1954-05";234 67 | "1954-06";264 68 | "1954-07";302 69 | "1954-08";293 70 | "1954-09";259 71 | "1954-10";229 72 | "1954-11";203 73 | "1954-12";229 74 | "1955-01";242 75 | "1955-02";233 76 | "1955-03";267 77 | "1955-04";269 78 | "1955-05";270 79 | "1955-06";315 80 | "1955-07";364 81 | "1955-08";347 82 | "1955-09";312 83 | "1955-10";274 84 | "1955-11";237 85 | "1955-12";278 86 | "1956-01";284 87 | "1956-02";277 88 | "1956-03";317 89 | "1956-04";313 90 | "1956-05";318 91 | "1956-06";374 92 | "1956-07";413 93 | "1956-08";405 94 | "1956-09";355 95 | "1956-10";306 96 | "1956-11";271 97 | "1956-12";306 98 | "1957-01";315 99 | "1957-02";301 100 | "1957-03";356 101 | "1957-04";348 102 | "1957-05";355 103 | "1957-06";422 104 | "1957-07";465 105 | "1957-08";467 106 | "1957-09";404 107 | "1957-10";347 108 | "1957-11";305 109 | "1957-12";336 110 | "1958-01";340 111 | "1958-02";318 112 | "1958-03";362 113 | "1958-04";348 114 | "1958-05";363 115 | "1958-06";435 116 | "1958-07";491 117 | "1958-08";505 118 | "1958-09";404 119 | "1958-10";359 120 | "1958-11";310 121 | "1958-12";337 122 | "1959-01";360 123 | "1959-02";342 124 | "1959-03";406 125 | "1959-04";396 126 | "1959-05";420 127 | "1959-06";472 128 | "1959-07";548 129 | "1959-08";559 130 | "1959-09";463 131 | "1959-10";407 132 | "1959-11";362 133 | "1959-12";405 134 | "1960-01";417 135 | "1960-02";391 136 | "1960-03";419 137 | "1960-04";461 138 | "1960-05";472 139 | "1960-06";535 140 | "1960-07";622 141 | "1960-08";606 142 | "1960-09";508 143 | "1960-10";461 144 | "1960-11";390 145 | "1960-12";432 146 | -------------------------------------------------------------------------------- /pydse/data/m1-us-1959119922.csv: -------------------------------------------------------------------------------- 1 | "Month";"M1" 2 | "1959-01";143.1 3 | "1959-02";140.3 4 | "1959-03";139.4 5 | "1959-04";140.7 6 | "1959-05";139.6 7 | "1959-06";140.4 8 | "1959-07";141.2 9 | "1959-08";140.9 10 | "1959-09";141.3 11 | "1959-10";141.7 12 | "1959-11";142.8 13 | "1959-12";144.7 14 | "1960-01";144.4 15 | "1960-02";140.9 16 | "1960-03";139.5 17 | "1960-04";140.8 18 | "1960-05";138.7 19 | "1960-06";139 20 | "1960-07";140 21 | "1960-08";140.4 22 | "1960-09";141.6 23 | "1960-10";142.3 24 | "1960-11";143.4 25 | "1960-12";145.7 26 | "1961-01";145.7 27 | "1961-02";142.8 28 | "1961-03";141.8 29 | "1961-04";143.5 30 | "1961-05";141.8 31 | "1961-06";142.4 32 | "1961-07";142.8 33 | "1961-08";142.7 34 | "1961-09";144.3 35 | "1961-10";145.7 36 | "1961-11";147.6 37 | "1961-12";150.5 38 | "1962-01";150.2 39 | "1962-02";146.9 40 | "1962-03";146 41 | "1962-04";148 42 | "1962-05";145.8 43 | "1962-06";146.2 44 | "1962-07";146.4 45 | "1962-08";145.8 46 | "1962-09";146.9 47 | "1962-10";148.4 48 | "1962-11";150.2 49 | "1962-12";153.3 50 | "1963-01";153.6 51 | "1963-02";150.1 52 | "1963-03";149.3 53 | "1963-04";151.5 54 | "1963-05";149.3 55 | "1963-06";151.4 56 | "1963-07";151.3 57 | "1963-08";150.9 58 | "1963-09";152.5 59 | "1963-10";154.4 60 | "1963-11";156.7 61 | "1963-12";159 62 | "1964-01";159.4 63 | "1964-02";155.4 64 | "1964-03";154.6 65 | "1964-04";156.8 66 | "1964-05";154.2 67 | "1964-06";155.5 68 | "1964-07";157.1 69 | "1964-08";157 70 | "1964-09";159.4 71 | "1964-10";161.3 72 | "1964-11";163.1 73 | "1964-12";166.4 74 | "1965-01";166.9 75 | "1965-02";161.9 76 | "1965-03";161.5 77 | "1965-04";164.2 78 | "1965-05";160.3 79 | "1965-06";162.2 80 | "1965-07";163.5 81 | "1965-08";162.8 82 | "1965-09";165.6 83 | "1965-10";168.2 84 | "1965-11";169.9 85 | "1965-12";174.4 86 | "1966-01";175.6 87 | "1966-02";170.3 88 | "1966-03";170.4 89 | "1966-04";174.1 90 | "1966-05";169.6 91 | "1966-06";171.7 92 | "1966-07";171 93 | "1966-08";170 94 | "1966-09";172.7 95 | "1966-10";173.4 96 | "1966-11";174.6 97 | "1966-12";178.6 98 | "1967-01";178.4 99 | "1967-02";173.4 100 | "1967-03";174.6 101 | "1967-04";176.6 102 | "1967-05";174.1 103 | "1967-06";177.4 104 | "1967-07";179.1 105 | "1967-08";179 106 | "1967-09";181.7 107 | "1967-10";183.9 108 | "1967-11";185.7 109 | "1967-12";190.3 110 | "1968-01";189 111 | "1968-02";184.9 112 | "1968-03";185.4 113 | "1968-04";189.3 114 | "1968-05";186.5 115 | "1968-06";190.2 116 | "1968-07";191.9 117 | "1968-08";191.4 118 | "1968-09";193.9 119 | "1968-10";196.3 120 | "1968-11";199.6 121 | "1968-12";204.8 122 | "1969-01";205.9 123 | "1969-02";199.3 124 | "1969-03";199.8 125 | "1969-04";203.6 126 | "1969-05";199.4 127 | "1969-06";202.3 128 | "1969-07";203.3 129 | "1969-08";201.5 130 | "1969-09";203.2 131 | "1969-10";205 132 | "1969-11";207 133 | "1969-12";211.4 134 | "1970-01";212.9 135 | "1970-02";204 136 | "1970-03";205.5 137 | "1970-04";210.1 138 | "1970-05";206.2 139 | "1970-06";208.9 140 | "1970-07";210.1 141 | "1970-08";210 142 | "1970-09";212.8 143 | "1970-10";214.4 144 | "1970-11";216.7 145 | "1970-12";222.2 146 | "1971-01";222.6 147 | "1971-02";216.6 148 | "1971-03";218.6 149 | "1971-04";223.7 150 | "1971-05";221.1 151 | "1971-06";225.2 152 | "1971-07";227.5 153 | "1971-08";225.9 154 | "1971-09";227.7 155 | "1971-10";229.1 156 | "1971-11";231.2 157 | "1971-12";236.9 158 | "1972-01";237.5 159 | "1972-02";231.4 160 | "1972-03";234.2 161 | "1972-04";239.5 162 | "1972-05";234.7 163 | "1972-06";238.8 164 | "1972-07";241.8 165 | "1972-08";241.3 166 | "1972-09";244.5 167 | "1972-10";247 168 | "1972-11";250.5 169 | "1972-12";258.9 170 | "1973-01";259.4 171 | "1973-02";251.2 172 | "1973-03";251.6 173 | "1973-04";257 174 | "1973-05";253.6 175 | "1973-06";259.3 176 | "1973-07";261.1 177 | "1973-08";258.6 178 | "1973-09";259.5 179 | "1973-10";261.4 180 | "1973-11";265.6 181 | "1973-12";273.3 182 | "1974-01";271.8 183 | "1974-02";264.1 184 | "1974-03";266.5 185 | "1974-04";271.6 186 | "1974-05";266.3 187 | "1974-06";271.5 188 | "1974-07";273.5 189 | "1974-08";271 190 | "1974-09";272.6 191 | "1974-10";274.8 192 | "1974-11";278.8 193 | "1974-12";285.2 194 | "1975-01";281.8 195 | "1975-02";273.3 196 | "1975-03";276.4 197 | "1975-04";281.4 198 | "1975-05";278.1 199 | "1975-06";286 200 | "1975-07";288 201 | "1975-08";286.3 202 | "1975-09";287.8 203 | "1975-10";288.5 204 | "1975-11";293.5 205 | "1975-12";299 206 | "1976-01";296.8 207 | "1976-02";289 208 | "1976-03";291.4 209 | "1976-04";299.9 210 | "1976-05";295.1 211 | "1976-06";299.4 212 | "1976-07";302.3 213 | "1976-08";301 214 | "1976-09";302.5 215 | "1976-10";307 216 | "1976-11";309.7 217 | "1976-12";318.6 218 | "1977-01";317.7 219 | "1977-02";309 220 | "1977-03";312.2 221 | "1977-04";322.7 222 | "1977-05";315.6 223 | "1977-06";321.7 224 | "1977-07";326.3 225 | "1977-08";324.3 226 | "1977-09";327.7 227 | "1977-10";332 228 | "1977-11";335.4 229 | "1977-12";344.1 230 | "1978-01";343.4 231 | "1978-02";332 232 | "1978-03";334.9 233 | "1978-04";347.5 234 | "1978-05";342.4 235 | "1978-06";349.4 236 | "1978-07";353.9 237 | "1978-08";351.7 238 | "1978-09";357 239 | "1978-10";359.4 240 | "1978-11";362.9 241 | "1978-12";372.5 242 | "1979-01";367.8 243 | "1979-02";356.4 244 | "1979-03";360.8 245 | "1979-04";376.2 246 | "1979-05";367.1 247 | "1979-06";376.7 248 | "1979-07";383.3 249 | "1979-08";381.9 250 | "1979-09";385.6 251 | "1979-10";387.7 252 | "1979-11";389.8 253 | "1979-12";398.6 254 | "1980-01";390.7 255 | "1980-02";380.9 256 | "1980-03";382.4 257 | "1980-04";387.1 258 | "1980-05";377.8 259 | "1980-06";387.6 260 | "1980-07";394.8 261 | "1980-08";398.5 262 | "1980-09";404.9 263 | "1980-10";411 264 | "1980-11";416.1 265 | "1980-12";419.8 266 | "1981-01";416.5 267 | "1981-02";405.7 268 | "1981-03";412.5 269 | "1981-04";431.3 270 | "1981-05";418.6 271 | "1981-06";423 272 | "1981-07";427.9 273 | "1981-08";426.1 274 | "1981-09";427.3 275 | "1981-10";429.8 276 | "1981-11";435.2 277 | "1981-12";447.2 278 | "1982-01";448.7 279 | "1982-02";432.6 280 | "1982-03";435.8 281 | "1982-04";451.3 282 | "1982-05";441.1 283 | "1982-06";446.5 284 | "1982-07";449.6 285 | "1982-08";450 286 | "1982-09";456.4 287 | "1982-10";466 288 | "1982-11";474.5 289 | "1982-12";486 290 | "1983-01";483 291 | "1983-02";474.2 292 | "1983-03";482.9 293 | "1983-04";498.7 294 | "1983-05";494.1 295 | "1983-06";503.7 296 | "1983-07";510.7 297 | "1983-08";508.5 298 | "1983-09";511.5 299 | "1983-10";517.4 300 | "1983-11";522.1 301 | "1983-12";533.4 302 | "1984-01";530.4 303 | "1984-02";517.6 304 | "1984-03";524.2 305 | "1984-04";539.2 306 | "1984-05";530.8 307 | "1984-06";541.4 308 | "1984-07";543.3 309 | "1984-08";539 310 | "1984-09";542.5 311 | "1984-10";542.1 312 | "1984-11";549.6 313 | "1984-12";564.5 314 | "1985-01";561.1 315 | "1985-02";551.9 316 | "1985-03";558.3 317 | "1985-04";575 318 | "1985-05";569.4 319 | "1985-06";585.2 320 | "1985-07";592 321 | "1985-08";594.8 322 | "1985-09";602.2 323 | "1985-10";605.5 324 | "1985-11";615.1 325 | "1985-12";633.5 326 | "1986-01";626.8 327 | "1986-02";613.1 328 | "1986-03";624.6 329 | "1986-04";647.2 330 | "1986-05";645.7 331 | "1986-06";663.5 332 | "1986-07";674 333 | "1986-08";679.1 334 | "1986-09";685.2 335 | "1986-10";692.8 336 | "1986-11";709.5 337 | "1986-12";740.6 338 | "1987-01";737.5 339 | "1987-02";717.1 340 | "1987-03";723.5 341 | "1987-04";752.5 342 | "1987-05";739.9 343 | "1987-06";744.4 344 | "1987-07";746.8 345 | "1987-08";745 346 | "1987-09";745.2 347 | "1987-10";753.7 348 | "1987-11";756 349 | "1987-12";765.9 350 | "1988-01";764.7 351 | "1988-02";745 352 | "1988-03";752.1 353 | "1988-04";778.3 354 | "1988-05";763.8 355 | "1988-06";778.8 356 | "1988-07";785.6 357 | "1988-08";781.3 358 | "1988-09";780 359 | "1988-10";780.8 360 | "1988-11";787.1 361 | "1988-12";803.2 362 | "1989-01";793 363 | "1989-02";772.3 364 | "1989-03";775.2 365 | "1989-04";791.3 366 | "1989-05";767.2 367 | "1989-06";773.8 368 | "1989-07";781.7 369 | "1989-08";777.4 370 | "1989-09";778.5 371 | "1989-10";784.5 372 | "1989-11";791.4 373 | "1989-12";811.9 374 | "1990-01";802.4 375 | "1990-02";788.3 376 | "1990-03";796.2 377 | "1990-04";818 378 | "1990-05";797.3 379 | "1990-06";810.8 380 | "1990-07";812.9 381 | "1990-08";814.5 382 | "1990-09";818.9 383 | "1990-10";817.6 384 | "1990-11";826.1 385 | "1990-12";844.3 386 | "1991-01";833.2 387 | "1991-02";823.4 388 | "1991-03";835 389 | "1991-04";852.9 390 | "1991-05";841.9 391 | "1991-06";857.8 392 | "1991-07";861.9 393 | "1991-08";864.2 394 | "1991-09";867.3 395 | "1991-10";875 396 | "1991-11";893.4 397 | "1991-12";916.8 398 | "1992-01";918.1 399 | "1992-02";916.5 400 | -------------------------------------------------------------------------------- /pydse/data/monthly-cpi-canada-19501973.csv: -------------------------------------------------------------------------------- 1 | "Month";"CPI" 2 | "1950-01";77.5 3 | "1950-02";77.6 4 | "1950-03";78.1 5 | "1950-04";78.3 6 | "1950-05";78.3 7 | "1950-06";78.9 8 | "1950-07";79.5 9 | "1950-08";80 10 | "1950-09";80.7 11 | "1950-10";82 12 | "1950-11";82.4 13 | "1950-12";82.5 14 | "1951-01";83.4 15 | "1951-02";84.4 16 | "1951-03";85.8 17 | "1951-04";86.5 18 | "1951-05";86.8 19 | "1951-06";88 20 | "1951-07";88.7 21 | "1951-08";89.4 22 | "1951-09";90.2 23 | "1951-10";90.6 24 | "1951-11";91.3 25 | "1951-12";91.4 26 | "1952-01";91.5 27 | "1952-02";91 28 | "1952-03";90.5 29 | "1952-04";90.4 30 | "1952-05";89.7 31 | "1952-06";89.8 32 | "1952-07";89.9 33 | "1952-08";89.8 34 | "1952-09";89.9 35 | "1952-10";89.8 36 | "1952-11";89.9 37 | "1952-12";89.6 38 | "1953-01";89.6 39 | "1953-02";89.4 40 | "1953-03";88.9 41 | "1953-04";88.7 42 | "1953-05";88.5 43 | "1953-06";88.9 44 | "1953-07";89.3 45 | "1953-08";89.6 46 | "1953-09";89.9 47 | "1953-10";90.3 48 | "1953-11";89.9 49 | "1953-12";89.6 50 | "1954-01";89.6 51 | "1954-02";89.6 52 | "1954-03";89.4 53 | "1954-04";89.5 54 | "1954-05";89.4 55 | "1954-06";89.9 56 | "1954-07";89.9 57 | "1954-08";90.6 58 | "1954-09";90.4 59 | "1954-10";90.4 60 | "1954-11";90.4 61 | "1954-12";90.2 62 | "1955-01";90.1 63 | "1955-02";90 64 | "1955-03";89.8 65 | "1955-04";89.9 66 | "1955-05";90.1 67 | "1955-06";89.7 68 | "1955-07";89.8 69 | "1955-08";90.1 70 | "1955-09";90.4 71 | "1955-10";90.5 72 | "1955-11";90.5 73 | "1955-12";90.5 74 | "1956-01";90.4 75 | "1956-02";90.1 76 | "1956-03";90.1 77 | "1956-04";90.2 78 | "1956-05";90.2 79 | "1956-06";91.2 80 | "1956-07";91.7 81 | "1956-08";92.2 82 | "1956-09";92.1 83 | "1956-10";92.7 84 | "1956-11";93.1 85 | "1956-12";93.2 86 | "1957-01";93.1 87 | "1957-02";93.3 88 | "1957-03";93.3 89 | "1957-04";93.6 90 | "1957-05";93.7 91 | "1957-06";94.1 92 | "1957-07";94.3 93 | "1957-08";94.9 94 | "1957-09";95.4 95 | "1957-10";95.5 96 | "1957-11";95.4 97 | "1957-12";95.3 98 | "1958-01";95.5 99 | "1958-02";95.7 100 | "1958-03";96.2 101 | "1958-04";96.9 102 | "1958-05";96.8 103 | "1958-06";96.8 104 | "1958-07";96.5 105 | "1958-08";96.9 106 | "1958-09";97.2 107 | "1958-10";97.5 108 | "1958-11";97.8 109 | "1958-12";97.7 110 | "1959-01";97.6 111 | "1959-02";97.3 112 | "1959-03";97.1 113 | "1959-04";97.1 114 | "1959-05";97.2 115 | "1959-06";96.7 116 | "1959-07";97.4 117 | "1959-08";97.8 118 | "1959-09";98.4 119 | "1959-10";99.1 120 | "1959-11";99.3 121 | "1959-12";99 122 | "1960-01";98.7 123 | "1960-02";98.5 124 | "1960-03";98.2 125 | "1960-04";98.7 126 | "1960-05";98.6 127 | "1960-06";98.8 128 | "1960-07";98.7 129 | "1960-08";99 130 | "1960-09";99.4 131 | "1960-10";100.2 132 | "1960-11";100.3 133 | "1960-12";100.3 134 | "1961-01";100 135 | "1961-02";99.8 136 | "1961-03";99.9 137 | "1961-04";99.9 138 | "1961-05";99.8 139 | "1961-06";99.8 140 | "1961-07";99.8 141 | "1961-08";99.9 142 | "1961-09";99.9 143 | "1961-10";100 144 | "1961-11";100.4 145 | "1961-12";100.5 146 | "1962-01";100.4 147 | "1962-02";100.5 148 | "1962-03";100.4 149 | "1962-04";100.9 150 | "1962-05";100.7 151 | "1962-06";101 152 | "1962-07";101.4 153 | "1962-08";101.7 154 | "1962-09";101.4 155 | "1962-10";101.8 156 | "1962-11";102.1 157 | "1962-12";102.1 158 | "1963-01";102.2 159 | "1963-02";102.2 160 | "1963-03";102.2 161 | "1963-04";102.4 162 | "1963-05";102.4 163 | "1963-06";102.8 164 | "1963-07";103.3 165 | "1963-08";103.6 166 | "1963-09";103.3 167 | "1963-10";103.4 168 | "1963-11";103.7 169 | "1963-12";103.9 170 | "1964-01";103.9 171 | "1964-02";104.1 172 | "1964-03";104.2 173 | "1964-04";104.5 174 | "1964-05";104.5 175 | "1964-06";104.7 176 | "1964-07";105.4 177 | "1964-08";105.3 178 | "1964-09";105 179 | "1964-10";105 180 | "1964-11";105.2 181 | "1964-12";105.9 182 | "1965-01";106 183 | "1965-02";106.2 184 | "1965-03";106.3 185 | "1965-04";106.6 186 | "1965-05";106.8 187 | "1965-06";107.6 188 | "1965-07";108 189 | "1965-08";107.9 190 | "1965-09";107.7 191 | "1965-10";107.8 192 | "1965-11";108.5 193 | "1965-12";109 194 | "1966-01";109.3 195 | "1966-02";110 196 | "1966-03";110.2 197 | "1966-04";110.8 198 | "1966-05";111 199 | "1966-06";111.3 200 | "1966-07";111.7 201 | "1966-08";112.2 202 | "1966-09";112.3 203 | "1966-10";112.5 204 | "1966-11";112.6 205 | "1966-12";112.9 206 | "1967-01";113 207 | "1967-02";113.1 208 | "1967-03";113.4 209 | "1967-04";114.4 210 | "1967-05";114.6 211 | "1967-06";115.2 212 | "1967-07";116.3 213 | "1967-08";116.8 214 | "1967-09";116.6 215 | "1967-10";116.5 216 | "1967-11";116.9 217 | "1967-12";117.5 218 | "1968-01";118.1 219 | "1968-02";118.2 220 | "1968-03";118.6 221 | "1968-04";119.3 222 | "1968-05";119.3 223 | "1968-06";119.7 224 | "1968-07";120.4 225 | "1968-08";120.7 226 | "1968-09";121.1 227 | "1968-10";121.4 228 | "1968-11";121.9 229 | "1968-12";122.3 230 | "1969-01";122.6 231 | "1969-02";122.6 232 | "1969-03";123.2 233 | "1969-04";124.6 234 | "1969-05";124.9 235 | "1969-06";125.9 236 | "1969-07";126.4 237 | "1969-08";126.9 238 | "1969-09";126.6 239 | "1969-10";126.8 240 | "1969-11";127.4 241 | "1969-12";127.9 242 | "1970-01";128.2 243 | "1970-02";128.7 244 | "1970-03";128.9 245 | "1970-04";129.7 246 | "1970-05";129.6 247 | "1970-06";129.9 248 | "1970-07";130.5 249 | "1970-08";130.5 250 | "1970-09";130.2 251 | "1970-10";130.3 252 | "1970-11";130.3 253 | "1970-12";129.8 254 | "1971-01";130.3 255 | "1971-02";130.9 256 | "1971-03";131.3 257 | "1971-04";132.2 258 | "1971-05";132.7 259 | "1971-06";133 260 | "1971-07";134.1 261 | "1971-08";135 262 | "1971-09";134.7 263 | "1971-10";134.9 264 | "1971-11";135.4 265 | "1971-12";136.3 266 | "1972-01";136.7 267 | "1972-02";137.3 268 | "1972-03";137.4 269 | "1972-04";138.2 270 | "1972-05";138.3 271 | "1972-06";138.5 272 | "1972-07";140.2 273 | "1972-08";141.3 274 | "1972-09";141.8 275 | "1972-10";142 276 | "1972-11";142.3 277 | "1972-12";143.3 278 | "1973-01";144.5 279 | "1973-02";145.3 280 | "1973-03";145.7 281 | "1973-04";147.3 282 | "1973-05";148.4 283 | "1973-06";149.7 284 | "1973-07";151 285 | "1973-08";153 286 | "1973-09";153.9 287 | "1973-10";154.3 288 | "1973-11";155.5 289 | "1973-12";156.4 290 | -------------------------------------------------------------------------------- /pydse/data/monthly-sales-of-product-a-for-a.csv: -------------------------------------------------------------------------------- 1 | "Month";"Sales" 2 | "01-01";742 3 | "01-02";741 4 | "01-03";896 5 | "01-04";951 6 | "01-05";1030 7 | "01-06";697 8 | "01-07";700 9 | "01-08";793 10 | "01-09";861 11 | "01-10";1032 12 | "01-11";776 13 | "01-12";774 14 | "02-01";885 15 | "02-02";938 16 | "02-03";1126 17 | "02-04";898 18 | "02-05";932 19 | "02-06";1055 20 | "02-07";1109 21 | "02-08";1285 22 | "02-09";1030 23 | "02-10";1099 24 | "02-11";1204 25 | "02-12";1274 26 | "03-01";1468 27 | "03-02";1107 28 | "03-03";1223 29 | "03-04";1326 30 | "03-05";1422 31 | "03-06";1637 32 | "03-07";1165 33 | "03-08";1290 34 | "03-09";1303 35 | "03-10";1486 36 | "03-11";1611 37 | "03-12";1216 38 | "04-01";1349 39 | "04-02";1436 40 | "04-03";1555 41 | "04-04";1608 42 | "04-05";1208 43 | "04-06";1341 44 | "04-07";1473 45 | "04-08";1604 46 | "04-09";1528 47 | "04-10";1131 48 | "04-11";1296 49 | "04-12";1453 50 | "05-01";1600 51 | "05-02";1420 52 | "05-03";971 53 | "05-04";1066 54 | "05-05";1170 55 | "05-06";1403 56 | "05-07";1119 57 | "05-08";783 58 | "05-09";901 59 | "05-10";1023 60 | "05-11";1209 61 | "05-12";1013 62 | -------------------------------------------------------------------------------- /pydse/data/monthly-sales-of-tasty-cola.csv: -------------------------------------------------------------------------------- 1 | "Month";"Sales" 2 | "01-01";189 3 | "01-02";229 4 | "01-03";249 5 | "01-04";289 6 | "01-05";260 7 | "01-06";431 8 | "01-07";660 9 | "01-08";777 10 | "01-09";915 11 | "01-10";613 12 | "01-11";485 13 | "01-12";277 14 | "02-01";244 15 | "02-02";296 16 | "02-03";319 17 | "02-04";370 18 | "02-05";313 19 | "02-06";556 20 | "02-07";831 21 | "02-08";960 22 | "02-09";1152 23 | "02-10";759 24 | "02-11";607 25 | "02-12";371 26 | "03-01";298 27 | "03-02";378 28 | "03-03";373 29 | "03-04";443 30 | "03-05";374 31 | "03-06";660 32 | "03-07";1004 33 | "03-08";1153 34 | "03-09";1388 35 | "03-10";904 36 | "03-11";715 37 | "03-12";441 38 | -------------------------------------------------------------------------------- /pydse/data/sales-of-shampoo-over-a-three-ye.csv: -------------------------------------------------------------------------------- 1 | "Month";"Sales" 2 | "01-01";266 3 | "01-02";145.9 4 | "01-03";183.1 5 | "01-04";119.3 6 | "01-05";180.3 7 | "01-06";168.5 8 | "01-07";231.8 9 | "01-08";224.5 10 | "01-09";192.8 11 | "01-10";122.9 12 | "01-11";336.5 13 | "01-12";185.9 14 | "02-01";194.3 15 | "02-02";149.5 16 | "02-03";210.1 17 | "02-04";273.3 18 | "02-05";191.4 19 | "02-06";287 20 | "02-07";226 21 | "02-08";303.6 22 | "02-09";289.9 23 | "02-10";421.6 24 | "02-11";264.5 25 | "02-12";342.3 26 | "03-01";339.7 27 | "03-02";440.4 28 | "03-03";315.9 29 | "03-04";439.3 30 | "03-05";401.3 31 | "03-06";437.4 32 | "03-07";575.5 33 | "03-08";407.6 34 | "03-09";682 35 | "03-10";475.3 36 | "03-11";581.3 37 | "03-12";646.9 38 | -------------------------------------------------------------------------------- /pydse/data/us-monthly-sales-of-petroleum-an.csv: -------------------------------------------------------------------------------- 1 | "Month";"Chemicals";"Coal";"Petrol";"Vehicles" 2 | "1971-01";3.896;49.78;2.154;4.367 3 | "1971-02";4.346;47.029;2.25;5.147 4 | "1971-03";4.318;56.92;2.165;5.418 5 | "1971-04";4.536;54.336;2.223;4.897 6 | "1971-05";4.454;50.442;2.19;5.002 7 | "1971-06";4.554;49.298;2.288;5.329 8 | "1971-07";4.058;39.537;2.25;3.537 9 | "1971-08";4.342;56.185;2.251;3.94 10 | "1971-09";4.693;54.449;2.281;5.226 11 | "1971-10";4.389;11.857;2.32;5.429 12 | "1971-11";4.289;26.327;2.285;5.4 13 | "1971-12";3.997;56.032;2.279;4.446 14 | "1972-01";4.353;49.68;2.319;5.072 15 | "1972-02";4.609;49.112;2.396;5.707 16 | "1972-03";4.794;54.438;2.397;5.76 17 | "1972-04";4.975;49.814;2.462;5.807 18 | "1972-05";4.859;52.879;2.368;5.865 19 | "1972-06";5.027;50.083;2.505;5.909 20 | "1972-07";4.382;40.964;2.49;3.681 21 | "1972-08";4.798;52.169;2.553;3.895 22 | "1972-09";5.076;49.374;2.611;6.247 23 | "1972-10";4.979;51.671;2.528;6.629 24 | "1972-11";4.845;50.297;2.618;6.67 25 | "1972-12";4.74;44.904;2.685;5.52 26 | "1973-01";4.885;49.379;2.649;6.71 27 | "1973-02";5.579;45.893;2.723;7.134 28 | "1973-03";5.741;50.547;2.675;7.097 29 | "1973-04";5.91;46.999;2.723;6.741 30 | "1973-05";5.784;51.42;2.781;7.006 31 | "1973-06";5.962;46.613;2.953;7.169 32 | "1973-07";5.152;43.801;2.919;5.419 33 | "1973-08";5.536;55.874;3.017;4.667 34 | "1973-09";5.769;48.338;3.121;6.227 35 | "1973-10";5.643;54.38;3.135;7.314 36 | "1973-11";5.61;49.825;3.425;6.928 37 | "1973-12";5.463;48.668;3.694;4.866 38 | "1974-01";5.685;53.53;3.742;5.611 39 | "1974-02";6.452;49.851;4.173;5.762 40 | "1974-03";6.729;51.027;4.374;5.638 41 | "1974-04";7.094;54.181;4.499;5.891 42 | "1974-05";6.987;57.448;4.725;6.229 43 | "1974-06";7.158;47.884;4.983;6.485 44 | "1974-07";6.58;49.206;5.068;5.169 45 | "1974-08";7.026;51.604;5.104;5.236 46 | "1974-09";7.556;52.472;5.056;6.921 47 | "1974-10";7.201;60.293;5.042;7.703 48 | "1974-11";6.791;33.524;5.116;6.985 49 | "1974-12";6.118;39.98;4.97;4.49 50 | "1975-01";6.702;55.167;5.108;4.816 51 | "1975-02";7.376;51.808;5.266;5.254 52 | "1975-03";7.609;52.603;5.166;5.589 53 | "1975-04";7.829;53.776;5.294;6.004 54 | "1975-05";7.558;55.921;5.313;6.037 55 | "1975-06";7.62;56.786;5.749;6.439 56 | "1975-07";6.923;45.96;5.825;4.82 57 | "1975-08";7.607;51.76;6.317;5.18 58 | "1975-09";8.003;56.066;6.213;6.589 59 | "1975-10";7.838;60.396;6.281;6.886 60 | "1975-11";7.375;53.976;6.426;6.531 61 | "1975-12";7.282;54.819;6.526;5.888 62 | "1976-01";7.615;52.568;6.441;6.765 63 | "1976-02";8.53;53.773;6.757;7.675 64 | "1976-03";9.107;60.918;6.422;8.295 65 | "1976-04";9.358;59.145;6.647;8.194 66 | "1976-05";8.882;57.934;6.534;8.256 67 | "1976-06";9.011;59.68;6.925;9.056 68 | "1976-07";8.003;44.318;6.957;6.573 69 | "1976-08";8.621;53.622;7.057;7.162 70 | "1976-09";9.324;60.634;7.054;7.978 71 | "1976-10";8.9;58.999;7.044;8.005 72 | "1976-11";8.65;58.78;7.087;9.185 73 | "1976-12";8.141;58.414;7.422;8.236 74 | "1977-01";8.314;44.679;7.676;8.726 75 | "1977-02";9.509;49.26;7.871;9.364 76 | "1977-03";10.264;66.776;7.608;10.777 77 | "1977-04";10.105;60.549;7.755;10.081 78 | "1977-05";9.844;62.499;7.757;10.112 79 | "1977-06";9.93;63.095;7.964;11.035 80 | "1977-07";8.669;49.584;8.087;8.554 81 | "1977-08";9.465;57.751;8.083;7.826 82 | "1977-09";10.055;69.51;8.077;10.229 83 | "1977-10";9.4;67.66;8.295;11.419 84 | "1977-11";9.175;68.979;8.137;10.501 85 | "1977-12";9.161;31.002;8.346;9.134 86 | "1978-01";9.366;23.115;8.005;9.07 87 | "1978-02";10.309;23.52;8.151;10.6 88 | "1978-03";11.01;38.765;8.019;11.641 89 | "1978-04";11.434;59.53;8.207;11.92 90 | "1978-05";10.841;68.76;8.273;11.78 91 | "1978-06";11.161;65.565;8.721;12.035 92 | "1978-07";9.605;53.64;8.719;8.645 93 | "1978-08";10.241;64.395;8.925;9.141 94 | "1978-09";10.914;57.775;9.11;11.29 95 | "1978-10";10.704;69.86;8.899;12.987 96 | "1978-11";10.435;69.245;9.074;12.532 97 | "1978-12";10.425;59.63;9.464;10.566 98 | "1979-01";11.117;57.451;9.736;11.891 99 | "1979-02";11.701;54.525;10.32;12.956 100 | "1979-03";13.268;66.379;10.79;13.669 101 | "1979-04";12.37;63.531;10.675;11.881 102 | "1979-05";12.952;69.058;11.403;13.167 103 | "1979-06";13.059;70.287;12.041;11.738 104 | "1979-07";11.54;53.252;12.375;8.484 105 | "1979-08";11.954;71.181;13.378;7.283 106 | "1979-09";12.84;64.189;14.004;10.483 107 | "1979-10";12.429;78.07;13.881;11.644 108 | "1979-11";12.205;68.037;14.469;10.048 109 | "1979-12";12.241;60.339;15.296;8.134 110 | "1980-01";12.948;69.105;15.696;8.808 111 | "1980-02";13.641;65.18;16.522;10.054 112 | "1980-03";14.682;70.473;16.554;9.921 113 | "1980-04";13.982;69.135;16.039;8.85 114 | "1980-05";13.213;70.531;16.394;7.78 115 | "1980-06";13.338;70.83;16.693;7.856 116 | "1980-07";11.83;60.656;15.875;6.102 117 | "1980-08";12.802;67.89;16.218;6.416 118 | "1980-09";14.276;68.344;16.254;9.469 119 | "1980-10";13.986;71.723;16.818;11.073 120 | "1980-11";13.362;68.13;17.647;9.935 121 | "1980-12";14.457;71.647;17.963;8.296 122 | "1981-01";14.415;65.622;19.128;8.34 123 | "1981-02";15.125;70.446;19.425;9.406 124 | "1981-03";16.638;77.718;18.351;10.862 125 | "1981-04";15.765;35.816;18.024;11.119 126 | "1981-05";15.565;37.829;18.629;11.43 127 | "1981-06";16.14;61.083;19.015;12.158 128 | "1981-07";14.063;73.51;18.306;8.39 129 | "1981-08";14.738;78.248;18.988;8.53 130 | "1981-09";15.743;81.303;18.577;9.579 131 | "1981-10";14.31;84.784;18.853;10.511 132 | "1981-11";13.822;76.027;18.366;9.14 133 | "1981-12";14.133;75.966;18.47;7.516 134 | "1982-01";13.833;66.443;17.685;7.148 135 | "1982-02";14.861;70.344;16.917;8.727 136 | "1982-03";15.806;82.932;15.878;10.142 137 | "1982-04";15.132;73.155;16.553;10.538 138 | "1982-05";15.237;70.656;17.669;10.931 139 | "1982-06";15.514;71.231;18.161;11.624 140 | "1982-07";13.098;59.868;17.721;8.605 141 | "1982-08";14.102;72.091;17.377;8.69 142 | "1982-09";14.948;67.203;17.824;10.024 143 | "1982-10";13.269;70.068;17.473;9.432 144 | "1982-11";13.284;63.043;16.793;8.622 145 | "1982-12";13.719;62.177;16.379;7.694 146 | "1983-01";13.516;62.454;15.468;9.703 147 | "1983-02";14.64;60.394;14.425;11.56 148 | "1983-03";15.59;68.553;15.026;12.127 149 | "1983-04";14.763;61.552;15.659;11.729 150 | "1983-05";15.501;62.916;16.073;12.571 151 | "1983-06";16.344;61.445;17.092;13.71 152 | "1983-07";13.935;54.904;16.829;9.897 153 | "1983-08";15.188;72.891;16.896;11.304 154 | "1983-09";16.894;69.927;16.987;13.493 155 | "1983-10";15.551;71.34;16.2;14.372 156 | "1983-11";15.36;68.272;15.864;14.226 157 | "1983-12";15.927;63.354;16.051;12.593 158 | "1984-01";15.563;67.622;15.166;14.62 159 | "1984-02";16.485;73.408;15.294;16.005 160 | "1984-03";17.592;81.232;16.176;16.683 161 | "1984-04";17.036;72.511;16.291;15.487 162 | "1984-05";17.091;80.676;15.918;15.684 163 | "1984-06";17.861;76.094;16.349;15.962 164 | "1984-07";15.408;74.381;15.73;12 165 | "1984-08";16.329;90.365;15.551;13.769 166 | "1984-09";17.277;78.538;16.322;14.031 167 | "1984-10";15.855;69.419;15.427;16.078 168 | "1984-11";15.434;64.039;15.418;15.827 169 | "1984-12";16.299;63.475;15.371;13.149 170 | "1985-01";15.911;67.921;13.61;15.969 171 | "1985-02";16.618;66.897;14.16;16.628 172 | "1985-03";17.756;77.347;14.792;16.67 173 | "1985-04";17.138;76.241;15.252;16.487 174 | "1985-05";16.739;77.979;15.221;16.883 175 | "1985-06";17.663;72.731;15.651;16.201 176 | "1985-07";14.98;68.821;14.884;12.168 177 | "1985-08";15.933;79.212;15.253;14.01 178 | "1985-09";17.163;73.596;14.825;16.556 179 | "1985-10";15.903;79.711;14.776;17.404 180 | "1985-11";15.314;68.881;15.449;16.435 181 | "1985-12";16.194;69.593;15.262;13.123 182 | "1986-01";16.246;77.804;13.937;16.744 183 | "1986-02";16.943;72.209;12.002;17.41 184 | "1986-03";17.069;77.078;10.834;16.484 185 | "1986-04";17.32;74.333;10.568;17.103 186 | "1986-05";16.421;72.568;11.049;17.301 187 | "1986-06";16.979;72.071;10.246;17.301 188 | "1986-07";14.749;67.253;9.413;12.843 189 | "1986-08";16.141;75.912;9.682;13.748 190 | "1986-09";17.461;74.407;9.671;16.904 191 | "1986-10";15.934;79.412;8.991;17.342 192 | "1986-11";15.581;69.774;9.224;15.476 193 | "1986-12";16.246;73.201;9.261;15.424 194 | "1987-01";16.394;74.473;9.289;15.988 195 | "1987-02";17.353;71.447;9.333;19.244 196 | "1987-03";17.887;75.609;9.663;18.715 197 | "1987-04";17.784;70.768;9.907;17.78 198 | "1987-05";17.959;70.436;10.318;17.16 199 | "1987-06";18.705;76.78;10.772;17.349 200 | "1987-07";16.474;69.427;11.194;11.171 201 | "1987-08";17.652;80.348;11.176;13.438 202 | "1987-09";19.064;82.118;10.952;16.713 203 | "1987-10";17.78;85.63;10.984;18.369 204 | "1987-11";17.6;78.919;10.699;17.067 205 | "1987-12";18.053;79.246;10.241;14.055 206 | "1988-01";19.957;75.364;10.257;15.5 207 | "1988-02";20.942;76.778;10.047;18.475 208 | "1988-03";22.343;83.964;10.606;19.423 209 | "1988-04";22.053;75.351;10.891;18.686 210 | "1988-05";21.286;73.979;11.16;19.646 211 | "1988-06";22.504;76.448;11.651;19.733 212 | "1988-07";20.243;69.198;11.5;12.605 213 | "1988-08";21.897;88.206;11.713;16.616 214 | "1988-09";22.816;83.272;11.004;19.156 215 | "1988-10";21.879;80.853;10.86;21.348 216 | "1988-11";21.676;82.973;10.853;20.049 217 | "1988-12";22.102;80.324;10.873;18.02 218 | "1989-01";22.459;81.969;10.59;20.262 219 | "1989-02";23.136;75.04;10.728;21.789 220 | "1989-03";23.686;88.981;11.354;20.603 221 | "1989-04";23.971;77.145;12.618;21.928 222 | "1989-05";23.244;82.486;12.712;21.025 223 | "1989-06";24.264;78.544;13.077;19.346 224 | "1989-07";21.819;66.269;12.384;11.786 225 | "1989-08";22.661;90.824;12.35;19.082 226 | "1989-09";23.602;84.618;12.625;20.127 227 | "1989-10";22.187;89.574;12.777;20.217 228 | "1989-11";21.805;86.965;12.357;20.385 229 | "1989-12";22.353;72.554;12.738;16.653 230 | "1990-01";22.131;90.276;13.268;13.065 231 | "1990-02";23.19;81.756;12.571;20.275 232 | "1990-03";24.548;91.292;12.442;21.776 233 | "1990-04";24.252;82.88;12.687;20.26 234 | "1990-05";23.506;86.2;12.995;22.523 235 | "1990-06";24.736;84.276;13.039;23.033 236 | "1990-07";22.049;79.535;13.035;14.133 237 | "1990-08";24.493;91.515;16.683;20.11 238 | "1990-09";25.487;82.813;18.752;19.682 239 | "1990-10";24.26;93.078;19.604;22.197 240 | "1990-11";23.929;86.461;18.201;17.212 241 | "1990-12";23.031;75.487;16.08;11.784 242 | "1991-01";23.701;85.81;14.935;15.467 243 | "1991-02";24.205;82.592;13.261;17.002 244 | "1991-03";24.2;85.012;12.838;15.952 245 | "1991-04";24.971;79.324;13.509;18.767 246 | "1991-05";24.56;79.917;14.352;20.605 247 | "1991-06";24.992;76.896;14.136;19.809 248 | "1991-07";22.566;79.72;13.672;14.233 249 | "1991-08";24.037;88.818;14.394;19.311 250 | "1991-09";25.047;81.504;14.406;20.827 251 | "1991-10";24.115;90.23;14.587;23.388 252 | "1991-11";23.034;81.644;14.271;20.181 253 | "1991-12";22.59;79.244;12.981;14.344 254 | -------------------------------------------------------------------------------- /pydse/stats.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | General statistical functions and related tools. 5 | """ 6 | 7 | from __future__ import division, print_function, absolute_import, \ 8 | unicode_literals 9 | 10 | import logging 11 | 12 | import numpy as np 13 | from numpy import linalg 14 | 15 | from . import utils 16 | 17 | __author__ = "Florian Wilhelm" 18 | __copyright__ = "Blue Yonder" 19 | __license__ = "new BSD" 20 | 21 | _logger = logging.getLogger(__name__) 22 | 23 | 24 | def negloglike(pred, y): 25 | """ 26 | Negative log-likelihood of the residual of two time series. 27 | 28 | :param pred: predicted time series 29 | :param y: target time series 30 | :return: scalar negative log-likelihood 31 | """ 32 | sampleT = pred.shape[0] 33 | res = pred[:sampleT, :] - y[:sampleT, :] 34 | p = res.shape[1] 35 | 36 | Om = np.dot(res.T, res) / sampleT 37 | 38 | if np.any(np.isnan(Om)) or np.any(Om > 1e100): 39 | like1 = like2 = 1e100 40 | else: 41 | _, s, _ = linalg.svd(Om) 42 | 43 | # Check for degeneracy 44 | non_degen_mask = s > s[0] * np.sqrt(np.finfo(np.float).eps) 45 | if not np.all(non_degen_mask): 46 | _logger.warn("Covariance matrix is singular. " 47 | "Working on subspace.") 48 | s = s[non_degen_mask] 49 | 50 | like1 = 0.5 * sampleT * np.log(np.prod(s)) 51 | like2 = 0.5 * sampleT * len(s) 52 | 53 | const = 0.5 * sampleT * p * np.log(2 * np.pi) 54 | return like1 + like2 + const 55 | 56 | 57 | def bic(L, k, n): 58 | """ 59 | Bayesian information criterion. 60 | 61 | :param L: maximized value of the negative log likelihood function 62 | :param k: number of free parameters 63 | :param n: number of data points 64 | :return: BIC 65 | """ 66 | return 2*L + k*(np.log(n) + np.log(2*np.pi)) 67 | 68 | 69 | def aic(L, k): 70 | """ 71 | Akaike information criterion. 72 | 73 | :param L: maximized value of the negative log likelihood function 74 | :param k: number of free parameters 75 | :return: AIC 76 | """ 77 | return 2*(k + L) 78 | -------------------------------------------------------------------------------- /pydse/utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | Various different utilities and tools. 5 | """ 6 | 7 | from __future__ import division, print_function, absolute_import, \ 8 | unicode_literals 9 | 10 | import sys 11 | import logging 12 | from itertools import chain, combinations 13 | 14 | from six.moves import xrange 15 | import numpy as np 16 | 17 | __author__ = "Florian Wilhelm" 18 | __copyright__ = "Blue Yonder" 19 | __license__ = "new BSD" 20 | 21 | _logger = logging.getLogger(__name__) 22 | 23 | 24 | def atleast_2d(arr): 25 | """ 26 | Ensure that an array has at least dimension 2. 27 | 28 | :param arr: array 29 | :return: array with dimension of at least 2 30 | """ 31 | if arr.ndim > 1: 32 | return arr 33 | else: 34 | return arr[:, np.newaxis] 35 | 36 | 37 | def powerset(iterable): 38 | """ 39 | Calculates the power set of an iterable. 40 | 41 | :param iterable: iterable set 42 | :return: iterable power set 43 | """ 44 | s = list(iterable) 45 | return chain.from_iterable(combinations(s, r) for r in xrange(len(s)+1)) 46 | 47 | 48 | def make_lag_arr(lags, fuzz=1e-2): 49 | """ 50 | Create a lag polynomial that can be used as 1-dim A and B for ARMA. 51 | 52 | This function creates a lag polynomial, i.e. 1-dim lag matrix, 53 | and sets to parameters that are to be estimated to the ``fuzz`` 54 | value. Check :obj:`~.arma.ARMA.fix_constants` for further information. 55 | 56 | :param lags: list of lags 57 | :param fuzz: fill value to mark non-constant lags 58 | :return: tuple of the lag array and its shape 59 | """ 60 | lag_size = np.max([0] + list(lags)) + 1 61 | pol = np.zeros(lag_size) 62 | pol[0], pol[list(lags)] = 1., fuzz 63 | shape = np.array([lag_size, 1, 1]) 64 | return pol, shape 65 | 66 | 67 | class UnicodeMixin(object): 68 | """ 69 | Mixin class to handle defining the proper __str__/__unicode__ 70 | methods in Python 2 or 3. 71 | """ 72 | 73 | if sys.version_info[0] >= 3: # Python 3 74 | def __str__(self): 75 | return self.__unicode__() 76 | else: # Python 2 77 | def __str__(self): 78 | return self.__unicode__().encode('utf8') 79 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | numpy 3 | scipy 4 | pandas 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Setup file for pydse. 5 | 6 | This file was generated with PyScaffold 1.3.1, a tool that easily 7 | puts up a scaffold for your new Python project. Learn more under: 8 | http://pyscaffold.readthedocs.org/ 9 | """ 10 | 11 | import os 12 | import sys 13 | import inspect 14 | from distutils.cmd import Command 15 | 16 | import versioneer 17 | import setuptools 18 | from setuptools.command.test import test as TestCommand 19 | from setuptools import setup 20 | 21 | __location__ = os.path.join(os.getcwd(), os.path.dirname( 22 | inspect.getfile(inspect.currentframe()))) 23 | 24 | # Change these settings according to your needs 25 | MAIN_PACKAGE = "pydse" 26 | DESCRIPTION = "Dynamic System Estimation for Python" 27 | LICENSE = "new-bsd" 28 | URL = "http://pydse.readthedocs.org/" 29 | AUTHOR = "Florian Wilhelm" 30 | EMAIL = "Florian.Wilhelm@blue-yonder.com" 31 | 32 | COVERAGE_XML = False 33 | COVERAGE_HTML = False 34 | JUNIT_XML = False 35 | 36 | # Add here all kinds of additional classifiers as defined under 37 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 38 | CLASSIFIERS = ['Development Status :: 4 - Beta', 39 | 'License :: OSI Approved :: BSD License', 40 | 'Programming Language :: Python', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Intended Audience :: Science/Research', 44 | 'Topic :: Scientific/Engineering :: Mathematics'] 45 | 46 | # Add here console scripts like ['hello_world = pydse.module:function'] 47 | CONSOLE_SCRIPTS = [] 48 | 49 | # Versioneer configuration 50 | versioneer.VCS = 'git' 51 | versioneer.versionfile_source = os.path.join(MAIN_PACKAGE, '_version.py') 52 | versioneer.versionfile_build = os.path.join(MAIN_PACKAGE, '_version.py') 53 | versioneer.tag_prefix = 'v' # tags are like v1.2.0 54 | versioneer.parentdir_prefix = MAIN_PACKAGE + '-' 55 | 56 | 57 | class PyTest(TestCommand): 58 | user_options = [("cov=", None, "Run coverage"), 59 | ("cov-xml=", None, "Generate junit xml report"), 60 | ("cov-html=", None, "Generate junit html report"), 61 | ("junitxml=", None, "Generate xml of test results")] 62 | 63 | def initialize_options(self): 64 | TestCommand.initialize_options(self) 65 | self.cov = None 66 | self.cov_xml = False 67 | self.cov_html = False 68 | self.junitxml = None 69 | 70 | def finalize_options(self): 71 | TestCommand.finalize_options(self) 72 | if self.cov is not None: 73 | self.cov = ["--cov", self.cov, "--cov-report", "term-missing"] 74 | if self.cov_xml: 75 | self.cov.extend(["--cov-report", "xml"]) 76 | if self.cov_html: 77 | self.cov.extend(["--cov-report", "html"]) 78 | if self.junitxml is not None: 79 | self.junitxml = ["--junitxml", self.junitxml] 80 | 81 | def run_tests(self): 82 | try: 83 | import pytest 84 | except: 85 | raise RuntimeError("py.test is not installed, " 86 | "run: pip install pytest") 87 | params = {"args": self.test_args} 88 | if self.cov: 89 | params["args"] += self.cov 90 | if self.junitxml: 91 | params["args"] += self.junitxml 92 | errno = pytest.main(**params) 93 | sys.exit(errno) 94 | 95 | 96 | def sphinx_builder(): 97 | try: 98 | from sphinx.setup_command import BuildDoc 99 | except ImportError: 100 | class NoSphinx(Command): 101 | user_options = [] 102 | 103 | def initialize_options(self): 104 | raise RuntimeError("Sphinx documentation is not installed, " 105 | "run: pip install sphinx") 106 | 107 | return NoSphinx 108 | 109 | class BuildSphinxDocs(BuildDoc): 110 | 111 | def run(self): 112 | if self.builder == "doctest": 113 | import sphinx.ext.doctest as doctest 114 | # Capture the DocTestBuilder class in order to return the total 115 | # number of failures when exiting 116 | ref = capture_objs(doctest.DocTestBuilder) 117 | BuildDoc.run(self) 118 | errno = ref[-1].total_failures 119 | sys.exit(errno) 120 | else: 121 | BuildDoc.run(self) 122 | 123 | return BuildSphinxDocs 124 | 125 | 126 | class ObjKeeper(type): 127 | instances = {} 128 | 129 | def __init__(cls, name, bases, dct): 130 | cls.instances[cls] = [] 131 | 132 | def __call__(cls, *args, **kwargs): 133 | cls.instances[cls].append(super(ObjKeeper, cls).__call__(*args, 134 | **kwargs)) 135 | return cls.instances[cls][-1] 136 | 137 | 138 | def capture_objs(cls): 139 | from six import add_metaclass 140 | module = inspect.getmodule(cls) 141 | name = cls.__name__ 142 | keeper_class = add_metaclass(ObjKeeper)(cls) 143 | setattr(module, name, keeper_class) 144 | cls = getattr(module, name) 145 | return keeper_class.instances[cls] 146 | 147 | 148 | def get_install_requirements(path): 149 | content = open(os.path.join(__location__, path)).read() 150 | return [req for req in content.splitlines() if req != ''] 151 | 152 | 153 | def read(fname): 154 | return open(os.path.join(__location__, fname)).read() 155 | 156 | 157 | def setup_package(): 158 | # Assemble additional setup commands 159 | cmdclass = versioneer.get_cmdclass() 160 | cmdclass['docs'] = sphinx_builder() 161 | cmdclass['doctest'] = sphinx_builder() 162 | cmdclass['test'] = PyTest 163 | 164 | # Some helper variables 165 | version = versioneer.get_version() 166 | docs_path = os.path.join(__location__, "docs") 167 | docs_build_path = os.path.join(docs_path, "_build") 168 | install_reqs = get_install_requirements("requirements.txt") 169 | 170 | command_options = { 171 | 'docs': {'project': ('setup.py', MAIN_PACKAGE), 172 | 'version': ('setup.py', version.split('-', 1)[0]), 173 | 'release': ('setup.py', version), 174 | 'build_dir': ('setup.py', docs_build_path), 175 | 'config_dir': ('setup.py', docs_path), 176 | 'source_dir': ('setup.py', docs_path)}, 177 | 'doctest': {'project': ('setup.py', MAIN_PACKAGE), 178 | 'version': ('setup.py', version.split('-', 1)[0]), 179 | 'release': ('setup.py', version), 180 | 'build_dir': ('setup.py', docs_build_path), 181 | 'config_dir': ('setup.py', docs_path), 182 | 'source_dir': ('setup.py', docs_path), 183 | 'builder': ('setup.py', 'doctest')}, 184 | 'test': {'test_suite': ('setup.py', 'tests'), 185 | 'cov': ('setup.py', 'pydse')}} 186 | if JUNIT_XML: 187 | command_options['test']['junitxml'] = ('setup.py', 'junit.xml') 188 | if COVERAGE_XML: 189 | command_options['test']['cov_xml'] = ('setup.py', True) 190 | if COVERAGE_HTML: 191 | command_options['test']['cov_html'] = ('setup.py', True) 192 | 193 | setup(name=MAIN_PACKAGE, 194 | version=version, 195 | url=URL, 196 | description=DESCRIPTION, 197 | author=AUTHOR, 198 | author_email=EMAIL, 199 | license=LICENSE, 200 | long_description=read('README.rst'), 201 | classifiers=CLASSIFIERS, 202 | test_suite='tests', 203 | packages=setuptools.find_packages(exclude=['tests', 'tests.*']), 204 | install_requires=install_reqs, 205 | setup_requires=['six'], 206 | cmdclass=cmdclass, 207 | tests_require=['pytest-cov', 'pytest'], 208 | command_options=command_options, 209 | include_package_data=True, 210 | package_data={MAIN_PACKAGE: ['data/*']}, 211 | entry_points={'console_scripts': CONSOLE_SCRIPTS}) 212 | 213 | if __name__ == "__main__": 214 | setup_package() 215 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blue-yonder/pydse/9d6fe86fd6442605ad9f01004d34d372c20834dd/tests/__init__.py -------------------------------------------------------------------------------- /tests/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is meant to be called by the "install" step defined in 3 | # .travis.yml. See http://docs.travis-ci.com/ for more details. 4 | # The behavior of the script is controlled by environment variabled defined 5 | # in the .travis.yml in the top level folder of the project. 6 | # 7 | # This script is taken from Scikit-Learn (http://scikit-learn.org/) 8 | # 9 | 10 | set -e 11 | 12 | sudo apt-get update -qq 13 | 14 | if [[ "$DISTRIB" == "conda" ]]; then 15 | # Deactivate the travis-provided virtual environment and setup a 16 | # conda-based environment instead 17 | deactivate 18 | 19 | # Use the miniconda installer for faster download / install of conda 20 | # itself 21 | wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh \ 22 | -O miniconda.sh 23 | chmod +x miniconda.sh && ./miniconda.sh -b 24 | export PATH=/home/travis/miniconda/bin:$PATH 25 | conda update --yes conda 26 | 27 | # Configure the conda environment and put it in the path using the 28 | # provided versions 29 | conda create -n testenv --yes python=$PYTHON_VERSION pip nose \ 30 | numpy=$NUMPY_VERSION scipy=$SCIPY_VERSION pandas libgfortran 31 | source activate testenv 32 | 33 | elif [[ "$DISTRIB" == "ubuntu" ]]; then 34 | # Use standard ubuntu packages in their default version 35 | sudo apt-get install -qq python-scipy python-nose python-pip 36 | fi 37 | 38 | if [[ "$COVERAGE" == "true" ]]; then 39 | pip install coverage coveralls 40 | fi 41 | -------------------------------------------------------------------------------- /tests/test_arma.R: -------------------------------------------------------------------------------- 1 | # Script to verify unit tests with respect to DSE 2 | # Run with R version 2.14.1 (2011-12-22) 3 | # Copyright (C) 2011 The R Foundation for Statistical Computing 4 | # ISBN 3-900051-07-0 5 | # Platform: x86_64-pc-linux-gnu (64-bit) 6 | 7 | library('dse') 8 | 9 | # For the simulation unittest 10 | AR <- array(c(1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3), c(3,2,2)) 11 | MA <- array(c(1, .2, 0, .1, 0, 0, 1, .3), c(2,2,2)) 12 | X <- array(c(1, .3, 0, .05, 0, 0.1, 1, .3), c(2,2,2)) 13 | arma <- ARMA(A=AR, B=MA, C=NULL) 14 | 15 | sampleT <- 10 16 | setRNG(seed=0) 17 | noise <- makeTSnoise(sampleT, 2, 2) 18 | 19 | # Results for test_simulate unittest 20 | R_result <- simulate(arma, noise=noise, sampleT=sampleT) 21 | 22 | TREND <- c(1,2) 23 | arma_trend <- ARMA(A=AR, B=MA, C=NULL, TREND=TREND) 24 | R_result_trend <- simulate(arma_trend, noise=noise, sampleT=sampleT) 25 | 26 | arma_trend_ext <- ARMA(A=AR, B=MA, C=X, TREND=TREND) 27 | input0 <- array(c(0.1, 0.2, 0.15, 0.05), dim=c(2,2)) 28 | input <- array(c(0.1*c(1:10), 0.05*c(1:10)), dim=c(10,2)) 29 | R_result_trend_ext <- simulate(arma_trend_ext, 30 | noise=noise, 31 | sampleT=sampleT, 32 | input0=input0, 33 | input=input) 34 | 35 | # Results for the forecast unittest 36 | arma <- l(arma, R_result) 37 | R_pred <- arma$estimates$pred 38 | 39 | arma_trend <- l(arma_trend, R_result_trend) 40 | R_pred_trend <- arma_trend$estimates$pred 41 | 42 | # Results for the negloglike unittest 43 | R_negloglike <- residualStats(R_pred, R_result$output)$like[1] 44 | 45 | # Results for the forecast with horizon unittest 46 | setRNG(seed=0) 47 | sampleT <- 15 48 | noise <- makeTSnoise(sampleT, 2, 2) 49 | AR <- array(c(1, 0.3, 0.5),c(3,1,1)) 50 | MA <- array(c(1, 0.1), c(2,1,1)) 51 | TREND <- c(1:20) 52 | arma <- ARMA(A=AR, B=MA, TREND=TREND) 53 | truth <- simulate(arma, noise=noise, sampleT=sampleT) 54 | forecast_obj <- forecast(obj=arma, data=truth, horizon=5) 55 | forecast <- rbind(forecast_obj$pred, forecast_obj$forecast[[1]]) 56 | 57 | # A parameter estimation test 58 | AR = array(c(1, 0.3, 0.5),c(3,1,1)) 59 | MA = array(c(1, 0.1), c(2,1,1)) 60 | arma <- ARMA(A=AR, B=MA) 61 | simu <- simulate(arma, sampleT=sampleT, noise=noise) 62 | 63 | AR = array(c(1, 0.01, 0.01),c(3,1,1)) 64 | MA = array(c(1, 0.01), c(2,1,1)) 65 | arma <- ARMA(A=AR, B=MA) 66 | arma <- fixConstants(arma) 67 | arma <- estMaxLik(obj1=arma, obj2=simu) 68 | 69 | #tfplot(arma) 70 | -------------------------------------------------------------------------------- /tests/test_arma.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | import logging 6 | 7 | import pandas as pd 8 | import numpy as np 9 | import numpy.testing as nptest 10 | import pytest 11 | 12 | from pydse.arma import ARMA, ARMAError 13 | from pydse import arma 14 | from pydse import data 15 | 16 | __author__ = "Florian Wilhelm" 17 | __copyright__ = "Blue Yonder" 18 | __license__ = "new BSD" 19 | 20 | logging.basicConfig(level=logging.WARN) 21 | 22 | 23 | def test_arma_construction(): 24 | AR = ([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3], [3, 2, 2]) 25 | MA = ([1, .2, 0, .1, 0, 0, 1, .3], [2, 2, 2]) 26 | X = ([1, 2, 3, 4, 5, 6], [1, 2, 3]) 27 | # Check construction 28 | ARMA(A=AR, B=MA) 29 | ARMA(A=AR) 30 | ARMA(A=AR, B=MA, C=X) 31 | 32 | MA = ([1, 0.2, 0, .1], [2, 2, 1]) 33 | X = ([1, 2, 3, 4, 5, 6], [2, 1, 3]) 34 | with pytest.raises(ARMAError): 35 | ARMA(A=AR, B=MA) 36 | ARMA(A=AR, C=X) 37 | 38 | AR = ([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3], [3, 2, 2]) 39 | MA = ([1, 2, 0, .1, 0, 0, 1, .3], [2, 2, 2]) 40 | TREND = [1, 2] 41 | ARMA(A=AR, B=MA, TREND=TREND) 42 | TREND = [[1, 2], [3, 4]] 43 | ARMA(A=AR, B=MA, TREND=TREND) 44 | TREND = [[1, 2], [3, 4], [4, 5]] 45 | ARMA(A=AR, B=MA, TREND=TREND) 46 | TREND = [1, 2, 3] 47 | # give a (3,) array while expecting a (2,) array as p = 2 48 | with pytest.raises(ARMAError): 49 | ARMA(A=AR, B=MA, TREND=TREND) 50 | TREND = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] 51 | # give a (3, 3) array while expect a (X, 2) array as p = 2 52 | with pytest.raises(ARMAError): 53 | ARMA(A=AR, B=MA, TREND=TREND) 54 | TREND = [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], 55 | [[1, 2, 3], [1, 2, 3], [1, 2, 3]], 56 | [[1, 2, 3], [1, 2, 3], [1, 2, 3]]] 57 | # give a (3, 3, 3) array while expect a 2-d matrix 58 | with pytest.raises(ARMAError): 59 | ARMA(A=AR, B=MA, TREND=TREND) 60 | 61 | 62 | def test_simulate_arma(): 63 | AR = (np.array([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3]), 64 | np.array([3, 2, 2])) 65 | MA = (np.array([1, .2, 0, .1, 0, 0, 1, .3]), np.array([2, 2, 2])) 66 | arma = ARMA(A=AR, B=MA, C=None) 67 | # noise generated with R for comparison 68 | # ( setRNG(seed=0); noise <- makeTSnoise(10, 2, 2) ) 69 | w0_series0 = np.array([1.2629543, -0.3262334]) 70 | w0_series1 = np.array([-1.1476570, -0.2894616]) 71 | w_series0 = np.array( 72 | [1.329799263, 1.272429321, 0.414641434, -1.539950042, -0.928567035, 73 | -0.294720447, -0.005767173, 2.404653389, 0.763593461, -0.799009249]) 74 | w_series1 = np.array( 75 | [-0.2992151, -0.4115108, 0.2522234, -0.8919211, 0.4356833, -1.2375384, 76 | -0.2242679, 0.3773956, 0.1333364, 0.8041895]) 77 | noise = (np.vstack([w0_series0, w0_series1]).T, 78 | np.vstack([w_series0, w_series1]).T) 79 | # R simulation results ( arma <- ARMA(A=AR, B=MA, C=NULL); 80 | # R_result <- simulate(arma, noise=noise, sampleT=10) ) 81 | series0 = np.array( 82 | [1.58239012, 0.85063747, -0.11981462, -1.69017627, -0.19912156, 83 | 0.02830831, 0.16284912, 2.42364792, -0.15007052, -1.27531927]) 84 | series1 = np.array( 85 | [-0.5172168, -0.4261651, 0.2958942, -0.8559883, 0.7033546, -1.0857290, 86 | -0.2788928, 0.7393030, -0.2999778, 0.6363970]) 87 | R_result = np.vstack([series0, series1]).T 88 | 89 | result = arma.simulate(sampleT=10, noise=noise) 90 | nptest.assert_almost_equal(result, R_result) 91 | 92 | # R simulations results with trend ( TREND <- c(1,2); 93 | # arma_trend <- ARMA(A=AR, B=MA, C=NULL, TREND=TREND); 94 | # R_result_trend <- simulate(arma_trend, noise=noise, sampleT=sampleT) ) 95 | TREND = np.array([1., 2.]) 96 | arma = ARMA(A=AR, B=MA, C=None, TREND=TREND) 97 | series0 = np.array( 98 | [2.5823901, 0.9506375, 0.2701854, -1.1311763, 0.1139784, 0.4486183, 99 | 0.6048481, 2.8091458, 0.2663152, -0.8590244]) 100 | series1 = np.array( 101 | [1.48278321, 0.37383493, 1.17589420, 0.37601165, 1.67255459, 102 | -0.05844899, 0.80133515, 1.76057423, 0.74401876, 1.68619050]) 103 | R_result = np.vstack([series0, series1]).T 104 | result = arma.simulate(sampleT=10, noise=noise) 105 | nptest.assert_almost_equal(result, R_result) 106 | 107 | # R simulations results with trend and external series 108 | # arma_trend_ext <- ARMA(A=AR, B=MA, C=X, TREND=TREND) 109 | # input0 <- array(c(0.1, 0.2, 0.15, 0.05), dim=c(2,2)) 110 | # input <- array(c(0.1*c(1:10), 0.05*c(1:10)), dim=c(10,2)) 111 | # R_result_trend_ext <- simulate(arma_trend_ext, noise=noise, 112 | # sampleT=sampleT, input0=input0, input=input) 113 | X = (np.array([1, .3, 0, .05, 0, 0.1, 1, .3]), np.array([2, 2, 2])) 114 | arma = ARMA(A=AR, B=MA, C=X, TREND=TREND) 115 | input0 = np.array([0.1, 0.2, 0.15, 0.05]).reshape((2, 2), order='F') 116 | input = np.hstack([0.1*np.arange(1, 11), 0.05*np.arange(1, 11)]) 117 | input = input.reshape((10, 2), order='F') 118 | series0 = np.array( 119 | [2.7273901, 1.0931375, 0.5122354, -0.8113013, 0.4892429, 0.9042891, 120 | 1.1312726, 3.4036172, 0.9345114, -0.1201090]) 121 | series1 = np.array( 122 | [1.5827832, 0.4148349, 1.2723942, 0.5128017, 1.8170296, 0.1212361, 123 | 1.0094895, 1.9917395, 1.0044531, 1.9735374]) 124 | R_result = np.vstack([series0, series1]).T 125 | result = arma.simulate(sampleT=10, noise=noise, u0=input0, u=input) 126 | nptest.assert_almost_equal(result, R_result) 127 | 128 | 129 | def test_non_consts(): 130 | AR = (np.array([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3]), 131 | np.array([3, 2, 2])) 132 | MA = (np.array([1, .2, 0, .1, 0, 0, 1, .3]), 133 | np.array([2, 2, 2])) 134 | arma = ARMA(A=AR, B=MA, C=None) 135 | arma.Aconst[:, 0, 0] = True 136 | arma.Bconst[0, :, 0] = True 137 | new_values = np.repeat(-999., 9 + 6) 138 | arma.non_consts = new_values 139 | result_A = np.array([1, .5, .3, -999., -999., -999., -999., -999., -999., 140 | -999., -999., -999.]).reshape((3, 2, 2), order='F') 141 | nptest.assert_array_equal(arma.A, result_A) 142 | result_B = np.array([1, -999., 0, -999., -999., -999., -999., 143 | -999.]).reshape((2, 2, 2), order='F') 144 | nptest.assert_array_equal(arma.B, result_B) 145 | set_values = arma.non_consts 146 | nptest.assert_array_equal(set_values, new_values) 147 | 148 | 149 | def test_forecast(): 150 | AR = (np.array([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3]), 151 | np.array([3, 2, 2])) 152 | MA = (np.array([1, .2, 0, .1, 0, 0, 1, .3]), 153 | np.array([2, 2, 2])) 154 | arma = ARMA(A=AR, B=MA, C=None) 155 | 156 | series0 = np.array( 157 | [1.58239012, 0.85063747, -0.11981462, -1.69017627, -0.19912156, 158 | 0.02830831, 0.16284912, 2.42364792, -0.15007052, -1.27531927]) 159 | series1 = np.array( 160 | [-0.5172168, -0.4261651, 0.2958942, -0.8559883, 0.7033546, -1.0857290, 161 | -0.2788928, 0.7393030, -0.2999778, 0.6363970]) 162 | R_result = np.vstack([series0, series1]).T 163 | 164 | # One ahead forecast generated with R 165 | # ( arma <- l(arma, R_result); R_pred <- arma$estimates$pred ) 166 | pred0 = np.array( 167 | [0.00000000, -0.37127368, -0.54455969, -0.14820550, 0.72904133, 168 | 0.32310958, 0.16860013, 0.01899776, -0.91366463, -0.47630989]) 169 | pred1 = np.array( 170 | [0.00000000, -0.05479565, 0.05066136, 0.03484596, 0.26779526, 171 | 0.15181266, -0.05463401, 0.36191171, -0.43331575, -0.16779191]) 172 | R_pred = np.vstack([pred0, pred1]).T 173 | 174 | pred = arma.forecast(y=R_result) 175 | nptest.assert_almost_equal(R_pred, pred) 176 | 177 | # One ahead forecast with trend generated with R 178 | # ( arma_trend <- l(arma_trend, R_result_trend); 179 | # R_pred_trend <- arma_trend$estimates$pred ) 180 | TREND = np.array([1., 2.]) 181 | arma_trend = ARMA(A=AR, B=MA, C=None, TREND=TREND) 182 | series0 = np.array( 183 | [2.5823901, 0.9506375, 0.2701854, -1.1311763, 0.1139784, 0.4486183, 184 | 0.6048481, 2.8091458, 0.2663152, -0.8590244]) 185 | series1 = np.array( 186 | [1.48278321, 0.37383493, 1.17589420, 0.37601165, 1.67255459, 187 | -0.05844899, 0.80133515, 1.76057423, 0.74401876, 1.68619050]) 188 | R_result_trend = np.vstack([series0, series1]).T 189 | 190 | pred0 = np.array( 191 | [1.00000000, -0.27127368, -0.15455969, 0.41079450, 1.04214133, 192 | 0.74341958, 0.61059913, 0.40449566, -0.49727892, -0.06001498]) 193 | pred1 = np.array( 194 | [2.00000000, 0.7452043, 0.9306614, 1.2668460, 1.2369953, 1.1790927, 195 | 1.0255940, 1.3831829, 0.6106808, 0.8820015]) 196 | R_pred_trend = np.vstack([pred0, pred1]).T 197 | 198 | pred_trend = arma_trend.forecast(y=R_result_trend) 199 | nptest.assert_almost_equal(R_pred_trend, pred_trend) 200 | 201 | 202 | def test_fix_constants(): 203 | AR = (np.array([1, .5, .31, 0, .2, .1, 0, .2, .01, 1, .49, .3]), 204 | np.array([3, 2, 2])) 205 | MA = (np.array([1, .21, 0, .1, 0, -0.01, 1, .3]), np.array([2, 2, 2])) 206 | arma = ARMA(A=AR, B=MA, C=None) 207 | arma.fix_constants() 208 | Aconst = np.array( 209 | [[[True, True], [True, True]], [[True, True], [True, False]], 210 | [[False, False], [True, True]]], dtype=bool) 211 | nptest.assert_array_equal(arma.Aconst, Aconst) 212 | Bconst = np.array( 213 | [[[True, True], [True, True]], [[False, False], [True, True]]], 214 | dtype=bool) 215 | nptest.assert_array_equal(arma.Bconst, Bconst) 216 | 217 | 218 | def test_est_params(): 219 | rand_state = np.random.RandomState() 220 | rand_state.seed(42) 221 | 222 | # Generate a target series 223 | AR = (np.array([1, 0.3, 0.5]), np.array([3, 1, 1])) 224 | MA = (np.array([1, .1]), np.array([2, 1, 1])) 225 | arma = ARMA(A=AR, B=MA, C=None, rand_state=rand_state) 226 | series = arma.simulate(sampleT=1000) 227 | 228 | # Estimate parameters by simulated series 229 | AR_est = (np.array([1, .01, .01]), np.array([3, 1, 1])) 230 | MA_est = (np.array([1, .01]), np.array([2, 1, 1])) 231 | arma_est = ARMA(A=AR_est, B=MA_est, C=None) 232 | arma_est.fix_constants() 233 | arma_est.est_params(y=series) 234 | 235 | nptest.assert_almost_equal(arma_est.A, arma.A, decimal=1) 236 | nptest.assert_almost_equal(arma_est.B, arma.B, decimal=1) 237 | 238 | 239 | def test_minic(): 240 | df = data.airline_passengers() 241 | df['Trend'] = pd.rolling_mean(df['Passengers'], window=36, min_periods=1) 242 | residual = df['Passengers'] / df['Trend'] 243 | 244 | ar_lags = [1, 2, 12] 245 | ma_lags = [1, 2, 12] 246 | lags = arma.minic(ar_lags, ma_lags, residual, crit='AIC') 247 | assert lags == ((1, 2, 12), (12,)) 248 | lags = arma.minic(ar_lags, ma_lags, residual, crit='BIC') 249 | assert lags == ((1,), (12,)) 250 | 251 | 252 | def test_forecast_with_horizon(): 253 | rand_state = np.random.RandomState() 254 | rand_state.seed(0) 255 | AR = (np.array([1, 0.3, 0.5]), np.array([3, 1, 1])) 256 | MA = (np.array([1, .1]), np.array([2, 1, 1])) 257 | TREND = np.arange(1, 21)[:, np.newaxis] 258 | arma = ARMA(A=AR, B=MA, C=None, TREND=TREND, rand_state=rand_state) 259 | truth = np.array([2.4560947, 2.6685808, 1.5132628, 0.7132449, 2.9468331, 260 | 4.3717505, 4.1798191, 6.9642557, 5.8248726, 4.0477605, 261 | 5.6456776, 7.8781892, 7.4855431, 7.3738101, 9.2561578]) 262 | result = np.array([1., 1.408781, 1.097358, 2.253321, 3.875388, 4.666472, 263 | 4.185586, 4.559602, 5.061279, 4.846770, 6.793335, 264 | 8.167651, 7.784758, 7.785321, 9.003934, 9.561470, 265 | 9.503480, 10.368221, 11.137794, 11.474551]) 266 | pred = arma.forecast(truth, horizon=5) 267 | nptest.assert_almost_equal(pred, result[:, np.newaxis], decimal=5) 268 | 269 | 270 | def test_print(): 271 | AR = (np.array([1, .5, .31, 0, .2, .1, 0, .2, .01, 1, .49, .3]), 272 | np.array([3, 2, 2])) 273 | MA = (np.array([1, .21, 0, .1, 0, -0.01, 1, .3]), np.array([2, 2, 2])) 274 | X = (np.array([1, .3, 0, .05, 0, 0.1, 1, .3]), np.array([2, 2, 2])) 275 | TREND = [[1, 21], [2, 22], [3, 23], [4, 24]] 276 | arma = ARMA(A=AR, B=MA, C=X, TREND=TREND) 277 | print(arma) 278 | arma = ARMA(A=AR, B=MA, C=X, TREND=[1, 2]) 279 | print(arma) 280 | AR = (np.array([1, .5, .31, 0, .2, .1, 0, .2, .01, 1, .49, .3]), 281 | np.array([3, 2, 2])) 282 | MA = (np.array([1., 2., 3., 0, 0, 0, 0, 0, 0, 1., 2., 3.]), 283 | np.array([3, 2, 2])) 284 | arma = ARMA(A=AR, B=MA) 285 | print(arma) 286 | -------------------------------------------------------------------------------- /tests/test_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Docstring""" 4 | 5 | from __future__ import division, print_function, absolute_import 6 | 7 | from pandas import DatetimeIndex 8 | 9 | from pydse import data 10 | 11 | __author__ = "Florian Wilhelm" 12 | __copyright__ = "Blue Yonder" 13 | __license__ = "new BSD" 14 | 15 | 16 | def test_data(): 17 | data_funcs = [data.airline_passengers, 18 | data.m1_us, 19 | data.cpi_canada, 20 | data.sales_product, 21 | data.sales_petroleum, 22 | data.sales_cola, 23 | data.sales_shampoo] 24 | for data_func in data_funcs: 25 | df = data_func() 26 | assert isinstance(df.index, DatetimeIndex) 27 | -------------------------------------------------------------------------------- /tests/test_stats.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | import logging 6 | 7 | import numpy as np 8 | import numpy.testing as nptest 9 | import pytest 10 | 11 | from pydse.arma import ARMA, ARMAError 12 | from pydse import stats 13 | 14 | __author__ = "Florian Wilhelm" 15 | __copyright__ = "Blue Yonder" 16 | __license__ = "new BSD" 17 | 18 | logging.basicConfig(level=logging.WARN) 19 | 20 | 21 | def test_negloglike(): 22 | AR = (np.array([1, .5, .3, 0, .2, .1, 0, .2, .05, 1, .5, .3]), 23 | np.array([3, 2, 2])) 24 | MA = (np.array([1, .2, 0, .1, 0, 0, 1, .3]), 25 | np.array([2, 2, 2])) 26 | arma = ARMA(A=AR, B=MA, C=None) 27 | # Noise generated with R for comparison 28 | # ( setRNG(seed=0); noise <- makeTSnoise(10, 2, 2) ) 29 | w0_series0 = np.array([1.2629543, -0.3262334]) 30 | w0_series1 = np.array([-1.1476570, -0.2894616]) 31 | w_series0 = np.array( 32 | [1.329799263, 1.272429321, 0.414641434, -1.539950042, -0.928567035, 33 | -0.294720447, -0.005767173, 2.404653389, 0.763593461, -0.799009249]) 34 | w_series1 = np.array( 35 | [-0.2992151, -0.4115108, 0.2522234, -0.8919211, 0.4356833, -1.2375384, 36 | -0.2242679, 0.3773956, 0.1333364, 0.8041895]) 37 | noise = (np.vstack([w0_series0, w0_series1]).T, 38 | np.vstack([w_series0, w_series1]).T) 39 | 40 | result = arma.simulate(sampleT=10, noise=noise) 41 | pred = arma.forecast(y=result) 42 | 43 | negloglike = stats.negloglike(pred, result) 44 | R_negloglike = 25.4247320523 45 | nptest.assert_almost_equal(negloglike, R_negloglike) 46 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | import logging 6 | 7 | import numpy as np 8 | 9 | from pydse import utils 10 | 11 | __author__ = "Florian Wilhelm" 12 | __copyright__ = "Blue Yonder" 13 | __license__ = "new BSD" 14 | 15 | logging.basicConfig(level=logging.WARN) 16 | 17 | 18 | def test_atleast_2d(): 19 | I = np.eye(2, 2) 20 | I2 = utils.atleast_2d(I) 21 | assert I is I2 22 | A = np.zeros((1, 2, 3)) 23 | A2 = utils.atleast_2d(A) 24 | assert A is A2 25 | B = np.arange(10) 26 | B2 = utils.atleast_2d(B) 27 | assert B2.ndim == 2 28 | assert np.all(B2[:, 0] == B) 29 | 30 | 31 | def test_powerset(): 32 | result = utils.powerset([1, 2, 3]) 33 | exp_result = [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] 34 | assert list(result) == exp_result 35 | 36 | 37 | def test_make_lag_arr(): 38 | A, shape = utils.make_lag_arr([1, 2, 12], fuzz=999) 39 | assert np.all(shape == (13, 1, 1)) 40 | assert A[0] == 1. 41 | assert A[1] == 999 42 | assert A[2] == 999 43 | assert A[12] == 999 44 | assert np.all(A[3:12] == [0]*9) 45 | -------------------------------------------------------------------------------- /versioneer.py: -------------------------------------------------------------------------------- 1 | 2 | # Version: 0.12 3 | 4 | """ 5 | The Versioneer 6 | ============== 7 | 8 | * like a rocketeer, but for versions! 9 | * https://github.com/warner/python-versioneer 10 | * Brian Warner (modified by Florian Wilhelm and Felix Wick) 11 | * License: Public Domain 12 | * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy 13 | 14 | [![Build Status](https://travis-ci.org/warner/python-versioneer.png?branch=master)](https://travis-ci.org/warner/python-versioneer) 15 | 16 | This is a tool for managing a recorded version number in distutils-based 17 | python projects. The goal is to remove the tedious and error-prone "update 18 | the embedded version string" step from your release process. Making a new 19 | release should be as easy as recording a new tag in your version-control 20 | system, and maybe making new tarballs. 21 | 22 | 23 | ## Quick Install 24 | 25 | * `pip install versioneer` to somewhere to your $PATH 26 | * run `versioneer-installer` in your source tree: this installs `versioneer.py` 27 | * follow the instructions below (also in the `versioneer.py` docstring) 28 | 29 | ## Version Identifiers 30 | 31 | Source trees come from a variety of places: 32 | 33 | * a version-control system checkout (mostly used by developers) 34 | * a nightly tarball, produced by build automation 35 | * a snapshot tarball, produced by a web-based VCS browser, like github's 36 | "tarball from tag" feature 37 | * a release tarball, produced by "setup.py sdist", distributed through PyPI 38 | 39 | Within each source tree, the version identifier (either a string or a number, 40 | this tool is format-agnostic) can come from a variety of places: 41 | 42 | * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows 43 | about recent "tags" and an absolute revision-id 44 | * the name of the directory into which the tarball was unpacked 45 | * an expanded VCS keyword ($Id$, etc) 46 | * a `_version.py` created by some earlier build step 47 | 48 | For released software, the version identifier is closely related to a VCS 49 | tag. Some projects use tag names that include more than just the version 50 | string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool 51 | needs to strip the tag prefix to extract the version identifier. For 52 | unreleased software (between tags), the version identifier should provide 53 | enough information to help developers recreate the same tree, while also 54 | giving them an idea of roughly how old the tree is (after version 1.2, before 55 | version 1.3). Many VCS systems can report a description that captures this, 56 | for example 'git describe --tags --dirty --always' reports things like 57 | "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 58 | 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has 59 | uncommitted changes. 60 | 61 | The version identifier is used for multiple purposes: 62 | 63 | * to allow the module to self-identify its version: `myproject.__version__` 64 | * to choose a name and prefix for a 'setup.py sdist' tarball 65 | 66 | ## Theory of Operation 67 | 68 | Versioneer works by adding a special `_version.py` file into your source 69 | tree, where your `__init__.py` can import it. This `_version.py` knows how to 70 | dynamically ask the VCS tool for version information at import time. However, 71 | when you use "setup.py build" or "setup.py sdist", `_version.py` in the new 72 | copy is replaced by a small static file that contains just the generated 73 | version data. 74 | 75 | `_version.py` also contains `$Revision$` markers, and the installation 76 | process marks `_version.py` to have this marker rewritten with a tag name 77 | during the "git archive" command. As a result, generated tarballs will 78 | contain enough information to get the proper version. 79 | 80 | 81 | ## Installation 82 | 83 | First, decide on values for the following configuration variables: 84 | 85 | * `VCS`: the version control system you use. Currently accepts "git". 86 | 87 | * `versionfile_source`: 88 | 89 | A project-relative pathname into which the generated version strings should 90 | be written. This is usually a `_version.py` next to your project's main 91 | `__init__.py` file, so it can be imported at runtime. If your project uses 92 | `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. 93 | This file should be checked in to your VCS as usual: the copy created below 94 | by `setup.py versioneer` will include code that parses expanded VCS 95 | keywords in generated tarballs. The 'build' and 'sdist' commands will 96 | replace it with a copy that has just the calculated version string. 97 | 98 | This must be set even if your project does not have any modules (and will 99 | therefore never import `_version.py`), since "setup.py sdist" -based trees 100 | still need somewhere to record the pre-calculated version strings. Anywhere 101 | in the source tree should do. If there is a `__init__.py` next to your 102 | `_version.py`, the `setup.py versioneer` command (described below) will 103 | append some `__version__`-setting assignments, if they aren't already 104 | present. 105 | 106 | * `versionfile_build`: 107 | 108 | Like `versionfile_source`, but relative to the build directory instead of 109 | the source directory. These will differ when your setup.py uses 110 | 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, 111 | then you will probably have `versionfile_build='myproject/_version.py'` and 112 | `versionfile_source='src/myproject/_version.py'`. 113 | 114 | If this is set to None, then `setup.py build` will not attempt to rewrite 115 | any `_version.py` in the built tree. If your project does not have any 116 | libraries (e.g. if it only builds a script), then you should use 117 | `versionfile_build = None` and override `distutils.command.build_scripts` 118 | to explicitly insert a copy of `versioneer.get_version()` into your 119 | generated script. 120 | 121 | * `tag_prefix`: 122 | 123 | a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. 124 | If your tags look like 'myproject-1.2.0', then you should use 125 | tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this 126 | should be an empty string. 127 | 128 | * `parentdir_prefix`: 129 | 130 | a string, frequently the same as tag_prefix, which appears at the start of 131 | all unpacked tarball filenames. If your tarball unpacks into 132 | 'myproject-1.2.0', this should be 'myproject-'. 133 | 134 | This tool provides one script, named `versioneer-installer`. That script does 135 | one thing: write a copy of `versioneer.py` into the current directory. 136 | 137 | To versioneer-enable your project: 138 | 139 | * 1: Run `versioneer-installer` to copy `versioneer.py` into the top of your 140 | source tree. 141 | 142 | * 2: add the following lines to the top of your `setup.py`, with the 143 | configuration values you decided earlier: 144 | 145 | import versioneer 146 | versioneer.VCS = 'git' 147 | versioneer.versionfile_source = 'src/myproject/_version.py' 148 | versioneer.versionfile_build = 'myproject/_version.py' 149 | versioneer.tag_prefix = '' # tags are like 1.2.0 150 | versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' 151 | 152 | * 3: add the following arguments to the setup() call in your setup.py: 153 | 154 | version=versioneer.get_version(), 155 | cmdclass=versioneer.get_cmdclass(), 156 | 157 | * 4: now run `setup.py versioneer`, which will create `_version.py`, and will 158 | modify your `__init__.py` (if one exists next to `_version.py`) to define 159 | `__version__` (by calling a function from `_version.py`). It will also 160 | modify your `MANIFEST.in` to include both `versioneer.py` and the generated 161 | `_version.py` in sdist tarballs. 162 | 163 | * 5: commit these changes to your VCS. To make sure you won't forget, 164 | `setup.py versioneer` will mark everything it touched for addition. 165 | 166 | ## Post-Installation Usage 167 | 168 | Once established, all uses of your tree from a VCS checkout should get the 169 | current version string. All generated tarballs should include an embedded 170 | version string (so users who unpack them will not need a VCS tool installed). 171 | 172 | If you distribute your project through PyPI, then the release process should 173 | boil down to two steps: 174 | 175 | * 1: git tag 1.0 176 | * 2: python setup.py register sdist upload 177 | 178 | If you distribute it through github (i.e. users use github to generate 179 | tarballs with `git archive`), the process is: 180 | 181 | * 1: git tag 1.0 182 | * 2: git push; git push --tags 183 | 184 | Currently, all version strings must be based upon a tag. Versioneer will 185 | report "unknown" until your tree has at least one tag in its history. This 186 | restriction will be fixed eventually (see issue #12). 187 | 188 | ## Version-String Flavors 189 | 190 | Code which uses Versioneer can learn about its version string at runtime by 191 | importing `_version` from your main `__init__.py` file and running the 192 | `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can 193 | import the top-level `versioneer.py` and run `get_versions()`. 194 | 195 | Both functions return a dictionary with different keys for different flavors 196 | of the version string: 197 | 198 | * `['version']`: condensed tag+distance+shortid+dirty identifier. For git, 199 | this uses the output of `git describe --tags --dirty --always` but strips 200 | the tag_prefix. For example "0.11-2-g1076c97-dirty" indicates that the tree 201 | is like the "1076c97" commit but has uncommitted changes ("-dirty"), and 202 | that this commit is two revisions ("-2-") beyond the "0.11" tag. For 203 | released software (exactly equal to a known tag), the identifier will only 204 | contain the stripped tag, e.g. "0.11". 205 | 206 | * `['full']`: detailed revision identifier. For Git, this is the full SHA1 207 | commit id, followed by "-dirty" if the tree contains uncommitted changes, 208 | e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac-dirty". 209 | 210 | Some variants are more useful than others. Including `full` in a bug report 211 | should allow developers to reconstruct the exact code being tested (or 212 | indicate the presence of local changes that should be shared with the 213 | developers). `version` is suitable for display in an "about" box or a CLI 214 | `--version` output: it can be easily compared against release notes and lists 215 | of bugs fixed in various releases. 216 | 217 | In the future, this will also include a 218 | [PEP-0440](http://legacy.python.org/dev/peps/pep-0440/) -compatible flavor 219 | (e.g. `1.2.post0.dev123`). This loses a lot of information (and has no room 220 | for a hash-based revision id), but is safe to use in a `setup.py` 221 | "`version=`" argument. It also enables tools like *pip* to compare version 222 | strings and evaluate compatibility constraint declarations. 223 | 224 | The `setup.py versioneer` command adds the following text to your 225 | `__init__.py` to place a basic version in `YOURPROJECT.__version__`: 226 | 227 | from ._version import get_versions 228 | __version__ = get_versions()['version'] 229 | del get_versions 230 | 231 | ## Updating Versioneer 232 | 233 | To upgrade your project to a new release of Versioneer, do the following: 234 | 235 | * install the new Versioneer (`pip install -U versioneer` or equivalent) 236 | * re-run `versioneer-installer` in your source tree to replace your copy of 237 | `versioneer.py` 238 | * edit `setup.py`, if necessary, to include any new configuration settings 239 | indicated by the release notes 240 | * re-run `setup.py versioneer` to replace `SRC/_version.py` 241 | * commit any changed files 242 | 243 | ### Upgrading from 0.10 to 0.11 244 | 245 | You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running 246 | `setup.py versioneer`. This will enable the use of additional version-control 247 | systems (SVN, etc) in the future. 248 | 249 | ### Upgrading from 0.11 to 0.12 250 | 251 | Nothing special. 252 | 253 | ## Future Directions 254 | 255 | This tool is designed to make it easily extended to other version-control 256 | systems: all VCS-specific components are in separate directories like 257 | src/git/ . The top-level `versioneer.py` script is assembled from these 258 | components by running make-versioneer.py . In the future, make-versioneer.py 259 | will take a VCS name as an argument, and will construct a version of 260 | `versioneer.py` that is specific to the given VCS. It might also take the 261 | configuration arguments that are currently provided manually during 262 | installation by editing setup.py . Alternatively, it might go the other 263 | direction and include code from all supported VCS systems, reducing the 264 | number of intermediate scripts. 265 | 266 | 267 | ## License 268 | 269 | To make Versioneer easier to embed, all its code is hereby released into the 270 | public domain. The `_version.py` that it creates is also in the public 271 | domain. 272 | 273 | """ 274 | 275 | import os, sys, re, subprocess, errno 276 | from distutils.core import Command 277 | from distutils.command.sdist import sdist as _sdist 278 | from distutils.command.build import build as _build 279 | 280 | # these configuration settings will be overridden by setup.py after it 281 | # imports us 282 | versionfile_source = None 283 | versionfile_build = None 284 | tag_prefix = None 285 | parentdir_prefix = None 286 | VCS = None 287 | 288 | # these dictionaries contain VCS-specific tools 289 | LONG_VERSION_PY = {} 290 | 291 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 292 | assert isinstance(commands, list) 293 | p = None 294 | for c in commands: 295 | try: 296 | # remember shell=False, so use git.cmd on windows, not just git 297 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 298 | stderr=(subprocess.PIPE if hide_stderr 299 | else None)) 300 | break 301 | except EnvironmentError: 302 | e = sys.exc_info()[1] 303 | if e.errno == errno.ENOENT: 304 | continue 305 | if verbose: 306 | print("unable to run %s" % args[0]) 307 | print(e) 308 | return None 309 | else: 310 | if verbose: 311 | print("unable to find command, tried %s" % (commands,)) 312 | return None 313 | stdout = p.communicate()[0].strip() 314 | if sys.version >= '3': 315 | stdout = stdout.decode() 316 | if p.returncode != 0: 317 | if verbose: 318 | print("unable to run %s (error)" % args[0]) 319 | return None 320 | return stdout 321 | 322 | LONG_VERSION_PY['git'] = ''' 323 | # This file helps to compute a version number in source trees obtained from 324 | # git-archive tarball (such as those provided by githubs download-from-tag 325 | # feature). Distribution tarballs (built by setup.py sdist) and build 326 | # directories (produced by setup.py build) will contain a much shorter file 327 | # that just contains the computed version number. 328 | 329 | # This file is released into the public domain. Generated by 330 | # versioneer-0.12 (https://github.com/warner/python-versioneer) 331 | 332 | # these strings will be replaced by git during git-archive 333 | git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" 334 | git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" 335 | 336 | # these strings are filled in when 'setup.py versioneer' creates _version.py 337 | tag_prefix = "%(TAG_PREFIX)s" 338 | parentdir_prefix = "%(PARENTDIR_PREFIX)s" 339 | versionfile_source = "%(VERSIONFILE_SOURCE)s" 340 | 341 | import os, sys, re, subprocess, errno 342 | 343 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 344 | assert isinstance(commands, list) 345 | p = None 346 | for c in commands: 347 | try: 348 | # remember shell=False, so use git.cmd on windows, not just git 349 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 350 | stderr=(subprocess.PIPE if hide_stderr 351 | else None)) 352 | break 353 | except EnvironmentError: 354 | e = sys.exc_info()[1] 355 | if e.errno == errno.ENOENT: 356 | continue 357 | if verbose: 358 | print("unable to run %%s" %% args[0]) 359 | print(e) 360 | return None 361 | else: 362 | if verbose: 363 | print("unable to find command, tried %%s" %% (commands,)) 364 | return None 365 | stdout = p.communicate()[0].strip() 366 | if sys.version >= '3': 367 | stdout = stdout.decode() 368 | if p.returncode != 0: 369 | if verbose: 370 | print("unable to run %%s (error)" %% args[0]) 371 | return None 372 | return stdout 373 | 374 | 375 | def versions_from_parentdir(parentdir_prefix, root, verbose=False): 376 | # Source tarballs conventionally unpack into a directory that includes 377 | # both the project name and a version string. 378 | dirname = os.path.basename(root) 379 | if not dirname.startswith(parentdir_prefix): 380 | if verbose: 381 | print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% 382 | (root, dirname, parentdir_prefix)) 383 | return None 384 | return {"version": dirname[len(parentdir_prefix):], "full": ""} 385 | 386 | def git_get_keywords(versionfile_abs): 387 | # the code embedded in _version.py can just fetch the value of these 388 | # keywords. When used from setup.py, we don't want to import _version.py, 389 | # so we do it with a regexp instead. This function is not used from 390 | # _version.py. 391 | keywords = {} 392 | try: 393 | f = open(versionfile_abs,"r") 394 | for line in f.readlines(): 395 | if line.strip().startswith("git_refnames ="): 396 | mo = re.search(r'=\s*"(.*)"', line) 397 | if mo: 398 | keywords["refnames"] = mo.group(1) 399 | if line.strip().startswith("git_full ="): 400 | mo = re.search(r'=\s*"(.*)"', line) 401 | if mo: 402 | keywords["full"] = mo.group(1) 403 | f.close() 404 | except EnvironmentError: 405 | pass 406 | return keywords 407 | 408 | def git_versions_from_keywords(keywords, tag_prefix, verbose=False): 409 | if not keywords: 410 | return {} # keyword-finding function failed to find keywords 411 | refnames = keywords["refnames"].strip() 412 | if refnames.startswith("$Format"): 413 | if verbose: 414 | print("keywords are unexpanded, not using") 415 | return {} # unexpanded, so not in an unpacked git-archive tarball 416 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 417 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 418 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 419 | TAG = "tag: " 420 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 421 | if not tags: 422 | # Either we're using git < 1.8.3, or there really are no tags. We use 423 | # a heuristic: assume all version tags have a digit. The old git %%d 424 | # expansion behaves like git log --decorate=short and strips out the 425 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 426 | # between branches and tags. By ignoring refnames without digits, we 427 | # filter out many common branch names like "release" and 428 | # "stabilization", as well as "HEAD" and "master". 429 | tags = set([r for r in refs if re.search(r'\d', r)]) 430 | if verbose: 431 | print("discarding '%%s', no digits" %% ",".join(refs-tags)) 432 | if verbose: 433 | print("likely tags: %%s" %% ",".join(sorted(tags))) 434 | for ref in sorted(tags): 435 | # sorting will prefer e.g. "2.0" over "2.0rc1" 436 | if ref.startswith(tag_prefix): 437 | r = ref[len(tag_prefix):] 438 | if verbose: 439 | print("picking %%s" %% r) 440 | return { "version": r, 441 | "full": keywords["full"].strip() } 442 | # no suitable tags, so we use the full revision id 443 | if verbose: 444 | print("no suitable tags, using full revision id") 445 | return { "version": keywords["full"].strip(), 446 | "full": keywords["full"].strip() } 447 | 448 | 449 | def git_versions_from_vcs(tag_prefix, root, verbose=False): 450 | # this runs 'git' from the root of the source tree. This only gets called 451 | # if the git-archive 'subst' keywords were *not* expanded, and 452 | # _version.py hasn't already been rewritten with a short version string, 453 | # meaning we're inside a checked out source tree. 454 | 455 | if not os.path.exists(os.path.join(root, ".git")): 456 | if verbose: 457 | print("no .git in %%s" %% root) 458 | return {} 459 | 460 | GITS = ["git"] 461 | if sys.platform == "win32": 462 | GITS = ["git.cmd", "git.exe"] 463 | stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], 464 | cwd=root) 465 | if stdout is None: 466 | return {} 467 | if not stdout.startswith(tag_prefix): 468 | if verbose: 469 | print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix)) 470 | return {} 471 | tag = stdout[len(tag_prefix):] 472 | stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 473 | if stdout is None: 474 | return {} 475 | full = stdout.strip() 476 | if tag.endswith("-dirty"): 477 | full += "-dirty" 478 | return {"version": tag, "full": full} 479 | 480 | 481 | def get_versions(default={"version": "unknown", "full": ""}, verbose=False): 482 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 483 | # __file__, we can work backwards from there to the root. Some 484 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 485 | # case we can only use expanded keywords. 486 | 487 | keywords = { "refnames": git_refnames, "full": git_full } 488 | ver = git_versions_from_keywords(keywords, tag_prefix, verbose) 489 | if ver: 490 | return rep_by_pep440(ver) 491 | 492 | try: 493 | root = os.path.abspath(__file__) 494 | # versionfile_source is the relative path from the top of the source 495 | # tree (where the .git directory might live) to this file. Invert 496 | # this to find the root from __file__. 497 | for i in range(len(versionfile_source.split('/'))): 498 | root = os.path.dirname(root) 499 | except NameError: 500 | return default 501 | 502 | return rep_by_pep440( 503 | git_versions_from_vcs(tag_prefix, root, verbose) 504 | or versions_from_parentdir(parentdir_prefix, root, verbose) 505 | or default) 506 | 507 | 508 | def git2pep440(ver_str): 509 | dash_count = ver_str.count('-') 510 | if dash_count == 0: 511 | return ver_str 512 | elif dash_count == 1: 513 | return ver_str.split('-')[0] + ".post.dev1.pre" 514 | elif dash_count == 2: 515 | tag, commits, _ = ver_str.split('-') 516 | return ".post.dev".join([tag, commits]) 517 | elif dash_count == 3: 518 | tag, commits, _, _ = ver_str.split('-') 519 | commits = str(int(commits) + 1) 520 | return ".post.dev".join([tag, commits]) + ".pre" 521 | else: 522 | raise RuntimeError("Invalid version string") 523 | 524 | 525 | def rep_by_pep440(ver): 526 | if ver["full"]: # only if versions_from_parentdir was not used 527 | ver["version"] = git2pep440(ver["version"]) 528 | else: 529 | ver["version"] = ver["version"].split('-')[0] 530 | return ver 531 | ''' 532 | 533 | def git_get_keywords(versionfile_abs): 534 | # the code embedded in _version.py can just fetch the value of these 535 | # keywords. When used from setup.py, we don't want to import _version.py, 536 | # so we do it with a regexp instead. This function is not used from 537 | # _version.py. 538 | keywords = {} 539 | try: 540 | f = open(versionfile_abs,"r") 541 | for line in f.readlines(): 542 | if line.strip().startswith("git_refnames ="): 543 | mo = re.search(r'=\s*"(.*)"', line) 544 | if mo: 545 | keywords["refnames"] = mo.group(1) 546 | if line.strip().startswith("git_full ="): 547 | mo = re.search(r'=\s*"(.*)"', line) 548 | if mo: 549 | keywords["full"] = mo.group(1) 550 | f.close() 551 | except EnvironmentError: 552 | pass 553 | return keywords 554 | 555 | def git_versions_from_keywords(keywords, tag_prefix, verbose=False): 556 | if not keywords: 557 | return {} # keyword-finding function failed to find keywords 558 | refnames = keywords["refnames"].strip() 559 | if refnames.startswith("$Format"): 560 | if verbose: 561 | print("keywords are unexpanded, not using") 562 | return {} # unexpanded, so not in an unpacked git-archive tarball 563 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 564 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 565 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 566 | TAG = "tag: " 567 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 568 | if not tags: 569 | # Either we're using git < 1.8.3, or there really are no tags. We use 570 | # a heuristic: assume all version tags have a digit. The old git %d 571 | # expansion behaves like git log --decorate=short and strips out the 572 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 573 | # between branches and tags. By ignoring refnames without digits, we 574 | # filter out many common branch names like "release" and 575 | # "stabilization", as well as "HEAD" and "master". 576 | tags = set([r for r in refs if re.search(r'\d', r)]) 577 | if verbose: 578 | print("discarding '%s', no digits" % ",".join(refs-tags)) 579 | if verbose: 580 | print("likely tags: %s" % ",".join(sorted(tags))) 581 | for ref in sorted(tags): 582 | # sorting will prefer e.g. "2.0" over "2.0rc1" 583 | if ref.startswith(tag_prefix): 584 | r = ref[len(tag_prefix):] 585 | if verbose: 586 | print("picking %s" % r) 587 | return { "version": r, 588 | "full": keywords["full"].strip() } 589 | # no suitable tags, so we use the full revision id 590 | if verbose: 591 | print("no suitable tags, using full revision id") 592 | return { "version": keywords["full"].strip(), 593 | "full": keywords["full"].strip() } 594 | 595 | 596 | def git_versions_from_vcs(tag_prefix, root, verbose=False): 597 | # this runs 'git' from the root of the source tree. This only gets called 598 | # if the git-archive 'subst' keywords were *not* expanded, and 599 | # _version.py hasn't already been rewritten with a short version string, 600 | # meaning we're inside a checked out source tree. 601 | 602 | if not os.path.exists(os.path.join(root, ".git")): 603 | if verbose: 604 | print("no .git in %s" % root) 605 | return {} 606 | 607 | GITS = ["git"] 608 | if sys.platform == "win32": 609 | GITS = ["git.cmd", "git.exe"] 610 | stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], 611 | cwd=root) 612 | if stdout is None: 613 | return {} 614 | if not stdout.startswith(tag_prefix): 615 | if verbose: 616 | print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) 617 | return {} 618 | tag = stdout[len(tag_prefix):] 619 | stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 620 | if stdout is None: 621 | return {} 622 | full = stdout.strip() 623 | if tag.endswith("-dirty"): 624 | full += "-dirty" 625 | return {"version": tag, "full": full} 626 | 627 | 628 | def do_vcs_install(manifest_in, versionfile_source, ipy): 629 | GITS = ["git"] 630 | if sys.platform == "win32": 631 | GITS = ["git.cmd", "git.exe"] 632 | files = [manifest_in, versionfile_source] 633 | if ipy: 634 | files.append(ipy) 635 | try: 636 | me = __file__ 637 | if me.endswith(".pyc") or me.endswith(".pyo"): 638 | me = os.path.splitext(me)[0] + ".py" 639 | versioneer_file = os.path.relpath(me) 640 | except NameError: 641 | versioneer_file = "versioneer.py" 642 | files.append(versioneer_file) 643 | present = False 644 | try: 645 | f = open(".gitattributes", "r") 646 | for line in f.readlines(): 647 | if line.strip().startswith(versionfile_source): 648 | if "export-subst" in line.strip().split()[1:]: 649 | present = True 650 | f.close() 651 | except EnvironmentError: 652 | pass 653 | if not present: 654 | f = open(".gitattributes", "a+") 655 | f.write("%s export-subst\n" % versionfile_source) 656 | f.close() 657 | files.append(".gitattributes") 658 | run_command(GITS, ["add", "--"] + files) 659 | 660 | def versions_from_parentdir(parentdir_prefix, root, verbose=False): 661 | # Source tarballs conventionally unpack into a directory that includes 662 | # both the project name and a version string. 663 | dirname = os.path.basename(root) 664 | if not dirname.startswith(parentdir_prefix): 665 | if verbose: 666 | print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % 667 | (root, dirname, parentdir_prefix)) 668 | return None 669 | return {"version": dirname[len(parentdir_prefix):], "full": ""} 670 | 671 | SHORT_VERSION_PY = """ 672 | # This file was generated by 'versioneer.py' (0.12) from 673 | # revision-control system data, or from the parent directory name of an 674 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 675 | # of this file. 676 | 677 | version_version = '%(version)s' 678 | version_full = '%(full)s' 679 | def get_versions(default={}, verbose=False): 680 | return {'version': version_version, 'full': version_full} 681 | 682 | """ 683 | 684 | DEFAULT = {"version": "unknown", "full": "unknown"} 685 | 686 | def versions_from_file(filename): 687 | versions = {} 688 | try: 689 | with open(filename) as f: 690 | for line in f.readlines(): 691 | mo = re.match("version_version = '([^']+)'", line) 692 | if mo: 693 | versions["version"] = mo.group(1) 694 | mo = re.match("version_full = '([^']+)'", line) 695 | if mo: 696 | versions["full"] = mo.group(1) 697 | except EnvironmentError: 698 | return {} 699 | 700 | return versions 701 | 702 | def write_to_version_file(filename, versions): 703 | with open(filename, "w") as f: 704 | f.write(SHORT_VERSION_PY % versions) 705 | 706 | print("set %s to '%s'" % (filename, versions["version"])) 707 | 708 | 709 | def get_root(): 710 | try: 711 | return os.path.dirname(os.path.abspath(__file__)) 712 | except NameError: 713 | return os.path.dirname(os.path.abspath(sys.argv[0])) 714 | 715 | def vcs_function(vcs, suffix): 716 | return getattr(sys.modules[__name__], '%s_%s' % (vcs, suffix), None) 717 | 718 | def get_versions(default=DEFAULT, verbose=False): 719 | # returns dict with two keys: 'version' and 'full' 720 | assert versionfile_source is not None, "please set versioneer.versionfile_source" 721 | assert tag_prefix is not None, "please set versioneer.tag_prefix" 722 | assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" 723 | assert VCS is not None, "please set versioneer.VCS" 724 | 725 | # I am in versioneer.py, which must live at the top of the source tree, 726 | # which we use to compute the root directory. py2exe/bbfreeze/non-CPython 727 | # don't have __file__, in which case we fall back to sys.argv[0] (which 728 | # ought to be the setup.py script). We prefer __file__ since that's more 729 | # robust in cases where setup.py was invoked in some weird way (e.g. pip) 730 | root = get_root() 731 | versionfile_abs = os.path.join(root, versionfile_source) 732 | 733 | # extract version from first of _version.py, VCS command (e.g. 'git 734 | # describe'), parentdir. This is meant to work for developers using a 735 | # source checkout, for users of a tarball created by 'setup.py sdist', 736 | # and for users of a tarball/zipball created by 'git archive' or github's 737 | # download-from-tag feature or the equivalent in other VCSes. 738 | 739 | get_keywords_f = vcs_function(VCS, "get_keywords") 740 | versions_from_keywords_f = vcs_function(VCS, "versions_from_keywords") 741 | if get_keywords_f and versions_from_keywords_f: 742 | vcs_keywords = get_keywords_f(versionfile_abs) 743 | ver = versions_from_keywords_f(vcs_keywords, tag_prefix) 744 | if ver: 745 | if verbose: print("got version from expanded keyword %s" % ver) 746 | return rep_by_pep440(ver) 747 | 748 | ver = versions_from_file(versionfile_abs) 749 | if ver: 750 | if verbose: print("got version from file %s %s" % (versionfile_abs,ver)) 751 | return rep_by_pep440(ver) 752 | 753 | versions_from_vcs_f = vcs_function(VCS, "versions_from_vcs") 754 | if versions_from_vcs_f: 755 | ver = versions_from_vcs_f(tag_prefix, root, verbose) 756 | if ver: 757 | if verbose: print("got version from VCS %s" % ver) 758 | return rep_by_pep440(ver) 759 | 760 | ver = versions_from_parentdir(parentdir_prefix, root, verbose) 761 | if ver: 762 | if verbose: print("got version from parentdir %s" % ver) 763 | return rep_by_pep440(ver) 764 | 765 | if verbose: print("got version from default %s" % default) 766 | return rep_by_pep440(default) 767 | 768 | def get_version(verbose=False): 769 | return get_versions(verbose=verbose)["version"] 770 | 771 | class cmd_version(Command): 772 | description = "report generated version string" 773 | user_options = [] 774 | boolean_options = [] 775 | def initialize_options(self): 776 | pass 777 | def finalize_options(self): 778 | pass 779 | def run(self): 780 | ver = get_version(verbose=True) 781 | print("Version is currently: %s" % ver) 782 | 783 | 784 | class cmd_build(_build): 785 | def run(self): 786 | versions = get_versions(verbose=True) 787 | _build.run(self) 788 | # now locate _version.py in the new build/ directory and replace it 789 | # with an updated value 790 | if versionfile_build: 791 | target_versionfile = os.path.join(self.build_lib, versionfile_build) 792 | print("UPDATING %s" % target_versionfile) 793 | os.unlink(target_versionfile) 794 | with open(target_versionfile, "w") as f: 795 | f.write(SHORT_VERSION_PY % versions) 796 | 797 | if 'cx_Freeze' in sys.modules: # cx_freeze enabled? 798 | from cx_Freeze.dist import build_exe as _build_exe 799 | 800 | class cmd_build_exe(_build_exe): 801 | def run(self): 802 | versions = get_versions(verbose=True) 803 | target_versionfile = versionfile_source 804 | print("UPDATING %s" % target_versionfile) 805 | os.unlink(target_versionfile) 806 | with open(target_versionfile, "w") as f: 807 | f.write(SHORT_VERSION_PY % versions) 808 | 809 | _build_exe.run(self) 810 | os.unlink(target_versionfile) 811 | with open(versionfile_source, "w") as f: 812 | assert VCS is not None, "please set versioneer.VCS" 813 | LONG = LONG_VERSION_PY[VCS] 814 | f.write(LONG % {"DOLLAR": "$", 815 | "TAG_PREFIX": tag_prefix, 816 | "PARENTDIR_PREFIX": parentdir_prefix, 817 | "VERSIONFILE_SOURCE": versionfile_source, 818 | }) 819 | 820 | class cmd_sdist(_sdist): 821 | def run(self): 822 | versions = get_versions(verbose=True) 823 | self._versioneer_generated_versions = versions 824 | # unless we update this, the command will keep using the old version 825 | self.distribution.metadata.version = versions["version"] 826 | return _sdist.run(self) 827 | 828 | def make_release_tree(self, base_dir, files): 829 | _sdist.make_release_tree(self, base_dir, files) 830 | # now locate _version.py in the new base_dir directory (remembering 831 | # that it may be a hardlink) and replace it with an updated value 832 | target_versionfile = os.path.join(base_dir, versionfile_source) 833 | print("UPDATING %s" % target_versionfile) 834 | os.unlink(target_versionfile) 835 | with open(target_versionfile, "w") as f: 836 | f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) 837 | 838 | INIT_PY_SNIPPET = """ 839 | from ._version import get_versions 840 | __version__ = get_versions()['version'] 841 | del get_versions 842 | """ 843 | 844 | class cmd_update_files(Command): 845 | description = "install/upgrade Versioneer files: __init__.py SRC/_version.py" 846 | user_options = [] 847 | boolean_options = [] 848 | def initialize_options(self): 849 | pass 850 | def finalize_options(self): 851 | pass 852 | def run(self): 853 | print(" creating %s" % versionfile_source) 854 | with open(versionfile_source, "w") as f: 855 | assert VCS is not None, "please set versioneer.VCS" 856 | LONG = LONG_VERSION_PY[VCS] 857 | f.write(LONG % {"DOLLAR": "$", 858 | "TAG_PREFIX": tag_prefix, 859 | "PARENTDIR_PREFIX": parentdir_prefix, 860 | "VERSIONFILE_SOURCE": versionfile_source, 861 | }) 862 | 863 | ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") 864 | if os.path.exists(ipy): 865 | try: 866 | with open(ipy, "r") as f: 867 | old = f.read() 868 | except EnvironmentError: 869 | old = "" 870 | if INIT_PY_SNIPPET not in old: 871 | print(" appending to %s" % ipy) 872 | with open(ipy, "a") as f: 873 | f.write(INIT_PY_SNIPPET) 874 | else: 875 | print(" %s unmodified" % ipy) 876 | else: 877 | print(" %s doesn't exist, ok" % ipy) 878 | ipy = None 879 | 880 | # Make sure both the top-level "versioneer.py" and versionfile_source 881 | # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so 882 | # they'll be copied into source distributions. Pip won't be able to 883 | # install the package without this. 884 | manifest_in = os.path.join(get_root(), "MANIFEST.in") 885 | simple_includes = set() 886 | try: 887 | with open(manifest_in, "r") as f: 888 | for line in f: 889 | if line.startswith("include "): 890 | for include in line.split()[1:]: 891 | simple_includes.add(include) 892 | except EnvironmentError: 893 | pass 894 | # That doesn't cover everything MANIFEST.in can do 895 | # (http://docs.python.org/2/distutils/sourcedist.html#commands), so 896 | # it might give some false negatives. Appending redundant 'include' 897 | # lines is safe, though. 898 | if "versioneer.py" not in simple_includes: 899 | print(" appending 'versioneer.py' to MANIFEST.in") 900 | with open(manifest_in, "a") as f: 901 | f.write("include versioneer.py\n") 902 | else: 903 | print(" 'versioneer.py' already in MANIFEST.in") 904 | if versionfile_source not in simple_includes: 905 | print(" appending versionfile_source ('%s') to MANIFEST.in" % 906 | versionfile_source) 907 | with open(manifest_in, "a") as f: 908 | f.write("include %s\n" % versionfile_source) 909 | else: 910 | print(" versionfile_source already in MANIFEST.in") 911 | 912 | # Make VCS-specific changes. For git, this means creating/changing 913 | # .gitattributes to mark _version.py for export-time keyword 914 | # substitution. 915 | do_vcs_install(manifest_in, versionfile_source, ipy) 916 | 917 | def get_cmdclass(): 918 | cmds = {'version': cmd_version, 919 | 'versioneer': cmd_update_files, 920 | 'build': cmd_build, 921 | 'sdist': cmd_sdist, 922 | } 923 | if 'cx_Freeze' in sys.modules: # cx_freeze enabled? 924 | cmds['build_exe'] = cmd_build_exe 925 | del cmds['build'] 926 | 927 | return cmds 928 | 929 | def git2pep440(ver_str): 930 | dash_count = ver_str.count('-') 931 | if dash_count == 0: 932 | return ver_str 933 | elif dash_count == 1: 934 | return ver_str.split('-')[0] + ".post.dev1.pre" 935 | elif dash_count == 2: 936 | tag, commits, _ = ver_str.split('-') 937 | return ".post.dev".join([tag, commits]) 938 | elif dash_count == 3: 939 | tag, commits, _, _ = ver_str.split('-') 940 | commits = str(int(commits) + 1) 941 | return ".post.dev".join([tag, commits]) + ".pre" 942 | else: 943 | raise RuntimeError("Invalid version string") 944 | 945 | def rep_by_pep440(ver): 946 | if ver["full"]: # only if versions_from_parentdir was not used 947 | ver["version"] = git2pep440(ver["version"]) 948 | else: 949 | ver["version"] = ver["version"].split('-')[0] 950 | return ver 951 | --------------------------------------------------------------------------------