We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
:wrench: Improve logging for analysis blocks
## Introduction

Lightwood is an AutoML library that aims at abstracting common procedures in the Machine Learning workflow (e.g. data preparation, feature engineering, model building, model analysis) with an approach that can be fully automated but easily customizable and extended thanks to its flexibility. 

To analyze a model, a series of "analysis blocks" run after the ensemble step and when generating predictions. Each of these blocks can (1) do some type of analysis after training has finished, (2) do computations at inference time to give additional insights about predictions (e.g. a confidence score), or both (1) and (2).

## Task

In this task, we seek to improve our logging so that the user knows what block is currently running.

## Steps :male_detective: :female_detective: 

- Fork the Lightwood repository, checkout the `staging` branch and from it create a new one.
- Add the appropriate log.info() call inside `lightwood/analysis/analyze.py` saying something similar to _"The block X is now running its analyze() method"_.
- Add the appropriate log.info() call inside `lightwood/analysis/explain.py` saying something similar to _"The block X is now running its explain() method"_.
- Run an integration test to ensure the log calls are being displayed.
- Make the PR and address any comments that reviewers might make.

## Additional rewards :1st_place_medal: 

Accepted PRs for improved logging or documentation bring :one: point for entry into the draw for a :computer:  Deep Learning Laptop powered by the NVIDIA RTX 3080 Max-Q GPU or other swag :shirt: :bear: .  For more info check out https://mindsdb.com/hacktoberfest/

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lightwood/analysis/explain.py`
Content:
```
1 from typing import Optional, List, Dict
2 import torch
3 import pandas as pd
4 
5 from lightwood.api.types import TimeseriesSettings
6 from lightwood.helpers.ts import get_inferred_timestamps
7 from lightwood.analysis.base import BaseAnalysisBlock
8 
9 
10 def explain(data: pd.DataFrame,
11             encoded_data: torch.Tensor,
12             predictions: pd.DataFrame,
13             timeseries_settings: TimeseriesSettings,
14             analysis: Dict,
15             target_name: str,
16             target_dtype: str,
17 
18             positive_domain: bool,  # @TODO: pass inside a {} with params for each block to avoid signature overload
19             fixed_confidence: float,
20             anomaly_detection: bool,
21 
22             # forces specific confidence level in ICP
23             anomaly_error_rate: float,
24 
25             # ignores anomaly detection for N steps after an
26             # initial anomaly triggers the cooldown period;
27             # implicitly assumes series are regularly spaced
28             anomaly_cooldown: int,
29 
30             explainer_blocks: Optional[List[BaseAnalysisBlock]] = [],
31             ts_analysis: Optional[Dict] = {}
32             ):
33     """
34     This procedure runs at the end of every normal `.predict()` call. Its goal is to generate prediction insights,
35     potentially using information generated at the model analysis stage (e.g. confidence estimation).
36 
37     As in `analysis()`, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.
38 
39     :return:
40     row_insights: a DataFrame containing predictions and all generated insights at a row-level.
41     """
42 
43     # ------------------------- #
44     # Setup base insights
45     # ------------------------- #
46     data = data.reset_index(drop=True)
47 
48     row_insights = pd.DataFrame()
49     global_insights = {}
50     row_insights['prediction'] = predictions['prediction']
51 
52     if target_name in data.columns:
53         row_insights['truth'] = data[target_name]
54     else:
55         row_insights['truth'] = [None] * len(predictions['prediction'])
56 
57     if timeseries_settings.is_timeseries:
58         if timeseries_settings.group_by:
59             for col in timeseries_settings.group_by:
60                 row_insights[f'group_{col}'] = data[col]
61 
62         for col in timeseries_settings.order_by:
63             row_insights[f'order_{col}'] = data[col]
64 
65         for col in timeseries_settings.order_by:
66             row_insights[f'order_{col}'] = get_inferred_timestamps(
67                 row_insights, col, ts_analysis['deltas'], timeseries_settings)
68 
69     kwargs = {
70         'data': data,
71         'encoded_data': encoded_data,
72         'predictions': predictions,
73         'analysis': analysis,
74         'target_name': target_name,
75         'target_dtype': target_dtype,
76         'tss': timeseries_settings,
77         'positive_domain': positive_domain,
78         'fixed_confidence': fixed_confidence,
79         'anomaly_detection': anomaly_detection,
80         'anomaly_error_rate': anomaly_error_rate,
81         'anomaly_cooldown': anomaly_cooldown
82     }
83 
84     # ------------------------- #
85     # Call explanation blocks
86     # ------------------------- #
87     for block in explainer_blocks:
88         row_insights, global_insights = block.explain(row_insights, global_insights, **kwargs)
89 
90     return row_insights, global_insights
91
```

Path: `lightwood/analysis/analyze.py`
Content:
```
1 from typing import Dict, List, Tuple, Optional
2 
3 from lightwood.api import dtype
4 from lightwood.ensemble import BaseEnsemble
5 from lightwood.analysis.base import BaseAnalysisBlock
6 from lightwood.data.encoded_ds import EncodedDs
7 from lightwood.encoder.text.pretrained import PretrainedLangEncoder
8 from lightwood.api.types import ModelAnalysis, StatisticalAnalysis, TimeseriesSettings, PredictionArguments
9 
10 
11 def model_analyzer(
12     predictor: BaseEnsemble,
13     data: EncodedDs,
14     train_data: EncodedDs,
15     stats_info: StatisticalAnalysis,
16     target: str,
17     ts_cfg: TimeseriesSettings,
18     dtype_dict: Dict[str, str],
19     accuracy_functions,
20     analysis_blocks: Optional[List[BaseAnalysisBlock]] = []
21 ) -> Tuple[ModelAnalysis, Dict[str, object]]:
22     """
23     Analyses model on a validation subset to evaluate accuracy, estimate feature importance and generate a
24     calibration model to estimating confidence in future predictions.
25 
26     Additionally, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.
27 
28     :return:
29     runtime_analyzer: This dictionary object gets populated in a sequential fashion with data generated from
30     any `.analyze()` block call. This dictionary object is stored in the predictor itself, and used when
31     calling the `.explain()` method of all analysis blocks when generating predictions.
32 
33     model_analysis: `ModelAnalysis` object that contains core analysis metrics, not necessarily needed when predicting.
34     """
35 
36     runtime_analyzer = {}
37     data_type = dtype_dict[target]
38 
39     # retrieve encoded data representations
40     encoded_train_data = train_data
41     encoded_val_data = data
42     data = encoded_val_data.data_frame
43     input_cols = list([col for col in data.columns if col != target])
44 
45     # predictive task
46     is_numerical = data_type in (dtype.integer, dtype.float, dtype.array, dtype.tsarray, dtype.quantity)
47     is_classification = data_type in (dtype.categorical, dtype.binary)
48     is_multi_ts = ts_cfg.is_timeseries and ts_cfg.nr_predictions > 1
49     has_pretrained_text_enc = any([isinstance(enc, PretrainedLangEncoder)
50                                    for enc in encoded_train_data.encoders.values()])
51 
52     # raw predictions for validation dataset
53     args = {} if not is_classification else {"predict_proba": True}
54     normal_predictions = predictor(encoded_val_data, args=PredictionArguments.from_dict(args))
55     normal_predictions = normal_predictions.set_index(data.index)
56 
57     # ------------------------- #
58     # Run analysis blocks, both core and user-defined
59     # ------------------------- #
60     kwargs = {
61         'predictor': predictor,
62         'target': target,
63         'input_cols': input_cols,
64         'dtype_dict': dtype_dict,
65         'normal_predictions': normal_predictions,
66         'data': data,
67         'train_data': train_data,
68         'encoded_val_data': encoded_val_data,
69         'is_classification': is_classification,
70         'is_numerical': is_numerical,
71         'is_multi_ts': is_multi_ts,
72         'stats_info': stats_info,
73         'ts_cfg': ts_cfg,
74         'accuracy_functions': accuracy_functions,
75         'has_pretrained_text_enc': has_pretrained_text_enc
76     }
77 
78     for block in analysis_blocks:
79         runtime_analyzer = block.analyze(runtime_analyzer, **kwargs)
80 
81     # ------------------------- #
82     # Populate ModelAnalysis object
83     # ------------------------- #
84     model_analysis = ModelAnalysis(
85         accuracies=runtime_analyzer['score_dict'],
86         accuracy_histogram=runtime_analyzer['acc_histogram'],
87         accuracy_samples=runtime_analyzer['acc_samples'],
88         train_sample_size=len(encoded_train_data),
89         test_sample_size=len(encoded_val_data),
90         confusion_matrix=runtime_analyzer['cm'],
91         column_importances=runtime_analyzer['column_importances'],
92         histograms=stats_info.histograms,
93         dtypes=dtype_dict
94     )
95 
96     return model_analysis, runtime_analyzer
97
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lightwood/analysis/analyze.py b/lightwood/analysis/analyze.py
--- a/lightwood/analysis/analyze.py
+++ b/lightwood/analysis/analyze.py
@@ -1,5 +1,6 @@
 from typing import Dict, List, Tuple, Optional
 
