4  Möjlighet att påverka

Code
library(foreign)
library(readxl)
library(RISEkbmRasch) # devtools::install_github("pgmj/RISEkbmRasch")
library(grateful) # devtools::install_github("Pakillo/grateful")
library(ggrepel)
library(car)
library(kableExtra)
library(readxl)
library(tidyverse)
library(eRm)
library(mirt)
library(psych)
library(ggplot2)
library(psychotree)
library(matrixStats)
library(reshape)
library(knitr)
library(cowplot)
library(formattable) 
library(glue)
library(hexbin)
library(skimr)


### some commands exist in multiple packages, here we define preferred ones that are frequently used
select <- dplyr::select
count <- dplyr::count
recode <- car::recode
rename <- dplyr::rename

# file paths will need to have "../" added at the beginning to be able to render document

# get itemlabels
itemlabels <- read_excel("../data/Itemlabels.xlsx") %>% 
  filter(str_detect(itemnr, pattern = "mp")) %>% 
  select(!Dimension)

spssDatafil <- "../data/2023-04-26 Prevent OSA-enkat.sav"

# read SurveyMonkey data
df <- read.spss(spssDatafil, to.data.frame = TRUE) %>% 
  select(starts_with("q0007"),q0001,q0002,q0003,q0004) %>% 
  rename(Kön = q0002,
         Ålder = q0001,
         Bransch = q0003,
         Hemarbete = q0004)



# SPSS format provides itemlabels too, we can save them in a dataframe
spssLabels <- df %>% attr('variable.labels') %>% as.data.frame()

dif.kön <- df$Kön
dif.ålder <- df$Ålder
dif.bransch <- df$Bransch
dif.hemarbete <- df$Hemarbete

df <- df %>% 
  select(starts_with("q0007"))

names(df) <- itemlabels$itemnr

4.1 Items

Code
itemlabels %>% 
  kbl_rise(width = 60)
itemnr item
mp1 Jag har de resurser som behövs för att kunna utföra mina arbetsuppgifter.
mp2 Jag kan prioritera i vilken ordning mina arbetsuppgifter ska utföras.
mp3 Jag har tillräckligt med befogenheter för att kunna utföra mina arbetsuppgifter.
mp4 Jag kan påverka beslut som rör mina arbetsuppgifter.
mp5 Jag kan själv bestämma min arbetstakt.

4.2 Bakgrundsdata

Code
RIdemographics(dif.kön,"Kön")
Kön n Percent
Kvinna 402 69.7
Man 172 29.8
Annat 1 0.2
Vill ej uppge 2 0.3
Code
RIdemographics(dif.ålder,"Ålder") # kanske även fixa en figur?
Ålder n Percent
18-29 22 3.8
30-39 103 17.8
40-49 175 30.2
50-59 215 37.1
60+ 64 11.1
Code
RIdemographics(dif.bransch,"Bransch")
Bransch n Percent
Kontorsarbete (oavsett bransch) 284 49.1
Industri 26 4.5
Hotell, restaurang, service 8 1.4
Handel 16 2.8
Skola, utbildning 67 11.6
Vård, omsorg 99 17.1
Byggverksamhet 7 1.2
Annat 71 12.3
Code
RIdemographics(dif.hemarbete,"Antal dagar med arbete hemifrån")
Antal dagar med arbete hemifrån n Percent
En dag 136 23.5
Två dagar 105 18.1
Tre dagar 35 6.0
Fyra dagar 10 1.7
Fem dagar 2 0.3
Jag arbetar aldrig eller sällan hemifrån 291 50.3

4.2.1 Svarsbortfall items

Vi filtrerar bort respondenter som har färre än två svar på frågorna i delskalan.

Code
# If you want to include participants with missing data, input the minimum number of items responses that a participant should have to be included in the analysis:
min.responses <- 2
scale.items <- itemlabels$itemnr

# Select the variables we will work with, and filter out respondents with a lot of missing data
df.omit.na <- df %>% 
  filter(length(scale.items)-rowSums(is.na(.[scale.items])) >= min.responses)

RImissing(df.omit.na,"mp")

Vi har extremt få saknade svar, och tar därför bort respondenter som inte har kompletta svar.

