# Simulate Godfrey Thomson's "sampling model" of mental abilities, and perform # factor analysis on the resulting test scores. # MASS package used to make random multivariate normal vectors require(MASS) # stats package used to do factor analysis, but if it's missing you've got big # problems! require(stats) # Set the mean and standard deviation of each elementary ability to that of an # IQ test - not actually important! ability.mean = 100 ability.sd = 15 thomson.model <- function(n,d,a,q,output="variance") { # Using incomprehensible parameter names is bad # number of testees = n # number of tests = d # number of shared abilities = a # max. number of specific abilities per test = q # assign abilities to tests general.per.test = floor(runif(d,min=0,max=a)) + 1 specifics.per.test = floor(runif(d,min=0,max=q)) + 1 # Define the matrix assigning abilities to tests general.to.tests = matrix(0,a,d) # The use of a for loop here is maybe a little slower than some sort # of vectorization, but it's sanity-preserving; so is using the temporary # variable "abilties" to hold the sample. for (i in 1:d) { abilities = sample(1:a,size=general.per.test[i],replace=FALSE) general.to.tests[abilities,i] = 1 } # Covariance matrix of the general abilities sigma = matrix(0,a,a) diag(sigma) = (ability.sd)^2 mu=rep(ability.mean,a) x = mvrnorm(n,mu,sigma) # person-by-abilities matrix of abilities # The "general" part of the tests general.tests = x %*% general.to.tests specific.tests = matrix(0,n,d) noisy.tests = matrix(0,n,d) # Each test gets its own specific abilities, which are independent for each # person # Again, I could probably vectorize, but d is small and this is saner for (i in 1:d) { # Each test has noises.per.test disturbances, each of which has s.d. 15, # since these are all independent their variances add j = specifics.per.test[i] specifics = rnorm(n,mean=100*j,sd=ability.sd*sqrt(j)) specific.tests[,i] = general.tests[,i] + specifics # Finally, for extra realism, some mean-zero trial-to-trial noise, so # that if we re-use this combination of general and specific ability # scores, we won't get the exact same test scores twice noises = rnorm(n,mean=0,sd=ability.sd) noisy.tests[,i] = specific.tests[,i] + noises } # do the factor analysis my.fa = factanal(noisy.tests,1) # extract the loadings v = as.vector(my.fa$loadings) # compute how much of the variance (really, correlation) the factor # describes varexp = sum(v^2)/d # output: should do a return() instead to be completely pukka switch(output, variance = varexp, everything = list(general.ability.pattern = general.to.tests, numbers.of.specifics = specifics.per.test, ability.matrix = x, specific.tests = specific.tests, data = noisy.tests, loadings = v, varexp = varexp), "I can't tell what you want me to output") }