factoriax.analysis.actions#

Find temporal patterns in what an agent did, and draw them.

Every function takes a Trajectory. Each plot returns its (figure, axes) without writing or closing anything, so the caller can change the result before saving it.

Five views, each with a plot and most with a finder that returns the numbers behind it:

  • A raster draws one colored cell for each episode and timestep. Phase changes and loops show up as bands.

  • A transition matrix counts how often each action follows each other action.

  • An n-gram count finds the short sequences an agent repeats.

  • An entropy curve measures how much the episodes of a batch agree at each timestep.

  • A run length measures how long an action was repeated without a break.

DEFAULT_ACTION_LABELS and DEFAULT_ACTION_COLORS are both built from the Action enum, so neither can fall behind it and both cover every action. The colors carry no meaning, though. Pass colors where the scheme has to say something, such as cool tones for movement.

Module Attributes

DEFAULT_ACTION_LABELS

The name of each action, in Action value order.

DEFAULT_ACTION_COLORS

Fallback line and cell colors, in Action value order, one for each action.

factoriax.analysis.actions.DEFAULT_ACTION_COLORS: list[str] = ['#c61010', '#4d72cb', '#b9f368', '#a63097', '#2beecd', '#dab181', '#3d10c6', '#53cb4d', '#f36896', '#307aa6', '#deee2b', '#c781da', '#10c66b', '#cb674d', '#6874f3', '#5da630', '#ee2bad', '#81d7da', '#c69810', '#874dcb', '#68f37f', '#a6303f', '#2b7dee', '#c0da81', '#c610c6', '#4dcba6', '#f3a168', '#3f30a6', '#4cee2b', '#da81aa', '#1099c6', '#cbc64d', '#c468f3', '#30a65c', '#ee3a2b', '#8194da', '#6cc610', '#cb4db2', '#68f3e7', '#a67930', '#6b2bee', '#81da84', '#c6103f', '#4d92cb', '#dcf368', '#9730a6', '#2bee9b', '#da9a81', '#1011c6', '#73cb4d', '#f368ba', '#3098a6', '#eecc2b', '#b181da', '#10c63c', '#cb4d53', '#6897f3', '#7ba630', '#ee2bdf', '#81dac7', '#c66a10', '#664dcb', '#75f368', '#a6305d', '#2baeee', '#d7da81', '#9710c6', '#4dcb86', '#f37e68', '#3040a6', '#7eee2b', '#da81c1', '#10c6c4', '#cba54d', '#a168f3', '#30a63e', '#ee2b4d', '#81abda', '#9ac610', '#c54dcb', '#68f3c3', '#a65b30', '#392bee', '#94da81', '#c6106d', '#4db2cb', '#f3e668']#

Fallback line and cell colors, in Action value order, one for each action. Generated, so the list cannot fall behind the enum the way a hand-written one does.

The colors carry no meaning: two neighboring actions are not related, and an action has no fixed color. Pass colors to any plot that needs a chosen scheme, such as cool tones for movement.

factoriax.analysis.actions.DEFAULT_ACTION_LABELS: list[str] = ['NOOP', 'UP', 'DOWN', 'LEFT', 'RIGHT', 'FACE_UP', 'FACE_DOWN', 'FACE_LEFT', 'FACE_RIGHT', 'MINE', 'PICKUP', 'WITHDRAW', 'REPAIR', 'ROTATE_LEFT', 'ROTATE_RIGHT', 'ROTATE_UP', 'ROTATE_DOWN', 'PLACE_MINER', 'PLACE_PALLET', 'PLACE_CONVEYOR_BELT', 'PLACE_ASSEMBLER', 'PLACE_ARM', 'PLACE_ROCKET', 'PLACE_FURNACE', 'PLACE_SCIENCE_LAB', 'PLACE_SPLITTER', 'PLACE_CROSSING', 'CRAFT_IRON_PLATE', 'CRAFT_COPPER_PLATE', 'CRAFT_TIN_PLATE', 'CRAFT_WAFER', 'CRAFT_FRAME', 'CRAFT_CIRCUIT', 'CRAFT_WIRE', 'CRAFT_MOTOR', 'CRAFT_SENSOR', 'CRAFT_TIER1_SCIENCE_PACK', 'CRAFT_TIER2_SCIENCE_PACK', 'CRAFT_REFRACTORY', 'CRAFT_HULL', 'CRAFT_ENGINE_UNIT', 'CRAFT_AVIONICS', 'CRAFT_ROCKET_CORE', 'CRAFT_TIER3_SCIENCE_PACK', 'CRAFT_MINER', 'CRAFT_PALLET', 'CRAFT_CONVEYOR_BELT', 'CRAFT_ASSEMBLER', 'CRAFT_ARM', 'CRAFT_ROCKET', 'CRAFT_FURNACE', 'CRAFT_SCIENCE_LAB', 'CRAFT_SPLITTER', 'CRAFT_CROSSING', 'DEPOSIT_COAL', 'DEPOSIT_IRON_ORE', 'DEPOSIT_COPPER_ORE', 'DEPOSIT_TIN_ORE', 'DEPOSIT_SILICON', 'DEPOSIT_LIMESTONE', 'DEPOSIT_IRON_PLATE', 'DEPOSIT_COPPER_PLATE', 'DEPOSIT_TIN_PLATE', 'DEPOSIT_WAFER', 'DEPOSIT_FRAME', 'DEPOSIT_CIRCUIT', 'DEPOSIT_WIRE', 'DEPOSIT_MOTOR', 'DEPOSIT_SENSOR', 'DEPOSIT_TIER1_SCIENCE_PACK', 'DEPOSIT_TIER2_SCIENCE_PACK', 'DEPOSIT_REFRACTORY', 'DEPOSIT_HULL', 'DEPOSIT_ENGINE_UNIT', 'DEPOSIT_AVIONICS', 'DEPOSIT_ROCKET_CORE', 'DEPOSIT_TIER3_SCIENCE_PACK', 'DEPOSIT_MINER', 'DEPOSIT_PALLET', 'DEPOSIT_CONVEYOR_BELT', 'DEPOSIT_ASSEMBLER', 'DEPOSIT_ARM', 'DEPOSIT_ROCKET', 'DEPOSIT_FURNACE', 'DEPOSIT_SCIENCE_LAB', 'DEPOSIT_SPLITTER', 'DEPOSIT_CROSSING']#

