Visualizing the Taxes and Transfers System#

How to create a plot#

To help you understand how GETTSIM works internally and how you are able to implement custom reforms, you can visualize the tax and transfer system. This tutorial explains how to create a graph and what information you can get from it.

[2]:
from gettsim import Labels, plot

The simplest way to create a plot is to use the plot.dag.tt function, passing only a policy date.

Unfortunately, this is also unreadable due to the complexity of the tax and transfers system when looked at in its entirety.

[3]:
plot.dag.tt(policy_date_str="2025-01-01")

Looking at the signature of plot.dag.tt, we see that it takes three types of arguments:

  • DAG-plotting options:

    • primary_nodes

    • selection_type

    • selection_depth

    • include_params

    • show_node_description

  • Arguments to main, which are the same as there

  • Any remaining keyword arguments are passed to plotly.graph_objects.Figure.update

[4]:
plot.dag.tt?

A more flexible way of looking at portions of the graph is to make use of the arguments primary_nodes and selection_type of plot.dag.tt().

Focusing on a subset of the system using the plotting interface#

Primary nodes allow you to visualize only a subset of the complete graph of the tax and transfer systems. They can be passed to the primary_nodes argument of the plot.dag.tt() function. This is useful only in conjunction with specifying a selection type — in case you leave that out or set it to None, the complete graph is displayed (i.e., primary_nodes does not do anything).

Other than None, selection_type may take one of four values:

  • "neighbors": The neighbors (parents and children) of the primary nodes are displayed.

  • "descendants": All descendants of the primary nodes are displayed.

  • "ancestors": All ancestors of the primary nodes are displayed.

  • "all_paths": All paths between the primary nodes are displayed (including any other nodes lying on these paths). You must pass at least two primary nodes.

We’ll go through these in turn.

Selection type: Neighbors#

This is the example above. We pick one primary node ("betrag_y_sn" in the "einkommensteuer" / "abgeltungssteuer" namespace) and it is displayed together with its parents ("zu_versteuernde_kapitalerträge_y_sn" and "satz" in the same namespace) and its child ("betrag_y_sn" in the "solidaritätszuschlag" namespace).

[5]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="neighbors",
)

By changing the selection_depth argument from its default of 1 for neighbors, you can look at a broader vicinity in both directions. Setting it to 2 will include all grandparents and grandchildren, and so on.

[6]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="neighbors",
    selection_depth=2,
)

Selection type: Ancestors#

This takes a node and plots it along with all of its ancestors. Picking again "betrag_y_sn" in the "einkommensteuer" / "abgeltungssteuer" namespace, we get its two parents as above. Among them, "satz" has no ancestors because it is a parameter of the tax system. However, "zu_versteuernde_kapitalerträge_y_sn" has a much longer set of preceding nodes, for setting up the tax unit from primitives (marital status, joint filing) and calculating the taxable portion of all capital income.

[7]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="ancestors",
)

Again, we can use the selection_depth argument to control how many levels of the graph we look at:

[8]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="ancestors",
    selection_depth=3,
)

As it happens, this is the same (up to possible layout differences) as letting the graph know that sn_id is an input column:

[9]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="ancestors",
    labels=Labels(input_columns=["sn_id"]),
    include_warn_nodes=False,
)

Selection type: Descendants#

This takes a node and plots it along with all of its descendants. Picking again "betrag_y_sn" in the "einkommensteuer" / "abgeltungssteuer" namespace and , we get a long DAG describing its descendants because the amount of Solidaritätszuschlag paid impacts the means-tested transfers Bürgergeld, Wohngeld, and Kinderzuschlag.

[10]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=["einkommensteuer__abgeltungssteuer__betrag_y_sn"],
    selection_type="descendants",
)

Selection type: All paths#

This takes a set of nodes and plots all paths connecting them, along with any nodes lying on these paths. This can be very useful to understand the connections between two or more nodes. Picking again "betrag_y_sn" in the "einkommensteuer" / "abgeltungssteuer" namespace, we additionally add "betrag_m_bg" in the "bürgergeld" namespace.

This ends up being almost the same as the previous example, except that unrelated nodes (those in the "grundsicherung_im_alter" namespace and the amounts of Wohngeld and Kinderzuschlag) are not displayed.

[11]:
plot.dag.tt(
    policy_date_str="2025-01-01",
    primary_nodes=[
        "einkommensteuer__abgeltungssteuer__betrag_y_sn",
        "bürgergeld__betrag_m_bg",
    ],
    selection_type="all_paths",
)