Background

Despite the prevalence of sleep complaints among psychiatric patients, few questionnaires have been specifically designed to measure sleep quality in clinical populations. The Pittsburgh Sleep Quality Index (PSQI) is a self-rated questionnaire which assesses sleep quality and disturbances over a 1-month time interval. Nineteen individual items generate seven “component” scores: subjective sleep quality, sleep latency, sleep duration, habitual sleep efficiency, sleep disturbances, use of sleeping medication, and daytime dysfunction. The sum of scores for these seven components yields one global score.

Scoring

component name description
1 subjective sleep quality Answer to q 6
2 sleep latency Scaled sum of number of minutes before sleep (q 2) and evaluation of sleep within 30min (q 5a), scaled to a 5 point scale
3 sleep duration Scaled score of number of hours before one falls asleep (q 4), scaled to a 5 point scale
4 habitual sleep efficiency hours of sleep (q 4) divided by bedtime (q 1) subtracted from rising time (q 3), and scaled to a 5 point scale
5 sleep disturbances Sum of evaluation of sleep within 30min (q 5a) and all remaining questions on sleep problems (q 5b-j), scaled to a 5 point scale
6 use of sleeping medication Answer to question on use of sleep medication (q 7)
7 daytime dysfunction Sum of evaluation of staying awake (q 8) and evaluation of keeping enthusiastic (q 9), scaled to a 5-point scale
global score sum of the above. If any of the above is not possible to calculate, the global sum is also not calculated

Data requirements

Column names

Questions with multiple subquestions should be named in a similar manner, suffixed by the alphabetical index (psqi_5a, psqi_5b etc.). For questions 5j and 10j, the frequency of occurence should have the names psqi_5j and psqi_10e, and the freehand explanations should have any type of suffix after this to indicate a text answers (i.e. psqi_5j_Desc or psqi_5j_string, psqi_5j_freehand). As an example, LCBC has the following set-up:

  • psqi_1
  • psqi_2
  • psqi_3
  • psqi_4
  • psqi_5a psqi_5b psqi_5c psqi_5d psqi_5e psqi_5f psqi_5g psqi_5h psqi_5i psqi_5j psqi_5j_Coded psqi_5j_Desc
  • psqi_6
  • psqi_7
  • psqi_8
  • psqi_9
  • psqi_10 psqi_10a psqi_10b psqi_10c psqi_10d psqi_10e psqi_10e_Desc psqi_10e_Coded
  • psqi_11a psqi_11b psqi_11c psqi_11d

4-option questions coding

All 4-option questions need to be coded 0-3, not 1-4.

Time formats

For question 1, 3 and 4 (bedtime, rising time, hours of sleep), data should be punched as “HH:MM”. Question 2 should be punched as minutes in numbers.

Use the psqi functions

All the functions necessary to compute the components and the global score for the psqi are included in this package, and loading it in your R-environment makes them accessible to you. All the functions for the PSQI, are prefixed with psqi_, so they are easy to find.

There are a total of 7 psqi_ functions in this package. One for each component, except 1 and 6 which are just the raw answers from questions 4 and 7. In addition, there is a function to calculate the global, which must be done after having computed the 7 components. Lastly, there is a function psqi_compute which will do it all for you, compute all the components and the global. All the functions are documented as any function in R, and so you may call ?psqi_compute to see the user documentation for the functions.

There are many options you can utilize for these functions, so you may explore them as you wish. The functions are also made such that you can use them on datasets other than the LCBC MOAS dataset, but you will then have to specify the columns to use for the various components your self. The functions are made to work directly with the MOAS, and require no extra input other that the object containing the MOAS to run all calculations. Running psqi_compute(MOAS) will calculate all components and append them to the data you provided. Running psqi_compute(MOAS, keep_all = FALSE) will only return the computed components and the global score in a data frame.

library(questionnaires)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

