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¶
- Run multi-index screening with
screen() - Combine signals with
composite() - Browse the index catalog
- Choose a machine-readable CLI output format