---
title: "How Members of Different Groups Obtain the Same Test Score: Profile Plots in Dexter"
author: "Timo Bechger and Ivailo Partchev"
date: "`r Sys.Date()`"
bibliography: dexter.bib
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Profile plots in dexter}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

<style>
img{ border:none;}

div.crop{
  position:relative;
  overflow:hidden;
  width:220px;
  height:220px;
}
div.crop img{
  margin-left: -30px;
  margin-top: -45px;
}
</style>


```{r setup, include=FALSE}
library(knitr)
opts_chunk$set(echo = FALSE, message=FALSE)

if (requireNamespace("Cairo", quietly = TRUE)) 
{
   opts_chunk$set(dev='CairoPNG')
}
RcppArmadillo::armadillo_throttle_cores(1)

library(dexter)
library(latticeExtra)
fq = c(10,12,30,20,40,24,15,18,14)
fq=matrix(fq,3,3)
attr(fq,"dimnames")=list(c(0,1,2),c(0,1,2))
den = matrix(c(10,32,85,32,85,42,85,42,14),3,3)

blues = c("#005D88","#0086A3","#84AEBF","#C2D0D6","#E2E2E2")
reds = c("#972B2F","#A94C4F","#BD6F71","#D39A9B","#F7EFEF")

```

The function `profile_plot` in package dexter produces plots like the following one based on the verbal aggression dataset:

```{r, figProf,  results="hide",fig.height=4,fig.width=4,fig.align='center'}
# fig.align='center' does not work anymore when you specify any css styles
# therefore the: display:block;margin: 0 auto;
db = start_new_project(verbAggrRules, ":memory:", person_properties=list(gender="unknown"))
add_booklet(db, verbAggrData, "agg")
add_item_properties(db, verbAggrProperties)
profile_plot(db, item_property='mode', covariate='gender', booklet_id=='agg')
```


The plot is, as we will try to show, quite informative. However, it is not very easy to understand as it is very condensed and contains some less familiar elements. Furthermore, experience shows that persons who are at ease with complicated formulae or tables may be challenged by a relatively simple graph, or vice versa. May this serve as an excuse for the very elementary level of this vignette.

Let us assume a test of four dichotomous items. The possible sum scores are then 0 through 4. Let the four items be classified into two non-overlapping domains, A and B, with two items each. The following table could be the frequency distribution of a sample of students by their scores on the two domains:

```{r frq}
rownames(fq) = c(0,1,2)
colnames(fq) = c(0,1,2)
knitr::kable(fq)
```



We never expected to use a 3-dimensional bar plot, ever, but we will make a once-in-a-lifetime exception:

<div class='crop' style='margin:auto'>
```{r, fig1, fig.align='center', out.extra='style="margin-left: -30px; margin-top: -45px;"'}
cloud(fq, panel.3d.cloud = panel.3dbars,
      xbase = 0.4, ybase = 0.4, zlim = c(0, max(fq)),
      scales = list(arrows = FALSE, just = "right"), xlab = "A", ylab = "B",
      col.facet =  "tan",
      zlab='',
      pretty=TRUE,
      par.box = list(lty=0),
      screen = list(z = 40, x = -30))
```
</div>

The well-known function, prop.table, can be used to estimate three kinds of probabilities: 

* the joint probabilities for each combination of A and B, which sum to 1 over the whole table;
* the conditional probabilities of A given B, which sum to 1 over each row, and
* the conditional probabilities of B given A, which sum to 1 over each column.

The first and the third of these possibilities are illustrated below:

<div style='text-align:center;margin-top:1em;'>
<div style='display:inline-block;text-align: center;'>
Joint
<div class='crop'>
```{r, fig21,  out.extra='style="margin-left: -30px; margin-top: -50px;"'}
joint = prop.table(fq)

cloud(joint, panel.3d.cloud = panel.3dbars,
      xbase = 0.4, ybase = 0.4, zlim = c(0, 1),
      scales = list(arrows = FALSE, just = "right"), 
      xlab = "A", ylab = "B", zlab ='',
      pretty=TRUE,
      par.box = list(lty=0),
      col.facet =  "skyblue", alpha.facet=.7,
      screen = list(z = 40, x = -30))
```
</div></div>
<div style='display:inline-block;text-align: center;margin-left:3em;'>
Conditional
<div class='crop'>
```{r, fig22,  out.extra='style="margin-left: -30px; margin-top: -50px;"'}

conditional = prop.table(fq,1)
co = rep(c('skyblue1','pink1','springgreen1'),3)

cloud(conditional, panel.3d.cloud = panel.3dbars,
      xbase = 0.4, ybase = 0.4, zlim = c(0, 1),
      scales = list(arrows = FALSE, just = "right"), 
      xlab = "A", ylab = "B", zlab ='',
      pretty=TRUE,
      par.box = list(lty=0),
      col.facet =  co, alpha.facet=.7,
      screen = list(z = 40, x = -30))
```
</div></div></div>