# Create some data to test on
data <- data.frame(
  psqi_01 = c("22:30:00", "07:00:00", "22:30:00", "NaN", "23:30:00"), 
  psqi_02 = c(5, 10, 30, 20, 30), 
  psqi_03 = c("05:50:00", "17:00:00", "06:30:00", "NaN", "06:45:00"), 
  psqi_04 = c(7, 9.75, 8, 9, 6), 
  psqi_05a = c(0L, 0L, 1L, 2L, 2L), 
  psqi_05b = c(0L, 0L, 2L, 0L, 2L), 
  psqi_05c = c(0L, 0L, 3L, 1L, 2L), 
  psqi_05d = c(0L, 0L, 0L, 0L, 0L), 
  psqi_05e = c(0L, 3L, 1L, 0L, 0L), 
  psqi_05f = c(0L, 0L, 1L, 0L, 1L),
  psqi_05g = c(0L, 0L, 1L, 1L, 3L), 
  psqi_05h = c(0L, 0L, 0L, 0L, 0L), 
  psqi_05i = c(0L, 0L, 0L, 0L, 0L), 
  psqi_05j_Coded = c(NA,NA, "No", NA, NA), 
  psqi_05j = c(0L, 0L, NA, NA, NA), 
  psqi_06 = c(0L, 1L, 2L, 1L, 1L), 
  psqi_07 = c(0L, 0L, 0L, 0L, 0L), 
  psqi_08 = c(0L, 0L, 0L, 1L, 0L), 
  psqi_09 = c(0L, 0L, 1L, 0L, 1L), 
  psqi_10 = c(3L, 1L, 3L, NA, 3L), 
  psqi_10a = c(1L, NA, 1L, NA, 0L), 
  psqi_10b = c(0L, NA, 0L, NA, 0L), 
  psqi_10c = c(2L, NA, 2L, NA, 0L), 
  psqi_10d = c(0L, NA, 0L, NA, 0L), 
  psqi_10e_Coded = c(NA, NA, "", NA, NA), 
  psqi_10e = c(NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_), 
  psqi_11a = c(0L, 2L, 1L, 1L, 2L), 
  psqi_11b = c(0L, 0L, 2L, 0L, 2L), 
  psqi_11c = c(0L, 0L, 1L, 1L, 2L), 
  psqi_11d = c(0L, 3L, 2L, 0L, 2L)
)

data
#>    psqi_01 psqi_02  psqi_03 psqi_04 psqi_05a psqi_05b psqi_05c psqi_05d
#> 1 22:30:00       5 05:50:00    7.00        0        0        0        0
#> 2 07:00:00      10 17:00:00    9.75        0        0        0        0
#> 3 22:30:00      30 06:30:00    8.00        1        2        3        0
#> 4      NaN      20      NaN    9.00        2        0        1        0
#> 5 23:30:00      30 06:45:00    6.00        2        2        2        0
#>   psqi_05e psqi_05f psqi_05g psqi_05h psqi_05i psqi_05j_Coded psqi_05j psqi_06
#> 1        0        0        0        0        0           <NA>        0       0
#> 2        3        0        0        0        0           <NA>        0       1
#> 3        1        1        1        0        0             No       NA       2
#> 4        0        0        1        0        0           <NA>       NA       1
#> 5        0        1        3        0        0           <NA>       NA       1
#>   psqi_07 psqi_08 psqi_09 psqi_10 psqi_10a psqi_10b psqi_10c psqi_10d
#> 1       0       0       0       3        1        0        2        0
#> 2       0       0       0       1       NA       NA       NA       NA
#> 3       0       0       1       3        1        0        2        0
#> 4       0       1       0      NA       NA       NA       NA       NA
#> 5       0       0       1       3        0        0        0        0
#>   psqi_10e_Coded psqi_10e psqi_11a psqi_11b psqi_11c psqi_11d
#> 1           <NA>       NA        0        0        0        0
#> 2           <NA>       NA        2        0        0        3
#> 3                      NA        1        2        1        2
#> 4           <NA>       NA        1        0        1        0
#> 5           <NA>       NA        2        2        2        2

Component-wise calculations

There are 7 components in the PSQI, and the functions in the package allow you to calculate 5 components based on two or more columns in the data. Two components do not have their own functions as they are just the raw answers to two questions.

Each component has unique arguments needed to calculate.

psqi_compute_comp2(min_before_sleep = data$psqi_02, 
                   no_sleep_30min = data$psqi_05a)
#> [1] 0 0 1 2 2

psqi_compute_comp3(hours_sleep = data$psqi_04)
#> [1] 1 0 0 0 1

psqi_compute_comp4(hours_sleep = data$psqi_04, 
                   bedtime = data$psqi_01, 
                   risingtime = data$psqi_03)
