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,618 @@
|
|
|
1
|
+
from pyspark.sql import DataFrame
|
|
2
|
+
from pyspark.sql import functions as F
|
|
3
|
+
from pyspark.sql.window import Window
|
|
4
|
+
|
|
5
|
+
from ..utils import EquivalenceGroups
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TCloseness(EquivalenceGroups):
|
|
9
|
+
"""
|
|
10
|
+
Calculates t-closeness for a dataset based on quasi-identifiers
|
|
11
|
+
and one sensitive attribute.
|
|
12
|
+
|
|
13
|
+
T-closeness measures the distance between the distribution of a
|
|
14
|
+
sensitive attribute within each equivalence group and the distribution
|
|
15
|
+
of that attribute in the complete dataset.
|
|
16
|
+
|
|
17
|
+
An equivalence group satisfies t-closeness when its distance from the
|
|
18
|
+
global sensitive-attribute distribution does not exceed the configured
|
|
19
|
+
threshold.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
quasi_identifiers : list of str
|
|
24
|
+
Columns used to form equivalence groups (quasi-identifiers).
|
|
25
|
+
These are attributes that could potentially re-identify individuals.
|
|
26
|
+
|
|
27
|
+
sensitive_attribute : str
|
|
28
|
+
Column representing the sensitive attribute whose distribution
|
|
29
|
+
must be protected (e.g., disease, salary, diagnosis).
|
|
30
|
+
|
|
31
|
+
t_threshold : float
|
|
32
|
+
Maximum allowed distance between the local distribution of the
|
|
33
|
+
sensitive attribute within an equivalence group and its global
|
|
34
|
+
distribution in the dataset.
|
|
35
|
+
|
|
36
|
+
distance_metric : str, optional
|
|
37
|
+
Metric used to compare distributions. Supported values are:
|
|
38
|
+
|
|
39
|
+
- "l1":
|
|
40
|
+
Total variation distance. It is calculated as half of the
|
|
41
|
+
L1 distance between the local and global probability
|
|
42
|
+
distributions.
|
|
43
|
+
|
|
44
|
+
- "jsd":
|
|
45
|
+
Jensen-Shannon divergence. It is a symmetric and bounded
|
|
46
|
+
divergence derived from the Kullback-Leibler divergence.
|
|
47
|
+
|
|
48
|
+
Default is "l1".
|
|
49
|
+
|
|
50
|
+
Notes
|
|
51
|
+
-----
|
|
52
|
+
The implementation is based entirely on Spark DataFrame operations
|
|
53
|
+
and avoids collecting the dataset to the driver.
|
|
54
|
+
|
|
55
|
+
All values of the sensitive attribute that occur in the global
|
|
56
|
+
distribution are considered when evaluating each equivalence group.
|
|
57
|
+
If a sensitive value is absent from a particular group, its local
|
|
58
|
+
probability is treated as zero.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
VALID_METRICS = {"l1", "jsd"}
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
quasi_identifiers=None,
|
|
66
|
+
sensitive_attribute=None,
|
|
67
|
+
t_threshold=None,
|
|
68
|
+
distance_metric="l1"
|
|
69
|
+
):
|
|
70
|
+
if quasi_identifiers is None:
|
|
71
|
+
quasi_identifiers = []
|
|
72
|
+
|
|
73
|
+
if not isinstance(quasi_identifiers, list):
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"'quasi_identifiers' must be a list of column names."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if not quasi_identifiers:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"At least one quasi-identifier must be provided."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not all(
|
|
84
|
+
isinstance(col, str) and col.strip()
|
|
85
|
+
for col in quasi_identifiers
|
|
86
|
+
):
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"'quasi_identifiers' must contain only non-empty strings."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if len(quasi_identifiers) != len(set(quasi_identifiers)):
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"'quasi_identifiers' must not contain duplicate columns."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
not isinstance(sensitive_attribute, str)
|
|
98
|
+
or not sensitive_attribute.strip()
|
|
99
|
+
):
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"'sensitive_attribute' must be a non-empty column name."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if sensitive_attribute in quasi_identifiers:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
"'sensitive_attribute' must not be included in "
|
|
107
|
+
"'quasi_identifiers'."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
not isinstance(t_threshold, (int, float))
|
|
112
|
+
or isinstance(t_threshold, bool)
|
|
113
|
+
):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"'t_threshold' must be a numeric value."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if t_threshold <= 0:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
"'t_threshold' must be greater than 0."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if distance_metric not in self.VALID_METRICS:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"Unsupported metric. Use {sorted(self.VALID_METRICS)}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.quasi_identifiers = quasi_identifiers
|
|
129
|
+
self.sensitive_attribute = sensitive_attribute
|
|
130
|
+
self.t_threshold = t_threshold
|
|
131
|
+
self.distance_metric = distance_metric
|
|
132
|
+
|
|
133
|
+
def summary(self, df: DataFrame):
|
|
134
|
+
"""
|
|
135
|
+
Computes t-closeness in a distributed manner.
|
|
136
|
+
|
|
137
|
+
The method compares the sensitive-attribute distribution of each
|
|
138
|
+
equivalence group with the global sensitive-attribute distribution.
|
|
139
|
+
|
|
140
|
+
The computation follows these steps:
|
|
141
|
+
|
|
142
|
+
1. Compute the global distribution of the sensitive attribute.
|
|
143
|
+
2. Build the equivalence groups defined by the quasi-identifiers.
|
|
144
|
+
3. Compute the sensitive-attribute distribution within each group.
|
|
145
|
+
4. Generate all combinations between equivalence groups and global
|
|
146
|
+
sensitive values so that values absent from a group receive a
|
|
147
|
+
local probability of zero.
|
|
148
|
+
5. Compute the selected distance between each local distribution
|
|
149
|
+
and the global distribution.
|
|
150
|
+
6. Identify groups whose distance exceeds the configured threshold.
|
|
151
|
+
7. Generate aggregated statistics describing the resulting
|
|
152
|
+
t-closeness values.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
df : pyspark.sql.DataFrame
|
|
157
|
+
Input dataset containing the quasi-identifiers and the
|
|
158
|
+
sensitive attribute.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
TClosenessResult
|
|
163
|
+
Container object with:
|
|
164
|
+
|
|
165
|
+
- summary_df:
|
|
166
|
+
Aggregated statistics describing t-closeness across all
|
|
167
|
+
equivalence groups.
|
|
168
|
+
|
|
169
|
+
- violating_groups:
|
|
170
|
+
Equivalence groups whose t-closeness value exceeds the
|
|
171
|
+
configured threshold.
|
|
172
|
+
|
|
173
|
+
Raises
|
|
174
|
+
------
|
|
175
|
+
ValueError
|
|
176
|
+
If the input DataFrame is None, is not Spark-compatible, or
|
|
177
|
+
does not contain all required columns.
|
|
178
|
+
"""
|
|
179
|
+
if df is None:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Input DataFrame cannot be None."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Compatible with both standard Spark and Spark Connect.
|
|
185
|
+
if not hasattr(df, "groupBy"):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"Input must be a Spark-compatible DataFrame."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
self._validate_columns(
|
|
191
|
+
df,
|
|
192
|
+
self.quasi_identifiers + [self.sensitive_attribute]
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# ------------------------------------------------------
|
|
196
|
+
# GLOBAL SENSITIVE-ATTRIBUTE DISTRIBUTION
|
|
197
|
+
# ------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
global_dist = (
|
|
200
|
+
df
|
|
201
|
+
.groupBy(self.sensitive_attribute)
|
|
202
|
+
.agg(
|
|
203
|
+
F.count("*").alias("global_count")
|
|
204
|
+
)
|
|
205
|
+
.withColumn(
|
|
206
|
+
"global_total",
|
|
207
|
+
F.sum("global_count").over(Window.partitionBy())
|
|
208
|
+
)
|
|
209
|
+
.withColumn(
|
|
210
|
+
"global_prob",
|
|
211
|
+
F.col("global_count") / F.col("global_total")
|
|
212
|
+
)
|
|
213
|
+
.select(
|
|
214
|
+
self.sensitive_attribute,
|
|
215
|
+
"global_prob"
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# ------------------------------------------------------
|
|
220
|
+
# LOCAL SENSITIVE-ATTRIBUTE DISTRIBUTIONS
|
|
221
|
+
# ------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
group_window = Window.partitionBy(
|
|
224
|
+
*self.quasi_identifiers
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
group_dist = (
|
|
228
|
+
df
|
|
229
|
+
.groupBy(
|
|
230
|
+
*self.quasi_identifiers,
|
|
231
|
+
self.sensitive_attribute
|
|
232
|
+
)
|
|
233
|
+
.agg(
|
|
234
|
+
F.count("*").alias("count")
|
|
235
|
+
)
|
|
236
|
+
.withColumn(
|
|
237
|
+
"group_total",
|
|
238
|
+
F.sum("count").over(group_window)
|
|
239
|
+
)
|
|
240
|
+
.withColumn(
|
|
241
|
+
"group_prob",
|
|
242
|
+
F.col("count") / F.col("group_total")
|
|
243
|
+
)
|
|
244
|
+
.select(
|
|
245
|
+
*self.quasi_identifiers,
|
|
246
|
+
self.sensitive_attribute,
|
|
247
|
+
"group_prob"
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# ------------------------------------------------------
|
|
252
|
+
# EQUIVALENCE GROUPS
|
|
253
|
+
# ------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
groups = (
|
|
256
|
+
group_dist
|
|
257
|
+
.select(*self.quasi_identifiers)
|
|
258
|
+
.distinct()
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# ------------------------------------------------------
|
|
262
|
+
# COMPLETE DISTRIBUTION DOMAIN
|
|
263
|
+
# ------------------------------------------------------
|
|
264
|
+
#
|
|
265
|
+
# Every equivalence group must be compared against every
|
|
266
|
+
# sensitive value that exists globally.
|
|
267
|
+
#
|
|
268
|
+
# If a value does not occur in a particular group, its
|
|
269
|
+
# group probability is zero.
|
|
270
|
+
# ------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
complete_domain = (
|
|
273
|
+
groups
|
|
274
|
+
.crossJoin(
|
|
275
|
+
F.broadcast(global_dist)
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
joined = (
|
|
280
|
+
complete_domain
|
|
281
|
+
.join(
|
|
282
|
+
group_dist,
|
|
283
|
+
on=(
|
|
284
|
+
self.quasi_identifiers
|
|
285
|
+
+ [self.sensitive_attribute]
|
|
286
|
+
),
|
|
287
|
+
how="left"
|
|
288
|
+
)
|
|
289
|
+
.fillna(
|
|
290
|
+
{"group_prob": 0.0}
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# ------------------------------------------------------
|
|
295
|
+
# DISTANCE COMPUTATION
|
|
296
|
+
# ------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
t_closeness_df = self._compute_distance(joined)
|
|
299
|
+
|
|
300
|
+
# ------------------------------------------------------
|
|
301
|
+
# VIOLATING GROUPS
|
|
302
|
+
# ------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
violating_groups = (
|
|
305
|
+
t_closeness_df
|
|
306
|
+
.filter(
|
|
307
|
+
F.col("t_closeness") > self.t_threshold
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# ------------------------------------------------------
|
|
312
|
+
# SUMMARY
|
|
313
|
+
# ------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
summary_df = (
|
|
316
|
+
t_closeness_df
|
|
317
|
+
.agg(
|
|
318
|
+
F.count("*").alias(
|
|
319
|
+
"num_equivalence_groups"
|
|
320
|
+
),
|
|
321
|
+
F.min("t_closeness").alias(
|
|
322
|
+
"min_t"
|
|
323
|
+
),
|
|
324
|
+
F.max("t_closeness").alias(
|
|
325
|
+
"max_t"
|
|
326
|
+
),
|
|
327
|
+
F.round(
|
|
328
|
+
F.avg("t_closeness"),
|
|
329
|
+
5
|
|
330
|
+
).alias(
|
|
331
|
+
"avg_t"
|
|
332
|
+
),
|
|
333
|
+
F.sum(
|
|
334
|
+
F.when(
|
|
335
|
+
F.col("t_closeness")
|
|
336
|
+
> self.t_threshold,
|
|
337
|
+
1
|
|
338
|
+
).otherwise(0)
|
|
339
|
+
).alias(
|
|
340
|
+
"num_violating_groups"
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
.withColumn(
|
|
344
|
+
"t_threshold",
|
|
345
|
+
F.lit(self.t_threshold)
|
|
346
|
+
)
|
|
347
|
+
.withColumn(
|
|
348
|
+
"satisfies_t_closeness",
|
|
349
|
+
F.col("max_t")
|
|
350
|
+
<= F.col("t_threshold")
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return TClosenessResult(
|
|
355
|
+
summary_df=summary_df,
|
|
356
|
+
violating_groups=violating_groups
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def _compute_distance(
|
|
360
|
+
self,
|
|
361
|
+
df: DataFrame
|
|
362
|
+
) -> DataFrame:
|
|
363
|
+
"""
|
|
364
|
+
Computes the selected distance between each equivalence group's
|
|
365
|
+
sensitive-attribute distribution and the global distribution.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
df : pyspark.sql.DataFrame
|
|
370
|
+
DataFrame containing one row for each combination of
|
|
371
|
+
equivalence group and globally observed sensitive value.
|
|
372
|
+
|
|
373
|
+
The DataFrame must contain:
|
|
374
|
+
|
|
375
|
+
- the configured quasi-identifiers,
|
|
376
|
+
- ``group_prob``: probability of the sensitive value inside
|
|
377
|
+
the equivalence group,
|
|
378
|
+
- ``global_prob``: probability of the sensitive value in the
|
|
379
|
+
complete dataset.
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
pyspark.sql.DataFrame
|
|
384
|
+
DataFrame containing one row per equivalence group and a
|
|
385
|
+
``t_closeness`` column with the calculated distance.
|
|
386
|
+
|
|
387
|
+
Notes
|
|
388
|
+
-----
|
|
389
|
+
For ``l1``, the implementation uses total variation distance:
|
|
390
|
+
|
|
391
|
+
0.5 * sum(|P(x) - Q(x)|)
|
|
392
|
+
|
|
393
|
+
For ``jsd``, the implementation uses Jensen-Shannon divergence
|
|
394
|
+
based on logarithms in base 2.
|
|
395
|
+
"""
|
|
396
|
+
if self.distance_metric == "l1":
|
|
397
|
+
return (
|
|
398
|
+
df
|
|
399
|
+
.withColumn(
|
|
400
|
+
"abs_diff",
|
|
401
|
+
F.abs(
|
|
402
|
+
F.col("group_prob")
|
|
403
|
+
- F.col("global_prob")
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
.groupBy(
|
|
407
|
+
*self.quasi_identifiers
|
|
408
|
+
)
|
|
409
|
+
.agg(
|
|
410
|
+
(
|
|
411
|
+
F.sum("abs_diff") / 2
|
|
412
|
+
).alias("t_closeness")
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if self.distance_metric == "jsd":
|
|
417
|
+
epsilon = F.lit(1e-12)
|
|
418
|
+
|
|
419
|
+
p = F.col("group_prob")
|
|
420
|
+
q = F.col("global_prob")
|
|
421
|
+
|
|
422
|
+
m = (p + q) / 2
|
|
423
|
+
|
|
424
|
+
# Terms with probability zero contribute zero to
|
|
425
|
+
# the corresponding KL divergence.
|
|
426
|
+
p_term = F.when(
|
|
427
|
+
p > 0,
|
|
428
|
+
p * F.log2((p + epsilon) / (m + epsilon))
|
|
429
|
+
).otherwise(
|
|
430
|
+
F.lit(0.0)
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
q_term = F.when(
|
|
434
|
+
q > 0,
|
|
435
|
+
q * F.log2((q + epsilon) / (m + epsilon))
|
|
436
|
+
).otherwise(
|
|
437
|
+
F.lit(0.0)
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
jsd_expr = (
|
|
441
|
+
F.lit(0.5) * p_term
|
|
442
|
+
+ F.lit(0.5) * q_term
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
df
|
|
447
|
+
.groupBy(
|
|
448
|
+
*self.quasi_identifiers
|
|
449
|
+
)
|
|
450
|
+
.agg(
|
|
451
|
+
F.sum(
|
|
452
|
+
jsd_expr
|
|
453
|
+
).alias("t_closeness")
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
raise ValueError(
|
|
458
|
+
f"Unsupported distance metric: "
|
|
459
|
+
f"{self.distance_metric}"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class TClosenessResult:
|
|
464
|
+
"""
|
|
465
|
+
Container for t-closeness results.
|
|
466
|
+
|
|
467
|
+
The object stores both the global summary generated by the
|
|
468
|
+
t-closeness analysis and the equivalence groups that violate the
|
|
469
|
+
configured privacy threshold.
|
|
470
|
+
|
|
471
|
+
Attributes
|
|
472
|
+
----------
|
|
473
|
+
summary_df : pyspark.sql.DataFrame
|
|
474
|
+
Aggregated statistics of t-closeness values across all equivalence
|
|
475
|
+
groups, including minimum, maximum and average distances.
|
|
476
|
+
|
|
477
|
+
violating_groups : pyspark.sql.DataFrame
|
|
478
|
+
Subset of equivalence groups whose t-closeness value exceeds the
|
|
479
|
+
configured threshold, representing potential privacy risks.
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
def __init__(
|
|
483
|
+
self,
|
|
484
|
+
summary_df: DataFrame,
|
|
485
|
+
violating_groups: DataFrame
|
|
486
|
+
):
|
|
487
|
+
"""
|
|
488
|
+
Initializes the result object.
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
summary_df : pyspark.sql.DataFrame
|
|
493
|
+
Aggregated statistics describing the t-closeness values of
|
|
494
|
+
the dataset.
|
|
495
|
+
|
|
496
|
+
violating_groups : pyspark.sql.DataFrame
|
|
497
|
+
Equivalence groups whose t-closeness value exceeds the
|
|
498
|
+
configured threshold.
|
|
499
|
+
"""
|
|
500
|
+
self.summary_df = summary_df
|
|
501
|
+
self.violating_groups = violating_groups
|
|
502
|
+
|
|
503
|
+
def show_summary(self):
|
|
504
|
+
"""
|
|
505
|
+
Displays aggregated statistics of t-closeness values.
|
|
506
|
+
|
|
507
|
+
The summary includes:
|
|
508
|
+
|
|
509
|
+
- num_equivalence_groups:
|
|
510
|
+
Total number of equivalence groups.
|
|
511
|
+
|
|
512
|
+
- min_t:
|
|
513
|
+
Minimum t-closeness distance among all equivalence groups.
|
|
514
|
+
|
|
515
|
+
- max_t:
|
|
516
|
+
Maximum t-closeness distance among all equivalence groups.
|
|
517
|
+
|
|
518
|
+
- avg_t:
|
|
519
|
+
Average t-closeness distance across all equivalence groups.
|
|
520
|
+
|
|
521
|
+
- num_violating_groups:
|
|
522
|
+
Number of equivalence groups whose distance exceeds the
|
|
523
|
+
configured t-threshold.
|
|
524
|
+
|
|
525
|
+
- t_threshold:
|
|
526
|
+
Maximum allowed distance configured by the user.
|
|
527
|
+
|
|
528
|
+
- satisfies_t_closeness:
|
|
529
|
+
Boolean indicating whether every equivalence group satisfies
|
|
530
|
+
the configured t-closeness requirement.
|
|
531
|
+
|
|
532
|
+
Interpretation
|
|
533
|
+
--------------
|
|
534
|
+
The dataset satisfies t-closeness when:
|
|
535
|
+
|
|
536
|
+
max_t <= t_threshold
|
|
537
|
+
|
|
538
|
+
and therefore:
|
|
539
|
+
|
|
540
|
+
num_violating_groups = 0
|
|
541
|
+
"""
|
|
542
|
+
self.summary_df.show(
|
|
543
|
+
truncate=False
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def show_violating_groups(
|
|
547
|
+
self,
|
|
548
|
+
n: int = 10,
|
|
549
|
+
sort: bool = False
|
|
550
|
+
):
|
|
551
|
+
"""
|
|
552
|
+
Displays equivalence groups that do not satisfy t-closeness.
|
|
553
|
+
|
|
554
|
+
Parameters
|
|
555
|
+
----------
|
|
556
|
+
n : int, optional
|
|
557
|
+
Number of violating groups to display.
|
|
558
|
+
Default is 10.
|
|
559
|
+
|
|
560
|
+
sort : bool, optional
|
|
561
|
+
Whether to sort violating groups by t-closeness distance in
|
|
562
|
+
descending order.
|
|
563
|
+
|
|
564
|
+
Sorting may increase computational cost for large datasets
|
|
565
|
+
because it requires an additional distributed ordering
|
|
566
|
+
operation.
|
|
567
|
+
|
|
568
|
+
Default is False.
|
|
569
|
+
|
|
570
|
+
Notes
|
|
571
|
+
-----
|
|
572
|
+
The displayed t-closeness values are rounded to five decimal
|
|
573
|
+
places for readability. The underlying values stored in the
|
|
574
|
+
DataFrame are not modified.
|
|
575
|
+
"""
|
|
576
|
+
df = self.violating_groups
|
|
577
|
+
|
|
578
|
+
if sort:
|
|
579
|
+
df = df.orderBy(
|
|
580
|
+
F.col("t_closeness").desc()
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
df = df.withColumn(
|
|
584
|
+
"t_closeness",
|
|
585
|
+
F.round(
|
|
586
|
+
F.col("t_closeness"),
|
|
587
|
+
5
|
|
588
|
+
)
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
df.show(
|
|
592
|
+
n,
|
|
593
|
+
truncate=False
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def get_summary_df(self) -> DataFrame:
|
|
597
|
+
"""
|
|
598
|
+
Returns aggregated statistics of t-closeness values.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
-------
|
|
602
|
+
pyspark.sql.DataFrame
|
|
603
|
+
DataFrame containing the global t-closeness assessment and
|
|
604
|
+
the configured privacy threshold.
|
|
605
|
+
"""
|
|
606
|
+
return self.summary_df
|
|
607
|
+
|
|
608
|
+
def get_violating_groups(self) -> DataFrame:
|
|
609
|
+
"""
|
|
610
|
+
Returns equivalence groups that do not satisfy t-closeness.
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
pyspark.sql.DataFrame
|
|
615
|
+
DataFrame containing the equivalence groups whose t-closeness
|
|
616
|
+
value exceeds the configured threshold.
|
|
617
|
+
"""
|
|
618
|
+
return self.violating_groups
|