anonymization-lib 1.4.0__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.
- anonymization_lib/__init__.py +7 -0
- anonymization_lib/io/__init__.py +4 -0
- anonymization_lib/io/data_import.py +90 -0
- anonymization_lib/io/export.py +87 -0
- anonymization_lib/metrics/__init__.py +5 -0
- anonymization_lib/metrics/k_anonymity.py +391 -0
- anonymization_lib/metrics/l_diversity.py +393 -0
- anonymization_lib/metrics/t_closeness.py +618 -0
- anonymization_lib/techniques/__init__.py +6 -0
- anonymization_lib/techniques/generalization.py +907 -0
- anonymization_lib/techniques/pipeline.py +134 -0
- anonymization_lib/techniques/substitution.py +343 -0
- anonymization_lib/techniques/suppression.py +255 -0
- anonymization_lib/utils.py +153 -0
- anonymization_lib/visualization/Suggestion.py +730 -0
- anonymization_lib/visualization/__init__.py +4 -0
- anonymization_lib/visualization/visualization.py +845 -0
- anonymization_lib-1.4.0.dist-info/METADATA +302 -0
- anonymization_lib-1.4.0.dist-info/RECORD +22 -0
- anonymization_lib-1.4.0.dist-info/WHEEL +5 -0
- anonymization_lib-1.4.0.dist-info/licenses/LICENSE +21 -0
- anonymization_lib-1.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
from pyspark.sql import DataFrame
|
|
2
|
+
from pyspark.sql import functions as F
|
|
3
|
+
|
|
4
|
+
from ..utils import EquivalenceGroups
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LDiversity(EquivalenceGroups):
|
|
8
|
+
"""
|
|
9
|
+
Calculates distinct l-diversity for a dataset based on
|
|
10
|
+
quasi-identifiers and one sensitive attribute.
|
|
11
|
+
|
|
12
|
+
L-diversity extends k-anonymity by requiring each equivalence group
|
|
13
|
+
to contain sufficient diversity in its sensitive attribute.
|
|
14
|
+
|
|
15
|
+
This implementation uses distinct l-diversity. An equivalence group
|
|
16
|
+
satisfies l-diversity when it contains at least ``l_threshold``
|
|
17
|
+
distinct values of the sensitive attribute.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
quasi_identifiers : list of str
|
|
22
|
+
Columns used to form equivalence groups (quasi-identifiers).
|
|
23
|
+
These are attributes that could potentially re-identify
|
|
24
|
+
individuals when combined.
|
|
25
|
+
|
|
26
|
+
sensitive_attribute : str
|
|
27
|
+
Column representing the sensitive attribute whose diversity
|
|
28
|
+
must be protected (e.g., disease, salary category, diagnosis).
|
|
29
|
+
|
|
30
|
+
l_threshold : int, optional
|
|
31
|
+
Minimum number of distinct sensitive-attribute values required
|
|
32
|
+
in every equivalence group.
|
|
33
|
+
|
|
34
|
+
The value must be an integer greater than 1.
|
|
35
|
+
Default is 2.
|
|
36
|
+
|
|
37
|
+
Notes
|
|
38
|
+
-----
|
|
39
|
+
The implementation uses Spark DataFrame operations and performs
|
|
40
|
+
the computation in a distributed manner.
|
|
41
|
+
|
|
42
|
+
The metric implemented is distinct l-diversity. Other variants,
|
|
43
|
+
such as entropy l-diversity or recursive (c, l)-diversity, are not
|
|
44
|
+
computed by this class.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
quasi_identifiers=None,
|
|
50
|
+
sensitive_attribute=None,
|
|
51
|
+
l_threshold: int = 2
|
|
52
|
+
):
|
|
53
|
+
if quasi_identifiers is None:
|
|
54
|
+
quasi_identifiers = []
|
|
55
|
+
|
|
56
|
+
if not isinstance(quasi_identifiers, list):
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"'quasi_identifiers' must be a list of column names."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if not quasi_identifiers:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"At least one quasi-identifier must be provided."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not all(
|
|
67
|
+
isinstance(col, str) and col.strip()
|
|
68
|
+
for col in quasi_identifiers
|
|
69
|
+
):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"'quasi_identifiers' must contain only non-empty strings."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if len(quasi_identifiers) != len(set(quasi_identifiers)):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"'quasi_identifiers' must not contain duplicate columns."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
not isinstance(sensitive_attribute, str)
|
|
81
|
+
or not sensitive_attribute.strip()
|
|
82
|
+
):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"'sensitive_attribute' must be a non-empty column name."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if sensitive_attribute in quasi_identifiers:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
"'sensitive_attribute' must not be included "
|
|
90
|
+
"in 'quasi_identifiers'."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
not isinstance(l_threshold, int)
|
|
95
|
+
or isinstance(l_threshold, bool)
|
|
96
|
+
or l_threshold <= 1
|
|
97
|
+
):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"'l_threshold' must be an integer greater than 1."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self.quasi_identifiers = quasi_identifiers
|
|
103
|
+
self.sensitive_attribute = sensitive_attribute
|
|
104
|
+
self.l_threshold = l_threshold
|
|
105
|
+
|
|
106
|
+
def summary(self, df: DataFrame):
|
|
107
|
+
"""
|
|
108
|
+
Computes distinct l-diversity in a distributed manner.
|
|
109
|
+
|
|
110
|
+
The method first creates equivalence groups using the configured
|
|
111
|
+
quasi-identifiers. For each equivalence group, it calculates the
|
|
112
|
+
number of distinct values of the sensitive attribute.
|
|
113
|
+
|
|
114
|
+
An equivalence group violates l-diversity when:
|
|
115
|
+
|
|
116
|
+
l_diversity < l_threshold
|
|
117
|
+
|
|
118
|
+
The method also calculates aggregated statistics describing the
|
|
119
|
+
l-diversity values of all equivalence groups.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
df : pyspark.sql.DataFrame
|
|
124
|
+
Input dataset containing the quasi-identifiers and the
|
|
125
|
+
sensitive attribute.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
LDiversityResult
|
|
130
|
+
Container object with:
|
|
131
|
+
|
|
132
|
+
- summary_df:
|
|
133
|
+
Aggregated statistics describing l-diversity across all
|
|
134
|
+
equivalence groups.
|
|
135
|
+
|
|
136
|
+
- violating_groups:
|
|
137
|
+
Equivalence groups whose number of distinct sensitive
|
|
138
|
+
values is below the configured l-threshold.
|
|
139
|
+
|
|
140
|
+
Raises
|
|
141
|
+
------
|
|
142
|
+
ValueError
|
|
143
|
+
If the input DataFrame is None, is not Spark-compatible, or
|
|
144
|
+
does not contain all required columns.
|
|
145
|
+
|
|
146
|
+
Notes
|
|
147
|
+
-----
|
|
148
|
+
The method is lazy. It builds the Spark transformations required
|
|
149
|
+
to calculate the metric, but execution occurs when an action such
|
|
150
|
+
as ``show()``, ``collect()`` or ``count()`` is performed on one
|
|
151
|
+
of the returned DataFrames.
|
|
152
|
+
"""
|
|
153
|
+
if df is None:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"Input DataFrame cannot be None."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Compatible with standard Spark and Spark Connect.
|
|
159
|
+
if not hasattr(df, "groupBy"):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"Input must be a Spark-compatible DataFrame."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
self._validate_columns(
|
|
165
|
+
df,
|
|
166
|
+
self.quasi_identifiers + [self.sensitive_attribute]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# ------------------------------------------------------
|
|
170
|
+
# EQUIVALENCE GROUPS AND DISTINCT L-DIVERSITY
|
|
171
|
+
# ------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
equivalence_groups = self._build_equivalence_groups(
|
|
174
|
+
df,
|
|
175
|
+
self.quasi_identifiers,
|
|
176
|
+
extra_aggs=[
|
|
177
|
+
F.countDistinct(
|
|
178
|
+
self.sensitive_attribute
|
|
179
|
+
).alias("l_diversity")
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# ------------------------------------------------------
|
|
184
|
+
# VIOLATING GROUPS
|
|
185
|
+
# ------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
violating_groups = (
|
|
188
|
+
equivalence_groups
|
|
189
|
+
.filter(
|
|
190
|
+
F.col("l_diversity") < self.l_threshold
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------------
|
|
195
|
+
# SUMMARY
|
|
196
|
+
# ------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
summary_df = (
|
|
199
|
+
equivalence_groups
|
|
200
|
+
.agg(
|
|
201
|
+
F.count("*").alias(
|
|
202
|
+
"num_equivalence_groups"
|
|
203
|
+
),
|
|
204
|
+
F.min("l_diversity").alias(
|
|
205
|
+
"l_value"
|
|
206
|
+
),
|
|
207
|
+
F.max("l_diversity").alias(
|
|
208
|
+
"max_l"
|
|
209
|
+
),
|
|
210
|
+
F.round(
|
|
211
|
+
F.avg("l_diversity"),
|
|
212
|
+
2
|
|
213
|
+
).alias(
|
|
214
|
+
"avg_l"
|
|
215
|
+
),
|
|
216
|
+
F.sum(
|
|
217
|
+
F.when(
|
|
218
|
+
F.col("l_diversity")
|
|
219
|
+
< self.l_threshold,
|
|
220
|
+
1
|
|
221
|
+
).otherwise(0)
|
|
222
|
+
).alias(
|
|
223
|
+
"num_violating_groups"
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
.withColumn(
|
|
227
|
+
"l_threshold",
|
|
228
|
+
F.lit(self.l_threshold)
|
|
229
|
+
)
|
|
230
|
+
.withColumn(
|
|
231
|
+
"satisfies_l_diversity",
|
|
232
|
+
F.col("l_value")
|
|
233
|
+
>= F.col("l_threshold")
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return LDiversityResult(
|
|
238
|
+
summary_df=summary_df,
|
|
239
|
+
violating_groups=violating_groups
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class LDiversityResult:
|
|
244
|
+
"""
|
|
245
|
+
Container for l-diversity results.
|
|
246
|
+
|
|
247
|
+
The object stores both the aggregated summary generated by the
|
|
248
|
+
l-diversity analysis and the equivalence groups that violate the
|
|
249
|
+
configured l-threshold.
|
|
250
|
+
|
|
251
|
+
Attributes
|
|
252
|
+
----------
|
|
253
|
+
summary_df : pyspark.sql.DataFrame
|
|
254
|
+
Aggregated statistics of l-diversity values across all
|
|
255
|
+
equivalence groups.
|
|
256
|
+
|
|
257
|
+
violating_groups : pyspark.sql.DataFrame
|
|
258
|
+
Equivalence groups whose number of distinct sensitive-attribute
|
|
259
|
+
values is below the configured l-threshold.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
summary_df: DataFrame,
|
|
265
|
+
violating_groups: DataFrame
|
|
266
|
+
):
|
|
267
|
+
"""
|
|
268
|
+
Initializes the result object.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
summary_df : pyspark.sql.DataFrame
|
|
273
|
+
Aggregated statistics describing the l-diversity values
|
|
274
|
+
across all equivalence groups.
|
|
275
|
+
|
|
276
|
+
violating_groups : pyspark.sql.DataFrame
|
|
277
|
+
Equivalence groups that do not satisfy the configured
|
|
278
|
+
l-diversity threshold.
|
|
279
|
+
"""
|
|
280
|
+
self.summary_df = summary_df
|
|
281
|
+
self.violating_groups = violating_groups
|
|
282
|
+
|
|
283
|
+
def show_summary(self):
|
|
284
|
+
"""
|
|
285
|
+
Displays aggregated statistics of l-diversity.
|
|
286
|
+
|
|
287
|
+
The summary includes:
|
|
288
|
+
|
|
289
|
+
- num_equivalence_groups:
|
|
290
|
+
Total number of equivalence groups.
|
|
291
|
+
|
|
292
|
+
- l_value:
|
|
293
|
+
Minimum number of distinct sensitive values found among all
|
|
294
|
+
equivalence groups. This represents the l-diversity value
|
|
295
|
+
of the complete dataset.
|
|
296
|
+
|
|
297
|
+
- max_l:
|
|
298
|
+
Maximum number of distinct sensitive values found in an
|
|
299
|
+
equivalence group.
|
|
300
|
+
|
|
301
|
+
- avg_l:
|
|
302
|
+
Average number of distinct sensitive values across all
|
|
303
|
+
equivalence groups.
|
|
304
|
+
|
|
305
|
+
- num_violating_groups:
|
|
306
|
+
Number of equivalence groups whose l-diversity is below the
|
|
307
|
+
configured threshold.
|
|
308
|
+
|
|
309
|
+
- l_threshold:
|
|
310
|
+
Minimum l-diversity value required by the user.
|
|
311
|
+
|
|
312
|
+
- satisfies_l_diversity:
|
|
313
|
+
Boolean indicating whether every equivalence group satisfies
|
|
314
|
+
the configured l-diversity requirement.
|
|
315
|
+
|
|
316
|
+
Interpretation
|
|
317
|
+
--------------
|
|
318
|
+
The dataset satisfies the configured l-diversity requirement when:
|
|
319
|
+
|
|
320
|
+
l_value >= l_threshold
|
|
321
|
+
|
|
322
|
+
and therefore:
|
|
323
|
+
|
|
324
|
+
num_violating_groups = 0
|
|
325
|
+
"""
|
|
326
|
+
self.summary_df.show(
|
|
327
|
+
truncate=False
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
def show_violating_groups(
|
|
331
|
+
self,
|
|
332
|
+
n: int = 10,
|
|
333
|
+
sort: bool = False
|
|
334
|
+
):
|
|
335
|
+
"""
|
|
336
|
+
Displays equivalence groups that do not satisfy l-diversity.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
n : int, optional
|
|
341
|
+
Number of violating equivalence groups to display.
|
|
342
|
+
Default is 10.
|
|
343
|
+
|
|
344
|
+
sort : bool, optional
|
|
345
|
+
Whether to sort violating groups by their l-diversity value
|
|
346
|
+
in ascending order.
|
|
347
|
+
|
|
348
|
+
When enabled, the groups with the lowest diversity and
|
|
349
|
+
therefore the greatest potential privacy risk are displayed
|
|
350
|
+
first.
|
|
351
|
+
|
|
352
|
+
Sorting may increase computational cost for large datasets
|
|
353
|
+
because it requires an additional distributed ordering
|
|
354
|
+
operation.
|
|
355
|
+
|
|
356
|
+
Default is False.
|
|
357
|
+
"""
|
|
358
|
+
df = self.violating_groups
|
|
359
|
+
|
|
360
|
+
if sort:
|
|
361
|
+
df = df.orderBy(
|
|
362
|
+
F.col("l_diversity").asc()
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
df.show(
|
|
366
|
+
n,
|
|
367
|
+
truncate=False
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def get_summary_df(self) -> DataFrame:
|
|
371
|
+
"""
|
|
372
|
+
Returns aggregated statistics of l-diversity.
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
pyspark.sql.DataFrame
|
|
377
|
+
DataFrame containing the global l-diversity assessment,
|
|
378
|
+
aggregated statistics and the configured privacy threshold.
|
|
379
|
+
"""
|
|
380
|
+
return self.summary_df
|
|
381
|
+
|
|
382
|
+
def get_violating_groups(self) -> DataFrame:
|
|
383
|
+
"""
|
|
384
|
+
Returns equivalence groups that do not satisfy l-diversity.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
pyspark.sql.DataFrame
|
|
389
|
+
DataFrame containing the equivalence groups whose number of
|
|
390
|
+
distinct sensitive-attribute values is below the configured
|
|
391
|
+
l-threshold.
|
|
392
|
+
"""
|
|
393
|
+
return self.violating_groups
|