Rev 2213 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
\documentclass[letterpaper]{article}\usepackage{url}\title{Creating an R data set from STAR}\author{Douglas Bates\\Department of Statistics\\University of Wisconsin -- Madison}\date{April 5, 2005}\newcommand{\code}[1]{\texttt{\small #1}}%%\VignetteIndexEntry{Creating an R data set from STAR}%%\VignetteDepends{lme4}\begin{document}\maketitle\begin{abstract}A substantial portion of the data from Tennessee's Student TeacherAchievement Ratio (STAR) project, a large-scale, four-year study ofreduced class size, has been made available to the public at\url{http://www.heros-inc.org/data.htm}. We describe the creationof an R (\url{http:www.r-project.org}) data set from these data.\end{abstract}<<preliminaries,echo=FALSE,results=hide>>=options(show.signif.stars = FALSE,width=68)library(Matrix)library(lme4)@\section{Introduction}\label{sec:intro}The data from the STAR project are available in several differentforms from the web site \url{http://www.heros-inc.org/data.htm}. Themost convenient form for creation of an R data set is thetab-delimited text file. Download and unzip the archive file\url{http://www.heros-inc.org/text-star.zip} producing two files:\texttt{readme.txt}, a description of the data, and\texttt{webstar.txt}, the data themselves.\section{Reading the data}\label{sec:reading}From the data description file we can see that there are 53 columns inthe data set and most of these columns are coded values. Such columnsshould be represented as factors in R but many of these columns willneed to be combined before we can work with them. We will convert thefirst 5 columns to factors and leave the remaining 48 columns as integers.\begin{Schunk}\begin{Sinput}orig <- read.table("webstar.txt", header = TRUE,colCl = rep(c("factor", "integer"), c(5, 48)))\end{Sinput}\end{Schunk}In the call to \code{read.table} we used an explicit file name for thedata file. In practice it is often more convenient to use the\code{file.choose} function which brings up a file chooser panel.<<datainput,echo=FALSE,results=hide>>=load("orig.rda")@We can check the form of the original data with<<strorig>>=str(orig)@\subsection{Missing value codes}\label{sec:missval}All the columns except the first column have missing values present.Typically the missing value code is \code{"9"} but \code{"99"},\code{"999"} and \code{"9999"} are also used.We convert these to R's missing value code \code{NA} column by column.<<missval>>=mv <- rep("9", 53)mv[c(4,17,26,34,45)] <- "99"mv[c(19,20,27,28,35,36,39,40,46:53)] <- "999"mv[5] <- "9999"mv[1] <- "999999"for (i in seq(a = orig)) orig[[i]][orig[[i]] == mv[i]] <- NAsummary(orig[1:5])@Notice that level \code{"9"} is still present for the \code{SSEX}variable even after all the observations at that level have beenreplaced by the missing value code. To remove these unused levelsfrom this and all the other columns, we loop over the columnsselecting all the values but using the optional argument \code{drop = TRUE}.<<dropunused>>=for (i in seq(a = orig)) orig[[i]] <- orig[[i]][drop = TRUE]summary(orig[1:5])@For convenience we convert the names of the columns to lower case.<<lcase>>=names(orig) <- tolower(names(orig))@\section{Setting factor levels}\label{sec:factorLevels}In R the levels of a factor can be given meaningful labels instead ofnumeric codes and in most cases this eliminates the need for aseparate codebook. For example storing the labels of \code{sex} as\code{"M"} and \code{"F"} makes the coding self-explanatory. Whenused in a model a factor is automatically converted to a set of``contrasts'' (there is a technical definition of the term``contrast'' in linear models that is not always fulfilled by thesederived variables) and the corresponding coefficients are givenmeaningful names.When there is a natural ordering of the levels of a factor it can becreated as an ordered factor that will preserve this ordering.The labels can be set after the factor is created or as part of thecreation of the factor. Below we will create a ``long form'' of thedata where each row corresponds to a combination of student andgrade. In doing this we will need to concatenate related columns ofthe original data frame. For example, the columns \code{cltypek},\code{cltype1}, \code{cltype2} and \code{cltype3} will be concatenatedto form a single column \code{cltype}. If the coding is consistentacross the grades then it is easiest to concatenate the integer codesand set the labels on the ``long'' version of the variable.However there are two groups of variables, \code{hdeg} and\code{clad}, that are not coded consistently. In each case the codesused for kindergarten teachers are different from those used forteachers of grades 1 to 3 classes. The codes for kindergartenteachers are a superset of those for the other teachers but thenumbering is not consistent; a bachelor's degree is coded as 2 forkindergarten but 1 for the others. Thus we cannot combine the numericvalues - we must create the labels for each column and thenconcatenate the labels and convert to a factor.<<factorlevels>>=orig$hdegk <- ordered(orig$hdegk, levels = 1:6,labels = c("ASSOC","BS/BA","MS/MA/MEd","MA+","Ed.S","Ed.D/Ph.D"))orig$hdeg1 <- ordered(orig$hdeg1, levels = 1:4,labels = c("BS/BA","MS/MA/MEd","Ed.S","Ed.D/Ph.D"))orig$hdeg2 <- ordered(orig$hdeg2, levels = 1:4,labels = c("BS/BA","MS/MA/MEd","Ed.S","Ed.D/Ph.D"))orig$hdeg3 <- ordered(orig$hdeg3, levels = 1:4,labels = c("BS/BA","MS/MA/MEd","Ed.S","Ed.D/Ph.D"))orig$cladk <- factor(orig$cladk, levels = c(1:3,5:8),labels = c("1","2","3","APPR","PROB","NOT","PEND"))orig$clad1 <- factor(orig$clad1, levels = 1:6,labels = c("NOT","APPR","PROB","1","2","3"))orig$clad2 <- factor(orig$clad2, levels = 1:6,labels = c("NOT","APPR","PROB","1","2","3"))orig$clad3 <- factor(orig$clad3, levels = 1:6,labels = c("NOT","APPR","PROB","1","2","3"))@\section{Creating separate data frames}\label{sec:separate}These data are represented in a ``wide'' format where each rowcorresponds to a student. Some of the columns, such as \code{ssex},are indeed a property of the student; some, such as \code{hdegk} areproperties of teachers; some, such as \code{schtypek} are propertiesof schools or classes in schools; and some are unique to astudent/grade combination. We will create separate frames for each ofthese types.The first 5 columns are student-level data<<student>>=student <- orig[1:5]names(student) <- c("id", "sx", "eth", "birthq", "birthy")levels(student$sx) <- c("M", "F")levels(student$eth) <- c("W", "B", "A", "H", "I", "O")student$birthy <- ordered(student$birthy)student$birthq <- ordered(paste(student$birthy,student$birthq,sep=":"))summary(student)@The other columns refer to a combination of the student and grade. Wefirst create an expanded or ``long'' version of the table with a rowfor each student/grade combination.To create the long version of the table we repeat the student ids fourtimes and add a column for the grade level. Related groups ofcolumns, such as \code{cltypek}, \code{cltype1}, \code{cltype2} and\code{cltype3}, are concatenated then converted to a factor. However,there are two groups, \code{hdeg} and \code{clad}, for which thisapproach will not work because these groups are not encodedconsistently.<<long>>=long <- data.frame(id = rep(orig$newid, 4),gr = ordered(rep(c("K", 1:3), each = nrow(orig)),levels = c("K", 1:3)),star = factor(unlist(orig[6:9])),cltype = factor(unlist(orig[10:13])),schtype = factor(unlist(orig[c(14,22,30,38)])),hdeg = ordered(unlist(lapply(orig[c(15,24,32,43)],as.character)),levels = c("ASSOC","BS/BA","MS/MA/MEd","MA+","Ed.S","Ed.D/Ph.D")),clad = factor(unlist(lapply(orig[c(16,25,33,44)],as.character)),levels = c("NOT","APPR","PROB","PEND","1","2","3")),exp = unlist(orig[c(17,26,34,45)]),trace = factor(unlist(orig[c(18,23,31,42)]), levels=1:6,labels=c("W", "B", "A", "H", "I", "O")),read = unlist(orig[c(19,27,35,39)]),math = unlist(orig[c(20,28,36,40)]),ses = factor(unlist(orig[c(21,29,37,41)]),labels=c("F","N")),sch = factor(unlist(orig[50:53])))@We can now eliminate the combinations that are completely missing. Checking<<summarylong>>=summary(long)@indicates that fewest missing values are in the \code{sch},\code{cltype}, and \code{schtype} columns. They are also consistent<<isnacheck>>=with(long, all.equal(is.na(schtype), is.na(sch)))with(long, all.equal(is.na(cltype), is.na(sch)))@hence we use these to subset the data frame<<longsubset>>=long <- long[!is.na(long$sch),]@It turns out that we could have used the \code{star} column as thissimply indicates if the student was in the study that year.<<summlong>>=summary(long[1:5])@Because it now contains no information we will drop it.<<dropstar>>=long$star <- NULL@For convenience we set the row names of this data frame to be a combination of the student id and the grade.<<rownames>>=rownames(long) <- paste(long$id, long$gr, sep = '/')@We can extract the school-level data from this table.<<schooltable>>=school <- unique(long[, c("sch", "schtype")])length(levels(school$sch)) == nrow(school)row.names(school) <- school$schschool <- school[order(as.integer(as.character(school$sch))),]long$schtype <- NULLlevels(school$schtype) <- c("inner","suburb","rural","urban")levels(long$cltype) <- c("small", "reg", "reg+A")@We can create a merged data set with<<merging>>=star <- merge(merge(long, school, by = "sch"), student, by = "id")star$time <- as.integer(star$gr) - 1@\section{Assigning teacher ids}\label{sec:teacher}There are no teacher id numbers available but we can obtain areasonably accurate surrogate by determining the unique combinationsof all the variables associated with the teacher.<<teacher>>=teacher <- unique(star[, c("cltype", "trace", "exp", "clad", "gr","hdeg", "sch")])teacher <- teacher[with(teacher, order(sch, gr, cltype, exp, hdeg,clad, trace)), ]@To generate the correspondence between the observations and theteacher we create labels that incorporate the levels of each of thevariables that defined the unique combinations.<<teacherlabels>>=row.names(teacher) <- tch <- teacher$tch <- seq(nrow(teacher))names(tch) <- do.call("paste", c(teacher[,1:7], list(sep=":")))star$tch <- tch[do.call("paste", c(star[c("cltype", "trace", "exp","clad", "gr", "hdeg", "sch")],list(sep = ":")))]@We can check if this is successful by generating tables of class sizes.<<classsizes>>=table(table(star$tch))table(table(subset(star, cltype == "small")$tch))table(table(subset(star, cltype == "reg")$tch))table(table(subset(star, cltype == "reg+A")$tch))@We see that there are three classes with sizes greater than 30 andthat one of these is labelled as a ``small'' class. It is likely thateach of these represents two or more classes but we do not have enoughinformation to distinguish them.\section{Initial model fits}\label{sec:initial}Some initial model fits are<<initialModel>>=library(lme4)(mm1 <- lmer(math ~ gr + sx + eth + cltype + (1|id) + (1|sch), star))(rm1 <- lmer(read ~ gr + sx + eth + cltype + (1|id) + (1|sch), star))@\end{document}