The name of each action, in Action value order. Built from the enum, so it cannot drift from it and always covers every action.

factoriax.analysis.actions.action_entropy(traj: Trajectory, player: int | None = None, num_actions: int = 87, window: int = 1) → ndarray#

Measure how much the chosen action varies across episodes.

At each timestep the function builds the distribution of actions over the episodes of the batch, then takes its Shannon entropy in bits. A value of 0 means every episode took the same action at that step. The largest possible value is log2(num_actions).

This measures agreement between episodes at one timestep. It does not measure how varied one episode is over time.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – How many action ids the distribution covers.

  • window – The width of a rolling mean over time. A window of 1 applies no smoothing.

Returns:

Shape (T,), in bits.

Return type:

numpy.ndarray

Notes

The smoothing uses mode="same", so the first and last window // 2 values average over fewer real timesteps and pull toward zero. Read the two ends with that in mind.

factoriax.analysis.actions.action_ngrams(traj: Trajectory, n: int = 2, player: int | None = None, top_k: int = 20, action_labels: list[str] | None = None) → list[tuple[Any, int]]#

Count the most common runs of n actions in a row.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • n – How many actions make one gram. An n above the episode length gives an empty result.

  • top_k – How many of the most common grams to return.

  • action_labels – The name of each action, or None to key the result by action id instead of by name.

Returns:

(gram, count) pairs, most common first. A gram is a tuple of n action ids, or of n names when action_labels is given. Grams are counted inside an episode only, so none spans two episodes.

Return type:

list

factoriax.analysis.actions.action_raster(traj: Trajectory, player: int | None = None, num_actions: int = 87, action_labels: list[str] | None = None, colors: Sequence[str] | None = None, episode_labels: list[str] | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (14, 6), title: str | None = None) → tuple[Figure, Axes]#

Draw the action of every episode and timestep as a colored cell.

One row is an episode, one column a timestep, and the color is the action. This is the first view to reach for when looking for temporal structure: phase changes, loops, and repeated sub-behaviors all show up as bands.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – How many actions the color scale covers.

  • action_labels – The name of each action, or None for DEFAULT_ACTION_LABELS, which is built from the Action enum and cannot drift from it.

  • colors – One color for each action, or None for the defaults.

  • episode_labels – The name of each row, or None to number them. The list must hold one entry for each episode.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

Notes

The default colors are generated and carry no meaning. Pass colors where the scheme has to say something, such as cool tones for movement.

factoriax.analysis.actions.plot_action_distribution(traj: Trajectory, player: int | None = None, num_actions: int = 87, action_labels: list[str] | None = None, colors: Sequence[str] | None = None, window: int = 10, ax: Axes | None = None, figsize: tuple[float, float] = (14, 5), title: str | None = None) → tuple[Figure, Axes]#

Draw how the mix of actions changes over an episode.

The chart is a stacked area, so each band is the share of one action at that timestep and the bands sum to 1.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – How many action ids to draw as bands.

  • action_labels – The name of each action, or None for the defaults.

  • colors – One color for each action, or None for the defaults.

  • window – The width of a rolling mean over time, which smooths the bands.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

Notes

The default colors are generated and carry no meaning. Pass colors where the scheme has to say something, such as cool tones for movement.

factoriax.analysis.actions.plot_entropy(traj: Trajectory, player: int | None = None, num_actions: int = 87, window: int = 5, ax: Axes | None = None, figsize: tuple[float, float] = (12, 4), title: str | None = None) → tuple[Figure, Axes]#

Draw the action entropy over the course of an episode.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – How many action ids the distribution covers.

  • window – The width of the rolling mean, as in action_entropy().

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

