We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
High correlation warning printed multiple times
I get the same warning "High correlation" with the same other column four times in the report.

Looks like a bug where the warning is accidentally generated multiple times or not de-duplicated properly.

Is it easy to spot the issue or reproduce? Or should I try to extract a standalone test case?

This is with pandas 1.3.0 and pandas-profiling 3.0.0.

<img width="572" alt="Screenshot 2021-09-05 at 18 54 44" src="https://user-images.githubusercontent.com/852409/132135015-45c0a273-763a-430e-b12f-d340e79b3ea7.png">


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pandas_profiling/model/alerts.py`
Content:
```
1 """Logic for alerting the user on possibly problematic patterns in the data (e.g. high number of zeros , constant
2 values, high correlations)."""
3 from enum import Enum, auto, unique
4 from typing import Any, Dict, List, Optional, Set
5 
6 import numpy as np
7 import pandas as pd
8 
9 from pandas_profiling.config import Settings
10 from pandas_profiling.model.correlations import perform_check_correlation
11 
12 
13 @unique
14 class AlertType(Enum):
15     """Alert types"""
16 
17     CONSTANT = auto()
18     """This variable has a constant value."""
19 
20     ZEROS = auto()
21     """This variable contains zeros."""
22 
23     HIGH_CORRELATION = auto()
24     """This variable is highly correlated."""
25 
26     HIGH_CARDINALITY = auto()
27     """This variable has a high cardinality."""
28 
29     UNSUPPORTED = auto()
30     """This variable is unsupported."""
31 
32     DUPLICATES = auto()
33     """This variable contains duplicates."""
34 
35     SKEWED = auto()
36     """This variable is highly skewed."""
37 
38     MISSING = auto()
39     """This variable contains missing values."""
40 
41     INFINITE = auto()
42     """This variable contains infinite values."""
43 
44     TYPE_DATE = auto()
45     """This variable is likely a datetime, but treated as categorical."""
46 
47     UNIQUE = auto()
48     """This variable has unique values."""
49 
50     CONSTANT_LENGTH = auto()
51     """This variable has a constant length"""
52 
53     REJECTED = auto()
54     """Variables are rejected if we do not want to consider them for further analysis."""
55 
56     UNIFORM = auto()
57     """The variable is uniformly distributed"""
58 
59     EMPTY = auto()
60     """The DataFrame is empty"""
61 
62 
63 class Alert:
64     """An alert object (type, values, column)."""
65 
66     _anchor_id: Optional[str] = None
67 
68     def __init__(
69         self,
70         alert_type: AlertType,
71         values: Optional[Dict] = None,
72         column_name: Optional[str] = None,
73         fields: Optional[Set] = None,
74     ):
75         if values is None:
76             values = {}
77         if fields is None:
78             fields = set()
79 
80         self.fields = fields
81         self.alert_type = alert_type
82         self.values = values
83         self.column_name = column_name
84 
85     @property
86     def anchor_id(self) -> Optional[str]:
87         if self._anchor_id is None:
88             self._anchor_id = str(hash(self.column_name))
89         return self._anchor_id
90 
91     def fmt(self) -> str:
92         # TODO: render in template
93         name = self.alert_type.name.replace("_", " ")
94         if name == "HIGH CORRELATION":
95             num = len(self.values["fields"])
96             title = ", ".join(self.values["fields"])
97             name = f'<abbr title="This variable has a high correlation with {num} fields: {title}">HIGH CORRELATION</abbr>'
98         return name
99 
100     def __repr__(self):
101         alert_type = self.alert_type.name
102         column = self.column_name
103         return f"[{alert_type}] alert on column {column}"
104 
105 
106 def check_table_alerts(table: dict) -> List[Alert]:
107     """Checks the overall dataset for alerts.
108 
109     Args:
110         table: Overall dataset statistics.
111 
112     Returns:
113         A list of alerts.
114     """
115     alerts = []
116     if alert_value(table.get("n_duplicates", np.nan)):
117         alerts.append(
118             Alert(
119                 alert_type=AlertType.DUPLICATES,
120                 values=table,
121                 fields={"n_duplicates"},
122             )
123         )
124     if table["n"] == 0:
125         alerts.append(
126             Alert(
127                 alert_type=AlertType.EMPTY,
128                 values=table,
129                 fields={"n"},
130             )
131         )
132     return alerts
133 
134 
135 def numeric_alerts(config: Settings, summary: dict) -> List[Alert]:
136     alerts = []
137 
138     # Skewness
139     if skewness_alert(summary["skewness"], config.vars.num.skewness_threshold):
140         alerts.append(
141             Alert(
142                 alert_type=AlertType.SKEWED,
143                 fields={"skewness"},
144             )
145         )
146 
147     # Infinite values
148     if alert_value(summary["p_infinite"]):
149         alerts.append(
150             Alert(
151                 alert_type=AlertType.INFINITE,
152                 fields={"p_infinite", "n_infinite"},
153             )
154         )
155 
156     # Zeros
157     if alert_value(summary["p_zeros"]):
158         alerts.append(
159             Alert(
160                 alert_type=AlertType.ZEROS,
161                 fields={"n_zeros", "p_zeros"},
162             )
163         )
164 
165     if (
166         "chi_squared" in summary
167         and summary["chi_squared"]["pvalue"] > config.vars.num.chi_squared_threshold
168     ):
169         alerts.append(Alert(alert_type=AlertType.UNIFORM))
170 
171     return alerts
172 
173 
174 def categorical_alerts(config: Settings, summary: dict) -> List[Alert]:
175     alerts = []
176 
177     # High cardinality
178     if summary.get("n_distinct", np.nan) > config.vars.cat.cardinality_threshold:
179         alerts.append(
180             Alert(
181                 alert_type=AlertType.HIGH_CARDINALITY,
182                 fields={"n_distinct"},
183             )
184         )
185 
186     if (
187         "chi_squared" in summary
188         and summary["chi_squared"]["pvalue"] > config.vars.cat.chi_squared_threshold
189     ):
190         alerts.append(Alert(alert_type=AlertType.UNIFORM))
191 
192     if summary.get("date_warning"):
193         alerts.append(Alert(alert_type=AlertType.TYPE_DATE))
194 
195     # Constant length
196     if "composition" in summary and summary["min_length"] == summary["max_length"]:
197         alerts.append(
198             Alert(
199                 alert_type=AlertType.CONSTANT_LENGTH,
200                 fields={"composition_min_length", "composition_max_length"},
201             )
202         )
203 
204     return alerts
205 
206 
207 def generic_alerts(summary: dict) -> List[Alert]:
208     alerts = []
209 
210     # Missing
211     if alert_value(summary["p_missing"]):
212         alerts.append(
213             Alert(
214                 alert_type=AlertType.MISSING,
215                 fields={"p_missing", "n_missing"},
216             )
217         )
218 
219     return alerts
220 
221 
222 def supported_alerts(summary: dict) -> List[Alert]:
223     alerts = []
224 
225     if summary.get("n_distinct", np.nan) == summary["n"]:
226         alerts.append(
227             Alert(
228                 alert_type=AlertType.UNIQUE,
229                 fields={"n_distinct", "p_distinct", "n_unique", "p_unique"},
230             )
231         )
232     if summary.get("n_distinct", np.nan) == 1:
233         summary["mode"] = summary["value_counts_without_nan"].index[0]
234         alerts.append(
235             Alert(
236                 alert_type=AlertType.CONSTANT,
237                 fields={"n_distinct"},
238             )
239         )
240         alerts.append(
241             Alert(
242                 alert_type=AlertType.REJECTED,
243                 fields=set(),
244             )
245         )
246     return alerts
247 
248 
249 def unsupported_alerts(summary: Dict[str, Any]) -> List[Alert]:
250     alerts = [
251         Alert(
252             alert_type=AlertType.UNSUPPORTED,
253             fields=set(),
254         ),
255         Alert(
256             alert_type=AlertType.REJECTED,
257             fields=set(),
258         ),
259     ]
260     return alerts
261 
262 
263 def check_variable_alerts(config: Settings, col: str, description: dict) -> List[Alert]:
264     """Checks individual variables for alerts.
265 
266     Args:
267         col: The column name that is checked.
268         description: The series description.
269 
270     Returns:
271         A list of alerts.
272     """
273     alerts = []
274 
275     alerts += generic_alerts(description)
276 
277     if description["type"] == "Unsupported":
278         alerts += unsupported_alerts(description)
279     else:
280         alerts += supported_alerts(description)
281 
282         if description["type"] == "Categorical":
283             alerts += categorical_alerts(config, description)
284         if description["type"] == "Numeric":
285             alerts += numeric_alerts(config, description)
286 
287     for idx in range(len(alerts)):
288         alerts[idx].column_name = col
289         alerts[idx].values = description
290     return alerts
291 
292 
293 def check_correlation_alerts(config: Settings, correlations: dict) -> List[Alert]:
294     alerts = []
295 
296     for corr, matrix in correlations.items():
297         if config.correlations[corr].warn_high_correlations:
298             threshold = config.correlations[corr].threshold
299             correlated_mapping = perform_check_correlation(matrix, threshold)
300             if len(correlated_mapping) > 0:
301                 for k, v in correlated_mapping.items():
302                     alerts.append(
303                         Alert(
304                             column_name=k,
305                             alert_type=AlertType.HIGH_CORRELATION,
306                             values={"corr": corr, "fields": v},
307                         )
308                     )
309     return alerts
310 
311 
312 def get_alerts(
313     config: Settings, table_stats: dict, series_description: dict, correlations: dict
314 ) -> List[Alert]:
315     alerts = check_table_alerts(table_stats)
316     for col, description in series_description.items():
317         alerts += check_variable_alerts(config, col, description)
318     alerts += check_correlation_alerts(config, correlations)
319     alerts.sort(key=lambda alert: str(alert.alert_type))
320     return alerts
321 
322 
323 def alert_value(value: float) -> bool:
324     return not np.isnan(value) and value > 0.01
325 
326 
327 def skewness_alert(v: float, threshold: int) -> bool:
328     return not np.isnan(v) and (v < (-1 * threshold) or v > threshold)
329 
330 
331 def type_date_alert(series: pd.Series) -> bool:
332     from dateutil.parser import ParserError, parse
333 
334     try:
335         series.apply(parse)
336     except ParserError:
337         return False
338     else:
339         return True
340
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pandas_profiling/model/alerts.py b/src/pandas_profiling/model/alerts.py
--- a/src/pandas_profiling/model/alerts.py
+++ b/src/pandas_profiling/model/alerts.py
@@ -293,19 +293,24 @@
 def check_correlation_alerts(config: Settings, correlations: dict) -> List[Alert]:
     alerts = []
 
