diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d80d3a4..4b00fa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,19 +6,57 @@ on: pull_request: jobs: - test: + test-3_10: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + + - name: Run tests + run: pytest -q + + test-3_11: + needs: test-3_10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + + - name: Run tests + run: pytest -q + test-3_12: + needs: test-3_11 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: "3.12" cache: 'pip' - name: Install dependencies @@ -28,5 +66,38 @@ jobs: pip install pytest - name: Run tests + run: pytest -q + + docs: + needs: test-3_12 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install system dependencies + run: sudo apt-get install -y graphviz + + - name: Install package + docs dependencies + run: pip install ".[docs]" + + - name: Build Sphinx docs run: | - pytest -q + sphinx-build --keep-going -b html docs/ docs/_build/html 2>&1 | tee /tmp/sphinx_out.txt + # Fail if any WARNING line is present that is NOT the benign placeholder one + if grep -v "Unknown type: placeholder" /tmp/sphinx_out.txt | grep -q "^WARNING:"; then + echo "Unexpected Sphinx warnings found:" + grep -v "Unknown type: placeholder" /tmp/sphinx_out.txt | grep "^WARNING:" + exit 1 + fi + + - name: Upload HTML docs as artifact + uses: actions/upload-artifact@v4 + with: + name: html-docs + path: docs/_build/html/ + retention-days: 7 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index cc561b3..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Docs - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - # Install Graphviz so Sphinx can import modules that reference it - - name: Install system dependencies - run: sudo apt-get install -y graphviz - - - name: Install package + docs dependencies - run: pip install ".[docs]" - - # -W → warnings become errors, but exclude the known-benign autoapi - # placeholder warning that fires when astroid can't fully introspect - # C-extension types (pandas._libs, numpy C API, etc.). - # We pipe through grep to fail on any OTHER unexpected warning. - - name: Build Sphinx docs - run: | - sphinx-build --keep-going -b html docs/ docs/_build/html 2>&1 | tee /tmp/sphinx_out.txt - # Fail if any WARNING line is present that is NOT the benign placeholder one - if grep -v "Unknown type: placeholder" /tmp/sphinx_out.txt | grep -q "^WARNING:"; then - echo "Unexpected Sphinx warnings found:" - grep -v "Unknown type: placeholder" /tmp/sphinx_out.txt | grep "^WARNING:" - exit 1 - fi - - - name: Upload HTML docs as artifact - uses: actions/upload-artifact@v4 - with: - name: html-docs - path: docs/_build/html/ - retention-days: 7 diff --git a/README.md b/README.md index c491fa4..13c4148 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,158 @@ DPG is a model-agnostic tool to provide a global interpretation of tree-based en DPG is a graph structure that captures the tree-based ensemble model and learned dataset details, preserving the relations among features, logical decisions, and predictions towards emphasising insightful points. DPG enables graph-based evaluations and the identification of model decisions towards facilitating comparisons between features and their associated values while offering insights into the entire model. DPG provides descriptive metrics that enhance the understanding of the decisions inherent in the model, offering valuable insights. -

- -

