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.
Files changed (238) hide show
  1. data_profiling/__init__.py +34 -0
  2. data_profiling/compare_reports.py +359 -0
  3. data_profiling/config.py +496 -0
  4. data_profiling/config_default.yaml +223 -0
  5. data_profiling/config_minimal.yaml +222 -0
  6. data_profiling/controller/__init__.py +1 -0
  7. data_profiling/controller/console.py +125 -0
  8. data_profiling/controller/pandas_decorator.py +21 -0
  9. data_profiling/expectations_report.py +117 -0
  10. data_profiling/model/__init__.py +4 -0
  11. data_profiling/model/alerts.py +780 -0
  12. data_profiling/model/correlations.py +163 -0
  13. data_profiling/model/dataframe.py +35 -0
  14. data_profiling/model/describe.py +210 -0
  15. data_profiling/model/description.py +108 -0
  16. data_profiling/model/duplicates.py +14 -0
  17. data_profiling/model/expectation_algorithms.py +112 -0
  18. data_profiling/model/handler.py +81 -0
  19. data_profiling/model/missing.py +146 -0
  20. data_profiling/model/pairwise.py +33 -0
  21. data_profiling/model/pandas/__init__.py +55 -0
  22. data_profiling/model/pandas/correlations_pandas.py +207 -0
  23. data_profiling/model/pandas/dataframe_pandas.py +26 -0
  24. data_profiling/model/pandas/describe_boolean_pandas.py +43 -0
  25. data_profiling/model/pandas/describe_categorical_pandas.py +274 -0
  26. data_profiling/model/pandas/describe_counts_pandas.py +63 -0
  27. data_profiling/model/pandas/describe_date_pandas.py +77 -0
  28. data_profiling/model/pandas/describe_file_pandas.py +56 -0
  29. data_profiling/model/pandas/describe_generic_pandas.py +36 -0
  30. data_profiling/model/pandas/describe_image_pandas.py +255 -0
  31. data_profiling/model/pandas/describe_numeric_pandas.py +175 -0
  32. data_profiling/model/pandas/describe_path_pandas.py +63 -0
  33. data_profiling/model/pandas/describe_supported_pandas.py +41 -0
  34. data_profiling/model/pandas/describe_text_pandas.py +62 -0
  35. data_profiling/model/pandas/describe_timeseries_pandas.py +222 -0
  36. data_profiling/model/pandas/describe_url_pandas.py +57 -0
  37. data_profiling/model/pandas/discretize_pandas.py +81 -0
  38. data_profiling/model/pandas/duplicates_pandas.py +56 -0
  39. data_profiling/model/pandas/imbalance_pandas.py +35 -0
  40. data_profiling/model/pandas/missing_pandas.py +42 -0
  41. data_profiling/model/pandas/sample_pandas.py +38 -0
  42. data_profiling/model/pandas/summary_pandas.py +101 -0
  43. data_profiling/model/pandas/table_pandas.py +56 -0
  44. data_profiling/model/pandas/timeseries_index_pandas.py +33 -0
  45. data_profiling/model/pandas/utils_pandas.py +27 -0
  46. data_profiling/model/sample.py +37 -0
  47. data_profiling/model/spark/__init__.py +48 -0
  48. data_profiling/model/spark/correlations_spark.py +152 -0
  49. data_profiling/model/spark/dataframe_spark.py +34 -0
  50. data_profiling/model/spark/describe_boolean_spark.py +27 -0
  51. data_profiling/model/spark/describe_categorical_spark.py +28 -0
  52. data_profiling/model/spark/describe_counts_spark.py +105 -0
  53. data_profiling/model/spark/describe_date_spark.py +51 -0
  54. data_profiling/model/spark/describe_generic_spark.py +30 -0
  55. data_profiling/model/spark/describe_numeric_spark.py +155 -0
  56. data_profiling/model/spark/describe_supported_spark.py +33 -0
  57. data_profiling/model/spark/describe_text_spark.py +25 -0
  58. data_profiling/model/spark/duplicates_spark.py +54 -0
  59. data_profiling/model/spark/missing_spark.py +96 -0
  60. data_profiling/model/spark/sample_spark.py +43 -0
  61. data_profiling/model/spark/summary_spark.py +95 -0
  62. data_profiling/model/spark/table_spark.py +58 -0
  63. data_profiling/model/spark/timeseries_index_spark.py +12 -0
  64. data_profiling/model/summarizer.py +207 -0
  65. data_profiling/model/summary.py +66 -0
  66. data_profiling/model/summary_algorithms.py +276 -0
  67. data_profiling/model/table.py +10 -0
  68. data_profiling/model/timeseries_index.py +16 -0
  69. data_profiling/model/typeset.py +365 -0
  70. data_profiling/model/typeset_relations.py +143 -0
  71. data_profiling/profile_report.py +576 -0
  72. data_profiling/report/__init__.py +4 -0
  73. data_profiling/report/formatters.py +346 -0
  74. data_profiling/report/presentation/__init__.py +1 -0
  75. data_profiling/report/presentation/core/__init__.py +39 -0
  76. data_profiling/report/presentation/core/alerts.py +18 -0
  77. data_profiling/report/presentation/core/collapse.py +24 -0
  78. data_profiling/report/presentation/core/container.py +50 -0
  79. data_profiling/report/presentation/core/correlation_table.py +21 -0
  80. data_profiling/report/presentation/core/dropdown.py +44 -0
  81. data_profiling/report/presentation/core/duplicate.py +16 -0
  82. data_profiling/report/presentation/core/frequency_table.py +14 -0
  83. data_profiling/report/presentation/core/frequency_table_small.py +16 -0
  84. data_profiling/report/presentation/core/html.py +14 -0
  85. data_profiling/report/presentation/core/image.py +34 -0
  86. data_profiling/report/presentation/core/item_renderer.py +17 -0
  87. data_profiling/report/presentation/core/renderable.py +42 -0
  88. data_profiling/report/presentation/core/root.py +35 -0
  89. data_profiling/report/presentation/core/sample.py +20 -0
  90. data_profiling/report/presentation/core/scores.py +32 -0
  91. data_profiling/report/presentation/core/table.py +26 -0
  92. data_profiling/report/presentation/core/toggle_button.py +14 -0
  93. data_profiling/report/presentation/core/variable.py +40 -0
  94. data_profiling/report/presentation/core/variable_info.py +36 -0
  95. data_profiling/report/presentation/flavours/__init__.py +9 -0
  96. data_profiling/report/presentation/flavours/flavour_html.py +64 -0
  97. data_profiling/report/presentation/flavours/flavour_widget.py +61 -0
  98. data_profiling/report/presentation/flavours/flavours.py +43 -0
  99. data_profiling/report/presentation/flavours/html/__init__.py +47 -0
  100. data_profiling/report/presentation/flavours/html/alerts.py +10 -0
  101. data_profiling/report/presentation/flavours/html/collapse.py +7 -0
  102. data_profiling/report/presentation/flavours/html/container.py +58 -0
  103. data_profiling/report/presentation/flavours/html/correlation_table.py +13 -0
  104. data_profiling/report/presentation/flavours/html/dropdown.py +7 -0
  105. data_profiling/report/presentation/flavours/html/duplicate.py +24 -0
  106. data_profiling/report/presentation/flavours/html/frequency_table.py +20 -0
  107. data_profiling/report/presentation/flavours/html/frequency_table_small.py +15 -0
  108. data_profiling/report/presentation/flavours/html/html.py +6 -0
  109. data_profiling/report/presentation/flavours/html/image.py +7 -0
  110. data_profiling/report/presentation/flavours/html/root.py +14 -0
  111. data_profiling/report/presentation/flavours/html/sample.py +12 -0
  112. data_profiling/report/presentation/flavours/html/scores.py +11 -0
  113. data_profiling/report/presentation/flavours/html/table.py +7 -0
  114. data_profiling/report/presentation/flavours/html/templates/alerts/alert_constant.html +1 -0
  115. data_profiling/report/presentation/flavours/html/templates/alerts/alert_constant_length.html +1 -0
  116. data_profiling/report/presentation/flavours/html/templates/alerts/alert_dirty_category.html +1 -0
  117. data_profiling/report/presentation/flavours/html/templates/alerts/alert_duplicates.html +1 -0
  118. data_profiling/report/presentation/flavours/html/templates/alerts/alert_empty.html +1 -0
  119. data_profiling/report/presentation/flavours/html/templates/alerts/alert_high_cardinality.html +1 -0
  120. data_profiling/report/presentation/flavours/html/templates/alerts/alert_high_correlation.html +4 -0
  121. data_profiling/report/presentation/flavours/html/templates/alerts/alert_imbalance.html +1 -0
  122. data_profiling/report/presentation/flavours/html/templates/alerts/alert_infinite.html +1 -0
  123. data_profiling/report/presentation/flavours/html/templates/alerts/alert_missing.html +1 -0
  124. data_profiling/report/presentation/flavours/html/templates/alerts/alert_near_duplicates.html +1 -0
  125. data_profiling/report/presentation/flavours/html/templates/alerts/alert_non_stationary.html +1 -0
  126. data_profiling/report/presentation/flavours/html/templates/alerts/alert_seasonal.html +1 -0
  127. data_profiling/report/presentation/flavours/html/templates/alerts/alert_skewed.html +1 -0
  128. data_profiling/report/presentation/flavours/html/templates/alerts/alert_truncated.html +1 -0
  129. data_profiling/report/presentation/flavours/html/templates/alerts/alert_type_date.html +1 -0
  130. data_profiling/report/presentation/flavours/html/templates/alerts/alert_uniform.html +1 -0
  131. data_profiling/report/presentation/flavours/html/templates/alerts/alert_unique.html +1 -0
  132. data_profiling/report/presentation/flavours/html/templates/alerts/alert_unsupported.html +1 -0
  133. data_profiling/report/presentation/flavours/html/templates/alerts/alert_zeros.html +1 -0
  134. data_profiling/report/presentation/flavours/html/templates/alerts.html +47 -0
  135. data_profiling/report/presentation/flavours/html/templates/collapse.html +11 -0
  136. data_profiling/report/presentation/flavours/html/templates/correlation_table.html +5 -0
  137. data_profiling/report/presentation/flavours/html/templates/diagram.html +11 -0
  138. data_profiling/report/presentation/flavours/html/templates/dropdown.html +16 -0
  139. data_profiling/report/presentation/flavours/html/templates/duplicate.html +5 -0
  140. data_profiling/report/presentation/flavours/html/templates/frequency_table.html +45 -0
  141. data_profiling/report/presentation/flavours/html/templates/frequency_table_small.html +34 -0
  142. data_profiling/report/presentation/flavours/html/templates/report.html +26 -0
  143. data_profiling/report/presentation/flavours/html/templates/sample.html +10 -0
  144. data_profiling/report/presentation/flavours/html/templates/scores.html +78 -0
  145. data_profiling/report/presentation/flavours/html/templates/sequence/batch_grid.html +16 -0
  146. data_profiling/report/presentation/flavours/html/templates/sequence/grid.html +18 -0
  147. data_profiling/report/presentation/flavours/html/templates/sequence/list.html +7 -0
  148. data_profiling/report/presentation/flavours/html/templates/sequence/named_list.html +8 -0
  149. data_profiling/report/presentation/flavours/html/templates/sequence/overview_tabs.html +30 -0
  150. data_profiling/report/presentation/flavours/html/templates/sequence/scores.html +3 -0
  151. data_profiling/report/presentation/flavours/html/templates/sequence/sections.html +13 -0
  152. data_profiling/report/presentation/flavours/html/templates/sequence/select.html +40 -0
  153. data_profiling/report/presentation/flavours/html/templates/sequence/tabs.html +30 -0
  154. data_profiling/report/presentation/flavours/html/templates/table.html +38 -0
  155. data_profiling/report/presentation/flavours/html/templates/toggle_button.html +18 -0
  156. data_profiling/report/presentation/flavours/html/templates/variable.html +7 -0
  157. data_profiling/report/presentation/flavours/html/templates/variable_info.html +49 -0
  158. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/bootstrap.bundle.min.js +7 -0
  159. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/bootstrap.min.css +6 -0
  160. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/cosmo.bootstrap.min.css +12 -0
  161. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/flatly.bootstrap.min.css +12 -0
  162. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/script.js +52 -0
  163. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/simplex.bootstrap.min.css +12 -0
  164. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/style.css +253 -0
  165. data_profiling/report/presentation/flavours/html/templates/wrapper/assets/united.bootstrap.min.css +12 -0
  166. data_profiling/report/presentation/flavours/html/templates/wrapper/footer.html +7 -0
  167. data_profiling/report/presentation/flavours/html/templates/wrapper/javascript.html +18 -0
  168. data_profiling/report/presentation/flavours/html/templates/wrapper/navigation.html +36 -0
  169. data_profiling/report/presentation/flavours/html/templates/wrapper/style.html +53 -0
  170. data_profiling/report/presentation/flavours/html/templates.py +76 -0
  171. data_profiling/report/presentation/flavours/html/toggle_button.py +7 -0
  172. data_profiling/report/presentation/flavours/html/variable.py +7 -0
  173. data_profiling/report/presentation/flavours/html/variable_info.py +7 -0
  174. data_profiling/report/presentation/flavours/widget/__init__.py +49 -0
  175. data_profiling/report/presentation/flavours/widget/alerts.py +45 -0
  176. data_profiling/report/presentation/flavours/widget/collapse.py +43 -0
  177. data_profiling/report/presentation/flavours/widget/container.py +121 -0
  178. data_profiling/report/presentation/flavours/widget/correlation_table.py +14 -0
  179. data_profiling/report/presentation/flavours/widget/dropdown.py +31 -0
  180. data_profiling/report/presentation/flavours/widget/duplicate.py +14 -0
  181. data_profiling/report/presentation/flavours/widget/frequency_table.py +57 -0
  182. data_profiling/report/presentation/flavours/widget/frequency_table_small.py +66 -0
  183. data_profiling/report/presentation/flavours/widget/html.py +11 -0
  184. data_profiling/report/presentation/flavours/widget/image.py +26 -0
  185. data_profiling/report/presentation/flavours/widget/notebook.py +81 -0
  186. data_profiling/report/presentation/flavours/widget/root.py +10 -0
  187. data_profiling/report/presentation/flavours/widget/sample.py +14 -0
  188. data_profiling/report/presentation/flavours/widget/table.py +30 -0
  189. data_profiling/report/presentation/flavours/widget/toggle_button.py +17 -0
  190. data_profiling/report/presentation/flavours/widget/variable.py +12 -0
  191. data_profiling/report/presentation/flavours/widget/variable_info.py +11 -0
  192. data_profiling/report/presentation/frequency_table_utils.py +141 -0
  193. data_profiling/report/structure/__init__.py +1 -0
  194. data_profiling/report/structure/correlations.py +123 -0
  195. data_profiling/report/structure/overview.py +376 -0
  196. data_profiling/report/structure/report.py +457 -0
  197. data_profiling/report/structure/variables/__init__.py +35 -0
  198. data_profiling/report/structure/variables/render_boolean.py +132 -0
  199. data_profiling/report/structure/variables/render_categorical.py +566 -0
  200. data_profiling/report/structure/variables/render_common.py +31 -0
  201. data_profiling/report/structure/variables/render_complex.py +102 -0
  202. data_profiling/report/structure/variables/render_count.py +172 -0
  203. data_profiling/report/structure/variables/render_date.py +143 -0
  204. data_profiling/report/structure/variables/render_file.py +70 -0
  205. data_profiling/report/structure/variables/render_generic.py +45 -0
  206. data_profiling/report/structure/variables/render_image.py +204 -0
  207. data_profiling/report/structure/variables/render_path.py +134 -0
  208. data_profiling/report/structure/variables/render_real.py +314 -0
  209. data_profiling/report/structure/variables/render_text.py +189 -0
  210. data_profiling/report/structure/variables/render_timeseries.py +371 -0
  211. data_profiling/report/structure/variables/render_url.py +132 -0
  212. data_profiling/report/utils.py +34 -0
  213. data_profiling/serialize_report.py +143 -0
  214. data_profiling/utils/__init__.py +1 -0
  215. data_profiling/utils/backend.py +9 -0
  216. data_profiling/utils/cache.py +59 -0
  217. data_profiling/utils/common.py +142 -0
  218. data_profiling/utils/compat.py +31 -0
  219. data_profiling/utils/dataframe.py +238 -0
  220. data_profiling/utils/logger.py +53 -0
  221. data_profiling/utils/notebook.py +8 -0
  222. data_profiling/utils/paths.py +45 -0
  223. data_profiling/utils/progress_bar.py +15 -0
  224. data_profiling/utils/styles.py +22 -0
  225. data_profiling/utils/versions.py +19 -0
  226. data_profiling/version.py +1 -0
  227. data_profiling/visualisation/__init__.py +1 -0
  228. data_profiling/visualisation/context.py +87 -0
  229. data_profiling/visualisation/missing.py +138 -0
  230. data_profiling/visualisation/plot.py +1158 -0
  231. data_profiling/visualisation/utils.py +113 -0
  232. fg_data_profiling_wasm-0.1.0.dist-info/METADATA +195 -0
  233. fg_data_profiling_wasm-0.1.0.dist-info/RECORD +238 -0
  234. fg_data_profiling_wasm-0.1.0.dist-info/WHEEL +6 -0
  235. fg_data_profiling_wasm-0.1.0.dist-info/entry_points.txt +3 -0
  236. fg_data_profiling_wasm-0.1.0.dist-info/licenses/LICENSE +21 -0
  237. fg_data_profiling_wasm-0.1.0.dist-info/top_level.txt +2 -0
  238. 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