Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

# WrightMap Tutorial 4 – More Flexibility

## Using the person and item side functions

### Introduction

Version 1.2 of the WrightMap package allows you to directly access the functions used for drawing the person and item sides of the map in order to allow more flexible item person maps. The parts can be put together on the same plot using the split.screen function.

### Calling the functions

Let’s start by installing the latest version of the package from CRAN.

install.packages('WrightMap')
library(WrightMap)

And set up some item data.

items.loc <- sort( rnorm( 20))
thresholds <- data.frame(
l1 = items.loc - 0.5 ,
l2 = items.loc - 0.25,
l3 = items.loc + 0.25,
l4 = items.loc + 0.5)

We can draw a simple item map by calling one of the item side functions. Currently there are three: itemModern, itemClassic, and itemHist.

The itemModern function is the default called by wrightMap.

itemModern(thresholds)

The itemClassic function creates item sides inspired by text-based Wright Maps.

itemClassic(thresholds)

Finally, the itemHist function plots the items as a histogram.

itemHist(thresholds)

Similarly, the person side functions allow you to graph the person parameters. There are two, personHist and personDens.

## Mock results
multi.proficiency <- data.frame(
d1 = rnorm(1000, mean =  -0.5, sd = .5),
d2 = rnorm(1000, mean =   0.0, sd = 1),
d3 = rnorm(1000, mean =  +0.5, sd = 1),
d4 = rnorm(1000, mean =   0.0, sd = .5),
d5 = rnorm(1000, mean =  -0.5, sd = .75))
personHist(multi.proficiency)

personDens(multi.proficiency)

To use these plots in a Wright Map, use the item.side and person.side parameters.

wrightMap(multi.proficiency,thresholds,item.side = itemClassic,person.side = personDens)

### Use with CQmodel: The personData and itemData functions

The person side and item side functions are expecting data in the form of matrices. They do not recognize CQmodel objects. When a CQModel object is sent to wrightMap, it first extracts the necessary data, and then sends the data to the plotting functions. In 1.2, the data processing functions have also been made directly accessible to users in the form of the personData and itemData functions. These are fast ways to pull the data out of a CQmodel object in such a way that it is ready to be sent to wrightMap or any of the item and person plotting functions.

The personData function is very simple. It can take either a CQmodel object or a string containing the name of a ConQuest person parameter file. It extracts the person estimates as a matrix.

fpath <- system.file("extdata", package="WrightMap")
model1 <- CQmodel(file.path(fpath,"ex7a.eap"), file.path(fpath,"ex7a.shw"))
head(model1$p.est) ## casenum est (d1) error (d1) pop (d1) est (d2) error (d2) pop (d2) ## 1 1 1.37364 0.70308 0.60309 1.73654 0.60556 0.52928 ## 2 2 -0.17097 0.64866 0.66216 0.75620 0.54852 0.61379 ## 3 3 0.46677 0.64837 0.66246 0.85146 0.55129 0.60987 ## 4 4 0.67448 0.66017 0.65006 1.16098 0.56368 0.59214 ## 5 5 0.89717 0.67704 0.63195 1.49079 0.58539 0.56012 ## 6 6 1.64704 0.72529 0.57762 2.11784 0.62916 0.49188 m1.person <- personData(model1) head(m1.person) ## d1 d2 ## 1 1.37364 1.73654 ## 2 -0.17097 0.75620 ## 3 0.46677 0.85146 ## 4 0.67448 1.16098 ## 5 0.89717 1.49079 ## 6 1.64704 2.11784 personHist(m1.person,dim.lab.side = 1) The itemData function uses the GIN table (Thurstonian thresholds) if it is there, and otherwise tries to create delta parameters out of the RMP tables. You can also specify tables to use as items, steps, and interactions, and it will add them together appropriately to create delta parameters. model2 <- CQmodel(file.path(fpath,"ex4a.mle"), file.path(fpath,"ex4a.shw")) names(model2$RMP)
## [1] "rater"                     "topic"
## [3] "criteria"                  "rater*topic"
## [5] "rater*criteria"            "topic*criteria"
## [7] "rater*topic*criteria*step"
m2.item <- itemData(model2,item.table = "topic", interactions = "rater*topic", step.table = "rater")
itemModern(m2.item)

