Multi-value QCA¶
A multi-value condition takes one of several unordered categories — regime type, welfare regime, sector — rather than being present or absent. Forcing such a condition into a binary set either loses information or invents a dichotomy the concept does not have.
from setqca.multivalue import MVQCA
result = MVQCA(consistency=0.8).fit(data, outcome="Y", conditions=["regime", "wealth"])
print(result)
print(result.truth_table.to_frame())
print(result.summary_frame("parsimonious"))
Conditions hold integer category codes from 0; the outcome is a membership in
[0, 1]. The workflow deliberately mirrors FSQCA and CSQCA — moving between
them is a change of estimator, not a change of method.
Notation¶
regime{0,2}*wealth{1} reads "regime is 0 or 2, and wealth is 1". A condition
allowing every level constrains nothing and is omitted from the expression, so
the binary case reduces to familiar QCA notation.
The property space¶
Configurations are indexed in mixed radix, which generalises the binary minterm and reduces to it exactly when every condition has two levels.
Declare levels that have no cases
Levels are inferred from the data, which understates a category that is theoretically possible but happens to be unobserved. That matters: an unobserved level is a remainder, and remainders change the parsimonious solution.
Declaring fewer levels than the data contain is an error.
Why not Boolean dummies¶
The obvious shortcut is to encode A{0,1,2} as three binary indicators and
reuse the binary minimiser. That transformation does not preserve the
semantics.
The binary space contains points such as A_0 = A_1 = 1 — a case that is
simultaneously in two mutually exclusive categories, which corresponds to no
configuration at all. The minimiser is free to build implicants across those
points, producing terms that look valid and describe nothing. Recovering a
multi-value expression afterwards requires exactly the mutual-exclusivity
constraints the encoding threw away.
So the cube algebra is implemented directly. A cube allows a set of levels per condition, and merging generalises the binary rule:
two cubes that agree on every condition but one merge into a single cube whose set at that condition is the union of the two.
Because the two cubes agree everywhere else, the merged cube covers exactly their union and nothing more — the same property the binary rule relies on. A test asserts precisely that, and another asserts every cube covers only real configurations.
The exact cover is then solved by the same verified solver the binary engine uses, so both inherit one exactness guarantee rather than two implementations. Minimisation is checked against exhaustive enumeration for four different level combinations, and against the binary minimiser for every three-condition problem.
Agreement with R¶
R QCA supports multi-value and writes literals as regime[2]. The truth table
and the parsimonious solution match exactly on the benchmarks in
validation/fixtures/r_qca.json.
The conservative solution can differ in representation:
Both cover the same configurations and both cost two terms and three literals,
so both are minimal. The difference is that R writes single-value literals only,
while setqca also forms subset literals — and here R's regime[1]*wealth[1]
is a proper subset of regime{1,2}*wealth{1}, so R's term is not a prime
implicant. The parity tests therefore compare cost and coverage rather than
text, which is the comparison that carries meaning.
setqca.multivalue ¶
Multi-value QCA: categorical conditions with more than two levels.
A multi-value condition takes one of several unordered categories — regime type, welfare regime, sector — rather than being present or absent. Forcing such a condition into a binary set either loses information or invents a dichotomy the concept does not have.
The cube algebra is implemented directly rather than by encoding categories as
Boolean indicators; see :mod:setqca.multivalue._cube for why that encoding is
unsound. The exact cover is solved by the same verified solver the binary
engine uses, so both inherit one exactness guarantee.
Examples:
>>> import pandas as pd
>>> from setqca.multivalue import MVQCA
>>> data = pd.DataFrame(
... {"regime": [0, 1, 2, 1], "wealth": [0, 1, 1, 0], "Y": [0.1, 0.9, 0.9, 0.2]}
... )
>>> result = MVQCA(consistency=0.8).fit(data, outcome="Y", conditions=["regime", "wealth"])
>>> print(result.summary_frame())
MultiValueCube
dataclass
¶
A conjunction allowing a set of levels for each condition.
from_configuration
classmethod
¶
from_configuration(
values: tuple[int, ...],
) -> MultiValueCube
Build the cube covering exactly one configuration.
literals ¶
literals(domain: MultiValueDomain) -> int
Return the number of conditions the cube actually constrains.
is_tautology ¶
is_tautology(domain: MultiValueDomain) -> bool
covers_values ¶
Return whether a configuration falls inside the cube.
covers ¶
covers(index: int, domain: MultiValueDomain) -> bool
Return whether the configuration at an index falls inside the cube.
contains ¶
contains(other: MultiValueCube) -> bool
Return whether this cube covers everything other covers.
merge ¶
merge(other: MultiValueCube) -> MultiValueCube | None
Merge two cubes differing at exactly one condition.
Returns None when they differ at none or several, in which case the
union would cover configurations neither cube covers.
Source code in src/setqca/multivalue/_cube.py
as_expression ¶
as_expression(domain: MultiValueDomain) -> str
Render in multi-value QCA notation, for example A{0,2}*B{1}.
Conditions allowing every level are omitted, since they constrain
nothing. A cube constraining nothing renders as 1.
Source code in src/setqca/multivalue/_cube.py
MultiValueSolution
dataclass
¶
MultiValueSolution(cubes: tuple[MultiValueCube, ...])
A minimal cover of multi-value configurations.
literal_count ¶
literal_count(domain: MultiValueDomain) -> int
as_expression ¶
as_expression(domain: MultiValueDomain) -> str
Render the whole cover, for example A{0}*B{1} + A{2}.
covers ¶
covers(index: int, domain: MultiValueDomain) -> bool
MultiValueDomain
dataclass
¶
Condition names and how many levels each takes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditions
|
tuple of str
|
Condition names, in the order used for indexing. |
required |
levels
|
tuple of int
|
Number of categories per condition. Level values are |
required |
from_mapping
classmethod
¶
from_mapping(levels: Mapping[str, int]) -> MultiValueDomain
Build a domain from a {condition: levels} mapping.
index_of ¶
Return the mixed-radix index of one configuration.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the length is wrong or a value is outside its condition's range. |
Source code in src/setqca/multivalue/_domain.py
values_of ¶
Return the configuration at a mixed-radix index.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the index is outside the property space. |
Source code in src/setqca/multivalue/_domain.py
configurations ¶
MVQCA
dataclass
¶
MVQCA(
consistency: float = 0.8,
frequency: int = 1,
max_solutions: int = 256,
levels: Mapping[str, int] | None = None,
)
Multi-value Qualitative Comparative Analysis estimator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
consistency
|
float
|
Inclusion cutoff on sufficiency consistency. |
0.8
|
frequency
|
int
|
Minimum number of cases for a configuration to be observed. |
1
|
max_solutions
|
int
|
Upper bound on tied minimal covers. |
256
|
levels
|
mapping of str to int
|
Declared number of categories per condition. Supply this when a level is theoretically possible but happens to have no cases, since it changes the property space and therefore the remainders. |
None
|
fit ¶
fit(
data: DataFrame,
*,
outcome: str,
conditions: list[str] | tuple[str, ...],
case_id: str | None = None,
) -> MultiValueResult
Fit mvQCA to categorical conditions and a calibrated outcome.
Source code in src/setqca/multivalue/_model.py
MultiValueResult
dataclass
¶
MultiValueResult(
domain: MultiValueDomain,
outcome: str,
truth_table: MultiValueTruthTable,
conservative: tuple[MultiValueSolution, ...],
parsimonious: tuple[MultiValueSolution, ...],
fits: dict[str, SufficiencyFit] = dict(),
)
A fitted mvQCA analysis.
summary_frame ¶
Return one row per minimal solution of a family.
Source code in src/setqca/multivalue/_model.py
MultiValueRow
dataclass
¶
MultiValueRow(
index: int,
configuration: tuple[int, ...],
frequency: int,
consistency: float,
pri: float,
outcome: MultiValueCode,
cases: tuple[str, ...],
exclusion_reason: str | None = None,
)
One configuration of the multi-value property space.
MultiValueTruthTable
dataclass
¶
MultiValueTruthTable(
domain: MultiValueDomain,
outcome_name: str,
rows: tuple[MultiValueRow, ...],
inclusion_cutoff: float,
frequency_cutoff: int,
)
A complete multi-value truth table.
remainder_indices
property
¶
Return configurations with too few cases to judge.
rows_with ¶
rows_with(
code: MultiValueCode,
) -> tuple[MultiValueRow, ...]
to_frame ¶
Return a tidy representation, one row per configuration.
Source code in src/setqca/multivalue/_model.py
minimize ¶
minimize(
*,
include_remainders: bool = False,
max_solutions: int = 256,
) -> tuple[MultiValueSolution, ...]
Minimise directly from the table.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no configuration is coded sufficient. |
Source code in src/setqca/multivalue/_model.py
minimize_multivalue ¶
minimize_multivalue(
on_set: set[int],
*,
domain: MultiValueDomain,
dont_cares: set[int] | None = None,
max_solutions: int = 256,
) -> tuple[MultiValueSolution, ...]
Return every exact minimum cover of a multi-value problem.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_set
|
set of int
|
Configuration indices that must be covered. |
required |
domain
|
MultiValueDomain
|
The property space. |
required |
dont_cares
|
set of int
|
Configurations usable but not required, typically logical remainders. |
None
|
max_solutions
|
int
|
Upper bound on tied minimum covers. |
256
|
Returns:
| Type | Description |
|---|---|
tuple of MultiValueSolution
|
Every cover of provably minimal cost. |
Source code in src/setqca/multivalue/_cube.py
prime_cubes ¶
prime_cubes(
on_set: set[int],
dont_cares: set[int],
domain: MultiValueDomain,
) -> tuple[MultiValueCube, ...]
Generate every prime cube for a multi-value problem.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_set
|
set of int
|
Configuration indices that must be covered. |
required |
dont_cares
|
set of int
|
Configurations usable but not required. |
required |
domain
|
MultiValueDomain
|
The property space. |
required |
Returns:
| Type | Description |
|---|---|
tuple of MultiValueCube
|
Prime cubes, ordered by literal count then rendered form, and filtered to those covering at least one required configuration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the two sets overlap. |
Source code in src/setqca/multivalue/_cube.py
build_multivalue_truth_table ¶
build_multivalue_truth_table(
data: DataFrame,
*,
outcome: str,
conditions: list[str] | tuple[str, ...],
levels: Mapping[str, int] | None = None,
inclusion_cutoff: float = 0.8,
frequency_cutoff: int = 1,
case_id: str | None = None,
) -> MultiValueTruthTable
Build a complete multi-value truth table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Condition columns holding integer category codes from |
required |
outcome
|
str
|
Name of the outcome column. |
required |
conditions
|
list of str or tuple of str
|
Condition columns. |
required |
levels
|
mapping of str to int
|
Number of categories per condition. Inferred from the data when omitted, which can understate a level that no case happens to take. |
None
|
inclusion_cutoff
|
float
|
Minimum sufficiency consistency for a configuration to count. |
0.8
|
frequency_cutoff
|
int
|
Minimum number of cases for a configuration to be observed. |
1
|
case_id
|
str
|
Column holding case labels. Defaults to the frame index. |
None
|
Returns:
| Type | Description |
|---|---|
MultiValueTruthTable
|
One row per logically possible configuration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a condition is not categorical, a level count is too small, or a cutoff is out of range. |
Source code in src/setqca/multivalue/_model.py
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |