wolfhece 2.2.29__py3-none-any.whl → 2.2.31__py3-none-any.whl

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.
wolfhece/analyze_poly.py CHANGED
@@ -4,10 +4,11 @@ import numpy as np
4
4
  from shapely.geometry import Point, LineString
5
5
  from typing import Literal
6
6
  import pandas as pd
7
+ from pathlib import Path
7
8
 
8
9
  from .PyTranslate import _
9
10
  from .drawing_obj import Element_To_Draw
10
- from .PyVertexvectors import Triangulation, vector,Zones, zone
11
+ from .PyVertexvectors import Triangulation, vector,Zones, zone, Polygon
11
12
  from .wolf_array import WolfArray, header_wolf
12
13
 
13
14
  class Array_analysis_onepolygon():
@@ -20,63 +21,92 @@ class Array_analysis_onepolygon():
20
21
  Plots of the values distribution can be generated using seaborn or plotly.
21
22
  """
22
23
 
23
- def __init__(self, wa:WolfArray, polygon:vector):
24
+ def __init__(self, wa:WolfArray, polygon:vector, buffer_size:float = 0.0):
24
25
 
25
26
  self._wa = wa
26
- self._polygon = polygon
27
+
28
+ if buffer_size > 0.0:
29
+ self._polygon = polygon.buffer(buffer_size, inplace=False)
30
+ elif buffer_size == 0.0:
31
+ self._polygon = polygon
32
+ else:
33
+ raise ValueError("Buffer size must be greater than or equal to 0.0.")
27
34
 
28
35
  self._selected_cells = None
29
36
  self._values = None
30
37
 
31
- def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values']) -> pd.DataFrame | float:
38
+ def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values', 'Area']) -> pd.DataFrame | float:
32
39
  """ Get the values as a pandas DataFrame
33
40
 
34
41
  :param which: Mean, Std, Median, Sum, Volume, Values
35
42
  """
36
43
 
37
- authrorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values']
38
- if which not in authrorized:
39
- raise ValueError(f"Invalid value for 'which'. Must be one of {authrorized}.")
44
+ authorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values', 'Area']
45
+ if which not in authorized:
46
+ raise ValueError(f"Invalid value for 'which'. Must be one of {authorized}.")
40
47
 
41
48
  if self._values is None:
42
49
  self.compute_values()
43
50
 
51
+ if which == 'Area' and 'Area' not in self._values:
52
+ self._add_area2velues()
53
+
44
54
  if self._values is None:
45
55
  raise ValueError("No values computed. Please call compute_values() first.")
46
56
 
47
57
  if which == 'Values':
48
- return pd.DataFrame(self._values[_(which)], columns=[which])
58
+ return pd.DataFrame(self._values[_(which)], columns=[self._polygon.myname])
49
59
  else:
50
60
  return self._values[which]
51
61
 
52
62
  def select_cells(self, mode:Literal['polygon', 'buffer'] = 'polygon', **kwargs):
53
- """ Select the cells inside the polygon """
63
+ """Select the cells inside the polygon.
64
+
65
+ :param mode: 'polygon' or 'buffer'
66
+ :param kwargs: 'polygon' for polygon selection or 'buffer' for buffer size
67
+
68
+ For polygon selection, the polygon must be provided in kwargs or use the polygon set during initialization.
69
+ For buffer selection, the buffer size in meter must be provided in kwargs.
70
+ """
54
71
 
55
72
  if mode == 'polygon':
56
73
  if 'polygon' in kwargs:
57
74
  self._polygon = kwargs['polygon']
58
- self._select_cells_polygon(self._polygon)
59
- else:
75
+ elif self._polygon is None:
60
76
  raise ValueError("No polygon provided. Please provide a polygon to select cells.")
77
+ self._select_cells_polygon(self._polygon)
78
+
61
79
  elif mode == 'buffer':
62
- if 'buffer' in kwargs:
80
+ if 'buffer' in kwargs and self._polygon is not None:
63
81
  self._select_cells_buffer(kwargs['buffer'])
64
82
  else:
65
83
  raise ValueError("No buffer size provided. Please provide a buffer size to select cells.")
66
84
  else:
67
85
  raise ValueError("Invalid mode. Please use 'polygon' or 'buffer'.")
68
86
 
69
- def _select_cells_polygon(self, selection_poly:vector):
87
+ def _select_cells_polygon(self, selection_poly:vector = None):
70
88
  """ Select the cells inside the polygon """
71
89
 
72
- self._polygon = selection_poly
73
- self._selected_cells = self._wa.get_xy_inside_polygon(self._polygon)
90
+ if selection_poly is None:
91
+ if self._polygon is None:
92
+ raise ValueError("No polygon provided. Please provide a polygon to select cells.")
93
+ selection_poly = self._polygon
94
+ else:
95
+ self._polygon = selection_poly
96
+
97
+ self._selected_cells = self._wa.get_xy_inside_polygon(selection_poly)
74
98
 
75
99
  def _select_cells_buffer(self, buffer_size:float = 0.0):
76
100
  """ Select the cells inside the buffer of the polygon """
77
101
 
78
- self._polygon = self._polygon.buffer(buffer_size, inplace=False)
79
- self._selected_cells = self._wa.get_xy_inside_polygon(self._polygon)
102
+ if buffer_size > 0.0:
103
+ selection_poly = self._polygon.buffer(buffer_size, inplace=False)
104
+ elif buffer_size == 0.0:
105
+ selection_poly = self._polygon
106
+ else:
107
+ raise ValueError("Buffer size must be greater than or equal to 0.0.")
108
+
109
+ self._selected_cells = self._wa.get_xy_inside_polygon(selection_poly)
80
110
 
81
111
  def compute_values(self):
82
112
  """ Get the values of the array inside the polygon """
@@ -85,7 +115,43 @@ class Array_analysis_onepolygon():
85
115
  if self._polygon is None:
86
116
  raise ValueError("No polygon provided. Please provide a polygon to select cells.")
87
117
 
88
- self._values = self._wa.statistics(self._polygon)
118
+ self._values = self._wa.statistics(self._polygon)
119
+ else:
120
+ self._values = self._wa.statistics(self._selected_cells)
121
+
122
+ def _add_area2velues(self):
123
+ """ Add the area of the polygon to the values """
124
+
125
+ if self._selected_cells is None:
126
+ if self._polygon is None:
127
+ raise ValueError("No polygon provided. Please provide a polygon to select cells.")
128
+
129
+ self._values['Area'] = self._polygon.area
130
+ else:
131
+ self._values['Area'] = len(self._selected_cells) * self._wa.dx * self._wa.dy
132
+
133
+ @property
134
+ def n_selected_cells(self) -> int:
135
+ """ Get the number of selected cells """
136
+ if self._selected_cells is None:
137
+ return 0
138
+
139
+ return len(self._selected_cells)
140
+
141
+ def get_selection(self) -> np.ndarray:
142
+ """ Get the selected cells as a numpy array of coordinates.
143
+
144
+ :return: numpy array of shape (n, 2) with the coordinates of the selected cells
145
+ """
146
+ if self._selected_cells is None:
147
+ raise ValueError("No cells selected. Please call select_cells() first.")
148
+
149
+ return np.array(self._selected_cells)
150
+
151
+ def reset_selection(self):
152
+ """ Reset the selection of cells """
153
+ self._selected_cells = None
154
+ self._values = None
89
155
 
90
156
  def plot_values(self, show:bool = True, bins:int = 100,
91
157
  engine:Literal['seaborn', 'plotly'] = 'seaborn'):
@@ -153,6 +219,41 @@ class Array_analysis_onepolygon():
153
219
 
154
220
  return fig
155
221
 
222
+ @property
223
+ def has_values(self) -> bool:
224
+ """ Check if there useful values """
225
+ if self._values is None:
226
+ self.compute_values()
227
+
228
+ return len(self.values('Values')) > 0
229
+
230
+ @property
231
+ def has_strictly_positive_values(self) -> bool:
232
+ """ Check if there useful values """
233
+ if self._values is None:
234
+ self.compute_values()
235
+
236
+ return len(self.values('Values') > 0) > 0
237
+
238
+ def distribute_values(self, bins:list[float]):
239
+ """ Distribute the values in bins
240
+
241
+ :param bins: list of bin edges
242
+ :return: pandas DataFrame with the counts of values in each bin
243
+ """
244
+
245
+ if self._values is None:
246
+ self.compute_values()
247
+
248
+ if self._values is None:
249
+ raise ValueError("No values computed. Please call compute_values() first.")
250
+
251
+ values = self.values('Values')
252
+ if values is None or len(values) == 0:
253
+ raise ValueError("No values to distribute. Please compute values first.")
254
+
255
+ counts, __ = np.histogram(values, bins=bins)
256
+ return pd.DataFrame({'Bin Edges': bins[:-1], 'Counts': counts})
156
257
 
157
258
  class Array_analysis_polygons():
158
259
  """ Class for values analysis of an array based on a polygon.