See Tutorial 3 for details on specifying tables from CQmodel objects.

Having these data functions pulled out also makes it easier to combine parameters from different models onto a single plot (when appropriate).

wrightMap(m1.person,m2.item)

### Putting it all together with split.screen

By calling these functions directly and using, we can make Wright Maps with other arrangements of persons and items. The item side functions can be combined using any of the base graphics options for combining plots (layout, par(mfrow)), but the person side functions are based on split.screen, which is incompatible with those options. We will be combining item and person maps, so we need to use split.screen.

The first step of combining these functions is to set up the screens. Details for screen functions are in the documentation for split.screen. The function takes as a parameter a 4-column matrix, in which each row is a screen, and the columns represent the left, bottom, right, and top of the screens respectively. Each value is expressed as a number from 0 to 1, where 0 is the left/bottom of the current device and 1 is the right/top.

To make a Wright Map with the items on the left and the persons on the right, we will set up two screens, with 80% of the width on the left and 20% on the right.

split.screen(figs = matrix(c(0,.8,0,1
,.8,1,0,1),ncol = 4, byrow = TRUE)))

Next, we’ll draw the item side. IMPORTANT NOTE: Make sure to explicitly set the yRange variable when combining plots to ensure they are on the same scale. We can also adjust some of the other parameters to work better with a left-side item plot. We’ll move the logit axis to the left with the show.axis.logit parameter, and set the righthand outer margin to 2 to give us a space between the plots.

itemModern(thresholds, yRange = c(-3,4), show.axis.logits = "L", oma = c(0,0,0,2))

We can also add a title at this time.

mtext("Wright Map", side = 3, font = 2, line = 1)

Finally, we will move to screen 2 and draw the person side. This plot will be adjusted to move the persons label and remove the axis.

screen(2)
personHist(multi.proficiency, axis.persons = "",yRange = c(-3,4)
, axis.logits = "Persons", show.axis.logits = FALSE)

The last thing to do is to close all the screens to prevent them from getting in the way of any future plotting.

close.screen(all.screens = TRUE)

Here is the complete plot:

 split.screen(figs = matrix(c(0,.8,0,1,.8,1,0,1),ncol = 4, byrow = TRUE))
itemModern(thresholds, yRange = c(-3,4), show.axis.logits = "L", oma = c(0,0,0,2))
mtext("Wright Map", side = 3, font = 2, line = 1)
screen(2)
personHist(multi.proficiency, axis.persons = "",yRange = c(-3,4)
, axis.logits = "Persons", show.axis.logits = FALSE)
close.screen(all.screens = TRUE)

Countless arrangements are possible. As one last example, here are two ways to put two dimensions put side by side in separate Wright Maps.

Explicitly splitting the device into four screens:

  d1 = rnorm(1000, mean =  -0.5, sd = 1)
d2 = rnorm(1000, mean =   0.0, sd = 1)

dim1.diff <- rnorm(5)
dim2.diff <- rnorm(5)

split.screen(figs = matrix(c(0,.09,0,1,
.11,.58,0,1,
.5,.59,0,1,
.51,1,0,1),ncol = 4,byrow = TRUE))

personDens(d1,yRange = c(-3,3),show.axis.logits = FALSE
, axis.logits = "")
screen(2)
itemModern(dim1.diff,yRange = c(-3,3),show.axis.logits = FALSE)
mtext("Wright Map", side = 3, font = 2, line = 1)
screen(3)
personDens(d2,yRange = c(-3,3),show.axis.logits = FALSE
, axis.logits = ""
, axis.persons = "",dim.names = "Dim2")
screen(4)
itemModern(dim2.diff,yRange = c(-3,3),show.axis.logits = FALSE
, label.items = paste("Item",6:10))

    close.screen(all.screens = TRUE)

Splitting the device into two screens with a Wright Map on each:

    split.screen(figs = matrix(c(0,.5,0,1,
.5,1,0,1),ncol = 4,byrow = TRUE))

wrightMap(d1,dim1.diff,person.side = personDens,show.axis.logits = FALSE)
screen(2)
wrightMap(d2,dim2.diff,person.side = personDens,show.axis.logits = FALSE)
close.screen(all.screens = TRUE)