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.
- bquant/__init__.py +31 -0
- bquant/analysis/__init__.py +297 -0
- bquant/analysis/candlestick/__init__.py +100 -0
- bquant/analysis/chart/__init__.py +98 -0
- bquant/analysis/statistical/__init__.py +414 -0
- bquant/analysis/statistical/hypothesis_testing.py +657 -0
- bquant/analysis/technical/__init__.py +98 -0
- bquant/analysis/timeseries/__init__.py +102 -0
- bquant/analysis/zones/__init__.py +547 -0
- bquant/analysis/zones/sequence_analysis.py +661 -0
- bquant/analysis/zones/zone_features.py +520 -0
- bquant/cli.py +160 -0
- bquant/core/__init__.py +112 -0
- bquant/core/cache.py +534 -0
- bquant/core/config.py +328 -0
- bquant/core/exceptions.py +351 -0
- bquant/core/logging_config.py +368 -0
- bquant/core/numpy_fix.py +99 -0
- bquant/core/performance.py +554 -0
- bquant/core/utils.py +327 -0
- bquant/data/__init__.py +85 -0
- bquant/data/loader.py +436 -0
- bquant/data/processor.py +644 -0
- bquant/data/samples/__init__.py +442 -0
- bquant/data/samples/datasets.py +246 -0
- bquant/data/samples/embedded/__init__.py +16 -0
- bquant/data/samples/embedded/mt_xauusd_m15.py +9018 -0
- bquant/data/samples/embedded/tv_xauusd_1h.py +17018 -0
- bquant/data/samples/utils.py +393 -0
- bquant/data/schemas.py +285 -0
- bquant/data/validator.py +498 -0
- bquant/indicators/__init__.py +115 -0
- bquant/indicators/base.py +487 -0
- bquant/indicators/calculators.py +404 -0
- bquant/indicators/library.py +514 -0
- bquant/indicators/loaders.py +413 -0
- bquant/indicators/macd.py +754 -0
- bquant/ml/__init__.py +19 -0
- bquant/visualization/__init__.py +316 -0
- bquant/visualization/charts.py +616 -0
- bquant/visualization/statistical.py +812 -0
- bquant/visualization/themes.py +599 -0
- bquant/visualization/zones.py +775 -0
- bquant-0.0.0.dist-info/METADATA +167 -0
- bquant-0.0.0.dist-info/RECORD +49 -0
- bquant-0.0.0.dist-info/WHEEL +5 -0
- bquant-0.0.0.dist-info/entry_points.txt +2 -0
- bquant-0.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
]
|