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,1158 @@
1
+ """Plot functions for the profiling report."""
2
+ import copy
3
+ from typing import Any, Callable, List, Optional, Tuple, Union
4
+
5
+ import matplotlib
6
+ import numpy as np
7
+ import pandas as pd
8
+ import seaborn as sns
9
+ from matplotlib import pyplot as plt
10
+ from matplotlib.collections import PolyCollection
11
+ from matplotlib.colors import Colormap, LinearSegmentedColormap, ListedColormap, rgb2hex
12
+ from matplotlib.dates import AutoDateLocator, ConciseDateFormatter
13
+ from matplotlib.patches import Patch
14
+ from matplotlib.ticker import FuncFormatter, MaxNLocator
15
+ from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
16
+ from typeguard import typechecked
17
+ from wordcloud import WordCloud
18
+
19
+ from data_profiling.config import Settings
20
+ from data_profiling.utils.common import convert_timestamp_to_datetime
21
+ from data_profiling.visualisation.context import manage_matplotlib_context
22
+ from data_profiling.visualisation.utils import plot_360_n0sc0pe
23
+
24
+
25
+ def format_fn(tick_val: int, tick_pos: Any) -> str:
26
+ return convert_timestamp_to_datetime(tick_val).strftime("%Y-%m-%d %H:%M:%S")
27
+
28
+
29
+ def _plot_word_cloud(
30
+ config: Settings,
31
+ series: Union[pd.Series, List[pd.Series]],
32
+ figsize: tuple = (6, 4),
33
+ ) -> plt.Figure:
34
+ if not isinstance(series, list):
35
+ series = [series]
36
+ plot = plt.figure(figsize=figsize)
37
+ for i, series_data in enumerate(series):
38
+ word_dict = series_data.to_dict()
39
+ wordcloud = WordCloud(
40
+ font_path=config.plot.font_path,
41
+ background_color="white",
42
+ random_state=123,
43
+ width=300,
44
+ height=200,
45
+ scale=2,
46
+ ).generate_from_frequencies(word_dict)
47
+
48
+ ax = plot.add_subplot(1, len(series), i + 1)
49
+ ax.imshow(wordcloud)
50
+ ax.axis("off")
51
+
52
+ return plot
53
+
54
+
55
+ def _plot_histogram(
56
+ config: Settings,
57
+ series: np.ndarray,
58
+ bins: Union[int, np.ndarray],
59
+ figsize: tuple = (6, 4),
60
+ date: bool = False,
61
+ hide_yaxis: bool = False,
62
+ ) -> plt.Figure:
63
+ """Plot a histogram from the data and return the AxesSubplot object.
64
+
65
+ Args:
66
+ config: the Settings object
67
+ series: The data to plot
68
+ bins: number of bins (int for equal size, ndarray for variable size)
69
+ figsize: The size of the figure (width, height) in inches, default (6,4)
70
+ date: is the x-axis of date type
71
+
72
+ Returns:
73
+ The histogram plot.
74
+ """
75
+ # we have precomputed the histograms...
76
+ if isinstance(bins, list):
77
+ n_labels = len(config.html.style._labels)
78
+ fig = plt.figure(figsize=figsize)
79
+ plot = fig.add_subplot(111)
80
+
81
+ for idx in reversed(list(range(n_labels))):
82
+ if len(bins):
83
+ diff = np.diff(bins[idx])
84
+ plot.bar(
85
+ bins[idx][:-1] + diff / 2, # type: ignore
86
+ series[idx],
87
+ diff,
88
+ facecolor=config.html.style.primary_colors[idx],
89
+ alpha=0.6,
90
+ )
91
+
92
+ if date:
93
+ plot.xaxis.set_major_formatter(FuncFormatter(format_fn))
94
+
95
+ if not config.plot.histogram.x_axis_labels:
96
+ plot.set_xticklabels([])
97
+
98
+ if hide_yaxis:
99
+ plot.yaxis.set_visible(False)
100
+
101
+ if not config.plot.histogram.x_axis_labels:
102
+ fig.xticklabels([])
103
+
104
+ if not hide_yaxis:
105
+ fig.supylabel("Frequency")
106
+ else:
107
+ fig = plt.figure(figsize=figsize)
108
+ plot = fig.add_subplot(111)
109
+ if not hide_yaxis:
110
+ plot.set_ylabel("Frequency")
111
+ else:
112
+ plot.axes.get_yaxis().set_visible(False)
113
+
114
+ diff = np.diff(bins)
115
+ plot.bar(
116
+ bins[:-1] + diff / 2, # type: ignore
117
+ series,
118
+ diff,
119
+ facecolor=config.html.style.primary_colors[0],
120
+ )
121
+
122
+ if date:
123
+ plot.xaxis.set_major_formatter(FuncFormatter(format_fn))
124
+
125
+ if not config.plot.histogram.x_axis_labels:
126
+ plot.set_xticklabels([])
127
+
128
+ return plot
129
+
130
+
131
+ @manage_matplotlib_context()
132
+ def plot_word_cloud(config: Settings, word_counts: pd.Series) -> str:
133
+ _plot_word_cloud(config=config, series=word_counts)
134
+ return plot_360_n0sc0pe(config)
135
+
136
+
137
+ def _is_valid_hist_data(series: np.ndarray, bins: Union[int, np.ndarray]) -> bool:
138
+ """
139
+ Returns True if the series and bins contain enough usable numeric data
140
+ to produce a histogram without matplotlib errors.
141
+ """
142
+ if series is None or bins is None:
143
+ return False
144
+
145
+ if len(series) == 0:
146
+ return False
147
+
148
+ try:
149
+ series_arr = np.asarray(series, dtype=float)
150
+ except Exception:
151
+ return False
152
+
153
+ if not np.isfinite(series_arr).any():
154
+ return False
155
+
156
+ # Handle bins type
157
+ if isinstance(bins, int):
158
+ if bins < 1:
159
+ return False
160
+ else:
161
+ try:
162
+ bins_arr = np.asarray(bins, dtype=float)
163
+ except Exception:
164
+ return False
165
+ if len(bins_arr) < 2:
166
+ return False
167
+ if not np.isfinite(bins_arr).all():
168
+ return False
169
+
170
+ return True
171
+
172
+
173
+ @manage_matplotlib_context()
174
+ def histogram(
175
+ config: Settings,
176
+ series: np.ndarray,
177
+ bins: Union[int, np.ndarray],
178
+ date: bool = False,
179
+ ) -> str | None:
180
+ """Plot an histogram of the data.
181
+
182
+ Args:
183
+ config: Settings
184
+ series: The data to plot.
185
+ bins: number of bins (int for equal size, ndarray for variable size)
186
+ date: is histogram of date(time)?
187
+
188
+ Returns:
189
+ The resulting histogram encoded as a string.
190
+
191
+ """
192
+
193
+ if not _is_valid_hist_data(series, bins):
194
+ return None
195
+ plot = _plot_histogram(config, series, bins, date=date, figsize=(7, 3))
196
+ plot.xaxis.set_tick_params(rotation=90 if date else 45)
197
+ plot.figure.tight_layout()
198
+ return plot_360_n0sc0pe(config)
199
+
200
+
201
+ @manage_matplotlib_context()
202
+ def mini_histogram(
203
+ config: Settings,
204
+ series: np.ndarray,
205
+ bins: Union[int, np.ndarray],
206
+ date: bool = False,
207
+ ) -> str | None:
208
+ """Plot a small (mini) histogram of the data.
209
+
210
+ Args:
211
+ config: Settings
212
+ series: The data to plot.
213
+ bins: number of bins (int for equal size, ndarray for variable size)
214
+
215
+ Returns:
216
+ The resulting mini histogram encoded as a string.
217
+ """
218
+ if not _is_valid_hist_data(series, bins):
219
+ return None
220
+
221
+ plot = _plot_histogram(
222
+ config, series, bins, figsize=(3, 2.25), date=date, hide_yaxis=True
223
+ )
224
+ plot.set_facecolor("w")
225
+
226
+ for tick in plot.xaxis.get_major_ticks():
227
+ tick.label1.set_fontsize(6 if date else 8)
228
+ plot.xaxis.set_tick_params(rotation=90 if date else 45)
229
+ plot.figure.tight_layout()
230
+
231
+ return plot_360_n0sc0pe(config)
232
+
233
+
234
+ def get_cmap_half(
235
+ cmap: Union[Colormap, LinearSegmentedColormap, ListedColormap]
236
+ ) -> LinearSegmentedColormap:
237
+ """Get the upper half of the color map
238
+
239
+ Args:
240
+ cmap: the color map
241
+
242
+ Returns:
243
+ A new color map based on the upper half of another color map
244
+
245
+ References:
246
+ https://stackoverflow.com/a/24746399/470433
247
+ """
248
+ # Evaluate an existing colormap from 0.5 (midpoint) to 1 (upper end)
249
+ colors = cmap(np.linspace(0.5, 1, cmap.N // 2))
250
+
251
+ # Create a new colormap from those colors
252
+ return LinearSegmentedColormap.from_list("cmap_half", colors)
253
+
254
+
255
+ def get_correlation_font_size(n_labels: int) -> Optional[int]:
256
+ """Dynamic label font sizes in correlation plots
257
+
258
+ Args:
259
+ n_labels: the number of labels
260
+
261
+ Returns:
262
+ A font size or None for the default font size
263
+ """
264
+ if n_labels > 100:
265
+ font_size = 4
266
+ elif n_labels > 80:
267
+ font_size = 5
268
+ elif n_labels > 50:
269
+ font_size = 6
270
+ elif n_labels > 40:
271
+ font_size = 8
272
+ else:
273
+ return None
274
+ return font_size
275
+
276
+
277
+ @manage_matplotlib_context()
278
+ def correlation_matrix(config: Settings, data: pd.DataFrame, vmin: int = -1) -> str:
279
+ """Plot image of a matrix correlation.
280
+
281
+ Args:
282
+ config: Settings
283
+ data: The matrix correlation to plot.
284
+ vmin: Minimum value of value range.
285
+
286
+ Returns:
287
+ The resulting correlation matrix encoded as a string.
288
+ """
289
+ fig_cor, axes_cor = plt.subplots()
290
+
291
+ cmap = plt.get_cmap(config.plot.correlation.cmap)
292
+ if vmin == 0:
293
+ cmap = get_cmap_half(cmap)
294
+ cmap = copy.copy(cmap)
295
+ cmap.set_bad(config.plot.correlation.bad)
296
+
297
+ labels = data.columns
298
+
299
+ try:
300
+ matrix = np.asarray(data, dtype=float)
301
+ except Exception:
302
+ # If conversion fails, create an all-NaN matrix of the appropriate shape
303
+ n = len(data)
304
+ matrix = np.full((n, n), np.nan, dtype=float)
305
+
306
+ matrix_image = axes_cor.imshow(
307
+ matrix, vmin=vmin, vmax=1, interpolation="nearest", cmap=cmap
308
+ )
309
+ plt.colorbar(matrix_image)
310
+
311
+ if data.isnull().values.any():
312
+ legend_elements = [Patch(facecolor=cmap(np.nan), label="invalid\ncoefficient")]
313
+
314
+ plt.legend(
315
+ handles=legend_elements,
316
+ loc="upper right",
317
+ handleheight=2.5,
318
+ )
319
+
320
+ axes_cor.set_xticks(np.arange(0, data.shape[0], float(data.shape[0]) / len(labels)))
321
+ axes_cor.set_yticks(np.arange(0, data.shape[1], float(data.shape[1]) / len(labels)))
322
+
323
+ font_size = get_correlation_font_size(len(labels))
324
+ axes_cor.set_xticklabels(labels, rotation=90, fontsize=font_size)
325
+ axes_cor.set_yticklabels(labels, fontsize=font_size)
326
+ plt.subplots_adjust(bottom=0.2)
327
+
328
+ return plot_360_n0sc0pe(config)
329
+
330
+
331
+ @manage_matplotlib_context()
332
+ def scatter_complex(config: Settings, series: pd.Series) -> str:
333
+ """Scatter plot (or hexbin plot) from a series of complex values
334
+
335
+ Examples:
336
+ >>> complex_series = pd.Series([complex(1, 3), complex(3, 1)])
337
+ >>> scatter_complex(complex_series)
338
+
339
+ Args:
340
+ config: Settings
341
+ series: the Series
342
+
343
+ Returns:
344
+ A string containing (a reference to) the image
345
+ """
346
+ plt.ylabel("Imaginary")
347
+ plt.xlabel("Real")
348
+
349
+ color = config.html.style.primary_colors[0]
350
+
351
+ if len(series) > config.plot.scatter_threshold:
352
+ cmap = sns.light_palette(color, as_cmap=True)
353
+ plt.hexbin(series.real, series.imag, cmap=cmap)
354
+ else:
355
+ plt.scatter(series.real, series.imag, color=color)
356
+
357
+ return plot_360_n0sc0pe(config)
358
+
359
+
360
+ @manage_matplotlib_context()
361
+ def scatter_series(
362
+ config: Settings, series: pd.Series, x_label: str = "Width", y_label: str = "Height"
363
+ ) -> str:
364
+ """Scatter plot (or hexbin plot) from one series of sequences with length 2
365
+
366
+ Examples:
367
+ >>> scatter_series(file_sizes, "Width", "Height")
368
+
369
+ Args:
370
+ config: report Settings object
371
+ series: the Series
372
+ x_label: the label on the x-axis
373
+ y_label: the label on the y-axis
374
+
375
+ Returns:
376
+ A string containing (a reference to) the image
377
+ """
378
+ plt.xlabel(x_label)
379
+ plt.ylabel(y_label)
380
+
381
+ color = config.html.style.primary_colors[0]
382
+
383
+ data = zip(*series.tolist())
384
+ if len(series) > config.plot.scatter_threshold:
385
+ cmap = sns.light_palette(color, as_cmap=True)
386
+ plt.hexbin(*data, cmap=cmap)
387
+ else:
388
+ plt.scatter(*data, color=color)
389
+ return plot_360_n0sc0pe(config)
390
+
391
+
392
+ @manage_matplotlib_context()
393
+ def scatter_pairwise(
394
+ config: Settings, series1: pd.Series, series2: pd.Series, x_label: str, y_label: str
395
+ ) -> str:
396
+ """Scatter plot (or hexbin plot) from two series
397
+
398
+ Examples:
399
+ >>> widths = pd.Series([800, 1024])
400
+ >>> heights = pd.Series([600, 768])
401
+ >>> scatter_series(widths, heights, "Width", "Height")
402
+
403
+ Args:
404
+ config: Settings
405
+ series1: the series corresponding to the x-axis
406
+ series2: the series corresponding to the y-axis
407
+ x_label: the label on the x-axis
408
+ y_label: the label on the y-axis
409
+
410
+ Returns:
411
+ A string containing (a reference to) the image
412
+ """
413
+ plt.xlabel(x_label)
414
+ plt.ylabel(y_label)
415
+
416
+ color = config.html.style.primary_colors[0]
417
+
418
+ indices = (series1.notna()) & (series2.notna())
419
+ if len(series1) > config.plot.scatter_threshold:
420
+ cmap = sns.light_palette(color, as_cmap=True)
421
+ plt.hexbin(series1[indices], series2[indices], gridsize=15, cmap=cmap)
422
+ else:
423
+ plt.scatter(series1[indices], series2[indices], color=color)
424
+ return plot_360_n0sc0pe(config)
425
+
426
+
427
+ def _plot_stacked_barh(
428
+ data: pd.Series, colors: List, hide_legend: bool = False
429
+ ) -> Tuple[plt.Axes, matplotlib.legend.Legend]:
430
+ """Plot a stacked horizontal bar chart to show category frequency.
431
+ Works for boolean and categorical features.
432
+
433
+ Args:
434
+ data (pd.Series): category frequencies with category names as index
435
+ colors (list): list of colors in a valid matplotlib format
436
+ hide_legend (bool): if true, the legend is omitted
437
+
438
+ Returns:
439
+ ax: Stacked bar plot (matplotlib.axes)
440
+ legend: Legend handler (matplotlib)
441
+ """
442
+ # Use the pd.Series indices as category names
443
+ labels = data.index.values.astype(str)
444
+
445
+ # Plot
446
+ _, ax = plt.subplots(figsize=(7, 2))
447
+ ax.axis("off")
448
+
449
+ ax.set_xlim(0, np.sum(data))
450
+ ax.set_ylim(0.4, 1.6)
451
+
452
+ starts = 0
453
+ for x, label, color in zip(data, labels, colors):
454
+ # Add a rectangle to the stacked barh chart
455
+ rects = ax.barh(y=1, width=x, height=1, left=starts, label=label, color=color)
456
+
457
+ # Label color depends on the darkness of the rectangle
458
+ r, g, b, _ = rects[0].get_facecolor()
459
+ text_color = "white" if r * g * b < 0.5 else "darkgrey"
460
+
461
+ # If the new bar is big enough write the label
462
+ pc_of_total = x / data.sum() * 100
463
+ # Requires matplotlib >= 3.4.0
464
+ if pc_of_total > 8 and hasattr(ax, "bar_label"):
465
+ display_txt = f"{pc_of_total:.1f}%\n({x})"
466
+ ax.bar_label(
467
+ rects,
468
+ labels=[display_txt],
469
+ label_type="center",
470
+ color=text_color,
471
+ fontsize="x-large",
472
+ fontweight="bold",
473
+ )
474
+
475
+ starts += x
476
+
477
+ legend = None
478
+ if not hide_legend:
479
+ legend = ax.legend(
480
+ ncol=1, bbox_to_anchor=(0, 0), fontsize="xx-large", loc="upper left"
481
+ )
482
+
483
+ return ax, legend
484
+
485
+
486
+ def _plot_pie_chart(
487
+ data: pd.Series, colors: List, hide_legend: bool = False
488
+ ) -> Tuple[plt.Axes, matplotlib.legend.Legend]:
489
+ """Plot a pie chart to show category frequency.
490
+ Works for boolean and categorical features.
491
+
492
+ Args:
493
+ data (pd.Series): category frequencies with category names as index
494
+ colors (list): list of colors in a valid matplotlib format
495
+ hide_legend (bool): if true, the legend is omitted
496
+
497
+ Returns:
498
+ ax: pie chart (matplotlib.axes)
499
+ legend: Legend handler (matplotlib)
500
+ """
501
+
502
+ def make_autopct(values: pd.Series) -> Callable:
503
+ def my_autopct(pct: float) -> str:
504
+ total = np.sum(values)
505
+ val = int(round(pct * total / 100.0))
506
+ return f"{pct:.1f}% ({val:d})"
507
+
508
+ return my_autopct
509
+
510
+ _, ax = plt.subplots(figsize=(4, 4))
511
+ wedges, _, _ = plt.pie(
512
+ data,
513
+ autopct=make_autopct(data),
514
+ textprops={"color": "w"},
515
+ colors=colors,
516
+ )
517
+
518
+ legend = None
519
+ if not hide_legend:
520
+ legend = plt.legend(
521
+ wedges,
522
+ data.index.values,
523
+ fontsize="large",
524
+ bbox_to_anchor=(0, 0),
525
+ loc="upper left",
526
+ )
527
+
528
+ return ax, legend
529
+
530
+
531
+ @manage_matplotlib_context()
532
+ def cat_frequency_plot(
533
+ config: Settings,
534
+ data: pd.Series,
535
+ ) -> str:
536
+ """Generate category frequency plot to show category frequency.
537
+ Works for boolean and categorical features.
538
+
539
+ Modify colors by setting 'config.plot.cat_freq.colors' to a
540
+ list of valid matplotib colors:
541
+ https://matplotlib.org/stable/tutorials/colors/colors.html
542
+
543
+ Args:
544
+ config (Settings): a profile report config
545
+ data (pd.Series): category frequencies with category names as index
546
+
547
+ Returns:
548
+ str: encoded category frequency plot encoded
549
+ """
550
+ # Get colors, if not defined, use matplotlib defaults
551
+ colors = config.plot.cat_freq.colors
552
+ if colors is None:
553
+ # Get matplotlib defaults
554
+ colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
555
+
556
+ # If there are more categories than colors, loop through the colors again
557
+ if len(colors) < len(data):
558
+ multiplier = int(len(data) / len(colors)) + 1
559
+ colors = multiplier * colors # repeat colors as required
560
+ colors = colors[0 : len(data)] # select the exact number of colors required
561
+
562
+ # Create the plot
563
+ plot_type = config.plot.cat_freq.type
564
+ if plot_type == "bar":
565
+ if isinstance(data, list):
566
+ for v in data:
567
+ plot, legend = _plot_stacked_barh(
568
+ v, colors, hide_legend=config.vars.cat.redact
569
+ )
570
+ else:
571
+ plot, legend = _plot_stacked_barh(
572
+ data, colors, hide_legend=config.vars.cat.redact
573
+ )
574
+
575
+ elif plot_type == "pie":
576
+ plot, legend = _plot_pie_chart(data, colors, hide_legend=config.vars.cat.redact)
577
+
578
+ else:
579
+ msg = (
580
+ f"'{plot_type}' is not a valid plot type! "
581
+ "Expected values are ['bar', 'pie']"
582
+ )
583
+ msg
584
+ raise ValueError(msg)
585
+
586
+ return plot_360_n0sc0pe(
587
+ config,
588
+ bbox_extra_artists=[] if legend is None else [legend],
589
+ bbox_inches="tight",
590
+ )
591
+
592
+
593
+ def create_comparison_color_list(config: Settings) -> List[str]:
594
+ colors = config.html.style.primary_colors
595
+ labels = config.html.style._labels
596
+
597
+ if colors < labels:
598
+ init = colors[0]
599
+ end = colors[1] if len(colors) >= 2 else "#000000"
600
+ cmap = LinearSegmentedColormap.from_list("ts_leg", [init, end], len(labels))
601
+ colors = [rgb2hex(cmap(i)) for i in range(cmap.N)]
602
+ return colors
603
+
604
+
605
+ def _format_ts_date_axis(
606
+ series: pd.Series,
607
+ axis: matplotlib.axis.Axis,
608
+ ) -> matplotlib.axis.Axis:
609
+ if isinstance(series.index, pd.DatetimeIndex):
610
+ locator = AutoDateLocator()
611
+ axis.xaxis.set_major_locator(locator)
612
+ axis.xaxis.set_major_formatter(ConciseDateFormatter(locator))
613
+
614
+ return axis
615
+
616
+
617
+ @manage_matplotlib_context()
618
+ def plot_timeseries_gap_analysis(
619
+ config: Settings,
620
+ series: Union[pd.Series, List[pd.Series]],
621
+ gaps: Union[pd.Series, List[pd.Series]],
622
+ figsize: tuple = (6, 3),
623
+ ) -> matplotlib.figure.Figure:
624
+ """Plot an line plot from the data and return the AxesSubplot object.
625
+ Args:
626
+ variables: The data to plot.
627
+ figsize: The size of the figure (width, height) in inches, default (6,4).
628
+ Returns:
629
+ The TimeSeries lineplot.
630
+ """
631
+ fig = plt.figure(figsize=figsize)
632
+ ax = fig.add_subplot(111)
633
+
634
+ colors = create_comparison_color_list(config)
635
+ if isinstance(series, list):
636
+ min_ = min(s.min() for s in series)
637
+ max_ = max(s.max() for s in series)
638
+ labels = config.html.style._labels
639
+ for serie, gaps_, color, label in zip(series, gaps, colors, labels):
640
+ serie.plot(
641
+ ax=ax,
642
+ label=label,
643
+ color=color,
644
+ alpha=0.65,
645
+ x_compat=True,
646
+ )
647
+ _format_ts_date_axis(serie, ax)
648
+ ax.yaxis.set_major_locator(MaxNLocator(integer=True))
649
+ for gap in gaps_:
650
+ ax.fill_between(x=gap, y1=min_, y2=max_, color=color, alpha=0.25)
651
+ else:
652
+ series.plot(ax=ax, x_compat=True)
653
+ _format_ts_date_axis(series, ax)
654
+ ax.yaxis.set_major_locator(MaxNLocator(integer=True))
655
+
656
+ for gap in gaps:
657
+ ax.fill_between(
658
+ x=gap, y1=series.min(), y2=series.max(), color=colors[0], alpha=0.25
659
+ )
660
+
661
+ return plot_360_n0sc0pe(config)
662
+
663
+
664
+ @manage_matplotlib_context()
665
+ def plot_overview_timeseries(
666
+ config: Settings,
667
+ variables: Any,
668
+ figsize: tuple = (6, 4),
669
+ scale: bool = False,
670
+ ) -> matplotlib.figure.Figure:
671
+ """Plot an line plot from the data and return the AxesSubplot object.
672
+ Args:
673
+ variables: The data to plot.
674
+ figsize: The size of the figure (width, height) in inches, default (6,4).
675
+ scale: Scale series values between [0,1]. Defaults to False.
676
+ Returns:
677
+ The TimeSeries lineplot.
678
+ """
679
+ fig = plt.figure(figsize=figsize)
680
+ ax = fig.add_subplot(111)
681
+
682
+ col = next(iter(variables))
683
+ if isinstance(variables[col]["type"], list):
684
+ colors = create_comparison_color_list(config)
685
+ line_styles = ["-", "--"]
686
+ for col, data in variables.items():
687
+ if all(iter([t == "TimeSeries" for t in data["type"]])):
688
+ for i, series in enumerate(data["series"]):
689
+ if scale:
690
+ series = (series - series.min()) / (series.max() - series.min())
691
+ series.plot(
692
+ ax=ax,
693
+ label=col,
694
+ linestyle=line_styles[i],
695
+ color=colors[i],
696
+ alpha=0.65,
697
+ )
698
+ else:
699
+ for col, data in variables.items():
700
+ if data["type"] == "TimeSeries":
701
+ series = data["series"]
702
+ if scale:
703
+ series = (series - series.min()) / (series.max() - series.min())
704
+ series.plot(ax=ax, label=col, alpha=0.65)
705
+
706
+ plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
707
+ plt.subplots_adjust(right=0.7)
708
+ return plot_360_n0sc0pe(config)
709
+
710
+
711
+ def _plot_timeseries(
712
+ config: Settings,
713
+ series: Union[list, pd.Series],
714
+ figsize: tuple = (6, 4),
715
+ ) -> matplotlib.figure.Figure:
716
+ """Plot an line plot from the data and return the AxesSubplot object.
717
+ Args:
718
+ series: The data to plot
719
+ figsize: The size of the figure (width, height) in inches, default (6,4)
720
+ Returns:
721
+ The TimeSeries lineplot.
722
+ """
723
+ fig = plt.figure(figsize=figsize)
724
+ plot = fig.add_subplot(111)
725
+
726
+ if isinstance(series, list):
727
+ labels = config.html.style._labels
728
+ colors = create_comparison_color_list(config)
729
+
730
+ for serie, color, label in zip(series, colors, labels):
731
+ ax = serie.plot(color=color, label=label, alpha=0.75, x_compat=True)
732
+ _format_ts_date_axis(serie, ax)
733
+
734
+ else:
735
+ ax = series.plot(color=config.html.style.primary_colors[0], x_compat=True)
736
+ _format_ts_date_axis(series, ax)
737
+
738
+ return plot
739
+
740
+
741
+ @manage_matplotlib_context()
742
+ def mini_ts_plot(
743
+ config: Settings,
744
+ series: Union[list, pd.Series],
745
+ figsize: Tuple[float, float] = (3, 2.25),
746
+ ) -> str:
747
+ """Plot an time-series plot of the data.
748
+ Args:
749
+ config: profiling settings.
750
+ series: The data to plot.
751
+ figsize: The size of the figure (width, height) in inches, default (3, 2.25)
752
+ Returns:
753
+ The resulting timeseries plot encoded as a string.
754
+ """
755
+ plot = _plot_timeseries(config, series, figsize=figsize)
756
+ plot.xaxis.set_tick_params(rotation=45)
757
+ plt.rc("ytick", labelsize=3)
758
+
759
+ for tick in plot.xaxis.get_major_ticks():
760
+ if isinstance(series.index, pd.DatetimeIndex):
761
+ tick.label1.set_fontsize(6)
762
+ else:
763
+ tick.label1.set_fontsize(8)
764
+ plot.figure.tight_layout()
765
+ return plot_360_n0sc0pe(config)
766
+
767
+
768
+ def _get_ts_lag(config: Settings, series: pd.Series) -> int:
769
+ lag = config.vars.timeseries.pacf_acf_lag
770
+ max_lag_size = (len(series) // 2) - 1
771
+ return np.min([lag, max_lag_size])
772
+
773
+
774
+ def _plot_acf_pacf(
775
+ config: Settings, series: pd.Series, figsize: tuple = (15, 5)
776
+ ) -> str:
777
+ color = config.html.style.primary_colors[0]
778
+
779
+ lag = _get_ts_lag(config, series)
780
+ _, axes = plt.subplots(nrows=1, ncols=2, figsize=figsize)
781
+
782
+ plot_acf(
783
+ series.dropna(),
784
+ lags=lag,
785
+ ax=axes[0],
786
+ title="ACF",
787
+ fft=True,
788
+ color=color,
789
+ vlines_kwargs={"colors": color},
790
+ )
791
+ plot_pacf(
792
+ series.dropna(),
793
+ lags=lag,
794
+ ax=axes[1],
795
+ title="PACF",
796
+ method="ywm",
797
+ color=color,
798
+ vlines_kwargs={"colors": color},
799
+ )
800
+
801
+ for ax in axes:
802
+ for item in ax.collections:
803
+ if type(item) is PolyCollection:
804
+ item.set_facecolor(color)
805
+
806
+ return plot_360_n0sc0pe(config)
807
+
808
+
809
+ def _plot_acf_pacf_comparison(
810
+ config: Settings, series: List[pd.Series], figsize: tuple = (15, 5)
811
+ ) -> str:
812
+ colors = config.html.style.primary_colors
813
+ n_labels = len(config.html.style._labels)
814
+ colors = create_comparison_color_list(config)
815
+
816
+ _, axes = plt.subplots(nrows=n_labels, ncols=2, figsize=figsize)
817
+ is_first = True
818
+ for serie, (acf_axis, pacf_axis), color in zip(series, axes, colors):
819
+ lag = _get_ts_lag(config, serie)
820
+
821
+ plot_acf(
822
+ serie.dropna(),
823
+ lags=lag,
824
+ ax=acf_axis,
825
+ title="ACF" if is_first else "",
826
+ fft=True,
827
+ color=color,
828
+ vlines_kwargs={"colors": color},
829
+ )
830
+ plot_pacf(
831
+ serie.dropna(),
832
+ lags=lag,
833
+ ax=pacf_axis,
834
+ title="PACF" if is_first else "",
835
+ method="ywm",
836
+ color=color,
837
+ vlines_kwargs={"colors": color},
838
+ )
839
+ is_first = False
840
+
841
+ for row, color in zip(axes, colors):
842
+ for ax in row:
843
+ for item in ax.collections:
844
+ if isinstance(item, PolyCollection):
845
+ item.set_facecolor(color)
846
+
847
+ return plot_360_n0sc0pe(config)
848
+
849
+
850
+ @manage_matplotlib_context()
851
+ def plot_acf_pacf(
852
+ config: Settings, series: Union[list, pd.Series], figsize: tuple = (15, 5)
853
+ ) -> str:
854
+ if isinstance(series, list):
855
+ return _plot_acf_pacf_comparison(config, series, figsize)
856
+ else:
857
+ return _plot_acf_pacf(config, series, figsize)
858
+
859
+
860
+ def _prepare_heatmap_data(
861
+ dataframe: pd.DataFrame,
862
+ entity_column: str,
863
+ sortby: Optional[Union[str, list]] = None,
864
+ max_entities: int = 5,
865
+ selected_entities: Optional[List[str]] = None,
866
+ ) -> pd.DataFrame:
867
+ if sortby is None:
868
+ sortbykey = "_index"
869
+ df = dataframe[entity_column].copy().reset_index()
870
+ df.columns = [sortbykey, entity_column]
871
+
872
+ else:
873
+ if isinstance(sortby, str):
874
+ sortby = [sortby]
875
+ cols = [entity_column, *sortby]
876
+ df = dataframe[cols].copy()
877
+ sortbykey = sortby[0]
878
+
879
+ if df[sortbykey].dtype == "O":
880
+ try:
881
+ df[sortbykey] = pd.to_datetime(df[sortbykey])
882
+ except Exception as ex:
883
+ raise ValueError(
884
+ f"column {sortbykey} dtype {df[sortbykey].dtype} is not supported."
885
+ ) from ex
886
+ nbins = np.min([50, df[sortbykey].nunique()])
887
+
888
+ df["__bins"] = pd.cut(
889
+ df[sortbykey], bins=nbins, include_lowest=True, labels=range(nbins)
890
+ )
891
+
892
+ df = df.groupby([entity_column, "__bins"])[sortbykey].count()
893
+ df = (
894
+ df.reset_index()
895
+ .pivot_table(values=sortbykey, index="__bins", columns=entity_column)
896
+ .T
897
+ )
898
+
899
+ if selected_entities:
900
+ df = df[selected_entities]
901
+ else:
902
+ df = df[:max_entities]
903
+
904
+ return df
905
+
906
+
907
+ def _create_timeseries_heatmap(
908
+ df: pd.DataFrame,
909
+ figsize: Tuple[int, int] = (12, 5),
910
+ color: str = "#337ab7",
911
+ ) -> plt.Axes:
912
+ _, ax = plt.subplots(figsize=figsize)
913
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
914
+ "report", ["white", color], N=64
915
+ )
916
+ pc = ax.pcolormesh(df, edgecolors=ax.get_facecolor(), linewidth=0.25, cmap=cmap)
917
+ pc.set_clim(0, np.nanmax(df))
918
+ ax.set_yticks([x + 0.5 for x in range(len(df))])
919
+ ax.set_yticklabels(df.index)
920
+ ax.set_xticks([])
921
+ ax.set_xlabel("Time")
922
+ ax.invert_yaxis()
923
+ return ax
924
+
925
+
926
+ @typechecked
927
+ def timeseries_heatmap(
928
+ dataframe: pd.DataFrame,
929
+ entity_column: str,
930
+ sortby: Optional[Union[str, list]] = None,
931
+ max_entities: int = 5,
932
+ selected_entities: Optional[List[str]] = None,
933
+ figsize: Tuple[int, int] = (12, 5),
934
+ color: str = "#337ab7",
935
+ ) -> plt.Axes:
936
+ """Generate a multi entity timeseries heatmap based on a pandas DataFrame.
937
+
938
+ Args:
939
+ dataframe: the pandas DataFrame
940
+ entity_column: name of the entities column
941
+ sortby: column that define the timesteps (only dates and numerical variables are supported)
942
+ max_entities: max entities that will be displayed
943
+ selected_entities: Optional list of entities to be displayed (overules max_entities)
944
+ figsize: The size of the figure (width, height) in inches, default (10,5)
945
+ color: the primary color, default '#337ab7'
946
+ Returns:
947
+ The TimeSeries heatmap.
948
+ """
949
+ df = _prepare_heatmap_data(
950
+ dataframe, entity_column, sortby, max_entities, selected_entities
951
+ )
952
+ ax = _create_timeseries_heatmap(df, figsize, color)
953
+ ax.set_aspect(1)
954
+ return ax
955
+
956
+
957
+ def _set_visibility(
958
+ axis: matplotlib.axis.Axis, tick_mark: str = "none"
959
+ ) -> matplotlib.axis.Axis:
960
+ for anchor in ["top", "right", "bottom", "left"]:
961
+ axis.spines[anchor].set_visible(False)
962
+ axis.xaxis.set_ticks_position(tick_mark)
963
+ axis.yaxis.set_ticks_position(tick_mark)
964
+ return axis
965
+
966
+
967
+ def missing_bar(
968
+ notnull_counts: pd.Series,
969
+ nrows: int,
970
+ figsize: Tuple[float, float] = (25, 10),
971
+ fontsize: float = 16,
972
+ labels: bool = True,
973
+ color: Tuple[float, ...] = (0.41, 0.41, 0.41),
974
+ label_rotation: int = 45,
975
+ ) -> matplotlib.axis.Axis:
976
+ """
977
+ A bar chart visualization of the missing data.
978
+
979
+ Inspired by https://github.com/ResidentMario/missingno
980
+
981
+ Args:
982
+ notnull_counts: Number of nonnull values per column.
983
+ nrows: Number of rows in the dataframe.
984
+ figsize: The size of the figure to display.
985
+ fontsize: The figure's font size. This default to 16.
986
+ labels: Whether or not to display the column names. Would need to be turned off on particularly large
987
+ displays. Defaults to True.
988
+ color: The color of the filled columns. Default to the RGB multiple `(0.25, 0.25, 0.25)`.
989
+ label_rotation: What angle to rotate the text labels to. Defaults to 45 degrees.
990
+ Returns:
991
+ The plot axis.
992
+ """
993
+ percentage = notnull_counts / nrows
994
+
995
+ if len(notnull_counts) <= 50:
996
+ ax0 = percentage.plot.bar(figsize=figsize, fontsize=fontsize, color=color)
997
+ ax0.set_xticklabels(
998
+ ax0.get_xticklabels(),
999
+ ha="right",
1000
+ fontsize=fontsize,
1001
+ rotation=label_rotation,
1002
+ )
1003
+
1004
+ ax1 = ax0.twiny()
1005
+ ax1.set_xticks(ax0.get_xticks())
1006
+ ax1.set_xlim(ax0.get_xlim())
1007
+ ax1.set_xticklabels(
1008
+ notnull_counts, ha="left", fontsize=fontsize, rotation=label_rotation
1009
+ )
1010
+ else:
1011
+ ax0 = percentage.plot.barh(figsize=figsize, fontsize=fontsize, color=color)
1012
+ ylabels = ax0.get_yticklabels() if labels else []
1013
+ ax0.set_yticklabels(ylabels, fontsize=fontsize)
1014
+
1015
+ ax1 = ax0.twinx()
1016
+ ax1.set_yticks(ax0.get_yticks())
1017
+ ax1.set_ylim(ax0.get_ylim())
1018
+ ax1.set_yticklabels(notnull_counts, fontsize=fontsize)
1019
+
1020
+ for ax in [ax0, ax1]:
1021
+ ax = _set_visibility(ax)
1022
+
1023
+ return ax0
1024
+
1025
+
1026
+ def missing_matrix(
1027
+ notnull: Any,
1028
+ columns: List[str],
1029
+ height: int,
1030
+ figsize: Tuple[float, float] = (25, 10),
1031
+ color: Tuple[float, ...] = (0.41, 0.41, 0.41),
1032
+ fontsize: float = 16,
1033
+ labels: bool = True,
1034
+ label_rotation: int = 45,
1035
+ ) -> matplotlib.axis.Axis:
1036
+ """
1037
+ A matrix visualization of missing data.
1038
+
1039
+ Inspired by https://github.com/ResidentMario/missingno
1040
+
1041
+ Args:
1042
+ notnull: Missing data indicator matrix.
1043
+ columns: List of column names.
1044
+ height: Number of rows in the dataframe.
1045
+ figsize: The size of the figure to display.
1046
+ fontsize: The figure's font size. Default to 16.
1047
+ labels: Whether or not to display the column names when there is more than 50 columns.
1048
+ label_rotation: What angle to rotate the text labels to. Defaults to 45 degrees.
1049
+ color: The color of the filled columns. Default is `(0.41, 0.41, 0.41)`.
1050
+ Returns:
1051
+ The plot axis.
1052
+ """
1053
+ width = len(columns)
1054
+ missing_grid = np.zeros((height, width, 3), dtype=np.float32)
1055
+
1056
+ missing_grid[notnull] = color
1057
+ missing_grid[~notnull] = [1, 1, 1]
1058
+
1059
+ _, ax = plt.subplots(1, 1, figsize=figsize)
1060
+
1061
+ # Create the missing matrix plot.
1062
+ ax.imshow(missing_grid, interpolation="none")
1063
+ ax.set_aspect("auto")
1064
+ ax.grid(False)
1065
+ ax.xaxis.tick_top()
1066
+
1067
+ ha = "left"
1068
+ ax.set_xticks(list(range(0, width)))
1069
+ ax.set_xticklabels(columns, rotation=label_rotation, ha=ha, fontsize=fontsize)
1070
+ ax.set_yticks([0, height - 1])
1071
+ ax.set_yticklabels([1, height], fontsize=fontsize)
1072
+
1073
+ separators = [x + 0.5 for x in range(0, width - 1)]
1074
+ for point in separators:
1075
+ ax.axvline(point, linestyle="-", color="white")
1076
+
1077
+ if not labels and width > 50:
1078
+ ax.set_xticklabels([])
1079
+
1080
+ ax = _set_visibility(ax)
1081
+ return ax
1082
+
1083
+
1084
+ def missing_heatmap(
1085
+ corr_mat: Any,
1086
+ mask: Any,
1087
+ figsize: Tuple[float, float] = (20, 12),
1088
+ fontsize: float = 16,
1089
+ labels: bool = True,
1090
+ label_rotation: int = 45,
1091
+ cmap: str = "RdBu",
1092
+ normalized_cmap: bool = True,
1093
+ cbar: bool = True,
1094
+ ax: matplotlib.axis.Axis = None,
1095
+ ) -> matplotlib.axis.Axis:
1096
+ """
1097
+ Presents a `seaborn` heatmap visualization of missing data correlation.
1098
+ Note that this visualization has no special support for large datasets.
1099
+
1100
+ Inspired by https://github.com/ResidentMario/missingno
1101
+
1102
+ Args:
1103
+ corr_mat: correlation matrix.
1104
+ mask: Upper-triangle mask.
1105
+ figsize: The size of the figure to display. Defaults to (20, 12).
1106
+ fontsize: The figure's font size.
1107
+ labels: Whether or not to label each matrix entry with its correlation (default is True).
1108
+ label_rotation: What angle to rotate the text labels to. Defaults to 45 degrees.
1109
+ cmap: Which colormap to use. Defaults to `RdBu`.
1110
+ normalized_cmap: Use a normalized colormap threshold or not. Defaults to True
1111
+ Returns:
1112
+ The plot axis.
1113
+ """
1114
+ _, ax = plt.subplots(1, 1, figsize=figsize)
1115
+ norm_args = {"vmin": -1, "vmax": 1} if normalized_cmap else {}
1116
+
1117
+ if labels:
1118
+ sns.heatmap(
1119
+ corr_mat,
1120
+ mask=mask,
1121
+ cmap=cmap,
1122
+ ax=ax,
1123
+ cbar=cbar,
1124
+ annot=True,
1125
+ annot_kws={"size": fontsize - 2},
1126
+ **norm_args,
1127
+ )
1128
+ else:
1129
+ sns.heatmap(corr_mat, mask=mask, cmap=cmap, ax=ax, cbar=cbar, **norm_args)
1130
+
1131
+ # Apply visual corrections and modifications.
1132
+ ax.xaxis.tick_bottom()
1133
+ ax.set_xticklabels(
1134
+ ax.xaxis.get_majorticklabels(),
1135
+ rotation=label_rotation,
1136
+ ha="right",
1137
+ fontsize=fontsize,
1138
+ )
1139
+ ax.set_yticklabels(ax.yaxis.get_majorticklabels(), rotation=0, fontsize=fontsize)
1140
+ ax = _set_visibility(ax)
1141
+ ax.patch.set_visible(False)
1142
+
1143
+ for text in ax.texts:
1144
+ t = float(text.get_text())
1145
+ if 0.95 <= t < 1:
1146
+ text.set_text("<1")
1147
+ elif -1 < t <= -0.95:
1148
+ text.set_text(">-1")
1149
+ elif t == 1:
1150
+ text.set_text("1")
1151
+ elif t == -1:
1152
+ text.set_text("-1")
1153
+ elif -0.05 < t < 0.05:
1154
+ text.set_text("")
1155
+ else:
1156
+ text.set_text(round(t, 1))
1157
+
1158
+ return ax