#> [1] NA NA NA NA NA

# Requires many columns, so for conveniece must be specified in another way than others.
psqi_compute_comp5(data = data, 
                   sleep_troubles = matches("^psqi_05[b-j]$"))
#> [1] 0 1 1 1 1

psqi_compute_comp7(keep_awake = data$psqi_08, 
                   keep_enthused = data$psqi_09)
#> [1] 0 0 1 1 1

If you want to add these directly to your data.frame, you can wrap then insire a dplyr::mutate.

data2 <- data %>% 
  mutate(
    comp1 = psqi_06,
    comp2 = psqi_compute_comp2(min_before_sleep = psqi_02, 
                                no_sleep_30min = psqi_05a),
    comp3 = psqi_compute_comp3(hours_sleep = psqi_04),
    comp4 = psqi_compute_comp4(hours_sleep = psqi_04, 
                               bedtime = psqi_01, 
                               risingtime = psqi_03),
    comp5 = psqi_compute_comp5(data = data, 
                               sleep_troubles = matches("^psqi_05[b-j]$")),
    comp6 = psqi_07,
    comp7 = psqi_compute_comp7(keep_awake = psqi_08, 
                               keep_enthused = psqi_09)
  )
data2
#>    psqi_01 psqi_02  psqi_03 psqi_04 psqi_05a psqi_05b psqi_05c psqi_05d
#> 1 22:30:00       5 05:50:00    7.00        0        0        0        0
#> 2 07:00:00      10 17:00:00    9.75        0        0        0        0
#> 3 22:30:00      30 06:30:00    8.00        1        2        3        0
#> 4      NaN      20      NaN    9.00        2        0        1        0
#> 5 23:30:00      30 06:45:00    6.00        2        2        2        0
#>   psqi_05e psqi_05f psqi_05g psqi_05h psqi_05i psqi_05j_Coded psqi_05j psqi_06
#> 1        0        0        0        0        0           <NA>        0       0
#> 2        3        0        0        0        0           <NA>        0       1
#> 3        1        1        1        0        0             No       NA       2
#> 4        0        0        1        0        0           <NA>       NA       1
#> 5        0        1        3        0        0           <NA>       NA       1
#>   psqi_07 psqi_08 psqi_09 psqi_10 psqi_10a psqi_10b psqi_10c psqi_10d
#> 1       0       0       0       3        1        0        2        0
#> 2       0       0       0       1       NA       NA       NA       NA
#> 3       0       0       1       3        1        0        2        0
#> 4       0       1       0      NA       NA       NA       NA       NA
#> 5       0       0       1       3        0        0        0        0
#>   psqi_10e_Coded psqi_10e psqi_11a psqi_11b psqi_11c psqi_11d comp1 comp2 comp3
#> 1           <NA>       NA        0        0        0        0     0     0     1
#> 2           <NA>       NA        2        0        0        3     1     0     0
#> 3                      NA        1        2        1        2     2     1     0
#> 4           <NA>       NA        1        0        1        0     1     2     0
#> 5           <NA>       NA        2        2        2        2     1     2     1
#>   comp4 comp5 comp6 comp7
#> 1    NA     0     0     0
#> 2    NA     1     0     0
#> 3    NA     1     0     1
#> 4    NA     1     0     1
#> 5    NA     1     0     1

Global calculation

After having all components calculated, the global is the sum of all these. The psqi_compute_global function takes an entire data frame, and a tidy-selected collection of columns of the calculated components. In this case we called the components with names starting with “Comp”, and can use this logic for the computation.

psqi_compute_global(data2, starts_with("comp"))
#> [1] NA NA NA NA NA

We get one NA because someone has omitted to answer to a question, and so a component is missing, and thus a global cannot be computed. You have the option to specify how many missing components the global calculation will allow, but remember that this will skew the data somewhat.

psqi_compute_global(data2, starts_with("comp"), max_missing = 2)
#> [1] 1.166667 2.333333 5.833333 5.833333 7.000000

Calculating all components and global in one go

It is quite cumbersome to mutate and fix every component manually, so the psqi_compute function is there to make everything in one go. If you are working on MOAS-like data, this is easy to use, as column names and specifications are pre-set to work with the MOAS. If you are working on other data, you will need to set each option to the column names in the data for each question manually.

