Leader

Sunday, 19 April 2015

Introduction to psychometric curves and fitting a simple psychometric function

Code
GitHub: Matlab code and Python package

Introduction
An important aim of psychophysics is to quantify the relationship between stimulus parameters and an observer's subjective appreciation of what's going on. Signal detection theory approaches this problem by attributing perceptual sensitivity to thresholds that act at multiple levels in a system. In this context, a threshold could, for example, be how loud a sound needs to be to determine its location to a certain degree of accuracy, or how long a light needs to be on to determine its direction of movement. Above threshold, and the task is possible to a certain degree of accuracy. Below threshold and it's not possible to discriminate as accurately.

Experimentally, thresholds are often measured in two-alternative forced choice (2AFC) tasks or in go-no go paradigms (GNG). In 2AFC tasks a subject is asked to indicate one of two choices in response to a stimulus. For example, indicate white if a colour appears white, or black if it appears black in response to a shade of grey. Alternatively, GNG tasks require a subject to not respond ("no go") until a stimuli meets a certain criteria, then respond ("go").

In both of these types of task, a subjects performance can be measured as a function of the stimulus parameters - either as % correct, or similar, in 2AFC or d' in GNG, where d' is a function of hits, misses, and false alarms. Regardless of the exact metric used, the subjects performance can be described by two main parameters; bias (the "point of subject equality", PSE) and discrimination sensitivity (as in ability to discriminate between two conditions, such as white and black).

For example, imagine a subject performing a 2AFC task where they're presented with a shade of grey and they have to press one button to categorise the colour as white, or another button to categorise the colour as black. Intuitively, they're more likely to classify a grey stimulus as black the closer it is to black, and less likely the colour it is to white. So plotting their responses as a proportion of black responses (y) against the stimuli presented (x). We could use this sort of task to see how other factors, like the colour of a background, could affect perception of the shade of grey.



Wednesday, 8 April 2015

Alternative function: Net present value of a cash flow

See also the time value of money for other related Matlab functions.

Download
npvFlow.m

Introduction
The net present value (NPV) of a cash flow is how much a flow of cash is worth right now. The "net" part refers to the fact that the cash flow isn't necessarily fixed, and may even be negative at certain points in time. Matlab has two functions in the financial toolbox for calculating NPV for fixed and variable cash flows (pvfix and pvvar respectively), npvFlow.m is designed to emulate the basic function of both these functions and works for fixed and variable cash flows.

For example, consider a cash flow of £200 per year for the next 4 years, with an annual interest rate of 4%. What is the value of this cash flow right now, at this point in time? It's not £800, it must be less. To work it out, we need to calculate the present value of each individual chunk of cash. The NPV is simply the sum of these.

This is done with the PV calculation (pvLump,m) of each lump sum, which is

PV = FV / (1+r)^n,

where r = interest rate and n = number of time periods.

So,
End of period 1: £200 received = PV1 = 200/(1+0.04)^1 = 192
End of period 2: £200 received = PV2 = 200/(1+0.04)^2 = 185
End of period 3: £200 received = PV3 = 200/(1+0.04)^3 = 178
End of period 4: £200 received = PV4 = 200/(1+0.04)^4 = 171

The sum of PV1-4 = NPV = 192 + 185 + 178 + 171 = £725.

Alternative function: Present value of lump sum

See also the time value of money for other related Matlab functions.

Download
pvLump.m

Introduction
The present value of a lump sum of cash is the value of cash available in the future, right now.

But why should the value of something be now compared to in the future? This is a core concept of what is known as the "time value of money", which basically states that money is worth more now than it is if received in the future. The reason for this is interest - if you have money now, you have the opportunity to earn interest on it. If you only get the money later, you miss this opportunity.

This concept also applies to cash flows, but is slightly more complex as the interest needs to be applied at multiple points in time - see npvFlow.m.

