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.
- 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 +573 -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-4.19.0.dist-info/METADATA +362 -0
- fg_data_profiling-4.19.0.dist-info/RECORD +238 -0
- fg_data_profiling-4.19.0.dist-info/WHEEL +6 -0
- fg_data_profiling-4.19.0.dist-info/entry_points.txt +3 -0
- fg_data_profiling-4.19.0.dist-info/licenses/LICENSE +21 -0
- fg_data_profiling-4.19.0.dist-info/top_level.txt +2 -0
- 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"""
|