Skip to content

Composite Scores

composite() combines multiple IER indices into one sample-relative score. Higher values indicate stronger careless-responding signal within the sample.

After computing components once, use composite_scores_summary() to change the combination while retaining raw components, per-respondent coverage counts, and aggregate statistics:

from ier import composite_scores_summary, load_score_archive

saved = load_score_archive("screening.npz")
details = composite_scores_summary(
    {name: saved["scores"][name] for name in ["irv", "longstring"]},
    weights={"irv": 2.0},
    min_valid_indices=2,
)
print(details["composite"], details["valid_index_counts"])

Only successful saved scores can be combined. The summary's errors mapping is empty because no index is recomputed; source failures remain in the loaded archive. Compatible arrays are retained in indices and never mutated. Coverage is measured using the same calibration and availability rules as a fresh composite summary, including the established raw single-observation policy.

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:

  1. Prefer multi-index agreement over any single cutoff.
  2. Review flagged cases substantively (open text, completion time, attention checks).
  3. Report which indices, combination method, weights, and completeness rule you used.
  4. 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, avgstr, autocorrelation, gpoly, u3poly_fit, ht

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

Explicit indices are ignored with best_subset. Python callers receive a DeprecationWarning, and ier composite --method best_subset --indices ... prints a warning on standard error before ignoring --indices. Both combinations will become errors in a future release.

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.

Standardization uses scaled, centered observations so finite large, tiny, and nearly constant components retain their differences. A constant component with at least two available observations contributes zero. Missing observations stay unavailable; components with only one available observation retain the existing raw-score fallback. Summary statistics use the same stable reductions as the individual scoring helpers.

Index weights

All composite helpers accept a partial weights mapping, or a pandas Series indexed by index name. Values must be positive finite numbers, and every named index must be selected by the resolved method and named only once. Selected indices omitted from the mapping retain weight 1.

Weights are applied after direction correction and optional standardization:

  • mean and best_subset compute a weighted mean. When an index score is missing for one respondent, its weight is omitted from that respondent's denominator.
  • sum computes a weighted sum; a respondent missing every index retains the established score of zero.
  • max takes the maximum weighted directed score.

Multiplying every weight by the same constant leaves weighted means unchanged. Mean reductions normalize safe common weight scales before multiplication. Extreme weight ratios, overflowing totals, and lost tiny products use separately scaled numerator and denominator reductions in bounded batches. Severe cancellation and subnormal rounding receive exact scalar repairs; this extra work can cost more for numerically degenerate samples. A mean with one computed component returns its directed score because its positive weight cancels. composite_summary() includes the full resolved weight mapping, including default weight 1 values.

Sums also normalize common weight units, then restore the weighted total. Overflowing intermediate products or additions, severe cancellation, and subnormal totals use bounded repairs. Maximum repairs compare scaled products before converting the winner, so an oversized negative contribution can be discarded safely. These repairs preserve finite final results. If a final sum or maximum exceeds the finite float range, the helpers raise ValueError with the respondent's zero-based index; reduce weights or choose method="mean". Respondents below min_valid_indices receive NaN without a range error. The same range errors apply when a computed index has already returned an infinite score. Weighting cannot recover its original magnitude. A negative infinite candidate can still be discarded by max when another component provides a finite winner. Raw means retain infinite contributions, with opposite infinities producing NaN.

A component with mixed finite and infinite observations cannot be standardized; its calibrated scores remain unavailable without arithmetic warnings. Coverage counts reflect this for both single and multiple components. The existing single-observation raw fallback and constant-component behavior still apply.

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"],
    errors=initial["errors"],
    weights={"irv": 2.0, "longstring": 0.75},
)
raw_maximum = composite_scores(
    initial["indices"],
    errors=initial["errors"],
    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.

Pass errors=initial["errors"] to preserve the original selected-index set when validating weights and min_valid_indices. Failed indices contribute no scores, weight totals, or availability. A minimum of three therefore still suppresses every score if only two of the three selected indices succeeded. Failure names must be composite-enabled registered indices, absent from the score mapping, and paired with nonblank string messages.

composite_scores_summary() accepts the same arguments and returns aggregate statistics, raw components, calibrated valid_index_counts, resolved weights, and supplied failure provenance. Its errors mapping is empty when omitted. Retained failures are copied without rerunning any index.

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("raw-components.npz")
reweighted = composite_scores(
    saved["scores"], errors=saved["errors"], 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.

Reusing screening scores

Default screen() output also contains the screen-only response-style indices u3_poly, midpoint, and acquiescence, which composite_scores() rejects by default. Pass unsupported="drop" to omit every registered index that is not composite-enabled from both the scores and the retained failures, preserving the order of the remaining components:

from ier import composite_scores, screen

screened = screen(data)
reused = composite_scores(
    screened["scores"], errors=screened["errors"], unsupported="drop"
)

Unknown index names still raise in this mode, and a mapping without any composite-enabled score raises a ValueError that names the dropped indices. Every remaining screen index contributes, including longstring_pattern, markov, and guttman, which a fresh composite() omits by default; select names explicitly when the reused composite must match the composite defaults. composite_scores_summary() accepts the same unsupported option.

The CLI applies the same policy to a saved screening archive with --skip-unsupported and names the skipped indices on standard error:

ier screen data.csv --format npz --output screen.npz
ier composite-scores screen.npz --skip-unsupported --weight irv=2 --format json

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.

Public type annotations preserve these return shapes: composite() returns an array by default and (scores, diagnostics) with return_diagnostics=True; composite_flag() returns (scores, flags) by default and adds diagnostics as a third element when requested. A dynamic Boolean option returns the corresponding union for explicit narrowing.

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 --indices irv missing_rate --missing-applicable-mask applicable.npy
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.

Named item selection supports grouped --item-columns q1,q2 and repeatable exact --item-column NAME, including names containing commas. Their combined order defines the response matrix and all item-index positions. For skip logic, --missing-applicable-mask PATH accepts headerless 0/1 text or an uncompressed Boolean .npy file aligned to that selected matrix. See missing-response screening for transports and validation rules.

CSV rows and JSON respondent arrays are written forward-only to plain files, gzip-, bzip2-, or XZ-compressed 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.