Composite Scores¶
composite() combines multiple IER indices into one sample-relative score.
Higher values indicate stronger careless-responding signal within the sample.
from ier import IndexOptions, composite, composite_probability
opts = IndexOptions()
scores = composite(
data,
indices=["irv", "longstring", "person_total", "markov"],
options=opts,
)
weighted_scores = composite(
data,
indices=["irv", "longstring", "person_total"],
weights={"irv": 2.0, "person_total": 0.5},
)
coverage_filtered = composite(
data,
indices=["irv", "longstring", "person_total"],
min_valid_indices=2,
)
ranks = composite_probability(data, indices=["irv", "longstring"], options=opts)
auditable_ranks, probability_errors = composite_probability(
data,
indices=["irv", "mad"],
options=opts,
return_diagnostics=True,
)
# Available on composite(), composite_flag(), composite_summary(), and
# composite_probability().
parallel_scores = composite(data, indices=["irv", "longstring"], workers=4)
Important caveats¶
Not a calibrated probability
composite_probability() applies a logistic transform to standardized
composite scores. Values lie in [0, 1] but are not validated
probabilities of IER unless you calibrate against labeled data from a
comparable survey. The transform is numerically stable for extreme scores,
including infinite inputs, without emitting overflow warnings.
Practical guidance:
- Prefer multi-index agreement over any single cutoff.
- Review flagged cases substantively (open text, completion time, attention checks).
- Report which indices, combination method, weights, and completeness rule you used.
- Use
method="best_subset"when you want the Curran/Meade-Craig style mix of consistency, pattern, and (optionally) MAD signals.
Allowed indices¶
Composite-enabled indices include:
irv, longstring, longstring_pattern, mahad, psychsyn, psychant,
person_total, markov, guttman, individual_reliability, evenodd, mad,
lz, semantic_syn, semantic_ant, infrequency, missing_rate
Configure missing_rate with IndexOptions(missing_item_indices=[...]) for a
fixed required-item subset or missing_applicable_mask=... for respondent-specific
skip logic. Respondents without applicable items contribute an unavailable
component score, so min_valid_indices can enforce the desired composite coverage.
The infrequency component also accepts infrequency_missing. Policies omit
and propagate can produce unavailable scores; combine them with
min_valid_indices when attention-check availability is required for a composite.
Screening-only response-style indices (u3_poly, midpoint, acquiescence,
onset) are excluded from composite combination because they measure different
constructs and can dilute pattern/consistency signals.
Combination methods¶
| Method | Behavior |
|---|---|
mean |
Mean of (optionally standardized) directed scores |
sum |
Sum of directed scores |
max |
Max of directed scores |
best_subset |
Forces ["mad", "irv", "longstring", "lz"] when MAD items are provided, else ["irv", "longstring", "lz"], combined with mean |
Direction is handled automatically: low-is-bad indices are sign-flipped before combination so that higher composite always means more IER signal.
Each directed index is standardized to a z-score by default so components with
different units contribute on a comparable scale. Set standardize=False in
Python or pass ier composite --no-standardize when original score units are
required. Raw-score combinations can be dominated by wider-ranging components,
so report the setting and justify any weights.
Index weights¶
All composite helpers accept a partial weights mapping. Values must be
positive finite numbers, and every named index must be selected by the resolved
method. Selected indices omitted from the mapping retain weight 1.
Weights are applied after direction correction and optional standardization:
meanandbest_subsetcompute a weighted mean. When an index score is missing for one respondent, its weight is omitted from that respondent's denominator.sumcomputes a weighted sum; a respondent missing every index retains the established score of zero.maxtakes the maximum weighted directed score.
Multiplying every weight by the same constant leaves weighted means unchanged.
composite_summary() includes the full resolved weight mapping, including
default weight 1 values.
Reusing component scores¶
Weight, method, standardization, and completeness sensitivity checks do not need
to recalculate the component indices. Retain the raw component mapping from one
detailed run and pass it to composite_scores():
from ier import composite_scores, composite_summary
initial = composite_summary(
data,
indices=["irv", "longstring", "person_total"],
)
weighted = composite_scores(
initial["indices"],
weights={"irv": 2.0, "longstring": 0.75},
)
raw_maximum = composite_scores(
initial["indices"],
method="max",
standardize=False,
min_valid_indices=2,
)
Inputs use the original public index directions. Low-is-suspicious signals are
reversed automatically, matching composite(). Only composite-enabled registered
indices are accepted, and every vector must be one-dimensional, non-empty,
equally sized, and contain only finite values or NaN. The input arrays are never
mutated.
best_subset is intentionally unavailable on this path because its purpose is
selecting and calculating a predefined component set. Supply that set explicitly
and use method="mean" once its raw scores have been retained.
On the bundled 10,000-respondent, 80-item benchmark, evaluating five weight scenarios from retained scores takes 1.4 ms and 0.7 MiB peak temporary allocation instead of 39.7 ms and 12.9 MiB for five full runs, a 27.9x speedup without another dependency.
Component scores exported with ier composite --include-components --format npz
or saved directly from composite_summary() can be reloaded for a later session:
from ier import composite_scores, load_score_archive, save_score_archive
save_score_archive(
"raw-components.npz",
initial["indices"],
result_type="composite",
errors=initial["errors"],
)
saved = load_score_archive("composite.npz")
reweighted = composite_scores(saved["scores"], weights={"irv": 2.0})
Aggregate-only composite archives intentionally fail this load because they do not contain the raw registered-index vectors needed for a new combination.
Composite completeness¶
By default, composite methods retain their established missing-value behavior:
means renormalize over available components, maxima ignore missing components,
and sums return zero when every component is missing. Set
min_valid_indices=N when a composite should only be reported from at least
N available component scores. Respondents below the minimum receive NaN
before flagging or logistic transformation.
The minimum counts components, not their weights. A single heavily weighted
index therefore cannot satisfy a two-index requirement. A selected index that
soft-fails for the whole matrix is unavailable for every respondent and does
not lower the requested minimum. composite_summary() returns both the resolved
min_valid_indices value and a respondent-aligned valid_index_counts array so
coverage decisions can be audited.
By default, one invalid configured index is returned in diagnostics while other
indices continue. Pass strict=True to composite(), composite_flag(),
composite_summary(), or composite_probability() when every selected index
must succeed. For probability output, pass return_diagnostics=True to receive
(probabilities, diagnostics); omitting it retains the established array-only
return value.
The CLI does not discard these soft failures. Every format writes a warning to
standard error, text output includes an errors section, JSON includes an
errors object, and NPZ stores aligned error_names and error_messages
vectors. CSV remains a respondent-only table, so retain the separate
standard-error stream when provenance is required.
All composite helpers accept workers. The default of 1 evaluates indices
sequentially; larger values retain index and diagnostic order while trading
higher temporary memory for potential throughput improvements.
CLI¶
Blank fields in comma-, tab-, or semicolon-delimited input are loaded as missing
values (NaN) and follow each index's documented missing-data behavior.
ier composite data.csv --indices irv longstring --method mean
ier composite data.csv --indices irv longstring --no-standardize
ier composite data.csv --indices irv longstring --percentile 95 --format csv
ier composite data.csv --indices irv longstring --threshold 1.5 --format json
ier composite data.csv --indices irv longstring --include-probability --format csv
ier composite data.csv --indices irv longstring --weight irv=2 --weight longstring=0.5
ier composite data.csv --indices irv longstring markov --min-valid-indices 2
ier composite data.csv --indices irv longstring --include-components --format json
ier composite data.csv --indices irv mad --strict
ier composite data.csv --indices irv longstring --workers 4
ier composite data.csv --format json --output composite.json
ier composite data.csv --format csv --evenodd-factors 5,5 --indices irv evenodd
ier composite data.npy --indices irv longstring --format json
ier composite data.npy --indices irv longstring --format npz --output composite.npz
Uncompressed .npy matrices are memory-mapped read-only. They must be non-empty,
two-dimensional, and real numeric; header selection and delimiter options do not
apply to this binary format.
CSV rows and JSON respondent arrays are written forward-only to plain files, gzip files, or standard output without retaining the complete export in memory.
JSON output is standards-compliant: unavailable or non-finite scores are encoded
as null. CSV output represents those scores as empty cells so numeric columns
remain compatible with spreadsheet and statistics tools.
NPZ output preserves the numeric score vector, combination method,
standardization setting, and optional respondent IDs. Text, JSON, and NPZ
outputs also record explicitly supplied weight overrides; unlisted selected
indices use weight 1. When supplied, the minimum valid-index rule and any soft
failures are recorded alongside those fields. See the
versioned archive schema.
Add --include-components when command-line results need respondent-level
provenance. Every format then includes the successfully computed raw index
scores and respondent-level availability counts. JSON and NPZ call the vector
valid_index_counts, CSV uses valid_index_count, and text labels it
valid_indices. Failed indices remain in the existing error metadata instead
of appearing as score columns. Component values are the raw public index
outputs, before composite direction correction, standardization, or weighting.
JSON arrays and CSV rows remain streamed, while NPZ stores one typed array per
successful index.
Add --include-probability when an export needs the same overflow-safe logistic
transform exposed by composite_probability(). The command transforms the
already-computed aggregate vector, so index scoring still runs once. JSON and
NPZ include probability_scale="uncalibrated_logistic", CSV adds
composite_probability, and text adds a probability column. Omitting the
option preserves every existing schema. These values are not calibrated, and
--threshold / --percentile continue to operate in composite-score units.
Add one of --threshold VALUE or --percentile VALUE when the export should
include a composite decision for each respondent. A fixed threshold is inclusive
(score >= threshold); a sample-percentile threshold is strict
(score > resolved cutoff) so ties at the percentile are not flagged. The two
options are mutually exclusive. Text, JSON, and NPZ record the resolved cutoff
and whether it was fixed or sample-derived, while CSV adds a composite_flag
column. Without either option, output remains score-only.