This release is a pre-release and may not be stable for production use.
xpsych: A Python toolbox for explainable psychiatry
Psychometric RSA and COMPASS construct scoring, with explicit measurement assumptions.
xpsych connects observations to psychological constructs through instrument items. Psychometric representational similarity analysis (RSA) compares how different sources organize the same items. COMPASS applies scoring keys to item activities to produce construct scores. Both use the same item coordinates, so you can examine what a score captures and which structure comes from the measurement procedure.
The numerical core accepts item profiles from any source. Optional text adapters produce profiles from embeddings or entailment models. Population geometry and person-level agreement are tested separately.
Author: Baihan Lin. For scientific questions or questions about using xpsych, contact doerlbh@gmail.com. For reproducible software problems, use GitHub Issues.
Install
Python 3.10 or later:
git clone https://github.com/linlab/xpsych.git
cd xpsych
python -m pip install .
# Optional embedding and entailment models:
python -m pip install '.[text]'
The core requires NumPy and SciPy. Importing xpsych does not load models or download weights. Install this rebuilt toolbox from GitHub until its PyPI release is available.
Quick start
import numpy as np
import xpsych as xp
# Real instrument: the public-domain 50-item IPIP Big-Five Factor Markers.
bank = xp.instruments.ipip50()
# Simulated activities for an offline example: 60 observations × 50 items.
# Replace with your profiles, with columns in bank.ids order.
rng = np.random.default_rng(7)
activities = rng.normal(size=(60, len(bank.items)))
scores = xp.compass(activities, bank) # construct name -> score per observation
rdm = xp.rsa(activities) # 50 × 50 item correlation distances
print(scores["extraversion"])
# Compare two sources measured on the same ordered items.
second_source = activities + rng.normal(size=activities.shape)
comparison = xp.rsa(activities, second_source, seed=7)
print(comparison.correlation, comparison.pvalue)
IPIP provides the real item wording and scoring directions; the observations above are explicitly simulated. The official IPIP items and keys are public domain. COMPASS applies signed weights to model activities. This differs from scoring 1–5 questionnaire responses, and does not produce calibrated personality ratings or diagnostic thresholds.
For actual text, supply a local model explicitly:
import xpsych as xp
from xpsych.text import EmbeddingScorer
bank = xp.instruments.ipip50()
scorer = EmbeddingScorer.from_pretrained("/path/to/local/embedding-model")
scores = xp.compass(
["I enjoy meeting new people and often start conversations."],
bank,
scorer=scorer,
)
Run python examples/quickstart.py for the offline example, or
python examples/ipip_text.py --model /path/to/local/embedding-model for text scoring.
See text scoring for the entailment adapter and model setup.
Compute item activities
An activity matrix has one row per observation and one column per instrument
item. The column order is bank.ids.
From text with a local embedding model:
import xpsych as xp
from xpsych.text import EmbeddingScorer
bank = xp.instruments.ipip50()
scorer = EmbeddingScorer.from_pretrained("/path/to/local/embedding-model")
texts = [
"I enjoy meeting new people and often start conversations.",
"I prefer quiet evenings and tend to listen rather than speak.",
"I plan my week carefully and finish tasks before starting new ones.",
]
activities = scorer.score(texts, bank) # cosine similarity, texts × items
scores = xp.compass(activities, bank)
rdm = xp.rsa(activities)
For entailment-minus-contradiction activities, use NLIScorer with a local NLI
model and explicit item hypotheses. See text scoring.
A small example demonstrates the API, not the sample size needed for inference.
From embeddings you already have:
# observation_embeddings: observations × embedding dimensions
# item_embeddings: items × the same embedding dimensions, in bank.ids order
activities = xp.cosine_activities(observation_embeddings, item_embeddings)
This normalizes embedding rows and computes their pairwise cosine similarities. For two-source RSA, each source must have the same ordered item columns.
Add your own inventory
Supply item IDs, wording and the signed weights for each construct. For example, you can define the published 10-item IPIP Extraversion scale explicitly:
import xpsych as xp
positive = [
"Am the life of the party.", "Feel comfortable around people.",
"Start conversations.", "Talk to a lot of different people at parties.",
"Don't mind being the center of attention.",
]
negative = [
"Don't talk a lot.", "Keep in the background.", "Have little to say.",
"Don't like to draw attention to myself.", "Am quiet around strangers.",
]
wording = positive + negative
items = [
xp.Item(f"E{i+1}", text, hypothesis="I " + text[0].lower() + text[1:])
for i, text in enumerate(wording)
]
keys = {"extraversion": {f"E{i+1}": 1 if i < 5 else -1 for i in range(10)}}
bank = xp.ItemBank(items, keys)
# Use the scorer and texts from the example above:
activities = scorer.score(texts, bank)
scores = xp.compass(activities, bank)
Replace the wording and keys with your instrument's authorized items. Add more
constructs as additional entries in keys; items omitted from a construct get
zero weight. The keys describe signed activity sums, not reverse-coded Likert
responses. This example uses the complete published IPIP Extraversion key,
rather than inventing a new validated scale.
How it fits together
flowchart LR
I[Instrument: items and scoring keys] --> A[Item activity profiles]
O[Observations] --> S[Scorer adapter]
S --> A
A --> C[COMPASS: construct scores]
I --> C
A --> R[Psychometric RSA: item geometry]
C --> V[Person-paired validation]
R --> G[Cross-source comparison and reference controls]
Scores and RDMs are two read-outs of the same profiles. A correlation RDM does not retain the activity levels needed to recover a person's construct scores. See architecture and UML for module responsibilities and API for function signatures.
| Module | Responsibility |
|---|---|
instruments |
Item definitions, ordered banks, scoring keys and the IPIP-50 bank |
scoring |
Item activities, keyed COMPASS scores and aggregation |
geometry |
RDMs, cross-source RSA, centring and reference whitening |
validation |
Agreement with observed scores, matched by person ID |
text |
Optional embedding and entailment adapters |
Reproduce the paper
COMPASS 2.0: psychometric representational similarity analysis distinguishes symptom structure from personal signal.
The papers/compass2/ folder contains the reproduction
recipe, input schemas and aggregate reference results. Exact reproduction uses
Python 3.11 and the pinned dependencies listed there. The wheel contains the reusable
package; the repository and source distribution also include the paper bundle.
Cite this work
If you use xpsych, psychometric RSA or COMPASS 2.0, please cite:
Lin, Baihan (2026). COMPASS 2.0: psychometric representational similarity analysis distinguishes symptom structure from personal signal. arXiv:2610.06615.
The preprint is available at arXiv:2610.06615. See citation metadata.
The package contains no participant transcripts, response records or model weights. Full reproduction requires authorized source datasets and permitted instrument inputs. The bundled IPIP-50 is public domain; other instruments retain their own terms. See data and instruments.
Interpretation
A high RSA correlation can reflect shared item wording rather than personal signal. Whitening tests departure from a specified reference and depends on that reference's assumptions. Use person-paired validation when making claims about individuals. See concepts.
Code license: MIT. Dataset and instrument terms are separate.
Metadata
Release files for xpsych 0.1.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| xpsych-0.1.0a1.tar.gz | 189.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xpsych-0.1.0a1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 210.4 kB
Release files / xpsych-0.1.0a1.tar.gz
| Download URL | xpsych-0.1.0a1.tar.gz |
|---|---|
| Size | 189.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a03c7502aa10a8d8059cc6161f828cd37c83c13bb8be434f1487c09b0826d6a0
|
|
BLAKE2b-256 checksum How to use checksums |
188ed1ab1a1a13abc8a7d5c1298b189e697ca392584eaafb214420892949d1b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Release files / xpsych-0.1.0a1-py3-none-any.whl
| Download URL | xpsych-0.1.0a1-py3-none-any.whl |
|---|---|
| Size | 21.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d6a2458fe7784d083ae570fead28f9de3182a25817d17ebc68ad9a4cd1b5e7f
|
|
BLAKE2b-256 checksum How to use checksums |
60b63b841c1d3bbd83466f8a36d6abed29bc7a68146e89056482ffc8f34a55a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Security for xpsych 0.1.0a1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.