Sunday, April 11, 2010

Testing Fitts's Law

Paul Fitts was a researcher in psychology who studied skilled performance. One of his most notorious (and misinterpreted) discoveries was named "Fitts's Law" or sometimes "Fitts' Law" or "Fitts Law" or even "Fitt's Law", and it concerns how long it takes to make aimed movements. The finding shows that the time to make prepared aimed movements is a tradeoff between the target size and the distance moved.  Fitts noticed that mean aimed movement times seemed to be basically linear with the log of the ratio between target size s and distance d:

t = a + b log(d/s)



which  also means:
t = a + b log(d) - b log(s).


which implies that time impact has the same coefficient for distance AND target size, just in opposite directions.  It should be apparent that this differs from:

t = a + b log(d) - c log(s).

Or, Fitts's law implies that b should be the same as c.  This is sort of remarkable, because it means that you can effectively trade distance for target size equally.  In my view, this is the 'real' Fitts's law relationship: RT is related to the log ratio of distance and target size, and it has close links to information theoretic accounts of behavior popular in the 1950s and 60s.

There are some important things to note about the procedures typically used to infer this relationship. Typically, the data used to infer such laws were for repeated aimed movement tasks. That is, one repeatedly taps between two points or buttons on a flat console (say 20 times), and the experimenter measures how long it takes to do this.  Repeated movements allow reuse of motor plans, and so it is possible that Fitts's law really happens because of repeated aimed movements, rather than aimed movements per se.  What would happen if the targets changed on each trial?

A lot of  modern discussion about Fitts's law on the internet these days comes from interface designers, because it can be used to justify design choices about button size and placement (see Bruce Tognazzini's column). Bruce's now famous (and famously-misinterpreted) point was that a button at the edge of the screen effectively increases the size of the button to infinity, which I guess makes Macs infinitely easier to use than their counterparts, because some of their menus are aligned with the edge of the screen. Nowadays, this 'Tognazzini Conjecture' is often stated as Fitts's law, (even though it really has nothing to do with Fitts's law, and even though it misinterprets Fitts's Law). It is wrong because if s really were infinite, time to make the aimed movement would be negative.  Or technically, time should go to 0 when the target size is as big as the distance to to the target.  But Fitts wasn't talking about boundary conditions, and this is clearly one where the law falls apart.   Furthermore, the remarkable thing about the law is not that time goes up as target distance goes up and target size goes down--the remarkable thing is that it is linear with their log-ratio.  Just to be clear, I don't think the 'Tognazzini Conjecture' was wrong, it is just it is hard to use Fitts's  law as justification for the conjecture.

But this post is not a comprehensive review of Fitts's law. Here, I will walk through a script for PEBL I've written to test Fitts's Law, and determine whether it holds in the domain of mouse movement, especially for isolated movements and unpredictable movements. The first thing to decide is what is the actual behavior we are interested in. Here are some questions:
  • Should the target be visible before timing begins (prepared versus unprepared)?
  • Should timing be initiated by the experimenter or the subject?
  • Should the subject click on the target, or should the just move a cursor to the target?
  • Should movement be completely free in 2 dimensions, or limited to 1 dimension?
  • Should targets appear anywhere on the screen?
I've decided to test two versions. In both of them, the aimed movement won't require clicking on the target, just stopping the cursor on the target. I'll measure one combination of target/distance at a time, not repeated moves between two targets. And in one condition, the subject won't see the target before they start moving.

To start with, I want to make 'cursor' object that gets moved whenever the mouse moves. This is not really necessary, because you can use the system cursor, but I wanted something a bit larger, and this allows us to constrain the vertical position of the cursor a little more easily. A graphical polygon in the correct shape can be made with the Plus() function:

gCursor <- Plus(0,0,10,2,MakeColor("black")); AddObject(gCursor,gWin)

And then,  I made a little function to move it to a specific spot.

define MoveCursor(pos)
{
  gCursor.x <- First(pos)
  gCursor.y <- Nth(pos,2)
  Draw()
}

In this experiment, I want to vary both the distance of movement and the size of the target. This is a standard counterbalance design, so I simply set of the levels of each condition (numbers indicate pixels), and create a design list with one of PEBL's special design functions:


targdist <- [50,75,100,200,350,500,700]
targsize <- [10,20,40] 
reps <- 5 ##Number of times through list

