sovai 0.2.56__tar.gz → 0.2.58__tar.gz

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 (105) hide show
  1. {sovai-0.2.56 → sovai-0.2.58}/PKG-INFO +1 -1
  2. {sovai-0.2.56 → sovai-0.2.58}/pyproject.toml +1 -1
  3. sovai-0.2.58/sovai/__init__.py +167 -0
  4. sovai-0.2.58/sovai/extensions/shapley_global_importance.py +214 -0
  5. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_data.py +10 -1
  6. sovai-0.2.56/sovai/__init__.py +0 -39
  7. sovai-0.2.56/sovai/extensions/shapley_global_importance.py +0 -127
  8. {sovai-0.2.56 → sovai-0.2.58}/LICENSE +0 -0
  9. {sovai-0.2.56 → sovai-0.2.58}/README.md +0 -0
  10. {sovai-0.2.56 → sovai-0.2.58}/data/codes.parq +0 -0
  11. {sovai-0.2.56 → sovai-0.2.58}/data/tickers.parq +0 -0
  12. {sovai-0.2.56 → sovai-0.2.58}/sovai/.DS_Store +0 -0
  13. {sovai-0.2.56 → sovai-0.2.58}/sovai/api_config.py +0 -0
  14. {sovai-0.2.56 → sovai-0.2.58}/sovai/assets/dropdown.css +0 -0
  15. {sovai-0.2.56 → sovai-0.2.58}/sovai/assets/features_mapping.parq +0 -0
  16. {sovai-0.2.56 → sovai-0.2.58}/sovai/basic_auth.py +0 -0
  17. {sovai-0.2.56 → sovai-0.2.58}/sovai/computations/__init__.py +0 -0
  18. {sovai-0.2.56 → sovai-0.2.58}/sovai/computations/functions.py +0 -0
  19. {sovai-0.2.56 → sovai-0.2.58}/sovai/errors/__init__.py +0 -0
  20. {sovai-0.2.56 → sovai-0.2.58}/sovai/errors/sovai_errors.py +0 -0
  21. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/__init__.py +0 -0
  22. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/anomalies.py +0 -0
  23. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/ask_df_llm.py +0 -0
  24. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/change_point_generator.py +0 -0
  25. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/clustering.py +0 -0
  26. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/core_kshape.py +0 -0
  27. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/cum_sum_torch.py +0 -0
  28. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/cusum.py +0 -0
  29. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/dimensionality_reduction.py +0 -0
  30. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/feature_extraction.py +0 -0
  31. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/feature_importance.py +0 -0
  32. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/feature_neutralizer.py +0 -0
  33. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/filter_df.py +0 -0
  34. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/fractional_differencing.py +0 -0
  35. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/nowcasting.py +0 -0
  36. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/pairwise.py +0 -0
  37. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/pandas_extensions.py +0 -0
  38. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/parallel_functions.py +0 -0
  39. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/pfa_feature_selector.py +0 -0
  40. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/regime_change.py +0 -0
  41. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/regime_change_pca.py +0 -0
  42. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/shapley_importance.py +0 -0
  43. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/signal_evaluation.py +0 -0
  44. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/technical_indicators.py +0 -0
  45. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/time_decomposition.py +0 -0
  46. {sovai-0.2.56 → sovai-0.2.58}/sovai/extensions/weight_optimization.py +0 -0
  47. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_compute.py +0 -0
  48. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_data copy.py +0 -0
  49. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_plots.py +0 -0
  50. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_reports.py +0 -0
  51. {sovai-0.2.56 → sovai-0.2.58}/sovai/get_tools.py +0 -0
  52. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/__init__.py +0 -0
  53. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/accounting/__init__.py +0 -0
  54. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/accounting/accounting_plots.py +0 -0
  55. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/allocation/__init__.py +0 -0
  56. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/allocation/allocation_plots.py +0 -0
  57. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/bankruptcy/__init__.py +0 -0
  58. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
  59. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/breakout/__init__.py +0 -0
  60. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/breakout/breakout_plots.py +0 -0
  61. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/corp_risk/__init__.py +0 -0
  62. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
  63. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/earnings_surprise/__init__.py +0 -0
  64. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
  65. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/insider/__init__.py +0 -0
  66. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/insider/insider_plots.py +0 -0
  67. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/institutional/__init__.py +0 -0
  68. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/institutional/institutional_plots.py +0 -0
  69. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/news/__init__.py +0 -0
  70. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/news/news_plots.py +0 -0
  71. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/ratios/__init__.py +0 -0
  72. {sovai-0.2.56 → sovai-0.2.58}/sovai/plots/ratios/ratios_plots.py +0 -0
  73. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/accounting/__init__.py +0 -0
  74. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
  75. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/bankruptcy/__init__.py +0 -0
  76. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  77. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/general/__init__.py +0 -0
  78. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/general/general_plots.py +0 -0
  79. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/news/__inti__.py +0 -0
  80. {sovai-0.2.56 → sovai-0.2.58}/sovai/reports/news/news_econometric_analysis.py +0 -0
  81. {sovai-0.2.56 → sovai-0.2.58}/sovai/studies/nowcasting.py +0 -0
  82. {sovai-0.2.56 → sovai-0.2.58}/sovai/token_auth.py +0 -0
  83. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/authentication/__init__.py +0 -0
  84. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/authentication/authentication.py +0 -0
  85. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/sec/__init__.py +0 -0
  86. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/sec/graphs.py +0 -0
  87. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/sec/llm_code_generator.py +0 -0
  88. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  89. {sovai-0.2.56 → sovai-0.2.58}/sovai/tools/sec/sec_edgar_search.py +0 -0
  90. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/__init__.py +0 -0
  91. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/client_side.py +0 -0
  92. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/client_side_s3.py +0 -0
  93. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/client_side_s3_part_high copy 2.py +0 -0
  94. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/client_side_s3_part_high copy.py +0 -0
  95. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/client_side_s3_part_high.py +0 -0
  96. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/converter.py +0 -0
  97. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/datetime_formats.py +0 -0
  98. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/file_management.py +0 -0
  99. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/get_tickers.py +0 -0
  100. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/helpers.py +0 -0
  101. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/pandas_extensions copy.py +0 -0
  102. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/plot.py +0 -0
  103. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/port_manager.py +0 -0
  104. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/stream.py +0 -0
  105. {sovai-0.2.56 → sovai-0.2.58}/sovai/utils/verbose_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sovai