For example, for a lump sum, assuming a positive interest rate; £1000 in 2 years time is worth £1000 in 2 years, whereas £1000 now is worth £1000+2 years interest in 2 years time. But what is £1000 in 2 years time worth to us right now? This value is the present value (PV).

So, for a lump sum, the equation is a rearranged form of the future value (FV) compound interest equation (see fvLump.m). Intuitively, for compounding interest, the FV is the PV plus the interest from each time point:

FV = PV * (1+r)^n,

where r = interest rate and n = number of time periods.

This means to calculate the PV, the equation rearranges to:

PV = FV / (1+r)^n.

This is the equation we will implement in a simple Matlab function, pvLump.m.


Alternative function: Future value of lump sum

See also the time value of money for other related Matlab functions.

Download
fvLump.m

Introduction
The future value (FV) of a single lump sum of cash is its present value (PV, see also pvLump.m and npvflow.m) plus interest earned over a period of time. Interest can be earned in two ways - simple interest or compound. Simple interest is linear and is a proportion of the original amount applied at the end of the time period. Compound interest, which is mostly commonly used, increases in an exponential fashion as it is added at each time period and adds to the amount on which the interest is applied in the next period. fvLump provides an alternative to fvdisc in the financial toolbox.

Simple interest
FV = PV * (1+r*t)

Compounding interest
FV = PV * (1+r)^t

Where = interest rate and = number of time periods.

These equations are available in fvLump. For calculating the future value of a cash flow, for example, when saving a regular amount of money and earning interest, see nfvFlow.m.

Saturday, 4 April 2015

Alternative function: Moving average


Download
movAv2.m

This is a slightly more advanced version of movAv and provides an alternative to movavg and tsmovavg ("time-series moving average"). This version allows weighting of the mean and setting of the lag value (rather than just centring the average automatically). It also shows examples of basic management of the variables in a functions workspace, using switch and case for convenient string logic, and how to perform a weighted mean calculation. See also movAv for a couple of examples when moving averages may or may not be appropriate.

Description
Compared to movAv.m, this version adds two features:

  • Lag - when performing a moving average with length n, n points in total are lost from the output. The lag value positions the output in a vector of the same length as in the input, by determining the number of NaNs at the start and end of the vector.
  • Weights - Included are a few random weights as examples in the script as examples, although the only one that might be useful is 'simExp', which exponentially weights the values in the centre of the average window higher than the outer points. Weights can also be supplied as a vector the same length as the average window (n). The absolute values of the weights are unimportant, as they're normalised to relative values that sum to 1 in the script.


Sunday, 29 March 2015

Alternative function: Simple moving average

Download
movAv.m
(see also movAv2 - an updated version allowing weighting)

Description
Matlab includes functions called movavg and tsmovavg ("time-series moving average") in the Financial Toolbox, movAv is designed to replicate the basic functionality of these. The code here provides a nice example of managing indexes inside loops, which can be confusing to begin with. I've deliberately kept the code short and simple to keep this process clear.

movAv performs a simple moving average that can be used to recover noisy data in some situations. It works by taking an the mean of the input (y) over a sliding time window, the size of which is specified by n. The larger n is, the greater the amount of smoothing; the effect of n is relative to the length of the input vector y, and effectively (well, sort of) creates a lowpass frequency filter - see the examples and considerations section.

Sunday, 22 March 2015

New graphics in Matlab 2014b and 2015 and creating nicer figures in one line of code

Download
og.m
ng.m
see also Matlab plot formats and how to quickly save high quality figures for hgx, a function to handle exporting figures.

The new graphics system in Matlab 2014b has a few handy features, including:
  • Perceptually-equally spaced default colours (although be careful, red is near green)
  • OpenGL hardware rendering - smooth lines!
  • Easier access to handle properties using structure syntax - eg. handle.FontSize = 12, instead of set(handle, 'FontSize', 12)
Even so, it still takes a little bit of effort to render nice figures and graph in Matlab, the default settings don't look great. Generally lines need to be made thicker, fonts bigger, etc., etc. Each setting requiring a line of code. Also, OpenGL can be a bit unreliable; it switches to software rendering on a whim, which disables features like anti-aliasing.