cond <- DesignFullCounterbalance(targdist,targsize)
trials <- Shuffle(RepeatList(cond,reps))

Next, I want to set up a 'starting cross'. This is easy--two 20-pixel lines at the appropriate start location on the left of the screen:

l1 <- Line(gstartx-10,gHomeY,20,0,MakeColor("black")) ; AddObject(l1,gWin)
l2 <- Line(gstartx,gHomeY-10,0,20,MakeColor("black")); AddObject(l2,gWin)

Let's also add a couple text messages we can put information on:

gMessage <- EasyLabel("",gHomeX,gHomeY-100,gWin,22)
gCounter <- EasyLabel("",gHomeX,gHomeY-150,gWin,22)

Before I get to the trial logic which will paint a target on the screen, let's look at what the screen will look like:






On the left, you see the the fixation cross, a little to the right is the cursor (which is moved dynamically with the mouse), and then the red target. Top middle is text showing the current trial number (helpful if you want your subject to remain motivated). The trials variable will be a shuffled list containing all possible pairs of target size and distance.  The red target will always appear at the same vertical height, but will appear at any one of seven positions left-to-right. 

After trying a few different things, I thought an interesting thing to look at would be movement to targets that were not prespecified, and which changed on each trial. Most classic Fitts's law studies used repeated aimed movement between two points. That is, the subject touches two circles at, say,  ten inches apart, twenty times.  I wanted to use a version of the task to evaluate the effect of initial motor planning--knowing exactly where the target was going to be before you start moving. So first, I'll create a global variable called gShowPreview which controls whether you can see the target before you start moving:

gShowPreview <- 0 ##Can you see the target before you move?


I put this near the beginning of the experiment so it can be easily edited by an experimenter. Note that in PEBL, any variable starting with a g has global scope. So let's create a Trial function that lets us run a trial if the distance, and size of the target are specified. Here, trial will be a number code, pos and size are the conditions, and type allows us to modify behavior, like requiring clicking to start or end.

define Trial(trial, pos, size,type) {

The first thing to do is create a target and display it if needed. I want a red target with a black outline, so it means making two rectangles, which I'll call targ and targ0. The targ rectangle will be red and on the bottom, the black outline will be unfilled (the last parameter is 0 instead of 1) and will lie on top of targ. If run with gShowPreview set to 1, we will just preview with the black outline:

targ <- Rectangle(gStartx+pos,gHomeY,size,gTargHeight,gTargCol,1)
AddObject(targ,gWin)
Hide(targ)
targ0 <- Rectangle(gStartx+pos,gHomeY,size,gTargHeight,MakeColor("black"),0)
AddObject(targ0,gWin)
Hide(targ0)
if(gShowPreview)
{
   Show(targ0)
}


Now, to start a trial, I'll move the mouse cursor to the starting location, and then move the cross cursor there too:

##Hide the cursor and wait for a click to start trial
SetMouseCursorPosition(gStartX,gHomeY)
MoveCursor([gStartx, gHomeY])

The basic procedure is that you click to start a trial, then you start moving. We will capture the time it takes from when you first start moving to when you get to the target and we show a preview. To do this, we will make a loop that repeatedly gets the cursor position, moves the cross-cursor to that position, checks if you are on the target, then draws:

time <- GetTime()
mousepos <- GetMouseCursorPosition()
MoveCursor(mousepos) #If you stop moving, check to see if you are inside the target
if(ListEqual(mousepos,lastpos) )
{
  continue <- (1 - ClickOn(mousepos,[gStartx+pos,gHomeY],size, gTargHeight))
}

Here, we only check to see if you are on the target if two consecutive mouse positions are identical (so that you have stopped). ClickOn() is a special function written for this test that determines whether the mouse is inside the rectangle. I also have a new function called WaitForClickOnTarget() that will do basically the same thing, but I didn't use it here. Now its basically a matter of adding a few instructions, instrumenting to record data, and testing to make sure flow goes well. The complete experiment is available under fitts/ in the PEBL test battery; the latest is here. In its current format, it probably isn't ready for using in a lab situation; you would want better instructions, and figure out which conditions are the ones you really need.

Here is a screencast of a few trials of the experiment, in which the target does not appear until after  the mouse starts moving. Notice how it is easy to overshoot close targets, which may create violations of Fitts's Law.





But what happens when I run it? Lets try two conditions: with target preview and without. To do this, I set the number of trails per condition a little higher, changed the gShowPreview between trials, and ran from the command line with the -s parameter to specify the output file name. I ran each way, with 15 trials per condition (7 distances, 3 widths). Here are the basic results:


OK, you can see the basic Fitts's Law results: movement gets longer as the distance increases, and as the size of the target gets smaller. More importantly, time is proportional to the log of their ratio. Many types of behaviors could violate Fitts's law. For example, suppose that distance and target width have different parameters, or suppose that they interact positively or negatively. Furthermore, we don't know what impact the 'look-ahead' time should have. Is it just a linear impact on time, or something else?

To test some of these hypotheses, at least on my own data, a made a few linear regression models in R. First, I'll regress RT on log(dist/width), which is called fsize. I'll add lookahead condition as a factor later. What happens?



Call:
lm(formula = x ~ fsize + show, data = dat.agg)

Residuals:
    Min      1Q  Median      3Q     Max
-158.93  -51.52  -11.75   56.75  234.41

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept)   312.89      36.06   8.677 1.21e-10 ***
fsize         235.18      13.20  17.815  < 2e-16 ***
show2        -155.66      28.77  -5.410 3.41e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 93.24 on 39 degrees of freedom
Multiple R-squared: 0.8989,    Adjusted R-squared: 0.8937
F-statistic: 173.3 on 2 and 39 DF,  p-value: < 2.2e-16
The intercept, Fitts coefficient, and condition difference are all reliably different from 0.  Predicted functions are plotted against data below:



