Log Analyses

Records

class econsimulacra.log_analyses.records.BaseRecord(type)[source]

Bases: object

Base record for log analyses.

A record instance represents a single log entry.

In typical usage, a record is created by parsing a log line, and the type field is set to the log entry’s type. A list of BaseRecord instances is then passed to a RecordStore (log_analyses.store) for efficient querying.

Parameters:

type (str)

type

The type of the log entry, e.g. “agent_action”, “space_assign”, etc. Corresponds to the “type” field in the econsimulacra.logs.Log.

Type:

str

type: str
class econsimulacra.log_analyses.records.TimedRecord(type, time, time_step)[source]

Bases: BaseRecord

A record with a timestamp and time step.

Parameters:
time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

time: int | datetime
time_step: int
class econsimulacra.log_analyses.records.AgentGenerationRecord(type, time, time_step, agent_id, agent_type, agent_name, wealth, inventory, persona)[source]

Bases: BaseRecord

A record for agent generation events.

Corresponds to econsimulacra.logs.AgentGenerationLog.

Parameters:
type

must be “agent_generation”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the generated agent.

Type:

int

agent_type

The type of the generated agent.

Type:

str

agent_name

The name of the generated agent.

Type:

str

wealth

The initial wealth of the generated agent.

Type:

float

inventory

The initial inventory of the generated agent, as a dict mapping item names to amounts.

Type:

dict[str, float]

persona

The persona of the generated agent, as a dict. May be None if the agent has no persona.

Type:

Optional[dict[str, Any]]

time: int | datetime
time_step: int
agent_id: int
agent_type: str
agent_name: str
wealth: float
inventory: dict[str, float]
persona: dict[str, Any] | None
class econsimulacra.log_analyses.records.ItemGenerationRecord(type, time, time_step, item_name, price)[source]

Bases: BaseRecord

A record for item generation events.

Corresponds to econsimulacra.logs.ItemGenerationLog.

Parameters:
type

must be “item_generation”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

item_name

The name of the generated item.

Type:

str

price

The initial price of the generated item.

Type:

float

time: int | datetime
time_step: int
item_name: str
price: float
class econsimulacra.log_analyses.records.SpaceAssignRecord(type, agent_id, pos)[source]

Bases: BaseRecord

A record for space assignment events.

Corresponds to econsimulacra.logs.SpaceAssignLog.

Parameters:
type

must be “space_assign”.

Type:

str

agent_id

The ID of the agent being assigned a space.

Type:

int

pos

The position to which the agent is assigned.

Type:

tuple[int, …]

agent_id: int
pos: tuple[int, ...]
class econsimulacra.log_analyses.records.SleepStartRecord(type, time, time_step, agent_id, until)[source]

Bases: TimedRecord

A record for sleep events.

Corresponds to econsimulacra.logs.SleepStartLog.

Parameters:
type

must be “sleep”.

Type:

str

time

The timestamp when the agent went to sleep, as a datetime object or integer.

Type:

int | datetime

time_step

The time step when the agent went to sleep, as an integer.

Type:

int

agent_id

The ID of the sleeping agent.

Type:

int

until

The timestamp when the agent is expected to wake up, as a datetime object or integer.

Type:

int | datetime

agent_id: int
until: int | datetime
class econsimulacra.log_analyses.records.SleepEndRecord(type, time, time_step, agent_id, since)[source]

Bases: TimedRecord

A record for sleep events.

Corresponds to econsimulacra.logs.SleepEndLog.

Parameters:
type

must be “sleep”.

Type:

str

time

The timestamp when the agent wake up, as a datetime object or integer.

Type:

int | datetime

time_step

The time step when the agent wake up, as an integer.

Type:

int

since

The timestamp when the agent went to sleep, as a datetime object or integer.

Type:

int | datetime

agent_id

The ID of the sleeping agent.

Type:

int

agent_id: int
since: int | datetime
class econsimulacra.log_analyses.records.MoveRecord(type, time, time_step, agent_id, old_pos, new_pos)[source]

Bases: TimedRecord

A record for agent movement events.

Corresponds to econsimulacra.logs.MoveLog.

Parameters:
type

must be “move”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that moved.

Type:

int

old_pos

The previous position of the agent.

Type:

tuple[int, …]

new_pos

The new position of the agent.

Type:

tuple[int, …]

agent_id: int
old_pos: tuple[int, ...]
new_pos: tuple[int, ...]
class econsimulacra.log_analyses.records.ConsumptionRecord(type, time, time_step, agent_id, item_name, item_amount)[source]

Bases: TimedRecord

A record for agent consumption events.

Corresponds to econsimulacra.logs.ConsumptionLog.

Parameters:
type

must be “consumption”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that consumed an item.

Type:

int

item_name

The name of the consumed item.

Type:

str

item_amount

The amount of the consumed item.

Type:

float

agent_id: int
item_name: str
item_amount: float | int
class econsimulacra.log_analyses.records.OrderRecord(type, time, time_step, agent_id, counterparty_id, item_name, item_amount, price, order_id)[source]

Bases: TimedRecord

A record for order events.

Corresponds to econsimulacra.logs.OrderLog.

Parameters:
type

must be “order”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that placed the order.

Type:

int

counterparty_id

The ID of the counterparty agent.

Type:

int

item_name

The name of the item being bought or sold.

Type:

str

item_amount

The amount of the item being bought or sold.

Type:

float | int

price

The price of the item, if applicable.

Type:

float, optional

order_id

The ID of the order.

Type:

int

agent_id: int
counterparty_id: int
item_name: str
item_amount: float | int
price: float | None
order_id: int
class econsimulacra.log_analyses.records.ProposalRecord(type, time, time_step, proposal_id, proposer_agent_id, responder_agent_id, give_item_name, give_item_amount, get_item_name, get_item_amount)[source]

Bases: TimedRecord

A record for proposal events.

Corresponds to econsimulacra.logs.ProposalLog.

Parameters:
type

must be “proposal”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

proposal_id

The ID of the proposal.

Type:

int

proposer_agent_id

The ID of the proposing agent.

Type:

int

responder_agent_id

The ID of the responding agent.

Type:

int

give_item_name

The name of the item being offered by the proposer.

Type:

str

give_item_amount

The amount of the item being offered by the proposer.

Type:

float | int

get_item_name

The name of the item being requested by the proposer.

Type:

str

get_item_amount

The amount of the item being requested by the proposer.

Type:

float | int

proposal_id: int
proposer_agent_id: int
responder_agent_id: int
give_item_name: str
give_item_amount: float | int
get_item_name: str
get_item_amount: float | int
class econsimulacra.log_analyses.records.OrderReactionRecord(type, time, time_step, agent_id, counterparty_id, item_name, item_amount, price, order_id, accept_amount)[source]

Bases: TimedRecord

A record for order reaction events.

Corresponds to econsimulacra.logs.OrderReactionLog.

Parameters:
type

must be “order_reaction”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that reacted to the order.

Type:

int

counterparty_id

The ID of the counterparty agent.

Type:

int

item_name

The name of the item being bought or sold.

Type:

str

item_amount

The amount of the item being bought or sold.

Type:

float | int

price

The price of the item.

Type:

float

order_id

The ID of the order.

Type:

int

accept_amount

The amount of the item that was accepted in the order reaction.

Type:

float | int

agent_id: int
counterparty_id: int
item_name: str
item_amount: float | int
price: float
order_id: int
accept_amount: float | int
class econsimulacra.log_analyses.records.OrderExpirationRecord(type, time, time_step, order_id)[source]

Bases: TimedRecord

A record for order expiration events.

Corresponds to econsimulacra.logs.OrderExpirationLog.

Parameters:
type

must be “order_expiration”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

order_id

The ID of the expired order.

Type:

int

order_id: int
class econsimulacra.log_analyses.records.ProposalReactionRecord(type, time, time_step, proposal_id, proposer_agent_id, responder_agent_id, give_item_name, give_item_amount, get_item_name, get_item_amount, accept)[source]

Bases: TimedRecord

A record for proposal reaction events.

Corresponds to econsimulacra.logs.ProposalReactionLog.

Parameters:
type

must be “proposal_reaction”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

proposal_id

The ID of the proposal.

Type:

int

proposer_agent_id

The ID of the proposing agent.

Type:

int

responder_agent_id

The ID of the responding agent.

Type:

int

give_item_name

The name of the item being offered by the proposer.

Type:

str

give_item_amount

The amount of the item being offered by the proposer.

Type:

float | int

get_item_name

The name of the item being requested by the proposer.

Type:

str

get_item_amount

The amount of the item being requested by the proposer.

Type:

float | int

accept

Whether the proposal was accepted or rejected.

Type:

bool

proposal_id: int
proposer_agent_id: int
responder_agent_id: int
give_item_name: str
give_item_amount: float | int
get_item_name: str
get_item_amount: float | int
accept: bool
class econsimulacra.log_analyses.records.ProposalExpirationRecord(type, time, time_step, proposal_id)[source]

Bases: TimedRecord

A record for proposal expiration events.

Corresponds to econsimulacra.logs.ProposalExpirationLog.

Parameters:
type

must be “proposal_expiration”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

proposal_id

The ID of the expired proposal.

Type:

int

proposal_id: int
class econsimulacra.log_analyses.records.ChangePriceRecord(type, time, time_step, agent_id, item_name, old_price, new_price)[source]

Bases: TimedRecord

A record for price change events.

Corresponds to econsimulacra.logs.ChangePriceLog.

Parameters:
type

must be “change_price”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that changed the price.

Type:

int

item_name

The name of the item whose price was changed.

Type:

str

old_price

The old price of the item.