The profile plot does not use any of these three kinds of probabilities. Instead, it computes the probability of each possible response pattern given the total score:

<div style='text-align: center;margin:auto;'>
Special
<div class='crop' style='margin:auto;'>
```{r, fig3, out.extra='style="margin-left: -30px; margin-top: -50px;"'}
special = fq / den
j = 6-c(1,2,3,2,3,4,3,4,5)
sp=blues[j]
cloud(special, panel.3d.cloud = panel.3dbars,
      xbase = 0.4, ybase = 0.4, zlim = c(0, 1),
      scales = list(arrows = FALSE, just = "right"), xlab = "A", ylab = "B",zlab ='',
      pretty=TRUE,
      par.box = list(lty=0),
      col.facet =  sp, alpha.facet=.7,
      screen = list(z = 40, x = -30))
```
</div></div>

A total score of 0 on the whole test can be achieved in one way: a subscore of 0 on A and a subscore of 0 on B. Hence, the probability, shown with the lightest bar, is 1. The same is true of the maximum score of 4: to gain it, one must score the maximum in both domains. A sum score of 1 or 3 can be achieved in 2 ways each, and there are 3 possibilities for a sum score of 2.

What we do now is identify the pattern with the largest probability given a total score of 0, 1, ..., 4, and trace a path through them:

<div style='text-align:center;'>
<div class='crop' style='display:inline-block;'>
```{r, fig41, out.extra='style="margin-left: -30px; margin-top: -50px;"'}
hi = c(1,4,5,6,9)
sp[hi] = reds[j[hi]]

cloud(special, panel.3d.cloud = panel.3dbars,
      xbase = 0.4, ybase = 0.4, zlim = c(0, 1),
      scales = list(arrows = FALSE, just = "right"), xlab = "A", ylab = "B",zlab ='',
      pretty=TRUE,
      par.box = list(lty=0),
      col.facet =  sp, alpha.facet=.7,
      screen = list(z = 40, x = -30))
```
</div>
<div style='display:inline-block;'margin-left:3em;'>
<div class='crop'>
```{r, fig42, out.extra='style="margin-top: -30px;margin-left:0px;"'}
d = expand.grid(0:2,0:2)
plot(d, xlab = "A", ylab = "B", xlim=c(0.5,4), ylim=c(-0.3,2.7), asp=1, pch=15,
     cex=2.5, col=sp, axes=FALSE)
lines(0:1,1:0,col="gray")
lines(1:2,2:1,col="gray")
lines(c(0,2),c(2,0),col="gray")
lines(c(0,0,1,2,2), c(0,1,1,1,2), col=3, lwd=4)
axis(1, at = 0:2,lty=0)
axis(2, at = 0:2,lty=0)

```
</div>
<p style='margin-top: 0px;margin-left: 24px;'>A</p>
</div></div>


The thin gray lines connect all patterns that yield the same total score on the test. The path must visit each of them once: it passes through the modal patterns given a total score of 0, 1, ..., 4. We are almost there, there are only two more things to mention:

* We have drawn one path so far, but the plot becomes interesting when there are two or more paths to compare

* The plots we made are based on observed proportions while dexter uses probabilities predicted by the interaction model or, optionally, the Rasch model

Back to the original profile plot: what does it mean? There are two paths, one for female respondents, the other one for males. The diagonal lines pass through all pairs of scores on "Want to curse, shout, "  and "Actually curse, shout" that result in the same overall score on verbally aggressive behavior. The fact that the curve for the females lies consistently to the left and above the curve for males means that, at each level of verbal aggressiveness, they tend to want more but do less.


```{r, prof_plot2,  fig.height=5, fig.width=5, fig.align='center'}
profile_plot(db, item_property='mode', covariate='gender', booklet_id=='agg', main='Gender')
mtext('Do versus Want')
```


Speaking in general, the profile plots can be used to investigate whether two (or more) groups of respondents obtain the same test score in the same way. When applied to educational test data, the plots can be used to detect differences in the relative difficulty of (sets of) items for respondents that belong to different groups and are matched on the test score. In some cases, one would associate this to *Differential Item Functioning (or DIF)*. Note however, that profile plots are different from regular procedures to detect DIF; and better. First of all, the procedure is not exploratory but requires its user to provide a meaningful classification of the items which should improve the odds that the results make sense to content-experts. Second, it looks explicitly at relations between items and avoids the erroneous conception of DIF as an item property [@BechgerMarisDIF]. 

Note that the profile plots got their name from their similarity to *profile analysis* which was proposed by Norman Verhelst [@VerhelstPA] and suggested by him and his co-workers as a method to test for DIF [@Yildirim14]. Regular profile analysis is also available in dexter. 

```{r, include=FALSE}
close_project(db)
RcppArmadillo::armadillo_reset_cores()
```


## References