Saturday, 21 March 2015

Alternative function: nansum (multi-dimensional)

Download
nansum.m

Explanation
See nanmean3 for a walk through of the code, the structure of the code is exactly the same, this version just uses SUM instead of MEAN.


Race model inequality script; R. Ulrich, J. Miller, H. Schröter, 2007

http://link.springer.com/article/10.3758/BF03193160#
Behavior Research Methods
May 2007, Volume 39, Issue 2, pp 291-302
Testing the race model inequality: An algorithm and computer programs
Rolf Ulrich, Jeff Miller, Hannes Schröter

In psychophysics the race model is used to compare reaction time data from two conditions (X,Y) to a third condition (Z). For example, response times to an auditory task and a visual task, compared to an audiovisual task. It tests if reaction times are faster in the third condition, and if so, they violate the race model and (possibly, depending on the task) represent multisensory integration. The race model inequality, simply, is defined as  P(RTz) < P(RTx) + P(RTy). For a better, and far more detailed explanation and run through - see the above paper!

In this paper, the authors present an algorithm to test the race model inequality and include a set of functions for Matlab that implements the algorithm, although no supplementary .m file appears to be included. The code is in the .pdf, but copying from a .pdf is, as usual, an unnecessarily painful experience.

Here is a slightly modified version of the code included in the paper, it's copied from the .pdf and corrected for various illegal characters, line breaks, etc. etc. Apart from a couple of minor edits, nothing else is changed from the authors original work.

Also included as an example of how to use RaceModel.m is a script (RaceModelTester.m) that generates some reaction time data in the correct form to use with RaceModel.m. The RT data supplied to RaceModel.m should be in three rows, rounded and in milliseconds. There should also be a vector of probabilities, and a true or false to indicate whether to create a plot or not.


Friday, 27 February 2015

Simple model fitting: Solar panel profit

Here's an example of how to use Matlab to create a basic predictive model. It'll cover;
  • Choosing an appropriate model based on our current understanding of the system, and what reasonable assumptions we can make about it
  • Using curve fitting to fit the model to the data we already have to refine the coefficients for the model
  • Qualitative and basic quantitative (but not statistical) analysis of the model, and the assumptions made.

The data used will be real income data for a 2.8 kWh solar panel system located in the UK. We have three years worth of data - let's work out how much we can expect to earn over the 25 year period of the original Feed-in Tariff scheme.

The first thing to understand is that all models require assumptions to be made. There has to be logical reason underlying these assumptions, or the model will lack predictive power. Matlab can not do this for you - it can certainly help - but brute force can't replace sensible reasoning.


Tuesday, 13 August 2013

Alternative function: nanmean (multi-dimensional)

Download
nanmean3.m



Multi-dimensional means
This function upgrades the alternative NANMEAN function here: nanmean2.m). This version can takes the mean of data containing NaNs, along any specified dimension.

This version basically performs 3 steps; the data is premuted so that the dimension specified is moved to dimension 1 (rows). The mean is then peformed on each column in turn, while ignoring the NaNs, using the same process as in nanmean2.m. The resulting row of means is then permuted back to the dimension it came from.

There are other ways of doing this, however, this method provides a nice example of permuting and reshaping data in multidimensional matrixes, which can be tricky to understand.


Friday, 19 April 2013

Multiple Velleman K8055D USB Boards in Matlab

Download
K8055_multiple.zip

Containing...
K8055D_connect.m - The main script used to connect to the boards, modified from this article from the hack hole to allow up to four Velleman K8055D boards to interface with matlab. 
K9055D.h and K8055D0-3.dll - files required to connect to the boards.
setdigital.m - script used to set the digital channels on the board to represent a decimal in 8 bit binary
dec2bin2.m -  used in setdigital.m to convert decimal to binary.


Connecting...

K8055D_connect.m
The first line of this function should be edited so that it points to the folder containing the .h and .dll files.

This script accepts a vector input, CardAddress which specifies the addresses of the cards to connect to (0, 1, 2, 3) and returns a list of board it successfully connects to. For example, to connect to four cards in one go:

>>CardAddress=0:3;
>>K8055D_connect(CardAddress
ans = 
      0 1 2 3

After connecting a window pops up for each board with possible commands. These can be set to the board using the CALLLIB function, and the .dll for a single board, for example:

>> calllib('K8055D0', 'ClearAllDigital');
Sends the command to clear all the digital channels (set them to 0) for board 0.


setdigital.m
This script takes a decimal as an input, converts it to binary and sets the 8 digital channels on the specified board to a 8-bit binary representation of the decimal. Uses dec2bin2.m. For example,

>> setdigital.m(0,100)
Sets the digital channels on board 0 to represent the number 100 in binary.


Monday, 8 April 2013

Alternative function: hanning

Download
hanning2.m


Final code
function w = hanning2(n,b)

 if nargin==1
    b='symmetric';
end

if strcmp(b,'symmetric')
    n = n+2;
    i = 1:n;
    w(i,1) = 0.5*(1-cos((2*pi*(i-1))/(n-1)));
elseif strcmp(b,'periodic')
    i = 1:n;
    w(i,1) = 0.5*(1-cos((2*pi*(i-1))/(n)));
else
    disp('Error')
end


Explanation
Matlab's HANNING function does almost exactly the same thing as Matlab's HANN function, except it doesn't include the first and last zeros in the output.

To compare hann(5,'symmetric') VS. hanning(5,'symmetric'):

>>hann(5,'symmetric')
ans=
0
0.5
1
0.5
0

VS.

>>hanning(5,'symmetric')
ans=
0.25
0.75
1
0.75
0.25


HANNING and HAN output is the same for periodic windows (the first zeros is present, the last one isn't, this allows the windows to be appended together without repeating the last point in the first point of the next window).


Code run-through
See Alternative function: hann, it's basically exactly the same except for two lines. When a symmetric window is requested, the index n is extended by two points, the window calculated over the requested length+2, and then the extra line w = w(2:end-1); chops off the first and last values (which are zeros).

Saturday, 6 April 2013

Alternative function: nanmean

Updated version (multi-dimensional): nanmean3.m

Download
nanmean2.m

Averaging that, but not that
One of Matlab's features is it’s ability to deal with NaN (Not-A-Number) values in data. But if you don’t have the statistics toolbox, it lacks the essential function NANMEAN, which simply returns the mean of a set of values containing a NaN. Using the MEAN function on data containing a NaN returns a NaN as the mean; because you’re supposed to buy the statistics toolbox, you cheapskate.

Anyway, try it.

>>excitingdata = [1; 2; 3; 4; NaN]
excitingdata =
1
2
3
4
NaN

You can probably calculate the mean yourself, but the MEAN function can’t.


>>mean(excitingdata)
ans = NaN


So we have to do it the long winded way. We can use the ISNAN function to create an index of the data excluding the N.


>>gooddata_index = ~isnan(excitingdata)


ISNAN returns a 1 if the “number” at a location is a NaN and a 0 if it’s a real number. So using the logical operator “not” or ‘~’ before it asks the opposite - is the number at a location not a NaN? When this is true, a 1 is returned, and when it isn’t (ie. it is a NaN), a 0 is returned. For example:


>>gooddata_index = ~isnan(excitingdata)

gooddata_index =

1

1

1
1
0

We can now use this index to extract the non-NaN numbers from excitingdata; where the index is a 1, the number at that location is used, when the index is a 0, it isn’t.

>>gooddata = excitingdata(gooddata_index)
ans =
1
2
3
4

The mean of this data can now be calculated using MEAN.

>>mean(gooddata)
ans=2.5000

The above four sections of code can be combined in to one line as follows:

>>mean(excitingdata(~isnan(excitingdata)))
ans=2.5000

Remember ~isnan(excitingdata) is the index of non-NaN values, which is inserted straight into excitingdata to extract just the non-NaN values, which in turn are passed straight to the MEAN function.

Note that the above steps will not work for a two-dimensional matrix, but the NANMEAN2 function will.


Code run-through
The NANMEAN2 function above follows the explanation to calculate the mean but adds flexibility to deal with matrices. It accepts two inputs; data should contain the data to calculate the mean for, and dim can be 1 or 2, which tells NANMEAN2 which dimension to take the mean along.

The first bit of code uses the NARGIN function to check the number of input arguments. If dim is not specified (ie. when the number of input arguments = 1) , it defaults to dim = 1, which means the mean is take for each column:


if nargin == 1
    dim = 1;
end

The calculation is then performed in the next section of code:

if dim == 1 %(column)
    dim_length = length(data(1,:));
    means = zeros(1,dim_length);
    for i = 1:dim_length
        col = data(:,i);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end
elseif dim == 2 %(rows)
    dim_length = length(data(:,1));
    means = zeros(dim_length,1);
    for i = 1:dim_length
        col = data(i,:);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end
else
    disp('Error')
end

The first if statement checks the value of dim. If dim = 2, it performs the code between the elseif and else statements, if dim doesn't equal 1 or 2, it displays an error and doesn't do anything. If dim = 1 it executes the code in the first part of the if statement:

    dim_length = length(data(1,:));
    means = zeros(1,dim_length);
    for i = 1:dim_length
        col = data(:,i);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end

In this section of code dim_length = length(data(1,:)); calculates the number of columns in data. The next line, means = zeros(1,dim_length); then preallocates an output vector with zeros ready to store the calculated mean for each column. Preallocating means the output vector won't grow inside the for loop (this is good) and also means that we can be specific with where each value is put in each loop of the for loop, rather than just appending to the end each time (this is good too).

The actual calculation of the mean is performed in the for loop, once for each column. for i = 1:dim_length creates a vector of values for the for loop to use. col = data(:,i); extracts a single column of data to use in this iteration of the for loop. The first value of i is 1, so the first time through the for loop col = data(:,1) extracts the first column. The second time through the loop col = data(:,2) extracts the second column, and so on.

m = mean(col(~isnan(col))); gets the non-NaN values from the column  and takes the mean (as per the explanation).  means(i) = m; then stores the mean in the output vector (which is a row containing a separate mean for each column), at index i, 

The second part of the main if statement does exactly the same as above, but for rows:


elseif dim == 2 %(rows)
    dim_length = length(data(:,1));
    means = zeros(dim_length,1);
    for i = 1:dim_length
        col = data(i,:);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end

Note the differences in the expressions dim_length = length(data(:,1));, means = zeros(dim_length,1); and col = data(i,:);. Everything is done along the row, dimension.

This function won't work for a greater than 2D matrices, but can be expanded to do so, if needed.

Final code
function means = nanmean2(data,dim)

if nargin == 1
    dim = 1;
end

if dim == 1 %(columns)
    dim_length = length(data(1,:));
    means = zeros(1,dim_length);
    for i = 1:dim_length
        col = data(:,i);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end
elseif dim == 2 %(rows)
    dim_length = length(data(:,1));
    means = zeros(dim_length,1);
    for i = 1:dim_length
        col = data(i,:);
        m = mean(col(~isnan(col)));
        means(i) = m;
    end
else
    disp('Error')
end

Alternative function: hann

Download
Explanation
The MATLAB Signal Processing toolbox has a function called HANN which generates a symmetric or periodic Hann window’s over a specified number of points. Implementing an alternative to this function is very simple and the equation is described here. Symmetric and periodic windows are the same length (specified by n), but a symmetric window starts and ends on 0, whereas a periodic window ends on the point before 0. 

This alternative Hann function produces the same output as the Matlab function where w is a column vector of length n. The second input is a string with the possible values 'symmetric' and 'periodic' If no second input is specified, it defaults to 'symmetric'.



AdSense