Visualizing participants performance [wbwit III]

This is the third post on the development of a web-based word identification task. See this post for the implementation of the word identification task and this post for uploading the participants results to the server. This post describes how to plot the
participants results, so that they can receive immediate feedback. I think giving feedback to participants is important because the feedback 1) shows them how well they did the task and/or 2) might trigger their curiosity. For the visualization I used d3.js to make performance plots (i.e., the amount of time spent deciding whether and item was a word or not and the percentage of accuracy).

The first step is determining whether the word was correctly identified or not. The function ‘getAccuracy’ assigns a 1 to the accuracy (‘acc’) attribute of the given ‘item’ if a word was displayed and the ‘w’ (i.e. 119) key was pushed or if a non-word was displayed and the ‘n’ key (i.e. 110) was pushed. For all the other cases ‘getAccuracy’ assigns a 0. Moreover, the function ‘getAccuracy’ assigns the codeWord attribute to the given ‘item’ allowing easy labeling/filtering of words vs. non-words.

function getAccuracy(item)
{
    item["acc"] = 
        (((item["isword"] == 0) && (item["keyPress"]) == 110) ||
	((item["isword"] == 1) && (item["keyPress"]) == 119)) ? 1 : 0;
    item["codeWord"] = (item["isword"] == 0) ? "non-word" : "word";
}

words.forEach(getAccuracy);

Next step is to determine the average accuracy and reaction times for words and non-words. The average can be computed using the d3 functions ‘nest’ and ‘rollup’. Nest and rollup allow to summaries the reaction times and number of correct responses into two arrays with two values, one for words, the other for non-words (great tutorial on d3.js nest and roll up).


var aves = d3.nest()
  .key(function(d) { return d.codeWord; })
  .rollup(function(v) { return d3.mean(v, function(d) { 
      return d.RT; }); 
   })
  .entries(words);
// round reaction times
for (i = 0; i < aves.length; i++)
{
    aves[i]["values"] = Math.round(aves[i]["values"]);
}
plotData(aves, "rtChart", "Reaction Times (ms)");

aves = d3.nest()
  .key(function(d) { return d.codeWord; })
  .rollup(function(v) { return d3.mean(v, function(d) { 
      return d.acc; }); 
   })
  .entries(words);
// round accuracies
for (i = 0; i < aves.length; i++)
{
    aves[i]["values"] = +aves[i]["values"].toFixed(2);
}
plotData(aves, 'accChart', "Proportion Correct");

The plotData function is a wrapper of d3 functionality to translate the average results into a bar chart (these are almost a perfect reproduction of Mike Bostock’s bar chart). Note that before passing the values to plot to the plotData function I round the results. In fact, since I find it helpful to read the actual value the bar represents, I added a text at the tip of the bar specifying the reaction times in ms or the percentage of correct responses. However, too long numbers mess-up the bar plot, therefore the rounding. Below is the plotData function:

function plotData(data, whichPlot, titleOfTheGraph)
{
var margin = {top: 20, right: 30, bottom: 30, left: 40},
    width = 200 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1)
    .domain(data.map(function(d) { return d.key; }));

var y = d3.scale.linear()
    .range([height, 0])
    .domain([0, d3.max(data, function(d) { return d.values; })]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var chart = d3.select("." + whichPlot)
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", 
         "translate(" + margin.left + "," + margin.top + ")");

var bar = chart.selectAll("g")
    .data(data)
  .enter().append("g")
    .attr("transform", function(d) { 
           return "translate(" + x(d.key) + ", 0)"; 
    });

bar.append("rect")
    .attr("y", function(d) { return y(d.values); })
    .style('fill', 'steelblue')
    .attr("width", x.rangeBand())
    .attr("height", function(d) {return height - y(d.values); });

bar.append("text")
    .attr("x", x.rangeBand() / 2)
    .attr("y", function(d) { return y(d.values) + 3; })
    .attr("dy", ".75em").style("fill", "white")
    .style("text-anchor", "middle")
    .text(function(d) { return d.values; });

chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

chart.append("text")
    .attr("x", (width / 2))
    .attr("y", 0 - (margin.top / 2))
    .attr("text-anchor", "middle")
    .style("font-size", "12px")
    .style("fill", "black")
    .text(titleOfTheGraph);
}

The final product can be seen here after performing the super short test (e.g., it won’t take more than 5 minutes). The code is available on github. A fancier, newer visualization than this one will soon be available in a following post.

Advertisements
Visualizing participants performance [wbwit III]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s