bquant 0.0.0__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.
Files changed (49) hide show
  1. bquant/__init__.py +31 -0
  2. bquant/analysis/__init__.py +297 -0
  3. bquant/analysis/candlestick/__init__.py +100 -0
  4. bquant/analysis/chart/__init__.py +98 -0
  5. bquant/analysis/statistical/__init__.py +414 -0
  6. bquant/analysis/statistical/hypothesis_testing.py +657 -0
  7. bquant/analysis/technical/__init__.py +98 -0
  8. bquant/analysis/timeseries/__init__.py +102 -0
  9. bquant/analysis/zones/__init__.py +547 -0
  10. bquant/analysis/zones/sequence_analysis.py +661 -0
  11. bquant/analysis/zones/zone_features.py +520 -0
  12. bquant/cli.py +160 -0
  13. bquant/core/__init__.py +112 -0
  14. bquant/core/cache.py +534 -0
  15. bquant/core/config.py +328 -0
  16. bquant/core/exceptions.py +351 -0
  17. bquant/core/logging_config.py +368 -0
  18. bquant/core/numpy_fix.py +99 -0
  19. bquant/core/performance.py +554 -0
  20. bquant/core/utils.py +327 -0
  21. bquant/data/__init__.py +85 -0
  22. bquant/data/loader.py +436 -0
  23. bquant/data/processor.py +644 -0
  24. bquant/data/samples/__init__.py +442 -0
  25. bquant/data/samples/datasets.py +246 -0
  26. bquant/data/samples/embedded/__init__.py +16 -0
  27. bquant/data/samples/embedded/mt_xauusd_m15.py +9018 -0
  28. bquant/data/samples/embedded/tv_xauusd_1h.py +17018 -0
  29. bquant/data/samples/utils.py +393 -0
  30. bquant/data/schemas.py +285 -0
  31. bquant/data/validator.py +498 -0
  32. bquant/indicators/__init__.py +115 -0
  33. bquant/indicators/base.py +487 -0
  34. bquant/indicators/calculators.py +404 -0
  35. bquant/indicators/library.py +514 -0
  36. bquant/indicators/loaders.py +413 -0
  37. bquant/indicators/macd.py +754 -0
  38. bquant/ml/__init__.py +19 -0
  39. bquant/visualization/__init__.py +316 -0
  40. bquant/visualization/charts.py +616 -0
  41. bquant/visualization/statistical.py +812 -0
  42. bquant/visualization/themes.py +599 -0
  43. bquant/visualization/zones.py +775 -0
  44. bquant-0.0.0.dist-info/METADATA +167 -0
  45. bquant-0.0.0.dist-info/RECORD +49 -0
  46. bquant-0.0.0.dist-info/WHEEL +5 -0
  47. bquant-0.0.0.dist-info/entry_points.txt +2 -0
  48. bquant-0.0.0.dist-info/licenses/LICENSE +21 -0
  49. bquant-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,812 @@
