--- title: "Scoring the PID-5-SF" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Scoring the PID-5-SF} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(hitop) ``` ```{r} #| include: false library(tibble) ``` ## Score simulated PID-5-SF data The PID-5-SF is a shorter version of the PID-5 with 100 items that still yields all domain and facet scores. The validity scales are still calculable but may have fewer items and their psychometric properties have not, to my knowledge, been examined with the FSF. The `sim_pid5sf` dataset names its items `pid5sf_001` to `pid5sf_100`. If your own PID-5-SF columns are named some other way, `rename_pid5_items()` with `version = "SF"` will rename them to this pattern. `label_pid5()` with `version = "SF"` then attaches the item prompts, or the scale display names under `target = "scales"`, as `label` attributes. To collect PID-5-SF responses online with hitop-form and read the saved files back with `read_form_responses()`, see the section "Collecting Responses Online with hitop-form" in `vignette("pid5_scoring")`. ```{r x3} data("sim_pid5sf") score_pid5(sim_pid5sf, items = 1:100, version = "SF", append = FALSE) validity_pid5(sim_pid5sf, items = 1:100, version = "SF", append = FALSE) ``` ## Score real PID-5-SF data We can repeat this process with real data that was collected at University of Kansas (KU). There should be fewer (but still some) validity problems since this is real data. We can also retain un-scored "ID" variables in the dataset. ```{r x4} data("ku_pid5sf") score_pid5( ku_pid5sf, items = sprintf("pid5sf_%03d", 1:100), version = "SF", append = FALSE ) validity_pid5( ku_pid5sf, items = sprintf("pid5sf_%03d", 1:100), version = "SF", append = FALSE ) ``` ## Simple Standard Errors (deprecated) The `calc_se` argument is **deprecated**. It, and the `_se` columns it adds, will be removed in a future release, and the call below warns because it passes `calc_se = TRUE`. This package has no interval function for the PID-5, so there is no replacement for it on this instrument; for measurement precision, see the reliability coefficients below. What the argument computes, while it lasts: for the 25 facets, this is the SD of the items the respondent actually answered divided by the square root of how many of those items they answered. The 5 domain scores are means of three facet scores rather than means of items, so their standard errors are taken one level up: the SD of the three contributing facet scores divided by the square root of 3. A standard error is `NA` wherever its scale score is `NA`. Note what these numbers do and do not describe. Each one summarizes how much a respondent's answers varied within a scale; it is not an estimate of how precisely the scale measures the underlying trait, so it does not give a confidence interval for a respondent's true score. ```{r se} score_pid5( ku_pid5sf, items = sprintf("pid5sf_%03d", 1:100), version = "SF", calc_se = TRUE, append = FALSE ) ``` Note how there are now 60 columns instead of 30. The extra columns aren't all shown in the preview above, but they are named with the `_se` suffix, e.g., `pid_anhedonia_se`. ## Scale Reliability As we compute scale scores, we can also estimate their inter-item reliability using Cronbach's α (alpha) or McDonald's ω (omega total). α is fast and widely used, but it assumes tau-equivalence (all items load equally on a single factor); violations can make α under- or over-estimate reliability. ω is based on a congeneric single-factor model, allowing items to have different loadings and error variances; it typically provides a more accurate reliability estimate for unit-weighted sums. Both assume the scale is essentially unidimensional; α and ω coincide when tau-equivalence holds. We estimate reliability with the `reliability_pid5()` function, which returns a tibble with one row per scale: its printed name (`Scale`), the stem that names its column in the scored output (`camelCase`), the number of items (`nItems`), and the requested coefficients. By default it computes both `alpha` and `omega`; for the latter, we will need the **lavaan** package installed (set `omega = FALSE` to skip it). ```{r reliability} #| warning: false reliability_pid5( data = ku_pid5sf, items = sprintf("pid5sf_%03d", 1:100), version = "SF" ) ``` ## Normative Scores The `pid_norms` dataset carries the normative score distributions published by Markon et al. (2024), including a set built on the short form. The `norm_pid5()` function looks scored columns up in those tables and returns, for each one, the T score and percentile printed against the nearest tabled raw score. It converts scores rather than computing them, so we hand it the output of `score_pid5()` — and of `validity_pid5()`, if we want those scales converted too. ```{r x6} #| message: false #| warning: false scored <- score_pid5(ku_pid5sf, items = sprintf("pid5sf_%03d", 1:100), version = "SF") scored <- validity_pid5(scored, items = sprintf("pid5sf_%03d", 1:100), version = "SF") ``` For the short form the published tables cover the five domain scales, all 25 facet scales, and the INC-S inconsistency scale. Each converted scale gains a `_ptl` column, and those whose tables print T scores also gain a `_t` column. INC-S is distributed as percentiles only, so it gets no `_t` column. Note that `version = "SF"` selects the short-form tables — the same raw score converts differently across forms. ```{r x7} norm_pid5( scored, scores = paste0( "pid_", c("negativeAffectivity", "detachment", "antagonism", "disinhibition", "psychoticism", "INCS") ), version = "SF", append = FALSE ) ``` Every number returned is a cell of a published table: the nearest printed row is selected and nothing is interpolated. A score that falls outside a printed range is capped to the nearest end rather than extrapolated, and a warning reports how many observations that happened to. A scale the tables do not cover returns `NA` in both columns with a warning naming it. Every report this function makes is a warning, so a single `suppressWarnings()` call silences it. If the items were answered on a four-option response scale that starts somewhere other than 0 — 1 to 4, say — pass that range as `srange` and each score is reconciled to the published 0–3 metric before it is looked up, with a warning naming which scales were adjusted and which were left alone. The per-scale formulas are given in `?norm_pid5`. ## Profile Plots Once a respondent's scores are normed, `plot_pid5()` draws them as a profile against the published metric. It takes one respondent — a profile plot shows one person — so we norm the whole dataset and hand it a single row. Passing `version = "SF"` builds the plot against the short form's own tables. ```{r x8} #| message: false #| warning: false domains <- paste0( "pid_", c("negativeAffectivity", "detachment", "antagonism", "disinhibition", "psychoticism") ) normed <- norm_pid5(scored, scores = domains, version = "SF") ``` ```{r x9} #| fig.width: 7 #| fig.height: 4 #| eval: !expr rlang::is_installed("ggplot2", version = "3.4.0") plot_pid5(normed[1, ], version = "SF") ``` The dashed line marks T = 50, the normative sample's mean, and the score axis spans the range the published short-form tables actually print for these scales — so the axis does not rescale from respondent to respondent and two short-form profiles are directly comparable. Nothing on the plot says whether a score is high, low, or concerning: {hitop} presents scores against norms and leaves the interpreting to you. The short form scores all 25 facets, so `level = "facet"` plots those instead, grouped into a panel per domain. The APA key ties three facets to each domain; the remaining ten define no domain and are grouped separately rather than dropped. ```{r x10} #| fig.width: 7.5 #| fig.height: 9 #| message: false #| warning: false #| eval: !expr rlang::is_installed("ggplot2", version = "3.4.0") facets <- paste0("pid_", pid_scales[["SF"]]$camelCase) normed_facets <- norm_pid5(scored, scores = facets, version = "SF") plot_pid5(normed_facets[1, ], version = "SF", level = "facet") ``` Set `metric = "percentile"` for a percentile axis instead of T scores; the full-form vignette shows one. The result is an ordinary ggplot object, so you can restyle it with any ggplot2 layer.