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,33 @@
|
|
|
1
|
+
"""Compute statistical description of datasets."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from pandas.api.types import is_numeric_dtype
|
|
5
|
+
|
|
6
|
+
from data_profiling.config import Settings
|
|
7
|
+
from data_profiling.model.timeseries_index import get_time_index_description
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@get_time_index_description.register
|
|
11
|
+
def pandas_get_time_index_description(
|
|
12
|
+
config: Settings,
|
|
13
|
+
df: pd.DataFrame,
|
|
14
|
+
table_stats: dict,
|
|
15
|
+
) -> dict:
|
|
16
|
+
if not (is_numeric_dtype(df.index) or isinstance(df.index, pd.DatetimeIndex)):
|
|
17
|
+
return {}
|
|
18
|
+
|
|
19
|
+
n_series = table_stats["types"].get("TimeSeries", 0)
|
|
20
|
+
length = table_stats["n"]
|
|
21
|
+
start = df.index.min()
|
|
22
|
+
end = df.index.max()
|
|
23
|
+
period = abs(np.diff(df.index)).mean()
|
|
24
|
+
if isinstance(df.index, pd.DatetimeIndex):
|
|
25
|
+
period = pd.Timedelta(period)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
"n_series": n_series,
|
|
29
|
+
"length": length,
|
|
30
|
+
"start": start,
|
|
31
|
+
"end": end,
|
|
32
|
+
"period": period,
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def weighted_median(data: np.ndarray, weights: np.ndarray) -> int:
|
|
5
|
+
"""
|
|
6
|
+
Args:
|
|
7
|
+
data (list or numpy.array): data
|
|
8
|
+
weights (list or numpy.array): weights
|
|
9
|
+
"""
|
|
10
|
+
if not isinstance(data, np.ndarray):
|
|
11
|
+
data = np.array(data)
|
|
12
|
+
if not isinstance(weights, np.ndarray):
|
|
13
|
+
weights = np.array(weights)
|
|
14
|
+
|
|
15
|
+
s_data, s_weights = map(np.sort, [data, weights])
|
|
16
|
+
midpoint = 0.5 * np.sum(s_weights)
|
|
17
|
+
|
|
18
|
+
if s_weights[-1] > midpoint:
|
|
19
|
+
w_median = data[weights == np.max(weights)][0]
|
|
20
|
+
else:
|
|
21
|
+
cs_weights = np.cumsum(s_weights)
|
|
22
|
+
idx = np.where(cs_weights <= midpoint)[0][-1]
|
|
23
|
+
if cs_weights[idx] == midpoint:
|
|
24
|
+
w_median = np.mean(s_data[idx : idx + 2])
|
|
25
|
+
else:
|
|
26
|
+
w_median = s_data[idx + 1]
|
|
27
|
+
return w_median
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List, Optional, TypeVar
|
|
2
|
+
|
|
3
|
+
from multimethod import multimethod
|
|
4
|
+
from pydantic.v1 import BaseModel
|
|
5
|
+
|
|
6
|
+
from data_profiling.config import Settings
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T") # type: ignore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Sample(BaseModel):
|
|
12
|
+
id: str
|
|
13
|
+
data: T # type: ignore
|
|
14
|
+
name: str
|
|
15
|
+
caption: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@multimethod
|
|
19
|
+
def get_sample(config: Settings, df: T) -> List[Sample]:
|
|
20
|
+
raise NotImplementedError()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_custom_sample(sample: dict) -> List[Sample]:
|
|
24
|
+
if "name" not in sample:
|
|
25
|
+
sample["name"] = None
|
|
26
|
+
if "caption" not in sample:
|
|
27
|
+
sample["caption"] = None
|
|
28
|
+
|
|
29
|
+
samples = [
|
|
30
|
+
Sample(
|
|
31
|
+
id="custom",
|
|
32
|
+
data=sample["data"],
|
|
33
|
+
name=sample["name"],
|
|
34
|
+
caption=sample["caption"],
|
|
35
|
+
)
|
|
36
|
+
]
|
|
37
|
+
return samples
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
# Dynamically import all modules inside the Spark folder
|
|
4
|
+
SPARK_MODULES = [
|
|
5
|
+
"correlations_spark",
|
|
6
|
+
"dataframe_spark",
|
|
7
|
+
"describe_boolean_spark",
|
|
8
|
+
"describe_categorical_spark",
|
|
9
|
+
"describe_counts_spark",
|
|
10
|
+
"describe_date_spark",
|
|
11
|
+
"describe_generic_spark",
|
|
12
|
+
"describe_numeric_spark",
|
|
13
|
+
"describe_supported_spark",
|
|
14
|
+
"duplicates_spark",
|
|
15
|
+
"missing_spark",
|
|
16
|
+
"sample_spark",
|
|
17
|
+
"summary_spark",
|
|
18
|
+
"table_spark",
|
|
19
|
+
"timeseries_index_spark",
|
|
20
|
+
"describe_text_spark",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Load modules dynamically
|
|
24
|
+
for module_name in SPARK_MODULES:
|
|
25
|
+
module = importlib.import_module(f"data_profiling.model.spark.{module_name}")
|
|
26
|
+
globals().update(
|
|
27
|
+
{
|
|
28
|
+
name: getattr(module, name)
|
|
29
|
+
for name in dir(module)
|
|
30
|
+
if not name.startswith("_")
|
|
31
|
+
}
|
|
32
|
+
) # type: ignore
|
|
33
|
+
|
|
34
|
+
# Explicitly list all available functions
|
|
35
|
+
__all__ = [
|
|
36
|
+
"describe_generic_spark",
|
|
37
|
+
"describe_boolean_1d_spark",
|
|
38
|
+
"describe_categorical_1d_spark",
|
|
39
|
+
"describe_text_1d_spark",
|
|
40
|
+
"describe_numeric_1d_spark",
|
|
41
|
+
"describe_date_1d_spark",
|
|
42
|
+
"describe_counts_spark",
|
|
43
|
+
"get_duplicates_spark",
|
|
44
|
+
"get_sample_spark",
|
|
45
|
+
"get_table_stats_spark",
|
|
46
|
+
"get_time_index_description_spark",
|
|
47
|
+
"get_series_descriptions_spark",
|
|
48
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Correlations between variables."""
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pyspark
|
|
6
|
+
from packaging import version
|
|
7
|
+
from pyspark.ml.feature import VectorAssembler
|
|
8
|
+
from pyspark.ml.stat import Correlation
|
|
9
|
+
from pyspark.sql import DataFrame
|
|
10
|
+
from pyspark.sql.functions import PandasUDFType, lit, pandas_udf
|
|
11
|
+
from pyspark.sql.types import ArrayType, DoubleType, StructField, StructType
|
|
12
|
+
|
|
13
|
+
from data_profiling.config import Settings
|
|
14
|
+
|
|
15
|
+
CORRELATION_PEARSON = "pearson"
|
|
16
|
+
CORRELATION_SPEARMAN = "spearman"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def spearman_compute(
|
|
20
|
+
config: Settings, df: DataFrame, summary: dict
|
|
21
|
+
) -> Optional[pd.DataFrame]:
|
|
22
|
+
# Get the numerical cols for index and column names
|
|
23
|
+
# Spark only computes Spearman natively for the above dtypes
|
|
24
|
+
matrix, num_cols = _compute_corr_natively(
|
|
25
|
+
df, summary, corr_type=CORRELATION_SPEARMAN
|
|
26
|
+
)
|
|
27
|
+
return pd.DataFrame(matrix, index=num_cols, columns=num_cols)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pearson_compute(
|
|
31
|
+
config: Settings, df: DataFrame, summary: dict
|
|
32
|
+
) -> Optional[pd.DataFrame]:
|
|
33
|
+
|
|
34
|
+
# Get the numerical cols for index and column names
|
|
35
|
+
# Spark only computes Pearson natively for the above dtypes
|
|
36
|
+
matrix, num_cols = _compute_corr_natively(
|
|
37
|
+
df, summary, corr_type=CORRELATION_PEARSON
|
|
38
|
+
)
|
|
39
|
+
return pd.DataFrame(matrix, index=num_cols, columns=num_cols)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _compute_corr_natively(df: DataFrame, summary: dict, corr_type: str) -> ArrayType:
|
|
43
|
+
"""
|
|
44
|
+
This function exists as pearson and spearman correlation computations have the
|
|
45
|
+
exact same workflow. The syntax is Correlation.corr(dataframe, method="pearson" OR "spearman"),
|
|
46
|
+
and Correlation is from pyspark.ml.stat
|
|
47
|
+
"""
|
|
48
|
+
variables = {
|
|
49
|
+
column: {
|
|
50
|
+
"type": description["type"],
|
|
51
|
+
"all_missing": description.get("n", 0) - description.get("n_missing", 0)
|
|
52
|
+
== 0,
|
|
53
|
+
}
|
|
54
|
+
for column, description in summary.items()
|
|
55
|
+
}
|
|
56
|
+
interval_columns = [
|
|
57
|
+
column
|
|
58
|
+
for column, mapping in variables.items()
|
|
59
|
+
if mapping["type"] == "Numeric" and not mapping["all_missing"]
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
if not interval_columns:
|
|
63
|
+
return [], interval_columns
|
|
64
|
+
|
|
65
|
+
df = df.select(*interval_columns)
|
|
66
|
+
|
|
67
|
+
# convert to vector column first
|
|
68
|
+
vector_col = "corr_features"
|
|
69
|
+
|
|
70
|
+
assembler_args = {"inputCols": df.columns, "outputCol": vector_col}
|
|
71
|
+
|
|
72
|
+
# As handleInvalid was only implemented in spark 2.4.0, we use it only if pyspark version >= 2.4.0
|
|
73
|
+
if version.parse(pyspark.__version__) >= version.parse("2.4.0"):
|
|
74
|
+
assembler_args["handleInvalid"] = "skip"
|
|
75
|
+
|
|
76
|
+
assembler = VectorAssembler(**assembler_args)
|
|
77
|
+
df_vector = assembler.transform(df).select(vector_col)
|
|
78
|
+
|
|
79
|
+
if df_vector.count() <= 1:
|
|
80
|
+
return [], interval_columns
|
|
81
|
+
|
|
82
|
+
# get correlation matrix
|
|
83
|
+
matrix = (
|
|
84
|
+
Correlation.corr(df_vector, vector_col, method=corr_type).head()[0].toArray()
|
|
85
|
+
)
|
|
86
|
+
return matrix, interval_columns
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def kendall_compute(
|
|
90
|
+
config: Settings, df: DataFrame, summary: dict
|
|
91
|
+
) -> Optional[pd.DataFrame]:
|
|
92
|
+
raise NotImplementedError()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cramers_compute(
|
|
96
|
+
config: Settings, df: DataFrame, summary: dict
|
|
97
|
+
) -> Optional[pd.DataFrame]:
|
|
98
|
+
raise NotImplementedError()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def phi_k_compute(
|
|
102
|
+
config: Settings, df: DataFrame, summary: dict
|
|
103
|
+
) -> Optional[pd.DataFrame]:
|
|
104
|
+
|
|
105
|
+
threshold = config.categorical_maximum_correlation_distinct
|
|
106
|
+
intcols = {
|
|
107
|
+
key
|
|
108
|
+
for key, value in summary.items()
|
|
109
|
+
# DateTime currently excluded
|
|
110
|
+
# In some use cases, it makes sense to convert it to interval
|
|
111
|
+
# See https://github.com/KaveIO/PhiK/issues/7
|
|
112
|
+
if value["type"] == "Numeric" and 1 < value["n_distinct"]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
supportedcols = {
|
|
116
|
+
key
|
|
117
|
+
for key, value in summary.items()
|
|
118
|
+
if value["type"] != "Unsupported" and 1 < value["n_distinct"] <= threshold
|
|
119
|
+
}
|
|
120
|
+
selcols = list(supportedcols.union(intcols))
|
|
121
|
+
|
|
122
|
+
if len(selcols) <= 1:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
# pandas mapped udf works only with a groupby, we force the groupby to operate on all columns at once
|
|
126
|
+
# by giving one value to all columns
|
|
127
|
+
groupby_df = df.select(selcols).withColumn("groupby", lit(1))
|
|
128
|
+
|
|
129
|
+
# generate output schema for pandas_udf
|
|
130
|
+
output_schema_components = []
|
|
131
|
+
for column in selcols:
|
|
132
|
+
output_schema_components.append(StructField(column, DoubleType(), True))
|
|
133
|
+
output_schema = StructType(output_schema_components)
|
|
134
|
+
|
|
135
|
+
# create the pandas grouped map function to do vectorized kendall within spark itself
|
|
136
|
+
@pandas_udf(output_schema, PandasUDFType.GROUPED_MAP)
|
|
137
|
+
def phik(pdf: pd.DataFrame) -> pd.DataFrame:
|
|
138
|
+
correlation = phik.phik_matrix(df=pdf, interval_cols=list(intcols))
|
|
139
|
+
return correlation
|
|
140
|
+
|
|
141
|
+
# return the appropriate dataframe (similar to pandas_df.corr results)
|
|
142
|
+
if len(groupby_df.head(1)) > 0:
|
|
143
|
+
# perform correlation in spark, and get the results back in pandas
|
|
144
|
+
df = pd.DataFrame(
|
|
145
|
+
groupby_df.groupby("groupby").apply(phik).toPandas().values,
|
|
146
|
+
columns=selcols,
|
|
147
|
+
index=selcols,
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
df = pd.DataFrame()
|
|
151
|
+
|
|
152
|
+
return df
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from pyspark.sql import DataFrame
|
|
4
|
+
from pyspark.sql.types import MapType
|
|
5
|
+
|
|
6
|
+
from data_profiling.config import Settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def spark_preprocess(config: Settings, df: DataFrame) -> DataFrame:
|
|
10
|
+
"""Preprocess the Spark DataFrame by removing MapType columns.
|
|
11
|
+
- Excludes columns of type MapType
|
|
12
|
+
- Raises a warning if any MapType columns are removed
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
config: Report settings object
|
|
16
|
+
df: The Spark DataFrame
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The preprocessed DataFrame without MapType columns.
|
|
20
|
+
"""
|
|
21
|
+
# Identify MapType columns
|
|
22
|
+
columns_to_remove = [
|
|
23
|
+
field.name for field in df.schema.fields if isinstance(field.dataType, MapType)
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
if columns_to_remove:
|
|
27
|
+
warnings.warn(
|
|
28
|
+
f"""Spark DataFrame profiling does not handle MapTypes. Column(s) {', '.join(columns_to_remove)} will be ignored.
|
|
29
|
+
To fix this, consider converting your MapType into a StructType or extracting key-value pairs using `explode()`.
|
|
30
|
+
"""
|
|
31
|
+
)
|
|
32
|
+
df = df.drop(*columns_to_remove)
|
|
33
|
+
|
|
34
|
+
return df # Return unchanged DataFrame if no MapType columns exist
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from pyspark.sql import DataFrame
|
|
4
|
+
|
|
5
|
+
from data_profiling.config import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def describe_boolean_1d_spark(
|
|
9
|
+
config: Settings, df: DataFrame, summary: dict
|
|
10
|
+
) -> Tuple[Settings, DataFrame, dict]:
|
|
11
|
+
"""Describe a boolean series.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
series: The Series to describe.
|
|
15
|
+
summary: The dict containing the series description so far.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A dict containing calculated series description values.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
value_counts = summary["value_counts"]
|
|
22
|
+
|
|
23
|
+
# get the most common boolean value and its frequency
|
|
24
|
+
top = value_counts.first()
|
|
25
|
+
summary.update({"top": top[0], "freq": top[1]})
|
|
26
|
+
|
|
27
|
+
return config, df, summary
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from pyspark.sql import DataFrame
|
|
4
|
+
|
|
5
|
+
from data_profiling.config import Settings
|
|
6
|
+
from data_profiling.model.summary_algorithms import describe_categorical_1d
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@describe_categorical_1d.register
|
|
10
|
+
def describe_categorical_1d_spark(
|
|
11
|
+
config: Settings, df: DataFrame, summary: dict
|
|
12
|
+
) -> Tuple[Settings, DataFrame, dict]:
|
|
13
|
+
"""Describe a categorical series.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
series: The Series to describe.
|
|
17
|
+
summary: The dict containing the series description so far.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A dict containing calculated series description values.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# FIXME: cat description
|
|
24
|
+
redact = config.vars.cat.redact
|
|
25
|
+
if not redact:
|
|
26
|
+
summary["first_rows"] = df.limit(5).toPandas().squeeze("columns")
|
|
27
|
+
|
|
28
|
+
return config, df, summary
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyspark counts
|
|
3
|
+
"""
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from pyspark.sql import DataFrame
|
|
8
|
+
from pyspark.sql import functions as F
|
|
9
|
+
from pyspark.sql import types as T
|
|
10
|
+
|
|
11
|
+
from data_profiling.config import Settings
|
|
12
|
+
from data_profiling.model.summary_algorithms import describe_counts
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@describe_counts.register
|
|
16
|
+
def describe_counts_spark(
|
|
17
|
+
config: Settings, series: DataFrame, summary: dict
|
|
18
|
+
) -> Tuple[Settings, DataFrame, dict]:
|
|
19
|
+
"""Counts the values in a series (with and without NaN, distinct).
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
config: Profiling settings.
|
|
23
|
+
series: Spark DataFrame column for which we want to calculate the values.
|
|
24
|
+
summary: Dictionary to store the summary results.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Updated settings, input series, and summary dictionary.
|
|
28
|
+
"""
|
|
29
|
+
# Cast Decimal Type s
|
|
30
|
+
if isinstance(series.schema.fields[0].dataType, T.DecimalType):
|
|
31
|
+
series = series.select(
|
|
32
|
+
F.col(series.columns[0]).cast(T.DoubleType()).alias(series.columns[0])
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Count occurrences of each value
|
|
36
|
+
value_counts = series.groupBy(series.columns[0]).count()
|
|
37
|
+
|
|
38
|
+
# Sort by count descending, persist the result
|
|
39
|
+
value_counts = value_counts.orderBy(F.desc("count")).persist()
|
|
40
|
+
|
|
41
|
+
# Sort by column value ascending (for frequency tables)
|
|
42
|
+
value_counts_index_sorted = value_counts.orderBy(F.asc(series.columns[0]))
|
|
43
|
+
|
|
44
|
+
# Count missing values
|
|
45
|
+
if series.dtypes[0][1] in ("int", "float", "bigint", "double"):
|
|
46
|
+
n_missing = (
|
|
47
|
+
# Need to add the isnan() check because Pandas isnull check will count NaN as null, but Spark does not
|
|
48
|
+
value_counts.filter(
|
|
49
|
+
F.col(series.columns[0]).isNull() | F.isnan(F.col(series.columns[0]))
|
|
50
|
+
)
|
|
51
|
+
.select("count")
|
|
52
|
+
.first()
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
n_missing = (
|
|
56
|
+
# Need to add the isnan() check because Pandas isnull check will count NaN as null, but Spark does not
|
|
57
|
+
value_counts.filter(F.col(series.columns[0]).isNull())
|
|
58
|
+
.select("count")
|
|
59
|
+
.first()
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
n_missing = n_missing["count"] if n_missing else 0
|
|
63
|
+
|
|
64
|
+
# Convert top 200 values to Pandas for frequency table display
|
|
65
|
+
top_200_sorted = (
|
|
66
|
+
value_counts_index_sorted.limit(200)
|
|
67
|
+
.toPandas()
|
|
68
|
+
.set_index(series.columns[0], drop=True)
|
|
69
|
+
.squeeze(axis="columns")
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
summary["n_missing"] = n_missing
|
|
73
|
+
summary["value_counts"] = value_counts.persist()
|
|
74
|
+
summary["value_counts_index_sorted"] = top_200_sorted
|
|
75
|
+
|
|
76
|
+
column = series.columns[0]
|
|
77
|
+
|
|
78
|
+
if series.dtypes[0][1] in ("int", "float", "bigint", "double"):
|
|
79
|
+
value_counts_no_nan = (
|
|
80
|
+
value_counts.filter(F.col(column).isNotNull()) # Exclude NaNs
|
|
81
|
+
.filter(~F.isnan(F.col(column))) # Remove implicit NaNs (if numeric column)
|
|
82
|
+
.groupBy(column) # Group by unique values
|
|
83
|
+
.agg(F.sum("count").alias("count")) # Sum of count
|
|
84
|
+
.orderBy(F.desc("count")) # Sort in descending order
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
value_counts_no_nan = (
|
|
88
|
+
value_counts.filter(F.col(column).isNotNull()) # Exclude NULLs
|
|
89
|
+
.groupBy(column) # Group by unique timestamp values
|
|
90
|
+
.agg(F.sum("count").alias("count")) # Sum of count
|
|
91
|
+
.orderBy(F.desc("count")) # Sort by most frequent timestamps
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Convert to Pandas Series, forcing proper structure
|
|
95
|
+
if value_counts_no_nan.count() > 0:
|
|
96
|
+
pdf = value_counts_no_nan.toPandas().set_index(column)["count"]
|
|
97
|
+
summary["value_counts_without_nan"] = pd.Series(
|
|
98
|
+
pdf
|
|
99
|
+
) # Ensures it's always a Series
|
|
100
|
+
else:
|
|
101
|
+
summary["value_counts_without_nan"] = pd.Series(
|
|
102
|
+
dtype=int
|
|
103
|
+
) # Ensures an empty Series
|
|
104
|
+
|
|
105
|
+
return config, series, summary
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import pyspark.sql.functions as F
|
|
4
|
+
from numpy import array
|
|
5
|
+
from pyspark.sql import DataFrame
|
|
6
|
+
|
|
7
|
+
from data_profiling.config import Settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def date_stats_spark(df: DataFrame, summary: dict) -> dict:
|
|
11
|
+
column = df.columns[0]
|
|
12
|
+
|
|
13
|
+
expr = [
|
|
14
|
+
F.min(F.col(column)).alias("min"),
|
|
15
|
+
F.max(F.col(column)).alias("max"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
return df.agg(*expr).first().asDict()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def describe_date_1d_spark(
|
|
22
|
+
config: Settings, df: DataFrame, summary: dict
|
|
23
|
+
) -> Tuple[Settings, DataFrame, dict]:
|
|
24
|
+
"""Describe a date series.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
series: The Series to describe.
|
|
28
|
+
summary: The dict containing the series description so far.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A dict containing calculated series description values.
|
|
32
|
+
"""
|
|
33
|
+
col_name = df.columns[0]
|
|
34
|
+
stats = date_stats_spark(df, summary)
|
|
35
|
+
|
|
36
|
+
summary.update({"min": stats["min"], "max": stats["max"]})
|
|
37
|
+
|
|
38
|
+
summary["range"] = summary["max"] - summary["min"]
|
|
39
|
+
|
|
40
|
+
# Convert date to numeric so we can compute the histogram
|
|
41
|
+
df = df.withColumn(col_name, F.unix_timestamp(df[col_name]))
|
|
42
|
+
|
|
43
|
+
# Get the number of bins
|
|
44
|
+
bins = config.plot.histogram.bins
|
|
45
|
+
bins_arg = "auto" if bins == 0 else min(bins, summary["n_distinct"])
|
|
46
|
+
|
|
47
|
+
# Run the histogram
|
|
48
|
+
bin_edges, hist = df.select(col_name).rdd.flatMap(lambda x: x).histogram(bins_arg)
|
|
49
|
+
|
|
50
|
+
summary.update({"histogram": (array(hist), array(bin_edges))})
|
|
51
|
+
return config, df, summary
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from pyspark.sql import DataFrame
|
|
4
|
+
|
|
5
|
+
from data_profiling.config import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def describe_generic_spark(
|
|
9
|
+
config: Settings, df: DataFrame, summary: dict
|
|
10
|
+
) -> Tuple[Settings, DataFrame, dict]:
|
|
11
|
+
"""Describe generic series.
|
|
12
|
+
Args:
|
|
13
|
+
series: The Series to describe.
|
|
14
|
+
summary: The dict containing the series description so far.
|
|
15
|
+
Returns:
|
|
16
|
+
A dict containing calculated series description values.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# number of observations in the Series
|
|
20
|
+
length = df.count()
|
|
21
|
+
|
|
22
|
+
summary["n"] = length
|
|
23
|
+
summary["p_missing"] = summary["n_missing"] / length
|
|
24
|
+
summary["count"] = length - summary["n_missing"]
|
|
25
|
+
|
|
26
|
+
# FIXME: This is not correct, but used to fulfil render expectations
|
|
27
|
+
# @chanedwin
|
|
28
|
+
summary["memory_size"] = 0
|
|
29
|
+
|
|
30
|
+
return config, df, summary
|