DPG 0.2.0__tar.gz → 0.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dpg
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A Python library for extracting Decision Predicate Graphs and global explanations from ensemble models.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -54,12 +54,12 @@ Description-Content-Type: text/markdown
54
54
 
55
55
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
56
56
  [![Python Versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12-blue.svg)](pyproject.toml)
57
- [![PyPI](https://img.shields.io/pypi/v/dpg.svg)](https://pypi.org/project/dpg/)
58
57
  [![Build Status](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml/badge.svg)](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml)
59
58
  [![Documentation Status](https://readthedocs.org/projects/dpg/badge/?version=latest)](https://dpg.readthedocs.io/en/latest/)
60
59
 
61
- | ![DPG logo](https://github.com/Meta-Group/DPG/blob/main/DPG.png) |
62
- |:--:|
60
+ <p align="center">
61
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/DPG.png" alt="DPG logo" width="300">
62
+ </p>
63
63
 
64
64
 
65
65
  DPG is a model-agnostic framework for global and local explanation of tree-based ensemble models.
@@ -72,7 +72,9 @@ insightful points. DPG enables graph-based evaluations and the identification of
72
72
  towards facilitating comparisons between features and their associated values while offering insights
73
73
  into the entire model. DPG provides descriptive metrics that enhance the understanding of the
74
74
  decisions inherent in the model, offering valuable insights.
75
- ![DPG overview](https://github.com/Meta-Group/DPG/blob/main/image.png)
75
+ <p align="center">
76
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/image.png" width="600" />
77
+ </p>
76
78
 
77
79
  ---
78
80
 
@@ -98,7 +100,9 @@ The concept behind DPG is to convert a generic tree-based ensemble model for cla
98
100
  - Nodes represent predicates, i.e., the feature-value associations present in each node of every tree;
99
101
  - Edges denote the frequency with which these predicates are satisfied during the model training phase by the samples of the dataset.
100
102
 
101
- ![DPG example](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example.png?raw=true)
103
+ <p align="center">
104
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example.png" width="600" />
105
+ </p>
102
106
 
103
107
  ## Metrics
104
108
  The graph-based nature of DPG provides significant enhancements in the direction of a complete mapping of the ensemble structure.
@@ -112,7 +116,7 @@ The graph-based nature of DPG provides significant enhancements in the direction
112
116
 
113
117
  |Constraints | Betweenness centrality | Local reaching centrality | Community|
114
118
  |------------|------------|--------------|--------------------|
115
- ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_constraints.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_bc.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_lrc.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_community.png) |
119
+ ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_constraints.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_bc.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_lrc.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_community.png) |
116
120
  |Constraints(Class 1) = val3 < F1 ≤ val1, F2 ≤ val2 | BC(F2 ≤ val2) = 4/24 | LRC(F1 ≤ val1) = 6 / 7 | Community(Class 1) = F1 ≤ val1, F2 ≤ val2 |
117
121
 
118
122
  ---
@@ -220,6 +224,7 @@ explainer = DPGExplainer(
220
224
  },
221
225
  "graph_construction": {
222
226
  "mode": "execution_trace", # or "aggregated_transitions"
227
+ "context_order": 1, # 1, an integer > 1, or "auto"
223
228
  },
224
229
  }
225
230
  },
@@ -229,6 +234,31 @@ explainer = DPGExplainer(
229
234
  - `"aggregated_transitions"`: current default behavior; filters path variants first, then discovers the DPG.
230
235
  - `"execution_trace"`: builds directly from raw traces and filters edges instead of whole-path variants when `perc_var > 0`.
231
236
 
237
+ `context_order` controls predicate identity in `execution_trace` mode. `1`
238
+ preserves the legacy graph exactly; an order greater than one uses the last k
239
+ executed predicates as context and keeps one sink per class. `"auto"` resolves
240
+ the smallest order for which every pooled graph path is supported by an
241
+ observed execution trace, including recombinations that only appear after
242
+ multiple hops. Orders greater than one require `mode: "execution_trace"`.
243
+ `context_order="auto"` also requires `execution_trace`; in
244
+ `aggregated_transitions` mode context resolution is not applied.
245
+
246
+ A phantom path is a multi-hop path assembled by pooling edges from different
247
+ tree executions even though no single execution produced it. DPG-k removes
248
+ these recombinations at the resolved context order while retaining one graph.
249
+ The effective order and each node's context are available through
250
+ `get_context_order()` and `get_node_context(node)`.
251
+
252
+ **Regression scope (0.3.0):** `context_order` builds without raising for
253
+ regressors (regression leaves, labeled `"Pred <value>"`, are treated as
254
+ terminal sinks just like class leaves), but "one sink per output" is not a
255
+ defined guarantee for regression the way it is for classification: two
256
+ leaves collapse into the same sink only when their rounded values coincide,
257
+ which is an artifact of label rounding, not a modeled invariant. A
258
+ principled regression sink policy is deferred past 0.3.0. `class_boundaries`
259
+ and `communities` remain classifier-only; calling `explain_global`
260
+ with `communities=True` on a regressor raises a clear `ValueError`.
261
+
232
262
  #### Minimal local workflow
233
263
 
234
264
  ```python
@@ -314,8 +344,8 @@ Important:
314
344
 
315
345
  ## CLI scripts
316
346
  The library contains two different scripts to apply DPG:
317
- - `run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
318
- - `run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
347
+ - `examples/run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
348
+ - `examples/run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
319
349
 
320
350
  ### Implementation notes
321
351
  The library also contains two other essential scripts:
@@ -323,14 +353,17 @@ The library also contains two other essential scripts:
323
353
  - `visualizer.py` contains the functions used to manage the visualization of DPG.
324
354
 
325
355
  ### Output
326
- The DPG output, through `run_dpg_standard.py` or `run_dpg_custom.py`, produces several files:
356
+ The DPG output, through `examples/run_dpg_standard.py` or `examples/run_dpg_custom.py`, produces several files:
327
357
  - the visualization of DPG in a dedicated environment, which can be zoomed and saved;
328
358
  - a `.txt` file containing the DPG metrics;
329
359
  - a `.csv` file containing the information about all the nodes of the DPG and their associated metrics;
330
360
  - a `.txt` file containing the Random Forest statistics (accuracy, confusion matrix, classification report)
331
361
 
332
362
  ### CLI parameter reference
333
- Usage: `python run_dpg_standard.py --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
363
+ Usage: `dpg --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
364
+
365
+ After installing DPG, the `dpg` command is the packaged equivalent of
366
+ `python examples/run_dpg_standard.py`.
334
367
  Where:
335
368
  - `dataset` is the name of the standard classification `sklearn` dataset to be analyzed;
336
369
  - `n_learners` is the number of base learners for the ensemble model;
@@ -357,23 +390,29 @@ Where:
357
390
 
358
391
  Disclaimer: `attribute`, `communities`, and `clusters` are mutually exclusive: DPG supports just one visualization mode at a time.
359
392
 
360
- The usage of `run_dpg_custom.py` is similar, but it requires another parameter:
393
+ The usage of `examples/run_dpg_custom.py` is similar, but it requires another parameter:
361
394
  - `target_column`, which is the name of the column to be used as the target variable;
362
395
  - while `ds` is the path of the directory where the dataset is.
363
396
 
364
- ### Example `run_dpg_standard.py`
397
+ ### Example `examples/run_dpg_standard.py`
365
398
  Some examples can be appreciated in the `examples` folder: https://github.com/Meta-Group/DPG/tree/main/examples
366
399
 
367
400
  In particular, the following DPG is obtained by transforming a Random Forest with 5 base learners, trained on Iris dataset.
368
- The used command is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
369
- ![Iris DPG](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png)
401
+ The used command is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
402
+ <p align="center">
403
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png" width="800" />
404
+ </p>
370
405
 
371
406
  The following visualizations are obtained using the same parameters as the previous example, but they show two different metrics: _Community_ and _Betweenness centrality_.
372
- The used command for showing communities is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
373
- ![Iris communities](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png)
374
-
375
- The used command for showing a specific property is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
376
- ![Iris betweenness centrality](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png)
407
+ The used command for showing communities is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
408
+ <p align="center">
409
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png" width="800" />
410
+ </p>
411
+
412
+ The used command for showing a specific property is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
413
+ <p align="center">
414
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png" width="800" />
415
+ </p>
377
416
 
378
417
  ***
379
418
  ## Citation
@@ -2,12 +2,12 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
4
  [![Python Versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12-blue.svg)](pyproject.toml)
5
- [![PyPI](https://img.shields.io/pypi/v/dpg.svg)](https://pypi.org/project/dpg/)
6
5
  [![Build Status](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml/badge.svg)](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml)
7
6
  [![Documentation Status](https://readthedocs.org/projects/dpg/badge/?version=latest)](https://dpg.readthedocs.io/en/latest/)
8
7
 
9
- | ![DPG logo](https://github.com/Meta-Group/DPG/blob/main/DPG.png) |
10
- |:--:|
8
+ <p align="center">
9
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/DPG.png" alt="DPG logo" width="300">
10
+ </p>
11
11
 
12
12
 
13
13
  DPG is a model-agnostic framework for global and local explanation of tree-based ensemble models.
@@ -20,7 +20,9 @@ insightful points. DPG enables graph-based evaluations and the identification of
20
20
  towards facilitating comparisons between features and their associated values while offering insights
21
21
  into the entire model. DPG provides descriptive metrics that enhance the understanding of the
22
22
  decisions inherent in the model, offering valuable insights.
23
- ![DPG overview](https://github.com/Meta-Group/DPG/blob/main/image.png)
23
+ <p align="center">
24
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/image.png" width="600" />
25
+ </p>
24
26
 
25
27
  ---
26
28
 
@@ -46,7 +48,9 @@ The concept behind DPG is to convert a generic tree-based ensemble model for cla
46
48
  - Nodes represent predicates, i.e., the feature-value associations present in each node of every tree;
47
49
  - Edges denote the frequency with which these predicates are satisfied during the model training phase by the samples of the dataset.
48
50
 
49
- ![DPG example](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example.png?raw=true)
51
+ <p align="center">
52
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example.png" width="600" />
53
+ </p>
50
54
 
51
55
  ## Metrics
52
56
  The graph-based nature of DPG provides significant enhancements in the direction of a complete mapping of the ensemble structure.
@@ -60,7 +64,7 @@ The graph-based nature of DPG provides significant enhancements in the direction
60
64
 
61
65
  |Constraints | Betweenness centrality | Local reaching centrality | Community|
62
66
  |------------|------------|--------------|--------------------|
63
- ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_constraints.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_bc.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_lrc.png) | ![](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/example_community.png) |
67
+ ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_constraints.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_bc.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_lrc.png) | ![](https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example_community.png) |
64
68
  |Constraints(Class 1) = val3 < F1 ≤ val1, F2 ≤ val2 | BC(F2 ≤ val2) = 4/24 | LRC(F1 ≤ val1) = 6 / 7 | Community(Class 1) = F1 ≤ val1, F2 ≤ val2 |
65
69
 
66
70
  ---
@@ -168,6 +172,7 @@ explainer = DPGExplainer(
168
172
  },
169
173
  "graph_construction": {
170
174
  "mode": "execution_trace", # or "aggregated_transitions"
175
+ "context_order": 1, # 1, an integer > 1, or "auto"
171
176
  },
172
177
  }
173
178
  },
@@ -177,6 +182,31 @@ explainer = DPGExplainer(
177
182
  - `"aggregated_transitions"`: current default behavior; filters path variants first, then discovers the DPG.
178
183
  - `"execution_trace"`: builds directly from raw traces and filters edges instead of whole-path variants when `perc_var > 0`.
179
184
 
185
+ `context_order` controls predicate identity in `execution_trace` mode. `1`
186
+ preserves the legacy graph exactly; an order greater than one uses the last k
187
+ executed predicates as context and keeps one sink per class. `"auto"` resolves
188
+ the smallest order for which every pooled graph path is supported by an
189
+ observed execution trace, including recombinations that only appear after
190
+ multiple hops. Orders greater than one require `mode: "execution_trace"`.
191
+ `context_order="auto"` also requires `execution_trace`; in
192
+ `aggregated_transitions` mode context resolution is not applied.
193
+
194
+ A phantom path is a multi-hop path assembled by pooling edges from different
195
+ tree executions even though no single execution produced it. DPG-k removes
196
+ these recombinations at the resolved context order while retaining one graph.
197
+ The effective order and each node's context are available through
198
+ `get_context_order()` and `get_node_context(node)`.
199
+
200
+ **Regression scope (0.3.0):** `context_order` builds without raising for
201
+ regressors (regression leaves, labeled `"Pred <value>"`, are treated as
202
+ terminal sinks just like class leaves), but "one sink per output" is not a
203
+ defined guarantee for regression the way it is for classification: two
204
+ leaves collapse into the same sink only when their rounded values coincide,
205
+ which is an artifact of label rounding, not a modeled invariant. A
206
+ principled regression sink policy is deferred past 0.3.0. `class_boundaries`
207
+ and `communities` remain classifier-only; calling `explain_global`
208
+ with `communities=True` on a regressor raises a clear `ValueError`.
209
+
180
210
  #### Minimal local workflow
181
211
 
182
212
  ```python
@@ -262,8 +292,8 @@ Important:
262
292
 
263
293
  ## CLI scripts
264
294
  The library contains two different scripts to apply DPG:
265
- - `run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
266
- - `run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
295
+ - `examples/run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
296
+ - `examples/run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
267
297
 
268
298
  ### Implementation notes
269
299
  The library also contains two other essential scripts:
@@ -271,14 +301,17 @@ The library also contains two other essential scripts:
271
301
  - `visualizer.py` contains the functions used to manage the visualization of DPG.
272
302
 
273
303
  ### Output
274
- The DPG output, through `run_dpg_standard.py` or `run_dpg_custom.py`, produces several files:
304
+ The DPG output, through `examples/run_dpg_standard.py` or `examples/run_dpg_custom.py`, produces several files:
275
305
  - the visualization of DPG in a dedicated environment, which can be zoomed and saved;
276
306
  - a `.txt` file containing the DPG metrics;
277
307
  - a `.csv` file containing the information about all the nodes of the DPG and their associated metrics;
278
308
  - a `.txt` file containing the Random Forest statistics (accuracy, confusion matrix, classification report)
279
309
 
280
310
  ### CLI parameter reference
281
- Usage: `python run_dpg_standard.py --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
311
+ Usage: `dpg --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
312
+
313
+ After installing DPG, the `dpg` command is the packaged equivalent of
314
+ `python examples/run_dpg_standard.py`.
282
315
  Where:
283
316
  - `dataset` is the name of the standard classification `sklearn` dataset to be analyzed;
284
317
  - `n_learners` is the number of base learners for the ensemble model;
@@ -305,23 +338,29 @@ Where:
305
338
 
306
339
  Disclaimer: `attribute`, `communities`, and `clusters` are mutually exclusive: DPG supports just one visualization mode at a time.
307
340
 
308
- The usage of `run_dpg_custom.py` is similar, but it requires another parameter:
341
+ The usage of `examples/run_dpg_custom.py` is similar, but it requires another parameter:
309
342
  - `target_column`, which is the name of the column to be used as the target variable;
310
343
  - while `ds` is the path of the directory where the dataset is.
311
344
 
312
- ### Example `run_dpg_standard.py`
345
+ ### Example `examples/run_dpg_standard.py`
313
346
  Some examples can be appreciated in the `examples` folder: https://github.com/Meta-Group/DPG/tree/main/examples
314
347
 
315
348
  In particular, the following DPG is obtained by transforming a Random Forest with 5 base learners, trained on Iris dataset.
316
- The used command is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
317
- ![Iris DPG](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png)
349
+ The used command is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
350
+ <p align="center">
351
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png" width="800" />
352
+ </p>
318
353
 
319
354
  The following visualizations are obtained using the same parameters as the previous example, but they show two different metrics: _Community_ and _Betweenness centrality_.
320
- The used command for showing communities is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
321
- ![Iris communities](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png)
322
-
323
- The used command for showing a specific property is `python run_dpg_standard.py --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
324
- ![Iris betweenness centrality](https://github.com/Meta-Group/DPG/blob/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png)
355
+ The used command for showing communities is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
356
+ <p align="center">
357
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png" width="800" />
358
+ </p>
359
+
360
+ The used command for showing a specific property is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
361
+ <p align="center">
362
+ <img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png" width="800" />
363
+ </p>
325
364
 
326
365
  ***
327
366
  ## Citation
@@ -13,6 +13,7 @@ from .visualizer import (
13
13
  classwise_feature_bounds_from_communities,
14
14
  plot_class_feature_complexity,
15
15
  plot_dpg,
16
+ plot_dpg_communities,
16
17
  plot_dpg_class_bounds_vs_dataset_feature_ranges,
17
18
  plot_dpg_constraints_overview,
18
19
  plot_dpg_local_paths_aggregate,
@@ -35,6 +36,7 @@ __all__ = [
35
36
  "DPG_OLIVE_CLASS_PALETTE",
36
37
  "resolve_theme_context",
37
38
  "plot_dpg",
39
+ "plot_dpg_communities",
38
40
  "plot_dpg_local_paths_aggregate",
39
41
  "plot_dpg_reg",
40
42
  "plot_dpg_constraints_overview",
dpg-0.3.0/dpg/cli.py ADDED
@@ -0,0 +1,96 @@
1
+ """Packaged command-line interface for the standard DPG demonstration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Sequence
8
+
9
+ from .sklearn_dpg import test_dpg
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ """Build the parser used by the installed ``dpg`` command."""
14
+ parser = argparse.ArgumentParser(
15
+ description="Train a sklearn tree ensemble and export its DPG metrics."
16
+ )
17
+ parser.add_argument(
18
+ "--dataset",
19
+ "--ds",
20
+ dest="dataset",
21
+ default="iris",
22
+ help="sklearn dataset name or path to a CSV file",
23
+ )
24
+ parser.add_argument(
25
+ "--target_column",
26
+ default=None,
27
+ help="target column when --dataset points to a CSV file",
28
+ )
29
+ parser.add_argument("--n_learners", "--l", dest="n_learners", type=int, default=5)
30
+ parser.add_argument("--model_name", default="RandomForestClassifier")
31
+ parser.add_argument("--dir", default="examples/results", help="output directory")
32
+ parser.add_argument("--plot", action="store_true")
33
+ parser.add_argument("--save_plot_dir", default="examples/results")
34
+ parser.add_argument("--attribute", default=None)
35
+ parser.add_argument("--communities", action="store_true")
36
+ parser.add_argument("--clusters", action="store_true")
37
+ parser.add_argument("--threshold_clusters", type=float, default=None)
38
+ parser.add_argument("--t", type=int, default=3, help="threshold decimal precision")
39
+ parser.add_argument("--class_flag", action="store_true")
40
+ parser.add_argument("--seed", type=int, default=160898)
41
+ parser.add_argument(
42
+ "--pv",
43
+ type=float,
44
+ default=1e-9,
45
+ help="minimum path frequency proportion",
46
+ )
47
+ return parser
48
+
49
+
50
+ def main(argv: Sequence[str] | None = None) -> int:
51
+ """Run the standard DPG workflow and write metrics to disk."""
52
+ args = build_parser().parse_args(argv)
53
+ output_dir = Path(args.dir)
54
+ output_dir.mkdir(parents=True, exist_ok=True)
55
+
56
+ result = test_dpg(
57
+ datasets=args.dataset,
58
+ target_column=args.target_column,
59
+ n_learners=args.n_learners,
60
+ perc_var=args.pv,
61
+ decimal_threshold=args.t,
62
+ n_jobs=-1,
63
+ model_name=args.model_name,
64
+ file_name=str(output_dir / f"{Path(args.dataset).stem}_seed{args.seed}_stats.txt"),
65
+ plot=args.plot,
66
+ save_plot_dir=args.save_plot_dir,
67
+ attribute=args.attribute,
68
+ communities=args.communities,
69
+ clusters_flag=args.clusters,
70
+ threshold_clusters=args.threshold_clusters,
71
+ class_flag=args.class_flag,
72
+ seed=args.seed,
73
+ )
74
+ # ``test_dpg`` signals "insufficient nodes for DPG analysis" with
75
+ # ``(None, None)`` rather than a bare ``None``; guard on shape, not identity.
76
+ if result is None or len(result) != 6:
77
+ return 1
78
+
79
+ df, df_edges, graph_metrics, clusters, node_prob, confidence = result
80
+ stem = Path(args.dataset).stem
81
+ df.to_csv(output_dir / f"{stem}_seed{args.seed}_node_metrics.csv", index=False)
82
+ df_edges.to_csv(output_dir / f"{stem}_seed{args.seed}_edge_metrics.csv", index=False)
83
+ with (output_dir / f"{stem}_seed{args.seed}_dpg_metrics.txt").open("w", encoding="utf-8") as handle:
84
+ for key, value in graph_metrics.items():
85
+ handle.write(f"{key}: {value}\n")
86
+
87
+ if clusters is not None:
88
+ with (output_dir / f"{stem}_seed{args.seed}_clusters.txt").open("w", encoding="utf-8") as handle:
89
+ handle.write(f"Clusters: {clusters}\n")
90
+ handle.write(f"Probability: {node_prob}\n")
91
+ handle.write(f"Confidence: {confidence}\n")
92
+ return 0
93
+
94
+
95
+ if __name__ == "__main__":
96
+ raise SystemExit(main())
@@ -0,0 +1,114 @@
1
+ """Fast resolution of the smallest context order without path enumeration."""
2
+
3
+ from collections import defaultdict
4
+ import math
5
+ from typing import Iterable, Sequence
6
+
7
+
8
+ def _node_windows(sequence: Sequence[str], k: int | float) -> list[object]:
9
+ """Represent a trace as contextual predicate nodes and class sinks."""
10
+ nodes: list[object] = []
11
+ for index, label in enumerate(sequence):
12
+ if str(label).startswith(("Class ", "Pred ")):
13
+ nodes.append(("sink", str(label)))
14
+ continue
15
+ if math.isinf(k):
16
+ context = tuple(sequence[: index + 1])
17
+ else:
18
+ context = tuple(sequence[max(0, index - int(k) + 1) : index + 1])
19
+ nodes.append(("ctx", context))
20
+ return nodes
21
+
22
+
23
+ def path_violations(traces: Iterable[Sequence[str]], k: int | float) -> int:
24
+ """Count pooled-graph paths that are not observed trace prefixes.
25
+
26
+ A pooled DFG can be locally consistent while still recombining after
27
+ several hops. For example, observing ``A-B-C-D`` and ``X-B-C-E`` gives
28
+ every adjacent transition a witness, but also creates the unobserved
29
+ path ``A-B-C-E``. This check builds a trie of observed contextual traces,
30
+ computes each prefix state's future signature bottom-up, and verifies
31
+ that states merged by the same contextual node have the same future
32
+ language. It avoids both exponential simple-path enumeration and the
33
+ potentially large graph-by-trie product.
34
+
35
+ The returned value is a diagnostic count, not a count of all phantom
36
+ paths. A value of zero is the useful acceptance condition: every graph
37
+ path is represented by an observed trace.
38
+ """
39
+ materialized = [tuple(trace) for trace in traces if trace]
40
+ if not materialized:
41
+ return 0
42
+
43
+ trie_children: list[dict[object, int]] = [{}]
44
+ trie_terminal: set[int] = set()
45
+ trie_context: list[object | None] = [None]
46
+
47
+ for sequence in materialized:
48
+ nodes = _node_windows(sequence, k)
49
+ trie_node = 0
50
+ for node in nodes:
51
+ next_node = trie_children[trie_node].get(node)
52
+ if next_node is None:
53
+ next_node = len(trie_children)
54
+ trie_children[trie_node][node] = next_node
55
+ trie_children.append({})
56
+ trie_context.append(node)
57
+ trie_node = next_node
58
+ trie_terminal.add(trie_node)
59
+
60
+ signatures: list[tuple[bool, tuple[tuple[object, object], ...]]] = [
61
+ (False, ())
62
+ ] * len(trie_children)
63
+ for trie_node in range(len(trie_children) - 1, -1, -1):
64
+ children = tuple(
65
+ sorted(
66
+ (
67
+ (child_label, signatures[child_node])
68
+ for child_label, child_node in trie_children[trie_node].items()
69
+ ),
70
+ key=repr,
71
+ )
72
+ )
73
+ signatures[trie_node] = (trie_node in trie_terminal, children)
74
+
75
+ future_by_context: defaultdict[object, set[tuple[bool, tuple]]] = defaultdict(set)
76
+ for trie_node in range(1, len(trie_children)):
77
+ context = trie_context[trie_node]
78
+ assert context is not None
79
+ future_by_context[context].add(signatures[trie_node])
80
+
81
+ return sum(max(0, len(futures) - 1) for futures in future_by_context.values())
82
+
83
+
84
+ def resolve_context_order(
85
+ traces: Iterable[Sequence[str]], max_k: int | None = None
86
+ ) -> tuple[int | float, dict[int | float, int]]:
87
+ """Return the smallest order with no global trace recombination.
88
+
89
+ ``max_k`` defaults to the longest observed trace. At that order every
90
+ observed prefix is distinct, which is a proof-based bound rather than a
91
+ user-facing cap. When a caller supplies a smaller cap, resolution must
92
+ still succeed within that cap; otherwise ``ValueError`` is raised instead
93
+ of returning an order whose history still contains violations.
94
+ """
95
+ if max_k is not None and (
96
+ isinstance(max_k, bool) or not isinstance(max_k, int) or max_k < 1
97
+ ):
98
+ raise ValueError("max_k must be a positive integer or None")
99
+
100
+ materialized = [tuple(trace) for trace in traces if trace]
101
+ if not materialized:
102
+ return 1, {1: 0}
103
+ if max_k is None:
104
+ max_k = max(len(trace) for trace in materialized)
105
+
106
+ history: dict[int | float, int] = {}
107
+ for k in range(1, max_k + 1):
108
+ violations = path_violations(materialized, k)
109
+ history[k] = violations
110
+ if violations == 0:
111
+ return k, history
112
+ raise ValueError(
113
+ f"no context order <= max_k={max_k} eliminates all global trace violations"
114
+ )