+from lightwood.helpers.log import log
 from lightwood.api import dtype
 from lightwood.ensemble import BaseEnsemble
 from lightwood.analysis.base import BaseAnalysisBlock
@@ -76,6 +77,7 @@
     }
 
     for block in analysis_blocks:
+        log.info("The block %s is now running its analyze() method", block.__class__.__name__)
         runtime_analyzer = block.analyze(runtime_analyzer, **kwargs)
 
     # ------------------------- #
diff --git a/lightwood/analysis/explain.py b/lightwood/analysis/explain.py
--- a/lightwood/analysis/explain.py
+++ b/lightwood/analysis/explain.py
@@ -2,6 +2,7 @@
 import torch
 import pandas as pd
 
+from lightwood.helpers.log import log
 from lightwood.api.types import TimeseriesSettings
 from lightwood.helpers.ts import get_inferred_timestamps
 from lightwood.analysis.base import BaseAnalysisBlock
@@ -85,6 +86,7 @@
     # Call explanation blocks
     # ------------------------- #
     for block in explainer_blocks:
+        log.info("The block %s is now running its explain() method", block.__class__.__name__)
         row_insights, global_insights = block.explain(row_insights, global_insights, **kwargs)
 
     return row_insights, global_insights

{"golden_diff": "diff --git a/lightwood/analysis/analyze.py b/lightwood/analysis/analyze.py\n--- a/lightwood/analysis/analyze.py\n+++ b/lightwood/analysis/analyze.py\n@@ -1,5 +1,6 @@\n from typing import Dict, List, Tuple, Optional\n \n+from lightwood.helpers.log import log\n from lightwood.api import dtype\n from lightwood.ensemble import BaseEnsemble\n from lightwood.analysis.base import BaseAnalysisBlock\n@@ -76,6 +77,7 @@\n     }\n \n     for block in analysis_blocks:\n+        log.info(\"The block %s is now running its analyze() method\", block.__class__.__name__)\n         runtime_analyzer = block.analyze(runtime_analyzer, **kwargs)\n \n     # ------------------------- #\ndiff --git a/lightwood/analysis/explain.py b/lightwood/analysis/explain.py\n--- a/lightwood/analysis/explain.py\n+++ b/lightwood/analysis/explain.py\n@@ -2,6 +2,7 @@\n import torch\n import pandas as pd\n \n+from lightwood.helpers.log import log\n from lightwood.api.types import TimeseriesSettings\n from lightwood.helpers.ts import get_inferred_timestamps\n from lightwood.analysis.base import BaseAnalysisBlock\n@@ -85,6 +86,7 @@\n     # Call explanation blocks\n     # ------------------------- #\n     for block in explainer_blocks:\n+        log.info(\"The block %s is now running its explain() method\", block.__class__.__name__)\n         row_insights, global_insights = block.explain(row_insights, global_insights, **kwargs)\n \n     return row_insights, global_insights\n", "issue": ":wrench: Improve logging for analysis blocks\n## Introduction\r\n\r\nLightwood is an AutoML library that aims at abstracting common procedures in the Machine Learning workflow (e.g. data preparation, feature engineering, model building, model analysis) with an approach that can be fully automated but easily customizable and extended thanks to its flexibility. \r\n\r\nTo analyze a model, a series of \"analysis blocks\" run after the ensemble step and when generating predictions. Each of these blocks can (1) do some type of analysis after training has finished, (2) do computations at inference time to give additional insights about predictions (e.g. a confidence score), or both (1) and (2).\r\n\r\n## Task\r\n\r\nIn this task, we seek to improve our logging so that the user knows what block is currently running.\r\n\r\n## Steps :male_detective: :female_detective: \r\n\r\n- Fork the Lightwood repository, checkout the `staging` branch and from it create a new one.\r\n- Add the appropriate log.info() call inside `lightwood/analysis/analyze.py` saying something similar to _\"The block X is now running its analyze() method\"_.\r\n- Add the appropriate log.info() call inside `lightwood/analysis/explain.py` saying something similar to _\"The block X is now running its explain() method\"_.\r\n- Run an integration test to ensure the log calls are being displayed.\r\n- Make the PR and address any comments that reviewers might make.\r\n\r\n## Additional rewards :1st_place_medal: \r\n\r\nAccepted PRs for improved logging or documentation bring :one: point for entry into the draw for a :computer:  Deep Learning Laptop powered by the NVIDIA RTX 3080 Max-Q GPU or other swag :shirt: :bear: .  For more info check out https://mindsdb.com/hacktoberfest/\n", "before_files": [{"content": "from typing import Optional, List, Dict\nimport torch\nimport pandas as pd\n\nfrom lightwood.api.types import TimeseriesSettings\nfrom lightwood.helpers.ts import get_inferred_timestamps\nfrom lightwood.analysis.base import BaseAnalysisBlock\n\n\ndef explain(data: pd.DataFrame,\n            encoded_data: torch.Tensor,\n            predictions: pd.DataFrame,\n            timeseries_settings: TimeseriesSettings,\n            analysis: Dict,\n            target_name: str,\n            target_dtype: str,\n\n            positive_domain: bool,  # @TODO: pass inside a {} with params for each block to avoid signature overload\n            fixed_confidence: float,\n            anomaly_detection: bool,\n\n            # forces specific confidence level in ICP\n            anomaly_error_rate: float,\n\n            # ignores anomaly detection for N steps after an\n            # initial anomaly triggers the cooldown period;\n            # implicitly assumes series are regularly spaced\n            anomaly_cooldown: int,\n\n            explainer_blocks: Optional[List[BaseAnalysisBlock]] = [],\n            ts_analysis: Optional[Dict] = {}\n            ):\n    \"\"\"\n    This procedure runs at the end of every normal `.predict()` call. Its goal is to generate prediction insights,\n    potentially using information generated at the model analysis stage (e.g. confidence estimation).\n\n    As in `analysis()`, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.\n\n    :return:\n    row_insights: a DataFrame containing predictions and all generated insights at a row-level.\n    \"\"\"\n\n    # ------------------------- #\n    # Setup base insights\n    # ------------------------- #\n    data = data.reset_index(drop=True)\n\n    row_insights = pd.DataFrame()\n    global_insights = {}\n    row_insights['prediction'] = predictions['prediction']\n\n    if target_name in data.columns:\n        row_insights['truth'] = data[target_name]\n    else:\n        row_insights['truth'] = [None] * len(predictions['prediction'])\n\n    if timeseries_settings.is_timeseries:\n        if timeseries_settings.group_by:\n            for col in timeseries_settings.group_by:\n                row_insights[f'group_{col}'] = data[col]\n\n        for col in timeseries_settings.order_by:\n            row_insights[f'order_{col}'] = data[col]\n\n        for col in timeseries_settings.order_by:\n            row_insights[f'order_{col}'] = get_inferred_timestamps(\n                row_insights, col, ts_analysis['deltas'], timeseries_settings)\n\n    kwargs = {\n        'data': data,\n        'encoded_data': encoded_data,\n        'predictions': predictions,\n        'analysis': analysis,\n        'target_name': target_name,\n        'target_dtype': target_dtype,\n        'tss': timeseries_settings,\n        'positive_domain': positive_domain,\n        'fixed_confidence': fixed_confidence,\n        'anomaly_detection': anomaly_detection,\n        'anomaly_error_rate': anomaly_error_rate,\n        'anomaly_cooldown': anomaly_cooldown\n    }\n\n    # ------------------------- #\n    # Call explanation blocks\n    # ------------------------- #\n    for block in explainer_blocks:\n        row_insights, global_insights = block.explain(row_insights, global_insights, **kwargs)\n\n    return row_insights, global_insights\n", "path": "lightwood/analysis/explain.py"}, {"content": "from typing import Dict, List, Tuple, Optional\n\nfrom lightwood.api import dtype\nfrom lightwood.ensemble import BaseEnsemble\nfrom lightwood.analysis.base import BaseAnalysisBlock\nfrom lightwood.data.encoded_ds import EncodedDs\nfrom lightwood.encoder.text.pretrained import PretrainedLangEncoder\nfrom lightwood.api.types import ModelAnalysis, StatisticalAnalysis, TimeseriesSettings, PredictionArguments\n\n\ndef model_analyzer(\n    predictor: BaseEnsemble,\n    data: EncodedDs,\n    train_data: EncodedDs,\n    stats_info: StatisticalAnalysis,\n    target: str,\n    ts_cfg: TimeseriesSettings,\n    dtype_dict: Dict[str, str],\n    accuracy_functions,\n    analysis_blocks: Optional[List[BaseAnalysisBlock]] = []\n) -> Tuple[ModelAnalysis, Dict[str, object]]:\n    \"\"\"\n    Analyses model on a validation subset to evaluate accuracy, estimate feature importance and generate a\n    calibration model to estimating confidence in future predictions.\n\n    Additionally, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.\n\n    :return:\n    runtime_analyzer: This dictionary object gets populated in a sequential fashion with data generated from\n    any `.analyze()` block call. This dictionary object is stored in the predictor itself, and used when\n    calling the `.explain()` method of all analysis blocks when generating predictions.\n\n    model_analysis: `ModelAnalysis` object that contains core analysis metrics, not necessarily needed when predicting.\n    \"\"\"\n\n    runtime_analyzer = {}\n    data_type = dtype_dict[target]\n\n    # retrieve encoded data representations\n    encoded_train_data = train_data\n    encoded_val_data = data\n    data = encoded_val_data.data_frame\n    input_cols = list([col for col in data.columns if col != target])\n\n    # predictive task\n    is_numerical = data_type in (dtype.integer, dtype.float, dtype.array, dtype.tsarray, dtype.quantity)\n    is_classification = data_type in (dtype.categorical, dtype.binary)\n    is_multi_ts = ts_cfg.is_timeseries and ts_cfg.nr_predictions > 1\n    has_pretrained_text_enc = any([isinstance(enc, PretrainedLangEncoder)\n                                   for enc in encoded_train_data.encoders.values()])\n\n    # raw predictions for validation dataset\n    args = {} if not is_classification else {\"predict_proba\": True}\n    normal_predictions = predictor(encoded_val_data, args=PredictionArguments.from_dict(args))\n    normal_predictions = normal_predictions.set_index(data.index)\n\n    # ------------------------- #\n    # Run analysis blocks, both core and user-defined\n    # ------------------------- #\n    kwargs = {\n        'predictor': predictor,\n        'target': target,\n        'input_cols': input_cols,\n        'dtype_dict': dtype_dict,\n        'normal_predictions': normal_predictions,\n        'data': data,\n        'train_data': train_data,\n        'encoded_val_data': encoded_val_data,\n        'is_classification': is_classification,\n        'is_numerical': is_numerical,\n        'is_multi_ts': is_multi_ts,\n        'stats_info': stats_info,\n        'ts_cfg': ts_cfg,\n        'accuracy_functions': accuracy_functions,\n        'has_pretrained_text_enc': has_pretrained_text_enc\n    }\n\n    for block in analysis_blocks:\n        runtime_analyzer = block.analyze(runtime_analyzer, **kwargs)\n\n    # ------------------------- #\n    # Populate ModelAnalysis object\n    # ------------------------- #\n    model_analysis = ModelAnalysis(\n        accuracies=runtime_analyzer['score_dict'],\n        accuracy_histogram=runtime_analyzer['acc_histogram'],\n        accuracy_samples=runtime_analyzer['acc_samples'],\n        train_sample_size=len(encoded_train_data),\n        test_sample_size=len(encoded_val_data),\n        confusion_matrix=runtime_analyzer['cm'],\n        column_importances=runtime_analyzer['column_importances'],\n        histograms=stats_info.histograms,\n        dtypes=dtype_dict\n    )\n\n    return model_analysis, runtime_analyzer\n", "path": "lightwood/analysis/analyze.py"}], "after_files": [{"content": "from typing import Optional, List, Dict\nimport torch\nimport pandas as pd\n\nfrom lightwood.helpers.log import log\nfrom lightwood.api.types import TimeseriesSettings\nfrom lightwood.helpers.ts import get_inferred_timestamps\nfrom lightwood.analysis.base import BaseAnalysisBlock\n\n\ndef explain(data: pd.DataFrame,\n            encoded_data: torch.Tensor,\n            predictions: pd.DataFrame,\n            timeseries_settings: TimeseriesSettings,\n            analysis: Dict,\n            target_name: str,\n            target_dtype: str,\n\n            positive_domain: bool,  # @TODO: pass inside a {} with params for each block to avoid signature overload\n            fixed_confidence: float,\n            anomaly_detection: bool,\n\n            # forces specific confidence level in ICP\n            anomaly_error_rate: float,\n\n            # ignores anomaly detection for N steps after an\n            # initial anomaly triggers the cooldown period;\n            # implicitly assumes series are regularly spaced\n            anomaly_cooldown: int,\n\n            explainer_blocks: Optional[List[BaseAnalysisBlock]] = [],\n            ts_analysis: Optional[Dict] = {}\n            ):\n    \"\"\"\n    This procedure runs at the end of every normal `.predict()` call. Its goal is to generate prediction insights,\n    potentially using information generated at the model analysis stage (e.g. confidence estimation).\n\n    As in `analysis()`, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.\n\n    :return:\n    row_insights: a DataFrame containing predictions and all generated insights at a row-level.\n    \"\"\"\n\n    # ------------------------- #\n    # Setup base insights\n    # ------------------------- #\n    data = data.reset_index(drop=True)\n\n    row_insights = pd.DataFrame()\n    global_insights = {}\n    row_insights['prediction'] = predictions['prediction']\n\n    if target_name in data.columns:\n        row_insights['truth'] = data[target_name]\n    else:\n        row_insights['truth'] = [None] * len(predictions['prediction'])\n\n    if timeseries_settings.is_timeseries:\n        if timeseries_settings.group_by:\n            for col in timeseries_settings.group_by:\n                row_insights[f'group_{col}'] = data[col]\n\n        for col in timeseries_settings.order_by:\n            row_insights[f'order_{col}'] = data[col]\n\n        for col in timeseries_settings.order_by:\n            row_insights[f'order_{col}'] = get_inferred_timestamps(\n                row_insights, col, ts_analysis['deltas'], timeseries_settings)\n\n    kwargs = {\n        'data': data,\n        'encoded_data': encoded_data,\n        'predictions': predictions,\n        'analysis': analysis,\n        'target_name': target_name,\n        'target_dtype': target_dtype,\n        'tss': timeseries_settings,\n        'positive_domain': positive_domain,\n        'fixed_confidence': fixed_confidence,\n        'anomaly_detection': anomaly_detection,\n        'anomaly_error_rate': anomaly_error_rate,\n        'anomaly_cooldown': anomaly_cooldown\n    }\n\n    # ------------------------- #\n    # Call explanation blocks\n    # ------------------------- #\n    for block in explainer_blocks:\n        log.info(\"The block %s is now running its explain() method\", block.__class__.__name__)\n        row_insights, global_insights = block.explain(row_insights, global_insights, **kwargs)\n\n    return row_insights, global_insights\n", "path": "lightwood/analysis/explain.py"}, {"content": "from typing import Dict, List, Tuple, Optional\n\nfrom lightwood.helpers.log import log\nfrom lightwood.api import dtype\nfrom lightwood.ensemble import BaseEnsemble\nfrom lightwood.analysis.base import BaseAnalysisBlock\nfrom lightwood.data.encoded_ds import EncodedDs\nfrom lightwood.encoder.text.pretrained import PretrainedLangEncoder\nfrom lightwood.api.types import ModelAnalysis, StatisticalAnalysis, TimeseriesSettings, PredictionArguments\n\n\ndef model_analyzer(\n    predictor: BaseEnsemble,\n    data: EncodedDs,\n    train_data: EncodedDs,\n    stats_info: StatisticalAnalysis,\n    target: str,\n    ts_cfg: TimeseriesSettings,\n    dtype_dict: Dict[str, str],\n    accuracy_functions,\n    analysis_blocks: Optional[List[BaseAnalysisBlock]] = []\n) -> Tuple[ModelAnalysis, Dict[str, object]]:\n    \"\"\"\n    Analyses model on a validation subset to evaluate accuracy, estimate feature importance and generate a\n    calibration model to estimating confidence in future predictions.\n\n    Additionally, any user-specified analysis blocks (see class `BaseAnalysisBlock`) are also called here.\n\n    :return:\n    runtime_analyzer: This dictionary object gets populated in a sequential fashion with data generated from\n    any `.analyze()` block call. This dictionary object is stored in the predictor itself, and used when\n    calling the `.explain()` method of all analysis blocks when generating predictions.\n\n    model_analysis: `ModelAnalysis` object that contains core analysis metrics, not necessarily needed when predicting.\n    \"\"\"\n\n    runtime_analyzer = {}\n    data_type = dtype_dict[target]\n\n    # retrieve encoded data representations\n    encoded_train_data = train_data\n    encoded_val_data = data\n    data = encoded_val_data.data_frame\n    input_cols = list([col for col in data.columns if col != target])\n\n    # predictive task\n    is_numerical = data_type in (dtype.integer, dtype.float, dtype.array, dtype.tsarray, dtype.quantity)\n    is_classification = data_type in (dtype.categorical, dtype.binary)\n    is_multi_ts = ts_cfg.is_timeseries and ts_cfg.nr_predictions > 1\n    has_pretrained_text_enc = any([isinstance(enc, PretrainedLangEncoder)\n                                   for enc in encoded_train_data.encoders.values()])\n\n    # raw predictions for validation dataset\n    args = {} if not is_classification else {\"predict_proba\": True}\n    normal_predictions = predictor(encoded_val_data, args=PredictionArguments.from_dict(args))\n    normal_predictions = normal_predictions.set_index(data.index)\n\n    # ------------------------- #\n    # Run analysis blocks, both core and user-defined\n    # ------------------------- #\n    kwargs = {\n        'predictor': predictor,\n        'target': target,\n        'input_cols': input_cols,\n        'dtype_dict': dtype_dict,\n        'normal_predictions': normal_predictions,\n        'data': data,\n        'train_data': train_data,\n        'encoded_val_data': encoded_val_data,\n        'is_classification': is_classification,\n        'is_numerical': is_numerical,\n        'is_multi_ts': is_multi_ts,\n        'stats_info': stats_info,\n        'ts_cfg': ts_cfg,\n        'accuracy_functions': accuracy_functions,\n        'has_pretrained_text_enc': has_pretrained_text_enc\n    }\n\n    for block in analysis_blocks:\n        log.info(\"The block %s is now running its analyze() method\", block.__class__.__name__)\n        runtime_analyzer = block.analyze(runtime_analyzer, **kwargs)\n\n    # ------------------------- #\n    # Populate ModelAnalysis object\n    # ------------------------- #\n    model_analysis = ModelAnalysis(\n        accuracies=runtime_analyzer['score_dict'],\n        accuracy_histogram=runtime_analyzer['acc_histogram'],\n        accuracy_samples=runtime_analyzer['acc_samples'],\n        train_sample_size=len(encoded_train_data),\n        test_sample_size=len(encoded_val_data),\n        confusion_matrix=runtime_analyzer['cm'],\n        column_importances=runtime_analyzer['column_importances'],\n        histograms=stats_info.histograms,\n        dtypes=dtype_dict\n    )\n\n    return model_analysis, runtime_analyzer\n", "path": "lightwood/analysis/analyze.py"}]}