Necessity¶
A condition is necessary for an outcome when the outcome is a subset of the condition: wherever the outcome appears, the condition appears too. It is the mirror of sufficiency, and it answers a different question.
| Question | Set relation | Consistency | |
|---|---|---|---|
| Sufficiency | Is this enough to produce the outcome? | X ⊆ Y |
Σ min(X,Y) / Σ X |
| Necessity | Can the outcome occur without this? | Y ⊆ X |
Σ min(X,Y) / Σ Y |
The two are duals: necessity of X for Y is sufficiency of Y for X with
the roles exchanged. Neither implies the other, and a condition can be both,
either, or neither.
Screening¶
from setqca import necessity_analysis
analysis = necessity_analysis(
data,
outcome="SURV",
conditions=["DEV", "URB", "LIT", "IND", "STB"],
consistency_threshold=0.90,
)
print(analysis)
print(analysis.to_frame())
Every condition is screened in both directions by default. A condition's absence can be necessary when its presence is not, and screening only presence is a common way to miss the finding.
Results are typed objects, not just a frame:
analysis.necessary # consistent and non-trivial
analysis.trivial # consistent but uninformative
analysis.candidates # everything screened
The trivialness problem¶
This is the part that matters most.
A condition present in almost every case is a superset of almost anything. It will show near-perfect necessity consistency while telling you nothing — you cannot explain a rare outcome with a ubiquitous condition.
ubiquitous scores consistency 1.000 — apparently a perfect necessary
condition. It is an artefact of prevalence.
Relevance of necessity is what exposes it:
The more prevalent X is, the smaller the numerator, and the closer RoN falls
to zero. In the example above RoN is exactly 0.
setqca therefore reports both, and separates the two lists:
Necessary:
~C [cons=1.000, cov=0.900, RoN=0.909]
Consistent but trivial (prevalent enough to be uninformative):
B [cons=1.000, RoN=0.000, prevalence=1.000]
A candidate is necessary only when it clears both thresholds. Clearing
consistency alone makes it trivial, which is reported rather than left for
the reader to notice.
Consistency alone is not evidence of necessity
A high necessity consistency with a low RoN is the single most common way a QCA writes up a finding that isn't there. Always report both.
Compound conditions¶
Only disjunctions are screened, and this is a mathematical result rather than a limitation:
consistency(A*B) ≤ min(consistency(A), consistency(B)) because min(A,B,Y) ≤ min(A,Y)
consistency(A+B) ≥ max(consistency(A), consistency(B)) because min(max(A,B),Y) ≥ min(A,Y)
A conjunction can never be more necessary than its own components, so testing conjunctions adds nothing. A union can be necessary when neither part is — the SUIN condition of the literature, a sufficient part of an insufficient but necessary condition.
analysis = necessity_analysis(
data,
outcome="SURV",
conditions=["DEV", "URB", "LIT"],
max_disjunction_size=2,
)
Beware that the number of unions grows quickly: with k literals and size n
there are C(k, n) of them, and screening many raises the chance that one
clears the threshold by luck. Treat unions as hypotheses to examine, not
findings to report.
Necessity is not causation¶
Necessity is a statement about set relations in the data you have. It does not establish that the condition produces the outcome, that removing it would prevent the outcome, or that the relation holds outside your cases. A constant-across-cases condition is necessary in the data and may be causally irrelevant, and vice versa.
setqca.analysis.necessity ¶
Systematic analysis of necessary conditions.
A condition is necessary for an outcome when the outcome is a subset of the condition: wherever the outcome is present, the condition is present too. This is the mirror image of sufficiency, and the two answer different questions — necessity asks what cannot be missing, sufficiency asks what is enough.
Necessity is easy to claim and easy to overclaim. A condition present in almost every case is a superset of almost anything, so it will show near-perfect necessity consistency while explaining nothing. That is trivial necessity, and it is reported here rather than left for the reader to notice.
Which compounds are worth testing
Only disjunctions. For the minimum/maximum operators:
consistency(A*B) <= min(consistency(A), consistency(B)), becausemin(A, B, Y) <= min(A, Y). A conjunction can therefore never be more necessary than its own components, and testing conjunctions adds nothing.consistency(A+B) >= max(consistency(A), consistency(B)), becausemin(max(A, B), Y) >= min(A, Y). A union can be necessary when neither part is, which is the SUIN condition of the literature — a sufficient part of an insufficient but necessary condition.
So conjunctions are excluded on mathematical grounds, not for lack of effort.
NecessityCandidate
dataclass
¶
NecessityCandidate(
expression: str,
fit: NecessityFit,
prevalence: float,
consistent: bool,
relevant: bool,
)
One candidate necessary condition, with everything needed to judge it.
Attributes:
| Name | Type | Description |
|---|---|---|
expression |
str
|
The candidate in standard QCA notation, e.g. |
fit |
NecessityFit
|
Consistency, coverage and relevance of necessity. |
prevalence |
float
|
Mean membership of the candidate across cases. A prevalence near 1 is what makes a necessity claim trivial. |
consistent |
bool
|
Whether consistency reached the threshold. |
relevant |
bool
|
Whether relevance of necessity reached its threshold. |
necessary
property
¶
Return whether the candidate is both consistent and non-trivial.
trivial
property
¶
Return whether the candidate is consistent but irrelevant.
This is the dangerous combination: the numbers look like necessity, but the condition is so prevalent that the claim carries no information.
NecessityAnalysis
dataclass
¶
NecessityAnalysis(
outcome: str,
consistency_threshold: float,
relevance_threshold: float,
candidates: tuple[NecessityCandidate, ...],
)
The result of screening candidates for necessity.
necessary
property
¶
necessary: tuple[NecessityCandidate, ...]
Return candidates that are consistent and non-trivial.
trivial
property
¶
trivial: tuple[NecessityCandidate, ...]
Return candidates that pass on consistency but fail on relevance.
to_frame ¶
Return a tidy table, sorted by consistency then relevance.
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Source code in src/setqca/analysis/necessity.py
necessity_analysis ¶
necessity_analysis(
data: DataFrame,
*,
outcome: str,
conditions: list[str] | tuple[str, ...],
consistency_threshold: float = DEFAULT_CONSISTENCY,
relevance_threshold: float = DEFAULT_RELEVANCE,
include_absence: bool = True,
max_disjunction_size: int = 1,
) -> NecessityAnalysis
Screen conditions, and optionally their disjunctions, for necessity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Calibrated memberships in |
required |
outcome
|
str
|
Name of the outcome column. |
required |
conditions
|
list of str or tuple of str
|
Condition columns to screen. |
required |
consistency_threshold
|
float
|
Minimum necessity consistency for a candidate to count as consistent. |
0.90
|
relevance_threshold
|
float
|
Minimum relevance of necessity. Candidates that pass on consistency but fail here are reported as trivial rather than necessary. |
0.50
|
include_absence
|
bool
|
Also screen the negation of every condition. A condition's absence can be necessary when its presence is not. |
True
|
max_disjunction_size
|
int
|
Largest union to test. |
1
|
Returns:
| Type | Description |
|---|---|
NecessityAnalysis
|
Every candidate screened, with those that are necessary and those that are merely trivial identified separately. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a threshold is out of range, |
KeyError
|
If a named column is absent. |
Examples:
>>> analysis = necessity_analysis(
... data, outcome="SURV", conditions=["DEV", "URB", "LIT"]
... )
>>> analysis.to_frame()
Source code in src/setqca/analysis/necessity.py
176 177 178 179 180 181 182 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 | |