|
| 1 | +# Multi-value QCA |
| 2 | + |
| 3 | +A multi-value condition takes one of several unordered categories — regime type, |
| 4 | +welfare regime, sector — rather than being present or absent. Forcing such a |
| 5 | +condition into a binary set either loses information or invents a dichotomy the |
| 6 | +concept does not have. |
| 7 | + |
| 8 | +```python |
| 9 | +from setqca.multivalue import MVQCA |
| 10 | + |
| 11 | +result = MVQCA(consistency=0.8).fit( |
| 12 | + data, outcome="Y", conditions=["regime", "wealth"] |
| 13 | +) |
| 14 | +print(result) |
| 15 | +print(result.truth_table.to_frame()) |
| 16 | +print(result.summary_frame("parsimonious")) |
| 17 | +``` |
| 18 | + |
| 19 | +Conditions hold integer category codes from `0`; the outcome is a membership in |
| 20 | +`[0, 1]`. The workflow deliberately mirrors `FSQCA` and `CSQCA` — moving between |
| 21 | +them is a change of estimator, not a change of method. |
| 22 | + |
| 23 | +## Notation |
| 24 | + |
| 25 | +`regime{0,2}*wealth{1}` reads "regime is 0 or 2, and wealth is 1". A condition |
| 26 | +allowing *every* level constrains nothing and is omitted from the expression, so |
| 27 | +the binary case reduces to familiar QCA notation. |
| 28 | + |
| 29 | +## The property space |
| 30 | + |
| 31 | +```python |
| 32 | +result.domain # regime{0,1,2}, wealth{0,1} |
| 33 | +result.domain.size # 6 logically possible configurations |
| 34 | +``` |
| 35 | + |
| 36 | +Configurations are indexed in mixed radix, which generalises the binary minterm |
| 37 | +and reduces to it exactly when every condition has two levels. |
| 38 | + |
| 39 | +!!! warning "Declare levels that have no cases" |
| 40 | + Levels are inferred from the data, which understates a category that is |
| 41 | + theoretically possible but happens to be unobserved. That matters: an |
| 42 | + unobserved level is a *remainder*, and remainders change the parsimonious |
| 43 | + solution. |
| 44 | + |
| 45 | + ```python |
| 46 | + MVQCA(levels={"regime": 4, "wealth": 2}).fit(...) |
| 47 | + ``` |
| 48 | + |
| 49 | + Declaring fewer levels than the data contain is an error. |
| 50 | + |
| 51 | +## Why not Boolean dummies |
| 52 | + |
| 53 | +The obvious shortcut is to encode `A{0,1,2}` as three binary indicators and |
| 54 | +reuse the binary minimiser. **That transformation does not preserve the |
| 55 | +semantics.** |
| 56 | + |
| 57 | +The binary space contains points such as `A_0 = A_1 = 1` — a case that is |
| 58 | +simultaneously in two mutually exclusive categories, which corresponds to no |
| 59 | +configuration at all. The minimiser is free to build implicants across those |
| 60 | +points, producing terms that look valid and describe nothing. Recovering a |
| 61 | +multi-value expression afterwards requires exactly the mutual-exclusivity |
| 62 | +constraints the encoding threw away. |
| 63 | + |
| 64 | +So the cube algebra is implemented directly. A cube allows a **set** of levels |
| 65 | +per condition, and merging generalises the binary rule: |
| 66 | + |
| 67 | +> two cubes that agree on every condition but one merge into a single cube whose |
| 68 | +> set at that condition is the union of the two. |
| 69 | +
|
| 70 | +Because the two cubes agree everywhere else, the merged cube covers exactly |
| 71 | +their union and nothing more — the same property the binary rule relies on. A |
| 72 | +test asserts precisely that, and another asserts every cube covers only real |
| 73 | +configurations. |
| 74 | + |
| 75 | +The exact cover is then solved by the **same verified solver the binary engine |
| 76 | +uses**, so both inherit one exactness guarantee rather than two implementations. |
| 77 | +Minimisation is checked against exhaustive enumeration for four different level |
| 78 | +combinations, and against the binary minimiser for every three-condition |
| 79 | +problem. |
| 80 | + |
| 81 | +## Agreement with R |
| 82 | + |
| 83 | +R `QCA` supports multi-value and writes literals as `regime[2]`. The truth table |
| 84 | +and the parsimonious solution match exactly on the benchmarks in |
| 85 | +`validation/fixtures/r_qca.json`. |
| 86 | + |
| 87 | +The conservative solution can differ in *representation*: |
| 88 | + |
| 89 | +```text |
| 90 | +R: regime[2] + regime[1]*wealth[1] |
| 91 | +setqca: regime{2} + regime{1,2}*wealth{1} |
| 92 | +``` |
| 93 | + |
| 94 | +Both cover the same configurations and both cost two terms and three literals, |
| 95 | +so both are minimal. The difference is that R writes single-value literals only, |
| 96 | +while `setqca` also forms subset literals — and here R's `regime[1]*wealth[1]` |
| 97 | +is a **proper subset** of `regime{1,2}*wealth{1}`, so R's term is not a prime |
| 98 | +implicant. The parity tests therefore compare cost and coverage rather than |
| 99 | +text, which is the comparison that carries meaning. |
| 100 | + |
| 101 | +::: setqca.multivalue |
0 commit comments