├── 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 |
--------------------------------------------------------------------------------