Log Analyses
Records
- class econsimulacra.log_analyses.records.BaseRecord(type)[source]
Bases:
objectBase 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:
- class econsimulacra.log_analyses.records.TimedRecord(type, time, time_step)[source]
Bases:
BaseRecordA record with a timestamp and time step.
- class econsimulacra.log_analyses.records.AgentGenerationRecord(type, time, time_step, agent_id, agent_type, agent_name, wealth, inventory, persona)[source]
Bases:
BaseRecordA record for agent generation events.
Corresponds to econsimulacra.logs.AgentGenerationLog.
- Parameters:
- inventory
The initial inventory of the generated agent, as a dict mapping item names to amounts.
- persona
The persona of the generated agent, as a dict. May be None if the agent has no persona.
- class econsimulacra.log_analyses.records.ItemGenerationRecord(type, time, time_step, item_name, price)[source]
Bases:
BaseRecordA record for item generation events.
Corresponds to econsimulacra.logs.ItemGenerationLog.
- class econsimulacra.log_analyses.records.SpaceAssignRecord(type, agent_id, pos)[source]
Bases:
BaseRecordA record for space assignment events.
Corresponds to econsimulacra.logs.SpaceAssignLog.
- class econsimulacra.log_analyses.records.SleepStartRecord(type, time, time_step, agent_id, until)[source]
Bases:
TimedRecordA record for sleep events.
Corresponds to econsimulacra.logs.SleepStartLog.
- time
The timestamp when the agent went to sleep, as a datetime object or integer.
- Type:
int | datetime
- class econsimulacra.log_analyses.records.SleepEndRecord(type, time, time_step, agent_id, since)[source]
Bases:
TimedRecordA record for sleep events.
Corresponds to econsimulacra.logs.SleepEndLog.
- class econsimulacra.log_analyses.records.MoveRecord(type, time, time_step, agent_id, old_pos, new_pos)[source]
Bases:
TimedRecordA record for agent movement events.
Corresponds to econsimulacra.logs.MoveLog.
- Parameters:
- class econsimulacra.log_analyses.records.ConsumptionRecord(type, time, time_step, agent_id, item_name, item_amount)[source]
Bases:
TimedRecordA record for agent consumption events.
Corresponds to econsimulacra.logs.ConsumptionLog.
- Parameters:
- class econsimulacra.log_analyses.records.OrderRecord(type, time, time_step, agent_id, counterparty_id, item_name, item_amount, price, order_id)[source]
Bases:
TimedRecordA record for order events.
Corresponds to econsimulacra.logs.OrderLog.
- Parameters:
- 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:
TimedRecordA record for proposal events.
Corresponds to econsimulacra.logs.ProposalLog.
- Parameters:
- 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:
TimedRecordA record for order reaction events.
Corresponds to econsimulacra.logs.OrderReactionLog.
- Parameters:
- class econsimulacra.log_analyses.records.OrderExpirationRecord(type, time, time_step, order_id)[source]
Bases:
TimedRecordA record for order expiration events.
Corresponds to econsimulacra.logs.OrderExpirationLog.
- 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:
TimedRecordA record for proposal reaction events.
Corresponds to econsimulacra.logs.ProposalReactionLog.
- Parameters:
- class econsimulacra.log_analyses.records.ProposalExpirationRecord(type, time, time_step, proposal_id)[source]
Bases:
TimedRecordA record for proposal expiration events.
Corresponds to econsimulacra.logs.ProposalExpirationLog.
- class econsimulacra.log_analyses.records.ChangePriceRecord(type, time, time_step, agent_id, item_name, old_price, new_price)[source]
Bases:
TimedRecordA record for price change events.
Corresponds to econsimulacra.logs.ChangePriceLog.
- Parameters:
- class econsimulacra.log_analyses.records.InnerThoughtRecord(type, time, time_step, agent_id, inner_thought, sentiment)[source]
Bases:
TimedRecordA record for inner thought events.
Corresponds to econsimulacra.logs.InnerThoughtLog.
- Parameters:
- class econsimulacra.log_analyses.records.TweetRecord(type, time, time_step, agent_id, message, sentiment, num_follows, num_followers)[source]
Bases:
TimedRecordA record for tweet events.
Corresponds to econsimulacra.logs.TweetLog.
- Parameters:
- class econsimulacra.log_analyses.records.FollowRecord(type, time, time_step, agent_id, target_agent_id, num_follows, num_followers)[source]
Bases:
TimedRecordA record for follow events.
Corresponds to econsimulacra.logs.FollowLog.
- Parameters:
- class econsimulacra.log_analyses.records.UnfollowRecord(type, time, time_step, agent_id, target_agent_id, num_follows, num_followers)[source]
Bases:
TimedRecordA record for unfollow events.
Corresponds to econsimulacra.logs.UnfollowLog.
- Parameters:
- class econsimulacra.log_analyses.records.StateEvaluationRecord(type, time, time_step, agent_id, wealth, inventory, persona)[source]
Bases:
TimedRecordA record for state evaluation events.
Corresponds to econsimulacra.logs.StateEvaluationLog.
- Parameters:
- class econsimulacra.log_analyses.records.ObsRecord(type, time, time_step, obs_type, agent_id, obs)[source]
Bases:
TimedRecordA record for observation events.
Corresponds to econsimulacra.logs.ObsLog.
- Parameters:
- obs
The content of the observation.
- Type:
Any
Record Store
- class econsimulacra.log_analyses.store.RecordStore(records)[source]
Bases:
objectA 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:
- 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:
- get_by_agent_type(agent_id, record_type)[source]
Get records by agent ID and type.
- Parameters:
- Returns:
A list of records associated with the specified agent ID and type.
- Return type:
Analyzer Base
- class econsimulacra.log_analyses.base.TimeAxisConfig(x_min, x_max, tick_positions, tick_labels)[source]
Bases:
objectConfiguration for a shared simulation-step x-axis.
Produced by
AnalyzerBase._prepare_time_axis()and consumed byAnalyzerBase._apply_time_axis(). All figures produced during one analysis pass share the same instance, guaranteeing consistent x-axis extents and tick positions across figures.- tick_positions
Evenly-spaced tick positions (as
time_stepvalues) sampled from the sorted global step list.
- tick_labels
Human-readable labels for each tick. Uses
datetimestrings formatted as%Y-%m-%d\n %H:%Mwhen datetime values are available and monotonically non-decreasing; falls back to plain step numbers otherwise.
- class econsimulacra.log_analyses.base.AnalyzerBase[source]
-
Base class for all log analyzers in EconSimulacra.
Each analyzer transforms a
RecordStoreinto a typed resultT(single run) andU(multi-run aggregate). The standard analysis pipeline is:Call
analyze()with aRecordStoreto obtain a result of typeT.Call
draw_figs()to produce Matplotlib figures from that result.Optionally call
build_summary()to render a Rich-formatted terminal summary.
For multi-run experiments, use
analyze_stores()anddraw_figs_all()instead.Shared time axis
Subclasses that produce time-series plots should call
_prepare_time_axis()at the start ofanalyze(). This reads alltime_stepvalues from the store and stores aTimeAxisConfig. Subclasses then call_apply_time_axis()on eachAxeswhen 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
AnalysisManagerand as a prefix for saved figure file names.- Type:
- Type Parameters:
T: Result type returned by
analyze()for a single store. U: Result type returned byanalyze_stores()for multiple stores.
- abstractmethod analyze(store)[source]
Analyse records in store and return a structured result.
Subclasses must implement this method. The result type
Tis defined by the subclass and should contain all intermediate data needed bydraw_figs()andbuild_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 typeUis defined by the subclass and may differ fromT.- 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.
- abstractmethod draw_figs_all(individual_results)[source]
Draw diagnostic figures across multiple runs.
- 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
- class econsimulacra.log_analyses.base.FigureSaver(dpi=300, use_tex=False, font_size=15, rc_params=None)[source]
Bases:
objectUtility class for saving analyzer figures to PDF files.
FigureSavercentralises 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_layoutandbbox_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"))
- class econsimulacra.log_analyses.base.AnalysisManager(analyzers)[source]
Bases:
objectOrchestrate multiple analyzers over one or more record stores.
AnalysisManageriterates over a list ofAnalyzerBasesubclasses, runs them on the suppliedRecordStore(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
resultsdict maps each analyzer’snameattribute to the result object produced byanalyze().- Parameters:
analyzers (list[AnalyzerBase[Any, Any]])
- run_all(store, render_summary=False, figs_save_path=None)[source]
Run all analyzers and save their figures.
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.
ActionCounterscans aRecordStoreand tallies the number of log entries of each type. The result is a flatdict[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 periodmove– agent changes its spatial positiontweet– agent posts a message on the social networkfollow– agent follows another agentunfollow– agent unfollows another agentinner_thought– agent records an internal monologueorder– agent places a market orderproposal– agent proposes a barter tradeconsumption– agent consumes an item from its inventoryorder_reaction– agent accepts or rejects an incoming orderproposal_reaction– agent accepts or rejects an incoming proposalchange_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. Usedraw_figs_all()with multiple stores to compare the distribution of action counts across runs via a box plot.- 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:
- 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 bydraw_figs_all()shows the distribution of counts across runs.
- 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, andTweetRecord, all of which carry anum_followerssnapshot at the moment the event occurred.FollowerCountercollects 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.
- analyze(store)[source]
Collect per-agent follower counts from social-network records.
Scans
FollowRecord,UnfollowRecord, andTweetRecordentries in store. For each record thenum_followersvalue attime_stepis stored, overwriting any earlier value for the same(agent_name, time_step)pair.
- 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- 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
OrderReactionRecordor as the responder in an acceptedProposalReactionRecord. 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.- analyze(store)[source]
Aggregate per-store revenue and unit sales from transaction records.
Processes
OrderReactionRecordand acceptedProposalReactionRecordentries. For order reactions the store is identified ascounterparty_id; for accepted proposals it isresponder_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
salesmaps store names to{time_step: total_revenue}.sold_amountsmaps 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- 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,ItemSalesAnalyzergroups 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
OrderReactionRecordentries 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.- analyze(store)[source]
Aggregate per-item revenue and unit sales from order-reaction records.
Iterates over all
OrderReactionRecordentries in store and accumulatesaccept_amount * price(revenue) andaccept_amount(units) into per-item, per-step buckets.- Parameters:
store (RecordStore) – Record store containing order-reaction records.
- Returns:
A 2-tuple where
salesmaps item names to{time_step: total_revenue}.sold_amountsmaps 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- 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.
AgentBehaviorStatsAnalyzercomputes 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.- analyze(store)[source]
Compute per-agent behaviour statistics from store.
Iterates over all agents found in
AgentGenerationRecordentries. For each agent, scans the corresponding records and accumulates:"total_purchase_price": \(\sum_i \text{accept\_amount}_i \times \text{price}_i\) fromOrderReactionRecord."avg_unit_purchase_price": mean price per order-reaction record."total_move_distance": Euclidean distance summed overMoveRecordentries."total_word_counts": word count summed overTweetRecordentries.
Agents in
exclude_agent_idsare 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
AgentBehaviorStatsdict per store. Usedraw_figs_all()to produce a box plot comparing stat distributions across runs.- Return type:
list[AgentBehaviorStats]
- 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:
ItemGenerationRecord– sets the initial price when an item is introduced.ChangePriceRecord– records every subsequent price update by a seller agent.
PriceAnalyzercollects 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.- analyze(store)[source]
Collect the price history for every item in store.
Combines
ItemGenerationRecord(initial prices) andChangePriceRecord(subsequent updates). When multiple events occur at the sametime_stepfor the same item, the last one processed overwrites earlier ones.
- 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- class econsimulacra.log_analyses.move_distance_analyzer.MoveDistanceWindowStats(total_distance, mean_distance, move_count, moving_agent_count)[source]
Bases:
objectAggregated movement statistics for a single time window.
All distances are computed with the metric specified in
MoveDistanceAnalyzer.distance_metric.- total_distance
Sum of all movement distances in this window:
\[D_{\text{total}} = \sum_{i \in \text{window}} d_i\]- Type:
- mean_distance
Average distance per move event. Zero when
move_countis zero:\[\bar{d} = \frac{D_{\text{total}}}{N_{\text{moves}}}\]- Type:
- move_count
Number of
MoveRecordentries falling in this window.- Type:
- moving_agent_count
Number of distinct agents that moved at least once during this window.
- Type:
- 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
MoveRecordcarries anold_posand anew_poscoordinate tuple. This analyzer computes the distance of each move under one of three metrics, then aggregates moves into non-overlapping windows ofwindow_sizesteps.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_distancesuggests agents are exploring or relocating frequently; low values indicate agents remain close to their initial positions. Combine withConsumerClusterAnalyzerto understand what agents are doing while they move.After
analyze()returns, the fitted attributesagent_distance_andagent_move_count_are populated and used bydraw_figs()(top-\(k\) agent bar chart) andbuild_summary().- Parameters:
- include_zero_distance
Whether to include records where
old_pos == new_pos. DefaultFalse.- Type:
- distance_metric: DistanceMetric = 'euclidean'
- analyze(store)[source]
Aggregate movement distances into fixed-size time windows.
Each
MoveRecordis 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_andagent_move_count_are populated for use bydraw_figs()andbuild_summary().- Parameters:
store (RecordStore) – Record store containing
MoveRecordinstances.- Returns:
Mapping from window-start step to a
MoveDistanceWindowStatsfor each window inrange(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_sizeis 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- 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
ObsRecordentries withobs_type == "memory".StressAnalyzerextracts memory keys that end in"_history_stress"and returns a three-level dict:{ agent_id: { stress_type: {time_step: stress_value, ...}, ... }, ... }
where
stress_typeis 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.- analyze(store)[source]
Extract per-agent, per-domain stress time series from store.
Scans all
ObsRecordentries whoseobs_typeis"memory". For each record the observation dict is searched for keys ending in"_history_stress"; the suffix is stripped to obtain thestress_typelabel and the numeric value is recorded at the correspondingtime_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_idsare 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 typeUis defined by the subclass and may differ fromT.- Parameters:
stores (list[RecordStore]) – One store per simulation run.
- Returns:
Aggregated analysis result across all stores.
- Return type:
U
- class econsimulacra.log_analyses.topic_analyzer.TopicResult(tweets, topic_counts, topic_shares, topic_summary, agent_topic_counts)[source]
Bases:
objectResult 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 istime_window; columns are topic name strings.- Type:
DataFrame
Row-normalised version of
topic_countsrepresenting 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 bynum_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(orInnerThoughtRecordwhenis_inner_thought=True) from aRecordStore, 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
Extract records → build a message DataFrame.
Fit (or transform) a BERTopic model to assign integer topic IDs.
Map topic IDs to human-readable names via
BERTopic.get_topic_info().Aggregate into:
topic_counts– message count per(time_window, topic_name)topic_shares– row-normalised counts (seeTopicResult)topic_summary– per-topic statistics across the whole runagent_topic_counts– per-agent topic distribution
Time windowing
Messages are grouped into windows of
time_window_sizesteps:\[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
-1is BERTopic’s built-in outlier/noise cluster. It is included in all DataFrames but excluded from top-topic charts.- Parameters:
- analyze(store)[source]
Fit BERTopic and assign topics to all messages in store.
Calls
BERTopic.fit_transformon all messages from this store. Iftopic_modelwas supplied at construction time, that model is used directly; otherwise a new BERTopic model is created withmin_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:
- 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 typeUis defined by the subclass and may differ fromT.- 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_strsuffix whereinner_stris"inner_thought"or"tweet"):topic_counts_over_time_{inner_str}– line chart of message count per topic over time windows (toptop_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 toptop_n_topicstopics.
- Parameters:
result (TopicResult) – Output of
analyze().- Returns:
Figure name → Matplotlib figure. Empty if result contains no tweets.
- Return type:
- 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
- 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 fromk_candidatesby maximising clustering stability: the mean pairwise Adjusted Rand Index (ARI) acrossn_stability_runsindependent 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 fordraw_figs()andbuild_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)
is_consumption (bool)
random_state (int)
n_stability_runs (int)
normalize (bool)
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)
- exclude_items
Items excluded from vectorisation (e.g. currency tokens such as
"Yen").
- is_consumption
If
True, useConsumptionRecord; otherwise useOrderRecord.- Type:
- analyze(store)[source]
Cluster agents by behavioural vectors and return per-window assignments.
Steps
Build item vocabulary \(\mathcal{I}\) from
ItemGenerationRecordentries (minusexclude_items).Construct behaviour vectors \(\mathbf{v}_{a,w}\) for every
(agent, window)pair.For each \(k \in\)
k_candidates, run KMeansn_stability_runstimes and compute mean pairwise ARI.Select \(k^* = \operatorname{argmax}\) ARI (ties → smaller \(k\)).
Refit KMeans with \(k^*\) and
random_state.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 typeUis defined by the subclass and may differ fromT.- 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:
- Raises:
RuntimeError – If called before
analyze().
- class econsimulacra.log_analyses.temporal_dynamics_analyzer.ActionTypeStats(mean_burstiness, mean_memory, inter_event_times, activity_by_hour, n_events)[source]
Bases:
objectTemporal 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}\]Noneif 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.
Noneif 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.
- 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:
objectAggregated temporal statistics for one action type across multiple runs.
- Parameters:
- mean_burstiness_std
Std of per-run burstiness values.
Nonewhen fewer than 2 runs contributed a value.- Type:
float, optional
- mean_memory_std
Std of per-run memory coefficients.
Nonewhen fewer than 2 runs contributed a value.- Type:
float, optional
- mean_iet_std
Std of per-run mean inter-event times.
Nonewhen fewer than 2 runs contributed a value.- Type:
float, optional
- std_n_events
Std of event counts across runs.
Nonewhen fewer than 2 runs contributed.- Type:
float, optional
- pooled_inter_event_times
All inter-event times pooled across every run for this action type.
- mean_activity_by_hour
Mean activity count per clock-hour averaged across runs.
- class econsimulacra.log_analyses.temporal_dynamics_analyzer.TemporalDynamicsAggregateResult(by_action_type, circadian_autocorr_mean, circadian_autocorr_std, n_runs)[source]
Bases:
objectAggregated temporal-dynamics statistics across multiple simulation runs.
- Parameters:
- by_action_type
Per-action-type aggregate statistics.
- Type:
- circadian_autocorr_std
Std of circadian autocorrelation.
Nonewhen fewer than 2 runs produced a value.- Type:
float, optional
- by_action_type: dict[str, ActionTypeAggregateStats]
- class econsimulacra.log_analyses.temporal_dynamics_analyzer.TemporalDynamicsResult(by_action_type, circadian_autocorr, n_agents, n_events)[source]
Bases:
objectTemporal-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:
- 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}\]Noneif the run is shorter than two periods.- Type:
float, optional
- by_action_type: dict[str, ActionTypeStats]
- 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_stepssteps — 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
datetimetimestamp, the hour is extracted directly; otherwisetime_step mod period_stepsis used.
- Parameters:
- action_types
Record types treated as overt agent actions. Defaults to move, consumption, order, tweet, follow, unfollow, and sleep-start events.
- Type:
- 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:
- 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
- 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'>)
- analyze(store)[source]
Compute temporal-dynamics statistics from a record store.
Algorithm
Optionally filter agents by
agent_typeusingAgentGenerationRecordentries.For each action type in
action_types, collect events for qualifying agents.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.
Build hourly histograms for each action type.
Compute the circadian autocorrelation at lag
period_stepsusing the pooled event stream across all action types.
- Parameters:
store (RecordStore) – Record store containing the simulation log.
- Returns:
Aggregated temporal statistics.
- Return type:
- Raises:
ValueError – If
period_stepsis 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:
- 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:
- 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:
- 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:
objectQuadratic 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:
- 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.
TopicSalesAnalyzercounts 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 foranalyze_stores().- Parameters:
- analyze(store)[source]
Collect word-count and sales data per time window from store.
Groups
TweetRecord(orInnerThoughtRecord) andOrderReactionRecordentries into non-overlapping windows ofwindow_sizesteps:\[w(t) = \left\lfloor \frac{t}{\text{window\_size}} \right\rfloor \times \text{window\_size}\]For each window, the total occurrence count of
topic_wordsin agent messages is accumulated as"word_count", and the revenue of each store is accumulated as"sales_{firm_name}"(or"sales_amount_{firm_name}"whenuse_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.
- draw_figs_all(individual_results)[source]
Draw pooled scatter plots across multiple runs.
Aggregates the data from all
TopicSalesResultinstances 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.
- 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:
- 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:
objectPer-window statistics for topic word count and sentiment distribution.
- Parameters:
- word_count
Total occurrences of topic words across all matching records in this window (same counting convention as
TopicSalesAnalyzer).- Type:
- mean_sentiment
Mean sentiment score of matching records in this window.
Noneif 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.
Noneif fewer than 2 records had a valid sentiment score.- Type:
float, optional
- n_sentiment
Number of matching records with a non-
Nonesentiment score used to computemean_sentimentandstd_sentiment.- Type:
- class econsimulacra.log_analyses.topic_sentiment_analyzer.TopicSentimentCorrelationResult(r, p_value, n_samples, word_counts, std_sentiments)[source]
Bases:
objectCross-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_countandstd_sentimentacross 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}}\]Noneif 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\).
NonewhenrisNone.- Type:
float, optional
- n_samples
Number of
(word_count, std_sentiment)pairs used in the correlation computation. Only windows withstd_sentiment is not Nonecontribute.- Type:
- 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_sizesteps, it counts how often thetopic_wordsappear 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-Nonesentiment 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
sentimentfield ofTweetRecordandInnerThoughtRecordmust be populated (notNone) for sentiment statistics to be meaningful. If sentiment scores are absent,std_sentimentwill beNonefor all windows and the correlation cannot be estimated.- Parameters:
- analyze(store)[source]
Compute per-window buzz intensity and sentiment statistics.
For each time window the method:
Filters records to those containing at least one occurrence of a word from
topic_words.Accumulates the total word-occurrence count as
word_count.Collects non-
Nonesentiment scores from matching records.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_sizeis 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 wherestd_sentimentis notNone, and computes \(r = \text{corr}(c_w, \sigma_w)\) usingscipy.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:
- 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.
- 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 whenscipyis available.
- 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