1
+ """
2
+ Модуль статистических графиков BQuant
3
+
4
+ Предоставляет инструменты для создания статистических визуализаций:
5
+ - Гистограммы и распределения
6
+ - Диаграммы рассеяния
7
+ - Корреляционные матрицы
8
+ - Box plots и violin plots
9
+ - Графики временных рядов
10
+ """
11
+
12
+ import pandas as pd
13
+ import numpy as np
14
+ from typing import Dict, Any, List, Optional, Union, Tuple
15
+ from datetime import datetime
16
+ import warnings
17
+
18
+ from ..core.logging_config import get_logger
19
+ from ..core.exceptions import AnalysisError
20
+
21
+ # Получаем логгер для модуля
22
+ logger = get_logger(__name__)
23
+
24
+ # Проверка доступности библиотек визуализации
25
+ try:
26
+ import plotly.graph_objects as go
27
+ import plotly.subplots as sp
28
+ from plotly.subplots import make_subplots
29
+ import plotly.express as px
30
+ import plotly.figure_factory as ff
31
+ PLOTLY_AVAILABLE = True
32
+ except ImportError:
33
+ PLOTLY_AVAILABLE = False
34
+ logger.warning("Plotly not available - statistical plots will be limited")
35
+
36
+ try:
37
+ import matplotlib.pyplot as plt
38
+ import seaborn as sns
39
+ from scipy import stats
40
+ MATPLOTLIB_AVAILABLE = True
41
+ except ImportError:
42
+ MATPLOTLIB_AVAILABLE = False
43
+ logger.warning("Matplotlib/Seaborn not available - statistical plots will be limited")
44
+
45
+
46
+ class StatisticalPlots:
47
+ """
48
+ Класс для создания статистических графиков.
49
+ """
50
+
51
+ def __init__(self, backend: str = 'plotly', **kwargs):
52
+ """
53
+ Инициализация создателя статистических графиков.
54
+
55
+ Args:
56
+ backend: Библиотека для построения ('plotly' или 'matplotlib')
57
+ **kwargs: Дополнительные параметры
58
+ """
59
+ self.backend = backend
60
+ self.logger = get_logger(f"{__name__}.StatisticalPlots")
61
+
62
+ # Проверяем доступность выбранной библиотеки
63
+ if backend == 'plotly' and not PLOTLY_AVAILABLE:
64
+ if MATPLOTLIB_AVAILABLE:
65
+ self.backend = 'matplotlib'
66
+ self.logger.warning("Plotly not available, switching to matplotlib")
67
+ else:
68
+ raise AnalysisError("No visualization libraries available")
69
+
70
+ elif backend == 'matplotlib' and not MATPLOTLIB_AVAILABLE:
71
+ if PLOTLY_AVAILABLE:
72
+ self.backend = 'plotly'
73
+ self.logger.warning("Matplotlib not available, switching to plotly")
74
+ else:
75
+ raise AnalysisError("No visualization libraries available")
76
+
77
+ # Настройки по умолчанию
78
+ self.default_config = {
79
+ 'width': kwargs.get('width', 1000),
80
+ 'height': kwargs.get('height', 600),
81
+ 'color_scheme': kwargs.get('color_scheme', 'plotly'),
82
+ 'show_statistics': kwargs.get('show_statistics', True)
83
+ }
84
+
85
+ self.logger.info(f"Statistical plots initialized with {self.backend} backend")
86
+
87
+ def create_histogram(self, data: Union[pd.Series, pd.DataFrame, np.ndarray],
88
+ title: str = "Histogram",
89
+ bins: int = 30,
90
+ column: str = None,
91
+ group_by: str = None,
92
+ **kwargs) -> Union[go.Figure, plt.Figure]:
93
+ """
94
+ Создание гистограммы.
95
+
96
+ Args:
97
+ data: Данные для гистограммы
98
+ title: Заголовок графика
99
+ bins: Количество бинов
100
+ column: Колонка для анализа (если data - DataFrame)
101
+ group_by: Колонка для группировки
102
+ **kwargs: Дополнительные параметры
103
+
104
+ Returns:
105
+ Объект графика
106
+ """
107
+ if self.backend == 'plotly':
108
+ return self._create_plotly_histogram(data, title, bins, column, group_by, **kwargs)
109
+ else:
110
+ return self._create_matplotlib_histogram(data, title, bins, column, group_by, **kwargs)
111
+
112
+ def create_scatter_plot(self, data: pd.DataFrame,
113
+ x_column: str,
114
+ y_column: str,
115
+ title: str = "Scatter Plot",
116
+ color_column: str = None,
117
+ size_column: str = None,
118
+ **kwargs) -> Union[go.Figure, plt.Figure]:
119
+ """
120
+ Создание диаграммы рассеяния.
121
+
122
+ Args:
123
+ data: DataFrame с данными
124
+ x_column: Колонка для оси X
125
+ y_column: Колонка для оси Y
126
+ title: Заголовок графика
127
+ color_column: Колонка для цветового кодирования
128
+ size_column: Колонка для размера точек
129
+ **kwargs: Дополнительные параметры
130
+
131
+ Returns:
132
+ Объект графика
133
+ """
134
+ if self.backend == 'plotly':
135
+ return self._create_plotly_scatter(data, x_column, y_column, title,
136
+ color_column, size_column, **kwargs)
137
+ else:
138
+ return self._create_matplotlib_scatter(data, x_column, y_column, title,
139
+ color_column, size_column, **kwargs)
140
+
141
+ def create_correlation_matrix(self, data: pd.DataFrame,
142
+ title: str = "Correlation Matrix",
143
+ method: str = 'pearson',
144
+ **kwargs) -> Union[go.Figure, plt.Figure]:
145
+ """
146
+ Создание матрицы корреляций.
147
+
148
+ Args:
149
+ data: DataFrame с данными
150
+ title: Заголовок графика
151
+ method: Метод корреляции ('pearson', 'spearman', 'kendall')
152
+ **kwargs: Дополнительные параметры
153
+
154
+ Returns:
155
+ Объект графика
156
+ """
157
+ if self.backend == 'plotly':
158
+ return self._create_plotly_correlation(data, title, method, **kwargs)
159
+ else:
160
+ return self._create_matplotlib_correlation(data, title, method, **kwargs)
161
+
162
+ def create_distribution_plot(self, data: Union[pd.Series, np.ndarray],
163
+ title: str = "Distribution Plot",
164
+ show_normal: bool = True,
165
+ **kwargs) -> Union[go.Figure, plt.Figure]:
166
+ """
167
+ Создание графика распределения с подгонкой кривой.
168
+
169
+ Args:
170
+ data: Данные для анализа
171
+ title: Заголовок графика
172
+ show_normal: Показать нормальное распределение
173
+ **kwargs: Дополнительные параметры
174
+
175
+ Returns:
176
+ Объект графика
177
+ """
178
+ if self.backend == 'plotly':
179
+ return self._create_plotly_distribution(data, title, show_normal, **kwargs)
180
+ else:
181
+ return self._create_matplotlib_distribution(data, title, show_normal, **kwargs)
182
+
183
+ def create_box_plot(self, data: pd.DataFrame,
184
+ y_column: str,
185
+ x_column: str = None,
186
+ title: str = "Box Plot",
187
+ **kwargs) -> Union[go.Figure, plt.Figure]:
188
+ """
189
+ Создание box plot.
190
+
191
+ Args:
192
+ data: DataFrame с данными
193
+ y_column: Колонка для анализа
194
+ x_column: Колонка для группировки (опционально)
195
+ title: Заголовок графика
196
+ **kwargs: Дополнительные параметры
197
+
198
+ Returns:
199
+ Объект графика
200
+ """
201
+ if self.backend == 'plotly':
202
+ return self._create_plotly_box(data, y_column, x_column, title, **kwargs)
203
+ else:
204
+ return self._create_matplotlib_box(data, y_column, x_column, title, **kwargs)
205
+
206
+ def create_time_series_plot(self, data: pd.DataFrame,
207
+ y_columns: List[str],
208
+ title: str = "Time Series",
209
+ show_trend: bool = False,
210
+ **kwargs) -> Union[go.Figure, plt.Figure]:
211
+ """
212
+ Создание графика временных рядов.
213
+
214
+ Args:
215
+ data: DataFrame с временным индексом
216
+ y_columns: Колонки для отображения
217
+ title: Заголовок графика
218
+ show_trend: Показать линию тренда
219
+ **kwargs: Дополнительные параметры
220
+
221
+ Returns:
222
+ Объект графика
223
+ """
224
+ if self.backend == 'plotly':
225
+ return self._create_plotly_timeseries(data, y_columns, title, show_trend, **kwargs)
226
+ else:
227
+ return self._create_matplotlib_timeseries(data, y_columns, title, show_trend, **kwargs)
228
+
229
+ # Plotly реализации
230
+ def _create_plotly_histogram(self, data, title: str, bins: int,
231
+ column: str, group_by: str, **kwargs) -> go.Figure:
232
+ """Создание гистограммы с помощью Plotly."""
233
+ # Подготовка данных
234
+ if isinstance(data, pd.DataFrame):
235
+ if column is None:
236
+ # Используем первую числовую колонку
237
+ numeric_cols = data.select_dtypes(include=[np.number]).columns
238
+ if len(numeric_cols) == 0:
239
+ raise ValueError("No numeric columns found")
240
+ column = numeric_cols[0]
241
+
242
+ plot_data = data[column].dropna()
243
+ else:
244
+ plot_data = pd.Series(data).dropna()
245
+
246
+ fig = go.Figure()
247
+
248
+ if group_by and isinstance(data, pd.DataFrame) and group_by in data.columns:
249
+ # Группированная гистограмма
250
+ for group in data[group_by].unique():
251
+ group_data = data[data[group_by] == group][column].dropna()
252
+ fig.add_trace(go.Histogram(
253
+ x=group_data,
254
+ name=str(group),
255
+ nbinsx=bins,
256
+ opacity=0.7
257
+ ))
258
+ else:
259
+ # Простая гистограмма
260
+ fig.add_trace(go.Histogram(
261
+ x=plot_data,
262
+ nbinsx=bins,
263
+ name='Frequency'
264
+ ))
265
+
266
+ # Добавляем статистики если требуется
267
+ if self.default_config['show_statistics']:
268
+ stats_text = f"Mean: {plot_data.mean():.3f}<br>"
269
+ stats_text += f"Std: {plot_data.std():.3f}<br>"
270
+ stats_text += f"Count: {len(plot_data)}"
271
+
272
+ fig.add_annotation(
273
+ text=stats_text,
274
+ xref="paper", yref="paper",
275
+ x=0.02, y=0.98,
276
+ xanchor='left', yanchor='top',
277
+ bgcolor="white",
278
+ bordercolor="black",
279
+ borderwidth=1
280
+ )
281
+
282
+ fig.update_layout(
283
+ title=title,
284
+ width=self.default_config['width'],
285
+ height=self.default_config['height'],
286
+ template='plotly_white',
287
+ barmode='overlay' if group_by else 'group'
288
+ )
289
+
290
+ return fig
291
+
292
+ def _create_plotly_scatter(self, data: pd.DataFrame, x_column: str,
293
+ y_column: str, title: str, color_column: str,
294
+ size_column: str, **kwargs) -> go.Figure:
295
+ """Создание диаграммы рассеяния с помощью Plotly."""
296
+ fig = go.Figure()
297
+
298
+ # Основные данные
299
+ scatter_kwargs = {
300
+ 'x': data[x_column],
301
+ 'y': data[y_column],
302
+ 'mode': 'markers',
303
+ 'name': 'Data Points'
304
+ }
305
+
306
+ # Цветовое кодирование
307
+ if color_column and color_column in data.columns:
308
+ scatter_kwargs['marker'] = dict(
309
+ color=data[color_column],
310
+ colorscale='viridis',
311
+ showscale=True,
312
+ colorbar=dict(title=color_column)
313
+ )
314
+
315
+ # Размер точек
316
+ if size_column and size_column in data.columns:
317
+ if 'marker' not in scatter_kwargs:
318
+ scatter_kwargs['marker'] = {}
319
+ scatter_kwargs['marker']['size'] = data[size_column]
320
+ scatter_kwargs['marker']['sizemode'] = 'diameter'
321
+ scatter_kwargs['marker']['sizeref'] = 2. * max(data[size_column]) / (20 ** 2)
322
+
323
+ fig.add_trace(go.Scatter(**scatter_kwargs))
324
+
325
+ # Добавляем линию тренда
326
+ if kwargs.get('show_trendline', False):
327
+ z = np.polyfit(data[x_column].dropna(), data[y_column].dropna(), 1)
328
+ p = np.poly1d(z)
329
+
330
+ fig.add_trace(go.Scatter(
331
+ x=data[x_column],
332
+ y=p(data[x_column]),
333
+ mode='lines',
334
+ name='Trend Line',
335
+ line=dict(color='red', dash='dash')
336
+ ))
337
+
338
+ # Корреляция
339
+ if self.default_config['show_statistics']:
340
+ correlation = data[x_column].corr(data[y_column])
341
+ fig.add_annotation(
342
+ text=f"Correlation: {correlation:.3f}",
343
+ xref="paper", yref="paper",
344
+ x=0.02, y=0.98,
345
+ xanchor='left', yanchor='top',
346
+ bgcolor="white",
347
+ bordercolor="black",
348
+ borderwidth=1
349
+ )
350
+
351
+ fig.update_layout(
352
+ title=title,
353
+ xaxis_title=x_column,
354
+ yaxis_title=y_column,
355
+ width=self.default_config['width'],
356
+ height=self.default_config['height'],
357
+ template='plotly_white'
358
+ )
359
+
360
+ return fig
361
+
362
+ def _create_plotly_correlation(self, data: pd.DataFrame, title: str,
363
+ method: str, **kwargs) -> go.Figure:
364
+ """Создание матрицы корреляций с помощью Plotly."""
365
+ # Выбираем только числовые колонки
366
+ numeric_data = data.select_dtypes(include=[np.number])
367
+
368
+ if numeric_data.empty:
369
+ raise ValueError("No numeric columns found for correlation analysis")
370
+
371
+ # Вычисляем корреляции
372
+ corr_matrix = numeric_data.corr(method=method)
373
+
374
+ # Создаем heatmap
375
+ fig = go.Figure(data=go.Heatmap(
376
+ z=corr_matrix.values,
377
+ x=corr_matrix.columns,
378
+ y=corr_matrix.columns,
379
+ colorscale='RdBu',
380
+ zmin=-1, zmax=1,
381
+ text=np.round(corr_matrix.values, 3),
382
+ texttemplate="%{text}",
383
+ textfont={"size": 10},
384
+ hoverongaps=False
385
+ ))
386
+
387
+ fig.update_layout(
388
+ title=f"{title} ({method.title()})",
389
+ width=self.default_config['width'],
390
+ height=self.default_config['height'],
391
+ template='plotly_white'
392
+ )
393
+
394
+ return fig
395
+
396
+ def _create_plotly_distribution(self, data, title: str, show_normal: bool,
397
+ **kwargs) -> go.Figure:
398
+ """Создание графика распределения с помощью Plotly."""
399
+ data_series = pd.Series(data).dropna()
400
+
401
+ # Создаем figure с подграфиками
402
+ fig = make_subplots(
403
+ rows=1, cols=2,
404
+ subplot_titles=['Histogram', 'Q-Q Plot'],
405
+ specs=[[{"secondary_y": False}, {"secondary_y": False}]]
406
+ )
407
+
408
+ # Гистограмма
409
+ fig.add_trace(go.Histogram(
410
+ x=data_series,
411
+ nbinsx=30,
412
+ name='Data',
413
+ opacity=0.7
414
+ ), row=1, col=1)
415
+
416
+ # Нормальное распределение если требуется
417
+ if show_normal:
418
+ x_range = np.linspace(data_series.min(), data_series.max(), 100)
419
+ normal_dist = stats.norm.pdf(x_range, data_series.mean(), data_series.std())
420
+
421
+ # Нормализуем к количеству точек
422
+ normal_dist = normal_dist * len(data_series) * (data_series.max() - data_series.min()) / 30
423
+
424
+ fig.add_trace(go.Scatter(
425
+ x=x_range,
426
+ y=normal_dist,
427
+ mode='lines',
428
+ name='Normal Distribution',
429
+ line=dict(color='red', width=2)
430
+ ), row=1, col=1)
431
+
432
+ # Q-Q plot
433
+ if MATPLOTLIB_AVAILABLE:
434
+ try:
435
+ # Используем scipy для Q-Q данных
436
+ theoretical_quantiles = stats.norm.ppf(np.linspace(0.01, 0.99, len(data_series)))
437
+ sample_quantiles = np.sort(data_series)
438
+
439
+ fig.add_trace(go.Scatter(
440
+ x=theoretical_quantiles,
441
+ y=sample_quantiles,
442
+ mode='markers',
443
+ name='Q-Q Plot',
444
+ marker=dict(size=4)
445
+ ), row=1, col=2)
446
+
447
+ # Добавляем идеальную линию
448
+ min_val = min(theoretical_quantiles.min(), sample_quantiles.min())
449
+ max_val = max(theoretical_quantiles.max(), sample_quantiles.max())
450
+ fig.add_trace(go.Scatter(
451
+ x=[min_val, max_val],
452
+ y=[min_val, max_val],
453
+ mode='lines',
454
+ name='Perfect Normal',
455
+ line=dict(color='red', dash='dash')
456
+ ), row=1, col=2)
457
+ except:
458
+ # Fallback если scipy недоступен
459
+ pass
460
+
461
+ # Статистики
462
+ if self.default_config['show_statistics']:
463
+ stats_text = f"Mean: {data_series.mean():.3f}<br>"
464
+ stats_text += f"Std: {data_series.std():.3f}<br>"
465
+ stats_text += f"Skewness: {data_series.skew():.3f}<br>"
466
+ stats_text += f"Kurtosis: {data_series.kurtosis():.3f}"
467
+
468
+ fig.add_annotation(
469
+ text=stats_text,
470
+ xref="paper", yref="paper",
471
+ x=0.02, y=0.98,
472
+ xanchor='left', yanchor='top',
473
+ bgcolor="white",
474
+ bordercolor="black",
475
+ borderwidth=1
476
+ )
477
+
478
+ fig.update_layout(
479
+ title=title,
480
+ width=self.default_config['width'],
481
+ height=self.default_config['height'],
482
+ template='plotly_white',
483
+ showlegend=True
484
+ )
485
+
486
+ return fig
487
+
488
+ def _create_plotly_box(self, data: pd.DataFrame, y_column: str,
489
+ x_column: str, title: str, **kwargs) -> go.Figure:
490
+ """Создание box plot с помощью Plotly."""
491
+ fig = go.Figure()
492
+
493
+ if x_column and x_column in data.columns:
494
+ # Группированный box plot
495
+ for group in data[x_column].unique():
496
+ group_data = data[data[x_column] == group][y_column].dropna()
497
+ fig.add_trace(go.Box(
498
+ y=group_data,
499
+ name=str(group),
500
+ boxpoints='outliers'
501
+ ))
502
+ else:
503
+ # Простой box plot
504
+ fig.add_trace(go.Box(
505
+ y=data[y_column].dropna(),
506
+ name=y_column,
507
+ boxpoints='outliers'
508
+ ))
509
+
510
+ fig.update_layout(
511
+ title=title,
512
+ yaxis_title=y_column,
513
+ xaxis_title=x_column if x_column else '',
514
+ width=self.default_config['width'],
515
+ height=self.default_config['height'],
516
+ template='plotly_white'
517
+ )
518
+
519
+ return fig
520
+
521
+ def _create_plotly_timeseries(self, data: pd.DataFrame, y_columns: List[str],
522
+ title: str, show_trend: bool, **kwargs) -> go.Figure:
523
+ """Создание графика временных рядов с помощью Plotly."""
524
+ fig = go.Figure()
525
+
526
+ for column in y_columns:
527
+ if column in data.columns:
528
+ fig.add_trace(go.Scatter(
529
+ x=data.index,
530
+ y=data[column],
531
+ mode='lines',
532
+ name=column,
533
+ line=dict(width=2)
534
+ ))
535
+
536
+ # Добавляем тренд если требуется
537
+ if show_trend:
538
+ # Простая линейная регрессия
539
+ x_numeric = np.arange(len(data))
540
+ y_data = data[column].dropna()
541
+ x_clean = x_numeric[:len(y_data)]
542
+
543
+ if len(x_clean) > 1:
544
+ z = np.polyfit(x_clean, y_data, 1)
545
+ p = np.poly1d(z)
546
+
547
+ fig.add_trace(go.Scatter(
548
+ x=data.index,
549
+ y=p(x_numeric),
550
+ mode='lines',
551
+ name=f'{column} Trend',
552
+ line=dict(dash='dash', width=1)
553
+ ))
554
+
555
+ fig.update_layout(
556
+ title=title,
557
+ width=self.default_config['width'],
558
+ height=self.default_config['height'],
559
+ template='plotly_white',
560
+ showlegend=True
561
+ )
562
+
563
+ return fig
564
+
565
+ # Matplotlib реализации (упрощенные)
566
+ def _create_matplotlib_histogram(self, data, title: str, bins: int,
567
+ column: str, group_by: str, **kwargs) -> plt.Figure:
568
+ """Создание гистограммы с помощью Matplotlib."""
569
+ fig, ax = plt.subplots(figsize=(10, 6))
570
+
571
+ if isinstance(data, pd.DataFrame):
572
+ if column is None:
573
+ numeric_cols = data.select_dtypes(include=[np.number]).columns
574
+ column = numeric_cols[0] if len(numeric_cols) > 0 else data.columns[0]
575
+ plot_data = data[column].dropna()
576
+ else:
577
+ plot_data = pd.Series(data).dropna()
578
+
579
+ if group_by and isinstance(data, pd.DataFrame):
580
+ for group in data[group_by].unique():
581
+ group_data = data[data[group_by] == group][column].dropna()
582
+ ax.hist(group_data, bins=bins, alpha=0.7, label=str(group))
583
+ ax.legend()
584
+ else:
585
+ ax.hist(plot_data, bins=bins, alpha=0.7)
586
+
587
+ ax.set_title(title)
588
+ ax.grid(True, alpha=0.3)
589
+
590
+ if self.default_config['show_statistics']:
591
+ stats_text = f"Mean: {plot_data.mean():.3f}\nStd: {plot_data.std():.3f}"
592
+ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes,
593
+ verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white'))
594
+
595
+ return fig
596
+
597
+ def _create_matplotlib_scatter(self, data: pd.DataFrame, x_column: str,
598
+ y_column: str, title: str, color_column: str,
599
+ size_column: str, **kwargs) -> plt.Figure:
600
+ """Создание диаграммы рассеяния с помощью Matplotlib."""
601
+ fig, ax = plt.subplots(figsize=(10, 6))
602
+
603
+ scatter_kwargs = {'alpha': 0.7}
604
+
605
+ if color_column and color_column in data.columns:
606
+ scatter_kwargs['c'] = data[color_column]
607
+ scatter_kwargs['cmap'] = 'viridis'
608
+
609
+ if size_column and size_column in data.columns:
610
+ scatter_kwargs['s'] = data[size_column] * 10
611
+
612
+ scatter = ax.scatter(data[x_column], data[y_column], **scatter_kwargs)
613
+
614
+ if color_column:
615
+ plt.colorbar(scatter, label=color_column)
616
+
617
+ ax.set_xlabel(x_column)
618
+ ax.set_ylabel(y_column)
619
+ ax.set_title(title)
620
+ ax.grid(True, alpha=0.3)
621
+
622
+ if self.default_config['show_statistics']:
623
+ correlation = data[x_column].corr(data[y_column])
624
+ ax.text(0.02, 0.98, f"Correlation: {correlation:.3f}",
625
+ transform=ax.transAxes, verticalalignment='top',
626
+ bbox=dict(boxstyle='round', facecolor='white'))
627
+
628
+ return fig
629
+
630
+ def _create_matplotlib_correlation(self, data: pd.DataFrame, title: str,
631
+ method: str, **kwargs) -> plt.Figure:
632
+ """Создание матрицы корреляций с помощью Matplotlib."""
633
+ numeric_data = data.select_dtypes(include=[np.number])
634
+ corr_matrix = numeric_data.corr(method=method)
635
+
636
+ fig, ax = plt.subplots(figsize=(10, 8))
637
+
638
+ if MATPLOTLIB_AVAILABLE:
639
+ sns.heatmap(corr_matrix, annot=True, cmap='RdBu', center=0,
640
+ square=True, ax=ax, cbar_kws={"shrink": .8})
641
+ else:
642
+ im = ax.imshow(corr_matrix.values, cmap='RdBu', vmin=-1, vmax=1)
643
+ ax.set_xticks(range(len(corr_matrix.columns)))
644
+ ax.set_yticks(range(len(corr_matrix.columns)))
645
+ ax.set_xticklabels(corr_matrix.columns, rotation=45)
646
+ ax.set_yticklabels(corr_matrix.columns)
647
+ plt.colorbar(im)
648
+
649
+ ax.set_title(f"{title} ({method.title()})")
650
+ plt.tight_layout()
651
+ return fig
652
+
653
+ def _create_matplotlib_distribution(self, data, title: str, show_normal: bool,
654
+ **kwargs) -> plt.Figure:
655
+ """Создание графика распределения с помощью Matplotlib."""
656
+ data_series = pd.Series(data).dropna()
657
+
658
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
659
+
660
+ # Гистограмма
661
+ ax1.hist(data_series, bins=30, alpha=0.7, density=True)
662
+
663
+ if show_normal and MATPLOTLIB_AVAILABLE:
664
+ x_range = np.linspace(data_series.min(), data_series.max(), 100)
665
+ normal_dist = stats.norm.pdf(x_range, data_series.mean(), data_series.std())
666
+ ax1.plot(x_range, normal_dist, 'r-', linewidth=2, label='Normal Distribution')
667
+ ax1.legend()
668
+
669
+ ax1.set_title('Distribution')
670
+ ax1.grid(True, alpha=0.3)
671
+
672
+ # Q-Q plot
673
+ if MATPLOTLIB_AVAILABLE:
674
+ try:
675
+ stats.probplot(data_series, dist="norm", plot=ax2)
676
+ ax2.set_title('Q-Q Plot')
677
+ except:
678
+ ax2.text(0.5, 0.5, 'Q-Q Plot not available', ha='center', va='center')
679
+
680
+ plt.suptitle(title)
681
+ plt.tight_layout()
682
+ return fig
683
+
684
+ def _create_matplotlib_box(self, data: pd.DataFrame, y_column: str,
685
+ x_column: str, title: str, **kwargs) -> plt.Figure:
686
+ """Создание box plot с помощью Matplotlib."""
687
+ fig, ax = plt.subplots(figsize=(10, 6))
688
+
689
+ if x_column and x_column in data.columns:
690
+ if MATPLOTLIB_AVAILABLE:
691
+ sns.boxplot(data=data, x=x_column, y=y_column, ax=ax)
692
+ else:
693
+ # Простая реализация без seaborn
694
+ groups = data[x_column].unique()
695
+ box_data = [data[data[x_column] == group][y_column].dropna() for group in groups]
696
+ ax.boxplot(box_data, labels=groups)
697
+ else:
698
+ ax.boxplot(data[y_column].dropna())
699
+
700
+ ax.set_title(title)
701
+ ax.grid(True, alpha=0.3)
702
+ return fig
703
+
704
+ def _create_matplotlib_timeseries(self, data: pd.DataFrame, y_columns: List[str],
705
+ title: str, show_trend: bool, **kwargs) -> plt.Figure:
706
+ """Создание графика временных рядов с помощью Matplotlib."""
707
+ fig, ax = plt.subplots(figsize=(12, 6))
708
+
709
+ for column in y_columns:
710
+ if column in data.columns:
711
+ ax.plot(data.index, data[column], label=column, linewidth=2)
712
+
713
+ if show_trend:
714
+ x_numeric = np.arange(len(data))
715
+ y_data = data[column].dropna()
716
+ if len(y_data) > 1:
717
+ z = np.polyfit(x_numeric[:len(y_data)], y_data, 1)
718
+ p = np.poly1d(z)
719
+ ax.plot(data.index, p(x_numeric), '--',
720
+ label=f'{column} Trend', alpha=0.7)
721
+
722
+ ax.set_title(title)
723
+ ax.legend()
724
+ ax.grid(True, alpha=0.3)
725
+ plt.xticks(rotation=45)
726
+ plt.tight_layout()
727
+ return fig
728
+
729
+
730
+ class DistributionPlotter(StatisticalPlots):
731
+ """
732
+ Специализированный класс для анализа распределений.
733
+ """
734
+
735
+ def plot_multiple_distributions(self, data: pd.DataFrame,
736
+ columns: List[str],
737
+ title: str = "Multiple Distributions",
738
+ **kwargs) -> Union[go.Figure, plt.Figure]:
739
+ """
740
+ Сравнение нескольких распределений.
741
+
742
+ Args:
743
+ data: DataFrame с данными
744
+ columns: Колонки для анализа
745
+ title: Заголовок графика
746
+ **kwargs: Дополнительные параметры
747
+
748
+ Returns:
749
+ Объект графика
750
+ """
751
+ if self.backend == 'plotly':
752
+ fig = go.Figure()
753
+
754
+ for column in columns:
755
+ if column in data.columns:
756
+ fig.add_trace(go.Histogram(
757
+ x=data[column].dropna(),
758
+ name=column,
759
+ opacity=0.7,
760
+ nbinsx=30
761
+ ))
762
+
763
+ fig.update_layout(
764
+ title=title,
765
+ barmode='overlay',
766
+ width=self.default_config['width'],
767
+ height=self.default_config['height'],
768
+ template='plotly_white'
769
+ )
770
+
771
+ return fig
772
+
773
+ else:
774
+ fig, ax = plt.subplots(figsize=(10, 6))
775
+
776
+ for column in columns:
777
+ if column in data.columns:
778
+ ax.hist(data[column].dropna(), alpha=0.7, label=column, bins=30)
779
+
780
+ ax.set_title(title)
781
+ ax.legend()
782
+ ax.grid(True, alpha=0.3)
783
+ return fig
784
+
785
+
786
+ # Удобные функции
787
+ def create_quick_histogram(data, title="Histogram", **kwargs):
788
+ """Быстрое создание гистограммы."""
789
+ plotter = StatisticalPlots()
790
+ return plotter.create_histogram(data, title, **kwargs)
791
+
792
+
793
+ def create_quick_scatter(data: pd.DataFrame, x: str, y: str, **kwargs):
794
+ """Быстрое создание диаграммы рассеяния."""
795
+ plotter = StatisticalPlots()
796
+ return plotter.create_scatter_plot(data, x, y, **kwargs)
797
+
798
+
799
+ def create_correlation_heatmap(data: pd.DataFrame, **kwargs):
800
+ """Быстрое создание матрицы корреляций."""
801
+ plotter = StatisticalPlots()
802
+ return plotter.create_correlation_matrix(data, **kwargs)
803
+
804
+
805
+ # Экспорт
806
+ __all__ = [
807
+ 'StatisticalPlots',
808
+ 'DistributionPlotter',
809
+ 'create_quick_histogram',
810
+ 'create_quick_scatter',
811
+ 'create_correlation_heatmap'
812
+ ]