Skip to content

Commit 8763309

Browse files
committed
Better align the two movingaverage classes
The one that is an average of multiple lines (instead of a time-average), now also has the x_values, mean_values, standard deviation and counts fields, just like the other class.
1 parent 9d0d4f0 commit 8763309

1 file changed

Lines changed: 48 additions & 31 deletions

File tree

organoid_tracker/util/moving_average.py

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -163,27 +163,50 @@ class LinesAverage(PlotAverage):
163163
_lines: List[Tuple[List[float], List[float]]]
164164
_x_step_size: float
165165

166+
x_values: ndarray # X values of the mean
167+
mean_values: ndarray # Y values of the mean. You can plot the mean like plt.plot(x_values, mean_values)
168+
standard_deviation_values: ndarray # Standard deviation in the mean. Useful for plt.fill_between.
169+
counts_in_standard_deviation_values: ndarray # Counts used for calculating the standard deviation
170+
166171
def __init__(self, *lines: Tuple[List[float], List[float]], x_step_size: float = 1):
167172
"""Creates the moving average. Each line is ([x1, x2, ...], [y1, y2, ...]), with the x in order from low to high."""
168173
self._lines = list(lines)
169174
self._x_step_size = x_step_size
170175
if x_step_size <= 0:
171176
raise ValueError(f"Illegal step size: {x_step_size}")
172177

173-
def _get_min_max_x(self, min_x: Optional[float] = None, max_x: Optional[float] = None) -> Tuple[float, float]:
178+
# Calculate error bounds
179+
min_x, max_x = self._get_min_max_x()
180+
181+
x_moving_average = list()
182+
y_moving_average = list()
183+
y_moving_average_standard_deviation = list()
184+
y_moving_average_counts = list()
185+
for x in numpy.arange(min_x + 0.01, max_x - 0.01, self._x_step_size):
186+
y_values = self._get_y_values_at(x)
187+
if len(y_values) <= 1:
188+
continue
189+
190+
y_mean = numpy.nanmean(y_values)
191+
y_std = numpy.nanstd(y_values, ddof=1)
192+
193+
x_moving_average.append(x)
194+
y_moving_average.append(y_mean)
195+
y_moving_average_standard_deviation.append(y_std)
196+
y_moving_average_counts.append(len(y_values))
197+
self.x_values = numpy.array(x_moving_average, dtype=numpy.float32)
198+
self.mean_values = numpy.array(y_moving_average, dtype=numpy.float32)
199+
self.standard_deviation_values = numpy.array(y_moving_average_standard_deviation, dtype=numpy.float32)
200+
self.counts_in_standard_deviation_values = numpy.array(y_moving_average_counts, dtype=numpy.uint16)
201+
202+
def _get_min_max_x(self) -> Tuple[float, float]:
174203
"""Gets the lowest and highest x values used in the lines. Returns (0, 1) if no lines are available."""
175204
found_min_x = None
176205
found_max_x = None
177206
for line_x, _ in self._lines:
178207
found_min_x = min_none(line_x[0], found_min_x)
179208
found_max_x = max_none(line_x[-1], found_max_x)
180209

181-
# Let min_x and max_x override the found values
182-
if min_x is not None and found_min_x is not None and min_x > found_min_x:
183-
found_min_x = min_x
184-
if max_x is not None and found_max_x is not None and max_x < found_max_x:
185-
found_max_x = max_x
186-
187210
if found_min_x is None:
188211
return 0, 1
189212
if found_max_x == found_min_x:
@@ -222,32 +245,26 @@ def count_values_at_min_max_x(self) -> Tuple[int, int]:
222245

223246
def plot(self, axes: Axes, *, color: Color = Color(0, 0, 255), linewidth=2, error_opacity=0.8,
224247
standard_error: bool = False, label="Average", min_x: Optional[float] = None, max_x: Optional[float] = None):
225-
min_x, max_x = self._get_min_max_x(min_x, max_x)
226-
227-
# Calculate error bounds
228-
x_error_values = list()
229-
y_error_values_min = list()
230-
y_error_values_mean = list()
231-
y_error_values_max = list()
232-
for x in numpy.arange(min_x + 0.01, max_x - 0.01, self._x_step_size):
233-
y_values = self._get_y_values_at(x)
234-
if len(y_values) <= 1:
235-
continue
236-
237-
y_mean = numpy.nanmean(y_values)
238-
y_error = numpy.nanstd(y_values, ddof=1)
239-
if standard_error:
240-
y_error /= numpy.sqrt(len(y_values))
241-
242-
x_error_values.append(x)
243-
y_error_values_min.append(y_mean - y_error)
244-
y_error_values_mean.append(y_mean)
245-
y_error_values_max.append(y_mean + y_error)
248+
# Find which x values to plot
249+
if min_x is None:
250+
min_x = self.x_values[0]
251+
if max_x is None:
252+
max_x = self.x_values[-1]
253+
x_mask = (self.x_values >= min_x) & (self.x_values <= max_x)
254+
255+
# Calculate means and error bars
256+
x_values = self.x_values[x_mask]
257+
mean_values = self.mean_values[x_mask]
258+
y_error_values = self.standard_deviation_values[x_mask]
259+
if standard_error:
260+
y_error_values /= numpy.sqrt(self.counts_in_standard_deviation_values[x_mask])
261+
y_error_values_min = mean_values - y_error_values
262+
y_error_values_max = mean_values + y_error_values
246263

247264
# Plot
248-
if len(x_error_values) > 0:
249-
axes.plot(x_error_values, y_error_values_mean, color=color.to_rgb_floats(), linewidth=linewidth, label=label)
250-
axes.fill_between(x_error_values, y_error_values_min,
265+
if len(x_values) > 0:
266+
axes.plot(x_values, mean_values, color=color.to_rgb_floats(), linewidth=linewidth, label=label)
267+
axes.fill_between(x_values, y_error_values_min,
251268
y_error_values_max, color=color.to_rgb_floats(), alpha=1 - error_opacity, linewidth=0)
252269

253270
def get_x_positions_and_means(self) -> Tuple[ndarray, ndarray]:

0 commit comments

Comments
 (0)