WrightMap Tutorial 4 – More Flexibility Using the person and item side…

April 21, 2016

(This article was first published on R Snippets for IRT, and kindly contributed to R-bloggers)

WrightMap Tutorial 4 – More Flexibility

Using the person and item side functions


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.


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.


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


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


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))


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"))
##   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)
##         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"))
## [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")

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).


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.

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)
    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,
                                .51,1,0,1),ncol = 4,byrow = TRUE))
    personDens(d1,yRange = c(-3,3),show.axis.logits = FALSE
  , axis.logits = "")
    itemModern(dim1.diff,yRange = c(-3,3),show.axis.logits = FALSE)
    mtext("Wright Map", side = 3, font = 2, line = 1)
    personDens(d2,yRange = c(-3,3),show.axis.logits = FALSE
  , axis.logits = ""
  , axis.persons = "",dim.names = "Dim2")
    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)
    wrightMap(d2,dim2.diff,person.side = personDens,show.axis.logits = FALSE)
    close.screen(all.screens = TRUE)

To leave a comment for the author, please follow the link and comment on their blog: R Snippets for IRT.

R-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: Data science, Big Data, R jobs, visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...

If you got this far, why not subscribe for updates from the site? Choose your flavor: e-mail, twitter, RSS, or facebook...

Comments are closed.


Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)