OK, this means that the best estimate for our model is 313 ms + 235 * log(dist/size) when you can't see the target first, and 155 + 235 * log(dist/size) ms otherwise. You can see that the fit to my noisy data is pretty nice. It is consistent with Fitts's law so far, but we need to test it a little bit to make sure. What if we predict against log(width) and log(distance), but let them each take on independent slopes? This model is fit below:


Call:
lm(formula = x ~ log(dist) + log(size) + as.factor(show), data = dat.agg)

Residuals:
    Min      1Q  Median      3Q     Max
-159.25  -56.06  -14.52   55.49  243.38

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)   
(Intercept)        223.33     113.56   1.967   0.0565 . 
log(dist)          241.89      15.51  15.596  < 2e-16 ***
log(size)         -217.04      25.52  -8.504 2.51e-10 ***
as.factor(show)2  -155.66      28.89  -5.389 3.92e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 93.61 on 38 degrees of freedom
Multiple R-squared: 0.9007,    Adjusted R-squared: 0.8928
F-statistic: 114.9 on 3 and 38 DF,  p-value: < 2.2e-16

 

Here, both the distance and size coefficients are reliably different from zero, but they are fairly similar (241 vs 217) Is this any better than the other model? An ANOVA says no!

anova(lm.fitts,lm.1)
Analysis of Variance Table


Model 1: x ~ fsize + show
Model 2: x ~ log(dist) + log(size) + as.factor(show)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 39 339018
2 38 332952 1 6066 0.6923 0.4106


So we don't gain anything by letting these variables take on different coefficients, Together, they had a coefficient of 235, separately they were 242 and 217, and so we didn't buy much by separating them.  I also tested whether size and distance had a reliable interaction:  the answer is no.  Plotting the observed mean RT of each condition back onto the log(dist/width) space, we see that they do really appear to follow this lawful behavior.

There is probably more to the story than just this. I also recorded point-by-point locations, which show some overshooting behavior that may be able to account for deviations.  For example, here is a time-by-x-coordinate plot for all the targets 75 pixels away from the base. The top shows no pre-cuing, and the bottom shows with precuing.  Notice how I would very quickly go way beyond the target, then correct.  This probably paid off for the longer targets, but hurt me on the shorter targets.  This overshooting was much smaller when the target was previewed.





So, that is it for PEBL's Fitts's Law experiment.  Sort of surprisingly to me, my data were consistent with a strong version of Fitts's law, not just a version that says "rt increases as distance increases and target size gets smaller".  Pre-cueing give an advantage, primarily because without precuing my initial movement was too big, and I would have to correct this for near distances when I overshot the target.

1 comment:

Dan McConnell said...

Is it possible to change the code to force the participant to click inside the target rather than just stop inside the target?