fg-data-profiling-wasm 0.1.0__py2.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.
- data_profiling/__init__.py +34 -0
- data_profiling/compare_reports.py +359 -0
- data_profiling/config.py +496 -0
- data_profiling/config_default.yaml +223 -0
- data_profiling/config_minimal.yaml +222 -0
- data_profiling/controller/__init__.py +1 -0
- data_profiling/controller/console.py +125 -0
- data_profiling/controller/pandas_decorator.py +21 -0
- data_profiling/expectations_report.py +117 -0
- data_profiling/model/__init__.py +4 -0
- data_profiling/model/alerts.py +780 -0
- data_profiling/model/correlations.py +163 -0
- data_profiling/model/dataframe.py +35 -0
- data_profiling/model/describe.py +210 -0
- data_profiling/model/description.py +108 -0
- data_profiling/model/duplicates.py +14 -0
- data_profiling/model/expectation_algorithms.py +112 -0
- data_profiling/model/handler.py +81 -0
- data_profiling/model/missing.py +146 -0
- data_profiling/model/pairwise.py +33 -0
- data_profiling/model/pandas/__init__.py +55 -0
- data_profiling/model/pandas/correlations_pandas.py +207 -0
- data_profiling/model/pandas/dataframe_pandas.py +26 -0
- data_profiling/model/pandas/describe_boolean_pandas.py +43 -0
- data_profiling/model/pandas/describe_categorical_pandas.py +274 -0
- data_profiling/model/pandas/describe_counts_pandas.py +63 -0
- data_profiling/model/pandas/describe_date_pandas.py +77 -0
- data_profiling/model/pandas/describe_file_pandas.py +56 -0
- data_profiling/model/pandas/describe_generic_pandas.py +36 -0
- data_profiling/model/pandas/describe_image_pandas.py +255 -0
- data_profiling/model/pandas/describe_numeric_pandas.py +175 -0
- data_profiling/model/pandas/describe_path_pandas.py +63 -0
- data_profiling/model/pandas/describe_supported_pandas.py +41 -0
- data_profiling/model/pandas/describe_text_pandas.py +62 -0
- data_profiling/model/pandas/describe_timeseries_pandas.py +222 -0
- data_profiling/model/pandas/describe_url_pandas.py +57 -0
- data_profiling/model/pandas/discretize_pandas.py +81 -0
- data_profiling/model/pandas/duplicates_pandas.py +56 -0
- data_profiling/model/pandas/imbalance_pandas.py +35 -0
- data_profiling/model/pandas/missing_pandas.py +42 -0
- data_profiling/model/pandas/sample_pandas.py +38 -0
- data_profiling/model/pandas/summary_pandas.py +101 -0
- data_profiling/model/pandas/table_pandas.py +56 -0
- data_profiling/model/pandas/timeseries_index_pandas.py +33 -0
- data_profiling/model/pandas/utils_pandas.py +27 -0
- data_profiling/model/sample.py +37 -0
- data_profiling/model/spark/__init__.py +48 -0
- data_profiling/model/spark/correlations_spark.py +152 -0
- data_profiling/model/spark/dataframe_spark.py +34 -0
- data_profiling/model/spark/describe_boolean_spark.py +27 -0
- data_profiling/model/spark/describe_categorical_spark.py +28 -0
- data_profiling/model/spark/describe_counts_spark.py +105 -0
- data_profiling/model/spark/describe_date_spark.py +51 -0
- data_profiling/model/spark/describe_generic_spark.py +30 -0
- data_profiling/model/spark/describe_numeric_spark.py +155 -0
- data_profiling/model/spark/describe_supported_spark.py +33 -0
- data_profiling/model/spark/describe_text_spark.py +25 -0
- data_profiling/model/spark/duplicates_spark.py +54 -0
- data_profiling/model/spark/missing_spark.py +96 -0
- data_profiling/model/spark/sample_spark.py +43 -0
- data_profiling/model/spark/summary_spark.py +95 -0
- data_profiling/model/spark/table_spark.py +58 -0
- data_profiling/model/spark/timeseries_index_spark.py +12 -0
- data_profiling/model/summarizer.py +207 -0
- data_profiling/model/summary.py +66 -0
- data_profiling/model/summary_algorithms.py +276 -0
- data_profiling/model/table.py +10 -0
- data_profiling/model/timeseries_index.py +16 -0
- data_profiling/model/typeset.py +365 -0
- data_profiling/model/typeset_relations.py +143 -0
- data_profiling/profile_report.py +576 -0
- data_profiling/report/__init__.py +4 -0
- data_profiling/report/formatters.py +346 -0
- data_profiling/report/presentation/__init__.py +1 -0
- data_profiling/report/presentation/core/__init__.py +39 -0
- data_profiling/report/presentation/core/alerts.py +18 -0
- data_profiling/report/presentation/core/collapse.py +24 -0
- data_profiling/report/presentation/core/container.py +50 -0
- data_profiling/report/presentation/core/correlation_table.py +21 -0
- data_profiling/report/presentation/core/dropdown.py +44 -0
- data_profiling/report/presentation/core/duplicate.py +16 -0
- data_profiling/report/presentation/core/frequency_table.py +14 -0
- data_profiling/report/presentation/core/frequency_table_small.py +16 -0
- data_profiling/report/presentation/core/html.py +14 -0
- data_profiling/report/presentation/core/image.py +34 -0
- data_profiling/report/presentation/core/item_renderer.py +17 -0
- data_profiling/report/presentation/core/renderable.py +42 -0
- data_profiling/report/presentation/core/root.py +35 -0
- data_profiling/report/presentation/core/sample.py +20 -0
- data_profiling/report/presentation/core/scores.py +32 -0
- data_profiling/report/presentation/core/table.py +26 -0
- data_profiling/report/presentation/core/toggle_button.py +14 -0
- data_profiling/report/presentation/core/variable.py +40 -0
- data_profiling/report/presentation/core/variable_info.py +36 -0
- data_profiling/report/presentation/flavours/__init__.py +9 -0
- data_profiling/report/presentation/flavours/flavour_html.py +64 -0
- data_profiling/report/presentation/flavours/flavour_widget.py +61 -0
- data_profiling/report/presentation/flavours/flavours.py +43 -0
- data_profiling/report/presentation/flavours/html/__init__.py +47 -0
- data_profiling/report/presentation/flavours/html/alerts.py +10 -0
- data_profiling/report/presentation/flavours/html/collapse.py +7 -0
- data_profiling/report/presentation/flavours/html/container.py +58 -0
- data_profiling/report/presentation/flavours/html/correlation_table.py +13 -0
- data_profiling/report/presentation/flavours/html/dropdown.py +7 -0
- data_profiling/report/presentation/flavours/html/duplicate.py +24 -0
- data_profiling/report/presentation/flavours/html/frequency_table.py +20 -0
- data_profiling/report/presentation/flavours/html/frequency_table_small.py +15 -0
- data_profiling/report/presentation/flavours/html/html.py +6 -0
- data_profiling/report/presentation/flavours/html/image.py +7 -0
- data_profiling/report/presentation/flavours/html/root.py +14 -0
- data_profiling/report/presentation/flavours/html/sample.py +12 -0
- data_profiling/report/presentation/flavours/html/scores.py +11 -0
- data_profiling/report/presentation/flavours/html/table.py +7 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_constant.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_constant_length.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_dirty_category.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_duplicates.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_empty.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_high_cardinality.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_high_correlation.html +4 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_imbalance.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_infinite.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_missing.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_near_duplicates.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_non_stationary.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_seasonal.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_skewed.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_truncated.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_type_date.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_uniform.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_unique.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_unsupported.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts/alert_zeros.html +1 -0
- data_profiling/report/presentation/flavours/html/templates/alerts.html +47 -0
- data_profiling/report/presentation/flavours/html/templates/collapse.html +11 -0
- data_profiling/report/presentation/flavours/html/templates/correlation_table.html +5 -0
- data_profiling/report/presentation/flavours/html/templates/diagram.html +11 -0
- data_profiling/report/presentation/flavours/html/templates/dropdown.html +16 -0
- data_profiling/report/presentation/flavours/html/templates/duplicate.html +5 -0
- data_profiling/report/presentation/flavours/html/templates/frequency_table.html +45 -0
- data_profiling/report/presentation/flavours/html/templates/frequency_table_small.html +34 -0
- data_profiling/report/presentation/flavours/html/templates/report.html +26 -0
- data_profiling/report/presentation/flavours/html/templates/sample.html +10 -0
- data_profiling/report/presentation/flavours/html/templates/scores.html +78 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/batch_grid.html +16 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/grid.html +18 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/list.html +7 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/named_list.html +8 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/overview_tabs.html +30 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/scores.html +3 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/sections.html +13 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/select.html +40 -0
- data_profiling/report/presentation/flavours/html/templates/sequence/tabs.html +30 -0
- data_profiling/report/presentation/flavours/html/templates/table.html +38 -0
- data_profiling/report/presentation/flavours/html/templates/toggle_button.html +18 -0
- data_profiling/report/presentation/flavours/html/templates/variable.html +7 -0
- data_profiling/report/presentation/flavours/html/templates/variable_info.html +49 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/bootstrap.bundle.min.js +7 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/bootstrap.min.css +6 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/cosmo.bootstrap.min.css +12 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/flatly.bootstrap.min.css +12 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/script.js +52 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/simplex.bootstrap.min.css +12 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/style.css +253 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/assets/united.bootstrap.min.css +12 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/footer.html +7 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/javascript.html +18 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/navigation.html +36 -0
- data_profiling/report/presentation/flavours/html/templates/wrapper/style.html +53 -0
- data_profiling/report/presentation/flavours/html/templates.py +76 -0
- data_profiling/report/presentation/flavours/html/toggle_button.py +7 -0
- data_profiling/report/presentation/flavours/html/variable.py +7 -0
- data_profiling/report/presentation/flavours/html/variable_info.py +7 -0
- data_profiling/report/presentation/flavours/widget/__init__.py +49 -0
- data_profiling/report/presentation/flavours/widget/alerts.py +45 -0
- data_profiling/report/presentation/flavours/widget/collapse.py +43 -0
- data_profiling/report/presentation/flavours/widget/container.py +121 -0
- data_profiling/report/presentation/flavours/widget/correlation_table.py +14 -0
- data_profiling/report/presentation/flavours/widget/dropdown.py +31 -0
- data_profiling/report/presentation/flavours/widget/duplicate.py +14 -0
- data_profiling/report/presentation/flavours/widget/frequency_table.py +57 -0
- data_profiling/report/presentation/flavours/widget/frequency_table_small.py +66 -0
- data_profiling/report/presentation/flavours/widget/html.py +11 -0
- data_profiling/report/presentation/flavours/widget/image.py +26 -0
- data_profiling/report/presentation/flavours/widget/notebook.py +81 -0
- data_profiling/report/presentation/flavours/widget/root.py +10 -0
- data_profiling/report/presentation/flavours/widget/sample.py +14 -0
- data_profiling/report/presentation/flavours/widget/table.py +30 -0
- data_profiling/report/presentation/flavours/widget/toggle_button.py +17 -0
- data_profiling/report/presentation/flavours/widget/variable.py +12 -0
- data_profiling/report/presentation/flavours/widget/variable_info.py +11 -0
- data_profiling/report/presentation/frequency_table_utils.py +141 -0
- data_profiling/report/structure/__init__.py +1 -0
- data_profiling/report/structure/correlations.py +123 -0
- data_profiling/report/structure/overview.py +376 -0
- data_profiling/report/structure/report.py +457 -0
- data_profiling/report/structure/variables/__init__.py +35 -0
- data_profiling/report/structure/variables/render_boolean.py +132 -0
- data_profiling/report/structure/variables/render_categorical.py +566 -0
- data_profiling/report/structure/variables/render_common.py +31 -0
- data_profiling/report/structure/variables/render_complex.py +102 -0
- data_profiling/report/structure/variables/render_count.py +172 -0
- data_profiling/report/structure/variables/render_date.py +143 -0
- data_profiling/report/structure/variables/render_file.py +70 -0
- data_profiling/report/structure/variables/render_generic.py +45 -0
- data_profiling/report/structure/variables/render_image.py +204 -0
- data_profiling/report/structure/variables/render_path.py +134 -0
- data_profiling/report/structure/variables/render_real.py +314 -0
- data_profiling/report/structure/variables/render_text.py +189 -0
- data_profiling/report/structure/variables/render_timeseries.py +371 -0
- data_profiling/report/structure/variables/render_url.py +132 -0
- data_profiling/report/utils.py +34 -0
- data_profiling/serialize_report.py +143 -0
- data_profiling/utils/__init__.py +1 -0
- data_profiling/utils/backend.py +9 -0
- data_profiling/utils/cache.py +59 -0
- data_profiling/utils/common.py +142 -0
- data_profiling/utils/compat.py +31 -0
- data_profiling/utils/dataframe.py +238 -0
- data_profiling/utils/logger.py +53 -0
- data_profiling/utils/notebook.py +8 -0
- data_profiling/utils/paths.py +45 -0
- data_profiling/utils/progress_bar.py +15 -0
- data_profiling/utils/styles.py +22 -0
- data_profiling/utils/versions.py +19 -0
- data_profiling/version.py +1 -0
- data_profiling/visualisation/__init__.py +1 -0
- data_profiling/visualisation/context.py +87 -0
- data_profiling/visualisation/missing.py +138 -0
- data_profiling/visualisation/plot.py +1158 -0
- data_profiling/visualisation/utils.py +113 -0
- fg_data_profiling_wasm-0.1.0.dist-info/METADATA +195 -0
- fg_data_profiling_wasm-0.1.0.dist-info/RECORD +238 -0
- fg_data_profiling_wasm-0.1.0.dist-info/WHEEL +6 -0
- fg_data_profiling_wasm-0.1.0.dist-info/entry_points.txt +3 -0
- fg_data_profiling_wasm-0.1.0.dist-info/licenses/LICENSE +21 -0
- fg_data_profiling_wasm-0.1.0.dist-info/top_level.txt +2 -0
- ydata_profiling/__init__.py +43 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Main module of data-profiling.
|
|
2
|
+
|
|
3
|
+
.. include:: ../../README.md
|
|
4
|
+
"""
|
|
5
|
+
# ignore numba warnings
|
|
6
|
+
import warnings # isort:skip # noqa
|
|
7
|
+
|
|
8
|
+
import importlib.util # isort:skip # noqa
|
|
9
|
+
from warnings import warn
|
|
10
|
+
|
|
11
|
+
from data_profiling.compare_reports import compare # isort:skip # noqa
|
|
12
|
+
from data_profiling.controller import pandas_decorator # isort:skip # noqa
|
|
13
|
+
from data_profiling.profile_report import ProfileReport # isort:skip # noqa
|
|
14
|
+
from data_profiling.version import __version__ # isort:skip # noqa
|
|
15
|
+
|
|
16
|
+
# backend
|
|
17
|
+
import data_profiling.model.pandas # isort:skip # noqa
|
|
18
|
+
|
|
19
|
+
spec = importlib.util.find_spec("pyspark")
|
|
20
|
+
if spec is not None:
|
|
21
|
+
import data_profiling.model.spark # isort:skip # noqa
|
|
22
|
+
|
|
23
|
+
spec_numba = importlib.util.find_spec("numba")
|
|
24
|
+
if spec_numba is not None:
|
|
25
|
+
from numba.core.errors import NumbaDeprecationWarning # isort:skip # noqa
|
|
26
|
+
|
|
27
|
+
warnings.simplefilter("ignore", category=NumbaDeprecationWarning)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"pandas_decorator",
|
|
31
|
+
"ProfileReport",
|
|
32
|
+
"__version__",
|
|
33
|
+
"compare",
|
|
34
|
+
]
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from dacite import from_dict
|
|
8
|
+
|
|
9
|
+
from data_profiling.config import Correlation, Settings
|
|
10
|
+
from data_profiling.model import BaseDescription
|
|
11
|
+
from data_profiling.model.alerts import Alert
|
|
12
|
+
from data_profiling.profile_report import ProfileReport
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _should_wrap(v1: Any, v2: Any) -> bool:
|
|
16
|
+
if isinstance(v1, (list, dict)):
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
if isinstance(v1, pd.DataFrame) and isinstance(v2, pd.DataFrame):
|
|
20
|
+
return v1.equals(v2)
|
|
21
|
+
if isinstance(v1, pd.Series) and isinstance(v2, pd.Series):
|
|
22
|
+
return v1.equals(v2)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return v1 == v2
|
|
26
|
+
except ValueError:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _update_merge_dict(d1: Any, d2: Any) -> dict:
|
|
31
|
+
# Unwrap d1 and d2 in new dictionary to keep non-shared keys with **d1, **d2
|
|
32
|
+
# Next unwrap a dict that treats shared keys
|
|
33
|
+
# If two keys have an equal value, we take that value as new value
|
|
34
|
+
# If the values are not equal, we recursively merge them
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
**d1,
|
|
38
|
+
**d2,
|
|
39
|
+
**{
|
|
40
|
+
k: [d1[k], d2[k]]
|
|
41
|
+
if _should_wrap(d1[k], d2[k])
|
|
42
|
+
else _update_merge_mixed(d1[k], d2[k])
|
|
43
|
+
for k in {*d1} & {*d2}
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _update_merge_seq(d1: Any, d2: Any) -> Union[list, tuple]:
|
|
49
|
+
# This case happens when values are merged
|
|
50
|
+
# It bundle values in a list, making sure
|
|
51
|
+
# to flatten them if they are already lists
|
|
52
|
+
|
|
53
|
+
if isinstance(d1, list) and isinstance(d2, list):
|
|
54
|
+
# This is the tuple for alerts
|
|
55
|
+
return d1, d2
|
|
56
|
+
elif isinstance(d1, tuple) and isinstance(d2, list):
|
|
57
|
+
return (*d1, d2)
|
|
58
|
+
else:
|
|
59
|
+
return [
|
|
60
|
+
*(d1 if isinstance(d1, list) else [d1]),
|
|
61
|
+
*(d2 if isinstance(d2, list) else [d2]),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _update_merge_mixed(d1: Any, d2: Any) -> Union[dict, list, tuple]:
|
|
66
|
+
if isinstance(d1, dict) and isinstance(d2, dict):
|
|
67
|
+
return _update_merge_dict(d1, d2)
|
|
68
|
+
else:
|
|
69
|
+
return _update_merge_seq(d1, d2)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _update_merge(d1: Optional[dict], d2: dict) -> dict:
|
|
73
|
+
# For convenience in the loop, allow d1 to be empty initially
|
|
74
|
+
if d1 is None:
|
|
75
|
+
return d2
|
|
76
|
+
|
|
77
|
+
if not isinstance(d1, dict) or not isinstance(d2, dict):
|
|
78
|
+
raise TypeError(
|
|
79
|
+
"Both arguments need to be of type dictionary (ProfileReport.description_set)"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return _update_merge_dict(d1, d2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _placeholders(reports: List[BaseDescription]) -> None:
|
|
86
|
+
"""Generates placeholders in the dataset descriptions where needed"""
|
|
87
|
+
|
|
88
|
+
keys = {v for r in reports for v in r.scatter}
|
|
89
|
+
type_keys = {v for r in reports for v in r.table["types"]}
|
|
90
|
+
for report in reports:
|
|
91
|
+
# Interactions
|
|
92
|
+
for k1 in keys:
|
|
93
|
+
for k2 in keys:
|
|
94
|
+
if k1 not in report.scatter:
|
|
95
|
+
report.scatter[k1] = {}
|
|
96
|
+
if k2 not in report.scatter[k1]:
|
|
97
|
+
report.scatter[k1][k2] = ""
|
|
98
|
+
|
|
99
|
+
# Types
|
|
100
|
+
for type_key in type_keys:
|
|
101
|
+
if type_key not in report.table["types"]:
|
|
102
|
+
report.table["types"][type_key] = 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _update_titles(reports: List[ProfileReport]) -> None:
|
|
106
|
+
"""Redefine title of reports with the default one."""
|
|
107
|
+
for idx, report in enumerate(reports):
|
|
108
|
+
if report.config.title == "YData Profiling Report":
|
|
109
|
+
report.config.title = f"Dataset {chr(65 + idx)}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _compare_title(titles: List[str]) -> str:
|
|
113
|
+
if all(titles[0] == title for title in titles[1:]):
|
|
114
|
+
return titles[0]
|
|
115
|
+
else:
|
|
116
|
+
title = ", ".join(titles[:-1])
|
|
117
|
+
return f"<em>Comparing</em> {title} <em>and</em> {titles[-1]}"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _compare_profile_report_preprocess(
|
|
121
|
+
reports: List[ProfileReport],
|
|
122
|
+
config: Optional[Settings] = None,
|
|
123
|
+
) -> Tuple[List[str], List[BaseDescription]]:
|
|
124
|
+
# Use titles as labels
|
|
125
|
+
labels = [report.config.title for report in reports]
|
|
126
|
+
|
|
127
|
+
# Use color per report if not custom set
|
|
128
|
+
if config is None:
|
|
129
|
+
if len(reports[0].config.html.style.primary_colors) > 1:
|
|
130
|
+
for idx, report in enumerate(reports):
|
|
131
|
+
report.config.html.style.primary_colors = [
|
|
132
|
+
report.config.html.style.primary_colors[idx]
|
|
133
|
+
]
|
|
134
|
+
else:
|
|
135
|
+
if len(config.html.style.primary_colors) > 1:
|
|
136
|
+
for idx, report in enumerate(reports):
|
|
137
|
+
report.config.html.style.primary_colors = (
|
|
138
|
+
config.html.style.primary_colors
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Obtain description sets
|
|
142
|
+
descriptions = [report.get_description() for report in reports]
|
|
143
|
+
for label, description in zip(labels, descriptions):
|
|
144
|
+
description.analysis.title = label
|
|
145
|
+
|
|
146
|
+
return labels, descriptions
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _compare_dataset_description_preprocess(
|
|
150
|
+
reports: List[BaseDescription],
|
|
151
|
+
) -> Tuple[List[str], List[BaseDescription]]:
|
|
152
|
+
labels = [report.analysis.title for report in reports]
|
|
153
|
+
return labels, reports
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def validate_reports(
|
|
157
|
+
reports: Union[List[ProfileReport], List[BaseDescription]], configs: List[dict]
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Validate if the reports are comparable.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
reports: two reports to compare
|
|
163
|
+
input may either be a ProfileReport, or the summary obtained from report.get_description()
|
|
164
|
+
"""
|
|
165
|
+
if len(reports) < 2:
|
|
166
|
+
raise ValueError("At least two reports are required for this comparison")
|
|
167
|
+
|
|
168
|
+
if len(reports) > 2:
|
|
169
|
+
warnings.warn(
|
|
170
|
+
"Comparison of more than two reports is not supported. "
|
|
171
|
+
"Reports may be produced, but may yield unexpected formatting."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
report_types = [c.vars.timeseries.active for c in configs] # type: ignore
|
|
175
|
+
if all(report_types) != any(report_types):
|
|
176
|
+
raise ValueError(
|
|
177
|
+
"Comparison between timeseries and tabular reports is not supported."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if isinstance(reports[0], ProfileReport):
|
|
181
|
+
is_df_available = [r.df is not None for r in reports] # type: ignore
|
|
182
|
+
if not all(is_df_available):
|
|
183
|
+
raise ValueError("Reports where not initialized with a DataFrame.")
|
|
184
|
+
|
|
185
|
+
if isinstance(reports[0], ProfileReport):
|
|
186
|
+
features = [set(r.df.columns) for r in reports] # type: ignore
|
|
187
|
+
else:
|
|
188
|
+
features = [set(r.variables.keys()) for r in reports] # type: ignore
|
|
189
|
+
|
|
190
|
+
if not all(features[0] == x for x in features):
|
|
191
|
+
warnings.warn(
|
|
192
|
+
"The datasets being profiled have a different set of columns. "
|
|
193
|
+
"Only the left side profile will be calculated."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _apply_config(description: BaseDescription, config: Settings) -> BaseDescription:
|
|
198
|
+
"""Apply the configuration for visualilzation purposes.
|
|
199
|
+
|
|
200
|
+
This handles the cases in which the report description
|
|
201
|
+
was computed prior to comparison with a different config
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
description: report summary
|
|
205
|
+
config: the settings object for the ProfileReport
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
the updated description
|
|
209
|
+
"""
|
|
210
|
+
description.missing = {
|
|
211
|
+
k: v for k, v in description.missing.items() if config.missing_diagrams[k]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
description.correlations = {
|
|
215
|
+
k: v
|
|
216
|
+
for k, v in description.correlations.items()
|
|
217
|
+
if config.correlations.get(k, Correlation(calculate=False).calculate)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
samples = [config.samples.head, config.samples.tail, config.samples.random]
|
|
221
|
+
samples = [s > 0 for s in samples]
|
|
222
|
+
description.sample = description.sample if any(samples) else []
|
|
223
|
+
description.duplicates = (
|
|
224
|
+
description.duplicates if config.duplicates.head > 0 else None
|
|
225
|
+
)
|
|
226
|
+
description.scatter = description.scatter if config.interactions.continuous else {}
|
|
227
|
+
|
|
228
|
+
return description
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _is_alert_present(alert: Alert, alert_list: list) -> bool:
|
|
232
|
+
return any(
|
|
233
|
+
a.column_name == alert.column_name and a.alert_type == alert.alert_type
|
|
234
|
+
for a in alert_list
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _create_placehoder_alerts(report_alerts: tuple) -> tuple:
|
|
239
|
+
from copy import copy
|
|
240
|
+
|
|
241
|
+
fixed: list = [[] for _ in report_alerts]
|
|
242
|
+
for idx, alerts in enumerate(report_alerts):
|
|
243
|
+
for alert in alerts:
|
|
244
|
+
fixed[idx].append(alert)
|
|
245
|
+
for i, fix in enumerate(fixed):
|
|
246
|
+
if i == idx:
|
|
247
|
+
continue
|
|
248
|
+
if not _is_alert_present(alert, report_alerts[i]):
|
|
249
|
+
empty_alert = copy(alert)
|
|
250
|
+
empty_alert._is_empty = True
|
|
251
|
+
fix.append(empty_alert)
|
|
252
|
+
return tuple(fixed)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def compare(
|
|
256
|
+
reports: Union[List[ProfileReport], List[BaseDescription]],
|
|
257
|
+
config: Optional[Settings] = None,
|
|
258
|
+
compute: bool = False,
|
|
259
|
+
) -> ProfileReport:
|
|
260
|
+
"""
|
|
261
|
+
Compare Profile reports
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
reports: two reports to compare
|
|
265
|
+
input may either be a ProfileReport, or the summary obtained from report.get_description()
|
|
266
|
+
config: the settings object for the merged ProfileReport
|
|
267
|
+
compute: recompute the profile report using config or the left report config
|
|
268
|
+
recommended in cases where the reports were created using different settings
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
if len(reports) == 0:
|
|
272
|
+
raise ValueError("No reports available for comparison.")
|
|
273
|
+
|
|
274
|
+
report_dtypes = [type(r) for r in reports]
|
|
275
|
+
if len(set(report_dtypes)) > 1:
|
|
276
|
+
raise TypeError(
|
|
277
|
+
"The input must have the same data type for all reports. Comparing ProfileReport objects to summaries obtained from the get_description() method is not supported."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if isinstance(reports[0], ProfileReport):
|
|
281
|
+
all_configs = [r.config for r in reports] # type: ignore
|
|
282
|
+
else:
|
|
283
|
+
configs_str = [
|
|
284
|
+
json.loads(r.package["data_profiling_config"]) for r in reports # type: ignore
|
|
285
|
+
]
|
|
286
|
+
all_configs = []
|
|
287
|
+
for c_str in configs_str:
|
|
288
|
+
c_setting = Settings()
|
|
289
|
+
c_setting = c_setting.update(c_str)
|
|
290
|
+
all_configs.append(c_setting)
|
|
291
|
+
|
|
292
|
+
validate_reports(reports=reports, configs=all_configs)
|
|
293
|
+
|
|
294
|
+
if isinstance(reports[0], ProfileReport):
|
|
295
|
+
base_features = reports[0].df.columns # type: ignore
|
|
296
|
+
for report in reports[1:]:
|
|
297
|
+
cols_2_compare = [col for col in base_features if col in report.df.columns] # type: ignore
|
|
298
|
+
report.df = report.df.loc[:, cols_2_compare] # type: ignore
|
|
299
|
+
reports = [r for r in reports if not r.df.empty] # type: ignore
|
|
300
|
+
if len(reports) == 1:
|
|
301
|
+
return reports[0] # type: ignore
|
|
302
|
+
else:
|
|
303
|
+
base_features = list(reports[0].variables.keys())
|
|
304
|
+
non_empty_reports = 0
|
|
305
|
+
for report in reports[1:]:
|
|
306
|
+
cols_2_compare = [
|
|
307
|
+
col for col in base_features if col in list(report.variables.keys()) # type: ignore
|
|
308
|
+
]
|
|
309
|
+
if len(cols_2_compare) > 0:
|
|
310
|
+
non_empty_reports += 1
|
|
311
|
+
if non_empty_reports == 0:
|
|
312
|
+
profile = ProfileReport(None, config=all_configs[0])
|
|
313
|
+
profile._description_set = reports[0]
|
|
314
|
+
return profile
|
|
315
|
+
|
|
316
|
+
_config = None
|
|
317
|
+
if config is None:
|
|
318
|
+
_config = all_configs[0].copy()
|
|
319
|
+
else:
|
|
320
|
+
_config = config.copy()
|
|
321
|
+
if isinstance(reports[0], ProfileReport):
|
|
322
|
+
for report in reports:
|
|
323
|
+
tsmode = report.config.vars.timeseries.active # type: ignore
|
|
324
|
+
title = report.config.title # type: ignore
|
|
325
|
+
report.config = config.copy() # type: ignore
|
|
326
|
+
report.config.title = title # type: ignore
|
|
327
|
+
report.config.vars.timeseries.active = tsmode # type: ignore
|
|
328
|
+
if compute:
|
|
329
|
+
report._description_set = None # type: ignore
|
|
330
|
+
|
|
331
|
+
if all(isinstance(report, ProfileReport) for report in reports):
|
|
332
|
+
# Type ignore is needed as mypy does not pick up on the type narrowing
|
|
333
|
+
# Consider using TypeGuard (3.10): https://docs.python.org/3/library/typing.html#typing.TypeGuard
|
|
334
|
+
_update_titles(reports) # type: ignore
|
|
335
|
+
labels, descriptions = _compare_profile_report_preprocess(reports, _config) # type: ignore
|
|
336
|
+
elif all(isinstance(report, BaseDescription) for report in reports):
|
|
337
|
+
labels, descriptions = _compare_dataset_description_preprocess(reports) # type: ignore
|
|
338
|
+
else:
|
|
339
|
+
raise TypeError(
|
|
340
|
+
"The input must have the same data type for all reports. Comparing ProfileReport objects to summaries obtained from the get_description() method is not supported."
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
_config.html.style._labels = labels
|
|
344
|
+
|
|
345
|
+
_placeholders(descriptions)
|
|
346
|
+
|
|
347
|
+
descriptions_dict = [asdict(_apply_config(d, _config)) for d in descriptions]
|
|
348
|
+
|
|
349
|
+
res: dict = _update_merge(None, descriptions_dict[0])
|
|
350
|
+
for r in descriptions_dict[1:]:
|
|
351
|
+
res = _update_merge(res, r)
|
|
352
|
+
|
|
353
|
+
res["analysis"]["title"] = _compare_title(res["analysis"]["title"])
|
|
354
|
+
res["alerts"] = _create_placehoder_alerts(res["alerts"])
|
|
355
|
+
if not any(res["time_index_analysis"]):
|
|
356
|
+
res["time_index_analysis"] = None
|
|
357
|
+
profile = ProfileReport(None, config=_config)
|
|
358
|
+
profile._description_set = from_dict(data_class=BaseDescription, data=res)
|
|
359
|
+
return profile
|