3
- Version: 0.2.56
3
+ Version: 0.2.58
4
4
  Summary: python for asset management
5
5
  License: Proprietary License
6
6
  Author: Derek Snow
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "sovai"
7
- version = "0.2.56"
7
+ version = "0.2.58"
8
8
  description = "python for asset management"
9
9
  authors = [
10
10
  {name = "Derek Snow", email = "d.snow@outlook.com"}
@@ -0,0 +1,167 @@
1
+ """
2
+ Main SovAI SDK Tool Kit package
3
+ """
4
+ from .api_config import read_key, save_key, ApiConfig
5
+ from .basic_auth import basic_auth
6
+ from .token_auth import token_auth
7
+ from .utils.file_management import update_data_files # Keep update_data_files import
8
+
9
+
10
+ import warnings
11
+ warnings.filterwarnings("ignore")
12
+
13
+
14
+ try:
15
+ from importlib.metadata import version
16
+ __version__ = version("sovai")
17
+ except:
18
+ __version__ = "0.2.49" # Fallback to current version in pyproject.toml
19
+
20
+
21
+ # Lazy loading for core modules
22
+ _data_module = None
23
+
24
+ def data(*args, **kwargs):
25
+ """Lazy-loaded access to the data function."""
26
+ global _data_module
27
+ if _data_module is None:
28
+ from .get_data import data as _loaded_data
29
+ _data_module = _loaded_data
30
+ return _data_module(*args, **kwargs)
31
+
32
+
33
+ # Lazy loading for full installation modules
34
+ HAS_FULL_INSTALL = False
35
+ _plot_module = None
36
+ _report_module = None
37
+ _compute_module = None
38
+ _nowcast_module = None
39
+ _extension_module = None
40
+ _sec_search_module = None
41
+ _sec_filing_module = None
42
+ _code_module = None
43
+ _sec_graph_module = None
44
+
45
+ try:
46
+ # Attempt to import a module from the full installation to check if it's installed
47
+ # This import is just to check for ImportError, the actual function is lazy-loaded below
48
+ from .get_plots import plot as _ # Use _ to indicate this import is just for the check
49
+ HAS_FULL_INSTALL = True
50
+
51
+ def plot(*args, **kwargs):
52
+ """Lazy-loaded access to the plot function."""
53
+ global _plot_module
54
+ if _plot_module is None:
55
+ from .get_plots import plot as _loaded_plot
56
+ _plot_module = _loaded_plot
57
+ return _plot_module(*args, **kwargs)
58
+
59
+ def report(*args, **kwargs):
60
+ """Lazy-loaded access to the report function."""
61
+ global _report_module
62
+ if _report_module is None:
63
+ from .get_reports import report as _loaded_report
64
+ _report_module = _loaded_report
65
+ return _report_module(*args, **kwargs)
66
+
67
+ def compute(*args, **kwargs):
68
+ """Lazy-loaded access to the compute function."""
69
+ global _compute_module
70
+ if _compute_module is None:
71
+ from .get_compute import compute as _loaded_compute
72
+ _compute_module = _loaded_compute
73
+ return _compute_module(*args, **kwargs)
74
+
75
+ def nowcast(*args, **kwargs):
76
+ """Lazy-loaded access to the nowcast function."""
77
+ global _nowcast_module
78
+ if _nowcast_module is None:
79
+ from .studies.nowcasting import nowcast as _loaded_nowcast
80
+ _nowcast_module = _loaded_nowcast
81
+ return _nowcast_module(*args, **kwargs)
82
+
83
+ # extension is likely a class, so the lazy loader needs to return the class itself
84
+ def extension(*args, **kwargs):
85
+ """Lazy-loaded access to the CustomDataFrame extension class."""
86
+ global _extension_module
87
+ if _extension_module is None:
88
+ from .extensions.pandas_extensions import CustomDataFrame as _loaded_extension
89
+ _extension_module = _loaded_extension
90
+ # If called with args/kwargs, assume it's being instantiated
91
+ if args or kwargs:
92
+ return _extension_module(*args, **kwargs)
93
+ # Otherwise, return the class itself (e.g., for type checking or accessing class methods)
94
+ return _extension_module
95
+
96
+
97
+ def sec_search(*args, **kwargs):
98
+ """Lazy-loaded access to the sec_search function."""
99
+ global _sec_search_module
100
+ if _sec_search_module is None:
101
+ from .get_tools import sec_search as _loaded_sec_search
102
+ _sec_search_module = _loaded_sec_search
103
+ return _sec_search_module(*args, **kwargs)
104
+
105
+ def sec_filing(*args, **kwargs):
106
+ """Lazy-loaded access to the sec_filing function."""
107
+ global _sec_filing_module
108
+ if _sec_filing_module is None:
109
+ from .get_tools import sec_filing as _loaded_sec_filing
110
+ _sec_filing_module = _loaded_sec_filing
111
+ return _sec_filing_module(*args, **kwargs)
112
+
113
+ def code(*args, **kwargs):
114
+ """Lazy-loaded access to the code function."""
115
+ global _code_module
116
+ if _code_module is None:
117
+ from .get_tools import code as _loaded_code
118
+ _code_module = _loaded_code
119
+ return _code_module(*args, **kwargs)
120
+
121
+ def sec_graph(*args, **kwargs):
122
+ """Lazy-loaded access to the sec_graph function."""
123
+ global _sec_graph_module
124
+ if _sec_graph_module is None:
125
+ from .get_tools import sec_graph as _loaded_sec_graph
126
+ _sec_graph_module = _loaded_sec_graph
127
+ return _sec_graph_module(*args, **kwargs)
128
+
129
+
130
+ except ImportError as e:
131
+ print("this is the lean installation, for full use sovai[full]")
132
+ # print(f"ImportError: {e}")
133
+ HAS_FULL_INSTALL = False
134
+
135
+ # Define dummy functions/objects for lean installation to avoid AttributeErrors
136
+ def plot(*args, **kwargs):
137
+ raise ImportError("Plotting requires the full installation. Install with 'pip install sovai[full]'")
138
+ def report(*args, **kwargs):
139
+ raise ImportError("Reporting requires the full installation. Install with 'pip install sovai[full]'")
140
+ def compute(*args, **kwargs):
141
+ raise ImportError("Compute requires the full installation. Install with 'pip install sovai[full]'")
142
+ def nowcast(*args, **kwargs):
143
+ raise ImportError("Nowcasting requires the full installation. Install with 'pip install sovai[full]'")
144
+ def extension(*args, **kwargs):
145
+ raise ImportError("Extensions require the full installation. Install with 'pip install sovai[full]'")
146
+ def sec_search(*args, **kwargs):
147
+ raise ImportError("SEC tools require the full installation. Install with 'pip install sovai[full]'")
148
+ def sec_filing(*args, **kwargs):
149
+ raise ImportError("SEC tools require the full installation. Install with 'pip install sovai[full]'")
150
+ def code(*args, **kwargs):
151
+ raise ImportError("SEC tools require the full installation. Install with 'pip install sovai[full]'")
152
+ def sec_graph(*args, **kwargs):
153
+ raise ImportError("SEC tools require the full installation. Install with 'pip install sovai[full]'")
154
+
155
+
156
+ __all__ = [
157
+ 'read_key', 'save_key', 'ApiConfig', 'basic_auth', 'token_auth',
158
+ 'update_data_files', # Keep update_data_files in __all__
159
+ 'data', # Add data to __all__
160
+ ]
161
+
162
+ # Add full installation lazy-loaded attributes to __all__ if available
163
+ if HAS_FULL_INSTALL:
164
+ __all__ += [
165
+ 'plot', 'report', 'compute', 'nowcast', 'extension',
166
+ 'sec_search', 'sec_filing', 'code', 'sec_graph'
167
+ ]
@@ -0,0 +1,214 @@
1
+ # Lightweight built-in imports can stay global
2
+ import random
3
+ import hashlib
4
+ import warnings
5
+ # import os # Removed as it was unused
6
+
7
+ # Note: Imports below are moved into functions/methods to defer loading cost,
8
+ # potentially speeding up the initial import of this script.
9
+
10
+ def hash_of_df(df, sample_size=100):
11
+ """Calculates a SHA256 hash of a sampled portion of a DataFrame."""
12
+ # Import pandas here as it's needed for DataFrame operations
13
+ import pandas as pd
14
+
15
+ df_sample = (
16
+ df.sample(n=min(sample_size, len(df)), random_state=42).to_string().encode()
17
+ )
18
+ return hashlib.sha256(df_sample).hexdigest()
19
+
20
+ class ClusteringExplainer:
21
+ """
22
+ Trains a classifier to predict cluster labels and provides SHAP explanations.
23
+ """
24
+ def __init__(self, random_state=42):
25
+ """Initializes the explainer and scaler."""
26
+ # Import StandardScaler here as it's used for self.scaler
27
+ from sklearn.preprocessing import StandardScaler
28
+
29
+ self.random_state = random_state
30
+ self.model = None
31
+ self.explainer = None
32
+ # Initialize scaler here after import
33
+ self.scaler = StandardScaler()
34
+
35
+ def fit(self, X, y):
36
+ """Fits the LGBM classifier and creates the SHAP explainer."""
37
+ # Imports needed specifically for fitting the model and explainer
38
+ from lightgbm import LGBMClassifier
39
+ from sklearn.utils.class_weight import compute_sample_weight
40
+ import shap
41
+
42
+ classes_weights = compute_sample_weight(class_weight="balanced", y=y)
43
+ self.model = LGBMClassifier(
44
+ objective="multiclass",
45
+ random_state=self.random_state,
46
+ verbose=-1,
47
+ force_col_wise=True,
48
+ min_gain_to_split=0.01
49
+ )
50
+ # Catch warnings during model fitting (e.g., from LightGBM)
51
+ with warnings.catch_warnings():
52
+ warnings.simplefilter("ignore")
53
+ self.model.fit(X, y, sample_weight=classes_weights)
54
+
55
+ # Create SHAP explainer after model is trained
56
+ self.explainer = shap.Explainer(self.model)
57
+
58
+ def get_shap_values(self, X):
59
+ """Gets SHAP values using the trained explainer."""
60
+ # No new imports needed here, uses self.explainer created in fit()
61
+ if self.explainer is None:
62
+ raise RuntimeError("Explainer not available. Call fit() method first.")
63
+ return self.explainer(X)
64
+
65
+ def get_shap_values_for_dataset(df, clustering_method="KMEANS", n_clusters=10, random_state=42, sample_size=5000):
66
+ """
67
+ Performs clustering, trains a model, and calculates mean absolute SHAP values.
68
+ """
69
+ # Imports needed for this function's operations
70
+ import pandas as pd
71
+ import numpy as np
72
+ from sklearn.cluster import KMeans, MeanShift, HDBSCAN
73
+ from sklearn.preprocessing import StandardScaler # Needed again for local scaler instance
74
+
75
+ # Sample for clustering and model training
76
+ X_sample = df.sample(n=min(sample_size, len(df)), random_state=random_state)
77
+
78
+ # Create and fit a local scaler for the sample
79
+ scaler = StandardScaler()
80
+ X_sample_scaled = scaler.fit_transform(X_sample)
81
+
82
+ # Select and fit the clustering algorithm
83
+ if clustering_method == "KMEANS":
84
+ # Use random module (imported globally)
85
+ num_clusters = random.randint(5, 10)
86
+ clustering = KMeans(n_clusters=num_clusters, random_state=random_state, n_init=10) # Added n_init
87
+ elif clustering_method == "MEANSHIFT":
88
+ clustering = MeanShift()
89
+ elif clustering_method == "HDBSCAN":
90
+ clustering = HDBSCAN(min_cluster_size=5)
91
+ else:
92
+ raise ValueError(f"Unsupported clustering method: {clustering_method}")
93
+
94
+ # Catch warnings during clustering (e.g., convergence warnings)
95
+ with warnings.catch_warnings():
96
+ warnings.simplefilter("ignore")
97
+ clustering.fit(X_sample_scaled)
98
+
99
+ y_sample = clustering.labels_
100
+
101
+ # Handle noise points from HDBSCAN if necessary
102
+ if clustering_method == "HDBSCAN":
103
+ # Check if noise points exist (-1 label)
104
+ if -1 in y_sample:
105
+ noise_label = np.max(y_sample) + 1
106
+ y_sample[y_sample == -1] = noise_label # Assign noise points to a new cluster index
107
+
108
+ # Create and fit the explainer using the clustered sample
109
+ clust_explnr = ClusteringExplainer(random_state=random_state)
110
+ clust_explnr.fit(X_sample_scaled, y_sample) # This triggers imports within ClusteringExplainer.fit
111
+
112
+ # Transform the entire dataset using the scaler fitted on the sample
113
+ X_full_scaled = scaler.transform(df)
114
+
115
+ # Get SHAP values for the entire scaled dataset
116
+ shap_values = clust_explnr.get_shap_values(X_full_scaled)
117
+
118
+ # Calculate mean absolute SHAP values across classes for each feature
119
+ # shap_values.values is typically (n_samples, n_features, n_classes) or (n_samples, n_features)
120
+ if len(shap_values.values.shape) == 3:
121
+ mean_abs_shap = np.mean(np.abs(shap_values.values), axis=2)
122
+ else: # If it's already 2D (e.g., binary classification or regression SHAP)
123
+ mean_abs_shap = np.abs(shap_values.values)
124
+
125
+ # Create DataFrame for the results
126
+ mean_abs_shap_df = pd.DataFrame(mean_abs_shap, columns=df.columns, index=df.index)
127
+
128
+ return mean_abs_shap_df
129
+
130
+ def run_simulations_frame_global(df, num_simulations=4, clustering_method="KMEANS"):
131
+ """
132
+ Runs multiple simulations of SHAP value calculation in parallel and averages.
133
+ """
134
+ # Imports needed for parallel execution and DataFrame manipulation
135
+ from joblib import Parallel, delayed
136
+ import pandas as pd
137
+
138
+ data_hash = hash_of_df(df) # Uses function defined above (triggers its pandas import if first call)
139
+ tasks = []
140
+ for i in range(num_simulations):
141
+ # Use random module (imported globally)
142
+ # Seed random state for reproducibility within the loop based on data hash
143
+ current_seed = int(data_hash, 16) + i
144
+ random.seed(current_seed)
145
+ # Generate distinct random states for clustering and model training per simulation
146
+ sim_random_state = random.randint(0, 2**32 - 1) # Use a large range for random state
147
+
148
+ tasks.append(
149
+ delayed(get_shap_values_for_dataset)(
150
+ df, clustering_method, 10, sim_random_state # Pass simulation-specific state
151
+ )
152
+ ) # This implicitly calls get_shap_values_for_dataset (triggering its imports)
153
+
154
+ # Catch warnings during parallel execution
155
+ with warnings.catch_warnings():
156
+ warnings.simplefilter("ignore")
157
+ # Run tasks in parallel, n_jobs=-1 uses all available CPU cores
158
+ all_shap_values = Parallel(n_jobs=-1, verbose=0)(tasks)
159
+
160
+ # Concatenate results and calculate the mean SHAP values grouped by index
161
+ # Assumes df.index is a MultiIndex with 'ticker' and 'date' levels
162
+ if isinstance(df.index, pd.MultiIndex) and all(level in df.index.names for level in ['ticker', 'date']):
163
+ avg_shap_values = pd.concat(all_shap_values).abs().groupby(level=["ticker", "date"]).mean()
164
+ else:
165
+ # Fallback if index is not the expected MultiIndex (might need adjustment)
166
+ # This assumes the index from the original df was preserved in mean_abs_shap_df
167
+ # The previous reset_index().groupby() might be safer if index structure varies
168
+ print("Warning: DataFrame index is not a MultiIndex with 'ticker' and 'date'. Averaging over the entire index.")
169
+ avg_shap_values = pd.concat(all_shap_values).abs().mean()
170
+
171
+
172
+ return avg_shap_values
173
+
174
+ def run_simulations_global_importance(df, num_simulations=4, clustering_method="KMEANS"):
175
+ """
176
+ Calculates overall feature importance based on averaged SHAP values.
177
+ """
178
+ # Imports needed for DataFrame creation and statistical calculation
179
+ import pandas as pd
180
+ from scipy import stats
181
+
182
+ # Get the averaged SHAP values per instance/group
183
+ avg_shap_values = run_simulations_frame_global(
184
+ df, num_simulations=num_simulations, clustering_method=clustering_method
185
+ ) # Triggers imports in run_simulations_frame_global if not already loaded
186
+
187
+ # Calculate the mean importance across all instances/groups for each feature
188
+ feature_importance_values = avg_shap_values.mean(axis=0)
189
+
190
+ # Create the feature importance DataFrame
191
+ feature_importance = pd.DataFrame(
192
+ {
193
+ "feature": feature_importance_values.index,
194
+ "importance": feature_importance_values.values,
195
+ }
196
+ )
197
+
198
+ # Calculate percentile rank for each feature's importance
199
+ feature_importance["importance_percentile"] = feature_importance["importance"].apply(
200
+ lambda x: stats.percentileofscore(feature_importance["importance"], x, kind='rank')
201
+ )
202
+ # Alternative using numpy might be faster for large numbers of features:
203
+ # feature_importance["importance_percentile"] = feature_importance["importance"].rank(pct=True) * 100
204
+
205
+ # Sort by importance percentile descending
206
+ return feature_importance.sort_values(
207
+ "importance_percentile", ascending=False
208
+ ).reset_index(drop=True)
209
+
210
+
211
+ # Usage example:
212
+ # Assuming 'df' is your pandas DataFrame with features and a MultiIndex ('ticker', 'date')
213
+ # importance_df = run_simulations_global_importance(df, num_simulations=10, clustering_method='KMEANS')
214
+ # print(importance_df)
@@ -808,6 +808,15 @@ def find_tickers(
808
808
  logger.debug(f"Mapped {len(sample_identifiers)} identifiers to {len(result)} unique tickers")
809
809
  return result
810
810
 
811
+ @lru_cache(maxsize=1)
812
+ def _get_ticker_codes_df():
813
+ logger.debug("Loading ticker mapping codes...")
814
+ try:
815
+ return pd.read_parquet("data/codes.parq")
816
+ except Exception as e:
817
+ logger.error(f"Failed to load ticker mapping data: {e}")
818
+ raise ValueError(f"Cannot perform ticker mapping: {e}")
819
+
811
820
 
812
821
  def ticker_mapper(params: Dict[str, Any], verbose: bool = False) -> Dict[str, Any]:
813
822
  """
@@ -828,7 +837,7 @@ def ticker_mapper(params: Dict[str, Any], verbose: bool = False) -> Dict[str, An
828
837
 
829
838
  # Load ticker mapping data
830
839
  try:
831
- df_codes = pd.read_parquet("data/codes.parq")
840
+ df_codes = _get_ticker_codes_df()
832
841
  except Exception as e:
833
842
  logger.error(f"Failed to load ticker mapping data: {e}")
834
843
  raise ValueError(f"Cannot perform ticker mapping: {e}")
@@ -1,39 +0,0 @@
1
- """
2
- Main SovAI SDK Tool Kit package
3
- """
4
- from .api_config import read_key, save_key, ApiConfig
5
- from .get_data import data
6
- from .basic_auth import basic_auth
7
- from .token_auth import token_auth
8
- from .utils.file_management import save_or_update_tickers, save_or_update_codes, update_data_files
9
-
10
-
11
- import warnings
12
- warnings.filterwarnings("ignore")
13
-
14
-
15
- try:
16
- from importlib.metadata import version
17
- __version__ = version("sovai")
18
- except:
19
- __version__ = "0.2.49" # Fallback to current version in pyproject.toml
20
-
21
-
22
- try:
23
- from .get_plots import plot
24
- from .get_reports import report
25
- from .get_compute import compute
26
- from .studies.nowcasting import nowcast
27
- from .extensions.pandas_extensions import CustomDataFrame as extension
28
- from .get_tools import sec_search, sec_filing, code, sec_graph
29
- HAS_FULL_INSTALL = True
30
- except ImportError as e:
31
- print("this is the lean installation, for full use sovai[full]")
32
- # print(f"ImportError: {e}")
33
- HAS_FULL_INSTALL = False
34
-
35
-
36
- __all__ = ['read_key', 'save_key', 'ApiConfig', 'data', 'basic_auth', 'token_auth', 'update_data_files']
37
-
38
- if HAS_FULL_INSTALL:
39
- __all__ += ['plot', 'report', 'compute', 'nowcast', 'extension', 'sec_search', 'sec_filing', 'code', 'sec_graph']
@@ -1,127 +0,0 @@
1
- import pandas as pd
2
- import numpy as np
3
- import random
4
- from lightgbm import LGBMClassifier
5
- from sklearn.cluster import KMeans, MeanShift, HDBSCAN
6
- from sklearn.utils.class_weight import compute_sample_weight
7
- from sklearn.preprocessing import StandardScaler
8
- import shap
9
- from joblib import Parallel, delayed
10
- import hashlib
11
- from scipy import stats
12
- import os
13
- import warnings
14
-
15
-
16
- def hash_of_df(df, sample_size=100):
17
- df_sample = (
18
- df.sample(n=min(sample_size, len(df)), random_state=42).to_string().encode()
19
- )
20
- return hashlib.sha256(df_sample).hexdigest()
21
-
22
- class ClusteringExplainer:
23
- def __init__(self, random_state=42):
24
- self.random_state = random_state
25
- self.model = None
26
- self.explainer = None
27
- self.scaler = StandardScaler()
28
-
29
- def fit(self, X, y):
30
- classes_weights = compute_sample_weight(class_weight="balanced", y=y)
31
- self.model = LGBMClassifier(
32
- objective="multiclass",
33
- random_state=self.random_state,
34
- verbose=-1,
35
- force_col_wise=True,
36
- min_gain_to_split=0.01
37
- )
38
- with warnings.catch_warnings():
39
- warnings.simplefilter("ignore")
40
- self.model.fit(X, y, sample_weight=classes_weights)
41
- self.explainer = shap.Explainer(self.model)
42
-
43
- def get_shap_values(self, X):
44
- return self.explainer(X)
45
-
46
- def get_shap_values_for_dataset(df, clustering_method="KMEANS", n_clusters=10, random_state=42, sample_size=5000):
47
- # Sample for clustering and model training
48
- X_sample = df.sample(n=min(sample_size, len(df)), random_state=random_state)
49
-
50
- scaler = StandardScaler()
51
- X_sample_scaled = scaler.fit_transform(X_sample)
52
-
53
- if clustering_method == "KMEANS":
54
- num_clusters = random.randint(5, 10)
55
- clustering = KMeans(n_clusters=num_clusters, random_state=random_state)
56
- elif clustering_method == "MEANSHIFT":
57
- clustering = MeanShift()
58
- elif clustering_method == "HDBSCAN":
59
- clustering = HDBSCAN(min_cluster_size=5)
60
- else:
61
- raise ValueError(f"Unsupported clustering method: {clustering_method}")
62
-
63
- with warnings.catch_warnings():
64
- warnings.simplefilter("ignore")
65
- clustering.fit(X_sample_scaled)
66
-
67
- y_sample = clustering.labels_
68
-
69
- if clustering_method == "HDBSCAN":
70
- noise_label = max(y_sample) + 1
71
- y_sample[y_sample == -1] = noise_label
72
-
73
- clust_explnr = ClusteringExplainer(random_state=random_state)
74
- clust_explnr.fit(X_sample_scaled, y_sample)
75
-
76
- # Transform the entire dataset
77
- X_full_scaled = scaler.transform(df)
78
-
79
- # Get SHAP values for the entire dataset
80
- shap_values = clust_explnr.get_shap_values(X_full_scaled)
81
- mean_abs_shap = np.mean(np.abs(shap_values.values), axis=2)
82
- mean_abs_shap_df = pd.DataFrame(mean_abs_shap, columns=df.columns, index=df.index)
83
-
84
- return mean_abs_shap_df
85
-
86
- def run_simulations_frame_global(df, num_simulations=4, clustering_method="KMEANS"):
87
- data_hash = hash_of_df(df)
88
- tasks = []
89
- for i in range(num_simulations):
90
- random.seed(int(data_hash, 16) + i)
91
- kmeans_random_state = random.randint(0, 1000)
92
- lgbm_random_state = random.randint(0, 1000)
93
- tasks.append(
94
- delayed(get_shap_values_for_dataset)(
95
- df, clustering_method, 10, kmeans_random_state
96
- )
97
- )
98
- with warnings.catch_warnings():
99
- warnings.simplefilter("ignore")
100
- all_shap_values = Parallel(n_jobs=-1, verbose=0)(tasks)
101
- # print(all_shap_values)
102
- # print(all_shap_values.shape)
103
-
104
- # print(all_shap_values[0].index)
105
- # print(all_shap_values[2].index)
106
- avg_shap_values = pd.concat(all_shap_values).abs().reset_index().groupby(by=["ticker","date"]).mean()
107
- # avg_shap_values.index = df.index
108
- return avg_shap_values
109
-
110
- def run_simulations_global_importance(df, num_simulations=4, clustering_method="KMEANS"):
111
- avg_shap_values = run_simulations_frame_global(df, num_simulations=num_simulations, clustering_method=clustering_method)
112
- feature_importance = pd.DataFrame(
113
- {
114
- "feature": avg_shap_values.columns,
115
- "importance": avg_shap_values.mean().values,
116
- }
117
- )
118
- feature_importance["importance_percentile"] = stats.percentileofscore(
119
- feature_importance["importance"], feature_importance["importance"]
120
- )
121
- return feature_importance.sort_values(
122
- "importance_percentile", ascending=False
123
- ).reset_index(drop=True)
124
-
125
-
126
- # Usage example:
127
- # importance_df = run_simulations(df, num_simulations=4, clustering_method='KMEANS')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes