The Personality Inventory for DSM-5 (PID-5) instrument has 220 items and yields 25 facet scales, 5 domain scales, and 5 validity scales. We can demonstrate the package’s functionality using some simulated data.
First, we load the package into memory using the
library() function. If this doesn’t work, make sure you
installed the package properly (see the README on GitHub).
Score simulated PID-5 data
The sim_pid5 dataset is built into the package and can
be loaded using the data() function. It contains 100 rows
(each representing a simulated participant) and 220 columns named
pid5_001 to pid5_220 (each representing an
item from the PID-5).
data("sim_pid5")
sim_pid5
#> # A tibble: 100 × 220
#> pid5_001 pid5_002 pid5_003 pid5_004 pid5_005 pid5_006 pid5_007 pid5_008
#> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 0 3 2 1 1 3 1 3
#> 2 3 3 0 3 0 3 2 2
#> 3 3 2 3 2 3 3 0 3
#> 4 1 3 0 2 1 0 2 0
#> 5 0 1 3 2 3 1 0 1
#> 6 2 1 1 3 3 2 2 0
#> 7 1 1 3 3 1 3 1 0
#> 8 2 0 3 0 3 2 0 1
#> 9 1 1 3 0 1 1 2 3
#> 10 0 3 2 3 3 0 1 2
#> # ℹ 90 more rows
#> # ℹ 212 more variables: pid5_009 <int>, pid5_010 <int>, pid5_011 <int>,
#> # pid5_012 <int>, pid5_013 <int>, pid5_014 <int>, pid5_015 <int>,
#> # pid5_016 <int>, pid5_017 <int>, pid5_018 <int>, pid5_019 <int>,
#> # pid5_020 <int>, pid5_021 <int>, pid5_022 <int>, pid5_023 <int>,
#> # pid5_024 <int>, pid5_025 <int>, pid5_026 <int>, pid5_027 <int>,
#> # pid5_028 <int>, pid5_029 <int>, pid5_030 <int>, pid5_031 <int>, …If your own PID-5 columns are named some other way,
rename_pid5_items() will rename them to this pattern. By
default it reads the item number out of a column already named
pid_1, pid_2 and so on – the spelling this
package’s own datasets used before they were renamed to match the
exports. Setting method = "text" instead matches the
literal item prompts. Columns it cannot match keep their names and are
reported, and a partial rename warns that fewer than 220 items were
matched, which suits a study that administered only some of them.
old_names <- data.frame(pid_1 = c(0, 1), pid_2 = c(2, 3), age = c(30, 40))
rename_pid5_items(old_names, version = "FULL")
#> Warning: Only 2 out of 220 PID-5 items were successfully matched and renamed.
#> ℹ Note: If you plan to use `score_pid5()`, ensure uncollected items exist in
#> the data frame as `NA` columns.
#> pid5_001 pid5_002 age
#> 1 0 2 30
#> 2 1 3 40label_pid5() is the companion helper: it attaches each
item’s questionnaire prompt to that item’s column as a
label attribute, which data viewers and reporting packages
can display in place of the column name. With
target = "scales" it does the same for the columns
score_pid5() writes, attaching each facet and domain its
display name.
labeled <- label_pid5(sim_pid5, target = "items", version = "FULL")
attr(labeled$pid5_001, "label")
#> [1] "I don't get as much pleasure out of things as others seem to"To turn these item-level data into scale scores on the 25 facets and
5 domains, we can use the score_pid5() function. We will
need to tell the function which columns contain our items and which
version of the PID this is. There are several ways we can specify the
items. First, we can provide the column numbers and use the
: shortcut. In this tibble, the items are from column 1 to
column 220 so we can use items = 1:220. I am going to also
set append = FALSE so that you can quickly see the scale
scores. I also can set the version to "FULL" (or leave that
argument off, as that is the default, shown in the example below) to let
it know we are using the full 220-item version.
scores <- score_pid5(sim_pid5, items = 1:220, version = "FULL", append = FALSE)
scores
#> # A tibble: 100 × 30
#> pid_anhedonia pid_suspiciousness pid_riskTaking pid_impulsivity
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1.25 1.71 1.36 2.33
#> 2 1.38 1.57 1.43 2
#> 3 1.88 1 1.29 1.83
#> 4 1.25 2.43 1.21 1.5
#> 5 1.12 1.57 1.64 2.5
#> 6 2.12 1 1.79 1.83
#> 7 1.38 1.14 1.86 1.17
#> 8 1.5 1.71 1.86 0.667
#> 9 1.12 1.14 1.86 1.67
#> 10 1.38 1.86 2.07 2
#> # ℹ 90 more rows
#> # ℹ 26 more variables: pid_eccentricity <dbl>, pid_distractibility <dbl>,
#> # pid_restrictedAffectivity <dbl>, pid_submissiveness <dbl>,
#> # pid_withdrawal <dbl>, pid_callousness <dbl>,
#> # pid_separationInsecurity <dbl>, pid_attentionSeeking <dbl>,
#> # pid_emotionalLability <dbl>, pid_depressivity <dbl>, pid_hostility <dbl>,
#> # pid_irresponsibility <dbl>, pid_rigidPerfectionism <dbl>, …If I had instead set append = TRUE (or left it off, as
that is the default), we would get back the sim_pid5 tibble
with the scale scores added to the end as extra columns. Notice below
how we now have 250 columns instead of 220 or 30.
scores <- score_pid5(sim_pid5, items = 1:220)
scores
#> # A tibble: 100 × 250
#> pid5_001 pid5_002 pid5_003 pid5_004 pid5_005 pid5_006 pid5_007 pid5_008
#> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 0 3 2 1 1 3 1 3
#> 2 3 3 0 3 0 3 2 2
#> 3 3 2 3 2 3 3 0 3
#> 4 1 3 0 2 1 0 2 0
#> 5 0 1 3 2 3 1 0 1
#> 6 2 1 1 3 3 2 2 0
#> 7 1 1 3 3 1 3 1 0
#> 8 2 0 3 0 3 2 0 1
#> 9 1 1 3 0 1 1 2 3
#> 10 0 3 2 3 3 0 1 2
#> # ℹ 90 more rows
#> # ℹ 242 more variables: pid5_009 <int>, pid5_010 <int>, pid5_011 <int>,
#> # pid5_012 <int>, pid5_013 <int>, pid5_014 <int>, pid5_015 <int>,
#> # pid5_016 <int>, pid5_017 <int>, pid5_018 <int>, pid5_019 <int>,
#> # pid5_020 <int>, pid5_021 <int>, pid5_022 <int>, pid5_023 <int>,
#> # pid5_024 <int>, pid5_025 <int>, pid5_026 <int>, pid5_027 <int>,
#> # pid5_028 <int>, pid5_029 <int>, pid5_030 <int>, pid5_031 <int>, …Alternatively, we could provide the item column names as a character
string. Typing out all 220 item names would be a hassle, but luckily
this data named them consistently so we can build the names
automatically using sprintf(). If we use the “pid5_%03d”
format and apply that across the numbers 1 to 220, that will create the
column names we need.
scores <- score_pid5(sim_pid5, items = sprintf("pid5_%03d", 1:220))
scores
#> # A tibble: 100 × 250
#> pid5_001 pid5_002 pid5_003 pid5_004 pid5_005 pid5_006 pid5_007 pid5_008
#> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 0 3 2 1 1 3 1 3
#> 2 3 3 0 3 0 3 2 2
#> 3 3 2 3 2 3 3 0 3
#> 4 1 3 0 2 1 0 2 0
#> 5 0 1 3 2 3 1 0 1
#> 6 2 1 1 3 3 2 2 0
#> 7 1 1 3 3 1 3 1 0
#> 8 2 0 3 0 3 2 0 1
#> 9 1 1 3 0 1 1 2 3
#> 10 0 3 2 3 3 0 1 2
#> # ℹ 90 more rows
#> # ℹ 242 more variables: pid5_009 <int>, pid5_010 <int>, pid5_011 <int>,
#> # pid5_012 <int>, pid5_013 <int>, pid5_014 <int>, pid5_015 <int>,
#> # pid5_016 <int>, pid5_017 <int>, pid5_018 <int>, pid5_019 <int>,
#> # pid5_020 <int>, pid5_021 <int>, pid5_022 <int>, pid5_023 <int>,
#> # pid5_024 <int>, pid5_025 <int>, pid5_026 <int>, pid5_027 <int>,
#> # pid5_028 <int>, pid5_029 <int>, pid5_030 <int>, pid5_031 <int>, …There are other useful arguments to the function that you can read
about using its documentation by typing the following into your R
console: ?score_pid5 or through the package
website.
Rank Each Person’s Highest Facets
Thirty scale columns is more than can be read at a glance. The
rank_scales() function condenses them: for each row it
sorts the columns you select and returns a single string naming the
highest-scoring ones, highest first. Here we rank the 25 facet scales —
the first 25 columns of the scored tibble, the last 5 being the domains
those facets roll up into — and ask for each participant’s top 5.
Setting prefix = "pid_" strips that leading string from the
column names, so the string reads as scale names.
facet_scores <- score_pid5(sim_pid5, items = 1:220, append = FALSE)
rank_scales(facet_scores, scales = 1:25, prefix = "pid_", top = 5, append = FALSE)
#> # A tibble: 100 × 1
#> top_scales
#> <chr>
#> 1 impulsivity,grandiosity,hostility,perceptualDysregulation,suspiciousness
#> 2 distractibility,rigidPerfectionism,impulsivity,attentionSeeking,withdrawal
#> 3 submissiveness,depressivity,anhedonia,eccentricity,impulsivity
#> 4 suspiciousness,withdrawal,hostility,intimacyAvoidance,callousness
#> 5 impulsivity,attentionSeeking,perseveration,perceptualDysregulation,distracti…
#> 6 distractibility,eccentricity,deceitfulness,separationInsecurity,anhedonia
#> 7 rigidPerfectionism,distractibility,riskTaking,grandiosity,perseveration
#> 8 unusualBeliefsExperiences,manipulativeness,intimacyAvoidance,attentionSeekin…
#> 9 rigidPerfectionism,depressivity,irresponsibility,perseveration,distractibili…
#> 10 intimacyAvoidance,riskTaking,impulsivity,distractibility,suspiciousness
#> # ℹ 90 more rowsThe top_scales column holds one comma-separated string
per participant: the names of their five highest-scoring facets, in
descending order. Ties are broken by the order of the columns you
selected. We passed append = FALSE so that the ranked
column comes back on its own; leaving that argument at its default
returns the scored tibble with the column added to the end instead. Use
name to call the output column something else and
dir = "low" to rank from the bottom.
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). Note that, because this is
naively simulated data, we would expect the reliability in this example
to be poor.
reliability_pid5(
data = sim_pid5,
items = sprintf("pid5_%03d", 1:220),
version = "FULL"
)
#> # A tibble: 25 × 5
#> Scale camelCase nItems alpha omega
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Anhedonia anhedonia 8 -0.211 NA
#> 2 Suspiciousness suspiciousness 7 -0.211 0.0411
#> 3 Risk Taking riskTaking 14 -0.0128 0.00697
#> 4 Impulsivity impulsivity 6 0.141 NA
#> 5 Eccentricity eccentricity 13 0.0554 0.152
#> 6 Distractibility distractibility 9 -0.154 0.00807
#> 7 Restricted Affectivity restrictedAffectivity 7 -0.144 NA
#> 8 Submissiveness submissiveness 4 -0.194 0.116
#> 9 Withdrawal withdrawal 10 -0.0290 NA
#> 10 Callousness callousness 14 0.0147 0.0279
#> # ℹ 15 more rowsValidity Scales for the PID-5
There are also several validity scales that have been developed for
the full PID-5, including measures of overreporting, inconsistent
responding, and positive impression management. We can use the simulated
data to demonstrate the ability of the validity_pid5()
function to calculate these scores and flag issues. The function
arguments will be consistent with what we just learned. Note that,
because the data is fake, we would expect there to be lots of validity
issues.
validity_pid5(sim_pid5, items = 1:220, append = FALSE)
#> ! A total of 99 observations (99.0%) met criteria for inconsistent responding on the INC (0 missing).
#> ℹ Consider removing them with `dplyr::filter(df, pid_INC < 17)`
#> ! A total of 53 observations (53.0%) met criteria for overreporting on the ORS (0 missing).
#> ℹ Consider removing them with `dplyr::filter(df, pid_ORS < 3)`
#> ! A total of 92 observations (92.0%) met criteria for defensiveness on the SDTD (0 missing).
#> ℹ Consider removing them with `dplyr::filter(df, pid_SDTD < 19)`
#> # A tibble: 100 × 5
#> pid_PNA pid_INC pid_ORS pid_PRD pid_SDTD
#> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 0 25 2 40 26
#> 2 0 18 2 34 31
#> 3 0 32 2 34 29
#> 4 0 29 3 34 31
#> 5 0 24 0 36 17
#> 6 0 23 2 35 36
#> 7 0 42 2 31 19
#> 8 0 17 1 28 21
#> 9 0 25 4 41 24
#> 10 0 30 5 31 29
#> # ℹ 90 more rowsNormative Scores
The pid_norms dataset carries the normative score
distributions published by Markon et al. (2024). 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.
scored <- score_pid5(sim_pid5, items = 1:220)
scored <- validity_pid5(scored, items = 1:220)For the full form the published tables cover the five domain scales,
all 25 facet scales, and three of the validity scales — INC, ORS, and
PRD. They do not cover SD-TD at all. Each converted scale gains a
_ptl column, and those whose tables print T scores also
gain a _t column. The validity scales are distributed as
percentiles only, so they get no _t column.
norm_pid5(
scored,
scores = paste0(
"pid_",
c("negativeAffectivity", "detachment", "antagonism", "disinhibition",
"psychoticism", "INC", "ORS", "PRD")
),
version = "FULL",
append = FALSE
)
#> Warning: ! 0 observations below and 63 above the printed range were capped to the
#> nearest printed row.
#> ℹ A capped score's T and percentile are the end row's printed values, not an
#> extrapolation.
#> # A tibble: 100 × 13
#> pid_negativeAffectivity_t pid_negativeAffectivity_ptl pid_detachment_t
#> <int> <dbl> <int>
#> 1 60 0.84 56
#> 2 54 0.71 59
#> 3 58 0.81 68
#> 4 59 0.82 67
#> 5 57 0.79 63
#> 6 64 0.89 66
#> 7 61 0.85 59
#> 8 58 0.81 65
#> 9 62 0.87 59
#> 10 57 0.79 70
#> # ℹ 90 more rows
#> # ℹ 10 more variables: pid_detachment_ptl <dbl>, pid_antagonism_t <int>,
#> # pid_antagonism_ptl <dbl>, pid_disinhibition_t <int>,
#> # pid_disinhibition_ptl <dbl>, pid_psychoticism_t <int>,
#> # pid_psychoticism_ptl <dbl>, pid_INC_ptl <dbl>, pid_ORS_ptl <dbl>,
#> # pid_PRD_ptl <dbl>The facets convert the same way. Anhedonia, for instance, is an item mean over its eight full-form items, and its table runs from a floor of 0.00 up to a printed 3.84 — past the 3.00 an item mean can actually reach, which is true of most of the facet columns and is a property of the published tables rather than of the conversion:
norm_pid5(scored, scores = "pid_anhedonia", version = "FULL", append = FALSE)
#> # A tibble: 100 × 2
#> pid_anhedonia_t pid_anhedonia_ptl
#> <int> <dbl>
#> 1 57 0.79
#> 2 59 0.8
#> 3 67 0.93
#> 4 57 0.79
#> 5 55 0.76
#> 6 71 0.95
#> 7 59 0.8
#> 8 61 0.84
#> 9 55 0.76
#> 10 59 0.8
#> # ℹ 90 more rowsEvery 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 — the PRD sum reaches 66 while its table stops at 55, so
it is a common one to see. 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.
domains <- paste0(
"pid_",
c("negativeAffectivity", "detachment", "antagonism", "disinhibition",
"psychoticism")
)
normed <- norm_pid5(scored, scores = domains, version = "FULL")
plot_pid5(normed[1, ], version = "FULL")
The dashed line marks T = 50, the normative sample’s mean, and the score axis spans the range the published tables actually print for these scales — so the axis does not rescale from respondent to respondent and two 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.
Passing level = "facet" plots all 25 facets 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.
facets <- paste0("pid_", pid_scales[["FULL"]]$camelCase)
normed_facets <- norm_pid5(scored, scores = facets, version = "FULL")
plot_pid5(normed_facets[1, ], version = "FULL", level = "facet")
Set metric = "percentile" for a percentile axis instead
of T scores. norm_pid5() returns percentiles as a
proportion; the plot multiplies them by 100 so the axis reads 0–100.
plot_pid5(normed[1, ], version = "FULL", metric = "percentile")
The result is an ordinary ggplot object, so you can restyle it with
any ggplot2 layer — + ggplot2::labs(title = ...), a
different theme, and so on.
The PID5BF+M
The PID5BF+M (Bach et al., 2020, doi:10.1159/000507589) is a
36-item form with 18 facets of 2 items each and 6 domains: the 5 PID-5
trait domains and Anankastia. Every item is a PID-5 item, and no item is
reverse-keyed. The pid_items$BFPM column gives each item’s
number on this form. The package names its item columns
pid5bfpm_01 to pid5bfpm_36.
The package has no simulated BF+M dataset. To have something to
score, we take the 36 BF+M items from sim_pid5 in BF+M
order and rename them.
bfpm_rows <- pid_items[!is.na(pid_items$BFPM), ]
bfpm_rows <- bfpm_rows[order(bfpm_rows$BFPM), ]
sim_bfpm <- sim_pid5[sprintf("pid5_%03d", bfpm_rows$FULL)]
names(sim_bfpm) <- sprintf("pid5bfpm_%02d", bfpm_rows$BFPM)score_pid5() with version = "BFPM" returns
the 18 facets, then the 6 domains, in the order of the form’s key. Each
facet is the mean of its 2 items, and each domain is the mean of its 3
facets. The map from domains to facets is in
pid_bfpm_domains.
bfpm_scores <- score_pid5(sim_bfpm, items = 1:36, version = "BFPM", append = FALSE)
bfpm_scores
#> # A tibble: 100 × 24
#> pid_emotionalLability pid_anxiousness pid_separationInsecurity pid_withdrawal
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1.5 1.5 1.5 2
#> 2 2 0.5 0 2.5
#> 3 1.5 0 2 1
#> 4 1 1 0.5 2
#> 5 1 0.5 1.5 0.5
#> 6 1.5 1.5 2 0.5
#> 7 2 1.5 1 1
#> 8 2.5 1.5 1 1
#> 9 2 1.5 3 1.5
#> 10 1.5 1 2 2
#> # ℹ 90 more rows
#> # ℹ 20 more variables: pid_anhedonia <dbl>, pid_intimacyAvoidance <dbl>,
#> # pid_manipulativeness <dbl>, pid_deceitfulness <dbl>, pid_grandiosity <dbl>,
#> # pid_irresponsibility <dbl>, pid_impulsivity <dbl>,
#> # pid_distractibility <dbl>, pid_perfectionism <dbl>, pid_rigidity <dbl>,
#> # pid_orderliness <dbl>, pid_unusualBeliefsExperiences <dbl>,
#> # pid_eccentricity <dbl>, pid_perceptualDysregulation <dbl>, …These are item means on the 0 to 3 scale, as for the other forms. The form’s published key sums the 2 items of a facet and averages the facet sums for a domain. On complete data, its values are twice the package’s:
head(2 * bfpm_scores$pid_anankastia)
#> [1] 2.000000 4.000000 4.000000 3.666667 3.000000 3.666667No missing-data rule is published for the PID5BF+M. Under the default
missing = "apa", any missing item makes its facet
NA, and an NA facet makes its domain
NA. With whole-number responses that is the same output as
missing = "complete". With
missing = "available", a facet can come from one item and a
domain from one or two facets.
reliability_pid5() returns the 18 facets and then the 6
domains. Omega is NA for every 2-item facet, because a
one-factor model of 2 items is not identified. These simulated responses
are random, so the estimates here are poor: some domain omegas also fail
and come back NA, and the chunk hides the warnings these
fits raise.
print(reliability_pid5(sim_bfpm, items = 1:36, version = "BFPM"), n = 24)
#> # A tibble: 24 × 5
#> Scale camelCase nItems alpha omega
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Emotional Lability emotionalLability 2 -2.06e-1 NA
#> 2 Anxiousness anxiousness 2 2.70e-2 NA
#> 3 Separation Insecurity separationInsecurity 2 7.94e-2 NA
#> 4 Withdrawal withdrawal 2 -7.60e-2 NA
#> 5 Anhedonia anhedonia 2 -1.88e-1 NA
#> 6 Intimacy Avoidance intimacyAvoidance 2 -1.18e-1 NA
#> 7 Manipulativeness manipulativeness 2 -7.03e-1 NA
#> 8 Deceitfulness deceitfulness 2 8.93e-2 NA
#> 9 Grandiosity grandiosity 2 -6.30e-2 NA
#> 10 Irresponsibility irresponsibility 2 -1.26e-1 NA
#> 11 Impulsivity impulsivity 2 2.67e-1 NA
#> 12 Distractibility distractibility 2 2.87e-2 NA
#> 13 Perfectionism perfectionism 2 -2.44e-2 NA
#> 14 Rigidity rigidity 2 2.92e-2 NA
#> 15 Orderliness orderliness 2 2.49e-2 NA
#> 16 Unusual Beliefs & Experiences unusualBeliefsExperie… 2 4.72e-2 NA
#> 17 Eccentricity eccentricity 2 2.17e-1 NA
#> 18 Perceptual Dysregulation perceptualDysregulati… 2 -3.79e-2 NA
#> 19 Negative affectivity negativeAffectivity 6 -1.30e-1 NA
#> 20 Detachment detachment 6 -1.89e-1 NA
#> 21 Antagonism antagonism 6 -1.37e-1 5.01e-4
#> 22 Disinhibition disinhibition 6 -1.12e-2 NA
#> 23 Anankastia anankastia 6 -3.37e-4 3.67e-2
#> 24 Psychoticism psychoticism 6 -2.64e-1 2.41e-2rename_pid5_items() and label_pid5() also
take version = "BFPM". The PID5BF+M has no validity scales,
and the package has no norms or profile plot for it.
The PID-5 Informant Form
The PID-5 Informant Form (PID-5-IRF; Markon et al., 2013, doi:10.1177/1073191113486513)
is the APA’s 218-item form on which an adult informant rates the person
receiving care. It has the full form’s 25 facets and 5 domains. It has
no counterpart to self-report items 96 and 177, so its Anxiousness and
Suspiciousness facets each have one item fewer. The
pid_items$IRF column gives each informant item’s number,
pid_items$TextIRF its wording (each item completes the stem
“He or she…”), and the package names its item columns
pid5irf_001 to pid5irf_218.
The package has no simulated informant dataset. For illustration, we
take the 218 self-report items that the informant items map to from
sim_pid5 and rename them.
irf_rows <- pid_items[!is.na(pid_items$IRF), ]
sim_irf <- sim_pid5[sprintf("pid5_%03d", irf_rows$FULL)]
names(sim_irf) <- sprintf("pid5irf_%03d", irf_rows$IRF)score_pid5() with version = "IRF" returns
the same 25 facets and 5 domains as the full form, under the same column
names, so informant and self-report scores of the same person line up
column by column.
irf_scores <- score_pid5(sim_irf, items = 1:218, version = "IRF", append = FALSE)
irf_scores
#> # A tibble: 100 × 30
#> pid_anhedonia pid_suspiciousness pid_riskTaking pid_impulsivity
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1.25 2 1.36 2.33
#> 2 1.38 1.67 1.43 2
#> 3 1.88 0.833 1.29 1.83
#> 4 1.25 2.67 1.21 1.5
#> 5 1.12 1.83 1.64 2.5
#> 6 2.12 1.17 1.79 1.83
#> 7 1.38 1.17 1.86 1.17
#> 8 1.5 1.5 1.86 0.667
#> 9 1.12 1.17 1.86 1.67
#> 10 1.38 2 2.07 2
#> # ℹ 90 more rows
#> # ℹ 26 more variables: pid_eccentricity <dbl>, pid_distractibility <dbl>,
#> # pid_restrictedAffectivity <dbl>, pid_submissiveness <dbl>,
#> # pid_withdrawal <dbl>, pid_callousness <dbl>,
#> # pid_separationInsecurity <dbl>, pid_attentionSeeking <dbl>,
#> # pid_emotionalLability <dbl>, pid_depressivity <dbl>, pid_hostility <dbl>,
#> # pid_irresponsibility <dbl>, pid_rigidPerfectionism <dbl>, …Scoring follows the APA informant key (Markon et al., 2013), with two
readings that ?score_pid5 explains. Fourteen items are
reverse-scored, the ones the key’s Facet Table marks R; the key’s Step 1
list also names items 98 and 176, which are not reversed. And the key
says to “round up” a fractional prorated raw score; the package applies
the nearest-whole-number rule of the other APA PID-5 keys, so informant
and self-report facets prorate the same way.
reliability_pid5(), rename_pid5_items() and
label_pid5() also take version = "IRF", and
label_pid5() labels items with the informant wording. No
validity scales for the informant form are on the package’s source
shelf, and its norms (Markon et al., 2024, Tables A–10 and A–11) are not
yet in pid_norms, so validity_pid5(),
norm_pid5() and plot_pid5() do not take it.
Because informant scores carry the full form’s column names,
norm_pid5() and plot_pid5() would accept them
as version = "FULL" without complaint; do not do that, as
it compares informant ratings with self-report norms.
The PID-5 Forensic Faceted Brief Form
The PID-5 Forensic Faceted Brief Form (PID-5-FFBF; Niemeyer, Grosz,
Zimmermann & Back, 2022, doi:10.1080/00223891.2021.1923522)
adapts the 100-item faceted short form for people in prison. It has a
self-report and an informant version. The study gave the items in
German, and the form was validated in German only. The English text in
pid_ffbf_items is the authors’ English version from the
supplement’s Table S3. Items they did not adapt keep the APA PID-5
wording. Most items are rewritten, and the authors numbered the form
afresh, so FFBF item numbers are not SF item numbers. Facet k, in
alphabetical order, holds items k, k + 25, k + 50 and k + 75, and items
12 and 26 are reverse-scored.
The package has no FFBF dataset. For illustration, we simulate answers to its 100 items.
set.seed(2022)
sim_ffbf <- as.data.frame(matrix(sample(0:3, 10 * 100, replace = TRUE), 10, 100))
names(sim_ffbf) <- sprintf("pid5ffbf_%03d", 1:100)score_pid5() with version = "FFBF" returns
25 facets, named and ordered as the SF’s, then 7 domains: the 5 APA
domains and the two domains of the paper’s four-factor solution that are
not APA domains, Disinhibited Aggression and Insecurity. The paper’s
four-factor Antagonism and Detachment have the facets of the APA domains
of the same names, so they are not repeated.
pid_ffbf_domains lists the facets of each domain.
ffbf_scores <- score_pid5(sim_ffbf, items = 1:100, version = "FFBF", append = FALSE)
ffbf_scores
#> # A tibble: 10 × 32
#> pid_suspiciousness pid_impulsivity pid_submissiveness pid_callousness
#> <dbl> <dbl> <dbl> <dbl>
#> 1 2.25 1 2 2.25
#> 2 1.25 2.25 0.75 2.25
#> 3 1.25 1 1.5 1.25
#> 4 1.25 0.5 1.5 1.5
#> 5 1 1.25 1 1.75
#> 6 1.5 1.25 1.25 0.75
#> 7 1.75 2.5 2.25 0.5
#> 8 2.25 2 1.5 1.25
#> 9 1.75 2.25 2.5 1.75
#> 10 1.75 1.25 1.25 1.5
#> # ℹ 28 more variables: pid_anhedonia <dbl>, pid_eccentricity <dbl>,
#> # pid_hostility <dbl>, pid_riskTaking <dbl>, pid_grandiosity <dbl>,
#> # pid_perceptualDysregulation <dbl>, pid_separationInsecurity <dbl>,
#> # pid_deceitfulness <dbl>, pid_perseveration <dbl>,
#> # pid_attentionSeeking <dbl>, pid_anxiousness <dbl>, pid_depressivity <dbl>,
#> # pid_withdrawal <dbl>, pid_restrictedAffectivity <dbl>,
#> # pid_intimacyAvoidance <dbl>, pid_rigidPerfectionism <dbl>, …
sapply(pid_ffbf_domains$primaryFacets, paste, collapse = ", ") |>
setNames(pid_ffbf_domains$Domain)
#> Negative affectivity
#> "Emotional Lability, Anxiousness, Separation Insecurity"
#> Detachment
#> "Withdrawal, Anhedonia, Intimacy Avoidance"
#> Antagonism
#> "Manipulativeness, Deceitfulness, Grandiosity"
#> Disinhibition
#> "Irresponsibility, Impulsivity, Distractibility"
#> Psychoticism
#> "Unusual Beliefs & Experiences, Eccentricity, Perceptual Dysregulation"
#> Disinhibited Aggression
#> "Emotional Lability, Hostility, Impulsivity"
#> Insecurity
#> "Separation Insecurity, Anxiousness, Perceptual Dysregulation"The seven domains share facets, so they are not independent. The four-factor structure comes from one exploratory study of 199 male prisoners, and the authors leave open whether Insecurity is needed.
The paper prints no missing-data rule. Under the default
missing = "apa", the package applies the same rule as for
the SF: a facet with 1 of its 4 items missing is prorated and rounded, a
facet with 2 or more missing is NA, and a domain with an
NA facet is NA. The authors’ analysis code
scores missing items differently: it does not round, so a facet with 1
item missing is the plain mean of the other 3, and it averages a domain
over its 12 items when up to 3 of them are missing, wherever they fall.
So with missing data a facet can differ by up to 1/12 from the authors’
scoring, a domain value can differ when one of its facets is prorated,
and a domain can be NA where theirs has a value;
?score_pid5 gives the details.
Informant data use the same version. The paper averaged the responses
of two informants item by item before scoring, and the authors’ code
uses one informant’s response where the other is missing. Do that step
first, for example with
rowMeans(cbind(x1, x2), na.rm = TRUE) for each item, then
set an item that both informants skipped (NaN from that
call) to NA. reliability_pid5(),
rename_pid5_items() and label_pid5() also take
version = "FFBF", and label_pid5() labels
items with the English self-report text, for informant data too. No FFBF
norms or validity keys are published, so validity_pid5(),
norm_pid5() and plot_pid5() do not take it.
The FFBF facet and APA domain columns carry the SF’s names, so
norm_pid5() would convert them as
version = "SF" with no version warning and
plot_pid5() would plot the result; do not do that, as it
compares them with SF self-report norms from another population.
The PID-5 Child Forms
The APA also publishes child forms of the PID-5 and the PID-5-BF for
ages 11 to 17 (Krueger, Derringer, Markon, Watson & Skodol, 2013,
The Personality Inventory for DSM-5 (PID-5)—Child Age 11–17 and
The Personality Inventory for DSM-5—Brief Form (PID-5-BF)—Child Age
11–17, American Psychiatric Association). They print the adult
forms’ items in the same order, and their scoring keys reverse the same
items and assign them to the same facets and domains. The full child
form’s instructions add a label and put “right” and “wrong” in quotation
marks, and the brief child form’s instructions are the adult paragraph.
So score_pid5() scores the 220-item child form with
version = "FULL" and the 25-item child form with
version = "BF", exactly as it scores the adult forms.
generate_docx_pid5child(),
generate_qualtrics_pid5child() and
generate_redcap_pid5child() write the full child form, and
the pid5bfchild functions write the brief one. Their online
exports use the adult item names (Qualtrics PID5_001 to
PID5_220 and PID5BF_01 to
PID5BF_25, REDCap pid5_001 to
pid5_220 and pid5bf_01 to
pid5bf_25), so child data need no renaming before scoring.
The package has no child norms, so do not pass child scores to
norm_pid5() or plot_pid5(). No source on the
package’s shelf gives validity cut scores for the child forms either, so
the cut scores validity_pid5() applies are not established
for child data.
Collecting Responses Online with hitop-form
hitop-form is an online form that shows the PID-5, the PID-5-SF or the PID-5-BF in the browser. It needs no survey platform. The item text, response options and instructions come from this package’s JSON export of the form. A study link tells the online form where each participant’s answers go: a file saved on the participant’s own device, which they send to you, or a Google Sheet, another web address or a Supabase table the link names. In the Study Link Builder, “A Google Sheet” is its own choice, with the sheet’s setup steps. You download the sheet or the table as one CSV file. The Collecting Responses Online article walks the Google Sheet route end to end.
To make a study link, open the Study Link Builder. Choose the form, name the study and choose where the responses go. To give a participant identifier, open the closed “Participants and recruiting site” section. Press “Make the link”, and the link shows under “Your study link”. Send it to the participant.
Whichever route the answers take, the CSV file has five lead columns
(study, participant, instrument,
form_build, submitted), a sixth,
item_order, when the link asked for a random order, two
more, prolific_study and prolific_session,
when the link recruits through Prolific, and then one column per item.
The item columns are named as this package names them:
pid5_001 to pid5_220, pid5sf_001
to pid5sf_100, or pid5bf_01 to
pid5bf_25. Each holds the value of the chosen option, 0 to
3. A file the online form saved holds one participant’s row; the
download of a sheet or a table holds one row per participant.
read_form_responses() reads a folder of these files, or
a vector of their paths, into one data frame with one row per response
row of each file. Read the files of each form in a call of their own.
The file below is one the online form saved from the full form. The
package installs it as an example, and system.file() gives
its path.
path <- system.file("examples", "responses-pid5.csv", package = "hitop")
responses <- read_form_responses(path)
responses
#> # A tibble: 1 × 228
#> study participant instrument form_build submitted item_order
#> <chr> <chr> <chr> <chr> <dttm> <chr>
#> 1 fixture p001 pid5 2026-09-20 2026-09-21 02:39:01 NA
#> # ℹ 222 more variables: prolific_study <chr>, prolific_session <chr>,
#> # pid5_001 <int>, pid5_002 <int>, pid5_003 <int>, pid5_004 <int>,
#> # pid5_005 <int>, pid5_006 <int>, pid5_007 <int>, pid5_008 <int>,
#> # pid5_009 <int>, pid5_010 <int>, pid5_011 <int>, pid5_012 <int>,
#> # pid5_013 <int>, pid5_014 <int>, pid5_015 <int>, pid5_016 <int>,
#> # pid5_017 <int>, pid5_018 <int>, pid5_019 <int>, pid5_020 <int>,
#> # pid5_021 <int>, pid5_022 <int>, pid5_023 <int>, pid5_024 <int>, …The item columns are integers and follow the form’s item order, so
pass them to score_pid5() by name with the matching
version ("FULL", "SF" or
"BF"):
items <- grep("^pid5_", names(responses), value = TRUE)
score_pid5(responses, items = items, version = "FULL", append = FALSE)
#> # A tibble: 1 × 30
#> pid_anhedonia pid_suspiciousness pid_riskTaking pid_impulsivity
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1.88 1.86 1.43 1
#> # ℹ 26 more variables: pid_eccentricity <dbl>, pid_distractibility <dbl>,
#> # pid_restrictedAffectivity <dbl>, pid_submissiveness <dbl>,
#> # pid_withdrawal <dbl>, pid_callousness <dbl>,
#> # pid_separationInsecurity <dbl>, pid_attentionSeeking <dbl>,
#> # pid_emotionalLability <dbl>, pid_depressivity <dbl>, pid_hostility <dbl>,
#> # pid_irresponsibility <dbl>, pid_rigidPerfectionism <dbl>,
#> # pid_perceptualDysregulation <dbl>, pid_grandiosity <dbl>, …