From cd9795d32aaf84576012e8318857fbcca5ef9e31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:55:46 +0000 Subject: [PATCH 1/8] Initial plan From ba232cc215d9693670f5a5a34741e7dbc81d2722 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:02:31 +0000 Subject: [PATCH 2/8] feat: add sankey flow plotting helper and unit test Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- _unittests/ut_faq/test_faq_python.py | 21 +++++++++++++- teachpyx/faq/faq_python.py | 42 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/_unittests/ut_faq/test_faq_python.py b/_unittests/ut_faq/test_faq_python.py index e2e0f364..f7aa1c2f 100644 --- a/_unittests/ut_faq/test_faq_python.py +++ b/_unittests/ut_faq/test_faq_python.py @@ -1,6 +1,14 @@ import unittest import datetime -from teachpyx.faq.faq_python import get_month_name, get_day_name, class_getitem +import matplotlib +from teachpyx.faq.faq_python import ( + class_getitem, + get_day_name, + get_month_name, + graph_sankey, +) + +matplotlib.use("Agg") class TestFaqPython(unittest.TestCase): @@ -17,6 +25,17 @@ def test_day_name(self): def test_class_getitem(self): class_getitem() + def test_graph_sankey(self): + ax, diagrams = graph_sankey( + [1, -0.25, -0.75], + labels=["input", "loss", "output"], + orientations=[0, 1, -1], + trunklength=2.0, + title="flux", + ) + self.assertEqual(ax.get_title(), "flux") + self.assertEqual(len(diagrams), 1) + if __name__ == "__main__": unittest.main() diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index 9e4fa380..b2903f0d 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -850,3 +850,45 @@ def __init__(self): a = A[2]() assert a.__class__.__name__ == "A2" + + +def graph_sankey( + flows, labels=None, orientations=None, ax=None, title=None, **kwargs +): + """ + Draws a :epkg:`Sankey` graph to represent flows. + + :param flows: list of positive/negative flows + :param labels: labels associated to each flow + :param orientations: list of orientations (-1, 0, 1) + :param ax: axis to use, if None, creates one + :param title: graph title + :param kwargs: additional parameters forwarded to + ``matplotlib.sankey.Sankey.add`` + :return: axis, sankey diagrams + """ + if len(flows) < 2: + raise ValueError("flows must contain at least two values.") + if abs(sum(flows)) > 1e-10: + raise ValueError("The sum of all flows must be 0.") + if labels is None: + labels = [None] * len(flows) + if orientations is None: + orientations = [0] * len(flows) + if len(labels) != len(flows): + raise ValueError("labels and flows must have the same length.") + if len(orientations) != len(flows): + raise ValueError("orientations and flows must have the same length.") + + import matplotlib.pyplot as plt + from matplotlib.sankey import Sankey + + if ax is None: + _, ax = plt.subplots(1, 1) + + sankey = Sankey(ax=ax) + sankey.add(flows=flows, labels=labels, orientations=orientations, **kwargs) + diagrams = sankey.finish() + if title is not None: + ax.set_title(title) + return ax, diagrams From 78158904fcd3d7790cc4466cdc57be953a092d11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:03:37 +0000 Subject: [PATCH 3/8] chore: name sankey flow balance tolerance constant Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- teachpyx/faq/faq_python.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index b2903f0d..888fb941 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -4,6 +4,8 @@ import os import re +FLOW_BALANCE_TOLERANCE = 1e-10 + def entier_grande_taille(): """ @@ -869,7 +871,7 @@ def graph_sankey( """ if len(flows) < 2: raise ValueError("flows must contain at least two values.") - if abs(sum(flows)) > 1e-10: + if abs(sum(flows)) > FLOW_BALANCE_TOLERANCE: raise ValueError("The sum of all flows must be 0.") if labels is None: labels = [None] * len(flows) From eec9f8e064a5ced082da4fca6dabad2e2c02ffba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:04:39 +0000 Subject: [PATCH 4/8] style: mark sankey tolerance constant as private Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- teachpyx/faq/faq_python.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index 888fb941..3dfdfecf 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -4,7 +4,7 @@ import os import re -FLOW_BALANCE_TOLERANCE = 1e-10 +_FLOW_BALANCE_TOLERANCE = 1e-10 def entier_grande_taille(): @@ -871,7 +871,7 @@ def graph_sankey( """ if len(flows) < 2: raise ValueError("flows must contain at least two values.") - if abs(sum(flows)) > FLOW_BALANCE_TOLERANCE: + if abs(sum(flows)) > _FLOW_BALANCE_TOLERANCE: raise ValueError("The sum of all flows must be 0.") if labels is None: labels = [None] * len(flows) From b0b770b8a61b7cac8403ee645a1f9a5cea69a8e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:05:38 +0000 Subject: [PATCH 5/8] chore: improve sankey validation error messages Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- teachpyx/faq/faq_python.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index 3dfdfecf..f397945b 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -870,9 +870,13 @@ def graph_sankey( :return: axis, sankey diagrams """ if len(flows) < 2: - raise ValueError("flows must contain at least two values.") - if abs(sum(flows)) > _FLOW_BALANCE_TOLERANCE: - raise ValueError("The sum of all flows must be 0.") + raise ValueError(f"flows must contain at least two values, got {len(flows)}.") + total_flow = sum(flows) + if abs(total_flow) > _FLOW_BALANCE_TOLERANCE: + raise ValueError( + "The sum of all flows must be 0 " + f"(within tolerance {_FLOW_BALANCE_TOLERANCE}), got {total_flow}." + ) if labels is None: labels = [None] * len(flows) if orientations is None: From ea69ae5c938fe70539836e72b31d5c2ebe84e6e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:06:32 +0000 Subject: [PATCH 6/8] test: cover sankey input validation errors Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- _unittests/ut_faq/test_faq_python.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/_unittests/ut_faq/test_faq_python.py b/_unittests/ut_faq/test_faq_python.py index f7aa1c2f..5108e8e8 100644 --- a/_unittests/ut_faq/test_faq_python.py +++ b/_unittests/ut_faq/test_faq_python.py @@ -36,6 +36,16 @@ def test_graph_sankey(self): self.assertEqual(ax.get_title(), "flux") self.assertEqual(len(diagrams), 1) + def test_graph_sankey_errors(self): + with self.assertRaisesRegex(ValueError, "at least two values"): + graph_sankey([1]) + with self.assertRaisesRegex(ValueError, "must be 0"): + graph_sankey([1, -0.5]) + with self.assertRaisesRegex(ValueError, "same length"): + graph_sankey([1, -1], labels=["only_one"]) + with self.assertRaisesRegex(ValueError, "same length"): + graph_sankey([1, -1], orientations=[0, 1, -1]) + if __name__ == "__main__": unittest.main() From 1d8f17a14010b330722d3c1dc62953c32f390451 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:07:27 +0000 Subject: [PATCH 7/8] chore: include lengths in sankey mismatch errors Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/68526f45-813e-4c5a-959d-84afb8a8544a Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- teachpyx/faq/faq_python.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index f397945b..6fa2c1cd 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -882,9 +882,15 @@ def graph_sankey( if orientations is None: orientations = [0] * len(flows) if len(labels) != len(flows): - raise ValueError("labels and flows must have the same length.") + raise ValueError( + "labels and flows must have the same length, " + f"got {len(labels)} and {len(flows)}." + ) if len(orientations) != len(flows): - raise ValueError("orientations and flows must have the same length.") + raise ValueError( + "orientations and flows must have the same length, " + f"got {len(orientations)} and {len(flows)}." + ) import matplotlib.pyplot as plt from matplotlib.sankey import Sankey From 146049e01c5ea67785edf54fd5852c1b208c9157 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:39:09 +0000 Subject: [PATCH 8/8] docs: add usage example to graph_sankey docstring Agent-Logs-Url: https://github.com/sdpython/teachpyx/sessions/cd5e1831-153c-45c1-a63e-d8624958ffd7 Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com> --- teachpyx/faq/faq_python.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/teachpyx/faq/faq_python.py b/teachpyx/faq/faq_python.py index 6fa2c1cd..bd965bda 100644 --- a/teachpyx/faq/faq_python.py +++ b/teachpyx/faq/faq_python.py @@ -868,6 +868,19 @@ def graph_sankey( :param kwargs: additional parameters forwarded to ``matplotlib.sankey.Sankey.add`` :return: axis, sankey diagrams + + Example:: + + import matplotlib.pyplot as plt + from teachpyx.faq.faq_python import graph_sankey + + ax, _ = graph_sankey( + [1, -0.25, -0.75], + labels=["input", "loss", "output"], + orientations=[0, 1, -1], + title="flux", + ) + plt.show() """ if len(flows) < 2: raise ValueError(f"flows must contain at least two values, got {len(flows)}.")