Type:

float

new_price

The new price of the item.

Type:

float

agent_id: int
item_name: str
old_price: float
new_price: float
class econsimulacra.log_analyses.records.InnerThoughtRecord(type, time, time_step, agent_id, inner_thought, sentiment)[source]

Bases: TimedRecord

A record for inner thought events.

Corresponds to econsimulacra.logs.InnerThoughtLog.

Parameters:
type

must be “inner_thought”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that had the inner thought.

Type:

int

inner_thought

The content of the inner thought.

Type:

str

sentiment

The sentiment of the inner thought, if applicable.

Type:

float, optional

agent_id: int
inner_thought: str
sentiment: float | None
class econsimulacra.log_analyses.records.TweetRecord(type, time, time_step, agent_id, message, sentiment, num_follows, num_followers)[source]

Bases: TimedRecord

A record for tweet events.

Corresponds to econsimulacra.logs.TweetLog.

Parameters:
type

must be “tweet”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that posted the tweet.

Type:

int

message

The content of the tweet.

Type:

str

sentiment

The sentiment of the tweet, if applicable.

Type:

float, optional

num_follows

The number of accounts the agent follows.

Type:

int

num_followers

The number of followers the agent has.

Type:

int

agent_id: int
message: str
sentiment: float | None
num_follows: int
num_followers: int
class econsimulacra.log_analyses.records.FollowRecord(type, time, time_step, agent_id, target_agent_id, num_follows, num_followers)[source]

Bases: TimedRecord

A record for follow events.

Corresponds to econsimulacra.logs.FollowLog.

Parameters:
type

must be “follow”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that followed another agent.

Type:

int

target_agent_id

The ID of the agent being followed.

Type:

int

agent_id: int
target_agent_id: int
num_follows: int
num_followers: int
class econsimulacra.log_analyses.records.UnfollowRecord(type, time, time_step, agent_id, target_agent_id, num_follows, num_followers)[source]

Bases: TimedRecord

A record for unfollow events.

Corresponds to econsimulacra.logs.UnfollowLog.

Parameters:
type

must be “unfollow”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that unfollowed another agent.

Type:

int

target_agent_id

The ID of the agent being unfollowed.

Type:

int

agent_id: int
target_agent_id: int
num_follows: int
num_followers: int
class econsimulacra.log_analyses.records.StateEvaluationRecord(type, time, time_step, agent_id, wealth, inventory, persona)[source]

Bases: TimedRecord

A record for state evaluation events.

Corresponds to econsimulacra.logs.StateEvaluationLog.

Parameters:
type

must be “state_evaluation”.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent whose state is being evaluated.

Type:

int

wealth

The wealth of the agent.

Type:

float

inventory

The inventory of the agent.

Type:

dict[str, float]

persona

The persona of the agent.

Type:

Optional[dict[str, Any]]

agent_id: int
wealth: float
inventory: dict[str, float]
persona: dict[str, Any] | None
class econsimulacra.log_analyses.records.ObsRecord(type, time, time_step, obs_type, agent_id, obs)[source]

Bases: TimedRecord

A record for observation events.

Corresponds to econsimulacra.logs.ObsLog.

Parameters:
type

must be “observation”.

Type:

str

obs_type

The type of the observation. See also: econsimulacra.logs.ObsLog.

Type:

str

time

The timestamp of the log entry, as a datetime object or integer.

Type:

int | datetime

time_step

The time step of the log entry, as an integer.

Type:

int

agent_id

The ID of the agent that made the observation.

Type:

int

obs

The content of the observation.

Type:

Any

obs_type: str
agent_id: int
obs: Any

Record Store

class econsimulacra.log_analyses.store.RecordStore(records)[source]

Bases: object

A record store that organizes records by type and agent ID for efficient retrieval. Basic usage:

>>> records = load_from_file("path/to/log.txt")
>>> store = RecordStore(records)
>>> move_records = store.get_by_type("move")
>>> agent_1_records = store.get_by_agent(1)
Parameters:

records (list[BaseRecord])

get_by_type(record_type)[source]

Get records by type.

Parameters:

record_type (str) – The type of records to retrieve. Example: “move”, “order”, etc.

Returns:

A list of records matching the specified type.

Return type:

list[BaseRecord]

get_by_agent(agent_id)[source]

Get records by agent ID.

Parameters:

agent_id (int) – The ID of the agent whose records to retrieve.

Returns:

A list of records associated with the specified agent ID.

Return type:

list[BaseRecord]

get_by_agent_type(agent_id, record_type)[source]

Get records by agent ID and type.

Parameters:
  • agent_id (int) – The ID of the agent whose records to retrieve.

  • record_type (str) – The type of records to retrieve. Example: “move”, “order”, etc.

Returns:

A list of records associated with the specified agent ID and type.

Return type:

list[BaseRecord]

get_by_agent_time(agent_id, time_step)[source]

Get records by agent ID and time step.

Parameters:
  • agent_id (int) – The ID of the agent whose records to retrieve.

  • time_step (int) – The time step to filter records by.

Returns:

A list of records associated with the specified agent ID and time step.

Return type:

list[BaseRecord]

typed(cls)[source]

Get records of a specific class.

Parameters:

cls (type[T]) – The class of records to retrieve. Example: MoveRecord, OrderRecord, etc.

Returns:

A list of records matching the specified class.

Return type:

list[T]

Analyzer Base

class econsimulacra.log_analyses.base.TimeAxisConfig(x_min, x_max, tick_positions, tick_labels)[source]

Bases: object

Configuration for a shared simulation-step x-axis.

Produced by AnalyzerBase._prepare_time_axis() and consumed by AnalyzerBase._apply_time_axis(). All figures produced during one analysis pass share the same instance, guaranteeing consistent x-axis extents and tick positions across figures.

Parameters:
x_min

Minimum x value (earliest time_step in the store).

Type:

float

x_max

Maximum x value (latest time_step in the store).

Type:

float

tick_positions

Evenly-spaced tick positions (as time_step values) sampled from the sorted global step list.

Type:

list[float]

tick_labels

Human-readable labels for each tick. Uses datetime strings formatted as %Y-%m-%d\n %H:%M when datetime values are available and monotonically non-decreasing; falls back to plain step numbers otherwise.

Type:

list[str]

x_min: float
x_max: float
tick_positions: list[float]
tick_labels: list[str]
class econsimulacra.log_analyses.base.AnalyzerBase[source]

Bases: ABC, Generic[T, U]

Base class for all log analyzers in EconSimulacra.

Each analyzer transforms a RecordStore into a typed result T (single run) and U (multi-run aggregate). The standard analysis pipeline is:

  1. Call analyze() with a RecordStore to obtain a result of type T.

  2. Call draw_figs() to produce Matplotlib figures from that result.

  3. Optionally call build_summary() to render a Rich-formatted terminal summary.

For multi-run experiments, use analyze_stores() and draw_figs_all() instead.

Shared time axis

Subclasses that produce time-series plots should call _prepare_time_axis() at the start of analyze(). This reads all time_step values from the store and stores a TimeAxisConfig. Subclasses then call _apply_time_axis() on each Axes when drawing figures. This ensures that plots from different analyzers share the same x-axis range and tick labels, making side-by-side comparison straightforward.

name

Unique analyzer name. Used as a dict key in result dicts returned by AnalysisManager and as a prefix for saved figure file names.

Type:

str

Type Parameters:

T: Result type returned by analyze() for a single store. U: Result type returned by analyze_stores() for multiple stores.

name: str
abstractmethod analyze(store)[source]

Analyse records in store and return a structured result.

Subclasses must implement this method. The result type T is defined by the subclass and should contain all intermediate data needed by draw_figs() and build_summary().

Parameters:

store (RecordStore) – The record store to analyse.

Returns:

Structured analysis result.

Return type:

T

abstractmethod analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

abstractmethod draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

abstractmethod draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich-renderable summary of the analysis result.

Subclasses can override this method.

Parameters:

result (T)

Return type:

rich.console.RenderableType

summarize_results(result, console=None)[source]

Print the analysis summary using rich.

Parameters:
  • result (T)

  • console (rich.console.Console | None)

Return type:

None

build_summary_all(results)[source]

Build a rich-renderable summary for multiple stores.

Subclasses can override this method.

Parameters:

results (U)

Return type:

rich.console.RenderableType

summarize_results_all(results, console=None)[source]

Summarize results for multiple stores.

Parameters:
  • results (U)

  • console (rich.console.Console | None)

Return type:

None

get_agent_id2name(store)[source]

Utility method to create a mapping from agent IDs to agent names using AgentGenerationRecord.

Parameters:

store (RecordStore) – The record store containing the records to analyze.

Returns:

A dictionary mapping agent IDs to agent names.

Return type:

dict[int, str]

class econsimulacra.log_analyses.base.FigureSaver(dpi=300, use_tex=False, font_size=15, rc_params=None)[source]

Bases: object

Utility class for saving analyzer figures to PDF files.

FigureSaver centralises DPI, font size, and LaTeX settings so that all figures produced by different analyzers share the same visual style. Figures are saved as PDFs; tight_layout and bbox_inches="tight" prevent tick labels from being clipped.

Example:

saver = FigureSaver(dpi=300, font_size=12)
figures = analyzer.draw_figs(result)
saver.save_to_pdf(figures, Path("outputs/price_analysis"))
Parameters:
set_style(rc_params=None)[source]

Set the Matplotlib style for figures.

Parameters:

rc_params (Optional[dict[str, Any]]) – Additional Matplotlib rcParams to customize figure style. This will override the default style settings if provided.

Return type:

None

