README.md

semanticfa

Response-free semantic analysis of psychometric scales.

semanticfa reads the meaning of a scale's item wording with a language model and recovers, interprets, and refines the scale's latent structure — entirely from the items, with no human response data. Factor analysis on the item embeddings is the centerpiece, but the package is a full toolkit for working with a scale before (or without) collecting data: building semantic similarity matrices, deciding how many factors to keep, reading a semantic "loadings" table, comparing the recovered structure to theory, flagging redundant items, building short forms, vetting brand-new candidate items, detecting jingle/jangle fallacies across scales, naming the factors it finds, auditing whether the items actually cover their construct, and visualizing the item space.

Installation

# from CRAN:
install.packages("semanticfa")

# development version:
# install.packages("remotes")
remotes::install_github("devon7y/semanticfa")

The core of the package is pure R. Turning item text into embeddings on your machine uses Python via reticulate — needed only if you want the package to embed text for you (you can always bring your own embeddings):

sfa_install_python()   # one-time: provisions sentence-transformers

Two-dimensional item maps work out of the box (Rtsne and uwot are bundled). One optional package, EGAnet, powers EGA-based factor retention / dimension selection and the faithful UVA redundancy method — install it only if you use those parts.

Quick start

library(semanticfa)
data(big5)   # 50 IPIP Big-Five items + precomputed Qwen3-Embedding-8B embeddings

# one call: embed -> similarity -> retain -> extract -> diagnose
fit <- sfa(
  data.frame(code = big5$codes, item = big5$items,
             factor = big5$factors, scoring = big5$scoring),
  embeddings = big5$embeddings, nfactors = 5)
fit

# interpret and refine, all from the same fit
plot(fit, type = "scree")          # scree with parallel-analysis overlay
sfa_corplot(fit)                   # item-by-item similarity heatmap, grouped by factor
sfa_anchor(fit)                    # item-by-construct "belonging" (a semantic loadings table)
sfa_congruence(fit, target = big5$factors,    # agreement with theory (partition metrics)
               metrics = c("nmi", "ari"))
sfa_redundancy(fit)                # near-duplicate items

No respondents are involved at any step.

What's in the box

1. Embed text and build a similarity matrix

| Function | Purpose | |---|---| | sfa_embed() | Embed item text — on-device sentence-transformers (Qwen3 models, default), the OpenAI API, or any custom function. Results are cached. | | sfa_load_npz() | Load pre-generated embeddings (e.g. a GPU job) from a NumPy .npz, no Python needed. | | sfa_similarity() | Item-by-item similarity matrix with a choice of four encodings (below). | | sfa_nli_matrix() | Signed, valence-aware similarity from natural-language inference (entailment − contradiction), so reverse-keyed items are handled directly. | | sfa_install_python(), sfa_clear_cache() | Provision the embedding environment / clear the cache. |

2. Recover the factor structure

| Function | Purpose | |---|---| | sfa() | The end-to-end pipeline: embed → similarity → retain → extract → diagnose. Accepts raw text, precomputed embeddings, an sfa_embeddings object, or a precomputed similarity matrix. | | sfa_nfactors() | How many factors to keep — parallel analysis by default, with Kaiser, TEFI, EKC, EGA, MAP, and sem-k as an opt-in consensus battery. | | sfa_parallel() | Embedding-adapted parallel analysis (random-unit-vector null; no sample size needed). | | sfa_ekc() | Empirical Kaiser criterion (Braeken & van Assen) with the embedding dimension as the sample size. | | sfa_map() | Velicer's minimum average partial (tracks all reliable structure — see docs before trusting it here). | | sfa_cd() | Comparison-data misfit profile (Ruscio & Roche, adapted): does the matrix have a crisp factor count, or graded structure? | | sfa_semk() | sem-k: a learned retention rule trained on planted-truth item sets, reporting a factor count with a 90% conformal interval. | | sfa_dimselect() | Select the informative leading embedding coordinates ("depth") by EGA depth optimization. | | as_psych() | Hand the solution to psych (factor.congruence(), fa.sort(), …) as a standard fa object. |

