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,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