save_to_pdf(figs, parent_path)[source]

Save figures to PDF files in the specified directory.

Parameters:
  • figs (dict[str, Figure]) – A dictionary mapping figure names to Matplotlib Figure objects.

  • parent_path (Path) – The directory path where the PDF files will be saved. The directory will be created if it does not exist.

Return type:

None

class econsimulacra.log_analyses.base.AnalysisManager(analyzers)[source]

Bases: object

Orchestrate multiple analyzers over one or more record stores.

AnalysisManager iterates over a list of AnalyzerBase subclasses, runs them on the supplied RecordStore(s), and optionally renders terminal summaries and saves PDF figures.

Example:

from econsimulacra.log_analyses import (
    load_from_file,
    RecordStore,
    ActionCounter,
    FollowerCounter,
    StoreSalesAnalyzer,
    AnalysisManager,
)

records = load_from_file("path/to/log.txt")
store = RecordStore(records)
manager = AnalysisManager([
    ActionCounter(),
    FollowerCounter(),
    StoreSalesAnalyzer(),
])
results = manager.run_all(store, figs_save_path="path/to/save/figs")

The returned results dict maps each analyzer’s name attribute to the result object produced by analyze().

Parameters:

analyzers (list[AnalyzerBase[Any, Any]])

run_all(store, render_summary=False, figs_save_path=None)[source]

Run all analyzers and save their figures.

Parameters:
  • store (RecordStore) – The record store to analyze.

  • render_summary (bool) – Whether to render a summary of the analysis results.

  • figs_save_path (str, optional) – The directory path to save figures.

Return type:

dict[str, Any]

run_all_w_multi_stores(stores, render_summary=False, figs_save_path=None)[source]

Run all analyzers on multiple stores and save their figures.

Parameters:
  • stores (list[RecordStore]) – The list of record stores to analyze.

  • render_summary (bool) – Whether to render a summary of the analysis results.

  • figs_save_path (str, optional) – The directory path to save figures.

Return type:

dict[str, Any]

Analyzers

class econsimulacra.log_analyses.action_counter.ActionCounter[source]

Bases: AnalyzerBase[dict[str, int], dict[str, list[int]]]

Count the occurrences of each action type across a simulation log.

EconSimulacra records twelve distinct action types that agents can perform. ActionCounter scans a RecordStore and tallies the number of log entries of each type. The result is a flat dict[str, int], where each key is an action-type string and the value is the total count across all agents and time steps.

Action types counted

  • sleep_start – agent begins a sleep period

  • move – agent changes its spatial position

  • tweet – agent posts a message on the social network

  • follow – agent follows another agent

  • unfollow – agent unfollows another agent

  • inner_thought – agent records an internal monologue

  • order – agent places a market order

  • proposal – agent proposes a barter trade

  • consumption – agent consumes an item from its inventory

  • order_reaction – agent accepts or rejects an incoming order

  • proposal_reaction – agent accepts or rejects an incoming proposal

  • change_price – agent updates the listed price of an item

The bar chart produced by draw_figs() provides a quick visual overview of the activity mix in a simulation run. Use draw_figs_all() with multiple stores to compare the distribution of action counts across runs via a box plot.

name: str = 'action_count'
analyze(store)[source]

Count the occurrences of each action type in store.

Iterates over the twelve predefined action types and calls get_by_type() for each. Zero counts are always preserved so that the output dict has a fixed set of keys regardless of which actions actually occurred in this run.

Parameters:

store (RecordStore) – The record store to analyse.

Returns:

Mapping from action-type string to its total occurrence count. Keys are always exactly: "sleep_start", "move", "tweet", "follow", "unfollow", "inner_thought", "order", "proposal", "consumption", "order_reaction", "proposal_reaction", "change_price".

Return type:

dict[str, int]

analyze_stores(stores)[source]

Count action occurrences across a collection of stores.

Calls analyze() independently for each store and collects the per-action counts into lists, enabling cross-run comparison. The box plot produced by draw_figs_all() shows the distribution of counts across runs.

Parameters:

stores (list[RecordStore]) – One record store per simulation run.

Returns:

Mapping from action-type string to a list of counts where result[action][i] is the count in stores[i]. Each inner list has the same length as stores.

Return type:

dict[str, list[int]]

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for action counts.

Parameters:

result (dict[str, int])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.follower_counter.FollowerCounter[source]

Bases: AnalyzerBase[dict[str, dict[int, int]], None]

Track the number of followers for each agent over simulation time.

Social-network dynamics in EconSimulacra are captured by FollowRecord, UnfollowRecord, and TweetRecord, all of which carry a num_followers snapshot at the moment the event occurred. FollowerCounter collects the most recently observed follower count for each (agent_name, time_step) pair.

The primary output of analyze() is a two-level dict:

{
    agent_name: {time_step: num_followers, ...},
    ...
}

draw_figs() visualises the maximum follower count reached by each agent as a ranked bar chart, making it easy to identify the most influential agents (influencers) in a run.

Note

Because follower counts are recorded only on social events (follow, unfollow, tweet), the time resolution of the resulting series depends on how frequently agents interact socially. Agents with no social events will be absent from the result.

name: str = 'follower_count'
analyze(store)[source]

Collect per-agent follower counts from social-network records.

Scans FollowRecord, UnfollowRecord, and TweetRecord entries in store. For each record the num_followers value at time_step is stored, overwriting any earlier value for the same (agent_name, time_step) pair.

Parameters:

store (RecordStore) – Record store containing social-network records to analyse.

Returns:

Nested dict mapping agent names to a {time_step: num_followers} mapping. Agents with no social events are absent from the result.

Return type:

dict[str, dict[int, int]]

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for follower counts.

Parameters:

result (dict[str, dict[int, int]])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.store_sales_analyzer.StoreSalesAnalyzer[source]

Bases: AnalyzerBase[tuple[dict[str, dict[int, float]], dict[str, dict[int, float]]], None]

Analyse revenue and unit sales for each store over simulation time.

A “store” is any agent that acts as the counterparty in an OrderReactionRecord or as the responder in an accepted ProposalReactionRecord. For each such store this analyzer aggregates two quantities per time step:

Revenue (sales)

The total monetary value of goods sold:

\[\text{sales}_{\text{store},t} = \sum_{i \,:\, t_i = t} \text{accept\_amount}_i \times \text{price}_i\]
Unit sales (sold amount)

The total physical quantity of goods sold:

\[\text{sold\_amount}_{\text{store},t} = \sum_{i \,:\, t_i = t} \text{accept\_amount}_i\]

Both quantities are plotted as bar charts over time by draw_figs(). build_summary() prints a revenue ranking across all stores, excluding household agents.

name: str = 'store_sales'
analyze(store)[source]

Aggregate per-store revenue and unit sales from transaction records.

Processes OrderReactionRecord and accepted ProposalReactionRecord entries. For order reactions the store is identified as counterparty_id; for accepted proposals it is responder_agent_id.

Revenue per time step is accumulated as:

\[\text{sales}_{t} = \sum_{i \,:\, t_i = t} \text{accept\_amount}_i \times \text{price}_i\]
Parameters:

store (RecordStore) – Record store containing transaction records.

Returns:

A 2-tuple where

  • sales maps store names to {time_step: total_revenue}.

  • sold_amounts maps store names to {time_step: total_units_sold}.

Return type:

tuple[SalesResult, SalesAmountResult]

Raises:

ValueError – If a transaction references an agent ID that is not present in AgentGenerationRecord.

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for total store sales and sold amounts.

Parameters:

result (tuple[dict[str, dict[int, float]], dict[str, dict[int, float]]])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.item_sales_analyzer.ItemSalesAnalyzer[source]

Bases: AnalyzerBase[tuple[dict[str, dict[int, float]], dict[str, dict[int, float]]], None]

Analyse revenue and unit sales aggregated by item type.

Unlike StoreSalesAnalyzer, which groups transactions by the selling agent, ItemSalesAnalyzer groups them by the item being traded. For each item it computes two time series:

Revenue (sales)

\[\begin{split}\text{sales}_{\text{item},t} = \sum_{\substack{i \,:\, \text{item}_i = \text{item} \\ t_i = t}} \text{accept\_amount}_i \times \text{price}_i\end{split}\]

Unit sales (sold amount)

\[\begin{split}\text{sold\_amount}_{\text{item},t} = \sum_{\substack{i \,:\, \text{item}_i = \text{item} \\ t_i = t}} \text{accept\_amount}_i\end{split}\]

Only OrderReactionRecord entries are considered; barter proposals (ProposalReactionRecord) are excluded because they do not carry a monetary price.

Both quantities are plotted as bar charts over time by draw_figs(). build_summary() ranks items by total revenue.

name: str = 'item_sales'
analyze(store)[source]

Aggregate per-item revenue and unit sales from order-reaction records.

Iterates over all OrderReactionRecord entries in store and accumulates accept_amount * price (revenue) and accept_amount (units) into per-item, per-step buckets.

Parameters:

store (RecordStore) – Record store containing order-reaction records.

Returns:

A 2-tuple where

  • sales maps item names to {time_step: total_revenue}.

  • sold_amounts maps item names to {time_step: total_units_sold}.

Return type:

tuple[SalesResult, SalesAmountResult]

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for total item sales and sold amounts.

Parameters:

result (tuple[dict[str, dict[int, float]], dict[str, dict[int, float]]])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.agent_behavior_stats_analyzer.AgentBehaviorStatsAnalyzer(exclude_agent_ids=[])[source]

Bases: AnalyzerBase[dict[str, dict[int, float]], list[dict[str, dict[int, float]]]]

Summarise each agent’s economic and social behaviour over a simulation run.

