{
  "id": "P086",
  "slug": "simpsons-paradox",
  "key": "P086-simpsons-paradox",
  "title": "Simpson's paradox",
  "summary": "Two treatments' success rates group by group and pooled, compared exactly by cross-multiplication: one better in every group and worse all together, why the weights decide it, the average of rates against the pooled rate, and a comparison that weighs the groups the same.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P086 - Simpson's paradox\n\nTwo treatments, A and B, and their success rates group by group and\npooled. One can do better in every group and worse with the groups put\ntogether. The program shows why the weights decide it, sets the average of\nthe rates against the pooled rate, and gives a comparison that weighs the\ngroups the same for both. It starts with the kidney-stone figures of\nCharig and others (1986): A wins on small stones and on large stones, B\nwins overall.\n\n- `main.eml` - the menu, the table, the explanations, and editing groups\n- `rates.eml` - comparing rates, percentages, totals, the verdicts, the\n  mean of rates and standardized rates\n\nHow each part works:\n\n- Every rate is a fraction of two whole numbers. As in the corpus case\n  `simpsons-paradox`, two rates are compared by cross-multiplication:\n  a/b beats c/d exactly when a d beats c b. No rounding can cause a\n  reversal; percentages are only for showing.\n- A paradox is every group going one way and the pooled rates going the\n  other. The pooled rate is each group's rate weighted by the share of\n  that arm's cases the group holds, so it depends on who got which\n  treatment: A was given mostly large stones, the harder cases.\n- The average of the group rates, as in the corpus case\n  `average-of-averages`, counts each group once, whatever its size. The\n  pooled rate counts each case once. They agree only when the groups are\n  the same size.\n- Weighing the groups the same: each arm's rate as if it had treated each\n  group in proportion to that group's share of all the cases. Then a\n  treatment that is at least as good in every group, and better in one,\n  always comes out ahead.\n\nWhat is checked: menu choices 1 to 7; a group name of up to 16\ncharacters, new when adding and existing when changing or removing; up to\n6 groups and at least 1; successes and cases as two whole numbers, at\nleast 1 case and no more successes than cases. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the kidney-stone table: A 93.1% and 73.0% in the groups, 78.0% pooled;\n  B 86.7% and 68.8%, 82.6% pooled;\n- why: A's cases are 75.1% large stones, B's 22.9%; weighing the groups the\n  same gives A 83.3% against B 77.9%;\n- the average of the rates against the pooled rates;\n- a changed group, an added one and its removal, the paradox holding\n  throughout.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- a group that does not exist, a name already used and one too long;\n- the counts \"10\", \"5 3\", \"x 5\" and \"3 0\", then a cancel;\n- removing a group that does not exist, then all but one, which has to\n  stay; with one group there is no paradox.\n\nBuilt on the verified corpus cases `simpsons-paradox` (rates compared by\ncross-multiplication, so the reversal is exact) and `average-of-averages`\n(the mean of ratios against the ratio of totals).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P086 Simpson's paradox: two treatments, their success rates group by\n# group and pooled, why the pooled comparison can turn round, the average\n# of the rates against the pooled rate, and a comparison that weighs the\n# groups the same for both.\nimport rates\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef number(s):\n    if len(s) == 0 or len(s) > 6:\n        return -1\n    0 => n\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef pad(s, n):\n    while len(s) < n:\n        s + \" \" => s\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef who(v):\n    if v == 1:\n        return \"A\"\n    if v == -1:\n        return \"B\"\n    return \"tie\"\n\ndef cell(ok, n):\n    return lpad(str(ok) + \"/\" + str(n), 11) + \" \" + lpad(rates.percent(ok, n), 6)\n\ndef table(groups):\n    rates.verdicts(groups) => v\n    (\"  \" + pad(\"group\", 16) + lpad(\"A\", 18) + lpad(\"B\", 18) + \"  better\") ^0\n    for i in [0:len(groups) - 1]:\n        groups[i] => g\n        (\"  \" + pad(g[0], 16) + cell(g[1], g[2]) + cell(g[3], g[4]) + \"  \" + who(v[0][i])) ^0\n    rates.totals(groups) => t\n    (\"  \" + pad(\"all together\", 16) + cell(t[0], t[1]) + cell(t[2], t[3]) + \"  \" + who(v[1])) ^0\n    if v[2]:\n        (\"Simpson's paradox: \" + who(0 - v[1]) + \" does better in every group, and \" + who(v[1]) + \" does better with the groups put together.\") ^0\n    else:\n        \"No paradox here: the pooled rates do not turn against the groups.\" ^0\n\ndef why(groups):\n    rates.totals(groups) => t\n    \"Where each treatment's cases came from:\" ^0\n    for g in groups:\n        (\"  \" + pad(g[0], 16) + \"A \" + lpad(rates.percent(g[2], t[1]), 6) + \" of its cases   B \" + lpad(rates.percent(g[4], t[3]), 6) + \" of its cases\") ^0\n    \"A pooled rate is each group's rate weighted by how many of that arm's cases the group holds - so it depends on who got which treatment.\" ^0\n    rates.standardized(groups, 1) => sa\n    rates.standardized(groups, 3) => sb\n    rates.better(sa[0], sa[1], sb[0], sb[1]) => v\n    (\"Weighing the groups the same for both (each by its share of all \" + str(t[1] + t[3]) + \" cases): A \" + rates.percent(sa[0], sa[1]) + \", B \" + rates.percent(sb[0], sb[1]) + \" - better: \" + who(v) + \".\") ^0\n\ndef means(groups):\n    rates.totals(groups) => t\n    rates.mean_of_rates(groups, 1) => ma\n    rates.mean_of_rates(groups, 3) => mb\n    (\"Average of the group rates: A \" + rates.percent(ma[0], ma[1]) + \", B \" + rates.percent(mb[0], mb[1]) + \".\") ^0\n    (\"Pooled rate, every case counted once: A \" + rates.percent(t[0], t[1]) + \", B \" + rates.percent(t[2], t[3]) + \".\") ^0\n    \"The average counts every group once, whatever its size; the pooled rate counts every case once. They agree only when the groups are the same size.\" ^0\n\ndef ask_counts(prompt):\n    # [successes, cases] from \"81 87\", or [] if cancelled.\n    while True:\n        trim(input(prompt)) => s\n        if s == \"\":\n            return []\n        0 => k\n        while k < len(s) and s[k] != \" \":\n            k + 1 => k\n        number(trim(s[0:k])) => ok\n        -1 => n\n        if k < len(s):\n            number(trim(s[k + 1:len(s)])) => n\n        if ok >= 0 and n >= 1 and ok <= n:\n            return [ok, n]\n        \"Type the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\" ^0\n\ndef find(groups, name):\n    for i in [0:len(groups) - 1]:\n        if groups[i][0] == name:\n            return i\n    return -1\n\ndef edit(groups, adding):\n    while True:\n        trim(input(\"group name> \")) => name\n        if name == \"\":\n            \"Cancelled.\" ^0\n            return groups\n        find(groups, name) => i\n        if len(name) > 16:\n            \"A name is up to 16 characters.\" ^0\n        elif adding and i >= 0:\n            (\"There is a group called \" + name + \" already.\") ^0\n        elif not adding and i == -1:\n            (\"No group is called \" + name + \".\") ^0\n        elif adding and len(groups) == 6:\n            \"There are 6 groups already.\" ^0\n            return groups\n        else:\n            ask_counts(\"A: successes and cases> \") => a\n            if len(a) == 0:\n                \"Cancelled.\" ^0\n                return groups\n            ask_counts(\"B: successes and cases> \") => b\n            if len(b) == 0:\n                \"Cancelled.\" ^0\n                return groups\n            [name, a[0], a[1], b[0], b[1]] => g\n            if adding:\n                (name + \" added.\") ^0\n                return groups + [g]\n            [] => out\n            for h in groups:\n                if h[0] == name:\n                    out + [g] => out\n                else:\n                    out + [h] => out\n            (name + \" changed.\") ^0\n            return out\n\ndef remove(groups):\n    if len(groups) == 1:\n        \"One group has to stay.\" ^0\n        return groups\n    trim(input(\"group name> \")) => name\n    if name == \"\":\n        \"Cancelled.\" ^0\n        return groups\n    [] => out\n    for h in groups:\n        if h[0] != name:\n            out + [h] => out\n    if len(out) == len(groups):\n        (\"No group is called \" + name + \".\") ^0\n        return groups\n    (name + \" removed.\") ^0\n    return out\n\n[[\"small stones\", 81, 87, 234, 270], [\"large stones\", 192, 263, 55, 80]] => groups\n\n\"== Simpson's paradox ==\" ^0\n\"Two treatments, A and B: better in every group, and worse all together.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        table(groups)\n    elif choice == \"2\":\n        why(groups)\n    elif choice == \"3\":\n        means(groups)\n    elif choice == \"4\":\n        edit(groups, False) => groups\n    elif choice == \"5\":\n        edit(groups, True) => groups\n    elif choice == \"6\":\n        remove(groups) => groups\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import rates\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef number(s):\n    if len(s) == 0 or len(s) > 6:\n        return -1\n    n = 0\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef pad(s, n):\n    while len(s) < n:\n        s = s + \" \"\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef who(v):\n    if v == 1:\n        return \"A\"\n    if v == -1:\n        return \"B\"\n    return \"tie\"\n\ndef cell(ok, n):\n    return lpad(str(ok) + \"/\" + str(n), 11) + \" \" + lpad(rates.percent(ok, n), 6)\n\ndef table(groups):\n    v = rates.verdicts(groups)\n    print(\"  \" + pad(\"group\", 16) + lpad(\"A\", 18) + lpad(\"B\", 18) + \"  better\")\n    for i in range(0, len(groups)):\n        g = groups[i]\n        print(\"  \" + pad(g[0], 16) + cell(g[1], g[2]) + cell(g[3], g[4]) + \"  \" + who(v[0][i]))\n    t = rates.totals(groups)\n    print(\"  \" + pad(\"all together\", 16) + cell(t[0], t[1]) + cell(t[2], t[3]) + \"  \" + who(v[1]))\n    if v[2]:\n        print(\"Simpson's paradox: \" + who(0 - v[1]) + \" does better in every group, and \" + who(v[1]) + \" does better with the groups put together.\")\n    else:\n        print(\"No paradox here: the pooled rates do not turn against the groups.\")\n\ndef why(groups):\n    t = rates.totals(groups)\n    print(\"Where each treatment's cases came from:\")\n    for g in groups:\n        print(\"  \" + pad(g[0], 16) + \"A \" + lpad(rates.percent(g[2], t[1]), 6) + \" of its cases   B \" + lpad(rates.percent(g[4], t[3]), 6) + \" of its cases\")\n    print(\"A pooled rate is each group's rate weighted by how many of that arm's cases the group holds - so it depends on who got which treatment.\")\n    sa = rates.standardized(groups, 1)\n    sb = rates.standardized(groups, 3)\n    v = rates.better(sa[0], sa[1], sb[0], sb[1])\n    print(\"Weighing the groups the same for both (each by its share of all \" + str(t[1] + t[3]) + \" cases): A \" + rates.percent(sa[0], sa[1]) + \", B \" + rates.percent(sb[0], sb[1]) + \" - better: \" + who(v) + \".\")\n\ndef means(groups):\n    t = rates.totals(groups)\n    ma = rates.mean_of_rates(groups, 1)\n    mb = rates.mean_of_rates(groups, 3)\n    print(\"Average of the group rates: A \" + rates.percent(ma[0], ma[1]) + \", B \" + rates.percent(mb[0], mb[1]) + \".\")\n    print(\"Pooled rate, every case counted once: A \" + rates.percent(t[0], t[1]) + \", B \" + rates.percent(t[2], t[3]) + \".\")\n    print(\"The average counts every group once, whatever its size; the pooled rate counts every case once. They agree only when the groups are the same size.\")\n\ndef ask_counts(prompt):\n    while True:\n        s = trim(input(prompt))\n        if s == \"\":\n            return []\n        k = 0\n        while k < len(s) and s[k] != \" \":\n            k = k + 1\n        ok = number(trim(s[0:k]))\n        n = -1\n        if k < len(s):\n            n = number(trim(s[k + 1:len(s)]))\n        if ok >= 0 and n >= 1 and ok <= n:\n            return [ok, n]\n        print(\"Type the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\")\n\ndef find(groups, name):\n    for i in range(0, len(groups)):\n        if groups[i][0] == name:\n            return i\n    return -1\n\ndef edit(groups, adding):\n    while True:\n        name = trim(input(\"group name> \"))\n        if name == \"\":\n            print(\"Cancelled.\")\n            return groups\n        i = find(groups, name)\n        if len(name) > 16:\n            print(\"A name is up to 16 characters.\")\n        elif adding and i >= 0:\n            print(\"There is a group called \" + name + \" already.\")\n        elif not adding and i == -1:\n            print(\"No group is called \" + name + \".\")\n        elif adding and len(groups) == 6:\n            print(\"There are 6 groups already.\")\n            return groups\n        else:\n            a = ask_counts(\"A: successes and cases> \")\n            if len(a) == 0:\n                print(\"Cancelled.\")\n                return groups\n            b = ask_counts(\"B: successes and cases> \")\n            if len(b) == 0:\n                print(\"Cancelled.\")\n                return groups\n            g = [name, a[0], a[1], b[0], b[1]]\n            if adding:\n                print(name + \" added.\")\n                return groups + [g]\n            out = []\n            for h in groups:\n                if h[0] == name:\n                    out = out + [g]\n                else:\n                    out = out + [h]\n            print(name + \" changed.\")\n            return out\n\ndef remove(groups):\n    if len(groups) == 1:\n        print(\"One group has to stay.\")\n        return groups\n    name = trim(input(\"group name> \"))\n    if name == \"\":\n        print(\"Cancelled.\")\n        return groups\n    out = []\n    for h in groups:\n        if h[0] != name:\n            out = out + [h]\n    if len(out) == len(groups):\n        print(\"No group is called \" + name + \".\")\n        return groups\n    print(name + \" removed.\")\n    return out\n\ngroups = [[\"small stones\", 81, 87, 234, 270], [\"large stones\", 192, 263, 55, 80]]\nprint(\"== Simpson's paradox ==\")\nprint(\"Two treatments, A and B: better in every group, and worse all together.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        table(groups)\n    elif choice == \"2\":\n        why(groups)\n    elif choice == \"3\":\n        means(groups)\n    elif choice == \"4\":\n        groups = edit(groups, False)\n    elif choice == \"5\":\n        groups = edit(groups, True)\n    elif choice == \"6\":\n        groups = remove(groups)\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "rates.eml",
      "eml": "# P086 Simpson's paradox - success rates of two treatments, A and B, group\n# by group and pooled. Every rate is a fraction of two whole numbers and\n# rates are compared by cross-multiplication, as in the corpus case\n# simpsons-paradox, so no rounding can cause a reversal. A group is\n# [name, A successes, A cases, B successes, B cases].\n\ndef better(a_ok, a_n, b_ok, b_n):\n    # 1 if A's rate beats B's, -1 if B's beats A's, 0 if equal:\n    # a_ok/a_n > b_ok/b_n exactly when a_ok b_n > b_ok a_n.\n    a_ok * b_n => l\n    b_ok * a_n => r\n    if l > r:\n        return 1\n    if l < r:\n        return -1\n    return 0\n\ndef percent(num, den):\n    # num/den as a percentage to one decimal, rounded half up, by long\n    # division in whole numbers.\n    2000 * num + den => n\n    2 * den => d\n    0 => q\n    while n >= d:\n        d => m\n        1 => k\n        while m + m <= n:\n            m + m => m\n            k + k => k\n        n - m => n\n        q + k => q\n    0 => w\n    q => r\n    while r >= 10:\n        r - 10 => r\n        w + 1 => w\n    return str(w) + \".\" + str(r) + \"%\"\n\ndef totals(groups):\n    # [A successes, A cases, B successes, B cases] over all groups.\n    [0, 0, 0, 0] => t\n    for g in groups:\n        [t[0] + g[1], t[1] + g[2], t[2] + g[3], t[3] + g[4]] => t\n    return t\n\ndef verdicts(groups):\n    # [winner in each group (1 A, -1 B, 0 tie), winner pooled, paradox]:\n    # a paradox is every group going one way (ties aside, at least one\n    # group won) and the pooled rates going the other way.\n    [] => each\n    0 => a_wins\n    0 => b_wins\n    for g in groups:\n        better(g[1], g[2], g[3], g[4]) => v\n        each + [v] => each\n        if v == 1:\n            a_wins + 1 => a_wins\n        elif v == -1:\n            b_wins + 1 => b_wins\n    totals(groups) => t\n    better(t[0], t[1], t[2], t[3]) => pooled\n    ((a_wins > 0 and b_wins == 0 and pooled == -1) or (b_wins > 0 and a_wins == 0 and pooled == 1)) => paradox\n    return [each, pooled, paradox]\n\ndef mean_of_rates(groups, arm):\n    # The plain average of one arm's group rates (arm 1 is A, 3 is B), as a\n    # fraction: each group counts once, whatever its size - the corpus case\n    # average-of-averages.\n    [0, 1] => s\n    for g in groups:\n        [s[0] * g[arm + 1] + g[arm] * s[1], s[1] * g[arm + 1]] => s\n    return [s[0], s[1] * len(groups)]\n\ndef standardized(groups, arm):\n    # One arm's rate if it had treated each group in the same proportion -\n    # the share of all cases (both arms) that group has: sum over groups of\n    # rate x group size, over all cases. Comparing these compares like with\n    # like.\n    0 => all_cases\n    for g in groups:\n        all_cases + g[2] + g[4] => all_cases\n    [0, 1] => s\n    for g in groups:\n        g[2] + g[4] => w\n        [s[0] * g[arm + 1] + g[arm] * w * s[1], s[1] * g[arm + 1]] => s\n    return [s[0], s[1] * all_cases]\n",
      "python": "def better(a_ok, a_n, b_ok, b_n):\n    l = a_ok * b_n\n    r = b_ok * a_n\n    if l > r:\n        return 1\n    if l < r:\n        return -1\n    return 0\n\ndef percent(num, den):\n    n = 2000 * num + den\n    d = 2 * den\n    q = 0\n    while n >= d:\n        m = d\n        k = 1\n        while m + m <= n:\n            m = m + m\n            k = k + k\n        n = n - m\n        q = q + k\n    w = 0\n    r = q\n    while r >= 10:\n        r = r - 10\n        w = w + 1\n    return str(w) + \".\" + str(r) + \"%\"\n\ndef totals(groups):\n    t = [0, 0, 0, 0]\n    for g in groups:\n        t = [t[0] + g[1], t[1] + g[2], t[2] + g[3], t[3] + g[4]]\n    return t\n\ndef verdicts(groups):\n    each = []\n    a_wins = 0\n    b_wins = 0\n    for g in groups:\n        v = better(g[1], g[2], g[3], g[4])\n        each = each + [v]\n        if v == 1:\n            a_wins = a_wins + 1\n        elif v == -1:\n            b_wins = b_wins + 1\n    t = totals(groups)\n    pooled = better(t[0], t[1], t[2], t[3])\n    paradox = a_wins > 0 and b_wins == 0 and pooled == -1 or b_wins > 0 and a_wins == 0 and pooled == 1\n    return [each, pooled, paradox]\n\ndef mean_of_rates(groups, arm):\n    s = [0, 1]\n    for g in groups:\n        s = [s[0] * g[arm + 1] + g[arm] * s[1], s[1] * g[arm + 1]]\n    return [s[0], s[1] * len(groups)]\n\ndef standardized(groups, arm):\n    all_cases = 0\n    for g in groups:\n        all_cases = all_cases + g[2] + g[4]\n    s = [0, 1]\n    for g in groups:\n        w = g[2] + g[4]\n        s = [s[0] * g[arm + 1] + g[arm] * w * s[1], s[1] * g[arm + 1]]\n    return [s[0], s[1] * all_cases]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\ntiny\n\n5\nsmall stones\na name far too long\nnew\n10\n5 3\nx 5\n3 0\n\n6\nnothing\n6\nlarge stones\n6\n1\n7\n",
      "screen": "== Simpson's paradox ==\nTwo treatments, A and B: better in every group, and worse all together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 4\ngroup name> tiny\nNo group is called tiny.\ngroup name> \nCancelled.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 5\ngroup name> small stones\nThere is a group called small stones already.\ngroup name> a name far too long\nA name is up to 16 characters.\ngroup name> new\nA: successes and cases> 10\nType the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\nA: successes and cases> 5 3\nType the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\nA: successes and cases> x 5\nType the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\nA: successes and cases> 3 0\nType the successes and the cases, like 81 87: whole numbers, at least 1 case, no more successes than cases.\nA: successes and cases> \nCancelled.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 6\ngroup name> nothing\nNo group is called nothing.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 6\ngroup name> large stones\nlarge stones removed.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 6\nOne group has to stay.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 1\n  group                            A                 B  better\n  small stones          81/87  93.1%    234/270  86.7%  A\n  all together          81/87  93.1%    234/270  86.7%  A\nNo paradox here: the pooled rates do not turn against the groups.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\nlarge stones\n192 263\n80 120\n1\n5\nmedium stones\n50 60\n40 50\n1\n6\nmedium stones\n1\n7\n",
      "screen": "== Simpson's paradox ==\nTwo treatments, A and B: better in every group, and worse all together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 1\n  group                            A                 B  better\n  small stones          81/87  93.1%    234/270  86.7%  A\n  large stones        192/263  73.0%      55/80  68.8%  A\n  all together        273/350  78.0%    289/350  82.6%  B\nSimpson's paradox: A does better in every group, and B does better with the groups put together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 2\nWhere each treatment's cases came from:\n  small stones    A  24.9% of its cases   B  77.1% of its cases\n  large stones    A  75.1% of its cases   B  22.9% of its cases\nA pooled rate is each group's rate weighted by how many of that arm's cases the group holds - so it depends on who got which treatment.\nWeighing the groups the same for both (each by its share of all 700 cases): A 83.3%, B 77.9% - better: A.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 3\nAverage of the group rates: A 83.1%, B 77.7%.\nPooled rate, every case counted once: A 78.0%, B 82.6%.\nThe average counts every group once, whatever its size; the pooled rate counts every case once. They agree only when the groups are the same size.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 4\ngroup name> large stones\nA: successes and cases> 192 263\nB: successes and cases> 80 120\nlarge stones changed.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 1\n  group                            A                 B  better\n  small stones          81/87  93.1%    234/270  86.7%  A\n  large stones        192/263  73.0%     80/120  66.7%  A\n  all together        273/350  78.0%    314/390  80.5%  B\nSimpson's paradox: A does better in every group, and B does better with the groups put together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 5\ngroup name> medium stones\nA: successes and cases> 50 60\nB: successes and cases> 40 50\nmedium stones added.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 1\n  group                            A                 B  better\n  small stones          81/87  93.1%    234/270  86.7%  A\n  large stones        192/263  73.0%     80/120  66.7%  A\n  medium stones         50/60  83.3%      40/50  80.0%  A\n  all together        323/410  78.8%    354/440  80.5%  B\nSimpson's paradox: A does better in every group, and B does better with the groups put together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 6\ngroup name> medium stones\nmedium stones removed.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 1\n  group                            A                 B  better\n  small stones          81/87  93.1%    234/270  86.7%  A\n  large stones        192/263  73.0%     80/120  66.7%  A\n  all together        273/350  78.0%    314/390  80.5%  B\nSimpson's paradox: A does better in every group, and B does better with the groups put together.\n\n1) table  2) why  3) average of rates  4) change a group  5) add a group  6) remove a group  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "simpsons-paradox",
      "caseId": "269-simpsons-paradox",
      "title": "Simpson's paradox — a treatment that wins every group and loses overall"
    },
    {
      "slug": "average-of-averages",
      "caseId": "216-average-of-averages",
      "title": "Winning every group and losing the company"
    }
  ],
  "updated": "2026-10-15"
}