+    correlations_consolidated = {}
     for corr, matrix in correlations.items():
         if config.correlations[corr].warn_high_correlations:
             threshold = config.correlations[corr].threshold
             correlated_mapping = perform_check_correlation(matrix, threshold)
-            if len(correlated_mapping) > 0:
-                for k, v in correlated_mapping.items():
-                    alerts.append(
-                        Alert(
-                            column_name=k,
-                            alert_type=AlertType.HIGH_CORRELATION,
-                            values={"corr": corr, "fields": v},
-                        )
-                    )
+            for col, fields in correlated_mapping.items():
+                set(fields).update(set(correlated_mapping.get(col, [])))
+                correlations_consolidated[col] = fields
+
+    if len(correlations_consolidated) > 0:
+        for col, fields in correlations_consolidated.items():
+            alerts.append(
+                Alert(
+                    column_name=col,
+                    alert_type=AlertType.HIGH_CORRELATION,
+                    values={"corr": 'Overall', "fields": fields},
+                )
+            )
     return alerts

{"golden_diff": "diff --git a/src/pandas_profiling/model/alerts.py b/src/pandas_profiling/model/alerts.py\n--- a/src/pandas_profiling/model/alerts.py\n+++ b/src/pandas_profiling/model/alerts.py\n@@ -293,19 +293,24 @@\n def check_correlation_alerts(config: Settings, correlations: dict) -> List[Alert]:\n     alerts = []\n \n+    correlations_consolidated = {}\n     for corr, matrix in correlations.items():\n         if config.correlations[corr].warn_high_correlations:\n             threshold = config.correlations[corr].threshold\n             correlated_mapping = perform_check_correlation(matrix, threshold)\n-            if len(correlated_mapping) > 0:\n-                for k, v in correlated_mapping.items():\n-                    alerts.append(\n-                        Alert(\n-                            column_name=k,\n-                            alert_type=AlertType.HIGH_CORRELATION,\n-                            values={\"corr\": corr, \"fields\": v},\n-                        )\n-                    )\n+            for col, fields in correlated_mapping.items():\n+                set(fields).update(set(correlated_mapping.get(col, [])))\n+                correlations_consolidated[col] = fields\n+\n+    if len(correlations_consolidated) > 0:\n+        for col, fields in correlations_consolidated.items():\n+            alerts.append(\n+                Alert(\n+                    column_name=col,\n+                    alert_type=AlertType.HIGH_CORRELATION,\n+                    values={\"corr\": 'Overall', \"fields\": fields},\n+                )\n+            )\n     return alerts\n", "issue": "High correlation warning printed multiple times\nI get the same warning \"High correlation\" with the same other column four times in the report.\r\n\r\nLooks like a bug where the warning is accidentally generated multiple times or not de-duplicated properly.\r\n\r\nIs it easy to spot the issue or reproduce? Or should I try to extract a standalone test case?\r\n\r\nThis is with pandas 1.3.0 and pandas-profiling 3.0.0.\r\n\r\n<img width=\"572\" alt=\"Screenshot 2021-09-05 at 18 54 44\" src=\"https://user-images.githubusercontent.com/852409/132135015-45c0a273-763a-430e-b12f-d340e79b3ea7.png\">\r\n\n", "before_files": [{"content": "\"\"\"Logic for alerting the user on possibly problematic patterns in the data (e.g. high number of zeros , constant\nvalues, high correlations).\"\"\"\nfrom enum import Enum, auto, unique\nfrom typing import Any, Dict, List, Optional, Set\n\nimport numpy as np\nimport pandas as pd\n\nfrom pandas_profiling.config import Settings\nfrom pandas_profiling.model.correlations import perform_check_correlation\n\n\n@unique\nclass AlertType(Enum):\n    \"\"\"Alert types\"\"\"\n\n    CONSTANT = auto()\n    \"\"\"This variable has a constant value.\"\"\"\n\n    ZEROS = auto()\n    \"\"\"This variable contains zeros.\"\"\"\n\n    HIGH_CORRELATION = auto()\n    \"\"\"This variable is highly correlated.\"\"\"\n\n    HIGH_CARDINALITY = auto()\n    \"\"\"This variable has a high cardinality.\"\"\"\n\n    UNSUPPORTED = auto()\n    \"\"\"This variable is unsupported.\"\"\"\n\n    DUPLICATES = auto()\n    \"\"\"This variable contains duplicates.\"\"\"\n\n    SKEWED = auto()\n    \"\"\"This variable is highly skewed.\"\"\"\n\n    MISSING = auto()\n    \"\"\"This variable contains missing values.\"\"\"\n\n    INFINITE = auto()\n    \"\"\"This variable contains infinite values.\"\"\"\n\n    TYPE_DATE = auto()\n    \"\"\"This variable is likely a datetime, but treated as categorical.\"\"\"\n\n    UNIQUE = auto()\n    \"\"\"This variable has unique values.\"\"\"\n\n    CONSTANT_LENGTH = auto()\n    \"\"\"This variable has a constant length\"\"\"\n\n    REJECTED = auto()\n    \"\"\"Variables are rejected if we do not want to consider them for further analysis.\"\"\"\n\n    UNIFORM = auto()\n    \"\"\"The variable is uniformly distributed\"\"\"\n\n    EMPTY = auto()\n    \"\"\"The DataFrame is empty\"\"\"\n\n\nclass Alert:\n    \"\"\"An alert object (type, values, column).\"\"\"\n\n    _anchor_id: Optional[str] = None\n\n    def __init__(\n        self,\n        alert_type: AlertType,\n        values: Optional[Dict] = None,\n        column_name: Optional[str] = None,\n        fields: Optional[Set] = None,\n    ):\n        if values is None:\n            values = {}\n        if fields is None:\n            fields = set()\n\n        self.fields = fields\n        self.alert_type = alert_type\n        self.values = values\n        self.column_name = column_name\n\n    @property\n    def anchor_id(self) -> Optional[str]:\n        if self._anchor_id is None:\n            self._anchor_id = str(hash(self.column_name))\n        return self._anchor_id\n\n    def fmt(self) -> str:\n        # TODO: render in template\n        name = self.alert_type.name.replace(\"_\", \" \")\n        if name == \"HIGH CORRELATION\":\n            num = len(self.values[\"fields\"])\n            title = \", \".join(self.values[\"fields\"])\n            name = f'<abbr title=\"This variable has a high correlation with {num} fields: {title}\">HIGH CORRELATION</abbr>'\n        return name\n\n    def __repr__(self):\n        alert_type = self.alert_type.name\n        column = self.column_name\n        return f\"[{alert_type}] alert on column {column}\"\n\n\ndef check_table_alerts(table: dict) -> List[Alert]:\n    \"\"\"Checks the overall dataset for alerts.\n\n    Args:\n        table: Overall dataset statistics.\n\n    Returns:\n        A list of alerts.\n    \"\"\"\n    alerts = []\n    if alert_value(table.get(\"n_duplicates\", np.nan)):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.DUPLICATES,\n                values=table,\n                fields={\"n_duplicates\"},\n            )\n        )\n    if table[\"n\"] == 0:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.EMPTY,\n                values=table,\n                fields={\"n\"},\n            )\n        )\n    return alerts\n\n\ndef numeric_alerts(config: Settings, summary: dict) -> List[Alert]:\n    alerts = []\n\n    # Skewness\n    if skewness_alert(summary[\"skewness\"], config.vars.num.skewness_threshold):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.SKEWED,\n                fields={\"skewness\"},\n            )\n        )\n\n    # Infinite values\n    if alert_value(summary[\"p_infinite\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.INFINITE,\n                fields={\"p_infinite\", \"n_infinite\"},\n            )\n        )\n\n    # Zeros\n    if alert_value(summary[\"p_zeros\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.ZEROS,\n                fields={\"n_zeros\", \"p_zeros\"},\n            )\n        )\n\n    if (\n        \"chi_squared\" in summary\n        and summary[\"chi_squared\"][\"pvalue\"] > config.vars.num.chi_squared_threshold\n    ):\n        alerts.append(Alert(alert_type=AlertType.UNIFORM))\n\n    return alerts\n\n\ndef categorical_alerts(config: Settings, summary: dict) -> List[Alert]:\n    alerts = []\n\n    # High cardinality\n    if summary.get(\"n_distinct\", np.nan) > config.vars.cat.cardinality_threshold:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.HIGH_CARDINALITY,\n                fields={\"n_distinct\"},\n            )\n        )\n\n    if (\n        \"chi_squared\" in summary\n        and summary[\"chi_squared\"][\"pvalue\"] > config.vars.cat.chi_squared_threshold\n    ):\n        alerts.append(Alert(alert_type=AlertType.UNIFORM))\n\n    if summary.get(\"date_warning\"):\n        alerts.append(Alert(alert_type=AlertType.TYPE_DATE))\n\n    # Constant length\n    if \"composition\" in summary and summary[\"min_length\"] == summary[\"max_length\"]:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.CONSTANT_LENGTH,\n                fields={\"composition_min_length\", \"composition_max_length\"},\n            )\n        )\n\n    return alerts\n\n\ndef generic_alerts(summary: dict) -> List[Alert]:\n    alerts = []\n\n    # Missing\n    if alert_value(summary[\"p_missing\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.MISSING,\n                fields={\"p_missing\", \"n_missing\"},\n            )\n        )\n\n    return alerts\n\n\ndef supported_alerts(summary: dict) -> List[Alert]:\n    alerts = []\n\n    if summary.get(\"n_distinct\", np.nan) == summary[\"n\"]:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.UNIQUE,\n                fields={\"n_distinct\", \"p_distinct\", \"n_unique\", \"p_unique\"},\n            )\n        )\n    if summary.get(\"n_distinct\", np.nan) == 1:\n        summary[\"mode\"] = summary[\"value_counts_without_nan\"].index[0]\n        alerts.append(\n            Alert(\n                alert_type=AlertType.CONSTANT,\n                fields={\"n_distinct\"},\n            )\n        )\n        alerts.append(\n            Alert(\n                alert_type=AlertType.REJECTED,\n                fields=set(),\n            )\n        )\n    return alerts\n\n\ndef unsupported_alerts(summary: Dict[str, Any]) -> List[Alert]:\n    alerts = [\n        Alert(\n            alert_type=AlertType.UNSUPPORTED,\n            fields=set(),\n        ),\n        Alert(\n            alert_type=AlertType.REJECTED,\n            fields=set(),\n        ),\n    ]\n    return alerts\n\n\ndef check_variable_alerts(config: Settings, col: str, description: dict) -> List[Alert]:\n    \"\"\"Checks individual variables for alerts.\n\n    Args:\n        col: The column name that is checked.\n        description: The series description.\n\n    Returns:\n        A list of alerts.\n    \"\"\"\n    alerts = []\n\n    alerts += generic_alerts(description)\n\n    if description[\"type\"] == \"Unsupported\":\n        alerts += unsupported_alerts(description)\n    else:\n        alerts += supported_alerts(description)\n\n        if description[\"type\"] == \"Categorical\":\n            alerts += categorical_alerts(config, description)\n        if description[\"type\"] == \"Numeric\":\n            alerts += numeric_alerts(config, description)\n\n    for idx in range(len(alerts)):\n        alerts[idx].column_name = col\n        alerts[idx].values = description\n    return alerts\n\n\ndef check_correlation_alerts(config: Settings, correlations: dict) -> List[Alert]:\n    alerts = []\n\n    for corr, matrix in correlations.items():\n        if config.correlations[corr].warn_high_correlations:\n            threshold = config.correlations[corr].threshold\n            correlated_mapping = perform_check_correlation(matrix, threshold)\n            if len(correlated_mapping) > 0:\n                for k, v in correlated_mapping.items():\n                    alerts.append(\n                        Alert(\n                            column_name=k,\n                            alert_type=AlertType.HIGH_CORRELATION,\n                            values={\"corr\": corr, \"fields\": v},\n                        )\n                    )\n    return alerts\n\n\ndef get_alerts(\n    config: Settings, table_stats: dict, series_description: dict, correlations: dict\n) -> List[Alert]:\n    alerts = check_table_alerts(table_stats)\n    for col, description in series_description.items():\n        alerts += check_variable_alerts(config, col, description)\n    alerts += check_correlation_alerts(config, correlations)\n    alerts.sort(key=lambda alert: str(alert.alert_type))\n    return alerts\n\n\ndef alert_value(value: float) -> bool:\n    return not np.isnan(value) and value > 0.01\n\n\ndef skewness_alert(v: float, threshold: int) -> bool:\n    return not np.isnan(v) and (v < (-1 * threshold) or v > threshold)\n\n\ndef type_date_alert(series: pd.Series) -> bool:\n    from dateutil.parser import ParserError, parse\n\n    try:\n        series.apply(parse)\n    except ParserError:\n        return False\n    else:\n        return True\n", "path": "src/pandas_profiling/model/alerts.py"}], "after_files": [{"content": "\"\"\"Logic for alerting the user on possibly problematic patterns in the data (e.g. high number of zeros , constant\nvalues, high correlations).\"\"\"\nfrom enum import Enum, auto, unique\nfrom typing import Any, Dict, List, Optional, Set\n\nimport numpy as np\nimport pandas as pd\n\nfrom pandas_profiling.config import Settings\nfrom pandas_profiling.model.correlations import perform_check_correlation\n\n\n@unique\nclass AlertType(Enum):\n    \"\"\"Alert types\"\"\"\n\n    CONSTANT = auto()\n    \"\"\"This variable has a constant value.\"\"\"\n\n    ZEROS = auto()\n    \"\"\"This variable contains zeros.\"\"\"\n\n    HIGH_CORRELATION = auto()\n    \"\"\"This variable is highly correlated.\"\"\"\n\n    HIGH_CARDINALITY = auto()\n    \"\"\"This variable has a high cardinality.\"\"\"\n\n    UNSUPPORTED = auto()\n    \"\"\"This variable is unsupported.\"\"\"\n\n    DUPLICATES = auto()\n    \"\"\"This variable contains duplicates.\"\"\"\n\n    SKEWED = auto()\n    \"\"\"This variable is highly skewed.\"\"\"\n\n    MISSING = auto()\n    \"\"\"This variable contains missing values.\"\"\"\n\n    INFINITE = auto()\n    \"\"\"This variable contains infinite values.\"\"\"\n\n    TYPE_DATE = auto()\n    \"\"\"This variable is likely a datetime, but treated as categorical.\"\"\"\n\n    UNIQUE = auto()\n    \"\"\"This variable has unique values.\"\"\"\n\n    CONSTANT_LENGTH = auto()\n    \"\"\"This variable has a constant length\"\"\"\n\n    REJECTED = auto()\n    \"\"\"Variables are rejected if we do not want to consider them for further analysis.\"\"\"\n\n    UNIFORM = auto()\n    \"\"\"The variable is uniformly distributed\"\"\"\n\n    EMPTY = auto()\n    \"\"\"The DataFrame is empty\"\"\"\n\n\nclass Alert:\n    \"\"\"An alert object (type, values, column).\"\"\"\n\n    _anchor_id: Optional[str] = None\n\n    def __init__(\n        self,\n        alert_type: AlertType,\n        values: Optional[Dict] = None,\n        column_name: Optional[str] = None,\n        fields: Optional[Set] = None,\n    ):\n        if values is None:\n            values = {}\n        if fields is None:\n            fields = set()\n\n        self.fields = fields\n        self.alert_type = alert_type\n        self.values = values\n        self.column_name = column_name\n\n    @property\n    def anchor_id(self) -> Optional[str]:\n        if self._anchor_id is None:\n            self._anchor_id = str(hash(self.column_name))\n        return self._anchor_id\n\n    def fmt(self) -> str:\n        # TODO: render in template\n        name = self.alert_type.name.replace(\"_\", \" \")\n        if name == \"HIGH CORRELATION\":\n            num = len(self.values[\"fields\"])\n            title = \", \".join(self.values[\"fields\"])\n            name = f'<abbr title=\"This variable has a high correlation with {num} fields: {title}\">HIGH CORRELATION</abbr>'\n        return name\n\n    def __repr__(self):\n        alert_type = self.alert_type.name\n        column = self.column_name\n        return f\"[{alert_type}] alert on column {column}\"\n\n\ndef check_table_alerts(table: dict) -> List[Alert]:\n    \"\"\"Checks the overall dataset for alerts.\n\n    Args:\n        table: Overall dataset statistics.\n\n    Returns:\n        A list of alerts.\n    \"\"\"\n    alerts = []\n    if alert_value(table.get(\"n_duplicates\", np.nan)):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.DUPLICATES,\n                values=table,\n                fields={\"n_duplicates\"},\n            )\n        )\n    if table[\"n\"] == 0:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.EMPTY,\n                values=table,\n                fields={\"n\"},\n            )\n        )\n    return alerts\n\n\ndef numeric_alerts(config: Settings, summary: dict) -> List[Alert]:\n    alerts = []\n\n    # Skewness\n    if skewness_alert(summary[\"skewness\"], config.vars.num.skewness_threshold):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.SKEWED,\n                fields={\"skewness\"},\n            )\n        )\n\n    # Infinite values\n    if alert_value(summary[\"p_infinite\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.INFINITE,\n                fields={\"p_infinite\", \"n_infinite\"},\n            )\n        )\n\n    # Zeros\n    if alert_value(summary[\"p_zeros\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.ZEROS,\n                fields={\"n_zeros\", \"p_zeros\"},\n            )\n        )\n\n    if (\n        \"chi_squared\" in summary\n        and summary[\"chi_squared\"][\"pvalue\"] > config.vars.num.chi_squared_threshold\n    ):\n        alerts.append(Alert(alert_type=AlertType.UNIFORM))\n\n    return alerts\n\n\ndef categorical_alerts(config: Settings, summary: dict) -> List[Alert]:\n    alerts = []\n\n    # High cardinality\n    if summary.get(\"n_distinct\", np.nan) > config.vars.cat.cardinality_threshold:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.HIGH_CARDINALITY,\n                fields={\"n_distinct\"},\n            )\n        )\n\n    if (\n        \"chi_squared\" in summary\n        and summary[\"chi_squared\"][\"pvalue\"] > config.vars.cat.chi_squared_threshold\n    ):\n        alerts.append(Alert(alert_type=AlertType.UNIFORM))\n\n    if summary.get(\"date_warning\"):\n        alerts.append(Alert(alert_type=AlertType.TYPE_DATE))\n\n    # Constant length\n    if \"composition\" in summary and summary[\"min_length\"] == summary[\"max_length\"]:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.CONSTANT_LENGTH,\n                fields={\"composition_min_length\", \"composition_max_length\"},\n            )\n        )\n\n    return alerts\n\n\ndef generic_alerts(summary: dict) -> List[Alert]:\n    alerts = []\n\n    # Missing\n    if alert_value(summary[\"p_missing\"]):\n        alerts.append(\n            Alert(\n                alert_type=AlertType.MISSING,\n                fields={\"p_missing\", \"n_missing\"},\n            )\n        )\n\n    return alerts\n\n\ndef supported_alerts(summary: dict) -> List[Alert]:\n    alerts = []\n\n    if summary.get(\"n_distinct\", np.nan) == summary[\"n\"]:\n        alerts.append(\n            Alert(\n                alert_type=AlertType.UNIQUE,\n                fields={\"n_distinct\", \"p_distinct\", \"n_unique\", \"p_unique\"},\n            )\n        )\n    if summary.get(\"n_distinct\", np.nan) == 1:\n        summary[\"mode\"] = summary[\"value_counts_without_nan\"].index[0]\n        alerts.append(\n            Alert(\n                alert_type=AlertType.CONSTANT,\n                fields={\"n_distinct\"},\n            )\n        )\n        alerts.append(\n            Alert(\n                alert_type=AlertType.REJECTED,\n                fields=set(),\n            )\n        )\n    return alerts\n\n\ndef unsupported_alerts(summary: Dict[str, Any]) -> List[Alert]:\n    alerts = [\n        Alert(\n            alert_type=AlertType.UNSUPPORTED,\n            fields=set(),\n        ),\n        Alert(\n            alert_type=AlertType.REJECTED,\n            fields=set(),\n        ),\n    ]\n    return alerts\n\n\ndef check_variable_alerts(config: Settings, col: str, description: dict) -> List[Alert]:\n    \"\"\"Checks individual variables for alerts.\n\n    Args:\n        col: The column name that is checked.\n        description: The series description.\n\n    Returns:\n        A list of alerts.\n    \"\"\"\n    alerts = []\n\n    alerts += generic_alerts(description)\n\n    if description[\"type\"] == \"Unsupported\":\n        alerts += unsupported_alerts(description)\n    else:\n        alerts += supported_alerts(description)\n\n        if description[\"type\"] == \"Categorical\":\n            alerts += categorical_alerts(config, description)\n        if description[\"type\"] == \"Numeric\":\n            alerts += numeric_alerts(config, description)\n\n    for idx in range(len(alerts)):\n        alerts[idx].column_name = col\n        alerts[idx].values = description\n    return alerts\n\n\ndef check_correlation_alerts(config: Settings, correlations: dict) -> List[Alert]:\n    alerts = []\n\n    correlations_consolidated = {}\n    for corr, matrix in correlations.items():\n        if config.correlations[corr].warn_high_correlations:\n            threshold = config.correlations[corr].threshold\n            correlated_mapping = perform_check_correlation(matrix, threshold)\n            for col, fields in correlated_mapping.items():\n                set(fields).update(set(correlated_mapping.get(col, [])))\n                correlations_consolidated[col] = fields\n\n    if len(correlations_consolidated) > 0:\n        for col, fields in correlations_consolidated.items():\n            alerts.append(\n                Alert(\n                    column_name=col,\n                    alert_type=AlertType.HIGH_CORRELATION,\n                    values={\"corr\": 'Overall', \"fields\": fields},\n                )\n            )\n    return alerts\n\n\ndef get_alerts(\n    config: Settings, table_stats: dict, series_description: dict, correlations: dict\n) -> List[Alert]:\n    alerts = check_table_alerts(table_stats)\n    for col, description in series_description.items():\n        alerts += check_variable_alerts(config, col, description)\n    alerts += check_correlation_alerts(config, correlations)\n    alerts.sort(key=lambda alert: str(alert.alert_type))\n    return alerts\n\n\ndef alert_value(value: float) -> bool:\n    return not np.isnan(value) and value > 0.01\n\n\ndef skewness_alert(v: float, threshold: int) -> bool:\n    return not np.isnan(v) and (v < (-1 * threshold) or v > threshold)\n\n\ndef type_date_alert(series: pd.Series) -> bool:\n    from dateutil.parser import ParserError, parse\n\n    try:\n        series.apply(parse)\n    except ParserError:\n        return False\n    else:\n        return True\n", "path": "src/pandas_profiling/model/alerts.py"}]}