factoriax.analysis.actions.plot_ngram_sweep(traj: Trajectory, n_range: tuple[int, int] = (2, 10), player: int | None = None, top_k: int = 3, action_labels: list[str] | None = None, colors: Sequence[str] | None = None, figsize: tuple[float, float] | None = None, title: str | None = None) → tuple[Figure, ndarray]#

Draw the top grams for each length in a range, one row each.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • n_range – (lowest, highest) gram length, both included. One row of panels is drawn for each length.

  • top_k – How many grams to show for each length.

  • action_labels – The name of each action, or None for the defaults.

  • colors – One color for each action, or None for the defaults. Each bar takes the mean color of its gram.

  • figsize – The size of the figure in inches, or None to size it from the row count.

  • title – The figure title, or None for a default.

Returns:

(figure, axes), where axes holds one entry for each gram length. The figure is not written and not closed.

Return type:

tuple

Notes

The default colors are generated and carry no meaning. Pass colors where the scheme has to say something, such as cool tones for movement.

factoriax.analysis.actions.plot_ngrams(traj: Trajectory, n: int = 2, player: int | None = None, top_k: int = 15, action_labels: list[str] | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (10, 5), title: str | None = None) → tuple[Figure, Axes]#

Draw the most common grams of one length as a bar chart.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • n – How many actions make one gram.

  • top_k – How many of the most common grams to draw.

  • action_labels – The name of each action, or None for the defaults.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

factoriax.analysis.actions.plot_phase_transitions(traj: Trajectory, phases: list[tuple[int, int]], player: int | None = None, num_actions: int = 87, action_labels: list[str] | None = None, figsize_per_phase: tuple[float, float] = (5, 4.5), cmap: str = 'Blues', phase_labels: list[str] | None = None) → tuple[Figure, ndarray]#

Draw one transition matrix for each phase, side by side.

Use this to see how the behavior of a policy changes over an episode. An early phase and a late phase each get their own matrix, on a shared color scale.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • phases – One (start, end) timestep window for each panel. The windows can overlap and need not cover the whole episode.

  • num_actions – The side length of each matrix.

  • action_labels – The name of each action, or None for the defaults.

  • figsize_per_phase – The size of one panel in inches. The figure width grows with the number of phases.

  • cmap – Any matplotlib colormap name.

  • phase_labels – The title of each panel, or None to build one from the window. The list must be as long as phases.

Returns:

(figure, axes), where axes holds one entry for each phase. The figure is not written and not closed.

Return type:

tuple

factoriax.analysis.actions.plot_run_lengths(traj: Trajectory, player: int | None = None, action_labels: list[str] | None = None, num_actions: int = 87, colors: Sequence[str] | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (10, 5), title: str | None = None) → tuple[Figure, Axes]#

Draw one box for each action, over its run lengths.

An action that never occurred gets no box, so the chart shows only the actions the policy used.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • action_labels – The name of each action, or None for the defaults.

  • num_actions – How many action ids to consider.

  • colors – One color for each action, or None for the defaults.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

Notes

The default colors are generated and carry no meaning. Pass colors where the scheme has to say something, such as cool tones for movement.

factoriax.analysis.actions.plot_transition_matrix(traj: Trajectory, player: int | None = None, num_actions: int = 87, action_labels: list[str] | None = None, time_range: tuple[int, int] | None = None, normalize: bool = True, ax: Axes | None = None, figsize: tuple[float, float] = (8, 7), cmap: str = 'Blues', title: str | None = None) → tuple[Figure, Axes]#

Draw the transition matrix as a heatmap.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – The side length of the matrix.

  • action_labels – The name of each action, or None for the defaults. The names label both axes.

  • time_range – (start, end) to count only that window, or None.

  • normalize – True for probabilities, False for raw counts. This also sets the colorbar label.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

  • cmap – Any matplotlib colormap name.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

factoriax.analysis.actions.run_lengths(traj: Trajectory, player: int | None = None) → dict[int, list[int]]#

Measure how long each action was repeated without a break.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

Returns:

{action_id: [length, ...]}. A length counts the steps in one unbroken run of that action. Runs are collected inside an episode only. The run at the end of an episode is closed at the last step, and not joined to the next episode. An action that never occurred is absent from the dict.

Return type:

dict

factoriax.analysis.actions.transition_matrix(traj: Trajectory, player: int | None = None, num_actions: int = 87, normalize: bool = True, time_range: tuple[int, int] | None = None) → ndarray#

Count how often each action follows each other action.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • player – Which player to read, or None for player 0.

  • num_actions – The side length of the matrix. An action id at or above this value raises IndexError.

  • normalize – True to divide each row by its total, giving the probability of the next action. False to return raw counts.

  • time_range – (start, end) to count only that window of timesteps, or None for the whole episode.

Returns:

Shape (num_actions, num_actions), float64. Entry (i, j) counts the steps where action i was followed by action j. Pairs are counted inside an episode only, so the last step of one episode is not paired with the first of the next.

Return type:

numpy.ndarray

Notes

With normalize=True, a row for an action that never occurred is divided by 1 rather than by 0. Such a row is therefore all zeros and not NaN, so it plots as an empty row and does not sum to 1.