3. Interpret the structure

| Function | Purpose | |---|---| | sfa_anchor() | An item-by-construct belonging matrix — a semantic loadings table — built from construct centroids and/or construct-name embeddings. | | sfa_project() | Place items on interpretable bipolar axes (e.g. mildsevere, passiveactive). | | sfa_congruence() | Compare the recovered structure to an empirical or theoretical one: Tucker φ, NMI, ARI, Frobenius, and disattenuated correlation. | | sfa_jinglejangle() | Flag jingle (same name, different content) and jangle (different name, same content) fallacies across multiple scales. | | sfa_name() | Name the factors: retrieve the construct term each factor's items point at, from a large word pool. | | sfa_leximax() | Lexical target rotation: orient the axes toward nameable constructs (also sfa(rotate = "leximax")). Model fit is invariant. | | sfa_nameability() | How nameable a given orientation is, plus each factor's retrieved label and runner-up candidates. |

4. Refine the scale — before collecting data

| Function | Purpose | |---|---| | sfa_redundancy() | Detect near-duplicate items via faithful Unique Variable Analysis (absolute wTO on an EBICglasso network) or a direct cosine criterion. | | sfa_simplify() | Build response-free short forms by selecting the most representative items per factor. | | sfa_item_fit() | Vet a brand-new candidate item: how well does it match the construct name and the other items, and is it redundant with any of them? |

5. Audit content validity — does the scale cover its construct?

| Function | Purpose | |---|---| | sfa_coverage() | Test whether a scale's items cover their construct's semantic region, against a matched-size null: construct coverage, per-item relevance with p-values, and the gaps left uncovered. Multi-factor scales audit per subscale. | | sfa_build_region() | Build a construct's semantic region from a text corpus (sfa_build_regions() for many at once). | | sfa_cross_matrix() | The content analogue of a multitrait matrix: every factor audited against every construct region, with no data collection. | | sfa_gaps(), sfa_deletion_gaps() | What a scale misses, and what a shortened form gives up relative to the full one. | | sfa_build_bank(), sfa_embedding_bank() | Pre-embed items and regions once, then run every audit from saved embeddings with no encoder loaded. |

6. Visualize

| Function | Purpose | |---|---| | sfa_corplot() | Heatmap of the item-by-item similarity matrix, grouped/ordered by factor (order accepts factor-name abbreviations, e.g. c("D","A","S")). | | sfa_itemplot() | 2-D item map via t-SNE, UMAP, PCA, or MDS (sfa_tsneplot() is a deprecated alias). | | plot(fit, "scree") | Scree plot with the parallel-analysis overlay. |

Fit diagnostics

Every sfa() fit reports KMO, a real partition-based TEFI (negative; lower is better), RMSR, CAF, McDonald's ω, and — when theoretical factors are supplied — a factor-to-theory alignment matrix (DAAL). summary(fit) adds the full breakdown, and calibrate = TRUE adds a Monte Carlo null reference for the diagnostics.

Encoding methods (sfa_similarity(..., encoding=))

| Method | Description | Keying | |---|---|---| | "atomic" (default) | L2-normalize, cosine similarity | keying-free (scoring ignored) | | "atomic_reversed" | Sign-flip reverse-keyed items, L2-normalize, cosine | uses scoring sign-flip | | "squid" | Subtract the questionnaire-mean embedding, then cosine | keying-free | | "mean_centered_pearson" | Mean-center → cosine = Pearson correlation | keying-free |

Bundled data

data(big5) — the 50-item IPIP Big-Five markers (public domain) with precomputed Qwen3-Embedding-8B embeddings (rounded to 4 decimal places), so every example runs without Python or network access.

Learn more

A getting-started tour, worked end-to-end on the bundled Big Five inventory, is in the package vignette (vignette("introduction", package = "semanticfa")).

References

License

GPL (>= 3)



Try the semanticfa package in your browser

Any scripts or data that you put into this service are public.

semanticfa documentation built on Sept. 2, 2026, 1:07 a.m.