Code
df <- read.spss(spssDatafil, to.data.frame = TRUE) %>% 
  select(starts_with("q0007"),q0001,q0002,q0003,q0004) %>% 
  rename(Kön = q0002,
         Ålder = q0001,
         Bransch = q0003,
         Hemarbete = q0004) %>% 
  na.omit()

dif.kön <- df$Kön
dif.ålder <- df$Ålder
dif.bransch <- df$Bransch
dif.hemarbete <- df$Hemarbete

df <- df %>% 
  select(starts_with("q0007"))

names(df) <- itemlabels$itemnr

df.omit.na <- df

4.2.2 Alla svar för alla frågor

Code
RIallresp(df.omit.na)
Response category Number of responses Percent
Alltid 542 21.6
Mycket ofta 1011 40.3
Ganska ofta 588 23.4
Ibland 241 9.6
Sällan 106 4.2
Aldrig 22 0.9
Code
# koda om svarskategorier till siffror
df.omit.na <- df.omit.na %>% 
  mutate(across(everything(), ~ car::recode(.x,"'Aldrig'=0;
                                            'Sällan' =1;
                                            'Ibland'=2;
                                            'Ganska ofta'=3;
                                            'Mycket ofta'=4;
                                            'Alltid'=5",
                                            as.factor = FALSE)))

4.2.3 Descriptives - all items

Code
RItileplot(df.omit.na)

Code
RIbarstack(df.omit.na) + ylab("Item")

Code
RIbarplot(df.omit.na)

Vi har väldigt få svar i lägsta svarskategorierna, speciellt för item mp1 och mp3. Vi slår därför ihop de två lägsta svarskategorierna för mp1 och mp3.

Code
df.omit.na$mp1<-recode(df.omit.na$mp1,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)
df.omit.na$mp3<-recode(df.omit.na$mp3,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)

4.3 Rasch-analys 1

itemnr item
mp1 Jag har de resurser som behövs för att kunna utföra mina arbetsuppgifter.
mp2 Jag kan prioritera i vilken ordning mina arbetsuppgifter ska utföras.
mp3 Jag har tillräckligt med befogenheter för att kunna utföra mina arbetsuppgifter.
mp4 Jag kan påverka beslut som rör mina arbetsuppgifter.
mp5 Jag kan själv bestämma min arbetstakt.
Code
RIitemfitPCM2(df.omit.na, samplesize = 250, nsamples = 32, cpu = 8)
OutfitMSQ InfitMSQ OutfitZSTD InfitZSTD
mp1 0.925 0.884 -0.698 -1.079
mp2 0.842 0.819 -1.683 -1.961
mp3 0.817 0.84 -1.88 -1.709
mp4 0.691 0.696 -3.581 -3.377
mp5 0.824 0.822 -2.019 -2.032
Code
RIpcmPCA(na.omit(df.omit.na))
PCA of Rasch model residuals
Eigenvalues
1.71
1.27
1.21
0.81
0.00
Code
RIresidcorr(df.omit.na, cutoff = 0.2)
mp1 mp2 mp3 mp4 mp5
mp1
mp2 -0.17
mp3 -0.23 -0.21
mp4 -0.32 -0.35 0.12
mp5 -0.15 -0.1 -0.48 -0.23
Note:
Relative cut-off value (highlighted in red) is -0.012, which is 0.2 above the average correlation.
Code
RIloadLoc(df.omit.na)

Code
RIitemCats(df.omit.na, items = "all")

Code
# increase fig-height above as needed, if you have many items
RItargeting(df.omit.na, xlim = c(-5,5))

Code
RIitemHierarchy(df.omit.na)

Det finns en residualkorrelation mellan mp3 och mp4 långt över det rekommenderade tröskelvärdet. Eftersom mp4 visar låg item fit exkluderas detta item från fortsatta analyser.

Det finns problem med oordnade svarskategorier. Vi slår samman de två lägsta svarskategorierna (T1 och T2 i targeting-figuren) för item 2 och de två näst lägsta (T2 och T3) för item 5.

Code
# create vector with eliminated items
removed_items <- c("mp4")

df.omit.na$mp2<-recode(df.omit.na$mp2,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)
df.omit.na$mp5<-recode(df.omit.na$mp5,"2=1;3=2;4=3;5=4",as.factor=FALSE)

