|
1 | | -""" |
2 | | -Metrics and evaluations? |
3 | | -""" |
4 | | -import numpy as _np |
5 | | -import matplotlib.pyplot as _plt |
6 | | -import scipy.stats as _scipy_stats |
| 1 | +"""Some tools to help evaluate and plot performance, used in optimization and in jupyter notebooks""" |
| 2 | +import numpy as np |
| 3 | +import matplotlib.pyplot as plt |
| 4 | +from scipy import stats |
7 | 5 |
|
8 | | -# local imports |
9 | | -from pynumdiff.utils import utility as _utility |
10 | | -from pynumdiff.finite_difference import first_order as _finite_difference |
| 6 | +from pynumdiff.utils import utility |
11 | 7 |
|
12 | 8 |
|
13 | 9 | # pylint: disable-msg=too-many-locals, too-many-arguments |
14 | 10 | def plot(x, dt, x_hat, dxdt_hat, x_truth, dxdt_truth, xlim=None, ax_x=None, ax_dxdt=None, |
15 | 11 | show_error=True, markersize=5): |
16 | | - """ |
17 | | - Make comparison plots of 'x (blue) vs x_truth (black) vs x_hat (red)' and |
18 | | - 'dxdt_truth (black) vs dxdt_hat (red)' |
19 | | -
|
20 | | - :param x: array of noisy time series |
21 | | - :type x: np.array (float) |
22 | | -
|
23 | | - :param dt: a float number representing the step size |
24 | | - :type dt: float |
25 | | -
|
26 | | - :param x_hat: array of smoothed estimation of x |
27 | | - :type x_hat: np.array (float) |
28 | | -
|
29 | | - :param dxdt_hat: array of estimated derivative |
30 | | - :type dxdt_hat: np.array (float) |
31 | | -
|
32 | | - :param x_truth: array of noise-free time series |
33 | | - :type x_truth: np.array (float) |
34 | | -
|
35 | | - :param dxdt_truth: array of true derivative |
36 | | - :type dxdt_truth: np.array (float) |
37 | | -
|
38 | | - :param xlim: a list specifying range of x |
39 | | - :type xlim: list (2 integers), optional |
40 | | -
|
41 | | - :param ax_x: axis of the first plot |
42 | | - :type ax_x: :class:`matplotlib.axes`, optional |
43 | | -
|
44 | | - :param ax_dxdt: axis of the second plot |
45 | | - :type ax_dxdt: :class:`matplotlib.axes`, optional |
46 | | -
|
47 | | - :param show_error: whether to show the rmse |
48 | | - :type show_error: boolean, optional |
49 | | -
|
50 | | - :param markersize: marker size of noisy observations |
51 | | - :type markersize: int, optional |
| 12 | + """Make comparison plots of 'x (blue) vs x_truth (black) vs x_hat (red)' and 'dxdt_truth |
| 13 | + (black) vs dxdt_hat (red)' |
| 14 | +
|
| 15 | + :param np.array[float] x: array of noisy data |
| 16 | + :param float dt: a float number representing the step size |
| 17 | + :param np.array[float] x_hat: array of smoothed estimation of x |
| 18 | + :param np.array[float] dxdt_hat: array of estimated derivative |
| 19 | + :param np.array[float] x_truth: array of noise-free time series |
| 20 | + :param np.array[float] dxdt_truth: array of true derivative |
| 21 | + :param list[int] xlim: a list specifying range of x |
| 22 | + :param matplotlib.axes ax_x: axis of the first plot |
| 23 | + :param matplotlib.axes ax_dxdt: axis of the second plot |
| 24 | + :param bool show_error: whether to show the rmse |
| 25 | + :param int markersize: marker size of noisy observations |
52 | 26 |
|
53 | 27 | :return: Display two plots |
54 | | - :rtype: None |
55 | 28 | """ |
56 | | - t = _np.arange(0, dt*len(x), dt) |
| 29 | + t = np.arange(0, dt*len(x), dt) |
57 | 30 | if ax_x is None and ax_dxdt is None: |
58 | | - fig = _plt.figure(figsize=(20, 6)) |
| 31 | + fig = plt.figure(figsize=(20, 6)) |
59 | 32 | ax_x = fig.add_subplot(121) |
60 | 33 | ax_dxdt = fig.add_subplot(122) |
61 | 34 |
|
@@ -89,125 +62,59 @@ def plot(x, dt, x_hat, dxdt_hat, x_truth, dxdt_truth, xlim=None, ax_x=None, ax_d |
89 | 62 | print('RMS error in velocity: ', rms_dxdt) |
90 | 63 |
|
91 | 64 |
|
92 | | -def __rms_error__(a, e): |
93 | | - """ |
94 | | - Calculate rms error |
95 | | -
|
96 | | - :param a: the first array |
97 | | - :param e: the second array |
98 | | - :return: a float number representing the rms error |
99 | | - """ |
100 | | - if _np.max(_np.abs(a-e)) > 1e16: |
101 | | - return 1e16 |
102 | | - s_error = _np.ravel((a - e))**2 |
103 | | - ms_error = _np.mean(s_error) |
104 | | - rms_error = _np.sqrt(ms_error) |
105 | | - return rms_error |
106 | | - |
107 | | - |
108 | | -def metrics(x, dt, x_hat, dxdt_hat, x_truth=None, dxdt_truth=None, padding=None): |
109 | | - """ |
110 | | - Evaluate x_hat based on various metrics, depending on whether dxdt_truth and x_truth are known or not. |
111 | | -
|
112 | | - :param x: time series that was differentiated |
113 | | - :type x: np.array |
114 | | -
|
115 | | - :param dt: time step in seconds |
116 | | - :type dt: float |
117 | | -
|
118 | | - :param x_hat: estimated (smoothed) x |
119 | | - :type x_hat: np.array |
| 65 | +def metrics(x, dt, x_hat, dxdt_hat, x_truth=None, dxdt_truth=None, padding=0): |
| 66 | + """Evaluate x_hat based on various metrics, depending on whether dxdt_truth and x_truth are known or not. |
120 | 67 |
|
121 | | - :param dxdt_hat: estimated xdot |
122 | | - :type dxdt_hat: np.array |
123 | | -
|
124 | | - :param x_truth: true value of x, if known, optional |
125 | | - :type x_truth: np.array like x or None |
126 | | -
|
127 | | - :param dxdt_truth: true value of dxdt, if known, optional |
128 | | - :type dxdt_truth: np.array like x or None |
129 | | -
|
130 | | - :param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x |
131 | | - :type padding: int, None, or auto |
132 | | -
|
133 | | - :return: a tuple containing the following: |
134 | | - - rms_rec_x: RMS error between the integral of dxdt_hat and x |
135 | | - - rms_x: RMS error between x_hat and x_truth, returns None if x_truth is None |
136 | | - - rms_dxdt: RMS error between dxdt_hat and dxdt_truth, returns None if dxdt_hat is None |
137 | | - :rtype: tuple -> (float, float, float) |
| 68 | + :param np.array[float] x: data that was differentiated |
| 69 | + :param float dt: step size |
| 70 | + :param np.array[float] x_hat: estimated (smoothed) x |
| 71 | + :param np.array[float] dxdt_hat: estimated xdot |
| 72 | + :param np.array[float] x_truth: true value of x, if known |
| 73 | + :param np.array[float] dxdt_truth: true value of dxdt, if known, optional |
| 74 | + :param int padding: number of snapshots on either side of the array to ignore when calculating |
| 75 | + the metric. If :code:`'auto'`, defaults to 2.5% of the size of x |
138 | 76 |
|
| 77 | + :return: tuple[float, float, float] containing\n |
| 78 | + - **rms_rec_x** -- RMS error between the integral of dxdt_hat and x |
| 79 | + - **rms_x** -- RMS error between x_hat and x_truth, returns None if x_truth is None |
| 80 | + - **rms_dxdt** -- RMS error between dxdt_hat and dxdt_truth, returns None if dxdt_hat is None |
139 | 81 | """ |
| 82 | + if np.isnan(x_hat).any(): |
| 83 | + return np.nan, np.nan, np.nan |
140 | 84 | if padding is None or padding == 'auto': |
141 | 85 | padding = int(0.025*len(x)) |
142 | 86 | padding = max(padding, 1) |
143 | | - if _np.isnan(x_hat).any(): |
144 | | - return _np.nan, _np.nan, _np.nan |
145 | | - |
146 | | - # RMS dxdt |
147 | | - if dxdt_truth is not None: |
148 | | - rms_dxdt = __rms_error__(dxdt_hat[padding:-padding], dxdt_truth[padding:-padding]) |
149 | | - else: |
150 | | - rms_dxdt = None |
151 | | - |
152 | | - # RMS x |
153 | | - if x_truth is not None: |
154 | | - rms_x = __rms_error__(x_hat[padding:-padding], x_truth[padding:-padding]) |
155 | | - else: |
156 | | - rms_x = None |
157 | | - |
158 | | - # RMS reconstructed x |
159 | | - rec_x = _utility.integrate_dxdt_hat(dxdt_hat, dt) |
160 | | - x0 = _utility.estimate_initial_condition(x, rec_x) |
| 87 | + s = slice(padding,len(x)-padding) # slice out where the data is |
| 88 | + |
| 89 | + # RMS of dxdt and x_hat |
| 90 | + root = np.sqrt(s.stop - s.start) |
| 91 | + rms_dxdt = np.linalg.norm(dxdt_hat[s] - dxdt_truth[s]) / root if dxdt_truth is not None else None |
| 92 | + rms_x = np.linalg.norm(x_hat[s] - x_truth[s]) / root if x_truth is not None else None |
| 93 | + |
| 94 | + # RMS reconstructed x from integrating dxdt vs given raw x, available even in the absence of ground truth |
| 95 | + rec_x = utility.integrate_dxdt_hat(dxdt_hat, dt) |
| 96 | + x0 = utility.estimate_initial_condition(x, rec_x) |
161 | 97 | rec_x = rec_x + x0 |
162 | | - rms_rec_x = __rms_error__(rec_x[padding:-padding], x[padding:-padding]) |
| 98 | + rms_rec_x = np.linalg.norm(rec_x[s] - x[s]) / root |
163 | 99 |
|
164 | 100 | return rms_rec_x, rms_x, rms_dxdt |
165 | 101 |
|
166 | 102 |
|
167 | 103 | def error_correlation(dxdt_hat, dxdt_truth, padding=None): |
168 | | - """ |
169 | | - Calculate the error correlation (pearsons correlation coefficient) between the estimated dxdt and true dxdt |
170 | | -
|
171 | | - :param dxdt_hat: estimated xdot |
172 | | - :type dxdt_hat: np.array |
173 | | -
|
174 | | - :param dxdt_truth: true value of dxdt, if known, optional |
175 | | - :type dxdt_truth: np.array like x or None |
| 104 | + """Calculate the error correlation (pearsons correlation coefficient) between the estimated |
| 105 | + dxdt and true dxdt |
176 | 106 |
|
177 | | - :param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x |
178 | | - :type padding: int, None, or auto |
179 | | -
|
180 | | - :return: r-squared correlation coefficient |
181 | | - :rtype: float |
| 107 | + :param np.array[float] dxdt_hat: estimated xdot |
| 108 | + :param np.array[float] dxdt_truth: true value of dxdt, if known, optional |
| 109 | + :param int padding: number of snapshots on either side of the array to ignore when calculating |
| 110 | + the metric. If auto or None, defaults to 2.5% of the size of x |
182 | 111 |
|
| 112 | + :return: (float) -- r-squared correlation coefficient |
183 | 113 | """ |
184 | 114 | if padding is None or padding == 'auto': |
185 | 115 | padding = int(0.025*len(dxdt_hat)) |
186 | 116 | padding = max(padding, 1) |
187 | 117 | errors = (dxdt_hat[padding:-padding] - dxdt_truth[padding:-padding]) |
188 | | - r = _scipy_stats.linregress(dxdt_truth[padding:-padding] - |
189 | | - _np.mean(dxdt_truth[padding:-padding]), errors) |
| 118 | + r = stats.linregress(dxdt_truth[padding:-padding] - |
| 119 | + np.mean(dxdt_truth[padding:-padding]), errors) |
190 | 120 | return r.rvalue**2 |
191 | | - |
192 | | - |
193 | | -def rmse(dxdt_hat, dxdt_truth, padding=None): |
194 | | - """ |
195 | | - Calculate the Root Mean Squared Error between the estimated dxdt and true dxdt |
196 | | -
|
197 | | - :param dxdt_hat: estimated xdot |
198 | | - :type dxdt_hat: np.array |
199 | | -
|
200 | | - :param dxdt_truth: true value of dxdt, if known, optional |
201 | | - :type dxdt_truth: np.array like x or None |
202 | | -
|
203 | | - :param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x |
204 | | - :type padding: int, None, or auto |
205 | | -
|
206 | | - :return: Root Mean Squared Error |
207 | | - :rtype: float |
208 | | - """ |
209 | | - if padding is None or padding == 'auto': |
210 | | - padding = int(0.025*len(dxdt_hat)) |
211 | | - padding = max(padding, 1) |
212 | | - RMSE = _np.sqrt(_np.mean((dxdt_hat[padding:-padding] - dxdt_truth[padding:-padding])**2)) |
213 | | - return RMSE |
0 commit comments