+ +```mermaid +flowchart LR + 979618565717729770316449045241379584267506428030["F1 <= 0.15"] + style 979618565717729770316449045241379584267506428030 fill:#deebf7,color:#000 + 400374097622164785150640648135130385802573046609["F4 > 8.595"] + style 400374097622164785150640648135130385802573046609 fill:#deebf7,color:#000 + 588807308583655218865209145046442769682314462588["Class C"] + style 588807308583655218865209145046442769682314462588 fill:#9dc3e6,color:#000 + 100415085763263903852039295602911702948457746142["F4 <= 10.305"] + style 100415085763263903852039295602911702948457746142 fill:#deebf7,color:#000 + 796167064376282459648852036451958843494915092088["F4 <= 9.23"] + style 796167064376282459648852036451958843494915092088 fill:#deebf7,color:#000 + 1210267766209222920336595164156346343579215270195["Class D"] + style 1210267766209222920336595164156346343579215270195 fill:#9dc3e6,color:#000 + 1453090674414881797846805718821638125042633934677["F4 > 9.23"] + style 1453090674414881797846805718821638125042633934677 fill:#deebf7,color:#000 + 1018563747240538230561512835449441483670234791480["F1 <= 0.275"] + style 1018563747240538230561512835449441483670234791480 fill:#deebf7,color:#000 + 604794083049618193149218731106783663700431359465["F6 <= 36.634998"] + style 604794083049618193149218731106783663700431359465 fill:#deebf7,color:#000 + 219056158314876120791781277892726213829941686289["F1 > 0.275"] + style 219056158314876120791781277892726213829941686289 fill:#deebf7,color:#000 + 971674044816125407632765233907262716905523475854["F4 <= 8.595"] + style 971674044816125407632765233907262716905523475854 fill:#deebf7,color:#000 + 690715845280696214418205697557183424080425915385["Class B"] + style 690715845280696214418205697557183424080425915385 fill:#9dc3e6,color:#000 + 373114052074461482953194598311845305553485338248["F3 <= 0.43"] + style 373114052074461482953194598311845305553485338248 fill:#deebf7,color:#000 + 130059101401781709675883937374914161454065576473["F7 > -1.54"] + style 130059101401781709675883937374914161454065576473 fill:#deebf7,color:#000 + 946778044485377185187951514693838972119007272915["F3 <= 0.515"] + style 946778044485377185187951514693838972119007272915 fill:#deebf7,color:#000 + 931261134179154107191046451665025768645110437780["F7 <= -5.635"] + style 931261134179154107191046451665025768645110437780 fill:#deebf7,color:#000 + 581835747318489062599131910623234493662676984432["Class A"] + style 581835747318489062599131910623234493662676984432 fill:#9dc3e6,color:#000 + 95328426820969179252858136277969691480959498483["F7 > -6.97"] + style 95328426820969179252858136277969691480959498483 fill:#deebf7,color:#000 + 838025964173435133002132899454873804629558551595["F1 <= 0.265"] + style 838025964173435133002132899454873804629558551595 fill:#deebf7,color:#000 + 157558602123247801017580030895190959370148853051["F7 > -5.635"] + style 157558602123247801017580030895190959370148853051 fill:#deebf7,color:#000 + 15000782385466634000392697558394770189706855891["F10 <= 91.349998"] + style 15000782385466634000392697558394770189706855891 fill:#deebf7,color:#000 + 737571175891001486850004482015570617772703609595["F7 > 4.94"] + style 737571175891001486850004482015570617772703609595 fill:#deebf7,color:#000 + 820268771082212553636570576938320986082031670022["F7 <= 4.755"] + style 820268771082212553636570576938320986082031670022 fill:#deebf7,color:#000 + 222225252368107721717009608605956582502482651473["F10 > 91.349998"] + style 222225252368107721717009608605956582502482651473 fill:#deebf7,color:#000 + 1290399266530111018100832136017555924837222381379["F6 > 38.915001"] + style 1290399266530111018100832136017555924837222381379 fill:#deebf7,color:#000 + 1432524820656786198276600212795640928391034583565["F9 <= 32.765001"] + style 1432524820656786198276600212795640928391034583565 fill:#deebf7,color:#000 + 1216879825140622668422253624475828723202673476705["F3 > 0.515"] + style 1216879825140622668422253624475828723202673476705 fill:#deebf7,color:#000 + 721812667918485594407398948094746476301162165281["F6 > 36.634998"] + style 721812667918485594407398948094746476301162165281 fill:#deebf7,color:#000 + 320172993420859182112091033633361489749254219598["F1 > 0.265"] + style 320172993420859182112091033633361489749254219598 fill:#deebf7,color:#000 + 1376178018489663700667465426715142260552127285659["F7 <= -1.54"] + style 1376178018489663700667465426715142260552127285659 fill:#deebf7,color:#000 + 1425576715341752496625268860315092560916679831871["F10 <= 100.09"] + style 1425576715341752496625268860315092560916679831871 fill:#deebf7,color:#000 + 1187091628211292434711457323232000634217732658464["F3 > 0.43"] + style 1187091628211292434711457323232000634217732658464 fill:#deebf7,color:#000 + 464506691893636266372305649238338629786578496086["F6 <= 38.915001"] + style 464506691893636266372305649238338629786578496086 fill:#deebf7,color:#000 + 1410694826222849576728516717748878457342387233404["F2 <= 3.075"] + style 1410694826222849576728516717748878457342387233404 fill:#deebf7,color:#000 + 50305701467499117808883646224307030877008964123["F1 > 0.15"] + style 50305701467499117808883646224307030877008964123 fill:#deebf7,color:#000 + 752639284302739662244140925771936984286998982681["F4 > 10.305"] + style 752639284302739662244140925771936984286998982681 fill:#deebf7,color:#000 + 421447506865222128999782636005206948808242116589["F2 > 3.075"] + style 421447506865222128999782636005206948808242116589 fill:#deebf7,color:#000 + 517945134451617424034782581737551822021612382097["F7 > 4.755"] + style 517945134451617424034782581737551822021612382097 fill:#deebf7,color:#000 + 732610326773121789369223421019172016167699952227["F2 <= 3.085"] + style 732610326773121789369223421019172016167699952227 fill:#deebf7,color:#000 + 75153629346265600498761343753901957591568438946["F7 <= -6.97"] + style 75153629346265600498761343753901957591568438946 fill:#deebf7,color:#000 + 1309043412683735018336339302863613748313349494003["F2 > 3.085"] + style 1309043412683735018336339302863613748313349494003 fill:#deebf7,color:#000 + 1137628253617425639228459882552895355870584674508["F7 <= 4.94"] + style 1137628253617425639228459882552895355870584674508 fill:#deebf7,color:#000 + 1220756731626743842054238342928376272838209981252["F10 > 100.09"] + style 1220756731626743842054238342928376272838209981252 fill:#deebf7,color:#000 + 466099670086209313380749716948554630644411676678["F9 > 32.765001"] + style 466099670086209313380749716948554630644411676678 fill:#deebf7,color:#000 + 979618565717729770316449045241379584267506428030 -->|"1.0000"| 400374097622164785150640648135130385802573046609 + 979618565717729770316449045241379584267506428030 -->|"3.0000"| 971674044816125407632765233907262716905523475854 + 400374097622164785150640648135130385802573046609 -->|"1.0000"| 588807308583655218865209145046442769682314462588 + 100415085763263903852039295602911702948457746142 -->|"1.0000"| 796167064376282459648852036451958843494915092088 + 100415085763263903852039295602911702948457746142 -->|"2.0000"| 1453090674414881797846805718821638125042633934677 + 796167064376282459648852036451958843494915092088 -->|"1.0000"| 1210267766209222920336595164156346343579215270195 + 1453090674414881797846805718821638125042633934677 -->|"2.0000"| 588807308583655218865209145046442769682314462588 + 1018563747240538230561512835449441483670234791480 -->|"3.0000"| 604794083049618193149218731106783663700431359465 + 1018563747240538230561512835449441483670234791480 -->|"7.0000"| 721812667918485594407398948094746476301162165281 + 604794083049618193149218731106783663700431359465 -->|"3.0000"| 588807308583655218865209145046442769682314462588 + 219056158314876120791781277892726213829941686289 -->|"3.0000"| 100415085763263903852039295602911702948457746142 + 219056158314876120791781277892726213829941686289 -->|"15.0000"| 752639284302739662244140925771936984286998982681 + 971674044816125407632765233907262716905523475854 -->|"3.0000"| 690715845280696214418205697557183424080425915385 + 373114052074461482953194598311845305553485338248 -->|"3.0000"| 130059101401781709675883937374914161454065576473 + 373114052074461482953194598311845305553485338248 -->|"9.0000"| 1376178018489663700667465426715142260552127285659 + 130059101401781709675883937374914161454065576473 -->|"3.0000"| 588807308583655218865209145046442769682314462588 + 946778044485377185187951514693838972119007272915 -->|"4.0000"| 931261134179154107191046451665025768645110437780 + 946778044485377185187951514693838972119007272915 -->|"18.0000"| 157558602123247801017580030895190959370148853051 + 931261134179154107191046451665025768645110437780 -->|"4.0000"| 581835747318489062599131910623234493662676984432 + 95328426820969179252858136277969691480959498483 -->|"4.0000"| 838025964173435133002132899454873804629558551595 + 95328426820969179252858136277969691480959498483 -->|"9.0000"| 320172993420859182112091033633361489749254219598 + 838025964173435133002132899454873804629558551595 -->|"4.0000"| 581835747318489062599131910623234493662676984432 + 157558602123247801017580030895190959370148853051 -->|"4.0000"| 979618565717729770316449045241379584267506428030 + 157558602123247801017580030895190959370148853051 -->|"14.0000"| 50305701467499117808883646224307030877008964123 + 15000782385466634000392697558394770189706855891 -->|"5.0000"| 737571175891001486850004482015570617772703609595 + 15000782385466634000392697558394770189706855891 -->|"29.0000"| 1137628253617425639228459882552895355870584674508 + 15000782385466634000392697558394770189706855891 -->|"35.0000"| 588807308583655218865209145046442769682314462588 + 737571175891001486850004482015570617772703609595 -->|"5.0000"| 1210267766209222920336595164156346343579215270195 + 820268771082212553636570576938320986082031670022 -->|"5.0000"| 222225252368107721717009608605956582502482651473 + 820268771082212553636570576938320986082031670022 -->|"35.0000"| 15000782385466634000392697558394770189706855891 + 222225252368107721717009608605956582502482651473 -->|"10.0000"| 690715845280696214418205697557183424080425915385 + 1290399266530111018100832136017555924837222381379 -->|"5.0000"| 222225252368107721717009608605956582502482651473 + 1290399266530111018100832136017555924837222381379 -->|"34.0000"| 15000782385466634000392697558394770189706855891 + 1432524820656786198276600212795640928391034583565 -->|"5.0000"| 1216879825140622668422253624475828723202673476705 + 1432524820656786198276600212795640928391034583565 -->|"22.0000"| 946778044485377185187951514693838972119007272915 + 1216879825140622668422253624475828723202673476705 -->|"5.0000"| 581835747318489062599131910623234493662676984432 + 721812667918485594407398948094746476301162165281 -->|"7.0000"| 690715845280696214418205697557183424080425915385 + 320172993420859182112091033633361489749254219598 -->|"9.0000"| 690715845280696214418205697557183424080425915385 + 1376178018489663700667465426715142260552127285659 -->|"9.0000"| 690715845280696214418205697557183424080425915385 + 1425576715341752496625268860315092560916679831871 -->|"10.0000"| 1187091628211292434711457323232000634217732658464 + 1425576715341752496625268860315092560916679831871 -->|"12.0000"| 373114052074461482953194598311845305553485338248 + 1187091628211292434711457323232000634217732658464 -->|"10.0000"| 690715845280696214418205697557183424080425915385 + 464506691893636266372305649238338629786578496086 -->|"10.0000"| 1018563747240538230561512835449441483670234791480 + 464506691893636266372305649238338629786578496086 -->|"18.0000"| 219056158314876120791781277892726213829941686289 + 1410694826222849576728516717748878457342387233404 -->|"13.0000"| 95328426820969179252858136277969691480959498483 + 1410694826222849576728516717748878457342387233404 -->|"24.0000"| 75153629346265600498761343753901957591568438946 + 50305701467499117808883646224307030877008964123 -->|"14.0000"| 690715845280696214418205697557183424080425915385 + 752639284302739662244140925771936984286998982681 -->|"15.0000"| 1210267766209222920336595164156346343579215270195 + 421447506865222128999782636005206948808242116589 -->|"19.0000"| 517945134451617424034782581737551822021612382097 + 421447506865222128999782636005206948808242116589 -->|"40.0000"| 820268771082212553636570576938320986082031670022 + 517945134451617424034782581737551822021612382097 -->|"19.0000"| 1210267766209222920336595164156346343579215270195 + 732610326773121789369223421019172016167699952227 -->|"22.0000"| 1425576715341752496625268860315092560916679831871 + 732610326773121789369223421019172016167699952227 -->|"34.0000"| 1220756731626743842054238342928376272838209981252 + 75153629346265600498761343753901957591568438946 -->|"24.0000"| 581835747318489062599131910623234493662676984432 + 1309043412683735018336339302863613748313349494003 -->|"28.0000"| 464506691893636266372305649238338629786578496086 + 1309043412683735018336339302863613748313349494003 -->|"39.0000"| 1290399266530111018100832136017555924837222381379 + 1137628253617425639228459882552895355870584674508 -->|"29.0000"| 588807308583655218865209145046442769682314462588 + 1220756731626743842054238342928376272838209981252 -->|"34.0000"| 581835747318489062599131910623234493662676984432 + 466099670086209313380749716948554630644411676678 -->|"37.0000"| 1410694826222849576728516717748878457342387233404 + 466099670086209313380749716948554630644411676678 -->|"59.0000"| 421447506865222128999782636005206948808242116589 +``` --- @@ -26,9 +175,93 @@ The concept behind DPG is to convert a generic tree-based ensemble model for cla - Nodes represent predicates, i.e., the feature-value associations present in each node of every tree; - Edges denote the frequency with which these predicates are satisfied during the model training phase by the samples of the dataset. -