@@ -164,21 +265,123 @@ class Array_analysis_polygons():
164
265
  Plots of the values distribution can be generated using seaborn or plotly.
165
266
  """
166
267
 
167
- def __init__(self, wa:WolfArray, polygons:zone):
268
+ def __init__(self, wa:WolfArray, polygons:zone, buffer_size:float = 0.0):
168
269
  """ Initialize the class with a WolfArray and a zone of polygons """
169
270
 
170
271
  self._wa = wa
171
- self._polygons = polygons
272
+ self._polygons = polygons # pointer to the original zone of polygons
273
+ self._check_names()
274
+
275
+ self._has_buffer = buffer_size > 0.0
276
+
277
+ self._zone = {polygon.myname: Array_analysis_onepolygon(self._wa, polygon, buffer_size) for polygon in self._polygons.myvectors if polygon.used}
278
+
279
+ self._active_categories = self.all_categories
280
+
281
+ @property
282
+ def _areas(self) -> list[float]:
283
+ """ Get the areas of the polygons in the zone """
284
+ return [poly.area for poly in self.polygons.myvectors if poly.used]
285
+
286
+ @property
287
+ def all_categories(self) -> list[str]:
288
+ """ Get the name of the building categories from the Polygons """
172
289
 
173
- self._zone = {polygon.myname: Array_analysis_onepolygon(self._wa, polygon) for polygon in self._polygons.myvectors}
290
+ return list(set([v.myname.split('___')[0] for v in self._polygons.myvectors if v.used]))
174
291
 
175
- def __getitem__(self, key):
292
+ @property
293
+ def active_categories(self) -> list[str]:
294
+ """ Get the active categories for the analysis """
295
+ return self._active_categories
296
+
297
+ @active_categories.setter
298
+ def active_categories(self, categories:list[str]):
299
+ """ Set the active categories for the analysis
300
+
301
+ :param categories: list of categories to activate
302
+ """
303
+ if not categories:
304
+ raise ValueError("The list of categories must not be empty.")
305
+
306
+ all_categories = self.all_categories
307
+ for cat in categories:
308
+ if cat not in all_categories:
309
+ logging.debug(f"Category '{cat}' is not a valid category.")
310
+
311
+ self._active_categories = categories
312
+
313
+ def activate_category(self, category_name:str):
314
+ """ Activate a category for the analysis
315
+
316
+ :param category_name: name of the category to activate
317
+ """
318
+ if category_name not in self.all_categories:
319
+ raise ValueError(f"Category '{category_name}' is not a valid category. Available categories: {self.all_categories}")
320
+
321
+ if category_name not in self._active_categories:
322
+ self._active_categories.append(category_name)
323
+
324
+ def deactivate_category(self, category_name:str):
325
+ """ Deactivate a category for the analysis
326
+
327
+ :param category_name: name of the category to deactivate
328
+ """
329
+ if category_name not in self._active_categories:
330
+ raise ValueError(f"Category '{category_name}' is not active. Active categories: {self._active_categories}")
331
+
332
+ self._active_categories.remove(category_name)
333
+
334
+ def _check_names(self):
335
+ """ Check if the names of the polygons are unique """
336
+ names = [poly.myname for poly in self._polygons.myvectors if poly.used]
337
+ if len(names) != len(set(names)):
338
+ raise ValueError("Polygon names must be unique. Please rename the polygons in the zone.")
339
+
340
+ def __getitem__(self, key:str) -> Array_analysis_onepolygon:
176
341
  """ Get the polygon by name """
177
342
  if key in self._zone:
178
343
  return self._zone[key]
179
344
  else:
180
345
  raise KeyError(f"Polygon {key} not found in zone.")
181
346
 
347
+ def reset_selection(self):
348
+ """ Reset the selection of cells in all polygons """
349
+ for poly in self._zone.values():
350
+ poly.reset_selection()
351
+
352
+ def get_values(self) -> pd.DataFrame:
353
+ """ Get the values of all polygons in the zones as a pandas DataFrame.
354
+
355
+ One column per polygon with the values."""
356
+
357
+ lst = [pol.values('Values') for key, pol in self._zone.items() if pol.has_values and key.split('___')[0] in self._active_categories]
358
+ return pd.concat(lst, axis=1)
359
+
360
+ @property
361
+ def polygons(self) -> zone:
362
+ """ Get the zone of polygons """
363
+ if self._has_buffer:
364
+ # return a new zone with the polygons and their buffers
365
+ ret_zone = zone(name = self._polygons.myname)
366
+ for name, poly in self._zone.items():
367
+ if name.split('___')[0] in self._active_categories:
368
+ ret_zone.add_vector(poly._polygon)
369
+
370
+ return ret_zone
371
+ else:
372
+ # return the original zone of polygons
373
+ return self._polygons
374
+
375
+ @property
376
+ def keys(self) -> list[str]:
377
+ """ Get the names of the polygons in the zone """
378
+ return list(self._zone.keys())
379
+
380
+ def update_values(self):
381
+ """ Update the polygons values in the zone """
382
+ for poly in self._zone.values():
383
+ poly.compute_values()
384
+
182
385
  def plot_values(self, show:bool = True, bins:int = 100,
183
386
  engine:Literal['seaborn', 'plotly'] = 'seaborn'):
184
387
  """ Plot a histogram of the values """
@@ -190,13 +393,818 @@ class Array_analysis_polygons():
190
393
 
191
394
  def plot_values_seaborn(self, bins:int = 100, show:bool = True):
192
395
  """ Plot a histogram of the values """
193
- return {key: pol.plot_values_seaborn(bins=bins, show=show) for key, pol in self._zone.items()}
396
+ return {key: pol.plot_values_seaborn(bins=bins, show=show) for key, pol in self._zone.items() if key.split('___')[0] in self._active_categories}
397
+
398
+ def plot_values_plotly(self, bins:int = 100, show:bool = True):
399
+ """ Plot a histogram of the values """
400
+
401
+ return {key: pol.plot_values_plotly(bins=bins, show=show) for key, pol in self._zone.items() if key.split('___')[0] in self._active_categories}
402
+
403
+ def count_strictly_positive(self) -> int:
404
+ """ Count the number of polygons with values greater than zero """
405
+ nb = 0
406
+ for key, poly in self._zone.items():
407
+ if key.split('___')[0] not in self._active_categories:
408
+ continue
409
+ if poly.has_strictly_positive_values:
410
+ nb += 1
411
+ return nb
412
+
413
+ def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']) -> pd.Series:
414
+ """ Get the values as a pandas DataFrame
415
+
416
+ :param which: Mean, Std, Median, Sum, Volume
417
+ :return: pandas DataFrame with the values for each polygon
418
+ """
419
+
420
+ authorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']
421
+ if which not in authorized:
422
+ raise ValueError(f"Invalid value for 'which'. Must be one of {authorized}.")
423
+
424
+ values = {name: poly.values(which) for name, poly in self._zone.items() if poly.has_values and name.split('___')[0] in self._active_categories}
425
+
426
+ if not values:
427
+ raise ValueError("No values computed. Please compute values first.")
428
+
429
+ return pd.Series(values)
430
+
431
+ def distribute_polygons(self, bins:list[float], operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area']) -> pd.DataFrame:
432
+ """ Distribute the values of each polygon in bins
433
+
434
+ :param bins: list of bin edges
435
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
436
+ :return: pandas DataFrame with the counts of values in each bin for each polygon
437
+ """
438
+
439
+ values = np.asarray([poly.values(operator) for key, poly in self._zone.items() if poly.has_values and key.split('___')[0] in self._active_categories] )
440
+
441
+ if values.size == 0:
442
+ raise ValueError("No values to distribute. Please compute values first.")
443
+
444
+ counts, __ = np.histogram(values, bins=bins)
445
+ distribution = pd.DataFrame({'Bin Edges': bins[:-1], 'Counts': counts})
446
+
447
+ return distribution
448
+
449
+ def plot_distributed_values(self, bins:list[float],
450
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'],
451
+ show:bool = True,
452
+ engine:Literal['seaborn', 'plotly'] = 'seaborn'):
453
+ """ Plot the distribution of values in bins for each polygon
454
+
455
+ :param bins: list of bin edges
456
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
457
+ :param show: whether to show the plot
458
+ :param engine: 'seaborn' or 'plotly'
459
+ """
460
+
461
+ distribution = self.distribute_polygons(bins, operator)
462
+
463
+ if engine == 'seaborn':
464
+ fig, ax = self.plot_distribution_seaborn(distribution, show=show)
465
+ elif engine == 'plotly':
466
+ fig, ax = self.plot_distribution_plotly(distribution, show=show)
467
+
468
+ ax.set_title(f'Distribution of Values ({operator})')
469
+
470
+ return fig, ax
471
+
472
+ def plot_distribution_seaborn(self, distribution:pd.DataFrame, show:bool = True):
473
+ """ Plot the distribution of values in bins using seaborn
474
+
475
+ :param distribution: pandas DataFrame with the counts of values in each bin
476
+ :param show: whether to show the plot
477
+ """
478
+
479
+ import seaborn as sns
480
+ import matplotlib.pyplot as plt
481
+
482
+ fig, ax = plt.subplots()
483
+ sns.barplot(x='Bin Edges', y='Counts', data=distribution, ax=ax)
484
+
485
+ ax.set_xlabel('Bin Edges')
486
+ ax.set_ylabel('Counts')
487
+ ax.set_title('Distribution of Values')
488
+
489
+ if show:
490
+ plt.show()
491
+
492
+ return (fig, ax)
493
+
494
+ def plot_distribution_plotly(self, distribution:pd.DataFrame, show:bool = True):
495
+ """ Plot the distribution of values in bins using plotly
496
+
497
+ :param distribution: pandas DataFrame with the counts of values in each bin
498
+ :param show: whether to show the plot
499
+ """
500
+
501
+ import plotly.express as px
502
+
503
+ fig = px.bar(distribution, x='Bin Edges', y='Counts',
504
+ title='Distribution of Values')
505
+
506
+ fig.update_layout(xaxis_title='Bin Edges', yaxis_title='Counts')
507
+
508
+ if show:
509
+ fig.show(renderer='browser')
510
+
511
+ return fig
512
+
513
+ class Array_analysis_zones():
514
+ """ Class for values analysis of an array based on a Zones instance.
515
+
516
+ This class select values insides a zone of polygons and plot statistics of the values.
517
+ """
518
+
519
+ def __init__(self, wa:WolfArray, zones:Zones, buffer_size:float = 0.0):
520
+ """ Initialize the class with a Wolf Zones """
521
+
522
+ self._wa = wa
523
+ self._zones = zones
524
+
525
+ self._polygons = {zone.myname: Array_analysis_polygons(self._wa, zone, buffer_size) for zone in self._zones.myzones if zone.used}
526
+
527
+ def __getitem__(self, key:str) -> Array_analysis_polygons:
528
+ """ Get the zone by name """
529
+ if key in self._polygons:
530
+ return self._polygons[key]
531
+ else:
532
+ raise KeyError(f"Zone {key} not found in zones.")
533
+
534
+ def reset_selection(self):
535
+ """ Reset the selection of cells in all polygons """
536
+ for poly in self._polygons.values():
537
+ poly.reset_selection()
538
+
539
+ @property
540
+ def keys(self) -> list[str]:
541
+ """ Get the names of the polygons in the zones """
542
+ return list(self._polygons.keys())
543
+
544
+ def update_values(self):
545
+ """ Update the polygons values in the zones """
546
+ for pol in self._polygons.values():
547
+ pol.update_values()
548
+
549
+ def get_values(self) -> dict[str, pd.DataFrame]:
550
+ """ Get the values of all polygons in the zones as a dictionary of pandas DataFrames """
551
+
552
+ return {key: pol.get_values() for key, pol in self._polygons.items()}
553
+
554
+ def plot_values(self, show:bool = True, bins:int = 100,
555
+ engine:Literal['seaborn', 'plotly'] = 'seaborn'):
556
+ """ Plot a histogram of the values """
557
+ if engine == 'seaborn':
558
+ return self.plot_values_seaborn(show=show, bins=bins)
559
+ elif engine == 'plotly':
560
+ return self.plot_values_plotly(show=show, bins=bins)
561
+
562
+ def plot_values_seaborn(self, bins:int = 100, show:bool = True):
563
+ """ Plot a histogram of the values """
564
+ return {key: pol.plot_values_seaborn(bins=bins, show=show) for key, pol in self._polygons.items()}
194
565
 
195
566
  def plot_values_plotly(self, bins:int = 100, show:bool = True):
196
567
  """ Plot a histogram of the values """
568
+ return {key: pol.plot_values_plotly(bins=bins, show=show) for key, pol in self._polygons.items()}
569
+
570
+ def distribute_zones(self, bins:list[float],
571
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area']) -> dict[str, pd.DataFrame]:
572
+ """ Distribute the values of each zone in bins
573
+
574
+ :param bins: list of bin edges
575
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
576
+ :return: pandas DataFrame with the counts of values in each bin for each zone
577
+ """
578
+
579
+ return {key: pol.distribute_polygons(bins, operator) for key, pol in self._polygons.items()}
580
+
581
+ def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']) -> dict[str, pd.Series]:
582
+ """ Get the values as a dictionnary of pandas Series
583
+
584
+ :param which: Mean, Std, Median, Sum, Volume
585
+ :return: pandas DataFrame with the values for each polygon
586
+ """
587
+
588
+ authorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']
589
+ if which not in authorized:
590
+ raise ValueError(f"Invalid value for 'which'. Must be one of {authorized}.")
591
+
592
+ values = {name: pol.values(which) for name, pol in self._polygons.items()}
593
+
594
+ if not values:
595
+ raise ValueError("No values computed. Please compute values first.")
596
+
597
+ return values
598
+
599
+ class Arrays_analysis_zones():
600
+ """
601
+ Class for analysis multiples arrays based on a Zones instance.
602
+ Each array must have the same shape.
603
+ """
604
+
605
+ def __init__(self, arrays:dict[str, WolfArray], zones:Zones, buffer_size:float = 0.0):
606
+ """ Initialize the class with a list of WolfArray and a Zones instance """
607
+ if not arrays:
608
+ raise ValueError("The list of arrays must not be empty.")
609
+
610
+ self._arrays = arrays
611
+ self._zones = zones
612
+ self._xlabel = 'Value'
613
+
614
+ # Check that all arrays have the same shape
615
+ ref = next(iter(arrays.values()))
616
+ for array in arrays.values():
617
+ if not array.is_like(ref):
618
+ raise ValueError("All arrays must have the same shape.")
619
+
620
+ self._polygons = {zone.myname: {key: Array_analysis_polygons(array, zone, buffer_size) for key, array in self._arrays.items()}
621
+ for zone in self._zones.myzones if zone.used }
622
+
623
+ self._active_categories = self.all_categories
624
+ self._active_arrays = self.all_arrays
625
+
626
+ @property
627
+ def _areas(self) -> dict[str, list[float]]:
628
+ """ Get the areas of the polygons in the zones """
629
+ return {polygons.myname: [poly.area for poly in polygons.myvectors if poly.used]
630
+ for polygons in self._zones.myzones}
631
+
632
+ @property
633
+ def all_categories(self) -> list[str]:
634
+ """ Get the name of the building categories from the Zones """
635
+
636
+ return sorted(list(set([v.myname.split('___')[0] for z in self._zones.myzones for v in z.myvectors])))
637
+
638
+ @property
639
+ def all_arrays(self) -> list[str]:
640
+ """ Get the names of all arrays """
641
+ return list(self._arrays.keys())
642
+
643
+ def activate_array(self, array_name:str):
644
+ """ Activate an array for the analysis
645
+
646
+ :param array_name: name of the array to activate
647
+ """
648
+ if array_name not in self.all_arrays:
649
+ raise ValueError(f"Array '{array_name}' is not a valid array. Available arrays: {self.all_arrays}")
650
+
651
+ if array_name not in self._active_arrays:
652
+ self._active_arrays.append(array_name)
653
+
654
+ def deactivate_array(self, array_name:str):
655
+ """ Deactivate an array for the analysis
656
+
657
+ :param array_name: name of the array to deactivate
658
+ """
659
+ if array_name not in self._active_arrays:
660
+ raise ValueError(f"Array '{array_name}' is not active. Active arrays: {self._active_arrays}")
661
+
662
+ self._active_arrays.remove(array_name)
663
+
664
+ def activate_category(self, category_name:str):
665
+ """ Activate a category for the analysis
666
+
667
+ :param category_name: name of the category to activate
668
+ """
669
+ if category_name not in self.all_categories:
670
+ raise ValueError(f"Category '{category_name}' is not a valid category. Available categories: {self.all_categories}")
671
+
672
+ if category_name not in self._active_categories:
673
+ self._active_categories.append(category_name)
674
+
675
+ for zone_name, dct in self._polygons.items():
676
+ for array_name, poly in dct.items():
677
+ poly.active_categories = self._active_categories
678
+
679
+ def deactivate_category(self, category_name:str):
680
+ """ Deactivate a category for the analysis
681
+
682
+ :param category_name: name of the category to deactivate
683
+ """
684
+ if category_name not in self._active_categories:
685
+ raise ValueError(f"Category '{category_name}' is not active. Active categories: {self._active_categories}")
686
+
687
+ self._active_categories.remove(category_name)
688
+
689
+ for zone_name, dct in self._polygons.items():
690
+ for array_name, poly in dct.items():
691
+ poly.active_categories = self._active_categories
692
+
693
+ @property
694
+ def active_arrays(self) -> list[str]:
695
+ """ Get the active arrays for the analysis """
696
+ return self._active_arrays
697
+
698
+ @active_arrays.setter
699
+ def active_arrays(self, arrays:list[str]):
700
+ """ Set the active arrays for the analysis
701
+
702
+ :param arrays: list of arrays to activate
703
+ """
704
+ if not arrays:
705
+ raise ValueError("The list of arrays must not be empty.")
706
+
707
+ all_arrays = self.all_arrays
708
+ for arr in arrays:
709
+ if arr not in all_arrays:
710
+ raise ValueError(f"Array '{arr}' is not a valid array. Available arrays: {all_arrays}")
711
+
712
+ self._active_arrays = arrays
713
+
714
+ @property
715
+ def active_categories(self) -> list[str]:
716
+ """ Get the active categories for the analysis """
717
+ return self._active_categories
718
+
719
+ @active_categories.setter
720
+ def active_categories(self, categories:list[str]):
721
+ """ Set the active categories for the analysis
722
+
723
+ :param categories: list of categories to activate
724
+ """
725
+ if not categories:
726
+ raise ValueError("The list of categories must not be empty.")
727
+
728
+ all_categories = self.all_categories
729
+ for cat in categories:
730
+ if cat not in all_categories:
731
+ raise ValueError(f"Category '{cat}' is not a valid category. Available categories: {all_categories}")
732
+
733
+ self._active_categories = categories
734
+
735
+ for zone_name, dct in self._polygons.items():
736
+ for array_name, poly in dct.items():
737
+ poly.active_categories = self._active_categories
738
+
739
+ def get_values(self) -> dict[str, dict[str, pd.DataFrame]]:
740
+ """ Get the values of all polygons in the zones as a dictionary of pandas DataFrames """
741
+
742
+ values = {}
743
+ for zone_name, polygons in self._polygons.items():
744
+ values[zone_name] = {}
745
+ for array_name, polygon in polygons.items():
746
+ values[zone_name][array_name] = polygon.get_values()
747
+
748
+ return values
749
+
750
+ def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']) -> dict[str, dict[str, pd.Series]]:
751
+ """ Get the values of all polygons in the zones as a dictionary of pandas Series
752
+
753
+ :param which: Mean, Std, Median, Sum, Volume, Area
754
+ :return: dictionary with zone names as keys and dictionaries of array names and their values as values
755
+ """
756
+
757
+ authorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area']
758
+ if which not in authorized:
759
+ raise ValueError(f"Invalid value for 'which'. Must be one of {authorized}.")
760
+
761
+ values = {}
762
+ for zone_name, polygons in self._polygons.items():
763
+ values[zone_name] = {}
764
+ for array_name, polygon in polygons.items():
765
+ if array_name not in self._active_arrays:
766
+ continue
767
+ values[zone_name][array_name] = polygon.values(which)
768
+
769
+ return values
770
+
771
+ def update_values(self):
772
+ """ Update the polygons values in the zones for all arrays """
773
+ for polygons in self._polygons.values():
774
+ for polygon in polygons.values():
775
+ polygon.update_values()
776
+
777
+ def count_strictly_positive(self) -> dict[str, int]:
778
+ """ Count the number of polygons with values greater than zero for each array """
779
+ import concurrent.futures
780
+
781
+ counts = {}
782
+ for zone_name, polygons in self._polygons.items():
783
+ counts[zone_name] = {}
784
+ with concurrent.futures.ThreadPoolExecutor() as executor:
785
+ futures = {}
786
+ for array_name, polygon in polygons.items():
787
+ if array_name not in self._active_arrays:
788
+ continue
789
+ futures[array_name] = executor.submit(polygon.count_strictly_positive)
790
+ for array_name, future in futures.items():
791
+ counts[zone_name][array_name] = future.result()
792
+
793
+ return counts
794
+
795
+ def count_strictly_positive_as_df(self, merge_zones: bool = False) -> pd.DataFrame:
796
+ """ Count the number of polygons with strictly positive values for each array as a pandas DataFrame
797
+
798
+ :return: pandas DataFrame with the counts of strictly positive values for each array in each zone
799
+ """
800
+
801
+ counts = self.count_strictly_positive()
802
+
803
+ df = pd.DataFrame({'Zone': zone_name, 'Array': array_name, 'Count': count}
804
+ for zone_name, arrays in counts.items()
805
+ for array_name, count in arrays.items())
806
+ if merge_zones:
807
+ # Sum counts across zones for each array
808
+ df = df.groupby('Array', as_index=False).sum()
809
+ # remove the 'Zone' column
810
+ df = df.drop(columns=['Zone'])
811
+
812
+ return df
813
+
814
+ def distribute_zones(self, bins:list[float],
815
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area']) -> dict[str, dict[str, pd.DataFrame]]:
816
+ """ Distribute the values of each zone in bins for each array
817
+
818
+ :param bins: list of bin edges
819
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
820
+ :return: dictionary with zone names as keys and dictionaries of array names and their distributions as values
821
+ """
822
+
823
+ distributions = {}
824
+ for zone_name, polygons in self._polygons.items():
825
+ distributions[zone_name] = {}
826
+ for array_name, polygon in polygons.items():
827
+ if array_name not in self._active_arrays:
828
+ continue
829
+ distributions[zone_name][array_name] = polygon.distribute_polygons(bins, operator)
830
+
831
+ return distributions
832
+
833
+ def distribute_zones_as_df(self, bins:list[float],
834
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'],
835
+ merge_zones:bool = False) -> pd.DataFrame:
836
+ """ Distribute the values of each zone in bins for each array as a pandas DataFrame.
837
+
838
+ Date are tabulated in a DataFrame with columns 'Zone', 'Array', 'Bin Edges', 'Count'.
839
+ It is more convenient for plotting and analysis.
840
+
841
+ :param bins: list of bin edges
842
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
843
+ :return: pandas DataFrame with the counts of values in each bin for each array in each zone
844
+ """
845
+
846
+ distributions = self.distribute_zones(bins, operator)
847
+ df = pd.DataFrame({'Zone': zone_name, 'Array': array, 'Bin Edges': bin, 'Count': count}
848
+ for zone_name, arrays in distributions.items()
849
+ for array, bins_counts in arrays.items()
850
+ for bin, count in zip(bins_counts['Bin Edges'], bins_counts['Counts']))
851
+ if merge_zones:
852
+ # Sum counts across zones for each array
853
+ df = df.groupby(['Array', 'Bin Edges'], as_index=False).sum()
854
+ # remove the 'Zone' column
855
+ df = df.drop(columns=['Zone'])
856
+ return df
857
+
858
+ def _values_as_df(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Area'],
859
+ merge_zones:bool = False) -> pd.DataFrame:
860
+ """ Get a full DataFrame with all arrays, zones and values for each polygon.
861
+
862
+ :param merge_zones: whether to merge the zones in the DataFrame
863
+ :return: pandas DataFrame with the counts of strictly positive values for each array in each zone
864
+ """
865
+ dct = self.values(which)
866
+
867
+ df = pd.DataFrame({'Zone': zone_name, 'Array': array_name, 'Value': value}
868
+ for zone_name, arrays in dct.items()
869
+ for array_name, value in arrays.items()
870
+ for value in value.values)
871
+ if merge_zones:
872
+ # remove the 'Zone' column
873
+ df = df.drop(columns=['Zone'])
874
+
875
+ return df
876
+
877
+ def plot_count_strictly_positive(self, show:bool = True,
878
+ engine:Literal['seaborn', 'plotly'] = 'seaborn',
879
+ merge_zones:bool = False):
880
+ """ Plot the count of strictly positive values for each array in each zone
881
+
882
+ :param show: whether to show the plot
883
+ :param engine: 'seaborn' or 'plotly'
884
+ """
885
+
886
+ if engine == 'seaborn':
887
+ return self._plot_count_strictly_positive_seaborn(show=show, merge_zones=merge_zones)
888
+ elif engine == 'plotly':
889
+ return self.plot_count_strictly_positive_plotly(show=show, merge_zones=merge_zones)
890
+
891
+ def _plot_count_strictly_positive_seaborn(self, show:bool = True, merge_zones:bool = False):
892
+ """ Plot the count of strictly positive values for each array in each zone using seaborn
893
+
894
+ :param counts: dictionary with zone names as keys, and dictionaries of array names and their counts as values
895
+ :param show: whether to show the plot
896
+ """
897
+
898
+ import seaborn as sns
899
+ import matplotlib.pyplot as plt
900
+
901
+ df = self.count_strictly_positive_as_df(merge_zones=merge_zones)
902
+
903
+ fig, ax = plt.subplots()
904
+
905
+ if merge_zones:
906
+ # If merging zones, we only have 'Array' and 'Count' columns
907
+ sns.barplot(x='Array', y='Count', data=df, ax=ax)
908
+ else:
909
+ sns.barplot(x='Array', y='Count', hue='Zone', data=df, ax=ax)
910
+
911
+ ax.set_xlabel('Array')
912
+ ax.set_ylabel('Count of strictly positive values')
913
+ ax.set_title('Count of strictly positive values')
914
+ plt.tight_layout()
915
+
916
+ if show:
917
+ plt.show()
918
+
919
+ return (fig, ax)
920
+
921
+ def _plot_count_strictly_positive_plotly(self, show:bool = True, merge_zones:bool = False):
922
+ """ Plot the count of strictly positive values for each array in each zone using plotly
923
+
924
+ :param counts: dictionary with zone names as keys, and dictionaries of array names and their counts as values
925
+ :param show: whether to show the plot
926
+ """
927
+
928
+ import plotly.express as px
929
+
930
+ df = self.count_strictly_positive_as_df(merge_zones=merge_zones)
931
+
932
+ if merge_zones:
933
+ fig = px.bar(df, x='Array', y='Count', title='Count of strictly positive values',
934
+ labels={'Count': 'Count of strictly positive values'})
935
+ else:
936
+ fig = px.bar(df, x='Array', y='Count', color='Zone',
937
+ title='Count of strictly positive values',
938
+ labels={'Count': 'Count of strictly positive values'})
939
+
940
+ fig.update_layout(xaxis_title='Array', yaxis_title='Count of strictly positive values')
941
+
942
+ if show:
943
+ fig.show(renderer='browser')
944
+
945
+ return fig
946
+
947
+ def plot_distributed_values(self, bins:list[float]= [0., .3, 1.3, -1.],
948
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'] = 'Median',
949
+ show:bool = True,
950
+ engine:Literal['seaborn', 'plotly'] = 'seaborn',
951
+ merge_zones:bool = False):
952
+ """ Plot the distribution of values in bins for each array in each zone or merged zones.
953
+ :param bins: list of bin edges
954
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
955
+ :param show: whether to show the plot
956
+ :param engine: 'seaborn' or 'plotly'
957
+ :param merge_zones: whether to merge the zones in the plot
958
+ """
959
+
960
+ if engine == 'seaborn':
961
+ return self._plot_distributed_values_seaborn(bins, operator, show=show, merge_zones=merge_zones)
962
+ elif engine == 'plotly':
963
+ return self._plot_distributed_values_plotly(bins, operator, show=show, merge_zones=merge_zones)
964
+ else:
965
+ raise ValueError(f"Invalid engine '{engine}'. Must be 'seaborn' or 'plotly'.")
966
+
967
+ def _plot_distributed_values_seaborn(self, bins:list[float],
968
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'],
969
+ show:bool = True,
970
+ merge_zones:bool = False):
971
+ """ Plot the distribution of values in bins for each array in each zone using seaborn
972
+
973
+ :param bins: list of bin edges
974
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
975
+ :param show: whether to show the plot
976
+ :param merge_zones: whether to merge the zones in the plot
977
+ """
978
+
979
+ import seaborn as sns
980
+ import matplotlib.pyplot as plt
981
+
982
+ df = self._values_as_df(operator, merge_zones=merge_zones)
983
+
984
+ if bins[-1] == -1:
985
+ bins[-1] = df['Value'].max()
986
+
987
+ if merge_zones:
988
+ fig, ax = plt.subplots()
989
+ sns.histplot(df, x='Value', hue='Array', multiple='stack', bins = bins, ax=ax)
990
+
991
+ # set ticks
992
+ ax.set_xticks(bins)
993
+ ax.set_xticklabels([f"{b:.2f}" for b in bins])
994
+ ax.set_xlabel(self._xlabel)
995
+ if show:
996
+ plt.show()
997
+
998
+ return (fig, ax)
999
+ else:
1000
+ # 1 plot per zone
1001
+ figs, axs = [], []
1002
+ for i, zone_name in enumerate(self._polygons.keys()):
1003
+ fig, ax = plt.subplots()
1004
+ sns.histplot(df[df['Zone'] == zone_name],
1005
+ x='Value',
1006
+ hue='Array',
1007
+ multiple='stack', bins=bins, ax=ax)
1008
+
1009
+ ax.set_xlabel(self._xlabel)
1010
+ ax.set_ylabel('Counts')
1011
+ ax.set_title(f'Distribution of Values ({zone_name})')
1012
+
1013
+ # set ticks
1014
+ ax.set_xticks(bins)
1015
+ ax.set_xticklabels([f"{b:.2f}" for b in bins])
1016
+ fig.tight_layout()
1017
+
1018
+ figs.append(fig)
1019
+ axs.append(ax)
1020
+
1021
+ if show:
1022
+ plt.show()
1023
+
1024
+ return (figs, axs)
1025
+
1026
+ def _plot_distributed_values_plotly(self, bins:list[float],
1027
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'],
1028
+ show:bool = True,
1029
+ merge_zones:bool = False):
1030
+ """ Plot the distribution of values in bins for each array in each zone using plotly
1031
+
1032
+ :param bins: list of bin edges
1033
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
1034
+ :param show: whether to show the plot
1035
+ :param merge_zones: whether to merge the zones in the plot
1036
+ """
1037
+
1038
+ import plotly.graph_objects as go
1039
+
1040
+ logging.warning("In plotly engine, the bins will be ignored and replaced by 10 automatic bins.")
1041
+
1042
+ df = self._values_as_df(operator, merge_zones=merge_zones)
1043
+
1044
+ if bins[-1] == -1:
1045
+ bins[-1] = df['Value'].max()
1046
+
1047
+ if merge_zones:
1048
+ fig = go.Figure()
1049
+ for array_name in df['Array'].unique():
1050
+ fig.add_trace(go.Histogram(x=df[df['Array'] == array_name]['Value'],
1051
+ name=array_name,
1052
+ histnorm='',
1053
+ xbins=dict(start=bins[0], end=bins[-1], size=(bins[-1] - bins[0]) / 10)))
1054
+ else:
1055
+ fig = go.Figure()
1056
+ for zone_name in df['Zone'].unique():
1057
+ for array_name in df['Array'].unique():
1058
+ fig.add_trace(go.Histogram(x=df[(df['Zone'] == zone_name) & (df['Array'] == array_name)]['Value'],
1059
+ name=f"{zone_name} - {array_name}",
1060
+ histnorm='',
1061
+ xbins=dict(start=bins[0], end=bins[-1], size=(bins[-1] - bins[0]) / 10)))
1062
+
1063
+ if show:
1064
+ fig.show(renderer='browser')
1065
+
1066
+ return fig
1067
+
1068
+ def _plot_distributed_areas_seaborn(self, bins:list[float],
1069
+ operator:Literal['Mean', 'Median', 'Sum', 'Volume', 'Area'],
1070
+ show:bool = True,
1071
+ merge_zones:bool = False):
1072
+ """ Plot the distribution of values in bins for each array in each zone using seaborn
1073
+
1074
+ :param bins: list of bin edges
1075
+ :param operator: 'Mean', 'Median', 'Sum', 'Volume', 'Area'
1076
+ :param show: whether to show the plot
1077
+ :param merge_zones: whether to merge the zones in the plot
1078
+ """
1079
+
1080
+ import seaborn as sns
1081
+ import matplotlib.pyplot as plt
1082
+
1083
+ df = self._values_as_df(operator, merge_zones=merge_zones)
1084
+ df_area = self._values_as_df('Area', merge_zones=merge_zones)
1085
+
1086
+ # # Multiply the values by the area to get the weighted distribution
1087
+ # df['Value'] = df['Value'] * df_area['Value']
1088
+
1089
+ if bins[-1] == -1:
1090
+ if df['Value'].max() > bins[-2]:
1091
+ bins[-1] = df['Value'].max()
1092
+ else:
1093
+ bins[-1] = bins[-2] + (bins[-2] - bins[-3])
1094
+
1095
+ if merge_zones:
1096
+ fig, ax = plt.subplots()
1097
+ sns.histplot(df, x='Value', hue='Array',
1098
+ multiple='stack', bins = bins, ax=ax,
1099
+ weights= df_area['Value'])
1100
+
1101
+ # set ticks
1102
+ ax.set_xticks(bins)
1103
+ ax.set_xticklabels([f"{b:.2f}" for b in bins])
1104
+ ax.set_xlabel(self._xlabel)
1105
+ ax.set_ylabel('Area [m²]')
1106
+ if show:
1107
+ plt.show()
1108
+
1109
+ return (fig, ax)
1110
+ else:
1111
+ # 1 plot per zone
1112
+ figs, axs = [], []
1113
+ for i, zone_name in enumerate(self._polygons.keys()):
1114
+ fig, ax = plt.subplots()
1115
+ sns.histplot(df[df['Zone'] == zone_name],
1116
+ x='Value',
1117
+ hue='Array',
1118
+ multiple='stack', bins=bins, ax=ax,
1119
+ weights=df_area[df_area['Zone'] == zone_name]['Value'])
1120
+
1121
+ ax.set_xlabel(self._xlabel)
1122
+ ax.set_ylabel('Area [m²]')
1123
+ ax.set_title(f'Distribution of Values ({zone_name})')
1124
+
1125
+ # set ticks
1126
+ ax.set_xticks(bins)
1127
+ ax.set_xticklabels([f"{b:.2f}" for b in bins])
1128
+ fig.tight_layout()
1129
+
1130
+ figs.append(fig)
1131
+ axs.append(ax)
1132
+
1133
+ if show:
1134
+ plt.show()
1135
+
1136
+ return (figs, axs)
1137
+
1138
+ class Building_Waterdepth_analysis(Arrays_analysis_zones):
1139
+ """ Class for water depth analysis of multiple arrays based on a Zones instance.
1140
+
1141
+ This class is designed to analyze water depth data from multiple arrays and zones.
1142
+ It inherits from Arrays_analysis_zones and provides additional methods specific to water depth analysis.
1143
+ """
1144
+
1145
+ def __init__(self, arrays:dict[str, WolfArray],
1146
+ zones:Zones | Path | str,
1147
+ buffer_size:float = 0.0,
1148
+ merge_zones:bool = False,
1149
+ thershold_area:float = 0.0):
1150
+ """ Initialize the class with a list of WolfArray and a Zones instance.
1151
+
1152
+ :param arrays: dictionary of WolfArray instances to analyze
1153
+ :param zones: Zones instance or path to a zones file
1154
+ :param buffer_size: size of the buffer around the zones (default is 0.0)
1155
+ :param merge_zones: whether to merge all zones into a single zone (default is False)
1156
+ :param thershold_area: minimum area of the polygon to consider (default is 0.0)
1157
+ :raises ValueError: if the arrays are empty or have different shapes
1158
+ :raises FileNotFoundError: if the zones file does not exist
1159
+ """
1160
+
1161
+ if isinstance(zones, (Path, str)):
1162
+
1163
+ zones = Path(zones)
1164
+ if not zones.exists():
1165
+ raise FileNotFoundError(f"Zones file {zones} does not exist.")
1166
+
1167
+ [xmin, xmax], [ymin, ymax] = arrays[next(iter(arrays))].get_bounds()
1168
+ logging.info(f"Using bounds from the first array: {xmin}, {xmax}, {ymin}, {ymax}")
1169
+ bbox = Polygon([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
1170
+ logging.info(f"Creating zones from {zones} with bounding box {bbox}")
1171
+ zones = Zones(zones, bbox=bbox)
1172
+ logging.info(f"Zones loaded from {zones}")
1173
+
1174
+ if merge_zones:
1175
+ # copy all vectors in an unique zone
1176
+ newz = Zones(idx='merged_zones')
1177
+ merged_zone = zone(name='merged_zone')
1178
+ newz.add_zone(merged_zone, forceparent= True)
1179
+
1180
+ for z in zones.myzones:
1181
+ if z.used:
1182
+ merged_zone.myvectors.extend(z.myvectors)
1183
+
1184
+ # Rename vectors adding the index position inside the zone
1185
+ for i, v in enumerate(merged_zone.myvectors):
1186
+ v.myname = f"{v.myname}___{i}"
1187
+
1188
+ zones = newz
1189
+
1190
+ for z in zones.myzones:
1191
+ if z.used:
1192
+ for v in z.myvectors:
1193
+ if v.area < thershold_area:
1194
+ logging.dbg(f"Polygon {v.myname} has an area of {v.area} which is below the threshold of {thershold_area}. It will be ignored.")
1195
+ v.used = False
1196
+
1197
+ super().__init__(arrays, zones, buffer_size)
1198
+
1199
+ self._xlabel = _('Water Depth [m]')
197
1200
 
198
- return {key: pol.plot_values_plotly(bins=bins, show=show) for key, pol in self._zone.items()}
1201
+ def plot_distributed_areas(self, bins = [0, 0.3, 1.3, -1],
1202
+ operator = 'Median',
1203
+ show = True,
1204
+ engine = 'seaborn',
1205
+ merge_zones = False):
199
1206
 
1207
+ return super()._plot_distributed_areas_seaborn(bins, operator, show=show, merge_zones=merge_zones)
200
1208
  class Slope_analysis:
201
1209
  """ Class for slope analysis of in an array based on a trace vector.
202
1210