zigzag-reload 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zigzag_reload-0.3.3/CHANGES.txt +12 -0
- zigzag_reload-0.3.3/LICENSE.txt +13 -0
- zigzag_reload-0.3.3/PKG-INFO +41 -0
- zigzag_reload-0.3.3/README.md +19 -0
- zigzag_reload-0.3.3/build.py +55 -0
- zigzag_reload-0.3.3/pyproject.toml +49 -0
- zigzag_reload-0.3.3/zigzag/__init__.py +4 -0
- zigzag_reload-0.3.3/zigzag/core.pyx +234 -0
- zigzag_reload-0.3.3/zigzag/plotting.py +18 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
v0.1.0, 2014-02-21 -- Initial Release, refactored version of my poorly
|
|
2
|
+
executed TSGeom package.
|
|
3
|
+
v0.1.1, 2014-02-25 -- Change README.txt to README.rst so that Github will
|
|
4
|
+
process it correctly.
|
|
5
|
+
v0.1.2, 2014-03-20 -- FIX bug that caused install error due to changed README
|
|
6
|
+
file name.
|
|
7
|
+
v0.1.3, 2014-03-20 -- FIX of previous FIX. Apparently, I don't understand the
|
|
8
|
+
README.rst usage pattern.
|
|
9
|
+
v0.1.4, 2017-05-09 -- Refactor for Cython instead of numba
|
|
10
|
+
v0.3.0, 2022-07-31 -- Switch to poetry for package management. Drop support
|
|
11
|
+
for Python below 3.8. Add plotting package helpers.
|
|
12
|
+
Add support for Pandas.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2014, John Bjorn Nelson
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
10
|
+
|
|
11
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zigzag-reload
|
|
3
|
+
Version: 0.3.3
|
|
4
|
+
Summary: Package for finding peaks and valleys of time series.
|
|
5
|
+
Home-page: https://github.com/jbn/ZigZag
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Author: generativist
|
|
8
|
+
Author-email: jbn@abreka.com
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Dist: Cython (>=0.29,<0.30)
|
|
18
|
+
Requires-Dist: numpy (>=1.21.1)
|
|
19
|
+
Project-URL: Repository, https://github.com/jbn/ZigZag
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
[](https://badge.fury.io/py/ZigZag)
|
|
24
|
+
[](https://github.com/jbn/ZigZag/stargazers)
|
|
25
|
+
[](https://github.com/jbn/ZigZag/blob/main/LICENSE.txt)
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ZigZag
|
|
30
|
+
|
|
31
|
+
ZigZag provides functions for identifying the peaks and valleys of a time
|
|
32
|
+
series. Additionally, it provides a function for computing the maximum drawdown.
|
|
33
|
+
|
|
34
|
+
For fastest understanding, [view the IPython notebook demo tutorial](https://github.com/jbn/ZigZag/blob/master/zigzag_demo.ipynb>).
|
|
35
|
+
|
|
36
|
+
## Contributing
|
|
37
|
+
|
|
38
|
+
This is an admittedly small project. Still, if you have any contributions,
|
|
39
|
+
please [fork this project on github](https://github.com/jbn/ZigZag) and
|
|
40
|
+
send me a pull request.
|
|
41
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+

|
|
2
|
+
[](https://badge.fury.io/py/ZigZag)
|
|
3
|
+
[](https://github.com/jbn/ZigZag/stargazers)
|
|
4
|
+
[](https://github.com/jbn/ZigZag/blob/main/LICENSE.txt)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# ZigZag
|
|
9
|
+
|
|
10
|
+
ZigZag provides functions for identifying the peaks and valleys of a time
|
|
11
|
+
series. Additionally, it provides a function for computing the maximum drawdown.
|
|
12
|
+
|
|
13
|
+
For fastest understanding, [view the IPython notebook demo tutorial](https://github.com/jbn/ZigZag/blob/master/zigzag_demo.ipynb>).
|
|
14
|
+
|
|
15
|
+
## Contributing
|
|
16
|
+
|
|
17
|
+
This is an admittedly small project. Still, if you have any contributions,
|
|
18
|
+
please [fork this project on github](https://github.com/jbn/ZigZag) and
|
|
19
|
+
send me a pull request.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
from distutils.command.build_ext import build_ext
|
|
5
|
+
from distutils.core import Distribution, Extension
|
|
6
|
+
|
|
7
|
+
from Cython.Build import cythonize
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
compile_args = ["-O3"]
|
|
11
|
+
link_args = []
|
|
12
|
+
include_dirs = [np.get_include()]
|
|
13
|
+
libraries = ["m"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build():
|
|
17
|
+
debug_mode_on = '1' if 'debug_mode_on' in os.environ else '0'
|
|
18
|
+
extensions = [
|
|
19
|
+
Extension(
|
|
20
|
+
"*",
|
|
21
|
+
["zigzag/*.pyx"],
|
|
22
|
+
extra_compile_args=compile_args,
|
|
23
|
+
extra_link_args=link_args,
|
|
24
|
+
include_dirs=include_dirs,
|
|
25
|
+
libraries=libraries if os.name != 'nt' else [],
|
|
26
|
+
define_macros=[('CYTHON_TRACE', debug_mode_on),
|
|
27
|
+
('CYTHON_TRACE_NOGIL', debug_mode_on),
|
|
28
|
+
('CYTHON_BINDING', debug_mode_on),
|
|
29
|
+
('CYTHON_FAST_PYCCALL', '1')],
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
ext_modules = cythonize(
|
|
33
|
+
extensions,
|
|
34
|
+
include_path=include_dirs,
|
|
35
|
+
compiler_directives={"binding": True, "language_level": sys.version_info.major},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
distribution = Distribution({"name": "extended", "ext_modules": ext_modules})
|
|
39
|
+
distribution.package_dir = "extended"
|
|
40
|
+
|
|
41
|
+
cmd = build_ext(distribution)
|
|
42
|
+
cmd.ensure_finalized()
|
|
43
|
+
cmd.run()
|
|
44
|
+
|
|
45
|
+
# Copy built extensions back to the project
|
|
46
|
+
for output in cmd.get_outputs():
|
|
47
|
+
relative_extension = os.path.relpath(output, cmd.build_lib)
|
|
48
|
+
shutil.copyfile(output, relative_extension)
|
|
49
|
+
mode = os.stat(relative_extension).st_mode
|
|
50
|
+
mode |= (mode & 0o444) >> 2
|
|
51
|
+
os.chmod(relative_extension, mode)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
build()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"poetry-core>=1.0.0",
|
|
4
|
+
"Cython>=0.29",
|
|
5
|
+
"numpy >=1.21.1",
|
|
6
|
+
]
|
|
7
|
+
build-backend = "poetry.core.masonry.api"
|
|
8
|
+
|
|
9
|
+
[[tool.poetry.source]]
|
|
10
|
+
name = "tsinghua"
|
|
11
|
+
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
12
|
+
default = true
|
|
13
|
+
|
|
14
|
+
[tool.poetry]
|
|
15
|
+
name = "zigzag-reload"
|
|
16
|
+
version = "0.3.3"
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
homepage = "https://github.com/jbn/ZigZag"
|
|
19
|
+
repository = "https://github.com/jbn/ZigZag"
|
|
20
|
+
description = "Package for finding peaks and valleys of time series."
|
|
21
|
+
authors = ["generativist <jbn@abreka.com>"]
|
|
22
|
+
license = "BSD-3-Clause"
|
|
23
|
+
packages = [{ include = "zigzag" }]
|
|
24
|
+
include = [
|
|
25
|
+
{ path = "zigzag/**/*.so", format = "wheel" },
|
|
26
|
+
{ path = "zigzag/**/*.pyx" },
|
|
27
|
+
{ path = "zigzag/**/*.pyd" },
|
|
28
|
+
"CHANGES.txt"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.poetry.dependencies]
|
|
32
|
+
python = ">=3.8"
|
|
33
|
+
numpy = ">=1.21.1"
|
|
34
|
+
Cython = "^0.29"
|
|
35
|
+
|
|
36
|
+
[tool.poetry.build]
|
|
37
|
+
generate-setup-file = false
|
|
38
|
+
script = "build.py"
|
|
39
|
+
|
|
40
|
+
[tool.poetry.dev-dependencies]
|
|
41
|
+
pytest = "^7.1.2"
|
|
42
|
+
pandas = ">=0.22,<2.0"
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
minversion = "6.0"
|
|
46
|
+
addopts = "-ra -q"
|
|
47
|
+
testpaths = [
|
|
48
|
+
"tests",
|
|
49
|
+
]
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
cimport cython
|
|
2
|
+
import numpy as np
|
|
3
|
+
from numpy cimport ndarray, int_t
|
|
4
|
+
|
|
5
|
+
DEF PEAK = 1
|
|
6
|
+
DEF VALLEY = -1
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@cython.boundscheck(False)
|
|
10
|
+
@cython.wraparound(False)
|
|
11
|
+
cpdef int_t identify_initial_pivot(double [:] X,
|
|
12
|
+
double up_thresh,
|
|
13
|
+
double down_thresh):
|
|
14
|
+
cdef:
|
|
15
|
+
double x_0 = X[0]
|
|
16
|
+
double x_t = x_0
|
|
17
|
+
|
|
18
|
+
double max_x = x_0
|
|
19
|
+
double min_x = x_0
|
|
20
|
+
|
|
21
|
+
int_t max_t = 0
|
|
22
|
+
int_t min_t = 0
|
|
23
|
+
|
|
24
|
+
up_thresh += 1
|
|
25
|
+
down_thresh += 1
|
|
26
|
+
|
|
27
|
+
for t in range(1, len(X)):
|
|
28
|
+
x_t = X[t]
|
|
29
|
+
|
|
30
|
+
if x_t / min_x >= up_thresh:
|
|
31
|
+
return VALLEY if min_t == 0 else PEAK
|
|
32
|
+
|
|
33
|
+
if x_t / max_x <= down_thresh:
|
|
34
|
+
return PEAK if max_t == 0 else VALLEY
|
|
35
|
+
|
|
36
|
+
if x_t > max_x:
|
|
37
|
+
max_x = x_t
|
|
38
|
+
max_t = t
|
|
39
|
+
|
|
40
|
+
if x_t < min_x:
|
|
41
|
+
min_x = x_t
|
|
42
|
+
min_t = t
|
|
43
|
+
|
|
44
|
+
t_n = len(X)-1
|
|
45
|
+
return VALLEY if x_0 < X[t_n] else PEAK
|
|
46
|
+
|
|
47
|
+
def _to_ndarray(X):
|
|
48
|
+
# The type signature in peak_valley_pivots_detailed does not work for
|
|
49
|
+
# pandas series because as of 0.13.0 it no longer sub-classes ndarray.
|
|
50
|
+
# The workaround everyone used was to call `.values` directly before
|
|
51
|
+
# calling the function. Which is fine but a little annoying.
|
|
52
|
+
t = type(X)
|
|
53
|
+
if t.__name__ == 'ndarray':
|
|
54
|
+
pass # Check for ndarray first for historical reasons
|
|
55
|
+
elif f"{t.__module__}.{t.__name__}" == 'pandas.core.series.Series':
|
|
56
|
+
X = X.values
|
|
57
|
+
elif isinstance(X, (list, tuple)):
|
|
58
|
+
X = np.array(X)
|
|
59
|
+
|
|
60
|
+
return X
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def peak_valley_pivots(X, up_thresh, down_thresh):
|
|
64
|
+
X = _to_ndarray(X)
|
|
65
|
+
|
|
66
|
+
# Ensure float for correct signature
|
|
67
|
+
if not str(X.dtype).startswith('float'):
|
|
68
|
+
X = X.astype(np.float64)
|
|
69
|
+
|
|
70
|
+
return peak_valley_pivots_detailed(X, up_thresh, down_thresh, True, False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@cython.boundscheck(False)
|
|
74
|
+
@cython.wraparound(False)
|
|
75
|
+
cpdef peak_valley_pivots_detailed(double [:] X,
|
|
76
|
+
double up_thresh,
|
|
77
|
+
double down_thresh,
|
|
78
|
+
bint limit_to_finalized_segments,
|
|
79
|
+
bint use_eager_switching_for_non_final):
|
|
80
|
+
"""
|
|
81
|
+
Find the peaks and valleys of a series.
|
|
82
|
+
|
|
83
|
+
:param X: the series to analyze
|
|
84
|
+
:param up_thresh: minimum relative change necessary to define a peak
|
|
85
|
+
:param down_thesh: minimum relative change necessary to define a valley
|
|
86
|
+
:return: an array with 0 indicating no pivot and -1 and 1 indicating
|
|
87
|
+
valley and peak
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
The First and Last Elements
|
|
91
|
+
---------------------------
|
|
92
|
+
The first and last elements are guaranteed to be annotated as peak or
|
|
93
|
+
valley even if the segments formed do not have the necessary relative
|
|
94
|
+
changes. This is a tradeoff between technical correctness and the
|
|
95
|
+
propensity to make mistakes in data analysis. The possible mistake is
|
|
96
|
+
ignoring data outside the fully realized segments, which may bias
|
|
97
|
+
analysis.
|
|
98
|
+
"""
|
|
99
|
+
if down_thresh > 0:
|
|
100
|
+
raise ValueError('The down_thresh must be negative.')
|
|
101
|
+
|
|
102
|
+
cdef:
|
|
103
|
+
int_t initial_pivot = identify_initial_pivot(X,
|
|
104
|
+
up_thresh,
|
|
105
|
+
down_thresh)
|
|
106
|
+
int_t t_n = len(X)
|
|
107
|
+
ndarray[int_t, ndim=1] pivots = np.zeros(t_n, dtype=np.int_)
|
|
108
|
+
int_t trend = -initial_pivot
|
|
109
|
+
int_t last_pivot_t = 0
|
|
110
|
+
double last_pivot_x = X[0]
|
|
111
|
+
double x, r
|
|
112
|
+
|
|
113
|
+
pivots[0] = initial_pivot
|
|
114
|
+
|
|
115
|
+
# Adding one to the relative change thresholds saves operations. Instead
|
|
116
|
+
# of computing relative change at each point as x_j / x_i - 1, it is
|
|
117
|
+
# computed as x_j / x_1. Then, this value is compared to the threshold + 1.
|
|
118
|
+
# This saves (t_n - 1) subtractions.
|
|
119
|
+
up_thresh += 1
|
|
120
|
+
down_thresh += 1
|
|
121
|
+
|
|
122
|
+
for t in range(1, t_n):
|
|
123
|
+
x = X[t]
|
|
124
|
+
r = x / last_pivot_x
|
|
125
|
+
|
|
126
|
+
if trend == -1:
|
|
127
|
+
if r >= up_thresh:
|
|
128
|
+
pivots[last_pivot_t] = trend
|
|
129
|
+
trend = PEAK
|
|
130
|
+
last_pivot_x = x
|
|
131
|
+
last_pivot_t = t
|
|
132
|
+
elif x < last_pivot_x:
|
|
133
|
+
last_pivot_x = x
|
|
134
|
+
last_pivot_t = t
|
|
135
|
+
else:
|
|
136
|
+
if r <= down_thresh:
|
|
137
|
+
pivots[last_pivot_t] = trend
|
|
138
|
+
trend = VALLEY
|
|
139
|
+
last_pivot_x = x
|
|
140
|
+
last_pivot_t = t
|
|
141
|
+
elif x > last_pivot_x:
|
|
142
|
+
last_pivot_x = x
|
|
143
|
+
last_pivot_t = t
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if limit_to_finalized_segments:
|
|
147
|
+
if use_eager_switching_for_non_final:
|
|
148
|
+
if last_pivot_t > 0 and last_pivot_t < t_n-1:
|
|
149
|
+
pivots[last_pivot_t] = trend
|
|
150
|
+
pivots[t_n-1] = -trend
|
|
151
|
+
else:
|
|
152
|
+
pivots[t_n-1] = trend
|
|
153
|
+
else:
|
|
154
|
+
if last_pivot_t == t_n-1:
|
|
155
|
+
pivots[last_pivot_t] = trend
|
|
156
|
+
elif pivots[t_n-1] == 0:
|
|
157
|
+
pivots[t_n-1] = -trend
|
|
158
|
+
|
|
159
|
+
return pivots
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def max_drawdown(X) -> float:
|
|
163
|
+
X = _to_ndarray(X)
|
|
164
|
+
|
|
165
|
+
# Ensure float for correct signature
|
|
166
|
+
if not str(X.dtype).startswith('float'):
|
|
167
|
+
X = X.astype(np.float64)
|
|
168
|
+
|
|
169
|
+
return max_drawdown_c(X)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@cython.boundscheck(False)
|
|
173
|
+
@cython.wraparound(False)
|
|
174
|
+
cpdef double max_drawdown_c(ndarray[double, ndim=1] X):
|
|
175
|
+
"""
|
|
176
|
+
Compute the maximum drawdown of some sequence.
|
|
177
|
+
|
|
178
|
+
:return: 0 if the sequence is strictly increasing.
|
|
179
|
+
otherwise the abs value of the maximum drawdown
|
|
180
|
+
of sequence X
|
|
181
|
+
"""
|
|
182
|
+
cdef:
|
|
183
|
+
double mdd = 0
|
|
184
|
+
double peak = X[0]
|
|
185
|
+
double x, dd
|
|
186
|
+
|
|
187
|
+
for x in X:
|
|
188
|
+
if x > peak:
|
|
189
|
+
peak = x
|
|
190
|
+
|
|
191
|
+
dd = (peak - x) / peak
|
|
192
|
+
|
|
193
|
+
if dd > mdd:
|
|
194
|
+
mdd = dd
|
|
195
|
+
|
|
196
|
+
return mdd if mdd != 0.0 else 0.0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@cython.boundscheck(False)
|
|
200
|
+
@cython.wraparound(False)
|
|
201
|
+
def pivots_to_modes(int_t [:] pivots):
|
|
202
|
+
"""
|
|
203
|
+
Translate pivots into trend modes.
|
|
204
|
+
|
|
205
|
+
:param pivots: the result of calling ``peak_valley_pivots``
|
|
206
|
+
:return: numpy array of trend modes. That is, between (VALLEY, PEAK] it
|
|
207
|
+
is 1 and between (PEAK, VALLEY] it is -1.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
cdef:
|
|
211
|
+
int_t x, t
|
|
212
|
+
ndarray[int_t, ndim=1] modes = np.zeros(len(pivots),
|
|
213
|
+
dtype=np.int_)
|
|
214
|
+
int_t mode = -pivots[0]
|
|
215
|
+
|
|
216
|
+
modes[0] = pivots[0]
|
|
217
|
+
|
|
218
|
+
for t in range(1, len(pivots)):
|
|
219
|
+
x = pivots[t]
|
|
220
|
+
if x != 0:
|
|
221
|
+
modes[t] = mode
|
|
222
|
+
mode = -x
|
|
223
|
+
else:
|
|
224
|
+
modes[t] = mode
|
|
225
|
+
|
|
226
|
+
return modes
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def compute_segment_returns(X, pivots):
|
|
230
|
+
"""
|
|
231
|
+
:return: numpy array of the pivot-to-pivot returns for each segment."""
|
|
232
|
+
X = _to_ndarray(X)
|
|
233
|
+
pivot_points = X[pivots != 0]
|
|
234
|
+
return pivot_points[1:] / pivot_points[:-1] - 1.0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The package requirements do not enforce matplotlib as a requirement so this
|
|
3
|
+
package is optional. However, it's useful.
|
|
4
|
+
"""
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def plot_pivots(X, pivots, ax=None):
|
|
10
|
+
if ax is None:
|
|
11
|
+
ax = plt.subplots(1, 1)[1]
|
|
12
|
+
|
|
13
|
+
ax.set_xlim(0, len(X))
|
|
14
|
+
ax.set_ylim(X.min()*0.99, X.max()*1.01)
|
|
15
|
+
ax.plot(np.arange(len(X)), X, 'k:', alpha=0.5)
|
|
16
|
+
ax.plot(np.arange(len(X))[pivots != 0], X[pivots != 0], 'k-')
|
|
17
|
+
ax.scatter(np.arange(len(X))[pivots == 1], X[pivots == 1], color='g')
|
|
18
|
+
ax.scatter(np.arange(len(X))[pivots == -1], X[pivots == -1], color='r')
|