Export results#

PyPhi’s result objects and substrates have their own analysis methods, but you will often want to hand the numbers to another tool: a plotting library, a statistics package, or a probabilistic-graphical-model toolkit. This guide shows the three export paths PyPhi provides: a Pandas DataFrame view of a cause-effect structure, a labeled xarray view of a substrate’s transition probabilities, and a dynamic Bayesian network view of a substrate’s dynamics.

import pyphi

pyphi.config.progress_bars = False

We use the IIT 4.0 paper’s three-unit Fig 1A substrate throughout.

from pyphi import examples

substrate = examples.iit4_2023_fig1a_substrate()
state = (0, 1, 1)

Cause-effect structures as a DataFrame#

Call .to_pandas() on a cause-effect structure to get one row per distinction, indexed by mechanism, with the mechanism state, cause and effect purviews, and \(\varphi\).

analysis = pyphi.analyze(substrate, state)
ces = analysis.ces

df = ces.to_pandas()
df
phi mechanism_state cause_purview cause_state effect_purview effect_state
mechanism
(A,) 0.428350 (0,) (B,) (0,) (B, C) (0, 0)
(B,) 0.523043 (1,) (C,) (0,) (A,) (1,)
(A, B) 0.193358 (0, 1) (B,) (0,) (A, B) (1, 0)
(A, C) 0.090205 (0, 1) (A,) (1,) (B,) (0,)
(B, C) 0.141602 (1, 1) (A,) (1,) (B,) (0,)
(A, B, C) 0.071440 (0, 1, 1) (A, B) (1, 0) (A, B) (1, 0)

The result is an ordinary DataFrame, so the full Pandas API is available for filtering, aggregation, and export to CSV, Excel, or Parquet.

df["phi"].sum()
np.float64(1.447998863725724)

The Analysis object itself also has a .to_pandas() returning a single-row summary of the system-level quantities.

analysis.to_pandas()
phi normalized_phi big_phi n_distinctions sum_phi_d sum_phi_r
0 0.133873 0.066937 4.365675 6 1.447999 2.917676

Transition probabilities as an xarray Dataset#

A substrate’s transition probability matrix exports to a labeled xarray.Dataset via substrate.tpm.to_xarray(). Each unit becomes a data variable holding its conditional distribution over the next state, with named dimensions and integer coordinates drawn from the state space. This requires the optional xarray dependency (pip install pyphi[xarray]).

ds = substrate.tpm.to_xarray()
ds
<xarray.Dataset> Size: 480B
Dimensions:  (u0: 2, u1: 2, u2: 2, u0_next: 2, u1_next: 2, u2_next: 2)
Coordinates:
  * u0       (u0) int64 16B 0 1
  * u1       (u1) int64 16B 0 1
  * u2       (u2) int64 16B 0 1
  * u0_next  (u0_next) int64 16B 0 1
  * u1_next  (u1_next) int64 16B 0 1
  * u2_next  (u2_next) int64 16B 0 1
Data variables:
    unit_0   (u0, u1, u2, u0_next) float64 128B 0.8808 0.1192 ... 0.1192 0.8808
    unit_1   (u0, u1, u2, u1_next) float64 128B 0.2315 0.7685 ... 0.7685 0.2315
    unit_2   (u0, u1, u2, u2_next) float64 128B 0.832 0.168 0.5 ... 0.168 0.832

Named dimensions make it straightforward to select a conditional slice by state rather than by raw axis position. Here we read unit 0’s next-state distribution given that its inputs are all off.

ds["unit_0"].sel(u0=0, u1=0, u2=0).values
array([0.88079708, 0.11920292])

Dynamics as a dynamic Bayesian network#

A substrate’s transition dynamics can be exported as a two-timeslice dynamic Bayesian network. Two forms are available.

substrate.to_dbn_dict() returns a plain dictionary with no third-party dependency. It has three keys: variables maps each node label to its alphabet size, edges lists the inter-slice parent-to-child pairs (implicitly from time \(t\) to \(t+1\)), and cpds maps each node to its parents and its conditional probability table.

dbn = substrate.to_dbn_dict()

print("variables:", dbn["variables"])
print("edges:    ", dbn["edges"])
print("A parents:", dbn["cpds"]["A"]["parents"])
variables: {'A': 2, 'B': 2, 'C': 2}
edges:     [('A', 'A'), ('B', 'A'), ('A', 'B'), ('B', 'B'), ('C', 'B'), ('A', 'C'), ('C', 'C')]
A parents: ['A', 'B']

substrate.to_dbn() returns the same structure as a networkx.DiGraph, ready for graph tooling. Each node label X becomes two variables, (X, 0) at time \(t\) and (X, 1) at time \(t+1\), connected only by inter-slice edges. The graph is therefore acyclic even when the substrate has feedback. This requires the visualize extra (pip install pyphi[visualize]), which provides networkx.

g = substrate.to_dbn()

print("nodes:", sorted(g.nodes, key=str))
print("edges:", sorted(g.edges, key=str))
nodes: [('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)]
edges: [(('A', 0), ('A', 1)), (('A', 0), ('B', 1)), (('A', 0), ('C', 1)), (('B', 0), ('A', 1)), (('B', 0), ('B', 1)), (('C', 0), ('B', 1)), (('C', 0), ('C', 1))]

Each next-timeslice node stores its conditional probability table and its ordered parent labels as node attributes.

g.nodes[("A", 1)]["parents"]
['A', 'B']

Where to go next#