# select all items except those removed
df2 <- df.omit.na %>%
  select(!any_of(removed_items))

4.3.1 Analysis of response categories

Code
RIitemCats(df2, items = "all")

4.4 Rasch-analys 2

itemnr item
mp1 Jag har de resurser som behövs för att kunna utföra mina arbetsuppgifter.
mp2 Jag kan prioritera i vilken ordning mina arbetsuppgifter ska utföras.
mp3 Jag har tillräckligt med befogenheter för att kunna utföra mina arbetsuppgifter.
mp5 Jag kan själv bestämma min arbetstakt.
Code
RIitemfitPCM(df2, samplesize = 250, nsamples = 32)
OutfitMSQ InfitMSQ OutfitZSTD InfitZSTD
mp1 0.792 0.756 -2.42 -2.948
mp2 0.718 0.71 -3.268 -3.282
mp3 0.903 0.887 -0.999 -1.202
mp5 0.725 0.707 -3.384 -3.669
Code
RIpcmPCA(na.omit(df2))
PCA of Rasch model residuals
Eigenvalues
1.45
1.39
1.15
0.01
Code
RIresidcorr(df2, cutoff = 0.2)
mp1 mp2 mp3 mp5
mp1
mp2 -0.32
mp3 -0.28 -0.26
mp5 -0.19 -0.14 -0.39
Note:
Relative cut-off value (highlighted in red) is -0.064, which is 0.2 above the average correlation.
Code
RIloadLoc(df2)

Code
# increase fig-height above as needed, if you have many items
RItargeting(df2)

Code
RIitemHierarchy(df2)

4.4.1 Targeting

Code
# increase fig-height above as needed, if you have many items
RItargeting(df2)

Figur 4.1: Targeting för området Möjlighet att påverka

Det finns inte längre något problem med residualkorrelationer. Flera items (mp1, mp2 och mp5) har något låg ZSTD item, medan MSQ ser ok ut. Vi går vidare och undersöker DIF och reliabilitet.

4.5 DIF-analysis

En DIF-analys bör inte inkludera svarskategorier med för få svar. Därför har svarskategorier med färre än 50 svar antingen exkluderas eller slagits ihop med andra svarskategorier (se avsnittet om bakgrundsdata för exakt antal svar per ordinarie kategori). DIF-variablerna omkodades till att innehålla följande kategorier:

  • Kön: man, kvinna
  • Ålder: 30-39, 40-49, 50-59, 60+
  • Bransch: kontorsarbete, ej kontorsarbete
  • Hemarbete: aldrig eller sällan, en dag, minst två dagar

Koden nedan specificerar exakt hur omkodningen gick till.

Code
# Omkodning för kön
dif.kön <- recode(dif.kön,"'Annat'=NA;'Vill ej uppge'=NA")

# Omkodning för ålder
dif.ålder <- recode(dif.ålder,"'18-29'=NA")

# Omkodning för bransch
dif.bransch <- recode(dif.bransch,"'Industri'='Ej kontorsarbete';'Hotell, restaurang, service'='Ej kontorsarbete';'Handel'='Ej kontorsarbete';'Skola, utbildning'='Ej kontorsarbete';'Vård, omsorg'='Ej kontorsarbete';'Byggverksamhet'='Ej kontorsarbete';'Annat'='Ej kontorsarbete'")

#Omkodning för hemarbete
dif.hemarbete <- recode(dif.hemarbete,"'Fem dagar'='Minst två dagar';'Fyra dagar'='Minst två dagar';'Tre dagar'='Minst två dagar';'Två dagar'='Minst två dagar'")
Code
RIdifTable(df2, dif.kön)
[1] "No significant DIF found."
Code
RIdifTable(df2, dif.ålder)
[1] "No significant DIF found."
Code
RIdifTable(df2, dif.bransch)
[1] "No significant DIF found."
Code
RIdifTable(df2, dif.hemarbete)
[1] "No significant DIF found."

Inga problem i DIF-analysen baserat på kön, ålder, bransch eller hemarbete.

4.6 Reliability

Code
RItif(df2, lo = -5, hi = 5)

Figur 4.2: Reliabilitet för området Möjlighet att påverka

