├── LICENSE ├── README.rst ├── data ├── aus.csv ├── bad.csv ├── livestock.csv ├── oil.csv └── oil2.csv ├── doc ├── Makefile ├── conf.py ├── howto.rst ├── index.rst ├── installation.rst ├── make.bat └── rforecast.rst ├── examples └── livestock.py ├── rforecast ├── __init__.py ├── converters.py ├── plots.py ├── rbase.py ├── ts_io.py ├── validate.py └── wrappers.py ├── setup.py └── test ├── test_all.py ├── test_converters.py ├── test_io.py ├── test_rbase.py └── test_wrappers.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python wrapper for R Forecast 2 | ============================= 3 | 4 | *This is work in progress.* 5 | 6 | This project uses rpy2 to expose most of the functionality of R's Forecast package in python. 7 | Some related functions in the stats and base packages are also exposed (e.g. seasonal decompositions). 8 | A few less-commnonly used functions and arguments are not exposed. 9 | 10 | The rforecast.py package uses Pandas Series objects to represent time series. 11 | For seasonal series, it uses a multindex with the second level of the index 12 | denoting the season. The ``read_series`` function in ``ts_io`` will return a 13 | series with the index constructed correctly, given .csv files like those in the 14 | ``/data`` directory. 15 | If the data are already in a Python sequence, such as a list or numpy array, 16 | you can convert it to a series of the right form like this: 17 | 18 | .. code-block:: python 19 | 20 | from rforecast import converters 21 | # A slice of the 'oil' data from R package fpp, also available in data/ 22 | data = [509, 506, 340, 240, 219, 172, 252, 221, 276, 271, 342, 428, 442, 432, 437] 23 | ts = converters.sequence_as_series(data, start=1980) 24 | print ts 25 | 26 | # A seasonal (quarterly) series: 27 | data = [30.05, 19.14, 25.31, 27.59, 32.07, 23.48, 28.47, 35.12, 28 | 36.83, 25.00, 30.72, 28.69, 36.64, 23.82, 29.31, 31.77] 29 | ts = converters.sequence_as_series(data, start=(1991, 1), freq=4) 30 | print ts 31 | 32 | Forecasting and decomposition methods are in the wrappers module: 33 | 34 | .. code-block:: 35 | 36 | from rforecast import wrappers 37 | 38 | An example of generating a forecast (using ts above): 39 | 40 | .. code-block:: 41 | 42 | fc = wrappers.forecast(ts) 43 | print fc 44 | 45 | An example of generating an STL decomposition : 46 | 47 | .. code-block:: 48 | 49 | dc = wrappers.stl(ts, s_window=5) 50 | print dc 51 | 52 | There is more information in the `.rst` files under ``doc/``. 53 | The documentation is built with Sphinx. 54 | If you have Sphinx installed, you can build the documentation using the Makefile 55 | in ``doc``: 56 | 57 | .. code-block:: bash 58 | 59 | cd doc 60 | make html 61 | 62 | Then the built documentation will start at: doc/_build/html/index.html. -------------------------------------------------------------------------------- /data/aus.csv: -------------------------------------------------------------------------------- 1 | 1999,1,30.052513 2 | 1999,2,19.148496 3 | 1999,3,25.317692 4 | 1999,4,27.591437 5 | 2000,1,32.076456 6 | 2000,2,23.487961 7 | 2000,3,28.47594 8 | 2000,4,35.123753 9 | 2001,1,36.838485 10 | 2001,2,25.007017 11 | 2001,3,30.72223 12 | 2001,4,28.693759 13 | 2002,1,36.640986 14 | 2002,2,23.824609 15 | 2002,3,29.311683 16 | 2002,4,31.770309 17 | 2003,1,35.177877 18 | 2003,2,19.775244 19 | 2003,3,29.60175 20 | 2003,4,34.538842 21 | 2004,1,41.273599 22 | 2004,2,26.655862 23 | 2004,3,28.279859 24 | 2004,4,35.191153 25 | 2005,1,41.727458 26 | 2005,2,24.04185 27 | 2005,3,32.328103 28 | 2005,4,37.328708 29 | 2006,1,46.213153 30 | 2006,2,29.346326 31 | 2006,3,36.48291 32 | 2006,4,42.977719 33 | 2007,1,48.901525 34 | 2007,2,31.180221 35 | 2007,3,37.717881 36 | 2007,4,40.420211 37 | 2008,1,51.206863 38 | 2008,2,31.887228 39 | 2008,3,40.978263 40 | 2008,4,43.772491 41 | 2009,1,55.558567 42 | 2009,2,33.850915 43 | 2009,3,42.076383 44 | 2009,4,45.642292 45 | 2010,1,59.76678 46 | 2010,2,35.191877 47 | 2010,3,44.319737 48 | 2010,4,47.913736 49 | -------------------------------------------------------------------------------- /data/bad.csv: -------------------------------------------------------------------------------- 1 | ,0,1,2 2 | 0,0.74,0.42,0.22 3 | 1,0.04,0.17,0.37 4 | 2,0.53,0.32,0.82 5 | 3,0.81,0.11,0.79 6 | -------------------------------------------------------------------------------- /data/livestock.csv: -------------------------------------------------------------------------------- 1 | 1961,232.288994 2 | 1962,229.536258 3 | 1963,233.145936 4 | 1964,243.763684 5 | 1965,252.602916 6 | 1966,259.677371 7 | 1967,260.766892 8 | 1968,269.784084 9 | 1969,266.414974 10 | 1970,263.917747 11 | 1971,268.307222 12 | 1972,260.662556 13 | 1973,266.639419 14 | 1974,277.515778 15 | 1975,283.834045 16 | 1976,290.309028 17 | 1977,292.474198 18 | 1978,300.830694 19 | 1979,309.286657 20 | 1980,318.331081 21 | 1981,329.37239 22 | 1982,338.883998 23 | 1983,339.244126 24 | 1984,328.600632 25 | 1985,314.255385 26 | 1986,314.459695 27 | 1987,321.413779 28 | 1988,329.789292 29 | 1989,346.385165 30 | 1990,352.297882 31 | 1991,348.370515 32 | 1992,417.562922 33 | 1993,417.12357 34 | 1994,417.749459 35 | 1995,412.233904 36 | 1996,411.946817 37 | 1997,394.697075 38 | 1998,401.49927 39 | 1999,408.270468 40 | 2000,414.2428 41 | 2001,407.997978 42 | 2002,403.460832 43 | 2003,413.824928 44 | 2004,428.104959 45 | 2005,445.338742 46 | 2006,452.994173 47 | 2007,455.74017 48 | -------------------------------------------------------------------------------- /data/oil.csv: -------------------------------------------------------------------------------- 1 | 1965,111.0091346 2 | 1966,130.8284341 3 | 1967,141.2870879 4 | 1968,154.2277747 5 | 1969,162.7408654 6 | 1970,192.1664835 7 | 1971,240.7997253 8 | 1972,304.2173901 9 | 1973,384.0045673 10 | 1974,429.6621566 11 | 1975,359.3169299 12 | 1976,437.2518544 13 | 1977,468.4007898 14 | 1978,424.4353365 15 | 1979,487.9794299 16 | 1980,509.8284478 17 | 1981,506.3472527 18 | 1982,340.1842374 19 | 1983,240.258921 20 | 1984,219.0327876 21 | 1985,172.0746632 22 | 1986,252.5900922 23 | 1987,221.0710774 24 | 1988,276.5187735 25 | 1989,271.1479517 26 | 1990,342.6186005 27 | 1991,428.3558357 28 | 1992,442.3945533 29 | 1993,432.7851482 30 | 1994,437.2497186 31 | 1995,438.4428287 32 | 1996,446.6565229 33 | 1997,454.4733065 34 | 1998,455.662974 35 | 1999,423.6322388 36 | 2000,456.2713279 37 | 2001,440.5880501 38 | 2002,425.3325201 39 | 2003,485.1494479 40 | 2004,506.0481621 41 | 2005,526.7919833 42 | 2006,514.268889 43 | 2007,494.2110193 44 | 2008,515.3052417 45 | 2009,464.7204667 46 | 2010,467.7723636 47 | -------------------------------------------------------------------------------- /data/oil2.csv: -------------------------------------------------------------------------------- 1 | 111.0091346 2 | 130.8284341 3 | 141.2870879 4 | 154.2277747 5 | 162.7408654 6 | 192.1664835 7 | 240.7997253 8 | 304.2173901 9 | 384.0045673 10 | 429.6621566 11 | 359.3169299 12 | 437.2518544 13 | 468.4007898 14 | 424.4353365 15 | 487.9794299 16 | 509.8284478 17 | 506.3472527 18 | 340.1842374 19 | 240.258921 20 | 219.0327876 21 | 172.0746632 22 | 252.5900922 23 | 221.0710774 24 | 276.5187735 25 | 271.1479517 26 | 342.6186005 27 | 428.3558357 28 | 442.3945533 29 | 432.7851482 30 | 437.2497186 31 | 438.4428287 32 | 446.6565229 33 | 454.4733065 34 | 455.662974 35 | 423.6322388 36 | 456.2713279 37 | 440.5880501 38 | 425.3325201 39 | 485.1494479 40 | 506.0481621 41 | 526.7919833 42 | 514.268889 43 | 494.2110193 44 | 515.3052417 45 | 464.7204667 46 | 467.7723636 47 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage 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 " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rforecast.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rforecast.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/rforecast" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rforecast" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # rforecast documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Nov 9 19:27:19 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.viewcode', 36 | 'sphinx.ext.napoleon' 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'rforecast' 55 | copyright = u'2015, David Thaler' 56 | author = u'David Thaler' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = '0.1' 64 | # The full version, including alpha/beta/rc tags. 65 | release = '0.1' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = 'en' 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built documents. 106 | #keep_warnings = False 107 | 108 | # If true, `todo` and `todoList` produce output, else they produce nothing. 109 | todo_include_todos = True 110 | 111 | 112 | # -- Options for HTML output ---------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | html_theme = 'sphinx_rtd_theme' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | #html_theme_options = {} 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 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 = None 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 | # Add any extra paths that contain custom files (such as robots.txt or 148 | # .htaccess) here, relative to this directory. These files are copied 149 | # directly to the root of the documentation. 150 | #html_extra_path = [] 151 | 152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 153 | # using the given strftime format. 154 | #html_last_updated_fmt = '%b %d, %Y' 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | #html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | #html_sidebars = {} 162 | 163 | # Additional templates that should be rendered to pages, maps page names to 164 | # template names. 165 | #html_additional_pages = {} 166 | 167 | # If false, no module index is generated. 168 | #html_domain_indices = True 169 | 170 | # If false, no index is generated. 171 | #html_use_index = True 172 | 173 | # If true, the index is split into individual pages for each letter. 174 | #html_split_index = False 175 | 176 | # If true, links to the reST sources are added to the pages. 177 | #html_show_sourcelink = True 178 | 179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 180 | #html_show_sphinx = True 181 | 182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 183 | #html_show_copyright = True 184 | 185 | # If true, an OpenSearch description file will be output, and all pages will 186 | # contain a tag referring to it. The value of this option must be the 187 | # base URL from which the finished HTML is served. 188 | #html_use_opensearch = '' 189 | 190 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 191 | #html_file_suffix = None 192 | 193 | # Language to be used for generating the HTML full-text search index. 194 | # Sphinx supports the following languages: 195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 196 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 197 | #html_search_language = 'en' 198 | 199 | # A dictionary with options for the search language support, empty by default. 200 | # Now only 'ja' uses this config value 201 | #html_search_options = {'type': 'default'} 202 | 203 | # The name of a javascript file (relative to the configuration directory) that 204 | # implements a search results scorer. If empty, the default will be used. 205 | #html_search_scorer = 'scorer.js' 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'rforecastdoc' 209 | 210 | # -- Options for LaTeX output --------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | #'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | #'preamble': '', 221 | 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | (master_doc, 'rforecast.tex', u'rforecast Documentation', 231 | u'Author', 'manual'), 232 | ] 233 | 234 | # The name of an image file (relative to this directory) to place at the top of 235 | # the title page. 236 | #latex_logo = None 237 | 238 | # For "manual" documents, if this is true, then toplevel headings are parts, 239 | # not chapters. 240 | #latex_use_parts = False 241 | 242 | # If true, show page references after internal links. 243 | #latex_show_pagerefs = False 244 | 245 | # If true, show URL addresses after external links. 246 | #latex_show_urls = False 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #latex_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #latex_domain_indices = True 253 | 254 | 255 | # -- Options for manual page output --------------------------------------- 256 | 257 | # One entry per manual page. List of tuples 258 | # (source start file, name, description, authors, manual section). 259 | man_pages = [ 260 | (master_doc, 'rforecast', u'rforecast Documentation', 261 | [author], 1) 262 | ] 263 | 264 | # If true, show URL addresses after external links. 265 | #man_show_urls = False 266 | 267 | 268 | # -- Options for Texinfo output ------------------------------------------- 269 | 270 | # Grouping the document tree into Texinfo files. List of tuples 271 | # (source start file, target name, title, author, 272 | # dir menu entry, description, category) 273 | texinfo_documents = [ 274 | (master_doc, 'rforecast', u'rforecast Documentation', 275 | author, 'rforecast', 'One line description of project.', 276 | 'Miscellaneous'), 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | #texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | #texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | #texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | #texinfo_no_detailmenu = False 290 | 291 | 292 | # -- Options for Epub output ---------------------------------------------- 293 | 294 | # Bibliographic Dublin Core info. 295 | epub_title = project 296 | epub_author = author 297 | epub_publisher = author 298 | epub_copyright = copyright 299 | 300 | # The basename for the epub file. It defaults to the project name. 301 | #epub_basename = project 302 | 303 | # The HTML theme for the epub output. Since the default themes are not optimized 304 | # for small screen space, using the same theme for HTML and epub output is 305 | # usually not wise. This defaults to 'epub', a theme designed to save visual 306 | # space. 307 | #epub_theme = 'epub' 308 | 309 | # The language of the text. It defaults to the language option 310 | # or 'en' if the language is not set. 311 | #epub_language = '' 312 | 313 | # The scheme of the identifier. Typical schemes are ISBN or URL. 314 | #epub_scheme = '' 315 | 316 | # The unique identifier of the text. This can be a ISBN number 317 | # or the project homepage. 318 | #epub_identifier = '' 319 | 320 | # A unique identification for the text. 321 | #epub_uid = '' 322 | 323 | # A tuple containing the cover image and cover page html template filenames. 324 | #epub_cover = () 325 | 326 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 327 | #epub_guide = () 328 | 329 | # HTML files that should be inserted before the pages created by sphinx. 330 | # The format is a list of tuples containing the path and title. 331 | #epub_pre_files = [] 332 | 333 | # HTML files shat should be inserted after the pages created by sphinx. 334 | # The format is a list of tuples containing the path and title. 335 | #epub_post_files = [] 336 | 337 | # A list of files that should not be packed into the epub file. 338 | epub_exclude_files = ['search.html'] 339 | 340 | # The depth of the table of contents in toc.ncx. 341 | #epub_tocdepth = 3 342 | 343 | # Allow duplicate toc entries. 344 | #epub_tocdup = True 345 | 346 | # Choose between 'default' and 'includehidden'. 347 | #epub_tocscope = 'default' 348 | 349 | # Fix unsupported image types using the Pillow. 350 | #epub_fix_images = False 351 | 352 | # Scale large images. 353 | #epub_max_image_width = 0 354 | 355 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 356 | #epub_show_urls = 'inline' 357 | 358 | # If false, no index is generated. 359 | #epub_use_index = True 360 | -------------------------------------------------------------------------------- /doc/howto.rst: -------------------------------------------------------------------------------- 1 | How-To 2 | ====== 3 | 4 | Loading data 5 | ------------ 6 | 7 | Functions to read time series in with the correct index are in ``rforecast.ts_io`` 8 | 9 | To read data from a .csv file into a suitable Pandas Series: 10 | 11 | .. code-block:: python 12 | 13 | from rforecast import ts_io 14 | x = ts_io.read_series('data/aus.csv') 15 | 16 | To read in a time series in an R package, use ``ts_io.read_ts``. Here we read 17 | the *livestock* series from package ``fpp``. 18 | 19 | .. code-block:: python 20 | 21 | x = ts_io.read_ts('livestock', pkgname='fpp') 22 | 23 | 24 | Forecasting 25 | ----------- 26 | 27 | Forecasting functions are in ``rforecast.wrappers``. 28 | 29 | To construct a forecast, using the ``forecast`` function in R forecast: 30 | 31 | .. code-block:: python 32 | 33 | from rforecast import wrappers 34 | fc = wrappers.forecast(x) 35 | 36 | That function uses ``stlf`` if the series is non-seasonal with frequency 37 | greater that 12, and otherwise uses ``ets``. 38 | 39 | 40 | Decomposition 41 | ------------- 42 | 43 | Seasonal decompositions are in ``rforecast.wrappers``. 44 | 45 | To get an STL decomposition: 46 | 47 | .. code-block:: python 48 | 49 | dc = wrappers.stl(x, s_window=7) 50 | 51 | Plotting 52 | -------- 53 | 54 | Plot functions are in ``rforecast.plots`` 55 | 56 | To plot forecast prediction intervals: 57 | 58 | .. code-block:: python 59 | 60 | plots.plot_forecast(fc, data=x) 61 | 62 | If you have test data for the forecast period, you can plot that, too. 63 | 64 | .. code-block:: python 65 | 66 | plots.plot_forecast(fc, data=x, test=x_test) 67 | 68 | To plot a decomposition: 69 | 70 | .. code-block:: python 71 | 72 | plots.plot_decomp(dc) 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to rforecast.py |version| 2 | ===================================== 3 | 4 | The forecast package in R offers best-of-breed automatic forecasting methods 5 | for univariate time series. 6 | Its automatic model selection makes it relatively easy to use. 7 | There is no equivalent available in Python. 8 | This project uses rpy2 to expose most of the functionality of the forecast package 9 | in Python, allowing Python users access to its forecasting tools, while expressing 10 | the inputs and outputs in familiar Pandas objects. 11 | 12 | 13 | Contents: 14 | 15 | .. toctree:: 16 | :maxdepth: 4 17 | 18 | installation 19 | howto 20 | rforecast 21 | 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | To use this package, you will need Pandas, Matplotlib and R with the forecast package. 5 | You may also want to install the ``fpp`` package, which has useful time series data. 6 | If you install ``fpp``, then that will pull in ``forecast`` as a dependency, 7 | so that is all you need. 8 | 9 | External dependencies: 10 | ---------------------- 11 | 12 | * `Pandas`_ - tested on 0.17.0 13 | * `Matplotlib`_ - tested 1.4.3 14 | * `R`_ - tested on 3.1.2 15 | * `R forecast`_ - tested on 6.1 16 | 17 | Optional dependencies: 18 | ---------------------- 19 | * `fpp package`_ - contains time series data sets 20 | * `nose`_ - helpful if you want to run the tests 21 | * `coverage`_ - if you want coverage statistics 22 | 23 | .. _Pandas: http://pandas.pydata.org 24 | .. _Matplotlib: http://matplotlib.org 25 | .. _R: https://www.r-project.org/ 26 | .. _R forecast: https://cran.r-project.org/web/packages/forecast/forecast.pdf 27 | .. _fpp package: https://cran.r-project.org/web/packages/fpp/index.html 28 | .. _nose: https://pypi.python.org/pypi/nose/ 29 | .. _coverage: https://pypi.python.org/pypi/coverage 30 | 31 | Installation Instructions 32 | ------------------------- 33 | 34 | To install the R packages, start R and then use: 35 | 36 | .. code-block:: bash 37 | 38 | install.packages('forecast') 39 | 40 | The rforecast.py package is installed from git: 41 | 42 | .. code-block:: bash 43 | 44 | git clone https://github.com/davidthaler/Python-wrapper-for-R-Forecast.git 45 | cd Python-wrapper-for-R-Forecast 46 | python setup.py install 47 | 48 | 49 | Documentation Build 50 | ------------------- 51 | 52 | The documentation builds with Sphinx (developed Sphinx 1.3). 53 | If you have Sphinx installed, you can build the documentation using the Makefile 54 | in ``doc``: 55 | 56 | .. code-block:: bash 57 | 58 | cd doc 59 | make html 60 | 61 | Then the built documentation will start at: *doc/_build/html/index.html*. 62 | 63 | Testing 64 | ------- 65 | 66 | Tests are in *test/* under the install directory. 67 | From that level, if you have nose installed, you can run all of the tests with: 68 | 69 | .. code-block:: bash 70 | 71 | nosetests -v 72 | 73 | If you have the *coverage* nose plugin, you can get statement coverage 74 | 75 | .. code-block:: bash 76 | 77 | nosetests -v --cover-package=rforecast --with-cover 78 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\rforecast.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\rforecast.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/rforecast.rst: -------------------------------------------------------------------------------- 1 | rforecast package 2 | ================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | rforecast.converters module 8 | --------------------------- 9 | 10 | .. automodule:: rforecast.converters 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | rforecast.plots module 16 | ---------------------- 17 | 18 | .. automodule:: rforecast.plots 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | rforecast.ts_io module 24 | ---------------------- 25 | 26 | .. automodule:: rforecast.ts_io 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | rforecast.validate module 32 | ------------------------- 33 | 34 | .. automodule:: rforecast.validate 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | rforecast.wrappers module 40 | ------------------------- 41 | 42 | .. automodule:: rforecast.wrappers 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: rforecast 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /examples/livestock.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This example shows automatic fitting of an arima model with a linear 3 | trend as a regressor. It is based on a post on Hyndsight, a blog by 4 | R Forecast package author Rob J. Hyndman. 5 | 6 | See: http://robjhyndman.com/hyndsight/piecewise-linear-trends/#more-3413 7 | ''' 8 | # Not needed if the package is installed 9 | import sys, os 10 | sys.path.append(os.path.abspath('..')) 11 | 12 | from rforecast import ts_io 13 | from rforecast import wrappers 14 | from rforecast import converters 15 | from rforecast import plots 16 | 17 | # This is how to import data that is installed in R. 18 | stock = ts_io.read_ts('livestock', 'fpp') 19 | n = len(stock) 20 | fc = wrappers.auto_arima(stock, xreg=range(n), newxreg=range(n, n + 10)) 21 | print 'Australia livestock population 1961-2007' 22 | print stock 23 | plots.plot_ts(stock) 24 | print '10-year forecast of livestock population' 25 | print fc 26 | plots.plot_forecast(fc, stock) 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /rforecast/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidthaler/Python-wrapper-for-R-Forecast/fc5a3024b01529c555049b1635243aa6b1e5ed5e/rforecast/__init__.py -------------------------------------------------------------------------------- /rforecast/converters.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Converters.py handles conversions between pandas Series with various index 3 | types and R time series (class 'ts'). It also creates Pandas Series objects 4 | with the correct index types. Seasonal time series are represented 5 | as a Pandas Series with a MultiIndex in which the first level is the longer, 6 | outer time period and the second level is the cycle. 7 | ''' 8 | import numpy 9 | import pandas 10 | from rpy2.robjects.packages import importr 11 | from rpy2 import robjects 12 | from math import floor 13 | import validate 14 | 15 | stats = importr('stats') 16 | 17 | 18 | def to_ts(x): 19 | ''' 20 | Takes in a time series as either a Pandas Series or an R time series. 21 | Returns the series as an R time series, along with a flag that is True 22 | if the input was a Pandas Series and false if it was an R time series. 23 | 24 | Args: 25 | x: an R time series or Pandas Series 26 | 27 | Returns: 28 | 2-tuple of x, as an R time series, and True/False, with true if 29 | input was a Pandas Series 30 | ''' 31 | if type(x) is pandas.Series: 32 | return series_as_ts(x), True 33 | elif validate.is_R_ts(x): 34 | return x, False 35 | else: 36 | raise TypeError('Must be a Pandas series or R ts object.') 37 | 38 | 39 | def acf_out(x, is_pandas): 40 | ''' 41 | Accepts an R 'acf' object and returns either that object, or a Pandas 42 | Series with the same data. 43 | 44 | Args: 45 | x: an R object of class 'acf' 46 | is_pandas: True if the output should be a Pandas Series, False otherwise 47 | 48 | Returns: 49 | either an R 'acf' object or a Pandas Series with the same data 50 | ''' 51 | if is_pandas: 52 | return Acf(x) 53 | else: 54 | return x 55 | 56 | def series_out(x, is_pandas): 57 | ''' 58 | Accepts an R time series and returns the input as-is if is_pandas is False, 59 | or else a Pandas Series with the same data as the input. 60 | 61 | Args: 62 | x: an R time series 63 | is_pandas: True if the output should be a Pandas Series, False otherwise 64 | 65 | Returns: 66 | either and R time series or a Pandas Series containing the data in x 67 | ''' 68 | if is_pandas: 69 | return ts_as_series(x) 70 | else: 71 | return x 72 | 73 | 74 | def forecast_out(fc, is_pandas): 75 | ''' 76 | Accepts an R forecast object and returns either the object as-is, 77 | or a Pandas Data Frame extracted from the object. 78 | 79 | Args: 80 | fc: an R forecast object 81 | is_pandas: True if the output should be a Pandas Data Frame, 82 | if False, return fc as-is 83 | 84 | Returns: 85 | either an R forecast object or a Pandas Data Frame containing the 86 | prediction intervals and mean prediction from fc 87 | ''' 88 | if is_pandas: 89 | return prediction_intervals(fc) 90 | else: 91 | return fc 92 | 93 | 94 | def decomposition_out(dc, is_pandas): 95 | ''' 96 | Accepts an R decomposition and returns either the object, or a Pandas 97 | Data Frame extracted from the object. 98 | 99 | Args: 100 | dc: an R decomposition (class 'stl' or decomposed.ts') 101 | is_pandas: True if the output should be a Pandas Data Frame, 102 | if False, return dc as-is 103 | 104 | Returns: 105 | either an R decomposition or a Pandas Data Frame with the same data 106 | ''' 107 | if is_pandas: 108 | return decomposition(dc) 109 | else: 110 | return dc 111 | 112 | 113 | def to_series(x): 114 | ''' 115 | Accepts either an R time series or a Pandas Series. Returns a Pandas Series 116 | containing the data in the input. 117 | 118 | Args: 119 | x: an R time series or a Pandas Series 120 | 121 | Returns: 122 | a Pandas Series with the data in x 123 | ''' 124 | if type(x) is pandas.Series: 125 | return x 126 | elif validate.is_R_ts(x): 127 | return ts_as_series(x) 128 | else: 129 | raise TypeError('Must be a Pandas series or R ts object.') 130 | 131 | 132 | def to_decomp(dc): 133 | ''' 134 | Accepts either an R decomposition or a Pandas Data Frame containing a 135 | decomposition. In either case, it returns a Pandas Data Frame. 136 | 137 | Args: 138 | dc: an R decomposition or a Pandas Data Frame containing a decomposition 139 | 140 | Returns: 141 | the decomposition in dc, as a Pandas Data Frame 142 | ''' 143 | if validate.is_Pandas_decomposition(dc): 144 | return dc 145 | elif validate.is_R_decomposition(dc): 146 | return decomposition(dc) 147 | else: 148 | raise TypeError('Must provide a Pandas-type or R-type decomposition') 149 | 150 | 151 | def to_forecast(fc, data, test): 152 | ''' 153 | Accepts a forecast either as an R forecast object, or as a Pandas Data Frame 154 | containing prediction intervals plus a Series with the original data. Data 155 | for the forecast period may be included. The output is a 3-tuple of Pandas 156 | objects (or optionally None for the test data) with the original data and 157 | prediction intervals. 158 | 159 | Args: 160 | fc - an R forecast or Pandas Data Frame containing prediction intervals 161 | data - Data for the forecast period. Ignored if fc is an R forecast, 162 | because the forecast contains this information already. If fc is a 163 | Pandas Data Frame, then data must be a Pandas Series. 164 | test - optional data for the forecast period 165 | 166 | Returns: 167 | prediction intervals, the original data, and optionally forecast period 168 | data, all as Pandas objects. 169 | ''' 170 | if test is not None: 171 | test = to_series(test) 172 | if validate.is_Pandas_forecast(fc): 173 | if type(data) is not pandas.Series: 174 | raise TypeError( 175 | 'If forecast is Pandas Data Frame, data must be Pandas Series') 176 | return fc, data, test 177 | elif validate.is_R_forecast(fc): 178 | pi = prediction_intervals(fc) 179 | x = ts_as_series(fc.rx2('x')) 180 | return pi, x, test 181 | else: 182 | raise TypeError('Forecast must be R forecast object or Pandas DataFrame') 183 | 184 | 185 | def as_matrix(x): 186 | ''' 187 | Converts any legal input into an R matrix. Sequences are converted to one 188 | column matrices. 189 | 190 | Args: 191 | x: a Python list, numpy ndarray (1-D or 2-D), Pandas Series or DataFrame, 192 | or an R matrix, or any R object that as.matrix can convert. 193 | 194 | Returns: 195 | an R matrix, with the data in x 196 | ''' 197 | if validate.is_R_matrix(x): 198 | return x 199 | else: 200 | return matrix(x) 201 | 202 | 203 | def matrix(x): 204 | ''' 205 | Converts Python data to an R matrix. This function converts lists, 1-D 206 | numpy arrays and Pandas Series to a 1-column matrix. Pandas DataFrames 207 | and numpy 2-D arrays are converted to an R matrix with the same shape. 208 | Forecast methods that allow regressors, like Arima or auto.arima, 209 | take them as an R matrix. 210 | 211 | Args: 212 | x: a 1-D or 2-D list, 1 numpy ndarray (1-D or 2-D), 213 | Pandas Series or DataFrame 214 | 215 | Returns: 216 | an R matrix containing x 217 | ''' 218 | nrow = len(x) 219 | x = numpy.array(x).ravel() 220 | rdata = robjects.FloatVector(x) 221 | return robjects.r.matrix(rdata, byrow=True, nrow=nrow) 222 | 223 | 224 | def map_arg(x): 225 | ''' 226 | Many arguments in R may be either numbers or vectors. Rpy2 translates 227 | arguments that are numbers automatically, but does not translate tuples 228 | or lists to R vectors. This function translates tuples or lists to R 229 | vectors, if needed. 230 | 231 | Args: 232 | x: a number or list/tuple 233 | 234 | Returns: 235 | either an R vector containing the values in x, or the number x 236 | ''' 237 | if type(x) in (tuple, list): 238 | return robjects.r.c(*x) 239 | else: 240 | return x 241 | 242 | 243 | def translate_kwargs(**kwargs): 244 | ''' 245 | Translates between python and R keyword arguments. 246 | First, tuple arguments are rewritten to R vectors. Next, substitution 247 | is performed for a specific list of arguments. Currently, this is just 248 | 'lam' -> 'lambda'; 'lambda' is a reserved word in python, but is used 249 | a lot in the R Forecast package. Finally, underscore-separated keywords 250 | are turned into R-style, dot-separated ones. If you need to pass an R 251 | argument that has an underscore, you must put it into the 'reserved' dict. 252 | 253 | Args: 254 | **kwargs: the dict of all keyword arguments to a python function 255 | 256 | Returns: 257 | A dict that can be passed as **kwargs to R functions 258 | ''' 259 | reserved = {'lam':'lambda'} 260 | for key in kwargs: 261 | if type(kwargs[key]) in (list, tuple): 262 | kwargs[key] = robjects.r.c(*kwargs[key]) 263 | if key in reserved: 264 | kwargs[reserved[key]] = kwargs[key] 265 | del kwargs[key] 266 | elif '_' in key: 267 | new_key = key.replace('_', '.') 268 | kwargs[new_key] = kwargs[key] 269 | del kwargs[key] 270 | return kwargs 271 | 272 | 273 | def ts(data, **kwargs): 274 | ''' 275 | Turns the provided data into an R time series. Only one of frequency and 276 | deltat should be given. If both of start and end are specified, truncation 277 | or recycling may occur, which is usually not sensible. 278 | 279 | Args: 280 | data: Python sequence representing values of a regular time series. 281 | start: default 1; a number or 2-tuple to use as start index of sequence. 282 | If 2-tuple, it is (period, step), e.g. March 2010 is (2010, 3). 283 | end: By default this is not specified, which is usually right. 284 | A number or 2-tuple (like start) to specify the end of the sequence. 285 | frequency: default 1; number of points in each time period 286 | e.g. 12 for monthly data with an annual period 287 | deltat: default 1; fraction of sampling period per observation 288 | e.g. 1/12 for monthly data with an annual period. Only one of deltat 289 | and frequency should be defined. 290 | 291 | Returns: 292 | an object containing the data that maps to an R time series (class 'ts') 293 | ''' 294 | rdata = robjects.FloatVector(data) 295 | kwargs = translate_kwargs(**kwargs) 296 | time_series = stats.ts(rdata, **kwargs) 297 | return time_series 298 | 299 | 300 | def _get_index(ts): 301 | ''' 302 | Utility function for making the correct argument to constructors for 303 | Pandas Series or DataFrame objects so as to get the index to match a 304 | given time series. 305 | 306 | Args: 307 | ts: an object that maps to an R time series (class ts) 308 | 309 | Returns: 310 | either a list or a list of lists 311 | ''' 312 | times = [int(floor(x)) for x in list(robjects.r('time')(ts))] 313 | cycles = [int(x) for x in list(robjects.r('cycle')(ts))] 314 | if robjects.r('frequency')(ts)[0] > 1: 315 | return [times, cycles] 316 | else: 317 | return times 318 | 319 | 320 | def ts_as_series(ts): 321 | ''' 322 | Convert an R time series into a Pandas Series with the appropriate 323 | (seasonal/non-seasonal) index. 324 | 325 | Args: 326 | ts: an object that maps to an R time series (class ts) 327 | 328 | Returns: 329 | a Pandas Series with the same data and index as ts 330 | ''' 331 | idx = _get_index(ts) 332 | return pandas.Series(ts, index=idx) 333 | 334 | 335 | 336 | def _seasonal_series_as_ts(x): 337 | ''' 338 | Converts a Pandas Series with a MultiIndex into a seasonal R time series. 339 | The MultiIndex should only ever be used to represent seasonal series. 340 | 341 | Args: 342 | x: a Pandas Series with a MultiIndex 343 | 344 | Returns: 345 | an R seasonal time series 346 | ''' 347 | idx = x.index 348 | start = idx[0] 349 | freq = len(idx.levels[1]) 350 | return ts(x, start=start, frequency=freq) 351 | 352 | 353 | def _regular_series_as_ts(x): 354 | ''' 355 | Converts a normally-indexed Pandas Series to an R time series object with 356 | the same start period. 357 | 358 | Args: 359 | x: a Pandas Series with a standard index 360 | 361 | Returns: 362 | a non-seasonal R time series. 363 | ''' 364 | return ts(x, start=x.index[0]) 365 | 366 | 367 | def series_as_ts(x): 368 | ''' 369 | Takes a Pandas Series with either a seasonal or non-seasonal time series 370 | in it, and converts it to an R time series (class 'ts'). If the series is 371 | seasonal, x must have a MultiIndex encoding the inner and outer period. 372 | If it is non-seasonal, x must have an ordinary index with the periods. 373 | 374 | Args: 375 | x: a Pandas Series 376 | 377 | Returns: 378 | an R time series 379 | ''' 380 | if x.index.nlevels == 2: 381 | return _seasonal_series_as_ts(x) 382 | else: 383 | return _regular_series_as_ts(x) 384 | 385 | 386 | # TODO: this need some arg-checking 387 | def sequence_as_series(x, start=1, freq=1): 388 | ''' 389 | Converts a list or other sequence input into a Pandas Series with the 390 | correct index for the type of Series created. 391 | 392 | Args: 393 | x: a time series as a Python list, Pandas Series or numpy ndarray (1-D) 394 | start: default 1; a number or 2-tuple to use as start index of sequence. 395 | If 2-tuple, it is (period, step), e.g. March 2010 is (2010, 3). 396 | freq: default 1; number of points in each time period 397 | e.g. 12 for monthly data with an annual period 398 | 399 | Returns: 400 | a Pandas Series with the correct index for the time series 401 | ''' 402 | if freq <= 1: 403 | idx = range(start, start + len(x)) 404 | return pandas.Series(list(x), index=idx) 405 | else: 406 | if type(start) not in (list, tuple): 407 | start = (start, 1) 408 | i, j = start 409 | inner = [] 410 | outer = [] 411 | for k in range(len(x)): 412 | inner.append(j) 413 | outer.append(i) 414 | j += 1 415 | if j > freq: 416 | i += 1 417 | j = 1 418 | return pandas.Series(data=list(x), index=[outer, inner]) 419 | 420 | 421 | def prediction_intervals(fc): 422 | ''' 423 | Function creates a Pandas DataFrame with the upper and lower prediction 424 | intervals, as well as the mean prediction. 425 | 426 | Args: 427 | fc: an object with class forecast from R Forecast 428 | 429 | Returns: 430 | a Pandas DataFrame with the mean prediction and prediction intervals 431 | ''' 432 | if robjects.r('class')(fc)[0] != 'forecast': 433 | raise ValueError('Argument must map to an R forecast.') 434 | mean_fc = list(fc.rx2('mean')) 435 | idx = _get_index(fc.rx2('mean')) 436 | df = pandas.DataFrame({'point_fc' : mean_fc}, index=idx) 437 | colnames = ['point_fc'] 438 | lower = fc.rx2('lower') 439 | upper = fc.rx2('upper') 440 | for (k, level) in enumerate(fc.rx2('level'), 1): 441 | lower_colname = 'lower%d' % level 442 | df[lower_colname] = lower.rx(True, k) 443 | colnames.append(lower_colname) 444 | upper_colname = 'upper%d' % level 445 | df[upper_colname] = upper.rx(True, k) 446 | colnames.append(upper_colname) 447 | return df[colnames] 448 | 449 | 450 | def accuracy(acc): 451 | ''' 452 | Convert the R matrix of forecast accuracy measures returned from 453 | wrappers.accuracy into a Pandas DataFrame. 454 | 455 | Args: 456 | acc: R matrix returned from wrappers.accuracy 457 | 458 | Returns: 459 | Pandas DataFrame with accuracy measures 460 | ''' 461 | index = pandas.Index(list(acc.colnames)) 462 | if acc.dim[0] == 2: 463 | data = {'Train' : list(acc)[::2]} 464 | data['Test'] = list(acc)[1::2] 465 | else: 466 | data = {'Train' : list(acc)} 467 | return pandas.DataFrame(data=data, index=index) 468 | 469 | 470 | def decomposition(decomp): 471 | ''' 472 | Function creates a Pandas DataFrame with the seasonal, trend and remainder 473 | components of a seasonal decomposition, along with the original series, in 474 | separate columns. 475 | 476 | Args: 477 | decomp: An object that maps to a seasonal decomposition (class 'stl' 478 | or 'decomposed.ts' in R), otained from stl or decompose in wrapper. 479 | 480 | Returns: 481 | a Pandas DataFrame with the seasonal, trend and remainder 482 | ''' 483 | cls = robjects.r('class') 484 | if cls(decomp)[0] == 'stl': 485 | data = decomp.rx2('time.series') 486 | seasonal = list(data.rx(True, 1)) 487 | trend = list(data.rx(True, 2)) 488 | remainder = list(data.rx(True, 3)) 489 | cols = ['seasonal', 'trend', 'remainder'] 490 | idx = _get_index(data) 491 | df = pandas.DataFrame(dict(zip(cols, (seasonal, trend, remainder))), index=idx) 492 | df['data'] = df.sum(axis=1) 493 | return df[['data', 'seasonal', 'trend', 'remainder']] 494 | elif cls(decomp)[0] == 'decomposed.ts': 495 | x = list(decomp.rx2('x')) 496 | seasonal = list(decomp.rx2('seasonal')) 497 | trend = list(decomp.rx2('trend')) 498 | remainder = list(decomp.rx2('random')) 499 | cols = ['data', 'seasonal', 'trend', 'remainder'] 500 | idx = _get_index(decomp.rx2('x')) 501 | df = pandas.DataFrame(dict(zip(cols, (x, seasonal, trend, remainder))), index=idx) 502 | return df[cols] 503 | else: 504 | raise ValueError('Argument must map to an R seasonal decomposition.') 505 | 506 | 507 | def Acf(acf): 508 | ''' 509 | Function to extract a Pandas Series based on the provided R acf object. 510 | 511 | Args: 512 | acf: an R object with class 'acf' 513 | 514 | Returns: 515 | a Pandas Series with the autocorrelation values from the argument 516 | ''' 517 | lags = list(acf.rx2('lag')) 518 | data = list(acf.rx2('acf')) 519 | if acf.rx2('type')[0] == 'partial': 520 | name = 'Pacf' 521 | elif acf.rx2('type')[0] == 'correlation': 522 | name = 'Acf' 523 | lags = lags[1:] 524 | data = data[1:] 525 | return pandas.Series(data=data, index=lags, name=name) 526 | 527 | def flatten_index(idx): 528 | ''' 529 | Function flattens a multindex into a form suitable for plotting. 530 | The inner (seasonal) steps are converted to decimals. 531 | If given a 1-level index, it returns it as-is. 532 | 533 | Args: 534 | idx: the index to possibly flatten 535 | 536 | Returns: 537 | a 1-level index 538 | ''' 539 | if idx.nlevels == 1: 540 | return idx 541 | elif idx.nlevels == 2: 542 | outer = idx.levels[0][idx.labels[0]] 543 | freq = float(len(idx.levels[1])) 544 | inner = idx.labels[1] / freq 545 | return outer + inner 546 | else: 547 | raise ValueError('rforecast only supports single seasonality') 548 | 549 | 550 | -------------------------------------------------------------------------------- /rforecast/plots.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The plots module contains functions for producing plots using matplotlib 3 | of time series, forecast results and seasonal decompositions. 4 | ''' 5 | import matplotlib.pyplot as plt 6 | import converters 7 | 8 | 9 | def plot_ts(ts, **kwargs): 10 | ''' 11 | Plots an R time series using matplotlib/pyplot/pandas. 12 | 13 | Args: 14 | ts: an object that maps to an R time series 15 | kwargs: keyword arguments passed through a pandas Series 16 | and on to pyplot.plot(). 17 | 18 | Output: 19 | a time series plot 20 | ''' 21 | s = converters.to_series(ts) 22 | s.plot(**kwargs) 23 | plt.style.use('ggplot') 24 | plt.show() 25 | 26 | 27 | def plot_decomp(decomp, **kwargs): 28 | ''' 29 | Plots a seasonal decomposition using matplotlib/pyplot/pandas. 30 | 31 | Args: 32 | decomp: either an R decomposition (class 'stl' or 'decomposed.ts') or 33 | a Pandas Data Frame from converters.decomposition. 34 | kwargs: keyword arguments passed through a pandas DataFrame 35 | and on to pyplot.plot(). 36 | 37 | Output: 38 | a plot of the seasonal, trend and remainder components from the 39 | decomposition plus the original time series data 40 | ''' 41 | decomp = converters.to_decomp(decomp) 42 | decomp.plot(subplots=True, **kwargs) 43 | plt.style.use('ggplot') 44 | plt.show() 45 | 46 | 47 | def plot_forecast(fc, data=None, test=None, loc='upper left'): 48 | ''' 49 | Plots a forecast and its prediction intervals. 50 | 51 | Args: 52 | fc: Pandas Data Frame from converters.prediction_intervals, 53 | or an R forecast object 54 | data: the data for the forecast period as a Pandas Series, or None 55 | if fc is an R forecast 56 | test: optional data for the forecast period as a Pandas Series 57 | loc: Default is 'upper left', since plots often go up and right. 58 | For other values see matplotlib.pyplot.legend(). 59 | 60 | Output: 61 | a plot of the series, the mean forecast, and the prediciton intervals, 62 | and optionally, the data for the forecast period, if provided, 63 | ''' 64 | fc, data, test = converters.to_forecast(fc, data, test) 65 | plt.style.use('ggplot') 66 | l = list(fc.columns) 67 | lowers = l[1::2] 68 | uppers = l[2::2] 69 | tr_idx = converters.flatten_index(data.index) 70 | fc_idx = converters.flatten_index(fc.index) 71 | plt.plot(tr_idx, data, color='black') 72 | plt.plot(fc_idx, fc[l[0]], color='blue') 73 | for (k, (low, up)) in enumerate(zip(lowers, uppers), 1): 74 | plt.fill_between(fc_idx, fc[low], fc[up], color='grey', alpha=0.5/k) 75 | labels = ['data', 'forecast'] 76 | if test is not None: 77 | n = min(len(fc.index), len(test)) 78 | plt.plot(fc_idx[:n], list(test[:n]), color='green') 79 | labels.append('test') 80 | plt.legend(labels, loc=loc) 81 | plt.show() 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /rforecast/rbase.py: -------------------------------------------------------------------------------- 1 | from rpy2 import robjects 2 | 3 | def cls(x): 4 | try: 5 | return list(robjects.r('class')(x)) 6 | except NotImplementedError: 7 | raise TypeError('Cannot call R function on Python object.') 8 | 9 | def colnames(x): 10 | try: 11 | out = robjects.r('colnames')(x) 12 | if out is robjects.NULL: 13 | return None 14 | else: 15 | return list(out) 16 | except NotImplementedError: 17 | raise TypeError('Cannot call R function on Python object.') 18 | 19 | def dim(x): 20 | try: 21 | out = robjects.r('dim')(x) 22 | if out is robjects.NULL: 23 | return None 24 | else: 25 | return list(out) 26 | except NotImplementedError: 27 | raise TypeError('Cannot call R function on Python object.') -------------------------------------------------------------------------------- /rforecast/ts_io.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ts_io.py handles reading time series into Pandas Series objects with the 3 | index set up as used in RForecast. 4 | ''' 5 | import pandas 6 | import converters 7 | from rpy2 import robjects 8 | from rpy2.robjects.packages import importr 9 | from rpy2.rinterface import RRuntimeError 10 | 11 | 12 | # TODO: if we accept msts, this will have to accept more than 3 columns 13 | def read_series(file): 14 | ''' 15 | Function read_ts reads a csv file of a time series. Input file should have 16 | 1, 2, or 3 columns. If 1 column, it is data-only. If 2-columns, it is read 17 | as a non-seasonal timeseries like: time, data. If 3-column, it is read as a 18 | seasonal time series, e.g. year, month, data. Seasonal time series will be 19 | represented with a Series with a MultiIndex. 20 | 21 | Args: 22 | file: a path or open file to the data 23 | 24 | Returns: 25 | a Pandas Series with the data in the file, and the appropriate type of 26 | index for the type of data (seasonal/non-seasonal) 27 | ''' 28 | df = pandas.read_csv(file, header=None) 29 | _, ncols = df.shape 30 | if ncols == 1: 31 | data = df[0].values 32 | index = range(1, len(data) + 1) 33 | elif ncols == 2: 34 | data = df[1].values 35 | index = df[0].values 36 | elif ncols == 3: 37 | data = df[2].values 38 | index = [df[0].values, df[1].values] 39 | else: 40 | raise IOError('File %s has wrong format' % file) 41 | return pandas.Series(data=data, index=index) 42 | 43 | 44 | def read_ts(ts_name, pkgname=None, as_pandas=True): 45 | ''' 46 | Function reads a time series in from R. If needed, it can load a package 47 | containing the time series. The output can be provided as an R object or 48 | as a Pandas Series. 49 | 50 | Args: 51 | ts_name: the name of the time series in R 52 | pkgname: Default None. The name of an R package with the time series. 53 | as_pandas: Default True. If true, return a Pandas Series. 54 | 55 | Returns: 56 | the time series as an R time series or a Pandas Series 57 | ''' 58 | if pkgname is not None: 59 | try: 60 | importr(pkgname) 61 | except RRuntimeError: 62 | raise IOError('Package %s not found in R.' % pkgname) 63 | try: 64 | tsout = robjects.r(ts_name) 65 | except RRuntimeError: 66 | raise IOError('Time series %s not found in R.' % ts_name) 67 | return converters.series_out(tsout, as_pandas) 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /rforecast/validate.py: -------------------------------------------------------------------------------- 1 | from rpy2 import robjects 2 | from rbase import cls, dim, colnames 3 | import pandas 4 | 5 | def is_R_forecast(fc): 6 | return type(fc) is robjects.ListVector and 'forecast' in cls(fc) 7 | 8 | def is_Pandas_forecast(fc): 9 | return type(fc) is pandas.DataFrame and 'point_fc' in fc.columns 10 | 11 | def is_forecast(fc): 12 | return is_R_forecast(fc) or is_Pandas_forecast(fc) 13 | 14 | def is_R_decomposition(dc): 15 | return (type(dc) is robjects.ListVector 16 | and cls(dc)[0] in ['stl', 'decomposed.ts']) 17 | 18 | def is_Pandas_decomposition(dc): 19 | col_names = [u'data', u'seasonal', u'trend', u'remainder'] 20 | return (type(dc) is pandas.DataFrame and dc.shape[1] == 4 21 | and len(set(dc.columns).intersection(col_names)) == 4) 22 | 23 | def is_decomposition(dc): 24 | return is_Pandas_decomposition(dc) or is_R_decomposition 25 | 26 | def is_R_accuracy(acc): 27 | if type(acc) is robjects.Matrix: 28 | cnames = colnames(acc) 29 | r, c = dim(acc) 30 | if ('matrix' in cls(acc) 31 | and 'MASE' in cnames 32 | and r in {1,2} and c in {7,8}): 33 | return True 34 | return False 35 | 36 | def is_R_ts(x): 37 | return type(x) is robjects.FloatVector and 'ts' in cls(x) 38 | 39 | def is_R_matrix(x): 40 | return type(x) is robjects.Matrix and 'matrix' in cls(x) 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /rforecast/wrappers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The wrappers module contains functions that wrap forecasting functions and 3 | seasonal decompositions from R. It is the main module in this package. 4 | ''' 5 | from rpy2 import robjects 6 | from rpy2.robjects.packages import importr 7 | import numpy 8 | import converters 9 | import validate 10 | import itertools 11 | 12 | fc = importr('forecast') 13 | stats = importr('stats') 14 | NULL = robjects.NULL 15 | NA = robjects.NA_Real 16 | 17 | 18 | def frequency(x): 19 | ''' 20 | Function returns the frequency attribute of an R time series. 21 | This should be 1 if the series is non-periodic. Otherwise, it should be 22 | the number of data points in one period, e.g. 12 for monthly data. 23 | 24 | Args: 25 | x: an R time series, obtained from converters.ts(), or a Pandas Series 26 | with the correct index (e.g. from converters.sequence_as_series(). 27 | Returns: 28 | The number of data points per period in x, as a single float 29 | ''' 30 | x, _ = converters.to_ts(x) 31 | return stats.frequency(x)[0] 32 | 33 | 34 | def _get_horizon(x, h=None): 35 | ''' 36 | Utility function for getting forecast horizons. 37 | 38 | Args: 39 | x: the R time series to be forecast 40 | h: None, or a forecast horizon 41 | 42 | Returns: 43 | the provided h value, or the correct default if h is None 44 | ''' 45 | if h is not None: 46 | return h 47 | if frequency(x) > 1: 48 | return 2 * frequency(x) 49 | else: 50 | return 10 51 | 52 | 53 | def meanf(x, h=10, level=(80,95), lam=NULL): 54 | ''' 55 | Perform a mean forecast on the provided data by calling meanf() 56 | from R Forecast. 57 | 58 | Args: 59 | x: an R time series, obtained from converters.ts(), or a Pandas Series 60 | with the correct index (e.g. from converters.sequence_as_series(). 61 | h: default 10; the forecast horizon. 62 | level: A number or list/tuple of prediction interval confidence values. 63 | Default is 80% and 95% intervals. 64 | lam: BoxCox transformation parameter. The default is R's NULL value. 65 | If NULL, no transformation is applied. Otherwise, a Box-Cox 66 | transformation is applied before forecasting and inverted after. 67 | 68 | Returns: 69 | If x is an R ts object, an R forecast is returned. If x is a Pandas 70 | Series, a Pandas Data Frame is returned. 71 | ''' 72 | x, is_pandas = converters.to_ts(x) 73 | level = converters.map_arg(level) 74 | out = fc.meanf(x, h, level=level, **{'lambda' : lam}) 75 | return converters.forecast_out(out, is_pandas) 76 | 77 | 78 | def thetaf(x, h=10, level=(80, 95)): 79 | ''' 80 | Perform a theta forecast on the provided data by calling thetaf() 81 | from R Forecast. The theta forecast is equivalent to a random walk 82 | forecast (rwf in R Forecast) with drift, with the drift equal to half 83 | the slope of a linear regression model fitted to with a trend. The 84 | theta forecast did well in the M3 competition. 85 | 86 | Args: 87 | x: an R time series, obtained from converters.ts(), or a Pandas Series 88 | with the correct index (e.g. from converters.sequence_as_series(). 89 | h: default 10; the forecast horizon. 90 | level: A number or list/tuple of prediction interval confidence values. 91 | Default is 80% and 95% intervals. 92 | 93 | Returns: 94 | If x is an R ts object, an R forecast is returned. If x is a Pandas 95 | Series, a Pandas Data Frame is returned. 96 | ''' 97 | x, is_pandas = converters.to_ts(x) 98 | level = converters.map_arg(level) 99 | out = fc.thetaf(x, h, level=level) 100 | return converters.forecast_out(out, is_pandas) 101 | 102 | 103 | def naive(x, h=10, level=(80, 95), lam=NULL): 104 | ''' 105 | Perform a naive forecast on the provided data by calling naive() 106 | from R Forecast. This is also called the 'Last Observed Value' 107 | forecast. The point forecast is a constant at the last observed value. 108 | 109 | Args: 110 | x: an R time series, obtained from converters.ts(), or a Pandas Series 111 | with the correct index (e.g. from converters.sequence_as_series(). 112 | h: default 10; the forecast horizon. 113 | level: A number or list/tuple of prediction interval confidence values. 114 | Default is 80% and 95% intervals. 115 | lam: BoxCox transformation parameter. The default is R's NULL value. 116 | If NULL, no transformation is applied. Otherwise, a Box-Cox 117 | transformation is applied before forecasting and inverted after. 118 | 119 | Returns: 120 | If x is an R ts object, an R forecast is returned. If x is a Pandas 121 | Series, a Pandas Data Frame is returned. 122 | ''' 123 | x, is_pandas = converters.to_ts(x) 124 | level = converters.map_arg(level) 125 | out = fc.naive(x, h, level=level, **{'lambda' : lam}) 126 | return converters.forecast_out(out, is_pandas) 127 | 128 | 129 | def snaive(x, h=None, level=(80, 95), lam=NULL): 130 | ''' 131 | Perform a seasonal naive forecast on the provided data by calling 132 | snaive() from R Forecast. This is also called the 'Last Observed 133 | Seasonal Value' forecast. The point forecast is the value of the 134 | series one full period in the past. 135 | 136 | Args: 137 | x: an R time series, obtained from converters.ts(), or a Pandas Series 138 | with the correct index (e.g. from converters.sequence_as_series(). 139 | For this forecast method, x should be seasonal. 140 | h: Forecast horizon; default is 2 full periods of a periodic series 141 | level: A number or list/tuple of prediction interval confidence values. 142 | Default is 80% and 95% intervals. 143 | lam: BoxCox transformation parameter. The default is R's NULL value. 144 | If NULL, no transformation is applied. Otherwise, a Box-Cox 145 | transformation is applied before forecasting and inverted after. 146 | 147 | Returns: 148 | If x is an R ts object, an R forecast is returned. If x is a Pandas 149 | Series, a Pandas Data Frame is returned. 150 | ''' 151 | x, is_pandas = converters.to_ts(x) 152 | h = _get_horizon(x, h) 153 | level = converters.map_arg(level) 154 | out = fc.snaive(x, h, level=level, **{'lambda' : lam}) 155 | return converters.forecast_out(out, is_pandas) 156 | 157 | 158 | def rwf(x, h=10, drift=False, level=(80, 95), lam=NULL): 159 | ''' 160 | Perform a random walk forecast on the provided data by calling 161 | rwf() from R Forecast. The forecast can have drift, which allows 162 | a trend in the mean prediction, but by default, it does not. 163 | 164 | Args: 165 | x: an R time series, obtained from converters.ts(), or a Pandas Series 166 | with the correct index (e.g. from converters.sequence_as_series(). 167 | h: default 10; the forecast horizon. 168 | drift: default False. If True, a random walk with drift model is fitted. 169 | level: A number or list/tuple of prediction interval confidence values. 170 | Default is 80% and 95% intervals. 171 | lam: BoxCox transformation parameter. The default is R's NULL value. 172 | If NULL, no transformation is applied. Otherwise, a Box-Cox 173 | transformation is applied before forecasting and inverted after. 174 | 175 | Returns: 176 | If x is an R ts object, an R forecast is returned. If x is a Pandas 177 | Series, a Pandas Data Frame is returned. 178 | ''' 179 | x, is_pandas = converters.to_ts(x) 180 | level = converters.map_arg(level) 181 | out = fc.rwf(x, h, drift, level=level, **{'lambda' : lam}) 182 | return converters.forecast_out(out, is_pandas) 183 | 184 | 185 | def ses(x, h=10, level=(80, 95), alpha=NULL, lam=NULL): 186 | ''' 187 | Generate a simple exponential smoothing forecast for the time series x. 188 | This function does not optimize the initial value. To get an optimal 189 | initial value, use ets() with model_spec='ANN'. 190 | 191 | Args: 192 | x: an R time series, obtained from converters.ts(), or a Pandas Series 193 | with the correct index (e.g. from converters.sequence_as_series(). 194 | h: the forecast horizon, default 10 195 | level: A number or list/tuple of prediction interval confidence values. 196 | Default is 80% and 95% intervals. 197 | alpha: exponential smoothing parameter. Must be a float value between 198 | 0.0001 and 0.9999 or R's NULL value (the default), in which 199 | case this parameter is optimized. 200 | lam: BoxCox transformation parameter. The default is R's NULL value. 201 | If NULL, no transformation is applied. Otherwise, a Box-Cox 202 | transformation is applied before forecasting and inverted after. 203 | 204 | Returns: 205 | If x is an R ts object, an R forecast is returned. If x is a Pandas 206 | Series, a Pandas Data Frame is returned. 207 | ''' 208 | if alpha is not NULL: 209 | if alpha < 0.0001 or alpha > 0.9999: 210 | raise ValueError('alpha must be between 0.0001 and 0.9999, if given') 211 | x, is_pandas = converters.to_ts(x) 212 | level = converters.map_arg(level) 213 | out = fc.ses(x, h, level=level, alpha=alpha, 214 | initial='simple', **{'lambda' : lam}) 215 | return converters.forecast_out(out, is_pandas) 216 | 217 | 218 | def holt(x, h=10, level=(80, 95), alpha=NULL, beta=NULL, lam=NULL): 219 | ''' 220 | Generates a forecast using Holt's exponential smoothing method. 221 | Initial values are fitted from the first values in x. For optimized values, 222 | use ets() with model_spec='AAN'. 223 | 224 | Args: 225 | x: an R time series, obtained from converters.ts(), or a Pandas Series 226 | with the correct index (e.g. from converters.sequence_as_series(). 227 | h: the forecast horizon, default 10 228 | level: A number or list/tuple of prediction interval confidence values. 229 | Default is 80% and 95% intervals. 230 | alpha: level smoothing parameter. Must be a float value between 231 | 0.0001 and 0.9999 or R's NULL value (the default), in which 232 | case this parameter is optimized. 233 | beta: trend smoothing parameter. Must be a float value between 234 | 0.0001 and 0.9999 or R's NULL value (the default), in which 235 | case this parameter is optimized. 236 | lam: BoxCox transformation parameter. The default is R's NULL value. 237 | If NULL, no transformation is applied. Otherwise, a Box-Cox 238 | transformation is applied before forecasting and inverted after. 239 | 240 | Returns: 241 | If x is an R ts object, an R forecast is returned. If x is a Pandas 242 | Series, a Pandas Data Frame is returned. 243 | ''' 244 | if alpha is not NULL: 245 | if alpha < 0.0001 or alpha > 0.9999: 246 | raise ValueError('alpha must be between 0.0001 and 0.9999, if given') 247 | if beta is not NULL: 248 | if beta < 0.0001 or beta > 0.9999: 249 | raise ValueError('beta must be between 0.0001 and 0.9999, if given') 250 | x, is_pandas = converters.to_ts(x) 251 | level = converters.map_arg(level) 252 | out = fc.holt(x, h, level=level, alpha=alpha, beta=beta, 253 | initial='simple', **{'lambda' : lam}) 254 | return converters.forecast_out(out, is_pandas) 255 | 256 | 257 | def hw(x, h=None, level=(80, 95), alpha=NULL, beta=NULL, gamma=NULL, lam=NULL): 258 | ''' 259 | Generates a forecast using Holt-Winter's exponential smoothing. 260 | Initial values are fitted from the first values in x. For optimized values, 261 | use ets() with model_spec='AAA'. 262 | 263 | Args: 264 | x: an R time series, obtained from converters.ts(), or a Pandas Series 265 | with the correct index (e.g. from converters.sequence_as_series(). 266 | h: the forecast horizon 267 | level: A number or list/tuple of prediction interval confidence values. 268 | Default is 80% and 95% intervals. 269 | alpha: level smoothing parameter. Must be a float value between 270 | 0.0001 and 0.9999 or R's NULL value (the default), in which 271 | case this parameter is optimized. 272 | beta: trend smoothing parameter. Must be a float value between 273 | 0.0001 and 0.9999 or R's NULL value (the default), in which 274 | case this parameter is optimized. 275 | gamma: seasonal smoothing parameter. Must be a float value between 276 | 0.0001 and 0.9999 or R's NULL value (the default), in which 277 | case this parameter is optimized. 278 | lam: BoxCox transformation parameter. The default is R's NULL value. 279 | If NULL, no transformation is applied. Otherwise, a Box-Cox 280 | transformation is applied before forecasting and inverted after. 281 | 282 | Returns: 283 | If x is an R ts object, an R forecast is returned. If x is a Pandas 284 | Series, a Pandas Data Frame is returned. 285 | ''' 286 | if alpha is not NULL: 287 | if alpha < 0.0001 or alpha > 0.9999: 288 | raise ValueError('alpha must be between 0.0001 and 0.9999, if given') 289 | if beta is not NULL: 290 | if beta < 0.0001 or beta > 0.9999: 291 | raise ValueError('beta must be between 0.0001 and 0.9999, if given') 292 | if gamma is not NULL: 293 | if gamma < 0.0001 or gamma > 0.9999: 294 | raise ValueError('gamma must be between 0.0001 and 0.9999, if given') 295 | x, is_pandas = converters.to_ts(x) 296 | h = _get_horizon(x, h) 297 | level = converters.map_arg(level) 298 | out = fc.hw(x, h, level=level, alpha=alpha, beta=beta, gamma=gamma, 299 | initial='simple', **{'lambda' : lam}) 300 | return converters.forecast_out(out, is_pandas) 301 | 302 | 303 | def forecast(x, h=None, **kwargs): 304 | ''' 305 | Generate a forecast for the time series x, using ets if x is non-seasonal 306 | or has frequency less than 13, and stlf if x is periodic with frequency 307 | above 13. 308 | 309 | Args: 310 | x: an R time series, obtained from converters.ts(), or a Pandas Series 311 | with the correct index (e.g. from converters.sequence_as_series(). 312 | h: the forecast horizon 313 | level: A number or list/tuple of prediction interval confidence values. 314 | Default is 80% and 95% intervals. 315 | robust: Default False. If True, missing values are filled before 316 | forecasting and outliers are identified and replaced with tsclean(). 317 | lam : BoxCox transformation parameter. The default is R's NULL value. 318 | If NULL, no transformation is applied. Otherwise, a Box-Cox 319 | transformation is applied before forecasting and inverted after. 320 | find_frequency: Default False. If True, function will try to determine 321 | the series frequency from the data. 322 | allow_multiplicative_trend: Default is False. If True, consider models 323 | with a multiplicative trend component. That type of model may grow 324 | explosively. 325 | 326 | Returns: 327 | If x is an R ts object, an R forecast is returned. If x is a Pandas 328 | Series, a Pandas Data Frame is returned. 329 | ''' 330 | x, is_pandas = converters.to_ts(x) 331 | h = _get_horizon(x, h) 332 | kwargs = converters.translate_kwargs(**kwargs) 333 | out = fc.forecast(x, h=h, **kwargs) 334 | return converters.forecast_out(out, is_pandas) 335 | 336 | 337 | def ets(x, h=None, model_spec='ZZZ', damped=NULL, alpha=NULL, 338 | beta=NULL, gamma=NULL, phi=NULL, additive_only=False, lam=NULL, 339 | opt_crit='lik', nmse=3, ic='aicc', allow_multiplicative_trend=False, 340 | level=(80, 95)): 341 | ''' 342 | Automatically select and fit an exponential smoothing model on the 343 | provided data using the ets() function from the R Forecast package, 344 | and use it to produce a forecast over the given horizon. 345 | 346 | Args: 347 | x: an R time series, obtained from converters.ts(), or a Pandas Series 348 | with the correct index (e.g. from converters.sequence_as_series(). 349 | h: Forecast horizon; default is 2 full periods of a periodic series, 350 | or 10 steps for non-seasonal series. 351 | model_spec : Default is 'ZZZ'. A 3-letter string denoting the model type. 352 | Letters denote error, trend, and seasonal parts: A=additive, 353 | N=none, M=multiplicative, Z=automatically selected. Legal 354 | values for first part are (A, M, Z), all values are legal 355 | for other parts. 356 | damped : If True, use a damped trend model. 357 | Default is NULL, which tries damped/undamped models and 358 | selects best model according to the selected ic. 359 | alpha : Smoothing parameter for error term. 360 | Default is NULL, which fits this value. 361 | beta : Smoothing paramter for trend component. 362 | Default is NULL, which fits this value. 363 | gamma : Smoothing parameter for seasonal component. 364 | Default is NULL, which fits this value. 365 | phi : Damping parameter. Default is NULL, which fits this value. 366 | additive_only : Default False. If True, only try additive models. 367 | lam : BoxCox transformation parameter. The default is R's NULL value. 368 | If NULL, no transformation is applied. Otherwise, a Box-Cox 369 | transformation is applied before forecasting and inverted after. 370 | opt_crit : Optimization criterion. Default is 'lik' for log-likelihood. 371 | Other values are 'mse' (mean squared error), 'amse' (MSE averaged 372 | over first nmse forecast horizons), 'sigma' (standard deviation of 373 | residuals), and 'mae' (mean absolute error). 374 | nmse : number of steps in average MSE, if 'amse' is opt_crit. 375 | Restricted to 1 <= nmse <= 10. 376 | ic : information crierion. Default is 'aicc' for bias-corrected AIC. 377 | Other values are 'aic' for regular AIC, or 'bic' for BIC. 378 | allow_multiplicative_trend : Default is False. If True, consider models 379 | with a multiplicative trend component. That type of model may grow 380 | explosively. 381 | level : A number or list/tuple of prediction interval confidence values. 382 | Default is 80% and 95% intervals. 383 | 384 | Returns: 385 | If x is an R ts object, an R forecast is returned. If x is a Pandas 386 | Series, a Pandas Data Frame is returned. 387 | ''' 388 | x, is_pandas = converters.to_ts(x) 389 | kwargs = {'allow.multiplicative.trend' : allow_multiplicative_trend, 390 | 'additive.only' : additive_only, 391 | 'opt.crit' : opt_crit, 392 | 'lambda' : lam} 393 | ets_model = fc.ets(x, model=model_spec, damped=damped, alpha=alpha, 394 | beta=beta, gamma=gamma, phi=phi, ic=ic, **kwargs) 395 | h = _get_horizon(x, h) 396 | level = converters.map_arg(level) 397 | # NB: default lambda is correct - it will be taken from model 398 | out = fc.forecast_ets(ets_model, h, level=level) 399 | return converters.forecast_out(out, is_pandas) 400 | 401 | 402 | def arima(x, h=None, level=(80,95), order=(0,0,0), seasonal=(0,0,0), 403 | lam=NULL, **kwargs): 404 | ''' 405 | Generates a forecast from an arima model with a fixed specification. 406 | For an arima model with an optimized specification, use auto.arima. 407 | Keyword arguments are allowed. Some common ones are noted below. 408 | 409 | Args: 410 | x: an R time series, obtained from converters.ts(), or a Pandas Series 411 | with the correct index (e.g. from converters.sequence_as_series(). 412 | h: the forecast horizon, default 10 if fitting a non-seasonal model, 413 | 2 * the frequency of the series for seasonal models. 414 | level: A number or list/tuple of prediction interval confidence values. 415 | Default is 80% and 95% intervals. 416 | order: the non-seasonal part of the arima model 417 | seasonal: the seasonal part of the arima model 418 | lam: BoxCox transformation parameter. The default is R's NULL value. 419 | If NULL, no transformation is applied. Otherwise, a Box-Cox 420 | transformation is applied before forecasting and inverted after. 421 | 422 | Keyword Args: 423 | include_drift: Default False. If True, the model includes a linear 424 | drift term 425 | include_mean: Should the model allow a non-zero mean term? 426 | Default is True if series is undifferenced, False otherwise 427 | include_constant: If True, include mean if series is not differenced, 428 | or include drift if it is differenced once. 429 | 430 | Returns: 431 | If x is an R ts object, an R forecast is returned. If x is a Pandas 432 | Series, a Pandas Data Frame is returned. 433 | ''' 434 | x, is_pandas = converters.to_ts(x) 435 | if h is None: 436 | if seasonal == (0,0,0): 437 | h = 10 438 | else: 439 | h = 2 * frequency(x) 440 | level = converters.map_arg(level) 441 | order = converters.map_arg(order) 442 | seasonal = converters.map_arg(seasonal) 443 | kwargs['lambda'] = lam 444 | model = fc.Arima(x, order=order, seasonal=seasonal, **kwargs) 445 | out = fc.forecast(model, h=h, level=level) 446 | return converters.forecast_out(out, is_pandas) 447 | 448 | 449 | # TODO: convert xreg and newxreg if needed 450 | def auto_arima(x, h=None, d=NA, D=NA, max_p=5, max_q=5, max_P=2, max_Q=2, 451 | max_order=5, max_d=2, max_D=1, start_p=2, start_q=2, 452 | start_P=1, start_Q=1, stationary=False, seasonal=True, 453 | ic='aicc', xreg=NULL, newxreg=NULL, test='kpss', 454 | seasonal_test='ocsb', lam=NULL, level=(80, 95)): 455 | ''' 456 | Use the auto.arima function from the R Forecast package to automatically 457 | select an arima model order, fit the model to the provided data, and 458 | generate a forecast. 459 | 460 | Args: 461 | x: an R time series, obtained from converters.ts(), or a Pandas Series 462 | with the correct index (e.g. from converters.sequence_as_series(). 463 | h : Forecast horizon; default is 2 full periods of a periodic series, 464 | or 10 steps for non-seasonal series. 465 | d : order of first differencing. Default is NA, which selects this 466 | value based on the value of 'test' (KPSS test by default). 467 | D : order of seasonal differencing. Default is NA, which selects this 468 | value based on 'seasonal_test' (OCSB test by default). 469 | max_p : maximum value for non-seasonal AR order 470 | max_q : maximum value for non-seasonal MA order 471 | max_P : maximum value for seasonal AR order 472 | max_Q : maximum value for seasonal MA order 473 | max_order : maximum value of p + q + P + Q 474 | start_p : starting value for non-seasonal AR order 475 | start_q : starting value for non-seasonal MA order 476 | start_P : starting value for seasonal AR order 477 | start_Q : starting value for seasonal MA order 478 | stationary : Default is False. If True, only consider stationary models. 479 | seasonal : Default is True. If False, only consider non-seasonal models. 480 | ic : information crierion. Default is 'aicc' for bias-corrected AIC. 481 | Other values are 'aic' for regular AIC, or 'bic' for BIC. 482 | xreg : An optional vector or matrix of regressors, which must have one 483 | row/element for each point in x. Default is NULL, for no regressors. 484 | newxreg : If regressors were used to fit the model, then they must be 485 | supplied for the forecast period as newxreg. If newxreg is present, 486 | h is ignored. 487 | test : Test to use to determine number of first differences. Default 488 | is 'kpss', for the KPSS test. Other values are 'adf' for augmented 489 | Dickey-Fuller, or 'pp' for Phillips-Perron. 490 | seasonal_test : Test to use to determine number of seasonal differences. 491 | Default is 'ocsb' for the Osborn-Chui-Smith-Birchenhall test. 492 | The alternative is 'ch' for the Canova-Hansen test. 493 | lam : BoxCox transformation parameter. The default is R's NULL value. 494 | If NULL, no transformation is applied. Otherwise, a Box-Cox 495 | transformation is applied before forecasting and inverted after. 496 | level : A number or list/tuple of prediction interval confidence values. 497 | Default is 80% and 95% intervals. 498 | 499 | Returns: 500 | If x is an R ts object, an R forecast is returned. If x is a Pandas 501 | Series, a Pandas Data Frame is returned. 502 | ''' 503 | x, is_pandas = converters.to_ts(x) 504 | kwargs = {'max.p' : max_p, 'max.q' : max_q, 'max.P' : max_P, 505 | 'max.Q' : max_Q, 'max.order' : max_order, 'max.d' : max_d, 506 | 'max.D' : max_D, 'start.p' : start_p, 'start.q' : start_q, 507 | 'start.P' : start_P, 'start.Q' : start_Q, 508 | 'seasonal.test' : seasonal_test, 'lambda' : lam} 509 | if (xreg is NULL) != (newxreg is NULL): 510 | raise ValueError( 511 | 'Specifiy both xreg and newxreg or neither.') 512 | if xreg is not NULL: 513 | xreg = converters.as_matrix(xreg) 514 | newxreg = converters.as_matrix(newxreg) 515 | arima_model = fc.auto_arima(x, d=d, D=D, stationary=stationary, 516 | seasonal=seasonal, ic=ic, xreg=xreg, 517 | test=test, **kwargs) 518 | h = _get_horizon(x, h) 519 | level = converters.map_arg(level) 520 | # NB: default lambda is correct - it will be taken from model 521 | out = fc.forecast_Arima(arima_model, h, level=level, xreg=newxreg) 522 | return converters.forecast_out(out, is_pandas) 523 | 524 | 525 | def stlf(x, h=None, s_window=7, robust=False, lam=NULL, method='ets', 526 | etsmodel='ZZZ', xreg=NULL, newxreg=NULL, level=(80, 95)): 527 | ''' 528 | Constructs a forecast of a seasonal time series by seasonally decomposing 529 | it using an STL decomposition, then making a non-seasonal forecast on the 530 | seasonally adjusted data, and finally adding the naively extended seasonal 531 | component on to the forecast. 532 | 533 | Args: 534 | x: an R time series, obtained from converters.ts(), or a Pandas Series 535 | with the correct index (e.g. from converters.sequence_as_series(). 536 | For this forecast method, x should be seasonal. 537 | h : Forecast horizon; default is 2 full periods of a periodic series 538 | s.window : either 'periodic' or the span (in lags) of the 539 | loess window for seasonal extraction, which should be odd. 540 | robust : If True, use robust fitting in the loess procedure. 541 | lam : BoxCox transformation parameter. The default is R's NULL value. 542 | If NULL, no transformation is applied. Otherwise, a Box-Cox 543 | transformation is applied before forecasting and inverted after. 544 | method : One of 'ets' or 'arima'; default is 'ets'. Specifies the type 545 | of model to use for forecasting the non-seasonal part. 546 | etsmodel : Default is 'ZZZ'. This is only used if 'method' is 'ets'. 547 | A 3-letter string denoting the ets model type. 548 | Letters denote error, trend, and seasonal parts: A=additive, 549 | N=none, M=multiplicative, Z=automatically selected. Legal 550 | values for first part are (A, M, Z), all values are legal 551 | for other parts. 552 | xreg : Only available if 'method' is arima. An optional vector or matrix 553 | of regressors, which must have one row/element for each point in x. 554 | Default is NULL, for no regressors. 555 | newxreg : Only available if 'method' is arima. If regressors are used in 556 | fitting, then they must be supplied for the forecast period as newxreg. 557 | level : A number or list/tuple of prediction interval confidence values. 558 | Default is 80% and 95% intervals. 559 | 560 | Returns: 561 | If x is an R ts object, an R forecast is returned. If x is a Pandas 562 | Series, a Pandas Data Frame is returned. 563 | ''' 564 | x, is_pandas = converters.to_ts(x) 565 | h = _get_horizon(x, h) 566 | kwargs = {'s.window' : s_window, 567 | 'lambda' : lam} 568 | level = converters.map_arg(level) 569 | out = fc.stlf(x, h, level=level, robust=robust, method=method, 570 | etsmodel=etsmodel, xreg=xreg, newxreg=newxreg, **kwargs) 571 | return converters.forecast_out(out, is_pandas) 572 | 573 | 574 | def stl(x, s_window, **kwargs): 575 | ''' 576 | Perform a decomposition of the time series x into seasonal, trend and 577 | remainder components using loess. Most of the arguments listed below are 578 | in **kwargs, and all of those arguments have sensible defaults. Usually 579 | only the mandatory s_window paramter has to be set. 580 | 581 | Args: 582 | x: an R time series, obtained from converters.ts(), or a Pandas Series 583 | with the correct index (e.g. from converters.sequence_as_series(). 584 | s_window : either 'periodic' or the span (in lags) of the 585 | loess window for seasonal extraction, which should be odd. 586 | This has no default. 587 | s_degree : Default 0, should be 0 or 1. Degree of local polynomial 588 | for seasonal extraction. 589 | t_window : The span (in lags) of the loess window for trend extraction, 590 | which should be odd. Default is a sensible, data-dependent value. 591 | See the R docs for the details. 592 | t_degree : Default 0, should be 0 or 1. Degree of local polynomial 593 | for trend extraction. 594 | l_window : Span in lags of the loess window used to low-pass filter each 595 | seasonal subseries. The default is first odd number greater than or 596 | equal to frequency, which is recommmended. 597 | s_jump, t_jump, l_jump : integer parameters (min. 1) to increase speed of 598 | each smoother by skipping data points. 599 | l_degree : Default is t.window, must be 0 or 1. Degree of local polynomial 600 | for subseries low-pass filter. 601 | robust : Default is False. If True, robust loess fitting used. 602 | inner : number of backfitting iterations 603 | outer : number of outer robustness iterations 604 | na_action : Default is na.fail, which means that the user has to fill or 605 | remove any missing values. If used, it must be an object that maps to 606 | an R function, obtained from rpy2. 607 | 608 | Returns: 609 | If x is an R ts object, an R object of class 'stl' is returned. 610 | If x is a Pandas Series, a Pandas Data Frame is returned. 611 | ''' 612 | x, is_pandas = converters.to_ts(x) 613 | kwargs['s.window'] = s_window 614 | kwargs = converters.translate_kwargs(**kwargs) 615 | out = stats.stl(x, **kwargs) 616 | return converters.decomposition_out(out, is_pandas) 617 | 618 | 619 | def decompose(x, type='additive'): 620 | ''' 621 | Performs a classical seasonal decomposition of a time series into 622 | season, trend and remainder components. 623 | 624 | Args: 625 | x: an R time series, obtained from converters.ts(), or a Pandas Series 626 | with the correct index (e.g. from converters.sequence_as_series(). 627 | The series should be seasonal. 628 | type: Type of seasonal decomposition to perform. 629 | Default is 'additive', other option is 'multiplicative'. 630 | 631 | Returns: 632 | If x is an R ts object, an R object of class 'decomposed.ts' is returned. 633 | If x is a Pandas Series, a Pandas Data Frame is returned. 634 | ''' 635 | x, is_pandas = converters.to_ts(x) 636 | out = stats.decompose(x, type=type) 637 | return converters.decomposition_out(out, is_pandas) 638 | 639 | 640 | def seasadj(decomp): 641 | ''' 642 | Return a seasonally adjusted version of the origin time series that 643 | was seasonally decomposed to get decomp. 644 | 645 | Args: 646 | decomp: an R seasonal decomposition from stl or decompose, 647 | or a Pandas data frame containing a seasonal decomposition. 648 | 649 | Returns: 650 | an object that maps an R time series of the seasonally adjusted 651 | values of the series that decomp was formed from 652 | ''' 653 | if validate.is_Pandas_decomposition(decomp): 654 | if decomp.trend.isnull().any(): 655 | # classical 656 | seasonal_mean = decomp.seasonal.mean(axis=0) 657 | if numpy.allclose(seasonal_mean, 1.0, atol=1e-5): 658 | # multiplicative 659 | return decomp.data / decomp.seasonal 660 | else: 661 | # additive 662 | return decomp.data - decomp.seasonal 663 | else: 664 | # stl decomposition 665 | return decomp.data - decomp.seasonal 666 | elif validate.is_R_decomposition(decomp): 667 | return fc.seasadj(decomp) 668 | else: 669 | raise ValueError('seasadj requires a seasonal decomposition as input') 670 | 671 | 672 | def sindexf(decomp, h): 673 | ''' 674 | Projects the seasonal component of a seasonal decomposition of a time series 675 | forward by h time steps into the future. 676 | 677 | Args: 678 | decomp: an R seasonal decomposition from stl or decompose, 679 | or a Pandas data frame containing a seasonal decomposition. 680 | h: a forecast horizon 681 | 682 | Returns: 683 | an object that maps to am R time series containing the seasonal component 684 | of decomp, projected naively forward h steps. 685 | ''' 686 | if validate.is_Pandas_decomposition(decomp): 687 | freq = len(decomp.index.levels[1]) 688 | seasonal = list(decomp.seasonal[-freq:]) 689 | it = itertools.cycle(seasonal) 690 | data = [] 691 | for k in range(h): 692 | data.append(it.next()) 693 | data = [0] + data 694 | start = decomp.seasonal.last_valid_index() 695 | series = converters.sequence_as_series(data, start=start, freq=freq) 696 | return series[1:] 697 | elif validate.is_R_decomposition(decomp): 698 | return fc.sindexf(decomp, h) 699 | else: 700 | raise ValueError('seasadj requires a seasonal decomposition as input') 701 | 702 | 703 | def BoxCox(x, lam): 704 | ''' 705 | Applies a Box-Cox transformation to the data in x. This can stabilize the 706 | variance of x, so that forecast model assumptions are more nearly satisfied. 707 | 708 | For x != 0, this is (x^lambda - 1) / lambda. 709 | For x = 0, it is log(x). 710 | 711 | Args: 712 | x: an R time series, obtained from converters.ts(), or a Pandas Series 713 | with the correct index (e.g. from converters.sequence_as_series(). 714 | lam: BoxCox transformation parameter. 715 | 716 | Returns: 717 | If x is an R ts object, an R time series is returned. 718 | If x is a Pandas Series, a Pandas Series is returned. 719 | ''' 720 | x, is_pandas = converters.to_ts(x) 721 | out = fc.BoxCox(x, **{'lambda' : lam}) 722 | return converters.series_out(out, is_pandas) 723 | 724 | 725 | def InvBoxCox(x, lam): 726 | ''' 727 | Invert a BoxCox transformation. The return value is a timeseries with 728 | values of x transformed back to the original scale 729 | 730 | Args: 731 | x: an R time series, obtained from converters.ts(), or a Pandas Series 732 | with the correct index (e.g. from converters.sequence_as_series(). 733 | Its values that should be on the scale of a BoxCox transformation 734 | with parameter lambda=lam. 735 | lam: BoxCox transformation parameter. 736 | 737 | Returns: 738 | If x is an R ts object, an R time series is returned. 739 | If x is a Pandas Series, a Pandas Series is returned. 740 | ''' 741 | x, is_pandas = converters.to_ts(x) 742 | out = fc.InvBoxCox(x, **{'lambda' : lam}) 743 | return converters.series_out(out, is_pandas) 744 | 745 | 746 | def BoxCox_lambda(x, method='guerrero', lower=-1, upper=2): 747 | ''' 748 | Function to find a good value of the BoxCox transformation parameter, lambda. 749 | 750 | Args: 751 | x: an R time series, obtained from converters.ts(), or a Pandas Series 752 | with the correct index (e.g. from converters.sequence_as_series(). 753 | method: Method of calculating lambda. 754 | Default is 'guerrero', other option is 'lik' for log-likelihood. 755 | upper: Upper limit of possible lambda values, default 2. 756 | lower: Lower limit of possible lambda values, default -1. 757 | 758 | Returns: 759 | value of lambda for the series x, as calculated by the selected method 760 | ''' 761 | x, _ = converters.to_ts(x) 762 | return fc.BoxCox_lambda(x, method=method, lower=lower, upper=upper)[0] 763 | 764 | 765 | def na_interp(x, lam=NULL): 766 | ''' 767 | Funtction for interpolating missing values in R time series. This function 768 | uses linear interpolation for non-seasonal data. For seasonal data, it 769 | uses an STL decomposition, imputing the seasonal value. 770 | 771 | Args: 772 | x: an R time series, obtained from converters.ts(), or a Pandas Series 773 | with the correct index (e.g. from converters.sequence_as_series(). 774 | If lam is used, its values should be on the scale of a BoxCox 775 | transformation with parameter lambda=lam. 776 | lam: BoxCox transformation parameter. The default is R's NULL value. 777 | If NULL, no transformation is applied. Otherwise, a Box-Cox 778 | transformation is applied before forecasting and inverted after. 779 | 780 | Returns: 781 | If x is an R ts object, an R time series is returned. 782 | If x is a Pandas Series, a Pandas Series is returned. 783 | In either case, missing values are filled. 784 | ''' 785 | x, is_pandas = converters.to_ts(x) 786 | out = fc.na_interp(x, **{'lambda' : lam}) 787 | return converters.series_out(out, is_pandas) 788 | 789 | 790 | def accuracy(result, x=None, **kwargs): 791 | ''' 792 | Computes an R matrix of forecast accuracy measures. Must take an R forecast 793 | object for input, since the residuals are not included in the Pandas 794 | output from forecast functions. One step-ahead errors are computed over the 795 | training data. Optionally, test data (x) can be included, in which case the 796 | error measures are evaluated over the test set. 797 | 798 | The accuracy measures used are: 799 | * Mean Error (ME) 800 | * Root Mean Squared Error (RMSE) 801 | * Mean Absolute Error (MAE) 802 | * Mean Percentage Error (MPE) 803 | * Mean Absolute Percentage Error (MAPE) 804 | * Mean Absolute Scaled Error (MASE) 805 | * Autocorrelatino of Errors at Lag 1 (ACF1) 806 | * Theil's U (only if x provided) 807 | 808 | Args: 809 | result: an R forecast object 810 | x: optional R vector of true values for the forecast (test data) 811 | d: Number of first differences taken in forecast, default is none. 812 | D: Number of seasonal differences taken in forecast, default is none. 813 | 814 | Returns: 815 | An R list of forecast accuracy measures. 816 | Use extractors.accuracy to get a Pandas DataFrame. 817 | ''' 818 | if x is not None: 819 | kwargs['x'] = x 820 | return fc.accuracy(result, **kwargs) 821 | 822 | 823 | def tsclean(x, **kwargs): 824 | ''' 825 | Identify and replace outliers. Uses loess for non-seasonal series and 826 | an STL decomposition for seasonal series. Optionally fills missing values. 827 | 828 | Args: 829 | x: an R time series, obtained from converters.ts(), or a Pandas Series 830 | with the correct index (e.g. from converters.sequence_as_series(). 831 | replace_missing: Default True. 832 | If True, use na_interp to fill missing values in x. 833 | lam: optional BoxCox transformation parameter. 834 | 835 | Returns: 836 | If x is an R ts object, an R time series is returned. If x is a Pandas 837 | Series, a Pandas Series is returned. In either case, outliers are replaced 838 | and optionally, missing values are filled. 839 | ''' 840 | x, is_pandas = converters.to_ts(x) 841 | kwargs = converters.translate_kwargs(**kwargs) 842 | out = fc.tsclean(x, **kwargs) 843 | return converters.series_out(out, is_pandas) 844 | 845 | 846 | def findfrequency(x): 847 | ''' 848 | Performs spectral analysis of x to find the dominant frequency, if there 849 | is one. 850 | 851 | Args: 852 | x: an R time series or a Pandas Series 853 | 854 | Returns: 855 | The dominant frequency in x, or 1 if there isn't one. 856 | ''' 857 | x, _ = converters.to_ts(x) 858 | return fc.findfrequency(x)[0] 859 | 860 | 861 | def ndiffs(x, **kwargs): 862 | ''' 863 | Estimates the number of first differences (non-seasonal) to take on the 864 | time series, x, to reach stationarity. 865 | 866 | Args: 867 | x: an R time series or a Pandas Series 868 | alpha: Default 0.05, the level of the test used 869 | test : Test to use to determine number of first differences. Default 870 | is 'kpss', for the KPSS test. Other values are 'adf' for augmented 871 | Dickey-Fuller, or 'pp' for Phillips-Perron. 872 | max_d: max number of differences to try. Default is 2. 873 | 874 | Returns: 875 | The number of differences to take 876 | ''' 877 | x, _ = converters.to_ts(x) 878 | kwargs = converters.translate_kwargs(**kwargs) 879 | return fc.ndiffs(x, **kwargs)[0] 880 | 881 | 882 | def nsdiffs(x, **kwargs): 883 | ''' 884 | Estimates the number of seasonal differences to take on the time series, 885 | x, to reach stationarity. For this function, x must be a seasonal series. 886 | 887 | Args: 888 | x: an R time series or a Pandas Series 889 | m: Seasonal period. Default is frequency(x). No other value makes sense. 890 | test : Test to use to determine number of seasonal differences. 891 | Default is 'ocsb' for the Osborn-Chui-Smith-Birchenhall test. 892 | The alternative is 'ch' for the Canova-Hansen test. 893 | max_D: Maximum number of seasonal differences to try. Default is 1. 894 | 895 | Returns: 896 | The number of seasonal differences to take 897 | ''' 898 | x, _ = converters.to_ts(x) 899 | kwargs = converters.translate_kwargs(**kwargs) 900 | return fc.nsdiffs(x, **kwargs)[0] 901 | 902 | 903 | def acf(x, lag_max=NULL): 904 | ''' 905 | Function computes the autocorrelation of a univariate time series. 906 | 907 | Args: 908 | x: an R time series or a Pandas Series 909 | lag_max: The maximum number of lags to use. The default is NULL, which 910 | uses a formula for the number of lags that should get a sensible value. 911 | 912 | Returns: 913 | The autocorrelation for all lags up to lag_max, either as a Pandas Series, 914 | or as an R object. 915 | ''' 916 | x, is_pandas = converters.to_ts(x) 917 | kwargs = {'lag.max' : lag_max} 918 | out = fc.Acf(x, plot=False, **kwargs) 919 | return converters.acf_out(out, is_pandas) 920 | 921 | 922 | def pacf(x, lag_max=NULL): 923 | ''' 924 | Function computes the partial autocorrelation of a univariate time series. 925 | 926 | Args: 927 | x: an R time series or a Pandas Series 928 | lag_max: The maximum number of lags to use. The default is NULL, which 929 | uses a formula for the number of lags that should get a sensible value. 930 | 931 | Returns: 932 | The partial autocorrelation for all lags up to lag_max, either as a 933 | Pandas Series, or as an R object. 934 | ''' 935 | x, is_pandas = converters.to_ts(x) 936 | kwargs = {'lag.max' : lag_max} 937 | out = fc.Pacf(x, plot=False, **kwargs) 938 | return converters.acf_out(out, is_pandas) 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name = 'rforecast', 5 | version = '0.1', 6 | author = 'David Thaler', 7 | author_email = 'davidathaler@gmail.com', 8 | url = 'https://github.com/davidthaler/Python-wrapper-for-R-Forecast', 9 | description = 'A python wrapper to the R forecast package for time series forecasting', 10 | license = 'GPL2', 11 | packages = ['rforecast'] 12 | ) -------------------------------------------------------------------------------- /test/test_all.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from rforecast import wrappers 3 | from rforecast import converters 4 | from rforecast import ts_io 5 | from rpy2 import robjects 6 | from rpy2.robjects.packages import importr 7 | 8 | 9 | class EndToEndTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.oil_r = ts_io.read_ts('oil', 'fpp', as_pandas=False) 13 | self.oil_py = converters.ts_as_series(self.oil_r) 14 | self.aus_r = ts_io.read_ts('austourists', 'fpp', as_pandas=False) 15 | self.aus_py = converters.ts_as_series(self.aus_r) 16 | self.austa_r = ts_io.read_ts('austa', 'fpp', as_pandas=False) 17 | self.austa_py = converters.ts_as_series(self.austa_r) 18 | self.fc = importr('forecast') 19 | 20 | def _check_points(self, fc_py, fc_r): 21 | ''' 22 | Checks that the R and python forecasts are the same at select points 23 | for both the mean forecast and the prediction intervals. Compares the 24 | first and last values of the mean forecast, and the first value of the 25 | 80% confidence lower PI and the last value of the 95% upper PI. 26 | 27 | Args: 28 | fc_py: the python forecast 29 | fc_r : the R forecast 30 | 31 | Return: 32 | Nothing, but makes tests assertions which can fail. 33 | ''' 34 | lower = fc_r.rx2('lower') 35 | upper = fc_r.rx2('upper') 36 | mean = fc_r.rx2('mean') 37 | self.assertAlmostEqual(fc_py.point_fc.iloc[0], mean[0], places=3) 38 | self.assertAlmostEqual(fc_py.point_fc.iloc[-1], mean[-1], places=3) 39 | self.assertAlmostEqual(fc_py.lower80.iloc[0], lower[0], places=3) 40 | self.assertAlmostEqual(fc_py.upper95.iloc[-1], upper[-1], places=3) 41 | 42 | def test_naive(self): 43 | fc_py = wrappers.naive(self.oil_py) 44 | fc_r = self.fc.naive(self.oil_r) 45 | self._check_points(fc_py, fc_r) 46 | 47 | def test_thetaf(self): 48 | fc_py = wrappers.thetaf(self.oil_py) 49 | fc_r = self.fc.thetaf(self.oil_r) 50 | self._check_points(fc_py, fc_r) 51 | 52 | def test_snaive(self): 53 | fc_py = wrappers.snaive(self.aus_py) 54 | fc_r = self.fc.snaive(self.aus_r) 55 | self._check_points(fc_py, fc_r) 56 | 57 | def test_rwf(self): 58 | fc_py = wrappers.rwf(self.oil_py) 59 | fc_r = self.fc.rwf(self.oil_r) 60 | self._check_points(fc_py, fc_r) 61 | 62 | def test_forecast_nonseasonal(self): 63 | fc_py = wrappers.forecast(self.oil_py) 64 | fc_r = self.fc.forecast(self.oil_r) 65 | self._check_points(fc_py, fc_r) 66 | 67 | def test_forecast_seasonal(self): 68 | fc_py = wrappers.forecast(self.aus_py) 69 | fc_r = self.fc.forecast(self.aus_r) 70 | self._check_points(fc_py, fc_r) 71 | 72 | def test_auto_arima_nonseasonal(self): 73 | fc_py = wrappers.auto_arima(self.oil_py) 74 | model = self.fc.auto_arima(self.oil_r) 75 | fc_r = self.fc.forecast(model) 76 | self._check_points(fc_py, fc_r) 77 | 78 | def test_auto_arima_seasonal(self): 79 | fc_py = wrappers.auto_arima(self.aus_py) 80 | model = self.fc.auto_arima(self.aus_r) 81 | fc_r = self.fc.forecast(model) 82 | self._check_points(fc_py, fc_r) 83 | 84 | def test_auto_arima_raises(self): 85 | self.assertRaises(ValueError, wrappers.auto_arima, self.oil_py , 86 | xreg=range(len(self.oil_py))) 87 | self.assertRaises(ValueError, wrappers.auto_arima, self.oil_py, h=10, 88 | newxreg=range(10)) 89 | 90 | def test_stlf(self): 91 | fc_py = wrappers.stlf(self.aus_py) 92 | fc_r = self.fc.stlf(self.aus_r) 93 | self._check_points(fc_py, fc_r) 94 | 95 | def test_acf(self): 96 | acf_py = wrappers.acf(self.oil_py, lag_max=10) 97 | self.assertEqual(acf_py.name, 'Acf') 98 | self.assertEqual(len(acf_py), 10) 99 | acf_r = self.fc.Acf(self.oil_r, plot=False, lag_max=10) 100 | self.assertAlmostEqual(acf_py[1], acf_r.rx2('acf')[1], places=3) 101 | self.assertAlmostEqual(acf_py[10], acf_r.rx2('acf')[10], places=3) 102 | 103 | def test_pacf(self): 104 | pacf_py = wrappers.pacf(self.oil_py, lag_max=10) 105 | self.assertEqual(pacf_py.name, 'Pacf') 106 | self.assertEqual(len(pacf_py), 10) 107 | pacf_r = self.fc.Pacf(self.oil_r, plot=False, **{'lag.max':10}) 108 | self.assertAlmostEqual(pacf_py.values[0], pacf_r.rx2('acf')[0], places=3) 109 | self.assertAlmostEqual(pacf_py.values[-1], pacf_r.rx2('acf')[-1], places=3) 110 | 111 | def test_ses(self): 112 | fc_py = wrappers.ses(self.oil_py) 113 | fc_r = self.fc.ses(self.oil_r, initial='simple') 114 | self._check_points(fc_py, fc_r) 115 | 116 | def test_ses_raises(self): 117 | self.assertRaises(ValueError, wrappers.ses, self.oil_py, alpha=0) 118 | self.assertRaises(ValueError, wrappers.ses, self.oil_py, alpha=1.0) 119 | 120 | def test_holt(self): 121 | fc_py = wrappers.holt(self.austa_py) 122 | fc_r = self.fc.holt(self.austa_r, initial='simple') 123 | self._check_points(fc_py, fc_r) 124 | 125 | def test_holt_raises(self): 126 | self.assertRaises(ValueError, wrappers.holt, self.austa_py, alpha=0) 127 | self.assertRaises(ValueError, wrappers.holt, self.austa_py, alpha=1.0) 128 | self.assertRaises(ValueError, wrappers.holt, self.austa_py, beta=0) 129 | self.assertRaises(ValueError, wrappers.holt, self.austa_py, beta=1.0) 130 | 131 | def test_hw(self): 132 | fc_py = wrappers.hw(self.aus_py) 133 | fc_r = self.fc.hw(self.aus_r, initial='simple') 134 | self._check_points(fc_py, fc_r) 135 | 136 | def test_hw_raises(self): 137 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, alpha=0) 138 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, alpha=1.0) 139 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, beta=0) 140 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, beta=1.0) 141 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, gamma=0) 142 | self.assertRaises(ValueError, wrappers.hw, self.aus_py, gamma=1.0) 143 | 144 | def test_arima_seasonal(self): 145 | fc_py = wrappers.arima(self.aus_py, order=(1,0,0), seasonal=(1,1,0), 146 | include_constant=True) 147 | order = robjects.r.c(1., 0., 0.) 148 | seasonal = robjects.r.c(1., 1., 0.) 149 | model = self.fc.Arima(self.aus_r, order=order, seasonal=seasonal, 150 | include_constant=True) 151 | fc_r = self.fc.forecast(model) 152 | self._check_points(fc_py, fc_r) 153 | self.assertEqual(fc_py.shape[0], 8) 154 | 155 | def test_arima_nonseasonal(self): 156 | fc_py = wrappers.arima(self.oil_py, order=(0,1,0)) 157 | order = robjects.r.c(0., 1., 0.) 158 | model = self.fc.Arima(self.oil_r, order=order) 159 | fc_r = self.fc.forecast(model) 160 | self._check_points(fc_py, fc_r) 161 | self.assertEqual(fc_py.shape[0], 10) 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /test/test_converters.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from rforecast import wrappers, converters, ts_io 3 | from rpy2 import robjects 4 | from rpy2.robjects.packages import importr 5 | import pandas 6 | import numpy 7 | 8 | 9 | class ConvertersTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | importr('fpp') 13 | self.oil_ts = robjects.r('oil') 14 | self.aus_ts = robjects.r('austourists') 15 | self.fc_oil = wrappers.meanf(self.oil_ts) 16 | self.fc_aus = wrappers.ets(self.aus_ts) 17 | self.oil = ts_io.read_series('data/oil.csv') 18 | self.aus = ts_io.read_series('data/aus.csv') 19 | self.data = [0.74, 0.42, 0.22, 0.04, 0.17, 0.37, 20 | 0.53, 0.32, 0.82, 0.81, 0.11, 0.79] 21 | self.npdata = numpy.array(self.data) 22 | 23 | 24 | def test_flatten_index(self): 25 | idx = converters.flatten_index(self.aus.index) 26 | self.assertEqual(len(idx), len(self.aus)) 27 | self.assertEqual(idx.nlevels, 1) 28 | self.assertEqual(idx[0], 1999.0) 29 | self.assertEqual(idx[-1], 2010.75) 30 | 31 | 32 | def test_translate_kwargs(self): 33 | self.assertEquals(converters.translate_kwargs(lam=1), {'lambda' : 1}) 34 | arg = converters.translate_kwargs(levels=(80, 95)) 35 | self.assertEquals(type(arg['levels']), robjects.vectors.IntVector) 36 | self.assertEquals(list(arg['levels']), [80, 95]) 37 | arg = converters.translate_kwargs(s_window=7) 38 | self.assertEquals(arg, {'s.window' : 7}) 39 | 40 | 41 | def test_map_arg(self): 42 | self.assertEqual(converters.map_arg(3), 3) 43 | arg = converters.map_arg((1, 2)) 44 | self.assertEqual(list(arg), [1, 2]) 45 | self.assertEqual(type(arg), robjects.vectors.IntVector) 46 | 47 | 48 | def test_sequence_as_series(self): 49 | loil = list(self.oil_ts) 50 | laus = list(self.aus_ts) 51 | oil = converters.sequence_as_series(loil, start=1965) 52 | aus = converters.sequence_as_series(laus, start=(1999, 1), freq=4) 53 | self.assertEqual(oil.shape, (46,)) 54 | self.assertEqual(oil.index.nlevels, 1) 55 | self.assertEqual(oil.index[0], 1965) 56 | self.assertEqual(oil.index[-1], 2010) 57 | self.assertAlmostEqual(oil[1965], 111.00, places=1) 58 | self.assertAlmostEqual(oil[2010], 467.77, places=1) 59 | 60 | self.assertEqual(aus.shape, (48, )) 61 | self.assertEqual(aus.index.nlevels, 2) 62 | self.assertEqual(aus.index[0], (1999, 1)) 63 | self.assertEqual(aus.index[-1], (2010, 4)) 64 | self.assertEqual(aus[2010].shape, (4,)) 65 | self.assertAlmostEqual(aus[(1999, 1)], 30.05, places=1) 66 | self.assertAlmostEqual(aus[(2010, 4)], 47.91, places=1) 67 | 68 | aus2 = converters.sequence_as_series(laus, start=1999, freq=4) 69 | self.assertTrue(aus2.equals(aus)) 70 | 71 | 72 | def test_series_as_ts(self): 73 | oil_ts = converters.series_as_ts(self.oil) 74 | self.assertTrue(type(oil_ts) is robjects.FloatVector) 75 | tsp = robjects.r('tsp')(oil_ts) 76 | self.assertAlmostEqual(tsp[0], 1965, places=1) 77 | self.assertAlmostEqual(tsp[1], 2010, places=1) 78 | self.assertAlmostEqual(tsp[2], 1, places=1) 79 | 80 | aus_ts = converters.series_as_ts(self.aus) 81 | self.assertTrue(type(aus_ts) is robjects.FloatVector) 82 | tsp = robjects.r('tsp')(aus_ts) 83 | self.assertAlmostEqual(tsp[0], 1999, places=2) 84 | self.assertAlmostEqual(tsp[1], 2010.75, places=2) 85 | self.assertAlmostEqual(tsp[2], 4, places=1) 86 | 87 | 88 | def test_matrix_list(self): 89 | # converters.matrix turns a list into a column matrix 90 | mat = converters.matrix(self.data) 91 | self.assertTrue(type(mat) is robjects.Matrix) 92 | self.assertListEqual(list(mat), self.data) 93 | self.assertTrue(self._check_dim(mat, 12, 1)) 94 | 95 | 96 | def test_matrix_array(self): 97 | data = numpy.array(self.data) 98 | mat = converters.matrix(data) 99 | self.assertTrue(type(mat) is robjects.Matrix) 100 | self.assertListEqual(list(mat), self.data) 101 | self.assertTrue(self._check_dim(mat, 12, 1)) 102 | 103 | # test 2D numpy array 104 | data = data.reshape((4, 3)) 105 | mat = converters.matrix(data) 106 | self.assertTrue(self._check_dim(mat, 4, 3)) 107 | 108 | 109 | def test_matrix_series(self): 110 | # converters.matrix turns a Pandas Series into a column matrix 111 | s = pandas.Series(self.data) 112 | mat = converters.matrix(s) 113 | self.assertTrue(type(mat) is robjects.Matrix) 114 | self.assertListEqual(list(mat), self.data) 115 | self.assertTrue(self._check_dim(mat, 12, 1)) 116 | 117 | 118 | def test_matrix_data_frame(self): 119 | data = self.npdata.reshape((4, 3)) 120 | df = pandas.DataFrame(data) 121 | mat = converters.matrix(df) 122 | self.assertTrue(self._check_dim(mat, 4, 3)) 123 | self.assertTrue( (numpy.array(mat) == df.values).all() ) 124 | 125 | 126 | def _check_dim(self, mat, nrows, ncols): 127 | n = nrows * ncols 128 | r, c = robjects.r('dim')(mat) 129 | if len(mat) == n and r == nrows and c == ncols: 130 | return True 131 | else: 132 | return False 133 | 134 | 135 | def test_get_index(self): 136 | oil_idx = converters._get_index(self.oil_ts) 137 | self.assertEqual(oil_idx, range(1965, 2011)) 138 | aus_idx = converters._get_index(self.aus_ts) 139 | self.assertEqual(len(aus_idx), 2) 140 | self.assertEqual(len(aus_idx[0]), 48) 141 | self.assertEqual(len(aus_idx[1]), 48) 142 | self.assertEqual(aus_idx[1], [1,2,3,4] * 12) 143 | 144 | 145 | def test_ts_as_series(self): 146 | oil = converters.ts_as_series(self.oil_ts) 147 | self.assertEqual(list(oil.index), range(1965, 2011)) 148 | self.assertAlmostEqual(oil[1965], 111.0091, places=3) 149 | self.assertAlmostEqual(oil[2010], 467.7724, places=3) 150 | aus = converters.ts_as_series(self.aus_ts) 151 | self.assertAlmostEqual(aus[(1999, 1)], 30.0525, places=3) 152 | self.assertAlmostEqual(aus[(2010, 4)], 47.9137, places=3) 153 | self.assertEqual(aus[2010].shape, (4, )) 154 | self.assertEqual(type(aus.index), pandas.core.index.MultiIndex) 155 | self.assertEqual(aus.index[0], (1999, 1)) 156 | self.assertEqual(aus.index[-1], (2010, 4)) 157 | 158 | 159 | def test_decomposition(self): 160 | dc = wrappers.stl(self.aus_ts, 7) 161 | dcdf = converters.decomposition(dc) 162 | self.assertEqual(type(dcdf.index), pandas.core.index.MultiIndex) 163 | self.assertEqual(dcdf.index[0], (1999, 1)) 164 | self.assertEqual(dcdf.index[-1], (2010, 4)) 165 | self.assertEqual(dcdf.shape, (48, 4)) 166 | self.assertEqual(list(dcdf.columns), 167 | [u'data', u'seasonal', u'trend', u'remainder']) 168 | self.assertAlmostEqual(dcdf.data[(1999, 1)], 169 | self.aus_ts.rx(1)[0], places=3) 170 | self.assertAlmostEqual(dcdf.data[(2010, 4)], 171 | self.aus_ts.rx(48)[0], places=3) 172 | self.assertAlmostEqual(dcdf.seasonal[(1999, 1)], 173 | dc.rx2('time.series').rx(1, 1)[0], places=3) 174 | self.assertAlmostEqual(dcdf.seasonal[(2010, 4)], 175 | dc.rx2('time.series').rx(48, 1)[0], places=3) 176 | self.assertAlmostEqual(dcdf.trend[(1999, 1)], 177 | dc.rx2('time.series').rx(1, 2)[0], places=3) 178 | self.assertAlmostEqual(dcdf.trend[(2010, 4)], 179 | dc.rx2('time.series').rx(48, 2)[0], places=3) 180 | self.assertAlmostEqual(dcdf.remainder[(1999, 1)], 181 | dc.rx2('time.series').rx(1, 3)[0], places=3) 182 | self.assertAlmostEqual(dcdf.remainder[(2010, 4)], 183 | dc.rx2('time.series').rx(48, 3)[0], places=3) 184 | 185 | dc = wrappers.decompose(self.aus_ts) 186 | dcdf = converters.decomposition(dc) 187 | self.assertEqual(type(dcdf.index), pandas.core.index.MultiIndex) 188 | self.assertEqual(dcdf.index[0], (1999, 1)) 189 | self.assertEqual(dcdf.index[-1], (2010, 4)) 190 | self.assertEqual(dcdf.shape, (48, 4)) 191 | self.assertEqual(list(dcdf.columns), 192 | [u'data', u'seasonal', u'trend', u'remainder']) 193 | self.assertAlmostEqual(dcdf.data[(1999, 1)], 194 | self.aus_ts.rx(1)[0], places=3) 195 | self.assertAlmostEqual(dcdf.data[(2010, 4)], 196 | self.aus_ts.rx(48)[0], places=3) 197 | self.assertAlmostEqual(dcdf.seasonal[(1999, 1)], 198 | dc.rx2('seasonal').rx(1)[0], places=3) 199 | self.assertAlmostEqual(dcdf.seasonal[(2010, 4)], 200 | dc.rx2('seasonal').rx(48)[0], places=3) 201 | self.assertAlmostEqual(dcdf.trend[(1999, 3)], 202 | dc.rx2('trend').rx(3)[0], places=3) 203 | self.assertAlmostEqual(dcdf.trend[(2010, 2)], 204 | dc.rx2('trend').rx(46)[0], places=3) 205 | self.assertTrue(dcdf.trend.isnull()[(1999, 1)]) 206 | self.assertTrue(dcdf.trend.isnull()[(1999, 2)]) 207 | self.assertTrue(dcdf.trend.isnull()[(2010, 3)]) 208 | self.assertTrue(dcdf.trend.isnull()[(2010, 4)]) 209 | self.assertAlmostEqual(dcdf.remainder[(1999, 3)], 210 | dc.rx2('random').rx(3)[0], places=3) 211 | self.assertAlmostEqual(dcdf.remainder[(2010, 2)], 212 | dc.rx2('random').rx(46)[0], places=3) 213 | self.assertTrue(dcdf.remainder.isnull()[(1999, 1)]) 214 | self.assertTrue(dcdf.remainder.isnull()[(1999, 2)]) 215 | self.assertTrue(dcdf.remainder.isnull()[(2010, 3)]) 216 | self.assertTrue(dcdf.remainder.isnull()[(2010, 4)]) 217 | self.assertRaises(ValueError, converters.decomposition, self.fc_oil) 218 | 219 | 220 | def test_prediction_intervals(self): 221 | pred = converters.prediction_intervals(self.fc_oil) 222 | self.assertEqual(pred.shape, (10, 5)) 223 | self.assertEqual(list(pred.index), range(2011, 2021)) 224 | self.assertEqual(list(pred.columns), [u'point_fc', u'lower80', 225 | u'upper80', u'lower95', u'upper95']) 226 | self.assertAlmostEqual(pred.point_fc[2011], 227 | self.fc_oil.rx2('mean').rx(1)[0], places=3) 228 | self.assertAlmostEqual(pred.point_fc[2020], 229 | self.fc_oil.rx2('mean').rx(10)[0], places=3) 230 | lower = self.fc_oil.rx2('lower') 231 | upper = self.fc_oil.rx2('upper') 232 | self.assertAlmostEqual(pred.lower80[2011], lower.rx(1, 1)[0], places=3) 233 | self.assertAlmostEqual(pred.lower80[2020], lower.rx(10, 1)[0], places=3) 234 | self.assertAlmostEqual(pred.upper80[2011], upper.rx(1, 1)[0], places=3) 235 | self.assertAlmostEqual(pred.upper80[2020], upper.rx(10, 1)[0], places=3) 236 | self.assertAlmostEqual(pred.lower95[2011], lower.rx(1, 2)[0], places=3) 237 | self.assertAlmostEqual(pred.lower95[2020], lower.rx(10, 2)[0], places=3) 238 | self.assertAlmostEqual(pred.upper95[2011], upper.rx(1, 2)[0], places=3) 239 | self.assertAlmostEqual(pred.upper95[2020], upper.rx(10, 2)[0], places=3) 240 | self.assertRaises(ValueError, converters.prediction_intervals, self.oil_ts) 241 | 242 | 243 | def test_accuracy(self): 244 | acc1 = wrappers.accuracy(self.fc_oil) 245 | acdf1 = converters.accuracy(acc1) 246 | acdf1.shape == (7, 1) 247 | list(acdf1.columns) == ['Train'] 248 | acc2 = wrappers.accuracy(self.fc_oil, 350) 249 | acdf2 = converters.accuracy(acc2) 250 | acdf2.shape == (7, 2) 251 | set(acdf2.columns) == {'Train', 'Test'} 252 | self.assertTrue(acdf1.Train.round(5).equals(acdf2.Train.round(5))) 253 | r_test_vals = list(acc2.rx(2, True)) 254 | r_cols = list(robjects.r.colnames(acc2)) 255 | self.assertAlmostEqual(acdf2.Test['ME'], 256 | r_test_vals[r_cols.index('ME')], places=3) 257 | self.assertAlmostEqual(acdf2.Test['MAE'], 258 | r_test_vals[r_cols.index('MAE')], places=3) 259 | self.assertAlmostEqual(acdf2.Test['RMSE'], 260 | r_test_vals[r_cols.index('RMSE')], places=3) 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /test/test_io.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pandas 3 | from rforecast import ts_io 4 | 5 | class IOTestCase(unittest.TestCase): 6 | 7 | def test_read_series(self): 8 | # The no-index case: 9 | oil2 = ts_io.read_series('data/oil2.csv') 10 | self.assertEqual(len(oil2), 46) 11 | self.assertListEqual(list(oil2.index), range(1, 46 + 1)) 12 | 13 | # The non-seasonal index case: 14 | oil = ts_io.read_series('data/oil.csv') 15 | self.assertEqual(len(oil), 46) 16 | self.assertListEqual(list(oil.index), range(1965, 2011)) 17 | 18 | # The seasonal index case: 19 | aus = ts_io.read_series('data/aus.csv') 20 | self.assertEqual(len(aus), 48) 21 | self.assertEqual(aus.index.nlevels, 2) 22 | 23 | # This has 4 columns, and should raise an IOError: 24 | self.assertRaises(IOError, ts_io.read_series, 'data/bad.csv') 25 | 26 | 27 | def test_read_ts(self): 28 | oil = ts_io.read_ts('oil','fpp',as_pandas=True) 29 | self.assertEqual(len(oil), 46) 30 | self.assertListEqual(list(oil.index), range(1965, 2011)) 31 | self.assertRaises(IOError, ts_io.read_ts, 'foo') 32 | self.assertRaises(IOError, ts_io.read_ts, 'oil', pkgname='foo') 33 | -------------------------------------------------------------------------------- /test/test_rbase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from rpy2 import robjects 3 | from rforecast import rbase, ts_io, converters, wrappers 4 | 5 | class rbaseTestCase(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.oil = ts_io.read_series('data/oil.csv') 9 | self.oil_ts = converters.series_as_ts(self.oil) 10 | self.mat = converters.matrix([[1,2,3],[2,3,4]]) 11 | self.acc = wrappers.accuracy(wrappers.thetaf(self.oil_ts)) 12 | 13 | def testCls(self): 14 | self.assertTrue('ts' in rbase.cls(self.oil_ts)) 15 | self.assertRaises(TypeError, rbase.cls, self.oil) 16 | 17 | def testDim(self): 18 | self.assertListEqual(rbase.dim(self.mat), [2, 3]) 19 | self.assertTrue(rbase.dim(self.oil_ts) is None) 20 | self.assertRaises(TypeError, rbase.dim, self.oil) 21 | 22 | def testColnames(self): 23 | self.assertRaises(TypeError, rbase.colnames, self.oil) 24 | self.assertTrue(rbase.colnames(self.mat) is None) 25 | self.assertTrue('MASE' in rbase.colnames(self.acc)) 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test_wrappers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from rforecast import wrappers 3 | from rforecast import converters 4 | from rforecast import ts_io 5 | from rpy2 import robjects 6 | from rpy2.robjects.packages import importr 7 | import numpy 8 | 9 | 10 | NULL = robjects.NULL 11 | NA = robjects.NA_Real 12 | 13 | 14 | class WrappersTestCase(unittest.TestCase): 15 | 16 | 17 | def setUp(self): 18 | self.oil = ts_io.read_ts('oil', 'fpp', False) 19 | self.aus = ts_io.read_ts('austourists', 'fpp', False) 20 | self.gold = ts_io.read_ts('gold', as_pandas=False) 21 | self.tsn = converters.ts([1, 2, NA, 4]) 22 | self.tss = converters.ts([1, 2, 3, 1, 2, 3, 1, NA, 3], frequency=3) 23 | self.vss = [1,2,3,4] * 4 24 | self.vns = range(10) 25 | r = [ 0.00287731, 0.58436909, 0.37650672, 0.10024602, 0.46983146, 26 | 0.36542408, 0.47136475, 0.79978803, 0.70349953, 0.69531808, 27 | 0.54447409, 0.82227504, 0.99736304, 0.91404314, 0.42225177, 28 | 0.14696605, 0.08098318, 0.11046747, 0.8412757 , 0.73562921] 29 | self.rnd = converters.sequence_as_series(r, freq=4) 30 | self.fc = importr('forecast') 31 | 32 | 33 | def test_frequency(self): 34 | self.assertEqual(wrappers.frequency(self.oil), 1) 35 | self.assertEqual(wrappers.frequency(self.aus), 4) 36 | 37 | 38 | def test_na_interp(self): 39 | self.assertEquals(list(wrappers.na_interp(self.tsn)), [1, 2, 3, 4]) 40 | seasonal = list(wrappers.na_interp(self.tss)) 41 | self.assertAlmostEqual(seasonal[7], 2.0, places=3) 42 | 43 | 44 | def test_ts(self): 45 | ts = converters.ts(self.vss, deltat=0.25, end=(1,1)) 46 | self.assertEqual(wrappers.frequency(ts), 4) 47 | self.assertEqual(tuple(robjects.r('end')(ts)), (1.0, 1.0)) 48 | self.assertEqual(tuple(robjects.r('start')(ts)), (-3.0, 2.0)) 49 | 50 | 51 | def test_get_horizon(self): 52 | self.assertEqual(wrappers._get_horizon(self.aus), 8) 53 | self.assertEqual(wrappers._get_horizon(self.aus, 10), 10) 54 | self.assertEqual(wrappers._get_horizon(self.oil), 10) 55 | 56 | 57 | def test_box_cox(self): 58 | bc = wrappers.BoxCox(self.oil, 0.5) 59 | bc1 = wrappers.BoxCox(self.oil, 1) 60 | bc0 = wrappers.BoxCox(self.oil, 0) 61 | self.assertAlmostEqual(self.oil[0], bc1[0] + 1, places=4) 62 | self.assertAlmostEqual(numpy.log(self.oil[0]), bc0[0], places=4) 63 | bc_value = (self.oil.rx(1)[0]**0.5 - 1) / 0.5 64 | self.assertAlmostEqual(bc[0], bc_value, places=2) 65 | inv_bc = wrappers.InvBoxCox(bc, 0.5) 66 | self.assertAlmostEqual(inv_bc[0], self.oil[0], places=4) 67 | 68 | def test_tsclean(self): 69 | gold_py = converters.ts_as_series(self.gold) 70 | clean_py = wrappers.tsclean(gold_py) 71 | self.assertFalse(clean_py.isnull().any()) 72 | clean_r = self.fc.tsclean(self.gold) 73 | self.assertAlmostEqual(clean_py[770], clean_r.rx(770), places=3) 74 | 75 | def test_findfrequency(self): 76 | self.assertEqual(wrappers.findfrequency(self.aus), 4) 77 | self.assertEqual(wrappers.findfrequency(self.oil), 1) 78 | 79 | def test_ndiffs(self): 80 | self.assertEqual(wrappers.ndiffs(self.oil), 1) 81 | self.assertEqual(wrappers.ndiffs(self.rnd), 0) 82 | 83 | def test_nsdiffs(self): 84 | self.assertEqual(wrappers.nsdiffs(self.aus), 1) 85 | self.assertEqual(wrappers.nsdiffs(self.rnd), 0) 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | --------------------------------------------------------------------------------