- -

+```mermaid +%%{init: {"flowchart": {"nodeSpacing": 70, "rankSpacing": 15}}}%% + +flowchart TB + subgraph trees ["Tree Base Learners"] + direction TB + subgraph tree2 [" "] + direction TB + T2R(("F1, val3")) + T2C["Class"] + T2L(("F2, val2")) + T2RL["..."] + T2RR["..."] + T2R -- "≤" --> T2C + T2R -- ">" --> T2L + T2L -- "≤" --> T2RL + T2L -- ">" --> T2RR + end + subgraph tree1 [" "] + direction TB + T1R(("F1, val1")) + T1L(("F2, val2")) + T1C["Class"] + T1LL["..."] + T1LR["..."] + T1R -- "≤" --> T1L + T1R -- ">" --> T1C + T1L -- "≤" --> T1LL + T1L -- ">" --> T1LR + end + end + + trees ==> dpg + + subgraph dpg ["DPG"] + direction TB + + E["F2 ≤ val2"] + FF["F2 > val2"] + G["Class"] + EoutLL["..."] + EoutLR["..."] + + E -- "w4" --> EoutLL + FF -- "w5" --> EoutLR + + subgraph f1line [" "] + direction LR + A["F1 ≤ val1"] + B["F1 > val3"] + C["F1 > val1"] + D["F1 ≤ val3"] + end + + A -- "w1" --> E + A -- "w6" --> FF + B -- "w2" --> E + B -- "w7" --> FF + C -- "w3" --> G + D -- "w8" --> G + end + + style f1line fill:transparent,stroke:transparent,color:transparent + style T1C fill:#a7d294,color:#000,stroke:#5a8a4a + style T2C fill:#a7d294,color:#000,stroke:#5a8a4a + style T1R fill:#e1f0db,color:#000,stroke:#89a + style T1L fill:#e1f0db,color:#000,stroke:#89a + style T2R fill:#e1f0db,color:#000,stroke:#89a + style T2L fill:#e1f0db,color:#000,stroke:#89a + style G fill:#4a86c8,color:#fff,stroke:#336 + style A fill:#d4e4f7,color:#000,stroke:#89a + style B fill:#d4e4f7,color:#000,stroke:#89a + style C fill:#d4e4f7,color:#000,stroke:#89a + style D fill:#d4e4f7,color:#000,stroke:#89a + style E fill:#d4e4f7,color:#000,stroke:#89a + style FF fill:#d4e4f7,color:#000,stroke:#89a + style T1LL fill:transparent,stroke:transparent + style T1LR fill:transparent,stroke:transparent + style T2RL fill:transparent,stroke:transparent + style T2RR fill:transparent,stroke:transparent + style EoutLL fill:transparent,stroke:transparent + style EoutLR fill:transparent,stroke:transparent + style EoutLR fill:transparent,stroke:transparent + style tree2 fill:transparent,stroke:transparent + style tree1 fill:transparent,stroke:transparent + +``` ## Metrics The graph-based nature of DPG provides significant enhancements in the direction of a complete mapping of the ensemble structure. diff --git a/dpg/__init__.py b/dpg/__init__.py index 6117ccd..13bad01 100644 --- a/dpg/__init__.py +++ b/dpg/__init__.py @@ -5,6 +5,7 @@ class_feature_predicate_counts, class_lookup_from_target_names, classwise_feature_bounds_from_communities, + export_dpg_html, plot_dpg, plot_dpg_class_bounds_vs_dataset_feature_ranges, plot_dpg_constraints_overview, @@ -18,6 +19,7 @@ "DecisionPredicateGraph", "DPGExplainer", "DPGExplanation", + "export_dpg_html", "plot_dpg", "plot_dpg_reg", "plot_dpg_constraints_overview", diff --git a/dpg/explainer.py b/dpg/explainer.py index be8050a..faff751 100644 --- a/dpg/explainer.py +++ b/dpg/explainer.py @@ -144,6 +144,7 @@ def plot( pdf_dpi: int = 600, show: bool = True, export_pdf: bool = False, + export_html: bool = False, ) -> None: """Render a standard DPG plot.""" if explanation is None: @@ -165,6 +166,7 @@ def plot( pdf_dpi=pdf_dpi, show=show, export_pdf=export_pdf, + export_html=export_html, ) def plot_communities( @@ -182,6 +184,7 @@ def plot_communities( pdf_dpi: int = 600, show: bool = True, export_pdf: bool = False, + export_html: bool = False, community_threshold: float = 0.2, ) -> None: """Render a community-colored DPG plot.""" @@ -206,6 +209,7 @@ def plot_communities( pdf_dpi=pdf_dpi, show=show, export_pdf=export_pdf, + export_html=export_html, ) def plot_lrc_importance( diff --git a/dpg/sklearn_dpg.py b/dpg/sklearn_dpg.py index 0b3c10f..cc693ab 100644 --- a/dpg/sklearn_dpg.py +++ b/dpg/sklearn_dpg.py @@ -88,7 +88,8 @@ def test_dpg(datasets: str, clusters_flag: bool = False, threshold_clusters: Optional[float] = None, class_flag: bool = False, - seed:int = 160898) -> Optional[Tuple[Any, Any]]: + seed:int = 160898, + export_format: str = "svg") -> Optional[Tuple[Any, Any]]: """ Unified function to train models and extract DPG for both standard and custom datasets. @@ -193,7 +194,40 @@ def test_dpg(datasets: str, if '.' in datasets else datasets ) plot_name += f"_{model_name}_l{n_learners}_pv{perc_var}_t{decimal_threshold}_{seed}" - + + # 1. Basic (plain) DPG + plot_dpg( + plot_name, + dot, + df, + df_edges, + save_dir=save_plot_dir, + class_flag=class_flag, + export_format=export_format, + ) + + # 2. One plot per node-metric attribute + node_attributes = [ + "Degree", + "In degree nodes", + "Out degree nodes", + "Betweenness centrality", + "Local reaching centrality", + ] + for attr in node_attributes: + if attr in df.columns: + plot_dpg( + plot_name, + dot, + df, + df_edges, + save_dir=save_plot_dir, + attribute=attr, + class_flag=class_flag, + export_format=export_format, + ) + + # 3. Communities plot if communities: plot_dpg_communities( plot_name, @@ -203,18 +237,21 @@ def test_dpg(datasets: str, save_dir=save_plot_dir, class_flag=class_flag, df_edges=df_edges, + export_format=export_format, ) - else: + + # 4. Clusters plot + if clusters is not None: plot_dpg( plot_name, dot, df, df_edges, save_dir=save_plot_dir, - attribute=attribute, clusters=clusters, threshold_clusters=threshold_clusters, class_flag=class_flag, + export_format=export_format, ) return df, df_edges, df_dpg, clusters, node_prob, confidence diff --git a/dpg/visualizer.py b/dpg/visualizer.py index fefd063..bdca208 100644 --- a/dpg/visualizer.py +++ b/dpg/visualizer.py @@ -1,3 +1,4 @@ +import json import os import re import warnings @@ -104,6 +105,213 @@ def _pipe_graph_png_with_fallback(dot_source: str, sanitizer) -> bytes: except Exception: raise first_exc + +def _pipe_graph_svg_with_fallback(dot_source: str, sanitizer) -> bytes: + try: + return Source(dot_source).pipe(format="svg") + except ExecutableNotFound as exc: + raise _graphviz_not_found_error() from exc + except Exception as first_exc: + try: + return Source(sanitizer(dot_source)).pipe(format="svg") + except ExecutableNotFound as exc: + raise _graphviz_not_found_error() from exc + except Exception: + raise first_exc + +def _parse_dot_node_data(dot) -> Dict[str, Dict[str, str]]: + """Extract node labels and fill colours from a Graphviz Digraph body. + + Handles both quoted (``"id"``) and unquoted (bare integer hash) node IDs. + """ + _DOT_KEYWORDS = {"graph", "node", "edge", "subgraph", "digraph", "strict"} + node_data: Dict[str, Dict[str, str]] = {} + for line in dot.body: + if "->" in line: + continue + # Match optional-quoted node ID followed by an attribute block. + id_match = re.match(r'^\s*"?([^"\s\[]+)"?\s*\[', line) + if not id_match: + continue + nid = id_match.group(1).strip() + if nid.lower() in _DOT_KEYWORDS: + continue + if nid not in node_data: + node_data[nid] = {} + label_m = re.search(r'label="([^"]*)"', line) + color_m = re.search(r'fillcolor="(#[0-9a-fA-F]{6})"', line) + if label_m: + node_data[nid]["label"] = label_m.group(1) + if color_m: + node_data[nid]["fillcolor"] = color_m.group(1) + return node_data + + +def _get_graphviz_layout_positions(dot_source: str) -> Dict[str, Tuple[float, float]]: + """Return Graphviz-computed {node_name: (x, y)} positions in vis.js coordinates.""" + try: + raw = Source(dot_source).pipe(format="json") + layout = json.loads(raw) + bb = layout.get("bb", "") + bb_vals = [float(v) for v in bb.split(",")] if bb else [] + graph_height = bb_vals[3] if len(bb_vals) >= 4 else 0.0 + positions: Dict[str, Tuple[float, float]] = {} + for obj in layout.get("objects", []): + pos_str = obj.get("pos", "") + if not pos_str or "," not in pos_str: + continue + x_str, y_str = pos_str.split(",", 1) + # Flip Y: Graphviz origin is bottom-left; vis.js origin is top-left. + positions[obj["name"]] = (float(x_str), graph_height - float(y_str)) + return positions + except Exception: + return {} + + +def export_dpg_html( + dot, + df_edges: pd.DataFrame, + output_path: str, + height: str = "750px", + width: str = "100%", + physics: bool = False, +) -> None: + """Export an interactive pan/zoom HTML visualisation of the DPG using pyvis. + + Preserves the Graphviz-computed layout by default (``physics=False``). + The resulting ``.html`` file is fully self-contained and can be opened in + any browser or hosted on GitHub Pages for a link in the README. + + Args: + dot: Fully-styled Graphviz Digraph (after ``plot_dpg`` / ``plot_dpg_communities`` coloring). + df_edges: DataFrame with edge metrics; must include ``'Source_id'``, + ``'Target_id'``, and ``'Weight'`` columns. + output_path: Destination ``.html`` file path. + height: CSS height of the vis.js canvas (default ``"750px"``). Accepts + any valid CSS unit, e.g. ``"100vh"``. + width: CSS width of the vis.js canvas (default ``"100%"``). Use + ``"100vw"`` for a full-viewport layout. + physics: If ``True``, enable vis.js force-directed physics so nodes + rearrange freely. Default is ``False`` (fixed Graphviz layout). + + Raises: + ImportError: When ``pyvis`` is not installed. + """ + try: + from pyvis.network import Network + except ImportError: + raise ImportError( + "pyvis is required for HTML export. " + "Install it with: pip install pyvis" + ) + + node_data = _parse_dot_node_data(dot) + positions = _get_graphviz_layout_positions(dot.source) + + net = Network( + height=height, + width=width, + directed=True, + bgcolor="#ffffff", + cdn_resources="in_line", + ) + net.set_options(json.dumps({ + "physics": {"enabled": physics}, + "interaction": {"navigationButtons": True, "keyboard": True}, + "edges": {"arrows": {"to": {"enabled": True, "scaleFactor": 0.6}}}, + })) + + for nid, attrs in node_data.items(): + label = attrs.get("label", nid) + color = attrs.get("fillcolor", "#dee1f7") + r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16) + brightness = (r * 299 + g * 587 + b * 114) / 1000 + font_color = "white" if brightness < 100 else "black" + kwargs: Dict[str, Any] = dict( + label=label, + color={"background": color, "border": color, + "highlight": {"background": color, "border": "#333333"}}, + font={"color": font_color, "size": 12}, + shape="box", + title=label, + ) + if nid in positions: + kwargs["x"], kwargs["y"] = positions[nid] + net.add_node(nid, **kwargs) + + if df_edges is not None and not df_edges.empty: + max_w = df_edges["Weight"].max() + min_w = df_edges["Weight"].min() + w_range = max(max_w - min_w, 1e-9) + for _, row in df_edges.iterrows(): + norm_w = (row["Weight"] - min_w) / w_range + grey_val = int(180 - 130 * norm_w) # dark grey for heavier edges + net.add_edge( + str(row["Source_id"]), + str(row["Target_id"]), + width=1.0 + 4.0 * norm_w, + color="#{v:02x}{v:02x}{v:02x}".format(v=grey_val), + title=f"Weight: {row['Weight']:.4f}", + ) + + out_dir = os.path.dirname(output_path) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + net.write_html(output_path) + print(f"Interactive HTML exported → {output_path}") + + +def export_dpg_mermaid( + dot, + df_edges: pd.DataFrame, + output_path: str, +) -> None: + """Export the DPG as a Mermaid flowchart markdown file. + + Args: + dot: Fully-styled Graphviz Digraph. + df_edges: DataFrame with edge metrics; must include ``'Source_id'``, + ``'Target_id'``, and ``'Weight'`` columns. + output_path: Destination ``.md`` file path. + """ + node_data = _parse_dot_node_data(dot) + + def _sanitize_mermaid_id(raw: str) -> str: + return re.sub(r'[^A-Za-z0-9_]', '_', raw) + + def _escape_mermaid_label(label: str) -> str: + return label.replace('"', '#quot;').replace('<', '<').replace('>', '>') + + lines: list[str] = ["```mermaid", "flowchart LR"] + + for nid, attrs in node_data.items(): + safe_id = _sanitize_mermaid_id(nid) + label = _escape_mermaid_label(attrs.get("label", nid)) + fill = attrs.get("fillcolor", "") + lines.append(f' {safe_id}["{label}"]') + if fill: + lines.append(f' style {safe_id} fill:{fill}') + + if df_edges is not None and not df_edges.empty: + for _, row in df_edges.iterrows(): + src = _sanitize_mermaid_id(str(row["Source_id"])) + tgt = _sanitize_mermaid_id(str(row["Target_id"])) + weight = row["Weight"] + lines.append(f' {src} -->|"{weight:.4f}"| {tgt}') + + lines.append("```") + + out_dir = os.path.dirname(output_path) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + with open(output_path, "w", encoding="utf-8") as f: + f.write("\n".join(lines) + "\n") + print(f"Mermaid markdown exported → {output_path}") + + +_VALID_EXPORT_FORMATS = {"png", "svg", "pdf", "html", "mermaid"} + + def plot_dpg( plot_name, dot, @@ -122,7 +330,7 @@ def plot_dpg( dpi=300, pdf_dpi=600, show=True, - export_pdf=False, + export_format="svg", ): """ Plot a Decision Predicate Graph (DPG) with optional node/edge styling. @@ -150,13 +358,17 @@ def plot_dpg( values. fig_size: Matplotlib figure size as ``(width, height)``. dpi: PNG export/display resolution. - pdf_dpi: PDF export resolution when ``export_pdf=True``. + pdf_dpi: PDF export resolution when ``export_format`` is ``"pdf"``. show: Whether to display the image via Matplotlib. Default is ``True``. - export_pdf: If ``True``, also writes a PDF next to the PNG. + export_format: Image format to export. One of + ``{'svg', 'png', 'pdf', 'html', 'mermaid'}``. Default is ``"svg"``. Returns: None """ + if export_format not in _VALID_EXPORT_FORMATS: + raise ValueError(f"Unsupported export_format '{export_format}'. " + f"Choose from {sorted(_VALID_EXPORT_FORMATS)}.") print("Plotting DPG...") _apply_layout_template( dot, @@ -309,8 +521,13 @@ def repl(m): # Save the plot to the specified directory os.makedirs(save_dir, exist_ok=True) - fig.savefig(os.path.join(save_dir, plot_name + ".png"), dpi=dpi, bbox_inches="tight", pad_inches=0.02) - if export_pdf: + if export_format == "png": + fig.savefig(os.path.join(save_dir, plot_name + ".png"), dpi=dpi, bbox_inches="tight", pad_inches=0.02) + elif export_format == "svg": + svg_bytes = _pipe_graph_svg_with_fallback(dot.source, _sanitize_dot_source) + with open(os.path.join(save_dir, plot_name + ".svg"), "wb") as f: + f.write(svg_bytes) + elif export_format == "pdf": fig.savefig( os.path.join(save_dir, plot_name + ".pdf"), format="pdf", @@ -318,11 +535,10 @@ def repl(m): bbox_inches="tight", pad_inches=0.02, ) - #plt.show() - # No PDF output by default - - # Clean up temporary files - # delete_folder_contents("temp") + elif export_format == "html": + export_dpg_html(dot, df_edges, os.path.join(save_dir, plot_name + ".html")) + elif export_format == "mermaid": + export_dpg_mermaid(dot, df_edges, os.path.join(save_dir, plot_name + ".md")) if not show: plt.close(fig) @@ -342,7 +558,7 @@ def plot_dpg_communities( dpi=300, pdf_dpi=600, show=True, - export_pdf=False, + export_format="svg", ): """ Plot a DPG colored by community assignment. @@ -369,13 +585,17 @@ def plot_dpg_communities( values. fig_size: Matplotlib figure size as ``(width, height)``. dpi: PNG export/display resolution. - pdf_dpi: PDF export resolution when ``export_pdf=True``. + pdf_dpi: PDF export resolution when ``export_format`` is ``"pdf"``. show: Whether to display the image via Matplotlib. Default is ``True``. - export_pdf: If ``True``, also writes a PDF next to the PNG. + export_format: Image format to export. One of + ``{'svg', 'png', 'pdf', 'html', 'mermaid'}``. Default is ``"svg"``. Returns: None """ + if export_format not in _VALID_EXPORT_FORMATS: + raise ValueError(f"Unsupported export_format '{export_format}'. " + f"Choose from {sorted(_VALID_EXPORT_FORMATS)}.") print("Plotting DPG (communities)...") _apply_layout_template( dot, @@ -481,13 +701,18 @@ def repl(m): # Save the plot to the specified directory with tight borders os.makedirs(save_dir, exist_ok=True) - fig.savefig( - os.path.join(save_dir, plot_name + ".png"), - dpi=dpi, - bbox_inches="tight", - pad_inches=0.02, - ) - if export_pdf: + if export_format == "png": + fig.savefig( + os.path.join(save_dir, plot_name + ".png"), + dpi=dpi, + bbox_inches="tight", + pad_inches=0.02, + ) + elif export_format == "svg": + svg_bytes = _pipe_graph_svg_with_fallback(dot.source, _sanitize_dot_source) + with open(os.path.join(save_dir, plot_name + ".svg"), "wb") as f: + f.write(svg_bytes) + elif export_format == "pdf": fig.savefig( os.path.join(save_dir, plot_name + ".pdf"), format="pdf", @@ -495,12 +720,12 @@ def repl(m): bbox_inches="tight", pad_inches=0.02, ) + elif export_format == "html": + export_dpg_html(dot, df_edges, os.path.join(save_dir, plot_name + ".html")) + elif export_format == "mermaid": + export_dpg_mermaid(dot, df_edges, os.path.join(save_dir, plot_name + ".md")) if not show: plt.close(fig) - # No PDF output by default - - # Clean up temporary files - # delete_folder_contents("temp") def change_node_color(dot, node_id: str, fillcolor: str) -> None: """Update a node's fill color and set an appropriate contrasting font color. diff --git a/dpg_image_examples/custom_RandomForestClassifier_l2_pv0.001_t2_160898.svg b/dpg_image_examples/custom_RandomForestClassifier_l2_pv0.001_t2_160898.svg new file mode 100644 index 0000000..6704ed8 --- /dev/null +++ b/dpg_image_examples/custom_RandomForestClassifier_l2_pv0.001_t2_160898.svg @@ -0,0 +1,697 @@ + + + + + + +dpg + + + +979618565717729770316449045241379584267506428030 + +F1 <= 0.15 + + + +400374097622164785150640648135130385802573046609 + +F4 > 8.595 + + + +979618565717729770316449045241379584267506428030->400374097622164785150640648135130385802573046609 + + +1 + + + +971674044816125407632765233907262716905523475854 + +F4 <= 8.595 + + + +979618565717729770316449045241379584267506428030->971674044816125407632765233907262716905523475854 + + +3 + + + +588807308583655218865209145046442769682314462588 + +Class C + + + +400374097622164785150640648135130385802573046609->588807308583655218865209145046442769682314462588 + + +1 + + + +100415085763263903852039295602911702948457746142 + +F4 <= 10.305 + + + +796167064376282459648852036451958843494915092088 + +F4 <= 9.23 + + + +100415085763263903852039295602911702948457746142->796167064376282459648852036451958843494915092088 + + +1 + + + +1453090674414881797846805718821638125042633934677 + +F4 > 9.23 + + + +100415085763263903852039295602911702948457746142->1453090674414881797846805718821638125042633934677 + + +2 + + + +1210267766209222920336595164156346343579215270195 + +Class D + + + +796167064376282459648852036451958843494915092088->1210267766209222920336595164156346343579215270195 + + +1 + + + +1453090674414881797846805718821638125042633934677->588807308583655218865209145046442769682314462588 + + +2 + + + +1018563747240538230561512835449441483670234791480 + +F1 <= 0.275 + + + +604794083049618193149218731106783663700431359465 + +F6 <= 36.634998 + + + +1018563747240538230561512835449441483670234791480->604794083049618193149218731106783663700431359465 + + +3 + + + +721812667918485594407398948094746476301162165281 + +F6 > 36.634998 + + + +1018563747240538230561512835449441483670234791480->721812667918485594407398948094746476301162165281 + + +7 + + + +604794083049618193149218731106783663700431359465->588807308583655218865209145046442769682314462588 + + +3 + + + +219056158314876120791781277892726213829941686289 + +F1 > 0.275 + + + +219056158314876120791781277892726213829941686289->100415085763263903852039295602911702948457746142 + + +3 + + + +752639284302739662244140925771936984286998982681 + +F4 > 10.305 + + + +219056158314876120791781277892726213829941686289->752639284302739662244140925771936984286998982681 + + +15 + + + +690715845280696214418205697557183424080425915385 + +Class B + + + +971674044816125407632765233907262716905523475854->690715845280696214418205697557183424080425915385 + + +3 + + + +373114052074461482953194598311845305553485338248 + +F3 <= 0.43 + + + +130059101401781709675883937374914161454065576473 + +F7 > -1.54 + + + +373114052074461482953194598311845305553485338248->130059101401781709675883937374914161454065576473 + + +3 + + + +1376178018489663700667465426715142260552127285659 + +F7 <= -1.54 + + + +373114052074461482953194598311845305553485338248->1376178018489663700667465426715142260552127285659 + + +9 + + + +130059101401781709675883937374914161454065576473->588807308583655218865209145046442769682314462588 + + +3 + + + +946778044485377185187951514693838972119007272915 + +F3 <= 0.515 + + + +931261134179154107191046451665025768645110437780 + +F7 <= -5.635 + + + +946778044485377185187951514693838972119007272915->931261134179154107191046451665025768645110437780 + + +4 + + + +157558602123247801017580030895190959370148853051 + +F7 > -5.635 + + + +946778044485377185187951514693838972119007272915->157558602123247801017580030895190959370148853051 + + +18 + + + +581835747318489062599131910623234493662676984432 + +Class A + + + +931261134179154107191046451665025768645110437780->581835747318489062599131910623234493662676984432 + + +4 + + + +95328426820969179252858136277969691480959498483 + +F7 > -6.97 + + + +838025964173435133002132899454873804629558551595 + +F1 <= 0.265 + + + +95328426820969179252858136277969691480959498483->838025964173435133002132899454873804629558551595 + + +4 + + + +320172993420859182112091033633361489749254219598 + +F1 > 0.265 + + + +95328426820969179252858136277969691480959498483->320172993420859182112091033633361489749254219598 + + +9 + + + +838025964173435133002132899454873804629558551595->581835747318489062599131910623234493662676984432 + + +4 + + + +157558602123247801017580030895190959370148853051->979618565717729770316449045241379584267506428030 + + +4 + + + +50305701467499117808883646224307030877008964123 + +F1 > 0.15 + + + +157558602123247801017580030895190959370148853051->50305701467499117808883646224307030877008964123 + + +14 + + + +15000782385466634000392697558394770189706855891 + +F10 <= 91.349998 + + + +15000782385466634000392697558394770189706855891->588807308583655218865209145046442769682314462588 + + +35 + + + +737571175891001486850004482015570617772703609595 + +F7 > 4.94 + + + +15000782385466634000392697558394770189706855891->737571175891001486850004482015570617772703609595 + + +5 + + + +1137628253617425639228459882552895355870584674508 + +F7 <= 4.94 + + + +15000782385466634000392697558394770189706855891->1137628253617425639228459882552895355870584674508 + + +29 + + + +737571175891001486850004482015570617772703609595->1210267766209222920336595164156346343579215270195 + + +5 + + + +820268771082212553636570576938320986082031670022 + +F7 <= 4.755 + + + +820268771082212553636570576938320986082031670022->15000782385466634000392697558394770189706855891 + + +35 + + + +222225252368107721717009608605956582502482651473 + +F10 > 91.349998 + + + +820268771082212553636570576938320986082031670022->222225252368107721717009608605956582502482651473 + + +5 + + + +222225252368107721717009608605956582502482651473->690715845280696214418205697557183424080425915385 + + +10 + + + +1290399266530111018100832136017555924837222381379 + +F6 > 38.915001 + + + +1290399266530111018100832136017555924837222381379->15000782385466634000392697558394770189706855891 + + +34 + + + +1290399266530111018100832136017555924837222381379->222225252368107721717009608605956582502482651473 + + +5 + + + +1432524820656786198276600212795640928391034583565 + +F9 <= 32.765001 + + + +1432524820656786198276600212795640928391034583565->946778044485377185187951514693838972119007272915 + + +22 + + + +1216879825140622668422253624475828723202673476705 + +F3 > 0.515 + + + +1432524820656786198276600212795640928391034583565->1216879825140622668422253624475828723202673476705 + + +5 + + + +1216879825140622668422253624475828723202673476705->581835747318489062599131910623234493662676984432 + + +5 + + + +721812667918485594407398948094746476301162165281->690715845280696214418205697557183424080425915385 + + +7 + + + +320172993420859182112091033633361489749254219598->690715845280696214418205697557183424080425915385 + + +9 + + + +1376178018489663700667465426715142260552127285659->690715845280696214418205697557183424080425915385 + + +9 + + + +1425576715341752496625268860315092560916679831871 + +F10 <= 100.09 + + + +1425576715341752496625268860315092560916679831871->373114052074461482953194598311845305553485338248 + + +12 + + + +1187091628211292434711457323232000634217732658464 + +F3 > 0.43 + + + +1425576715341752496625268860315092560916679831871->1187091628211292434711457323232000634217732658464 + + +10 + + + +1187091628211292434711457323232000634217732658464->690715845280696214418205697557183424080425915385 + + +10 + + + +464506691893636266372305649238338629786578496086 + +F6 <= 38.915001 + + + +464506691893636266372305649238338629786578496086->1018563747240538230561512835449441483670234791480 + + +10 + + + +464506691893636266372305649238338629786578496086->219056158314876120791781277892726213829941686289 + + +18 + + + +1410694826222849576728516717748878457342387233404 + +F2 <= 3.075 + + + +1410694826222849576728516717748878457342387233404->95328426820969179252858136277969691480959498483 + + +13 + + + +75153629346265600498761343753901957591568438946 + +F7 <= -6.97 + + + +1410694826222849576728516717748878457342387233404->75153629346265600498761343753901957591568438946 + + +24 + + + +50305701467499117808883646224307030877008964123->690715845280696214418205697557183424080425915385 + + +14 + + + +752639284302739662244140925771936984286998982681->1210267766209222920336595164156346343579215270195 + + +15 + + + +421447506865222128999782636005206948808242116589 + +F2 > 3.075 + + + +421447506865222128999782636005206948808242116589->820268771082212553636570576938320986082031670022 + + +40 + + + +517945134451617424034782581737551822021612382097 + +F7 > 4.755 + + + +421447506865222128999782636005206948808242116589->517945134451617424034782581737551822021612382097 + + +19 + + + +517945134451617424034782581737551822021612382097->1210267766209222920336595164156346343579215270195 + + +19 + + + +732610326773121789369223421019172016167699952227 + +F2 <= 3.085 + + + +732610326773121789369223421019172016167699952227->1425576715341752496625268860315092560916679831871 + + +22 + + + +1220756731626743842054238342928376272838209981252 + +F10 > 100.09 + + + +732610326773121789369223421019172016167699952227->1220756731626743842054238342928376272838209981252 + + +34 + + + +75153629346265600498761343753901957591568438946->581835747318489062599131910623234493662676984432 + + +24 + + + +1309043412683735018336339302863613748313349494003 + +F2 > 3.085 + + + +1309043412683735018336339302863613748313349494003->1290399266530111018100832136017555924837222381379 + + +39 + + + +1309043412683735018336339302863613748313349494003->464506691893636266372305649238338629786578496086 + + +28 + + + +1137628253617425639228459882552895355870584674508->588807308583655218865209145046442769682314462588 + + +29 + + + +1220756731626743842054238342928376272838209981252->581835747318489062599131910623234493662676984432 + + +34 + + + +466099670086209313380749716948554630644411676678 + +F9 > 32.765001 + + + +466099670086209313380749716948554630644411676678->1410694826222849576728516717748878457342387233404 + + +37 + + + +466099670086209313380749716948554630644411676678->421447506865222128999782636005206948808242116589 + + +59 + + + diff --git a/dpg_image_examples/iris_RandomForestClassifier_l5_pv1e-09_t6_None.html b/dpg_image_examples/iris_RandomForestClassifier_l5_pv1e-09_t6_None.html new file mode 100644 index 0000000..15c5228 --- /dev/null +++ b/dpg_image_examples/iris_RandomForestClassifier_l5_pv1e-09_t6_None.html @@ -0,0 +1,341 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/examples/run_dpg_custom.py b/examples/run_dpg_custom.py index d60b097..12433a4 100644 --- a/examples/run_dpg_custom.py +++ b/examples/run_dpg_custom.py @@ -18,11 +18,20 @@ parser.add_argument("--n_learners", type=int, default=5, help="Number of learners for the Ensemble model") parser.add_argument("--model_name", type=str, default="RandomForestClassifier", help="Chosen tree-based ensemble model") parser.add_argument("--dir", type=str, default=os.path.join(SCRIPT_DIR, "results"), help="Directory to save results") - parser.add_argument("--plot", action='store_true', help="Plot the DPG, add the argument to use it as True") + parser.add_argument("--no-plot", dest='plot', action='store_false', help="Disable exporting the DPG plot image (exported by default)") + parser.set_defaults(plot=True) + parser.add_argument("--format", dest='export_format', type=str, default='svg', + choices=['svg', 'png', 'pdf', 'html', 'mermaid'], + help="Export format for DPG plots (default: svg)") parser.add_argument("--save_plot_dir", type=str, default=os.path.join(SCRIPT_DIR, "results"), help="Directory to save the plot image") parser.add_argument("--attribute", type=str, default=None, help="A specific node attribute to visualize") parser.add_argument("--communities", action='store_true', help="Boolean indicating whether to visualize communities, add the argument to use it as True") + parser.add_argument("--clusters", action='store_true', help="Boolean indicating whether to visualize clusters, add the argument to use it as True") + parser.add_argument("--threshold_clusters", type=float, default=None, help="Threshold for detecting ambiguous nodes in clusters") parser.add_argument("--class_flag", action='store_true', help="Boolean indicating whether to highlight class nodes, add the argument to use it as True") + parser.add_argument("--seed", type=int, default=160898, help="Randomicity control") + parser.add_argument("--pv", type=float, default=None, help="Override perc_var from config") + parser.add_argument("--t", type=int, default=None, help="Override decimal_threshold from config") args = parser.parse_args() config_path = os.path.join(PROJECT_ROOT, "config.yaml") @@ -38,10 +47,15 @@ pv = config['dpg']['default']['perc_var'] t = config['dpg']['default']['decimal_threshold'] j = config['dpg']['default']['n_jobs'] + if args.pv is not None: + pv = args.pv + if args.t is not None: + t = args.t os.makedirs(args.dir, exist_ok=True) - df, df_dpg_metrics = test.test_dpg(datasets = args.dataset, + df, df_edges, df_dpg_metrics, clusters, node_prob, confidence = test.test_dpg( + datasets = args.dataset, target_column = args.target_column, n_learners = args.n_learners, perc_var = pv, @@ -52,8 +66,12 @@ plot = args.plot, save_plot_dir = args.save_plot_dir, attribute = args.attribute, - communities = args.communities, - class_flag = args.class_flag + communities = args.communities, + clusters_flag = args.clusters, + threshold_clusters = args.threshold_clusters, + class_flag = args.class_flag, + seed = args.seed, + export_format = args.export_format ) df.sort_values(['Degree']) diff --git a/examples/run_dpg_standard.py b/examples/run_dpg_standard.py index eb6bb8f..9cf8ed4 100644 --- a/examples/run_dpg_standard.py +++ b/examples/run_dpg_standard.py @@ -21,7 +21,11 @@ parser.add_argument("--l", "--n_learners", type=int, default=5, help="Number of learners for the Random Forest") parser.add_argument("--model_name", type=str, default="RandomForestClassifier", help="Chosen tree-based ensemble model") parser.add_argument("--dir", type=str, default=os.path.join(SCRIPT_DIR, "results"), help="Directory to save results") - parser.add_argument("--plot", action='store_true', help="Plot the DPG, add the argument to use it as True") + parser.add_argument("--no-plot", dest='plot', action='store_false', help="Disable exporting the DPG plot image (exported by default)") + parser.set_defaults(plot=True) + parser.add_argument("--format", dest='export_format', type=str, default='svg', + choices=['svg', 'png', 'pdf', 'html', 'mermaid'], + help="Export format for DPG plots (default: svg)") parser.add_argument("--save_plot_dir", type=str, default=os.path.join(SCRIPT_DIR, "results"), help="Directory to save the plot image") parser.add_argument("--attribute", type=str, default=None, help="A specific node attribute to visualize") parser.add_argument("--communities", action='store_true', help="Boolean indicating whether to visualize communities, add the argument to use it as True") @@ -60,14 +64,15 @@ n_jobs = j, model_name = args.model_name, file_name = os.path.join(args.dir, f'{args.ds}_l{args.l}_seed{args.seed}_stats.txt'), - plot = args.plot, + plot = args.plot, save_plot_dir = args.save_plot_dir, attribute = args.attribute, communities = args.communities, clusters_flag = args.clusters, threshold_clusters = args.threshold_clusters, class_flag = args.class_flag, - seed = args.seed) + seed = args.seed, + export_format = args.export_format) df.sort_values(['Degree']) diff --git a/pyproject.toml b/pyproject.toml index 51409b2..5d62d9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ threadpoolctl = "3.5.0" tqdm = "4.67.1" tzdata = "2024.1" pyyaml = "^6.0.2" +pyvis = ">=0.3.2" # -- optional docs dependencies (pip install ".[docs]") -- sphinx = {version = ">=7.3", optional = true} diff --git a/requirements.txt b/requirements.txt index 659af5e..b31cac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ threadpoolctl==3.5.0 tqdm==4.67.1 tzdata==2024.1 pyyaml==6.0.2 +pyvis>=0.3.2 # counterfactual dependencies seaborn