fg-data-profiling 4.19.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 +573 -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-4.19.0.dist-info/METADATA +362 -0
  233. fg_data_profiling-4.19.0.dist-info/RECORD +238 -0
  234. fg_data_profiling-4.19.0.dist-info/WHEEL +6 -0
  235. fg_data_profiling-4.19.0.dist-info/entry_points.txt +3 -0
  236. fg_data_profiling-4.19.0.dist-info/licenses/LICENSE +21 -0
  237. fg_data_profiling-4.19.0.dist-info/top_level.txt +2 -0
  238. ydata_profiling/__init__.py +43 -0
@@ -0,0 +1,59 @@
1
+ """Dataset cache utility functions"""
2
+ import zipfile
3
+ from pathlib import Path
4
+
5
+ from requests import get as get_file
6
+
7
+ from data_profiling.utils.paths import get_data_path
8
+
9
+
10
+ def cache_file(file_name: str, url: str) -> Path:
11
+ """Check if file_name already is in the data path, otherwise download it from url.
12
+
13
+ Args:
14
+ file_name: the file name
15
+ url: the URL of the dataset
16
+
17
+ Returns:
18
+ The relative path to the dataset
19
+ """
20
+
21
+ data_path = get_data_path()
22
+ data_path.mkdir(exist_ok=True)
23
+
24
+ file_path = data_path / file_name
25
+
26
+ # If not exists, download and create file
27
+ if not file_path.exists():
28
+ response = get_file(url, allow_redirects=True)
29
+ response.raise_for_status()
30
+
31
+ file_path.write_bytes(response.content)
32
+
33
+ return file_path
34
+
35
+
36
+ def cache_zipped_file(file_name: str, url: str) -> Path:
37
+ """Check if file_name already is in the data path, otherwise download it from url.
38
+
39
+ Args:
40
+ file_name: the file name
41
+ url: the URL of the dataset
42
+
43
+ Returns:
44
+ The relative path to the dataset
45
+ """
46
+
47
+ data_path = get_data_path()
48
+ file_path = data_path / file_name
49
+
50
+ # If not exists, download and create file
51
+ if not file_path.exists():
52
+ tmp_path = cache_file("tmp.zip", url)
53
+
54
+ with zipfile.ZipFile(tmp_path, "r") as zip_file:
55
+ zip_file.extract(file_path.name, data_path)
56
+
57
+ tmp_path.unlink()
58
+
59
+ return file_path
@@ -0,0 +1,142 @@
1
+ """Common util functions (e.g. missing in Python)."""
2
+
3
+ import collections.abc
4
+ import contextlib
5
+ import os
6
+ import platform
7
+ import subprocess
8
+ import zipfile
9
+ from datetime import datetime, timedelta
10
+
11
+ # Image type detection
12
+ from pathlib import Path
13
+ from typing import Mapping
14
+
15
+ import pandas as pd
16
+ import requests
17
+
18
+ from data_profiling.version import __version__
19
+
20
+
21
+ def update(d: dict, u: Mapping) -> dict:
22
+ """Recursively update a dict.
23
+
24
+ Args:
25
+ d: Dictionary to update.
26
+ u: Dictionary with values to use.
27
+
28
+ Returns:
29
+ The merged dictionary.
30
+ """
31
+ for k, v in u.items():
32
+ if isinstance(v, collections.abc.Mapping):
33
+ d[k] = update(d.get(k, {}), v)
34
+ else:
35
+ d[k] = v
36
+ return d
37
+
38
+
39
+ def _copy(self, target):
40
+ """Monkeypatch for pathlib
41
+
42
+ Args:
43
+ self:
44
+ target:
45
+
46
+ Returns:
47
+
48
+ """
49
+ import shutil
50
+
51
+ assert self.is_file()
52
+ shutil.copy(str(self), target)
53
+
54
+
55
+ Path.copy = _copy # type: ignore
56
+
57
+
58
+ def extract_zip(outfile, effective_path):
59
+ try:
60
+ with zipfile.ZipFile(outfile) as z:
61
+ z.extractall(effective_path)
62
+ except zipfile.BadZipFile as e:
63
+ raise ValueError("Bad zip file") from e
64
+
65
+
66
+ def convert_timestamp_to_datetime(timestamp: int) -> datetime:
67
+ if timestamp >= 0:
68
+ return datetime.fromtimestamp(timestamp)
69
+ else:
70
+ return datetime(1970, 1, 1) + timedelta(seconds=int(timestamp))
71
+
72
+
73
+ def analytics_features(
74
+ dataframe: str, datatype: str, report_type: str, ncols: int, nrows: int, dbx: str
75
+ ) -> None:
76
+ endpoint = "https://packages.ydata.ai/data-profiling?"
77
+ package_version = __version__
78
+
79
+ if (
80
+ bool(os.getenv("YDATA_PROFILING_NO_ANALYTICS")) is not True
81
+ and package_version != "0.0.dev0"
82
+ ):
83
+ try:
84
+ subprocess.check_output("nvidia-smi")
85
+ gpu_present = True
86
+ except Exception:
87
+ gpu_present = False
88
+
89
+ python_version = ".".join(platform.python_version().split(".")[:2])
90
+
91
+ with contextlib.suppress(Exception):
92
+ request_message = (
93
+ f"{endpoint}version={package_version}"
94
+ f"&python_version={python_version}"
95
+ f"&report_type={report_type}"
96
+ f"&dataframe={dataframe}"
97
+ f"&ncols={ncols}"
98
+ f"&nrows={nrows}"
99
+ f"&datatype={datatype}"
100
+ f"&os={platform.system()}"
101
+ f"&gpu={str(gpu_present)}"
102
+ f"&dbx={dbx}"
103
+ )
104
+
105
+ requests.get(request_message)
106
+
107
+
108
+ def is_running_in_databricks():
109
+ mask = "DATABRICKS_RUNTIME_VERSION" in os.environ
110
+ if "DATABRICKS_RUNTIME_VERSION" in os.environ:
111
+ return os.environ["DATABRICKS_RUNTIME_VERSION"]
112
+ else:
113
+ return str(mask)
114
+
115
+
116
+ def calculate_nrows(df):
117
+ """
118
+ Calculates the approx. number of rows spark dataframes
119
+
120
+ Returns: int, approximate number of rows
121
+ """
122
+ if isinstance(df, pd.DataFrame):
123
+ if df is not None:
124
+ nrows = len(df)
125
+ else:
126
+ nrows = 0
127
+ else:
128
+ try:
129
+ n_partitions = df.rdd.getNumPartitions()
130
+
131
+ nrows = (
132
+ df.rdd.mapPartitionsWithIndex(
133
+ lambda idx, partition: [sum(1 for _ in partition)]
134
+ if idx == 0
135
+ else [0]
136
+ ).collect()[0]
137
+ * n_partitions
138
+ )
139
+ except Exception:
140
+ nrows = 0
141
+
142
+ return nrows
@@ -0,0 +1,31 @@
1
+ """Utility functions for (version) compatibility"""
2
+
3
+ from contextlib import contextmanager
4
+ from functools import lru_cache
5
+ from typing import Generator, Tuple
6
+
7
+ import pandas as pd
8
+
9
+
10
+ @lru_cache(maxsize=1)
11
+ def pandas_version_info() -> Tuple[int, ...]:
12
+ """
13
+ Get the Pandas version as a tuple of integers,
14
+ akin to `sys.version_info` for the Python version.
15
+ """
16
+ return tuple(int(s) for s in pd.__version__.split("."))
17
+
18
+
19
+ @contextmanager
20
+ def optional_option_context(
21
+ option_key: str, value: object
22
+ ) -> Generator[None, None, None]:
23
+ """
24
+ A context manager that sets an option only if it is available in the
25
+ current pandas version; otherwise, it is a no-op.
26
+ """
27
+ try:
28
+ with pd.option_context(option_key, value):
29
+ yield
30
+ except pd.errors.OptionError:
31
+ yield
@@ -0,0 +1,238 @@
1
+ """Utils for pandas DataFrames."""
2
+ import hashlib
3
+ import re
4
+ import unicodedata
5
+ import warnings
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ import pandas as pd
10
+ from pandas.core.util.hashing import hash_pandas_object
11
+
12
+
13
+ def warn_read(extension: str) -> None:
14
+ """Warn the user when an extension is not supported.
15
+
16
+ Args:
17
+ extension: The extension that is not supported.
18
+ """
19
+ warnings.warn(
20
+ f"""There was an attempt to read a file with extension {extension}, we assume it to be in CSV format.
21
+ To prevent this warning from showing up, please rename the file to any of the extensions supported by pandas
22
+ (docs: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html)
23
+ If you think this extension should be supported, please report this as an issue:
24
+ https://github.com/Data-Centric-AI-Community/data-profiling/issues"""
25
+ )
26
+
27
+
28
+ def is_supported_compression(file_extension: str) -> bool:
29
+ """Determine if the given file extension indicates a compression format that pandas can handle automatically.
30
+
31
+ Args:
32
+ file_extension (str): the file extension to test
33
+
34
+ Returns:
35
+ bool: True if the extension indicates a compression format that pandas handles automatically and False otherwise
36
+
37
+ Notes:
38
+ Pandas can handle on the fly decompression from the following extensions: ‘.bz2’, ‘.gz’, ‘.zip’, or ‘.xz’
39
+ (otherwise no decompression). If using ‘.zip’, the ZIP file must contain exactly one data file to be read in.
40
+ """
41
+ return file_extension.lower() in [".bz2", ".gz", ".xz", ".zip"]
42
+
43
+
44
+ def remove_suffix(text: str, suffix: str) -> str:
45
+ """Removes the given suffix from the given string.
46
+
47
+ Args:
48
+ text (str): the string to remove the suffix from
49
+ suffix (str): the suffix to remove from the string
50
+
51
+ Returns:
52
+ str: the string with the suffix removed, if the string ends with the suffix, otherwise the unmodified string
53
+
54
+ Notes:
55
+ In python 3.9+, there is a built-in string method called removesuffix() that can serve this purpose.
56
+ """
57
+ return text[: -len(suffix)] if suffix and text.endswith(suffix) else text
58
+
59
+
60
+ def uncompressed_extension(file_name: Path) -> str:
61
+ """Returns the uncompressed extension of the given file name.
62
+
63
+ Args:
64
+ file_name (Path): the file name to get the uncompressed extension of
65
+
66
+ Returns:
67
+ str: the uncompressed extension, or the original extension if pandas doesn't handle it automatically
68
+ """
69
+ extension = file_name.suffix.lower()
70
+ return (
71
+ Path(remove_suffix(str(file_name).lower(), extension)).suffix
72
+ if is_supported_compression(extension)
73
+ else extension
74
+ )
75
+
76
+
77
+ def read_pandas(file_name: Path) -> pd.DataFrame:
78
+ """Read DataFrame based on the file extension. This function is used when the file is in a standard format.
79
+ Various file types are supported (.csv, .json, .jsonl, .data, .tsv, .xls, .xlsx, .xpt, .sas7bdat, .parquet)
80
+
81
+ Args:
82
+ file_name: the file to read
83
+
84
+ Returns:
85
+ DataFrame
86
+
87
+ Notes:
88
+ This function is based on pandas IO tools:
89
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html
90
+ https://pandas.pydata.org/pandas-docs/stable/reference/io.html
91
+
92
+ This function is not intended to be flexible or complete. The main use case is to be able to read files without
93
+ user input, which is currently used in the editor integration. For more advanced use cases, the user should load
94
+ the DataFrame in code.
95
+ """
96
+ extension = uncompressed_extension(file_name)
97
+ if extension == ".json":
98
+ df = pd.read_json(str(file_name))
99
+ elif extension == ".jsonl":
100
+ df = pd.read_json(str(file_name), lines=True)
101
+ elif extension == ".dta":
102
+ df = pd.read_stata(str(file_name))
103
+ elif extension == ".tsv":
104
+ df = pd.read_csv(str(file_name), sep="\t")
105
+ elif extension in [".xls", ".xlsx"]:
106
+ df = pd.read_excel(str(file_name))
107
+ elif extension in [".hdf", ".h5"]:
108
+ df = pd.read_hdf(str(file_name))
109
+ elif extension in [".sas7bdat", ".xpt"]:
110
+ df = pd.read_sas(str(file_name))
111
+ elif extension == ".parquet":
112
+ df = pd.read_parquet(str(file_name))
113
+ elif extension in [".pkl", ".pickle"]:
114
+ df = pd.read_pickle(str(file_name))
115
+ elif extension == ".tar":
116
+ raise ValueError(
117
+ "tar compression is not supported directly by pandas, please use the 'tarfile' module"
118
+ )
119
+ else:
120
+ if extension != ".csv":
121
+ warn_read(extension)
122
+
123
+ df = pd.read_csv(str(file_name))
124
+ return df
125
+
126
+
127
+ def rename_index(df: pd.DataFrame) -> pd.DataFrame:
128
+ """If the DataFrame contains a column or index named `index`, this will produce errors. We rename the {index,column}
129
+ to be `df_index`.
130
+
131
+ Args:
132
+ df: DataFrame to process.
133
+
134
+ Returns:
135
+ The DataFrame with {index,column} `index` replaced by `df_index`, unchanged if the DataFrame does not contain such a string.
136
+ """
137
+ df.rename(columns={"index": "df_index"}, inplace=True)
138
+
139
+ if "index" in df.index.names:
140
+ df.index.names = [x if x != "index" else "df_index" for x in df.index.names]
141
+ return df
142
+
143
+
144
+ def expand_mixed(df: pd.DataFrame, types: Any = None) -> pd.DataFrame:
145
+ """Expand non-nested lists, dicts and tuples in a DataFrame into columns with a prefix.
146
+
147
+ Args:
148
+ types: list of types to expand (Default: list, dict, tuple)
149
+ df: DataFrame
150
+
151
+ Returns:
152
+ DataFrame with the dict values as series, identifier by the combination of the series and dict keys.
153
+
154
+ Notes:
155
+ TODO: allow for list expansion by duplicating rows.
156
+ TODO: allow for expansion of nested data structures.
157
+ """
158
+ if types is None:
159
+ types = [list, dict, tuple]
160
+
161
+ for column_name in df.columns:
162
+ # All
163
+ non_nested_enumeration = (
164
+ df[column_name]
165
+ .dropna()
166
+ .map(lambda x: type(x) in types and not any(type(y) in types for y in x))
167
+ )
168
+
169
+ if non_nested_enumeration.all():
170
+ # Expand and prefix
171
+ expanded = pd.DataFrame(df[column_name].dropna().tolist())
172
+ expanded = expanded.add_prefix(column_name + "_")
173
+
174
+ # Add recursion
175
+ expanded = expand_mixed(expanded)
176
+
177
+ # Drop the expanded
178
+ df.drop(columns=[column_name], inplace=True)
179
+
180
+ df = pd.concat([df, expanded], axis=1)
181
+ return df
182
+
183
+
184
+ # Change this if `hash_dataframe`'s implementation changes.
185
+ HASH_PREFIX = "2@"
186
+
187
+
188
+ def hash_dataframe(df: pd.DataFrame) -> str:
189
+ """Hash a DataFrame (implementation might change in the future)
190
+
191
+ Args:
192
+ df: the DataFrame
193
+
194
+ Returns:
195
+ The DataFrame's hash
196
+ """
197
+ # hash_pandas_object returns a series of uint64s. Using their
198
+ # binary representation would be more efficient, but it's not
199
+ # necessarily portable across architectures. Using the human-readable
200
+ # string values should be good enough.
201
+ hash_values = "\n".join(hash_pandas_object(df).values.astype(str))
202
+ digest = hashlib.sha256(hash_values.encode("utf-8")).hexdigest()
203
+ return f"{HASH_PREFIX}{digest}"
204
+
205
+
206
+ def slugify(value: str, allow_unicode: bool = False) -> str:
207
+ """
208
+ Taken from https://github.com/django/django/blob/master/django/utils/text.py
209
+ Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
210
+ dashes to single dashes. Remove characters that aren't alphanumerics,
211
+ underscores, or hyphens. Convert to lowercase. Also strip leading and
212
+ trailing whitespace, dashes, and underscores.
213
+ """
214
+ value = str(value)
215
+ if allow_unicode:
216
+ value = unicodedata.normalize("NFKC", value)
217
+ else:
218
+ value = (
219
+ unicodedata.normalize("NFKD", value)
220
+ .encode("ascii", "ignore")
221
+ .decode("ascii")
222
+ )
223
+ value = re.sub(r"[^\w\s-]", "", value.lower())
224
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
225
+
226
+
227
+ def sort_column_names(dct: dict, sort: Optional[str]) -> dict:
228
+ if sort is None:
229
+ return dct
230
+
231
+ sort = sort.lower()
232
+ if sort.startswith("asc"):
233
+ dct = dict(sorted(dct.items(), key=lambda x: x[0].casefold()))
234
+ elif sort.startswith("desc"):
235
+ dct = dict(sorted(dct.items(), key=lambda x: x[0].casefold(), reverse=True))
236
+ else:
237
+ raise ValueError('"sort" should be "ascending", "descending" or None.')
238
+ return dct
@@ -0,0 +1,53 @@
1
+ """
2
+ Logger function for data-profiling reports
3
+ """
4
+
5
+ import logging
6
+
7
+ import pandas as pd
8
+
9
+ from data_profiling.utils.common import (
10
+ analytics_features,
11
+ calculate_nrows,
12
+ is_running_in_databricks,
13
+ )
14
+
15
+
16
+ class ProfilingLogger(logging.Logger):
17
+ def __init__(self, name: str, level: int = logging.INFO):
18
+ super().__init__(name, level)
19
+
20
+ def info_def_report(self, df, timeseries: bool) -> None: # noqa: ANN001
21
+ try:
22
+ ncols = len(df.columns)
23
+ except AttributeError:
24
+ ncols = 0
25
+
26
+ nrows = calculate_nrows(df)
27
+
28
+ if isinstance(df, pd.DataFrame):
29
+ dataframe = "pandas"
30
+ report_type = "regular"
31
+ elif df is None:
32
+ dataframe = "pandas"
33
+ report_type = "compare"
34
+ else:
35
+ dataframe = "spark"
36
+ report_type = "regular"
37
+
38
+ dbx = is_running_in_databricks()
39
+ datatype = "timeseries" if timeseries else "tabular"
40
+
41
+ analytics_features(
42
+ dataframe=dataframe,
43
+ datatype=datatype,
44
+ report_type=report_type,
45
+ nrows=nrows,
46
+ ncols=ncols,
47
+ dbx=dbx,
48
+ )
49
+
50
+ super().info(
51
+ f"[PROFILING] Calculating profile with the following characteristics "
52
+ f"- {dataframe} | {datatype} | {report_type}."
53
+ )
@@ -0,0 +1,8 @@
1
+ """Utility functions that come in handy when working with Jupyter notebooks"""
2
+
3
+
4
+ def full_width() -> None:
5
+ """Resize the notebook to use it's full width"""
6
+ from IPython.display import HTML, display
7
+
8
+ display(HTML("<style>.container { width:100% !important; }</style>"))
@@ -0,0 +1,45 @@
1
+ """Paths that are useful throughout the project."""
2
+ from pathlib import Path
3
+
4
+
5
+ def get_project_root() -> Path:
6
+ """Returns the path to the project root folder.
7
+
8
+ Returns:
9
+ The path to the project root folder.
10
+ """
11
+ return Path(__file__).parent.parent.parent.parent
12
+
13
+
14
+ def get_config(file_name: str) -> Path:
15
+ """Returns the path a config file.
16
+
17
+ Returns:
18
+ The path to a config file.
19
+ """
20
+ return Path(__file__).parent.parent / file_name
21
+
22
+
23
+ def get_data_path() -> Path:
24
+ """Returns the path to the dataset cache ([root] / data)
25
+
26
+ Returns:
27
+ The path to the dataset cache
28
+ """
29
+ return get_project_root() / "data"
30
+
31
+
32
+ def get_html_template_path() -> Path:
33
+ """Returns the path to the HTML templates
34
+
35
+ Returns:
36
+ The path to the HTML templates
37
+ """
38
+ return (
39
+ Path(__file__).parent.parent
40
+ / "report"
41
+ / "presentation"
42
+ / "flavours"
43
+ / "html"
44
+ / "templates"
45
+ )
@@ -0,0 +1,15 @@
1
+ from functools import wraps
2
+ from typing import Any, Callable
3
+
4
+ from tqdm import tqdm
5
+
6
+
7
+ def progress(fn: Callable, bar: tqdm, message: str) -> Callable:
8
+ @wraps(fn)
9
+ def inner(*args, **kwargs) -> Any:
10
+ bar.set_postfix_str(message)
11
+ ret = fn(*args, **kwargs)
12
+ bar.update()
13
+ return ret
14
+
15
+ return inner
@@ -0,0 +1,22 @@
1
+ def get_alert_styles() -> dict:
2
+ return {
3
+ "constant": "warning",
4
+ "constant_length": "primary",
5
+ "duplicates": "secondary",
6
+ "empty": "info",
7
+ "high_cardinality": "danger",
8
+ "high_correlation": "secondary",
9
+ "infinite": "info",
10
+ "imbalance": "primary",
11
+ "missing": "info",
12
+ "non_stationary": "secondary",
13
+ "rejected": "danger",
14
+ "seasonal": "secondary",
15
+ "skewed": "info",
16
+ "truncated": "info",
17
+ "type_date": "warning",
18
+ "uniform": "danger",
19
+ "unique": "danger",
20
+ "unsupported": "warning",
21
+ "zeros": "info",
22
+ }
@@ -0,0 +1,19 @@
1
+ try:
2
+ from importlib.metadata import version
3
+ except ImportError:
4
+ import pkg_resources
5
+
6
+ def version(pkg: str) -> str: # type: ignore
7
+ return pkg_resources.get_distribution(pkg).version
8
+
9
+
10
+ def pandas_version() -> list:
11
+ return list(map(int, version("pandas").split(".")))
12
+
13
+
14
+ def pandas_major_version() -> int:
15
+ return pandas_version()[0]
16
+
17
+
18
+ def is_pandas_1() -> bool:
19
+ return pandas_major_version() == 1
@@ -0,0 +1 @@
1
+ __version__ = '4.19.0'
@@ -0,0 +1 @@
1
+ """Code for generating plots"""