Reliabiliteten kommer inte riktigt upp i önskad nivå, men eftersom det avsedda användningsområdet enbart är på gruppnivå kan detta vara tillräckligt nära.

4.7 Person fit

Code
RIpfit(df2)

4.8 Item-parametrar

Code
RIitemparams(df2)
Threshold 1 Threshold 2 Threshold 3 Threshold 4 Item location
mp1 -0.86 0.04 1.21 3.96 1.09
mp2 -1.33 -0.13 0.84 3.33 0.68
mp3 -0.82 -0.62 0.43 2.63 0.4
mp5 -2.37 0.70 1.46 3.75 0.88

4.9 Transformeringstabell

Code
RIscoreSE(df2, width = 50, score_range = c(-4,5))
Ordinal sum score Logit score Logit std.error
0 -3.726 1.646
1 -2.316 0.971
2 -1.606 0.765
3 -1.132 0.667
4 -0.757 0.616
5 -0.425 0.589
6 -0.109 0.580
7 0.208 0.581
8 0.537 0.593
9 0.888 0.614
10 1.272 0.645
11 1.710 0.688
12 2.227 0.744
13 2.838 0.807
14 3.531 0.887
15 4.354 1.046
16 5.000 1.264

4.9.1 Estimering av mätvärden

Code
df2$score <- RIestThetas(df2, theta_range = c(-4,5))
write.csv(df2, "scored.csv")
summary(df2$score)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-2.3163  0.8879  1.7096  1.8079  2.8383  5.0000 
Code
ggplot(
  data = df2,
  aes(x = score, y = 0, fill = factor(score))
  ) +
  geom_dotplot(color = "white", 
                dotsize = 1,
                binwidth = 0.12) +
   # plot mean value
  geom_point(
    aes(
      x = mean(score),
      y = 0
    ),
    size = 6,
    shape = 18,
    color = "black"
  ) +
    # errorbar has brackets at the endpoints
  geom_errorbar(
    aes(
      xmin = mean(score) - sd(score),
      xmax = mean(score) + sd(score),
      y = 0,
      width = 0.025
    ),
    color = "black"
  ) +
  theme_minimal(base_family = "Lato") +
  theme_rise() +
  coord_cartesian(xlim = c(-4,5)) +
  scale_color_viridis_d("Antal svar",
    begin = 0.2,
    aesthetics = c("fill", "color"),
    guide = "none"
  ) +
  labs(
    title = "Mätvärden",
    subtitle = "Värden längre till höger är bättre",
    caption = "Svart diamant indikerar medelvärde, strecken runt indikerar en standardavvikelse.",
    y = "Antal respondenter",
    x = "Mätvärden"
  ) +
  theme(
    #axis.text.x = element_blank(), # remove text from both axes
    axis.text.y = element_blank()
  )

4.10 Rekommenderade items

Code
itemlabels %>% 
  filter(itemnr %in% names(df2)) %>% 
  write_csv("finalItems.csv")

itemlabels %>% 
  filter(!itemnr %in% names(df2)) %>% 
  write_csv("removedItems.csv")

itemlabels %>% 
  kbl_rise(width = 50,
           options = "hover") %>% 
  row_spec(c(4), background = "lightpink") %>% 
  footnote(general_title = "Notera: ",
           "Borttagna item markerade med färg.")
itemnr item
mp1 Jag har de resurser som behövs för att kunna utföra mina arbetsuppgifter.
mp2 Jag kan prioritera i vilken ordning mina arbetsuppgifter ska utföras.
mp3 Jag har tillräckligt med befogenheter för att kunna utföra mina arbetsuppgifter.
mp4 Jag kan påverka beslut som rör mina arbetsuppgifter.
mp5 Jag kan själv bestämma min arbetstakt.
Notera:
Borttagna item markerade med färg.

4.10.1 Förändringar i svarskategorier

Code
df.omit.na$mp1<-recode(df.omit.na$mp1,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)
df.omit.na$mp3<-recode(df.omit.na$mp3,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)

df.omit.na$mp2<-recode(df.omit.na$mp2,"1=0;2=1;3=2;4=3;5=4",as.factor=FALSE)
df.omit.na$mp5<-recode(df.omit.na$mp5,"2=1;3=2;4=3;5=4",as.factor=FALSE)