Skip to content

Getting Started

Installation

From PyPI

pip install insufficient-effort

With optional dependencies

pip install "insufficient-effort[plot]"
Extra Provides
(none) All statistical indices, including chi-square and response-time mixture helpers
full Empty compatibility alias retained for existing installation commands
plot matplotlib helpers (plot_distributions, etc.)

From source (development)

git clone https://github.com/Cameron-Lyons/ier.git
cd ier
uv sync --all-groups

Input shapes

Functions expect a matrix where rows are respondents and columns are items. Accepted inputs:

  • nested lists / tuples
  • NumPy arrays
  • objects with __array__ (e.g. pandas DataFrames)

Polars DataFrames usually work via __array__, but converting with .to_numpy() is the most explicit path:

import polars as pl
from ier import irv

df = pl.DataFrame({"a": [1, 4], "b": [2, 5], "c": [3, 6]})
scores = irv(df.to_numpy())
import pandas as pd
from ier import irv

df = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
scores = irv(df)

Missing data

Most scorers accept na_rm=True (often the default) to skip incomplete rows or pairwise comparisons rather than failing on NaNs.

import numpy as np
from ier import irv, mahad

data = np.array([
    [1, 2],
    [2, 3],
    [np.nan, 4],
    [3, 4],
], dtype=float)

irv(data, na_rm=True)
mahad(data, na_rm=True, method="iqr")

Quick screening

from ier import IndexOptions, screen

result = screen(responses, options=IndexOptions(scale_min=1, scale_max=5))
print(result["flag_counts"])
print(result["consensus_flags"])

Or from the CLI:

ier screen responses.csv --scale-min 1 --scale-max 5 --min-flags 2
ier screen responses.csv --indices irv longstring missing_rate --min-valid-indices 2
ier screen responses.csv --index-percentile irv=90 --index-percentile longstring=99

For files with a respondent identifier column, preserve it in every output format by naming its header:

ier screen responses.csv --id-column participant_id --format csv --output screening.csv

Identifier values must be unique and nonblank. The selected column is excluded from the numeric item matrix before scoring.

Survey exports may also contain demographics, conditions, or other non-item metadata. Select only named numeric item columns, in scoring order, with a comma-separated or repeated option:

ier screen responses.csv \
  --id-column participant_id \
  --item-columns q1,q2,q3 \
  --item-columns q4,q5,q6

Named item selection requires a header. Item-index options such as --mad-positive-items use zero-based positions in the selected order, not the original file's column positions.

Delimited input detects a header automatically. For ambiguous files, make the contract explicit: --header present always treats the first non-empty row as a header, including when every column name looks numeric, while --header absent requires the first row to contain data. Named ID or item columns require auto or present mode.

Blank cells are always loaded as missing values. Survey exports that use explicit markers can map each exact, whitespace-trimmed token to NaN without preprocessing:

ier screen responses.csv --missing-value NA --missing-value -99

The option may be repeated and also works with gzip input and standard input. It applies only to scored numeric cells, so identifier and unselected metadata values remain unchanged.

For large headerless numeric matrices, save an uncompressed NumPy array and pass it directly. The CLI memory-maps .npy input read-only instead of copying it:

ier screen responses.npy --indices irv longstring --format json

Binary input must contain one non-empty, two-dimensional, real numeric array. Header, missing-value, and delimiter options and .npy.gz input are not supported.

Timing matrices have a dedicated command so their units cannot be mixed with item-response indices:

ier response-time timings.csv --metric median --threshold 1.0
ier response-time timings.csv --metric mixture --random-seed 42 --format json

Retain a timing score vector when comparing decision cutoffs:

from ier import response_time, response_time_score_flags

median_times = response_time(timings, metric="median")
strict_flags = response_time_score_flags(median_times, cutoff_percentile=1)

Use direction="high" when reflagging fast-component mixture probabilities.

Scoring commands also accept forward-only standard input and gzip-compressed files without extra packages:

cat responses.csv | ier screen - --indices irv longstring --format json
ier screen responses.csv.gz --format json --output screening.json.gz

CSV rows and JSON respondent arrays are forward-only for plain files, gzip files, and standard output, so large respondent-level exports do not retain the complete document in memory.

Independent indices can be scored concurrently for larger matrices:

result = screen(responses, workers=4)
scores = composite(responses, workers=4)
ier screen responses.npy --workers 4 --format json --output screening.json

The default workers=1 path remains sequential. Parallel scoring retains the requested index and failure order but may use more temporary memory, so benchmark representative data before choosing a worker count.

Final screening flag counts and composite reductions use respondent-sized workspaces rather than another respondent-by-index matrix, keeping post-scoring memory bounded as the number of selected indices grows.

Reuse the returned score vectors when comparing alternative decision rules:

from ier import screen_scores

strict = screen_scores(
    result["scores"],
    percentile=99,
    min_flags=3,
    min_valid_indices=3,
)

This returns a fresh screening result without recalculating any index.

Detailed composite results support the same reuse pattern for alternative weights, reductions, or completeness rules:

from ier import composite_scores, composite_summary

details = composite_summary(responses, indices=["irv", "longstring", "person_total"])
weighted = composite_scores(
    details["indices"],
    weights={"irv": 2.0, "person_total": 0.5},
    min_valid_indices=2,
)

For fast, lossless NumPy workflows, write a versioned, pickle-free archive:

ier screen responses.npy --indices irv longstring --format npz --output screening.npz

NPZ output requires a .npz file path and preserves typed flags, metadata, and non-finite values. Writes use same-directory atomic replacement, so existing results survive an interrupted serialization. See CLI output formats for the schema.

Save a compact reusable-score archive directly from Python, or reload compatible CLI output for later sensitivity work:

from ier import load_score_archive, save_score_archive, screen_scores

save_score_archive("raw-scores.npz", result["scores"], errors=result["errors"])
saved = load_score_archive("screening.npz")
revised = screen_scores(saved["scores"], percentile=99)

save_score_archive() validates every vector and metadata field before opening the staged archive and atomically replaces the destination only after every member is complete. Detailed composite NPZ output produced with --include-components works with the same loader and composite_scores(). Response-time results have matching save_response_time_archive() and load_response_time_archive() boundaries; retained scores feed directly into response_time_score_flags() and can be written back with revised flags.

Next steps