Accessing Raw Simulation Results

How to access raw results in the BPTK-Py simulation framework.
Keywords

agent-based modeling, abm, bptk, bptk-py, python

How To: Accessing Raw Simulation Results

In some situations, it is helpful obtain the raw simulation results rather than the plot. To activate this feature, set the return_df flag to True.

Below is example code that runs a scenario and sets return_df to true. This way it is possible to work with the data outside BPTK_Py!

from BPTK_Py.bptk import bptk
bptk = bptk()
df = bptk.plot_scenarios(
    scenario_managers=["smSimpleProjectManagement"],
    scenarios=["scenario120"], 
    equations=["openTasks"],
    title="Deadline changes\n",
    x_label="Time",start_date="1/1/2018",freq="ME",
    y_label="Marketing Budget (USD)",
    kind="line",
    return_df=True ## <--- HERE
    ,series_names = {"smSimpleProjectManagement_scenario120_openTasks" : "openTasks"}
    )

The following code prints useful information by calling the head() and describe() functions of the dataFrame. Head return the first 5 elements and Describe gives some important information on the data. For instance, we learn that there are 121 elements in the dataFrame (“count”). Further values are the mean, standard deviation, min, max, and the 25th / 50th and 75th percentile.

***************************
Properties of the dataFrame
     first 5 elements:
             openTasks
2018-01-31  120.000000
2018-02-28  118.904800
2018-03-31  117.809096
2018-04-30  116.712881
2018-05-31  115.616145
Main description of the dataFrame
        openTasks
count  121.000000
mean    53.582193
std     38.468884
min      0.000000
25%     18.411872
50%     53.185085
75%     86.886076
max    120.000000
# marimo sends a cell's stdout to the console rather than to its output area.
# Captured and handed to `mo.plain_text` it comes back as one block, laid out
# the way `print` wrote it.
with mo.capture_stdout() as output:
    print("***************************")
    print("Properties of the dataFrame")
    print("\t first 5 elements:")
    print(df.head())
    print("")
    print("Main description of the dataFrame")
    print(df.describe())

mo.plain_text(output.getvalue())

To select only certain periods, two different approaches can be used.

  1. Use the list index representation
  2. Use dates (if you created a time series using start_date)

In both cases, the selected range is supplied in square brackets:

BY INDEX
             openTasks
2018-01-31  120.000000
2018-02-28  118.904800
2018-03-31  117.809096
2018-04-30  116.712881
2018-05-31  115.616145
2018-06-30  114.518882
BY YEAR-MONTH:
             openTasks
2018-01-31  120.000000
2018-02-28  118.904800
2018-03-31  117.809096
2018-04-30  116.712881
2018-05-31  115.616145
2018-06-30  114.518882
CHECK FOR EQUALITY OF BOTH
            openTasks
2018-01-31       True
2018-02-28       True
2018-03-31       True
2018-04-30       True
2018-05-31       True
2018-06-30       True
# Select the first 6 months
by_index = df[0:6]

# Select all values of the months January to June 2018:
by_year = df["2018-01":"2018-06"]

with mo.capture_stdout() as output_1:
    # The blank lines belong to the labels rather than being printed on their own:
    # a bare `print("")` survives into the live output and gets swallowed on the way
    # into the pre-rendered page, so the two differed by two empty lines.
    print("BY INDEX")
    print(by_index)

    print("\nBY YEAR-MONTH:")
    print(by_year)

    print("\nCHECK FOR EQUALITY OF BOTH")
    print(by_index == by_year)

mo.plain_text(output_1.getvalue())

This allows for versatile and easy analysis of the returned data. For example, equality testing using by_index == by_year. The return type is a Series that may be used for further computation.

We now simulate the equation “closedTasks”, append it to the existing dataFrame, derive a third series from the two by computation, and finally compute the percentage of tasks closed. Every value of initialOpenTasks should come out at 120 — the initial number of tasks of the scenario scenario120.

All four steps live in one cell, so that changing any of them recomputes the rest: each one adds a column to the same dataFrame rather than producing a new name of its own, and marimo follows names.

2018-01-31    120.0
2018-02-28    120.0
2018-03-31    120.0
2018-04-30    120.0
2018-05-31    120.0
Freq: ME, Name: initialOpenTasks, dtype: float64
df_closed = bptk.plot_scenarios(
    scenario_managers=["smSimpleProjectManagement"],
    scenarios=["scenario120"],
    equations=["closedTasks"],
    title="Deadline changes\n",
    x_label="Time", start_date="1/1/2018", freq="ME",
    y_label="Tasks",
    kind="line",
    return_df=True,
    series_names={"smSimpleProjectManagement_scenario120_closedTasks": "closedTasks"}
    )

df["closedTasks"] = df_closed["closedTasks"]

# A new series by computation
df["initialOpenTasks"] = df["openTasks"] + df["closedTasks"]

# And the share of tasks closed, as a percentage
df["Percent Tasks Closed"] = df["closedTasks"] / df["initialOpenTasks"] * 100

with mo.capture_stdout() as output_2:
    print(df["initialOpenTasks"].head())

mo.vstack([
    mo.plain_text(output_2.getvalue()),
    df["Percent Tasks Closed"].plot(title="Tasks closed %", figsize=(20, 10)).figure,
])

As you can see, the DataFrame handles all the heavy lifting and we can focus on high-level analysis.