Repository: TTitcombe/PrivacyPanda

Issue:
Create simple report
A user should be able to query a dataframe and see a list of privacy breaching columns and their reason for breaching privacy.

Basic functionality is to print a list of  `column: reason` pairs

Discussion:

Patch:
diff --git a/privacypanda/__init__.py b/privacypanda/__init__.py
index 5e891fe..ab09290 100644
--- a/privacypanda/__init__.py
+++ b/privacypanda/__init__.py
@@ -1,4 +1,5 @@
 from .addresses import *
 from .anonymize import *
+from .report import *
 
 __version__ = "0.1.0dev"
diff --git a/privacypanda/report.py b/privacypanda/report.py
new file mode 100644
index 0000000..e8af3a3
--- /dev/null
+++ b/privacypanda/report.py
@@ -0,0 +1,66 @@
+"""
+Code for reporting the privacy of a dataframe
+"""
+from collections import defaultdict
+from typing import TYPE_CHECKING, List, Union
+
+from .addresses import check_addresses
+
+if TYPE_CHECKING:
+    import pandas
+
+
+__all__ = ["report_privacy"]
+
+
+class Report:
+    def __init__(self):
+        self._breaches = defaultdict(list)
+
+    def add_breach(self, columns: Union[List[str], str], breach: str):
+        """
+        Add columns which breach privacy to the dictionary of breaches
+
+        Parameters
+        ----------
+        columns : list of strings or a string
+            A single column or a list of columns which have breached privacy
+        breach : str
+            A type of breach
+
+        Returns
+        -------
+        None
+
+        Raises
+        ------
+        AssertionError
+            If breach not "address", "name", "email", "phone number"
+        """
+        breach = breach.lower()
+        valid_breaches = ("address", "name", "email", "phone number")
+        assert breach in valid_breaches, f"{breach} not in {valid_breaches}"
+
+        if isinstance(columns, str):
+            columns = [columns]
+
+        for column in columns:
+            self._breaches[column].append(breach)
+
+    def __str__(self):
+        report = ""
+        for column, breaches in self._breaches.items():
+            report += f"{column}: {breaches}\n"
+        return report
+
+
+def report_privacy(df: "pandas.DataFrame") -> Report:
+    report = Report()
+
+    checks = {"address": check_addresses}
+
+    for breach, check in checks.items():
+        columns = check(df)
+        report.add_breach(columns, breach)
+
+    return report


Test patch:
diff --git a/tests/test_report.py b/tests/test_report.py
new file mode 100644
index 0000000..ced0754
--- /dev/null
+++ b/tests/test_report.py
@@ -0,0 +1,40 @@
+"""
+Test reporting functionality
+"""
+import pandas as pd
+import pytest
+
+import privacypanda as pp
+
+
+def test_can_report_addresses():
+    df = pd.DataFrame(
+        {
+            "col1": ["a", "b", "10 Downing Street"],
+            "col2": [1, 2, 3],
+            "col3": ["11 Downing Street", "b", "c"],
+        }
+    )
+
+    # Check correct breaches have been logged
+    report = pp.report_privacy(df)
+    expected_breaches = {"col1": ["address"], "col3": ["address"]}
+
+    assert report._breaches == expected_breaches
+
+    # Check string report
+    actual_string = str(report)
+    expected_string = "col1: ['address']\ncol3: ['address']\n"
+
+    assert actual_string == expected_string
+
+
+def test_report_can_accept_multiple_breaches_per_column():
+    report = pp.report.Report()
+
+    report.add_breach("col1", "address")
+    report.add_breach("col1", "phone number")
+
+    expected_breaches = {"col1": ["address", "phone number"]}
+
+    assert report._breaches == expected_breaches