psqi_compute(data)
#>    psqi_01 psqi_02  psqi_03 psqi_04 psqi_05a psqi_05b psqi_05c psqi_05d
#> 1 22:30:00       5 05:50:00    7.00        0        0        0        0
#> 2 07:00:00      10 17:00:00    9.75        0        0        0        0
#> 3 22:30:00      30 06:30:00    8.00        1        2        3        0
#> 4      NaN      20      NaN    9.00        2        0        1        0
#> 5 23:30:00      30 06:45:00    6.00        2        2        2        0
#>   psqi_05e psqi_05f psqi_05g psqi_05h psqi_05i psqi_05j_Coded psqi_05j psqi_06
#> 1        0        0        0        0        0           <NA>        0       0
#> 2        3        0        0        0        0           <NA>        0       1
#> 3        1        1        1        0        0             No       NA       2
#> 4        0        0        1        0        0           <NA>       NA       1
#> 5        0        1        3        0        0           <NA>       NA       1
#>   psqi_07 psqi_08 psqi_09 psqi_10 psqi_10a psqi_10b psqi_10c psqi_10d
#> 1       0       0       0       3        1        0        2        0
#> 2       0       0       0       1       NA       NA       NA       NA
#> 3       0       0       1       3        1        0        2        0
#> 4       0       1       0      NA       NA       NA       NA       NA
#> 5       0       0       1       3        0        0        0        0
#>   psqi_10e_Coded psqi_10e psqi_11a psqi_11b psqi_11c psqi_11d
#> 1           <NA>       NA        0        0        0        0
#> 2           <NA>       NA        2        0        0        3
#> 3                      NA        1        2        1        2
#> 4           <NA>       NA        1        0        1        0
#> 5           <NA>       NA        2        2        2        2
#>   psqi_comp1_quality psqi_comp2_latency psqi_comp3_duration
#> 1                  0                  0                   1
#> 2                  1                  0                   0
#> 3                  2                  1                   0
#> 4                  1                  2                   0
#> 5                  1                  2                   1
#>   psqi_comp4_efficiency psqi_comp5_problems psqi_comp6_medication
#> 1                    NA                   0                     0
#> 2                    NA                   1                     0
#> 3                    NA                   1                     0
#> 4                    NA                   1                     0
#> 5                    NA                   1                     0
#>   psqi_comp7_tired psqi_global
#> 1                0          NA
#> 2                0          NA
#> 3                1          NA
#> 4                1          NA
#> 5                1          NA

psqi_compute(data, keep_all = FALSE)
#>   psqi_comp1_quality psqi_comp2_latency psqi_comp3_duration
#> 1                  0                  0                   1
#> 2                  1                  0                   0
#> 3                  2                  1                   0
#> 4                  1                  2                   0
#> 5                  1                  2                   1
#>   psqi_comp4_efficiency psqi_comp5_problems psqi_comp6_medication
#> 1                    NA                   0                     0
#> 2                    NA                   1                     0
#> 3                    NA                   1                     0
#> 4                    NA                   1                     0
#> 5                    NA                   1                     0
#>   psqi_comp7_tired psqi_global
#> 1                0          NA
#> 2                0          NA
#> 3                1          NA
#> 4                1          NA
#> 5                1          NA

psqi_compute(data, keep_all = FALSE, max_missing = 1)
#>   psqi_comp1_quality psqi_comp2_latency psqi_comp3_duration
#> 1                  0                  0                   1
#> 2                  1                  0                   0
#> 3                  2                  1                   0
#> 4                  1                  2                   0
#> 5                  1                  2                   1
#>   psqi_comp4_efficiency psqi_comp5_problems psqi_comp6_medication
#> 1                    NA                   0                     0
#> 2                    NA                   1                     0
#> 3                    NA                   1                     0
#> 4                    NA                   1                     0
#> 5                    NA                   1                     0
#>   psqi_comp7_tired psqi_global
#> 1                0    1.166667
#> 2                0    2.333333
#> 3                1    5.833333
#> 4                1    5.833333
#> 5                1    7.000000

References

Buysse et al. (1989) The Pittsburgh sleep quality index: A new instrument for psychiatric practice and research, Psychiatry Research, 28:2, 193-213