axiom

Bringing your own problem · step 1 of 6

Start from the dataframe you already have

What does axiom need to know about your table?

Long format: one row per unit per period, a column for the treatment and a column for the outcome. That is the whole data requirement, and it is deliberately the shape almost everyone already has.

What axiom will not do is guess which column is which. A RoleMap names the unit, the time, the outcome and every treatment explicitly. Column-name conventions are how a spend column gets read as an outcome in somebody's third refactor, and the map is five lines against that.

step 1
import numpy as np
import pandas as pd

# Whatever produced your table. This one is 24 stores over 40 weeks:
# leaflet spend, and footfall that responds to it with diminishing
# returns. Substitute your own read_csv here -- nothing below cares.
rng = np.random.default_rng(7)
rows = []
for store in range(24):
    base = 40.0 + rng.normal(0, 6)          # stores differ
    for week in range(40):
        spend = float(max(0.0, rng.normal(300, 160)))
        rows.append({
            "store": f"s{store:02d}",
            "week": week,
            "leaflets": spend,
            "footfall": base + 9.0 * spend / (spend + 250.0)
                        + rng.normal(0, 2),
        })
frame = pd.DataFrame(rows)
print(frame.head(3).to_string(index=False))
print(f"\n{len(frame)} rows, {frame['store'].nunique()} stores, "
      f"{frame['week'].nunique()} weeks")
what it printed
store  week   leaflets  footfall
  s00     0 347.799286 44.695300
  s00     1 157.505306 42.576639
  s00     2 141.336551 43.378141

960 rows, 24 stores, 40 weeks

Every line above was captured by running this code at build time. The steps share one namespace, so the code on this page is the code you would type after the steps before it.