AgentBehaviorStatsAnalyzer computes four scalar statistics for every agent across the entire simulation:

Total purchase price

Total monetary value of all goods bought via order reactions:

\[S_{\text{total}} = \sum_{i} \text{accept\_amount}_i \times \text{price}_i\]
Average unit purchase price

Mean price paid per transaction:

\[\bar{p} = \frac{1}{n} \sum_{i=1}^{n} \text{price}_i\]
Total move distance

Cumulative Euclidean displacement over all move events:

\[L = \sum_{j} \sqrt{\sum_{k} (\text{new\_pos}_{j,k} - \text{old\_pos}_{j,k})^2}\]
Total word count

Sum of word counts (whitespace-split) across all tweets.

These statistics are useful for identifying behavioural heterogeneity: e.g., high-spending consumers, highly mobile agents, or unusually prolific tweeters. draw_figs() plots a histogram for each statistic across all agents.

Parameters:

exclude_agent_ids (list[int])

name: str = 'agent_behavior_stats'
analyze(store)[source]

Compute per-agent behaviour statistics from store.

Iterates over all agents found in AgentGenerationRecord entries. For each agent, scans the corresponding records and accumulates:

  • "total_purchase_price": \(\sum_i \text{accept\_amount}_i \times \text{price}_i\) from OrderReactionRecord.

  • "avg_unit_purchase_price": mean price per order-reaction record.

  • "total_move_distance": Euclidean distance summed over MoveRecord entries.

  • "total_word_counts": word count summed over TweetRecord entries.

Agents in exclude_agent_ids are skipped.

Parameters:

store (RecordStore) – Record store containing the simulation log.

Returns:

Outer keys are stat names ("total_purchase_price", "avg_unit_purchase_price", "total_move_distance", "total_word_counts"). Inner keys are agent IDs (int) and values are the corresponding scalar statistics (float).

Return type:

AgentBehaviorStats

analyze_stores(stores)[source]

Compute per-agent behaviour statistics for multiple stores.

Calls analyze() independently for each store. The resulting list preserves the order of stores.

Parameters:

stores (list[RecordStore]) – One record store per simulation run.

Returns:

One AgentBehaviorStats dict per store. Use draw_figs_all() to produce a box plot comparing stat distributions across runs.

Return type:

list[AgentBehaviorStats]

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for agent behavior stats.

Parameters:

result (dict[str, dict[int, float]])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.price_analyzer.PriceAnalyzer[source]

Bases: AnalyzerBase[dict[str, dict[int, float]], None]

Track the listed price of each item over simulation time.

Price changes in EconSimulacra are triggered by two event types:

PriceAnalyzer collects all such events and returns a step function \(p_{\text{item}}(t)\) for each item, where \(t\) is the simulation step at which the price was last set.

draw_figs() renders each item’s price trajectory using a step-wise line plot (where="post"), faithfully representing that prices remain constant between updates.

build_summary() reports for each item: initial price, final price, absolute change, relative change \((p_{\text{final}} - p_{\text{init}}) / p_{\text{init}}\), and the total number of update events.

name: str = 'price'
analyze(store)[source]

Collect the price history for every item in store.

Combines ItemGenerationRecord (initial prices) and ChangePriceRecord (subsequent updates). When multiple events occur at the same time_step for the same item, the last one processed overwrites earlier ones.

Parameters:

store (RecordStore) – Record store containing item-generation and price-change records.

Returns:

Mapping from item name to a {time_step: price} dict representing the step function \(p_{\text{item}}(t)\).

Return type:

dict[str, dict[int, float]]

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw price time series figures for each item.

Parameters:

result (dict[str, dict[int, float]]) – A dictionary mapping item names to dictionaries of timestamps and prices.

Returns:

A dictionary mapping figure names to matplotlib Figure objects.

Return type:

dict[str, matplotlib.figure.Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a rich summary table for item prices.

Parameters:

result (dict[str, dict[int, float]])

Return type:

rich.console.RenderableType

class econsimulacra.log_analyses.move_distance_analyzer.MoveDistanceWindowStats(total_distance, mean_distance, move_count, moving_agent_count)[source]

Bases: object

Aggregated movement statistics for a single time window.

All distances are computed with the metric specified in MoveDistanceAnalyzer.distance_metric.

Parameters:
  • total_distance (float)

  • mean_distance (float)

  • move_count (int)

  • moving_agent_count (int)

total_distance

Sum of all movement distances in this window:

\[D_{\text{total}} = \sum_{i \in \text{window}} d_i\]
Type:

float

mean_distance

Average distance per move event. Zero when move_count is zero:

\[\bar{d} = \frac{D_{\text{total}}}{N_{\text{moves}}}\]
Type:

float

move_count

Number of MoveRecord entries falling in this window.

Type:

int

moving_agent_count

Number of distinct agents that moved at least once during this window.

Type:

int

total_distance: float
mean_distance: float
move_count: int
moving_agent_count: int
class econsimulacra.log_analyses.move_distance_analyzer.MoveDistanceAnalyzer(name='move_distance', window_size=10, total_time_steps=None, distance_metric='euclidean', include_zero_distance=False, top_k_agents=10, agent_distance_=None, agent_move_count_=None)[source]

Bases: AnalyzerBase[dict[int, MoveDistanceWindowStats], None]

Analyse agent spatial mobility over fixed-size time windows.

Each MoveRecord carries an old_pos and a new_pos coordinate tuple. This analyzer computes the distance of each move under one of three metrics, then aggregates moves into non-overlapping windows of window_size steps.

Distance metrics

Euclidean distance (default):

\[d(\mathbf{p}, \mathbf{q}) = \sqrt{\sum_{k} (q_k - p_k)^2}\]

Manhattan distance:

\[d(\mathbf{p}, \mathbf{q}) = \sum_{k} |q_k - p_k|\]

Chebyshev distance:

\[d(\mathbf{p}, \mathbf{q}) = \max_{k} |q_k - p_k|\]

Typical use

High total_distance suggests agents are exploring or relocating frequently; low values indicate agents remain close to their initial positions. Combine with ConsumerClusterAnalyzer to understand what agents are doing while they move.

After analyze() returns, the fitted attributes agent_distance_ and agent_move_count_ are populated and used by draw_figs() (top-\(k\) agent bar chart) and build_summary().

Parameters:
  • name (str)

  • window_size (int)

  • total_time_steps (int | None)

  • distance_metric (Literal['euclidean', 'manhattan', 'chebyshev'])

  • include_zero_distance (bool)

  • top_k_agents (int)

  • agent_distance_ (dict[int, float] | None)

  • agent_move_count_ (dict[int, int] | None)

name

Analyzer name used for organizing outputs.

Type:

str

window_size

Number of simulation steps per aggregation window.

Type:

int

total_time_steps

Total simulation steps. Inferred from the data if None.

Type:

int, optional

distance_metric

One of "euclidean", "manhattan", or "chebyshev".

Type:

str

include_zero_distance

Whether to include records where old_pos == new_pos. Default False.

Type:

bool

top_k_agents

Number of highest-mobility agents shown in the summary and bar chart.

Type:

int

name: str = 'move_distance'
window_size: int = 10
total_time_steps: int | None = None
distance_metric: DistanceMetric = 'euclidean'
include_zero_distance: bool = False
top_k_agents: int = 10
agent_distance_: dict[int, float] | None = None
agent_move_count_: dict[int, int] | None = None
analyze(store)[source]

Aggregate movement distances into fixed-size time windows.

Each MoveRecord is assigned to the window:

\[w = \left\lfloor \frac{t}{\text{window\_size}} \right\rfloor \times \text{window\_size}\]

where \(t\) is time_step. Within each window the total distance \(D_{\text{total}}\), mean distance \(\bar{d}\), move count, and moving-agent count are computed.

After the call, the per-agent totals agent_distance_ and agent_move_count_ are populated for use by draw_figs() and build_summary().

Parameters:

store (RecordStore) – Record store containing MoveRecord instances.

Returns:

Mapping from window-start step to a MoveDistanceWindowStats for each window in range(0, total_time_steps, window_size). Windows with no moves have all-zero statistics.

Return type:

MoveDistanceResult

Raises:

ValueError – If no valid movement records are found, if window_size is not positive, or if position vectors have inconsistent dimensionalities.

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw movement-distance diagnostic figures.

Parameters:

result (dict[int, MoveDistanceWindowStats]) – Output returned by analyze().

Returns:

Dictionary mapping figure names to Matplotlib figures.

Return type:

dict[str, matplotlib.figure.Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a Rich summary panel for movement-distance analysis.

Parameters:

result (dict[int, MoveDistanceWindowStats]) – Output returned by analyze().

Returns:

Rich panel summarizing aggregate movement statistics.

Return type:

rich.panel.Panel

class econsimulacra.log_analyses.stress_analyzer.StressAnalyzer(exclude_agent_ids=[])[source]

Bases: AnalyzerBase[dict[int, dict[str, dict[int, float]]], None]

Analyse per-agent stress levels over simulation time.

EconSimulacra agents maintain internal stress values for different life domains (e.g., consumption, movement, economic condition, sleep). These values are periodically written to agent memory and recorded as ObsRecord entries with obs_type == "memory".

StressAnalyzer extracts memory keys that end in "_history_stress" and returns a three-level dict:

{
    agent_id: {
        stress_type: {time_step: stress_value, ...},
        ...
    },
    ...
}

where stress_type is the prefix before "_history_stress" (e.g., "consumption", "movement", "economic", "sleep"). Higher stress values indicate that an agent is further from its target state for that domain.

draw_figs() plots each stress type as a time-series overlay across all agents, allowing quick identification of agents under sustained stress and the time periods where stress peaks.

Note

Agents listed in exclude_agent_ids (e.g., firm agents that do not have stress models) are silently skipped.

Parameters:

exclude_agent_ids (list[int])

name: str = 'stress'
analyze(store)[source]

Extract per-agent, per-domain stress time series from store.

Scans all ObsRecord entries whose obs_type is "memory". For each record the observation dict is searched for keys ending in "_history_stress"; the suffix is stripped to obtain the stress_type label and the numeric value is recorded at the corresponding time_step.

Parameters:

store (RecordStore) – Record store containing memory observation records.

Returns:

Three-level dict of the form:

{
    agent_id: {
        stress_type: {
            time_step: stress_value,
            ...
        },
        ...
    },
    ...
}

Agents listed in exclude_agent_ids are omitted.

Return type:

StressData

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw diagnostic figures from a single-run analysis result.

Parameters:

result (T) – Output of analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

class econsimulacra.log_analyses.topic_analyzer.TopicResult(tweets, topic_counts, topic_shares, topic_summary, agent_topic_counts)[source]

Bases: object

Result of tweet (or inner-thought) topic analysis with BERTopic.

Parameters:
  • tweets (pandas.DataFrame)

  • topic_counts (pandas.DataFrame)

  • topic_shares (pandas.DataFrame)

  • topic_summary (pandas.DataFrame)

  • agent_topic_counts (pandas.DataFrame)

tweets

Raw message table with columns time, time_step, agent_id, message, num_follows, num_followers, topic, topic_name, time_window. One row per message.

Type:

DataFrame

topic_counts

Message count per (time_window, topic_name) pivot table. Index is time_window; columns are topic name strings.

Type:

DataFrame

topic_shares

Row-normalised version of topic_counts representing the share of each topic at each time window:

\[\text{share}_{t, k} = \frac{\text{count}_{t, k}}{\sum_{k'} \text{count}_{t, k'}}\]
Type:

DataFrame

topic_summary

One row per topic. Columns include topic, topic_name, num_tweets, num_agents, first_time_step, last_time_step, avg_followers, avg_follows. Sorted descending by num_tweets.

Type:

DataFrame

agent_topic_counts

Message count per (agent_id, topic_name) pivot table showing which agents discuss which topics.

Type:

DataFrame

tweets: pandas.DataFrame
topic_counts: pandas.DataFrame
topic_shares: pandas.DataFrame
topic_summary: pandas.DataFrame
agent_topic_counts: pandas.DataFrame
class econsimulacra.log_analyses.topic_analyzer.TopicAnalyzer(topic_model=None, time_window_size=1, top_n_topics=10, min_topic_size=5, calculate_probabilities=False, is_inner_thought=False, exclude_agent_ids=[])[source]

Bases: AnalyzerBase[TopicResult, list[TopicResult]]

Discover and track discussion topics in agent tweets or inner thoughts.

This analyzer extracts TweetRecord (or InnerThoughtRecord when is_inner_thought=True) from a RecordStore, runs BERTopic to assign a topic to each message, and then aggregates topic assignments over time windows to capture how the topic mix of agent discourse evolves.

Processing pipeline

  1. Extract records → build a message DataFrame.

  2. Fit (or transform) a BERTopic model to assign integer topic IDs.

  3. Map topic IDs to human-readable names via BERTopic.get_topic_info().

  4. Aggregate into:

    • topic_counts – message count per (time_window, topic_name)

    • topic_shares – row-normalised counts (see TopicResult)

    • topic_summary – per-topic statistics across the whole run

    • agent_topic_counts – per-agent topic distribution

Time windowing

Messages are grouped into windows of time_window_size steps:

\[w(t) = \left\lfloor \frac{t}{\text{time\_window\_size}} \right\rfloor \times \text{time\_window\_size}\]

Multi-store mode

analyze_stores() fits a single BERTopic model on the pooled corpus from all stores, then transforms each store’s messages independently. This guarantees that topic IDs are comparable across runs.

Note

Topic -1 is BERTopic’s built-in outlier/noise cluster. It is included in all DataFrames but excluded from top-topic charts.

Parameters:
  • topic_model (Optional[object])

  • time_window_size (int)

  • top_n_topics (int)

  • min_topic_size (int)

  • calculate_probabilities (bool)

  • is_inner_thought (bool)

  • exclude_agent_ids (list[int])

name: str = 'tweet_topic'
analyze(store)[source]

Fit BERTopic and assign topics to all messages in store.

Calls BERTopic.fit_transform on all messages from this store. If topic_model was supplied at construction time, that model is used directly; otherwise a new BERTopic model is created with min_topic_size.

Parameters:

store (RecordStore) – Record store containing tweet or inner- thought records.

Returns:

Aggregated topic analysis result. Returns an empty TopicResult (all DataFrames empty) if no relevant records are found.

Return type:

TopicResult

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Draw topic analysis figures for a single run.

Produces up to three figures (keyed by inner_str suffix where inner_str is "inner_thought" or "tweet"):

  • topic_counts_over_time_{inner_str} – line chart of message count per topic over time windows (top top_n_topics).

  • topic_shares_over_time_{inner_str} – stacked area chart of topic share over time.

  • top_topic_counts_{inner_str} – horizontal bar chart of total message counts for the top top_n_topics topics.

Parameters:

result (TopicResult) – Output of analyze().

Returns:

Figure name → Matplotlib figure. Empty if result contains no tweets.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary_all(results)[source]

Build rich summary aggregated across multiple stores.

Parameters:

results (list[TopicResult])

build_summary(result)[source]

Build rich summary.

Parameters:

result (TopicResult)

class econsimulacra.log_analyses.consumer_cluster_analyzer.FitTransform2D(*args, **kwargs)[source]

Bases: Protocol

fit_transform(x)[source]

Project input vectors into a lower-dimensional representation.

Parameters:

x (numpy.typing.NDArray.numpy.float32)

Return type:

Any

class econsimulacra.log_analyses.consumer_cluster_analyzer.ConsumerClusterAnalyzer(name='consumer_cluster', window_size=10, total_time_steps=None, k_candidates=(2, 3, 4, 5, 6), exclude_items=('Yen',), is_consumption=True, random_state=42, n_stability_runs=10, normalize=False, item_list=None, selected_k=None, cluster_centers_=None, labels_=None, vectors_=None, agent_ids_=None, window_starts_=None, stability_scores_=None, silhouette_scores_=None)[source]

Bases: AnalyzerBase[dict[int, dict[int | datetime, tuple[int, float32]]], None]

Cluster agents by their purchase or consumption behaviour over rolling windows.

For each (agent, time-window) pair a behaviour vector is constructed:

\[\mathbf{v}_{a,w} \in \mathbb{R}^{|\mathcal{I}|}, \quad v_{a,w,i} = \sum_{t \in w} \text{amount}_{a,t,i}\]

where \(\mathcal{I}\) is the set of tracked items and \(\text{amount}_{a,t,i}\) is the quantity of item \(i\) consumed (or ordered) by agent \(a\) at step \(t\).

The resulting matrix of vectors is clustered with KMeans. The number of clusters \(k\) is selected automatically from k_candidates by maximising clustering stability: the mean pairwise Adjusted Rand Index (ARI) across n_stability_runs independent KMeans runs:

\[k^* = \operatorname*{arg\,max}_{k \in K} \left\langle \text{ARI}\bigl( \mathbf{z}^{(r)}, \mathbf{z}^{(s)}\bigr) \right\rangle_{r < s}\]

where \(\mathbf{z}^{(r)}\) is the cluster-label vector from the \(r\)-th run. Ties are broken by preferring a smaller \(k\). Silhouette scores are computed for diagnostics but are not used for model selection.

After analyze() returns, the following fitted attributes are available for draw_figs() and build_summary():

  • vectors_ – the \((N_{\text{samples}},\, |\mathcal{I}|)\) behaviour matrix.

  • labels_ – cluster assignment for each sample.

  • cluster_centers_ – the actual sample closest to each centroid.

  • selected_k – the chosen number of clusters \(k^*\).

  • stability_scores_ – mean pairwise ARI per candidate \(k\).

  • silhouette_scores_ – silhouette score per candidate \(k\).

Parameters:
  • name (str)

  • window_size (int)

  • total_time_steps (int | None)

  • k_candidates (tuple[int, ...])

  • exclude_items (tuple[str, ...])

  • is_consumption (bool)

  • random_state (int)

  • n_stability_runs (int)

  • normalize (bool)

  • item_list (list[str] | None)

  • selected_k (int | None)

  • cluster_centers_ (numpy.typing.NDArray.numpy.float32 | None)

  • labels_ (numpy.typing.NDArray.numpy.int_ | None)

  • vectors_ (numpy.typing.NDArray.numpy.float32 | None)

  • agent_ids_ (list[int] | None)

  • window_starts_ (list[int | datetime] | None)

  • stability_scores_ (dict[int, float] | None)

  • silhouette_scores_ (dict[int, float] | None)

name

Analyzer name.

Type:

str

window_size

Number of steps in each rolling window.

Type:

int

total_time_steps

Total simulation steps. Inferred from the data if None.

Type:

int, optional

k_candidates

Candidate values of \(k\).

Type:

tuple[int, …]

exclude_items

Items excluded from vectorisation (e.g. currency tokens such as "Yen").

Type:

tuple[str, …]

is_consumption

If True, use ConsumptionRecord; otherwise use OrderRecord.

Type:

bool

random_state

Base random seed for KMeans and UMAP/PCA.

Type:

int

n_stability_runs

Number of KMeans fits per \(k\) for stability estimation.

Type:

int

normalize

If True, normalise each non-zero vector to sum to 1 before clustering.

Type:

bool

name: str = 'consumer_cluster'
window_size: int = 10
total_time_steps: int | None = None
k_candidates: tuple[int, ...] = (2, 3, 4, 5, 6)
exclude_items: tuple[str, ...] = ('Yen',)
is_consumption: bool = True
random_state: int = 42
n_stability_runs: int = 10
normalize: bool = False
item_list: list[str] | None = None
selected_k: int | None = None
cluster_centers_: ConsumerVector | None = None
labels_: ClusterLabelArray | None = None
vectors_: ConsumerVector | None = None
agent_ids_: list[int] | None = None
window_starts_: list[TimeKey] | None = None
stability_scores_: dict[int, float] | None = None
silhouette_scores_: dict[int, float] | None = None
analyze(store)[source]

Cluster agents by behavioural vectors and return per-window assignments.

Steps

  1. Build item vocabulary \(\mathcal{I}\) from ItemGenerationRecord entries (minus exclude_items).

  2. Construct behaviour vectors \(\mathbf{v}_{a,w}\) for every (agent, window) pair.

  3. For each \(k \in\) k_candidates, run KMeans n_stability_runs times and compute mean pairwise ARI.

  4. Select \(k^* = \operatorname{argmax}\) ARI (ties → smaller \(k\)).

  5. Refit KMeans with \(k^*\) and random_state.

  6. Map cluster labels back to (agent_id, window_start) pairs.

Parameters:

store (RecordStore) – Record store containing item-generation records and either consumption or order records.

Returns:

Nested dict {agent_id: {window_start: (cluster_label, behavior_vector)}}.

Return type:

ConsumerClusterResult

Raises:

ValueError – If no valid items, records, or vectors are found, or if no valid \(k\) candidates remain after filtering.

analyze_stores(stores)[source]

Analyse a list of stores and return an aggregate result.

This is the multi-run counterpart of analyze(). The aggregate result type U is defined by the subclass and may differ from T.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated analysis result across all stores.

Return type:

U

draw_figs(result)[source]

Create diagnostic figures for the clustering result.

Produces four figures:

  • embedding_by_agent – 2-D scatter plot (UMAP or PCA fallback) coloured by agent ID.

  • embedding_by_time – same scatter plot coloured by window start time, showing temporal drift in behaviour space.

  • cluster_transition_network – directed graph where node size is proportional to cluster membership and edge width is proportional to the number of agents transitioning between clusters across consecutive windows.

  • cluster_representatives – bar charts of the representative item-consumption profile for each cluster.

  • cluster_counts – stacked bar chart of the number of agents in each cluster at each time window.

Parameters:

result (ConsumerClusterResult) – Output returned by analyze().

Returns:

Figure name → Matplotlib figure.

Return type:

dict[str, Figure]

Raises:

RuntimeError – If called before analyze().

draw_figs_all(individual_results)[source]

Draw diagnostic figures across multiple runs.

Parameters:

individual_results (list[T]) – One result per store, each produced by analyze().

Returns:

Mapping from figure name to Matplotlib figure.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a Rich summary panel for clustering diagnostics.

The summary includes the clustering mode, sample size, item count, selected number of clusters, stability scores, silhouette scores, sample counts per cluster, and the most frequent periods for each cluster.

Parameters:

result (dict[int, dict[int | datetime, tuple[int, numpy.typing.NDArray.numpy.float32]]]) – Output returned by analyze().

Returns:

Rich panel containing a summary table.

Return type:

rich.panel.Panel

class econsimulacra.log_analyses.temporal_dynamics_analyzer.ActionTypeStats(mean_burstiness, mean_memory, inter_event_times, activity_by_hour, n_events)[source]

Bases: object

Temporal statistics for a single action type.

Parameters:
mean_burstiness

Mean across agents of the burstiness parameter \(B\) (Goh & Barabási, 2008):

\[B = \frac{\sigma_\tau - \mu_\tau}{\sigma_\tau + \mu_\tau}\]

None if no agent had at least 2 inter-event intervals.

Type:

float, optional

mean_memory

Mean across agents of the memory coefficient \(M\), the Pearson correlation between consecutive inter-event times. None if no agent had at least 3 intervals.

Type:

float, optional

inter_event_times

Pooled inter-event times (in simulation steps) across all agents for this action type.

Type:

list[int]

activity_by_hour

Count of events per clock-hour (0–23) for this action type.

Type:

dict[int, int]

n_events

Total number of events of this action type.

Type:

int

mean_burstiness: float | None
mean_memory: float | None
inter_event_times: list[int]
activity_by_hour: dict[int, int]
n_events: int
class econsimulacra.log_analyses.temporal_dynamics_analyzer.ActionTypeAggregateStats(mean_burstiness_mean, mean_burstiness_std, mean_memory_mean, mean_memory_std, mean_iet_mean, mean_iet_std, mean_n_events, std_n_events, pooled_inter_event_times, mean_activity_by_hour)[source]

Bases: object

Aggregated temporal statistics for one action type across multiple runs.

Parameters:
  • mean_burstiness_mean (float | None)

  • mean_burstiness_std (float | None)

  • mean_memory_mean (float | None)

  • mean_memory_std (float | None)

  • mean_iet_mean (float | None)

  • mean_iet_std (float | None)

  • mean_n_events (float)

  • std_n_events (float | None)

  • pooled_inter_event_times (list[int])

  • mean_activity_by_hour (dict[int, float])

mean_burstiness_mean

Mean of per-run burstiness values.

Type:

float, optional

mean_burstiness_std

Std of per-run burstiness values. None when fewer than 2 runs contributed a value.

Type:

float, optional

mean_memory_mean

Mean of per-run memory coefficients.

Type:

float, optional

mean_memory_std

Std of per-run memory coefficients. None when fewer than 2 runs contributed a value.

Type:

float, optional

mean_iet_mean

Mean of per-run mean inter-event times.

Type:

float, optional

mean_iet_std

Std of per-run mean inter-event times. None when fewer than 2 runs contributed a value.

Type:

float, optional

mean_n_events

Mean event count across runs.

Type:

float

std_n_events

Std of event counts across runs. None when fewer than 2 runs contributed.

Type:

float, optional

pooled_inter_event_times

All inter-event times pooled across every run for this action type.

Type:

list[int]

mean_activity_by_hour

Mean activity count per clock-hour averaged across runs.

Type:

dict[int, float]

mean_burstiness_mean: float | None
mean_burstiness_std: float | None
mean_memory_mean: float | None
mean_memory_std: float | None
mean_iet_mean: float | None
mean_iet_std: float | None
mean_n_events: float
std_n_events: float | None
pooled_inter_event_times: list[int]
mean_activity_by_hour: dict[int, float]
class econsimulacra.log_analyses.temporal_dynamics_analyzer.TemporalDynamicsAggregateResult(by_action_type, circadian_autocorr_mean, circadian_autocorr_std, n_runs)[source]

Bases: object

Aggregated temporal-dynamics statistics across multiple simulation runs.

Parameters:
by_action_type

Per-action-type aggregate statistics.

Type:

dict[str, ActionTypeAggregateStats]

circadian_autocorr_mean

Mean circadian autocorrelation across runs.

Type:

float, optional

circadian_autocorr_std

Std of circadian autocorrelation. None when fewer than 2 runs produced a value.

Type:

float, optional

n_runs

Number of simulation runs included in the aggregate.

Type:

int

by_action_type: dict[str, ActionTypeAggregateStats]
circadian_autocorr_mean: float | None
circadian_autocorr_std: float | None
n_runs: int
class econsimulacra.log_analyses.temporal_dynamics_analyzer.TemporalDynamicsResult(by_action_type, circadian_autocorr, n_agents, n_events)[source]

Bases: object

Temporal-dynamics statistics for one simulation run.

Statistics are computed per action type so that, for example, the burstiness of movement can be compared against the burstiness of consumption independently.

Parameters:
by_action_type

Mapping from action-type name (e.g. "Move", "Consumption") to its temporal statistics. Only action types that produced at least one event are included.

Type:

dict[str, ActionTypeStats]

circadian_autocorr

Autocorrelation of the per-step action-count series (pooled over all action types) at lag period_steps:

\[R(\ell) = \frac{\sum_t (x_t - \bar{x}) (x_{t+\ell} - \bar{x})} {\sum_t (x_t - \bar{x})^2}\]

None if the run is shorter than two periods.

Type:

float, optional

n_agents

Number of agents with at least one recorded action.

Type:

int

n_events

Total number of action events across all agents and all action types.

Type:

int

by_action_type: dict[str, ActionTypeStats]
circadian_autocorr: float | None
n_agents: int
n_events: int
class econsimulacra.log_analyses.temporal_dynamics_analyzer.TemporalDynamicsAnalyzer(name='temporal_dynamics', action_types=(<class 'econsimulacra.log_analyses.records.MoveRecord'>, <class 'econsimulacra.log_analyses.records.ConsumptionRecord'>, <class 'econsimulacra.log_analyses.records.OrderRecord'>, <class 'econsimulacra.log_analyses.records.TweetRecord'>, <class 'econsimulacra.log_analyses.records.FollowRecord'>, <class 'econsimulacra.log_analyses.records.UnfollowRecord'>, <class 'econsimulacra.log_analyses.records.SleepStartRecord'>), period_steps=24, agent_type=None, exclude_agent_ids=<factory>)[source]

Bases: AnalyzerBase[TemporalDynamicsResult, TemporalDynamicsAggregateResult]

Quantify the temporal structure of agent activity in a simulation run.

This analyzer measures whether agent activity is bursty or regular, whether inactive periods cluster together (memory), and whether there is a circadian rhythm. Statistics are computed per action type: burstiness of movement, burstiness of consumption, etc. are reported independently.

Burstiness \(B\)

Measured per agent as the normalised difference between standard deviation and mean of that agent’s inter-event times for the given action type:

\[B = \frac{\sigma_\tau - \mu_\tau}{\sigma_\tau + \mu_\tau} \in [-1,\; 1]\]

\(B > 0\) indicates bursty activity with many short intervals and a few long intervals; \(B < 0\) indicates regular activity with similar-length intervals.

Memory \(M\)

The lag-1 Pearson correlation of inter-event times:

\[M = \text{corr}(\tau_i,\, \tau_{i+1})\]

\(M > 0\) indicates that short active periods follow other short active periods; \(M < 0\) indicates alternating active and quiet phases.

Circadian autocorrelation

The normalised autocorrelation of the per-step event count (pooled across all action types) at lag period_steps:

\[R(\ell) = \frac{\sum_t (x_t - \bar{x})(x_{t+\ell} - \bar{x})} {\sum_t (x_t - \bar{x})^2}\]

A value near 1 means the activity pattern repeats every period_steps steps — a sign of a functioning day–night cycle.

Hourly histograms

Activity counts are binned by clock-hour (0–23) for each action type separately. If records carry a datetime timestamp, the hour is extracted directly; otherwise time_step mod period_steps is used.

Parameters:
name

Analyzer name used for organizing outputs.

Type:

str

action_types

Record types treated as overt agent actions. Defaults to move, consumption, order, tweet, follow, unfollow, and sleep-start events.

Type:

tuple

period_steps

Number of simulation steps per day, used both as the circadian autocorrelation lag and the fallback for clock-hour binning when records lack datetime timestamps.

Type:

int

agent_type

If set, restrict the analysis to agents of this type (e.g. "LLMAgent"); otherwise all agents with action records are used.

Type:

str, optional

exclude_agent_ids

Agent IDs to exclude from the analysis.

Type:

list[int]

name: str = 'temporal_dynamics'
action_types: tuple[type[TimedRecord], ...] = (<class 'econsimulacra.log_analyses.records.MoveRecord'>, <class 'econsimulacra.log_analyses.records.ConsumptionRecord'>, <class 'econsimulacra.log_analyses.records.OrderRecord'>, <class 'econsimulacra.log_analyses.records.TweetRecord'>, <class 'econsimulacra.log_analyses.records.FollowRecord'>, <class 'econsimulacra.log_analyses.records.UnfollowRecord'>, <class 'econsimulacra.log_analyses.records.SleepStartRecord'>)
period_steps: int = 24
agent_type: str | None = None
exclude_agent_ids: list[int]
analyze(store)[source]

Compute temporal-dynamics statistics from a record store.

Algorithm

  1. Optionally filter agents by agent_type using AgentGenerationRecord entries.

  2. For each action type in action_types, collect events for qualifying agents.

  3. Per action type and per agent, sort event steps and compute inter-event intervals \(\tau_i = t_{i+1} - t_i\). Compute \(B\) and \(M\) from each agent’s interval sequence, then average across agents to obtain the per-action-type statistics.

  4. Build hourly histograms for each action type.

  5. Compute the circadian autocorrelation at lag period_steps using the pooled event stream across all action types.

Parameters:

store (RecordStore) – Record store containing the simulation log.

Returns:

Aggregated temporal statistics.

Return type:

TemporalDynamicsResult

Raises:

ValueError – If period_steps is not positive, or if no action records are found.

analyze_stores(stores)[source]

Aggregate temporal-dynamics statistics across multiple stores.

Runs analyze() on each store and aggregates per-action-type statistics (mean and standard deviation of burstiness, memory, mean inter-event time, and event count) across runs.

Parameters:

stores (list[RecordStore]) – One store per simulation run.

Returns:

Aggregated statistics.

Return type:

TemporalDynamicsAggregateResult

draw_figs(result)[source]

Draw temporal-dynamics figures.

Produces one inter-event time line plot and one activity-by-hour bar chart for each action type that has recorded events.

Parameters:

result (TemporalDynamicsResult) – Output returned by analyze().

Returns:

Dictionary mapping figure names to Matplotlib figures. Keys follow the pattern "inter_event_times_{action_name}" and "activity_by_hour_{action_name}".

Return type:

dict[str, matplotlib.figure.Figure]

draw_figs_all(individual_results)[source]

Draw temporal-dynamics figures pooled across multiple runs.

Produces one inter-event time log-log line plot and one activity-by-hour bar chart (mean across runs) for each action type.

Parameters:

individual_results (list[TemporalDynamicsResult]) – List of results from analyze(), one per run.

Returns:

Dictionary mapping figure names to Matplotlib figures. Keys follow the same pattern as draw_figs().

Return type:

dict[str, matplotlib.figure.Figure]

build_summary(result)[source]

Build a Rich summary panel for temporal-dynamics analysis.

Parameters:

result (TemporalDynamicsResult) – Output returned by analyze().

Returns:

Rich panel summarising per-action-type temporal statistics.

Return type:

rich.panel.Panel

build_summary_all(result)[source]

Build a Rich summary panel for multi-run temporal-dynamics analysis.

Shows mean ± standard deviation across runs for burstiness, memory, mean inter-event time, and event count per action type.

Parameters:

result (TemporalDynamicsAggregateResult) – Output returned by analyze_stores().

Returns:

Rich panel summarising aggregated temporal statistics.

Return type:

rich.panel.Panel

class econsimulacra.log_analyses.topic_sales_analyzer.QuadraticFitResult(store_name, a, b, c, r2, mse, rmse, n_samples, x_mean, x_std, y_mean, y_std)[source]

Bases: object

Quadratic regression result for a single store.

Stores the fitted coefficients of the model:

\[\hat{y}_{\text{norm}} = a \cdot x_{\text{norm}}^2 + b \cdot x_{\text{norm}} + c\]

where \(x\) and \(y\) are z-scored word count and sales:

\[x_{\text{norm}} = \frac{x - \bar{x}}{\sigma_x}, \qquad y_{\text{norm}} = \frac{y - \bar{y}}{\sigma_y}\]
Parameters:
store_name

Name of the store.

Type:

str

a

Coefficient of the quadratic term \(x^2\).

Type:

float

b

Coefficient of the linear term \(x\).

Type:

float

c

Constant term (intercept).

Type:

float

r2

Coefficient of determination \(R^2\).

Type:

float

mse

Mean squared error on the normalised scale.

Type:

float

rmse

Root mean squared error on the normalised scale.

Type:

float

n_samples

Number of (word_count, sales) data points.

Type:

int

x_mean

Sample mean of the raw word-count values.

Type:

float

x_std

Sample standard deviation of the raw word-count values.

Type:

float

y_mean

Sample mean of the raw sales values.

Type:

float

y_std

Sample standard deviation of the raw sales values.

Type:

float

store_name: str
a: float
b: float
c: float
r2: float
mse: float
rmse: float
n_samples: int
x_mean: float
x_std: float
y_mean: float
y_std: float
class econsimulacra.log_analyses.topic_sales_analyzer.TopicSalesAnalyzer(topic_words, window_size=24, is_inner_thought=False, use_amount=False, exclude_agent_ids=[])[source]

Bases: AnalyzerBase[dict[int, dict[str, float]], dict[str, QuadraticFitResult]]

Estimate the relationship between topic word count and store sales.

TopicSalesAnalyzer counts how often a set of topic words appear in agent tweets (or inner thoughts) within each time window, and cross-references those counts with the concurrent sales of each store. Across multiple runs, analyze_stores() pools (word_count, sales) data points and fits the quadratic model on the z-scored variables:

\[\hat{y}_{\text{norm}} = a \cdot x_{\text{norm}}^2 + b \cdot x_{\text{norm}} + c\]

Interpretation of curvature

  • \(a > 0\) (convex ↑): sales accelerate with topic buzz — a super-linear amplification effect.

  • \(a < 0\) (concave ∩): the relationship saturates or reverses at high word counts — possible over-saturation.

  • \(a \approx 0\) (linear): a proportional relationship, well captured by the linear term \(b\).

Goodness of fit is reported via \(R^2\) and RMSE. A low \(R^2\) suggests store sales are driven by factors beyond topic word count alone.

Note

The single-run analyze() method collects the raw (word_count, sales) data and is intended as a building block for analyze_stores().

Parameters:
name: str = 'topic_sales'
analyze(store)[source]

Collect word-count and sales data per time window from store.

Groups TweetRecord (or InnerThoughtRecord) and OrderReactionRecord entries into non-overlapping windows of window_size steps:

\[w(t) = \left\lfloor \frac{t}{\text{window\_size}} \right\rfloor \times \text{window\_size}\]

For each window, the total occurrence count of topic_words in agent messages is accumulated as "word_count", and the revenue of each store is accumulated as "sales_{firm_name}" (or "sales_amount_{firm_name}" when use_amount=True).

Parameters:

store (RecordStore) – Record store to analyse.

Returns:

Dict mapping window-start step to a nested dict. Example:

{
    0:  {"word_count": 0,  "sales_Pizza Place": 100.5, ...},
    24: {"word_count": 5,  "sales_Pizza Place": 50.0,  ...},
    ...
}

Return type:

TopicSalesResult

draw_figs(result)[source]

Draw scatter plots of topic word count vs sales for each store.

Produces one scatter plot per store found in result, with the raw (non-normalised) word-count on the x-axis and revenue (or accepted amount when use_amount=True) on the y-axis.

Parameters:

result (TopicSalesResult) – Output of analyze().

Returns:

Mapping from firm name to Matplotlib figure.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw pooled scatter plots across multiple runs.

Aggregates the data from all TopicSalesResult instances and draws one scatter plot per firm showing all (word_count, sales) data points from all runs on the same axes. Both axes are z-scored (standardised) to make cross-firm comparison easier.

Parameters:

individual_results (list[TopicSalesResult]) – One result per simulation run, each produced by analyze().

Returns:

Mapping from firm name to Matplotlib figure.

Return type:

dict[str, Figure]

analyze_stores(stores)[source]

Fit a quadratic model to pooled (word_count, sales) data.

Calls analyze() on each store, pools the resulting data points per firm, z-scores both variables, and fits the quadratic model:

\[\hat{y}_{\text{norm}} = a \cdot x_{\text{norm}}^2 + b \cdot x_{\text{norm}} + c\]

using scipy.optimize.curve_fit(). Stores with fewer than 2 data points or zero variance in \(x\) or \(y\) are skipped silently.

Parameters:

stores (list[RecordStore]) – One record store per simulation run.

Returns:

Mapping from firm name to the corresponding QuadraticFitResult. Firms for which a fit could not be obtained are absent from the dict.

Return type:

dict[str, QuadraticFitResult]

build_summary_all(results)[source]

Summarise quadratic regression results in a Rich table.

Displays one row per store, ranked by \(R^2\) descending, with columns for \(a\), \(b\), \(c\), \(R^2\), RMSE, sample count, and a trend label:

  • Convex ↑ when \(a > 0\) (accelerating returns with buzz)

  • Concave ∩ when \(a < 0\) (diminishing / saturating returns)

  • Linear when \(a \approx 0\)

Parameters:

results (dict[str, QuadraticFitResult]) – Mapping from store name to regression result, as returned by analyze_stores().

Returns:

Rich panel containing the summary table.

Return type:

RenderableType

class econsimulacra.log_analyses.topic_sentiment_analyzer.TopicSentimentWindowStats(word_count, mean_sentiment, std_sentiment, n_sentiment)[source]

Bases: object

Per-window statistics for topic word count and sentiment distribution.

Parameters:
  • word_count (int)

  • mean_sentiment (float | None)

  • std_sentiment (float | None)

  • n_sentiment (int)

word_count

Total occurrences of topic words across all matching records in this window (same counting convention as TopicSalesAnalyzer).

Type:

int

mean_sentiment

Mean sentiment score of matching records in this window. None if no records had a valid (non-None) sentiment score.

Type:

float, optional

std_sentiment

Sample standard deviation (ddof=1) of sentiment scores of matching records. None if fewer than 2 records had a valid sentiment score.

Type:

float, optional

n_sentiment

Number of matching records with a non-None sentiment score used to compute mean_sentiment and std_sentiment.

Type:

int

word_count: int
mean_sentiment: float | None
std_sentiment: float | None
n_sentiment: int
class econsimulacra.log_analyses.topic_sentiment_analyzer.TopicSentimentCorrelationResult(r, p_value, n_samples, word_counts, std_sentiments)[source]

Bases: object

Cross-store correlation result between topic buzz and sentiment polarisation.

This is the output of TopicSentimentAnalyzer.analyze_stores(). It pools (word_count, std_sentiment) pairs across all windows and all stores to estimate the Pearson correlation coefficient \(r\).

Parameters:
r

Pearson correlation coefficient between word_count and std_sentiment across all pooled (window, store) data points:

\[r = \frac{\sum_i (x_i - \bar{x})(y_i - \bar{y})} {\sqrt{\sum_i (x_i - \bar{x})^2}\; \sqrt{\sum_i (y_i - \bar{y})^2}}\]

None if fewer than 2 valid data points are available, or if either variable has zero variance.

Type:

float, optional

p_value

Two-tailed p-value for the null hypothesis \(r = 0\). None when r is None.

Type:

float, optional

n_samples

Number of (word_count, std_sentiment) pairs used in the correlation computation. Only windows with std_sentiment is not None contribute.

Type:

int

word_counts

Pooled word-count values (\(x\)).

Type:

list[float]

std_sentiments

Pooled sentiment std-deviation values (\(y\)).

Type:

list[float]

r: float | None
p_value: float | None
n_samples: int
word_counts: list[float]
std_sentiments: list[float]
class econsimulacra.log_analyses.topic_sentiment_analyzer.TopicSentimentAnalyzer(topic_words, window_size=24, is_inner_thought=False, exclude_agent_ids=[])[source]

Bases: AnalyzerBase[dict[int, TopicSentimentWindowStats], TopicSentimentCorrelationResult]

Measure how topic buzz intensity relates to sentiment polarisation.

This analyzer tests the hypothesis:

The more agents discuss a topic in a time window, the more polarised (higher standard deviation) the sentiment of those discussions becomes.

For each time window of window_size steps, it counts how often the topic_words appear in agent tweets (or inner thoughts) and computes the distribution of sentiment scores for those matching records.

Per-window computation

Let \(\mathcal{R}_w\) be the set of records in window \(w\) that contain at least one word from topic_words, and let \(\mathcal{S}_w \subseteq \mathcal{R}_w\) be the subset with a non-None sentiment score.

  • Word count – total topic-word occurrences (buzz intensity):

    \[c_w = \sum_{r \in \mathcal{R}_w} \sum_{k} \text{count}(\text{word}_k,\, \text{message}_r)\]
  • Sentiment mean:

    \[\bar{s}_w = \frac{1}{|\mathcal{S}_w|} \sum_{r \in \mathcal{S}_w} s_r\]
  • Sentiment std (sample standard deviation, ddof=1):

    \[\sigma_w = \sqrt{\frac{1}{|\mathcal{S}_w|-1} \sum_{r \in \mathcal{S}_w} (s_r - \bar{s}_w)^2}\]

Cross-store correlation

analyze_stores() pools all (c_w, \sigma_w) pairs across every window and every run, then computes the Pearson correlation coefficient:

\[r = \text{corr}(c_w,\; \sigma_w)\]

A significantly positive \(r\) supports the polarisation hypothesis. Use build_summary_all() to display \(r\) and its p-value.

Note

The sentiment field of TweetRecord and InnerThoughtRecord must be populated (not None) for sentiment statistics to be meaningful. If sentiment scores are absent, std_sentiment will be None for all windows and the correlation cannot be estimated.

Parameters:
name: str = 'topic_sentiment'
analyze(store)[source]

Compute per-window buzz intensity and sentiment statistics.

For each time window the method:

  1. Filters records to those containing at least one occurrence of a word from topic_words.

  2. Accumulates the total word-occurrence count as word_count.

  3. Collects non-None sentiment scores from matching records.

  4. Computes the mean and sample standard deviation (ddof=1) of those scores.

Windows with no matching records are omitted from the result.

Parameters:

store (RecordStore) – Record store containing tweet or inner-thought records.

Returns:

Mapping from window-start step to a TopicSentimentWindowStats. Only windows that contain at least one matching record are included.

Return type:

TopicSentimentResult

Raises:

ValueError – If window_size is not positive.

analyze_stores(stores)[source]

Estimate the Pearson correlation between buzz and sentiment polarisation.

Calls analyze() on each store, pools all (word_count, std_sentiment) pairs where std_sentiment is not None, and computes \(r = \text{corr}(c_w, \sigma_w)\) using scipy.stats.pearsonr().

Parameters:

stores (list[RecordStore]) – One record store per simulation run.

Returns:

Correlation result including \(r\), the two-tailed p-value, the sample size, and the raw pooled vectors for custom plotting.

Return type:

TopicSentimentCorrelationResult

draw_figs(result)[source]

Draw per-run diagnostic figures.

Produces up to two figures:

  • topic_sentiment_timeseries – two-row subplot: (top) word count as a bar chart over time; (bottom) mean sentiment ± std as a line with a shaded band.

  • word_count_vs_std_sentiment – scatter plot of word count (\(x\)) vs sentiment standard deviation (\(y\)) across windows within this run, directly visualising the within-run polarisation trend.

Parameters:

result (TopicSentimentResult) – Output of analyze().

Returns:

Figure name → Matplotlib figure. Empty if result contains no windows.

Return type:

dict[str, Figure]

draw_figs_all(individual_results)[source]

Draw a pooled scatter of word count vs sentiment std across all runs.

Aggregates (word_count, std_sentiment) pairs from all runs and plots them in a single scatter. A linear regression line is overlaid and the Pearson \(r\) and p-value are shown in the legend when scipy is available.

Parameters:

individual_results (list[TopicSentimentResult]) – One result per simulation run, each produced by analyze().

Returns:

{"word_count_vs_std_sentiment_all": fig} or empty if no valid data points exist.

Return type:

dict[str, Figure]

build_summary(result)[source]

Build a Rich summary table for a single-run analysis.

Displays one row per time window with word count, number of records with valid sentiment, mean sentiment, and sentiment standard deviation.

Parameters:

result (TopicSentimentResult) – Output of analyze().

Returns:

Rich panel containing the per-window summary table.

Return type:

RenderableType

build_summary_all(results)[source]

Build a Rich summary panel for the cross-store correlation result.

Displays the sample size, Pearson \(r\), p-value, and an interpretation of the sign of \(r\). A positive and statistically significant \(r\) supports the hypothesis that higher topic buzz leads to more polarised sentiment.

Parameters:

results (TopicSentimentCorrelationResult) – Output of analyze_stores().

Returns:

Rich panel containing the correlation summary.

Return type:

RenderableType