<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-8557918062988474595</id><updated>2009-09-29T13:50:20.917-04:00</updated><title type='text'>Computational Ecology</title><subtitle type='html'>See Todd Jobe's thoughts about computers and ecology.  R code.  Arc-GIS and GRASS code.  Bash scripts.  Matlab code.</subtitle><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.unc.edu/~toddjobe/blog/atom.xml'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8557918062988474595.post-4360165287495531220</id><published>2009-09-18T14:58:00.006-04:00</published><updated>2009-09-18T15:39:23.826-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='error handling'/><category scheme='http://www.blogger.com/atom/ns#' term='parallel processing'/><category scheme='http://www.blogger.com/atom/ns#' term='power analysis'/><category scheme='http://www.blogger.com/atom/ns#' term='simulation'/><category scheme='http://www.blogger.com/atom/ns#' term='mixed-effect models'/><category scheme='http://www.blogger.com/atom/ns#' term='R'/><title type='text'>Power Analysis for mixed-effect models in R</title><content type='html'>&lt;p&gt;The power of a statistical test is the probability that a null&lt;br /&gt;hypothesis will be rejected when the alternative hypothesis is true.&lt;br /&gt;In lay terms, power is your ability to refine or "prove" your&lt;br /&gt;expectations from the data you collect.  The most frequent motivation&lt;br /&gt;for estimating the power of a study is to figure out what sample size&lt;br /&gt;will be needed to observe a treatment effect.  Given a set of pilot&lt;br /&gt;data or some other estimate of the variation in a sample, we can use&lt;br /&gt;power analysis to inform how much additional data we should collect.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;I recently did a power analysis on a set of pilot data for a long-term&lt;br /&gt;monitoring study of the US National Park Service.  I thought I would&lt;br /&gt;share some of the things I learned and a bit of R code for others that&lt;br /&gt;might need to do something like this.  If you aren't into power&lt;br /&gt;analysis, the code below may still be useful as examples of how to use&lt;br /&gt;the error handling functions in R (&lt;code&gt;withCallingHandlers&lt;/code&gt;,&lt;br /&gt;&lt;code&gt;withRestarts&lt;/code&gt;), parallel programming using the &lt;code&gt;snow&lt;/code&gt;&lt;br /&gt;package, and linear mixed effect regression using &lt;code&gt;nlme&lt;/code&gt;.  If you&lt;br /&gt;have any suggestions for improvement or if I got something wrong on&lt;br /&gt;the analysis, I'd love to hear from you.&lt;br /&gt;&lt;/p&gt;&lt;div id="outline-container-1" class="outline-2"&gt;&lt;br /&gt;&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; The Study &lt;/h2&gt;&lt;br /&gt;&lt;div class="outline-text-2" id="text-1"&gt;&lt;br /&gt;&lt;p&gt;The study system was cobblebars along the Cumberland river in Big&lt;br /&gt;South Fork National Park (Kentucky and Tennessee, United States).&lt;br /&gt;Cobblebars are typically dominated by grassy vegetation that include&lt;br /&gt;disjunct tall-grass prairie species.  It is hypothesized that woody&lt;br /&gt;species will encroach onto cobblebars if they are not seasonally&lt;br /&gt;scoured by floods.  The purpose of the NPS sampling was to observe&lt;br /&gt;changes in woody cover through time.  The study design consisted of&lt;br /&gt;two-stages of clustering: the first being cobblebars, and the second&lt;br /&gt;being transects within cobblebars.  The response variable was the&lt;br /&gt;percentage of the transect that was woody vegetation.  Because of&lt;br /&gt;the clustered design, the inferential model for this study design&lt;br /&gt;has mixed-effects.  I used a simple varying intercept&lt;br /&gt;model:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="ltxpng/powerAnalysis_0001.png"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;where &lt;i&gt;y&lt;/i&gt; is the percent of each transect in woody vegetation sampled&lt;br /&gt;&lt;i&gt;n&lt;/i&gt; times within &lt;i&gt;J&lt;/i&gt; cobblebars, each with &lt;i&gt;K&lt;/i&gt; transects.  The&lt;br /&gt;parameter of inference for the purpose of monitoring change in woody&lt;br /&gt;vegetation through time is &lt;i&gt;&amp;beta;&lt;/i&gt;, the rate at which cover changes as&lt;br /&gt;a function of time.  &lt;i&gt;&amp;alpha;&lt;/i&gt;, &lt;i&gt;&amp;gamma;&lt;/i&gt;, &lt;i&gt;&amp;sigma;&lt;sup&gt;2&lt;/sup&gt;&lt;sub&gt;&amp;gamma;&lt;/sub&gt;&lt;/i&gt;, and&lt;br /&gt;&lt;i&gt;&amp;sigma;&lt;sup&gt;2&lt;/sup&gt;&lt;sub&gt;y&lt;/sub&gt;&lt;/i&gt; are hyper-parameters that describe the hierarchical&lt;br /&gt;variance structure inherent in the clustered sampling design.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Below is the function code used I used to regress the pilot data.  It&lt;br /&gt;should be noted that with this function you can log or logit transform&lt;br /&gt;the response variable (percentage of transect that is woody).  I put&lt;br /&gt;this in because the responses are proportions (0,1) and errors should&lt;br /&gt;technically follow a beta-distribution.  Log and logit transforms with&lt;br /&gt;Gaussian errors could approximate this. I ran all the models with&lt;br /&gt;transformed and untransformed response, and the results did not vary&lt;br /&gt;at all.  So, I stuck with untransformed responses:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;Model&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(x = cobblebars,&lt;br /&gt;  type = c(&lt;span style="color: #bc8f8f;"&gt;"normal"&lt;/span&gt;,&lt;span style="color: #bc8f8f;"&gt;"log"&lt;/span&gt;,&lt;span style="color: #bc8f8f;"&gt;"logit"&lt;/span&gt;)){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Transforms&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (type[1] == &lt;span style="color: #bc8f8f;"&gt;"log"&lt;/span&gt;)&lt;br /&gt;    x$prop.woody &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; log(x$prop.woody)&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (type[1] == &lt;span style="color: #bc8f8f;"&gt;"logit"&lt;/span&gt;)&lt;br /&gt;    x$prop.woody &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; log(x$prop.woody / (1 - x$prop.woody))&lt;br /&gt;&lt;br /&gt;  mod &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; lme(prop.woody ~ year,&lt;br /&gt;             data = x,&lt;br /&gt;             random = ~ 1 | cobblebar/transect,&lt;br /&gt;             na.action = na.omit,&lt;br /&gt;             control = lmeControl(opt = &lt;span style="color: #bc8f8f;"&gt;"optim"&lt;/span&gt;,&lt;br /&gt;               maxIter = 800, msMaxIter = 800)&lt;br /&gt;             )&lt;br /&gt;  mod$type &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; type[1]&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(mod)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Here are the results from this regression of the pilot data:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;Linear mixed-effects model fit by REML&lt;br /&gt; Data: x &lt;br /&gt;        AIC       BIC   logLik&lt;br /&gt;  -134.4319 -124.1297 72.21595&lt;br /&gt;&lt;br /&gt;Random effects:&lt;br /&gt; Formula: ~1 | cobblebar&lt;br /&gt;        (Intercept)&lt;br /&gt;StdDev:  0.03668416&lt;br /&gt;&lt;br /&gt; Formula: ~1 | transect %&lt;span style="color: #a020f0;"&gt;in&lt;/span&gt;% cobblebar&lt;br /&gt;        (Intercept)   Residual&lt;br /&gt;StdDev:  0.02625062 0.05663784&lt;br /&gt;&lt;br /&gt;Fixed effects: prop.woody ~ year &lt;br /&gt;                  Value  Std.Error DF   t-value p-value&lt;br /&gt;(Intercept)  0.12966667 0.01881983 29  6.889896  0.0000&lt;br /&gt;year        -0.00704598 0.01462383 29 -0.481815  0.6336&lt;br /&gt; Correlation: &lt;br /&gt;     (Intr)&lt;br /&gt;year -0.389&lt;br /&gt;&lt;br /&gt;Number of Observations: 60&lt;br /&gt;Number of Groups: &lt;br /&gt;              cobblebar transect %&lt;span style="color: #a020f0;"&gt;in&lt;/span&gt;% cobblebar &lt;br /&gt;                      6                      30 &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="outline-container-2" class="outline-2"&gt;&lt;br /&gt;&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; We don't learn about power analysis and complex models &lt;/h2&gt;&lt;br /&gt;&lt;div class="outline-text-2" id="text-2"&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;When I decided upon the inferential model the first thing that&lt;br /&gt;occurred to me was that I never learned in any statistics course I&lt;br /&gt;had taken how to do such a power analysis on a multi-level model.&lt;br /&gt;I've taken more statistics courses than I'd like to count and taught&lt;br /&gt;my own statistics courses for undergrads and graduate students, and&lt;br /&gt;the only exposure to power analysis that I had was in the context of&lt;br /&gt;simple t-tests or ANOVA.  You learn about it in your first 2&lt;br /&gt;statistics courses, then it rarely if ever comes up again until you&lt;br /&gt;actually need it.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;I was, however, able to find a great resource on power analysis from&lt;br /&gt;a Bayesian perspective in the excellent book "Data Analysis Using&lt;br /&gt;Regression and Multilevel/Hierarchical Models" by Andrew Gelman and&lt;br /&gt;Jennifer Hill.  Andrew Gelman has thought and debated about power&lt;br /&gt;analysis and you can get more from his &lt;a href="http://www.stat.columbia.edu/~gelman/blog/"&gt;blog&lt;/a&gt;.  The approach in the&lt;br /&gt;book is a simulation-based one and I have adopted it for this&lt;br /&gt;analysis.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="outline-container-3" class="outline-2"&gt;&lt;br /&gt;&lt;h2 id="sec-3"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Analysis Procedure &lt;/h2&gt;&lt;br /&gt;&lt;div class="outline-text-2" id="text-3"&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For the current analysis we needed to know three things: effect&lt;br /&gt;size, sample size, and estimates of population variance. We set&lt;br /&gt;effect size beforehand.  In this context, the parameter of interest&lt;br /&gt;is the rate of change in woody cover through time &lt;i&gt;&amp;beta;&lt;/i&gt;, and&lt;br /&gt;effect size is simply how large or small a value of &lt;i&gt;&amp;beta;&lt;/i&gt; you want&lt;br /&gt;to distinguish with a regression.  Sample size is also set &lt;i&gt;a   priori&lt;/i&gt;. In the analysis we want to vary sample size by varying the&lt;br /&gt;number of cobblebars, the number of transects per cobblebar or the&lt;br /&gt;number of years the study is conducted.  &lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;The population variance cannot be known precisely, and this is where&lt;br /&gt;the pilot data come in.  By regressing the pilot data using the&lt;br /&gt;model we can obtain estimates of all the different components of the&lt;br /&gt;variance (cobblebars, transects within cobblebars, and the residual&lt;br /&gt;variance).  Below is the R function that will return all the&lt;br /&gt;hyperparameters (and &lt;i&gt;&amp;beta;&lt;/i&gt;) from the regression:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;GetHyperparam&lt;/span&gt;&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;&lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(x,b=&lt;span style="color: #228b22;"&gt;NULL&lt;/span&gt;){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Get the hyperparameters from the mixed effect model&lt;br /&gt;&lt;/span&gt;  fe &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; fixef(x)&lt;br /&gt;  &lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(is.null(b))&lt;br /&gt;    b&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;fe[2] &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;use the data effect size if not supplied&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;  mu.a &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; fe[1] &lt;br /&gt;&lt;br /&gt;  vc &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; VarCorr(x)&lt;br /&gt;  sigma.y &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; as.numeric(vc[5, 2]) &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Residual StdDev&lt;br /&gt;&lt;/span&gt;  sigma.a &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; as.numeric(vc[2, 2]) &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Cobblebar StdDev&lt;br /&gt;&lt;/span&gt;  sigma.g &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; as.numeric(vc[4, 2]) &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Cobblebar:transect StdDev&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;  hp&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;c(b, mu.a, sigma.y, sigma.a, sigma.g)&lt;br /&gt;  names(hp)&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;c(&lt;span style="color: #bc8f8f;"&gt;"b"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"mu.a"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"sigma.y"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"sigma.a"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"sigma.g"&lt;/span&gt;)&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(hp)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;To calculate power we to regress the simulated data in the same way we&lt;br /&gt;did the pilot data, and check for a significant &lt;i&gt;&amp;beta;&lt;/i&gt;.  Since&lt;br /&gt;optimization is done using numeric methods there is always the chance&lt;br /&gt;that the optimization will not work.  So, we make sure the regression&lt;br /&gt;on the fake data catches and recovers from all errors.  The solution&lt;br /&gt;for error recovery is to simply try the regression on a new set of&lt;br /&gt;fake data.  This function is a pretty good example of using the R&lt;br /&gt;error handling function &lt;code&gt;withCallingHandlers&lt;/code&gt; and&lt;br /&gt;&lt;code&gt;withRestarts&lt;/code&gt;.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;fakeModWithRestarts&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(m.o, n = 100,  ...){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;A Fake Model&lt;br /&gt;&lt;/span&gt;  withCallingHandlers({&lt;br /&gt;    i &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; 0&lt;br /&gt;    mod &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #228b22;"&gt;NULL&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;while&lt;/span&gt; (i &amp;lt; n &amp;amp; is.null(mod)){&lt;br /&gt;      mod &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; withRestarts({&lt;br /&gt;        f &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; fake(m.orig = m.o, transform = F, ...)&lt;br /&gt;        &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(update(m.o, data = f))&lt;br /&gt;      },&lt;br /&gt;      rs = &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(){&lt;br /&gt;        i &lt;span style="color: #5f9ea0;"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; i + 1&lt;br /&gt;        &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(&lt;span style="color: #228b22;"&gt;NULL&lt;/span&gt;)&lt;br /&gt;      })&lt;br /&gt;    }&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(is.null(mod))&lt;br /&gt;      &lt;span style="color: #a020f0;"&gt;warning&lt;/span&gt;(&lt;span style="color: #bc8f8f;"&gt;"ExceededIterations"&lt;/span&gt;)&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(mod)&lt;br /&gt;  },&lt;br /&gt;  error = &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(e){&lt;br /&gt;    invokeRestart(&lt;span style="color: #bc8f8f;"&gt;"rs"&lt;/span&gt;)&lt;br /&gt;  },&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;warning&lt;/span&gt; = &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(w){&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(w$&lt;span style="color: #a020f0;"&gt;message&lt;/span&gt; == &lt;span style="color: #bc8f8f;"&gt;"ExceededIterations"&lt;/span&gt;)&lt;br /&gt;      cat(&lt;span style="color: #bc8f8f;"&gt;"\n"&lt;/span&gt;, w$&lt;span style="color: #a020f0;"&gt;message&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"\n"&lt;/span&gt;)&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt;&lt;br /&gt;      invokeRestart(&lt;span style="color: #bc8f8f;"&gt;"rs"&lt;/span&gt;)&lt;br /&gt;  })&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;To calculate the power of a particular design we run&lt;br /&gt;&lt;code&gt;fakeModWithRestarts&lt;/code&gt; 1000 times and look at the proportion of&lt;br /&gt;significant &lt;i&gt;&amp;beta;&lt;/i&gt; values:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;dt.power&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt; (m, n.sims = 1000, alpha=0.05, ...){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Calculate power for a particular sampling design&lt;br /&gt;&lt;/span&gt;  signif&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;rep(&lt;span style="color: #228b22;"&gt;NA&lt;/span&gt;, n.sims)&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;for&lt;/span&gt;(i &lt;span style="color: #a020f0;"&gt;in&lt;/span&gt; 1:n.sims){&lt;br /&gt;    lme.power &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; fakeModWithRestarts(m.o = m, ...)&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(!is.null(lme.power))&lt;br /&gt;      signif[i] &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; summary(lme.power)$tTable[2, 5] &amp;lt; alpha&lt;br /&gt;  }&lt;br /&gt;  power &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; mean(signif, na.rm = T)&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(power)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Finally, we want to perform this analysis on many different sampling&lt;br /&gt;designs.  In my case I did all combinations of set of effect sizes,&lt;br /&gt;cobblebars, transects, and years.  So, I generated the appropriate designs:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;factoredDesign&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(Elevs = 0.2/c(1,5,10,20),&lt;br /&gt;                           Nlevs = seq(2, 10, by = 2),&lt;br /&gt;                           Jlevs = seq(4, 10, by = 2),&lt;br /&gt;                           Klevs = c(3, 5, 7), ...){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Generates factored series of sampling designs for simulation&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;of data that follow a particular model.&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Inputs:&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Elevs - vector of effect sizes for the slope parameter.&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Nlevs - vector of number of years to sample.&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Jlevs - vector of number of cobblebars to sample.&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Klevs - vector of number of transects to sample.&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Results:&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Data frame with where columns are the factors and&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;##   &lt;/span&gt;&lt;span style="color: #b22222;"&gt;rows are the designs.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Level lengths&lt;br /&gt;&lt;/span&gt;  lE &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; length(Elevs)&lt;br /&gt;  lN &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; length(Nlevs)&lt;br /&gt;  lJ &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; length(Jlevs)&lt;br /&gt;  lK &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; length(Klevs)&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Generate repeated vectors for each factor&lt;br /&gt;&lt;/span&gt;  E &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rep(Elevs, each = lN*lJ*lK)&lt;br /&gt;  N &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rep(rep(Nlevs, each = lJ*lK), times = lE)&lt;br /&gt;  J &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rep(rep(Jlevs, each = lK), times = lE*lN)&lt;br /&gt;  K &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rep(Klevs, times = lE*lN*lJ)&lt;br /&gt;  &lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(data.frame(E, N, J, K))&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Once we know our effect sizes, the different sample sizes we want,&lt;br /&gt;and the estimates of population variance we can generate simulated&lt;br /&gt;dataset that are similar to the pilot data.  To calculate power we&lt;br /&gt;simply simulate a large number of dataset and calculate the&lt;br /&gt;proportion of slopes, &lt;i&gt;&amp;beta;&lt;/i&gt; that are significantly different from&lt;br /&gt;zero (p-value &amp;lt; 0.05).  This procedure is repeated for all the&lt;br /&gt;effect sizes and sample sizes of interest. Here is the code for&lt;br /&gt;generating a simulated dataset. It also does the work of doing the&lt;br /&gt;inverse transform of the response variables if necessary.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;fake&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(N = 2, J = 6, K = 5, b = &lt;span style="color: #228b22;"&gt;NULL&lt;/span&gt;, m.orig = mod,&lt;br /&gt;                 transform = &lt;span style="color: #228b22;"&gt;TRUE&lt;/span&gt;, ...){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Simulated Data for power analysis&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;N = Number of years&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;J = Number of cobblebars&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;K = Number of transects within cobblebars&lt;br /&gt;&lt;/span&gt;  year &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rep(0:(N-1), each = J*K)&lt;br /&gt;  cobblebar &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; factor(rep(rep(1:J, each = K), times = N))&lt;br /&gt;  transect &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; factor(rep(1:K, times = N*J))&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Simulated parameters&lt;br /&gt;&lt;/span&gt;  hp&lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt;GetHyperparam(x=m.orig)&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(is.null(b))&lt;br /&gt;    b &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; hp[&lt;span style="color: #bc8f8f;"&gt;'b'&lt;/span&gt;]&lt;br /&gt;  g &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rnorm(J*K, 0, hp[&lt;span style="color: #bc8f8f;"&gt;'sigma.g'&lt;/span&gt;])&lt;br /&gt;  a &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rnorm(J*K, hp[&lt;span style="color: #bc8f8f;"&gt;'mu.a'&lt;/span&gt;] + g, hp[&lt;span style="color: #bc8f8f;"&gt;'sigma.a'&lt;/span&gt;])&lt;br /&gt;  &lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Simulated responses&lt;br /&gt;&lt;/span&gt;  eta &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; rnorm(J*K*N, a + b * year, hp[&lt;span style="color: #bc8f8f;"&gt;'sigma.y'&lt;/span&gt;])&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (transform){&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (m.orig$type == &lt;span style="color: #bc8f8f;"&gt;"normal"&lt;/span&gt;){&lt;br /&gt;      y &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; eta&lt;br /&gt;      y[y &amp;gt; 1] &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; 1 &lt;span style="color: #b22222;"&gt;# &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Fix any boundary problems.&lt;br /&gt;&lt;/span&gt;      y[y &amp;lt; 0] &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; 0&lt;br /&gt;    }&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (m.orig$type == &lt;span style="color: #bc8f8f;"&gt;"log"&lt;/span&gt;){&lt;br /&gt;      y &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; exp(eta)&lt;br /&gt;      y[y &amp;gt; 1] &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; 1&lt;br /&gt;    }&lt;br /&gt;    &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (m.orig$type == &lt;span style="color: #bc8f8f;"&gt;"logit"&lt;/span&gt;)&lt;br /&gt;      y &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; exp(eta) / (1 + exp(eta))&lt;br /&gt;  }&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt;{&lt;br /&gt;    y &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; eta&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(data.frame(prop.woody = y, year, transect, cobblebar))&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Then I performed the power calculations on each of these designs.  This&lt;br /&gt;could take a long time, so I set this procedure to use parallel processing&lt;br /&gt;if needed.  Note that I had to re-~source~ the file with all the&lt;br /&gt;necessary functions for each processor.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;powerAnalysis&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(parallel = T, ...){&lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Full Power Analysis&lt;br /&gt;&lt;/span&gt;  &lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;Parallel&lt;br /&gt;&lt;/span&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt;(parallel){&lt;br /&gt;    closeAllConnections()&lt;br /&gt;    cl &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; makeCluster(7, type = &lt;span style="color: #bc8f8f;"&gt;"SOCK"&lt;/span&gt;)&lt;br /&gt;    on.exit(closeAllConnections())&lt;br /&gt;    clusterEvalQ(cl, &lt;span style="color: #5f9ea0;"&gt;source&lt;/span&gt;(&lt;span style="color: #bc8f8f;"&gt;"cobblebars2.r"&lt;/span&gt;))&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  &lt;span style="color: #b22222;"&gt;## &lt;/span&gt;&lt;span style="color: #b22222;"&gt;The simulations&lt;br /&gt;&lt;/span&gt;  dat &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; factoredDesign(...)&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;if&lt;/span&gt; (parallel){&lt;br /&gt;    dat$power &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; parRapply(cl, dat, &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(x,...){&lt;br /&gt;      dt.power(N = x[2], J = x[3], K = x[4], b = x[1], ...)&lt;br /&gt;    }, ...)&lt;br /&gt;  } &lt;span style="color: #a020f0;"&gt;else&lt;/span&gt; {&lt;br /&gt;    dat$power &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; apply(dat, 1, &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(x, ...){&lt;br /&gt;      dt.power(N = x[2], J = x[3], K = x[4], b = x[1], ...)&lt;br /&gt;    }, ...)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: #a020f0;"&gt;return&lt;/span&gt;(dat)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;The output of the &lt;code&gt;powerAnalysis&lt;/code&gt; function is a data frame with&lt;br /&gt;columns for the power and all the sample design settings.  So, I wrote&lt;br /&gt;a custom plotting function for this data frame:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="src src-R"&gt;&lt;span style="color: #0000ff;"&gt;plotPower&lt;/span&gt; &lt;span style="color: #5f9ea0;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(dt){&lt;br /&gt;  xyplot(power~N|J*K, data = dt, groups = E,&lt;br /&gt;         panel = &lt;span style="color: #a020f0;"&gt;function&lt;/span&gt;(...){panel.xyplot(...)&lt;br /&gt;                               panel.abline(h = 0.8, lty = 2)},&lt;br /&gt;         type = c(&lt;span style="color: #bc8f8f;"&gt;"p"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"l"&lt;/span&gt;),&lt;br /&gt;         xlab = &lt;span style="color: #bc8f8f;"&gt;"sampling years"&lt;/span&gt;,&lt;br /&gt;         ylab = &lt;span style="color: #bc8f8f;"&gt;"power"&lt;/span&gt;,&lt;br /&gt;         strip = strip.custom(var.name = c(&lt;span style="color: #bc8f8f;"&gt;"C"&lt;/span&gt;, &lt;span style="color: #bc8f8f;"&gt;"T"&lt;/span&gt;),&lt;br /&gt;           strip.levels = c(T, T)),&lt;br /&gt;         auto.key = T&lt;br /&gt;         )&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Below is the figure for the cobblebar power analysis.  I won't go into&lt;br /&gt;detail on what the results mean since I am concerned here with&lt;br /&gt;illustrating the technique and the R code.  Obviously, as the number of&lt;br /&gt;cobblebars and transects per year increase, so does power.  And, as&lt;br /&gt;the effect size increases, observing it with a test is easier.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="ltxpng/powerAnalysis_0002.png"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div id="postamble"&gt;&lt;br /&gt;&lt;p class="author"&gt; Author: Todd Jobe&lt;br /&gt;&lt;a href="mailto:toddjobe@unc.edu"&gt;&amp;lt;toddjobe@unc.edu&amp;gt;&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p class="date"&gt; Date: 2009-09-18 Fri&lt;/p&gt;&lt;br /&gt;&lt;p class="creator"&gt;HTML generated by org-mode 6.30trans in emacs 22&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8557918062988474595-4360165287495531220?l=www.unc.edu%2F%7Etoddjobe%2Fblog'/&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/4360165287495531220/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/09/power-analysis-for-mixed-effect-models.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/4360165287495531220'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/4360165287495531220'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/09/power-analysis-for-mixed-effect-models.html' title='Power Analysis for mixed-effect models in R'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02619324934317304202'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8557918062988474595.post-851937783058829180</id><published>2009-06-15T08:40:00.007-04:00</published><updated>2009-06-15T12:03:44.608-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='latex'/><category scheme='http://www.blogger.com/atom/ns#' term='ecology'/><title type='text'>Ecology Journals LaTeX Class</title><content type='html'>LaTeX is a document markup language used to create manuscripts that have nice typesetting.  Professional looking documents can be created from simple text files.  It is particularly useful for creating manuscripts that have math equations in them.&lt;br /&gt;&lt;br /&gt;Writing documents in LaTeX should allow authors to focus on content of the manuscript rather than the formatting.  All La-tex documents are associated with a class file which sets the formatting for the document.  Users simply associate a document with a class and the text is formatted appropriately.  Unfortunately, many academic journals have very specific formatting requirements not built into LaTeX.&lt;br /&gt;&lt;br /&gt;The journals of the &lt;a href="http://www.esa.org"&gt;Ecological Society of America&lt;/a&gt; do not have LaTeX class files appropriate for their major journals: Ecology, Ecological Applications and Ecological Monographs (even though they will accept documents in LaTeX format).  One user-created LaTeX class file for ecology is available &lt;a href="http://floliveweb.free.fr/htm/tele.php?lang=fr"&gt;here&lt;/a&gt;.  However, this class formats a LaTeX file into a proof-style document, not a submission-style document.&lt;br /&gt;&lt;br /&gt;I have recently been writing a submission for Ecological Applications that is in LaTeX.  I had to write my own class file for formatting this document according to the ESA guidelines (&lt;a href="http://esapubs.org/esapubs/preparation.htm"&gt;http://esapubs.org/esapubs/preparation.htm&lt;/a&gt;).  I am sure that others have run into the same problem.  So, I am making my class file publicly available.&lt;br /&gt;&lt;br /&gt;Please consider this class to be in alpha.  I have not, yet, written support for multiple author addresses simply because I didn't need it for the current manuscript.  Feel free to improve the class.  Just let me know about it, so I can use it as well.&lt;br /&gt;&lt;br /&gt;To use this class in a LaTeX document, you can either install the class file into your LaTeX distribution or simply place the file in the same folder as your LaTeX document.  Bibliography formatting using BibTex requires the &lt;a href="http://www.ctan.org/tex-archive/help/Catalogue/entries/natbib.html"&gt;natbib&lt;/a&gt; package be installed and the &lt;a href="http://www.math.ntnu.no/~jarlet/latex/ecology.bst"&gt;ecology.bst&lt;/a&gt; bibliography format file.  I have packaged the ecology.cls class along with an example latex file, the proof pdf, an example figure, and an example bibligraphy into a tarball available below.&lt;br /&gt;&lt;br /&gt;Download: &lt;a href="http://www.unc.edu/~toddjobe/src/ecologyLatexClass.tar.gz"&gt;ecologyLatexClass.tar.gz&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8557918062988474595-851937783058829180?l=www.unc.edu%2F%7Etoddjobe%2Fblog'/&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/851937783058829180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/06/ecology-journals-latex-class.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/851937783058829180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/851937783058829180'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/06/ecology-journals-latex-class.html' title='Ecology Journals LaTeX Class'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02619324934317304202'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8557918062988474595.post-750237721121533856</id><published>2009-06-01T15:40:00.006-04:00</published><updated>2009-06-01T16:26:23.131-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grass'/><category scheme='http://www.blogger.com/atom/ns#' term='csv'/><category scheme='http://www.blogger.com/atom/ns#' term='points'/><category scheme='http://www.blogger.com/atom/ns#' term='raster'/><title type='text'>Add a column to a text file from a raster</title><content type='html'>&lt;h2&gt;Text files are great&lt;/h2&gt;&lt;br /&gt;I have mixed feelings about software interoperability.  Sure GRASS interfaces with R, and R can interface with Excel and Access if you have the ODBC drivers.  But, often users do not have the energy to learn the best techniques for moving data back and forth between programs.  Further, there always seems to be some quirk with data that prevents the transfer from going as smoothly as the user's manual examples do.  Then, I remember the lowly text file.&lt;br /&gt;&lt;br /&gt;Whether you use comma separated values (csv) files or tab-delimited files, every software platform on the planet can read text files of records (lines) and fields (values on a line separated by commas,tabs or some other character).  If you're trying to send data to someone else, you can be confident that your recipient can read them.  It may not be the flashiest or fastest way to move data between people and systems, but it always works.  Finally, when you use plain text files for data, you have many more options for working with the files.  Tools such as bash, awk, sed, perl, and any other programming language you can think of will can be brought to bear on data analysis problems.&lt;br /&gt;&lt;h2&gt;Attaching raster data to points in GIS&lt;/h2&gt;&lt;br /&gt;Switch gears for a moment from a common file format to a common analysis task.  For researchers who work with spatial point patterns (like plots), a regular step in data analysis is to pull data from raster layers where they intersect the point data and add it as columns to the point data.  Informally, these are known as "drill-down" operations.  You have a stack of maps and you want to drill down through them vertically at point locations, grab the data, and add it to the points. &lt;br /&gt;&lt;br /&gt;This is an a relatively simple task in ArcGIS if you use &lt;a href="http://www.spatialecology.com/htools/tooldesc.php"&gt;Hawth's tools&lt;/a&gt;.  It's also pretty easy in GRASS, though it involves a couple of steps.  The function &lt;a href="http://grass.itc.it/gdp/html_grass63/v.what.rast.html"&gt;&lt;code&gt;v.what.rast&lt;/code&gt;&lt;/a&gt; will put raster values into a column of a point dataset.  The column must already exist in the dataset, which can be done through the command &lt;a href="http://grass.itc.it/gdp/html_grass63/v.db.addcol.html"&gt;&lt;code&gt;v.db.addcol&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;v.what.rast&lt;/code&gt; function just fills in the missing values with values where the raster intersects the points.&lt;br /&gt;&lt;h2&gt;Adding raster values to text files&lt;/h2&gt;&lt;br /&gt;Now, back to text files.  I recently worked with a graduate student who wanted elevation values from a lidar-derived digital elevation model (dem) on 4 samples totalling about 1100 plots.  She is not a GRASS user, and so she sent me the locations of all the plots along with an identifier in csv format.  I wanted to send her back the same csv files with elevation simply added as a field.  I wrote a bash script to accomplish this quickly and repeatably for her files.  In this script, all the GRASS functions are hidden from the user.  Users simply input a csv file and the raster column you wish to add, and a csv file is output with that column added.  The script requires the awk script that I &lt;a href="http://www.unc.edu/%7Etoddjobe/blog/2009/05/extract-variable-types-from-text-file.html"&gt;posted previously&lt;/a&gt;, which recognizes the types of varibles present in the input csv.  I have included a portion of the full script below.  GRASS has a very particaular code style that it uses for running scripts natively (See &lt;a href="http://grass.itc.it/gdp/html_grass63/g.parser.html"&gt;&lt;code&gt;g.parser&lt;/code&gt;&lt;/a&gt; for details).  This makes the script very long.  So, I've cut out the not-so-important parts.  If you would like the full file you can get it &lt;a href="http://www.unc.edu/%7Etoddjobe/src/v.ascii.addrastcol"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;v.ascii.addrastcol&lt;/h2&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;##################################################################&lt;br /&gt;#&lt;br /&gt;# MODULE:       v.ascii.addrastcol&lt;br /&gt;# AUTHOR(S):    R. Todd Jobe &lt;toddjobe@unc.edu&gt;&lt;br /&gt;# PURPOSE:      Add a raster value field to a csv of points&lt;br /&gt;# COPYRIGHT:    (C) 2009 by R. Todd Jobe&lt;br /&gt;#&lt;br /&gt;#  This program is free software; you can redistribute it and/or modify&lt;br /&gt;#  it under the terms of the GNU General Public License as published by&lt;br /&gt;#  the Free Software Foundation; either version 2 of the License, or&lt;br /&gt;#  (at your option) any later version.&lt;br /&gt;#&lt;br /&gt;#  This program is distributed in the hope that it will be useful,&lt;br /&gt;#  but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;#  GNU General Public License for more details.&lt;br /&gt;#&lt;br /&gt;##################################################################&lt;br /&gt;# . . . Grass header information removed&lt;br /&gt;# . . . The cut code creates variables GIS_OPT_* and GIS_FLAG_*&lt;br /&gt;# . . . Get the full code at:&lt;br /&gt;# . . . http://www.unc.edu/~toddjobe/src/v.ascii.addrastcol&lt;br /&gt;##################################################################&lt;br /&gt;# Need space-delimited arguments for looping&lt;br /&gt;GIS_OPT_INPUT=${GIS_OPT_INPUT//,/ }&lt;br /&gt;&lt;br /&gt;# test for input raster map&lt;br /&gt;eval `g.findfile element=cell file="$GIS_OPT_RAST"`&lt;br /&gt;if [ ! "$file" ] ; then&lt;br /&gt;   g.message -e "Raster map &lt;$GIS_OPT_RAST&gt; not found in mapset search path"&lt;br /&gt;   exit 1&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;for i in $GIS_OPT_INPUT; do&lt;br /&gt;   # Check that input files exist&lt;br /&gt;   if [ ! "$i" ] ; then&lt;br /&gt; echo "file $i does not exist" &gt;&amp;amp;2&lt;br /&gt; exit $E_NOTFOUND&lt;br /&gt;   fi&lt;br /&gt;&lt;br /&gt;   # Create header line if necessary and get variable types&lt;br /&gt;   if [ $GIS_FLAG_H -eq 1 ]; then&lt;br /&gt; skip=1&lt;br /&gt;   else&lt;br /&gt; skip=0&lt;br /&gt;   fi&lt;br /&gt;&lt;br /&gt;   # Intermediate variables&lt;br /&gt;   map=`basename $i .csv`&lt;br /&gt;   tmp=`tempfile -s ".csv"`&lt;br /&gt;   of="${map}${GIS_OPT_SUFFIX}"&lt;br /&gt;   col=`typeVars.awk -v h=$GIS_FLAG_H -F, $i`&lt;br /&gt;&lt;br /&gt;   # Get the raster type&lt;br /&gt;   if `r.info -t $GIS_OPT_RAST | grep -q "FCELL"`; then&lt;br /&gt; rtype="double precision"&lt;br /&gt;   else&lt;br /&gt; rtype="int"&lt;br /&gt;   fi&lt;br /&gt;  &lt;br /&gt;   # Grass-specific calls&lt;br /&gt;   v.in.ascii --overwrite input="$i" output="$map" fs="$GIS_OPT_FS" \&lt;br /&gt; skip=$skip column="$col" x="$GIS_OPT_X" y="$GIS_OPT_Y"&lt;br /&gt;   v.db.addcol map="$map" \&lt;br /&gt; columns="$GIS_OPT_RAST $rtype"&lt;br /&gt;   v.what.rast vector="$map" column="$GIS_OPT_RAST" \&lt;br /&gt; raster="$GIS_OPT_RAST"&lt;br /&gt;   v.out.ascii input="$map" fs="," \&lt;br /&gt; columns="$GIS_OPT_RAST" | \&lt;br /&gt;   awk -F, -v meters="$GIS_FLAG_M" -v head="$GIS_FLAG_H" \&lt;br /&gt; -v rast="$GIS_OPT_RAST" '&lt;br /&gt;   BEGIN{ if(head == 1) print rast;}&lt;br /&gt;   {&lt;br /&gt;        # Convert Ft. elevation to meters if necessary&lt;br /&gt;        if($4!="" &amp;amp;&amp;amp; meters==1) $4=$4 * 0.30480060&lt;br /&gt;        print $4&lt;br /&gt;   }' &gt;"$tmp"&lt;br /&gt;   # Join raster values to input and write to output&lt;br /&gt;   paste -d$GIS_OPT_FS "$i" "$tmp" &gt;"$of"&lt;br /&gt;&lt;br /&gt;   # Cleanup&lt;br /&gt;   rm "$tmp"&lt;br /&gt;   if [ $GIS_FLAG_R -eq 1 ] ; then&lt;br /&gt; g.remove -f --quiet vect=$map&lt;br /&gt;   fi&lt;br /&gt;done&lt;br /&gt;&lt;/toddjobe@unc.edu&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To see the useage for the full script, you can simply start GRASS and from the command line type &lt;code&gt;v.ascii.addrastcol --help&lt;/code&gt;.  Here's a short example for a random set of 25 points on Ft. Bragg North Carolina.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;GRASS 6.4.0RC3 (ftBragg):~ &gt; head locs.csv&lt;br /&gt;"","V1","V2","V3"&lt;br /&gt;"1",594927.510605283,153893.107849400,1&lt;br /&gt;"2",590884.780751329,155855.275043186,2&lt;br /&gt;"3",594095.580107841,155284.234116311,3&lt;br /&gt;"4",605646.865006463,154397.274762958,4&lt;br /&gt;"5",603002.815879479,153284.801461238,5&lt;br /&gt;"6",603453.085431226,152633.248500899,6&lt;br /&gt;"7",602981.046299442,152156.288326646,7&lt;br /&gt;"8",604160.50828444,151921.617483228,8&lt;br /&gt;"9",603752.189821169,152062.642227756,9&lt;br /&gt;GRASS 6.4.0RC3 (ftBragg):~ &gt; v.ascii.addrastcol --quiet -hr \&lt;br /&gt;input=locs.csv rast=iLandcover x=2 y=3 --quiet -r&lt;br /&gt;GRASS 6.4.0RC3 (ftBragg):~ &gt; head locs_isect.csv&lt;br /&gt;"","V1","V2","V3",iLandcover&lt;br /&gt;"1",594927.510605283,153893.107849400,1,3&lt;br /&gt;"2",590884.780751329,155855.275043186,2,3&lt;br /&gt;"3",594095.580107841,155284.234116311,3,1&lt;br /&gt;"4",605646.865006463,154397.274762958,4,3&lt;br /&gt;"5",603002.815879479,153284.801461238,5,3&lt;br /&gt;"6",603453.085431226,152633.248500899,6,3&lt;br /&gt;"7",602981.046299442,152156.288326646,7,3&lt;br /&gt;"8",604160.50828444,151921.617483228,8,1&lt;br /&gt;"9",603752.189821169,152062.642227756,9,3&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8557918062988474595-750237721121533856?l=www.unc.edu%2F%7Etoddjobe%2Fblog'/&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/750237721121533856/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/06/add-column-to-text-file-from-raster.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/750237721121533856'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/750237721121533856'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/06/add-column-to-text-file-from-raster.html' title='Add a column to a text file from a raster'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02619324934317304202'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8557918062988474595.post-7382480169972045849</id><published>2009-05-25T14:05:00.012-04:00</published><updated>2009-05-26T10:48:36.851-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='awk'/><category scheme='http://www.blogger.com/atom/ns#' term='text'/><title type='text'>Extract variable types from a text file using awk</title><content type='html'>The most difficult tasks in data analysis are not, from my experience, the statistical or analytic tasks.  Instead, the most time consuming and frustrating parts of analysis are simply extracting and transforming raw data into a format that can be analyzed by common software.  A common task in this vein is to determine what type of variables are contained in a text file containing fields and records (e.g. a comma separated values file or .csv).  Most commonly, these data types are one of either strings, integers or decimal numbers called doubles. Some software, such as &lt;a href="http://www.blogger.com/www.r-project.org"&gt;R&lt;/a&gt; does a good job at automatically recognizing what kinds of data are contained in a text file through commands like &lt;a href="http://wiki.r-project.org/rwiki/doku.php?id=rdoc:base:read.table"&gt;read.table&lt;/a&gt;.  Other software, such as the GIS software &lt;a href="http://grass.osgeo.org/"&gt;GRASS&lt;/a&gt;, may require you to explicitly input data types of each column of data.  I find that to be time consuming, especially when you may have dozens of columns or dozens of text files to work through.&lt;br /&gt;&lt;br /&gt;I have written the following script in &lt;a href="http://www.gnu.org/software/gawk/"&gt;awk&lt;/a&gt; (technically, gawk) to automatically determine what type of data is contained in a columnar text file. Awk is a programming language specifically designed to parse and transform these text files.  Awk scripts can often developed to be included as short snippets of larger bash scripts.  Such is the case here.  It could probably be done more easily in Perl, but I was already working on a bash script that this will go into, which I will post later.&lt;br /&gt;&lt;br /&gt;The task accomplished by the script is a simple one.  Create a comma separated string that lists the variable names and data types for each column in a text file.  This string will ultimately be used as input to the command &lt;a href="http://grass.itc.it/gdp/html_grass63/v.in.ascii.html"&gt;v.in.ascii&lt;/a&gt; in GRASS, which creates a point vector dataset from a text file.&lt;br /&gt;&lt;h2&gt;typeVars.awk&lt;/h2&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;#!/bin/awk -f&lt;br /&gt;##&lt;br /&gt;## file: typeVars.awk&lt;br /&gt;## created by: R. Todd Jobe &amp;#60toddjobe@unc.edu&gt;&lt;br /&gt;## date: 2009.05.25&lt;br /&gt;## This script will print the variable names and types from a &lt;br /&gt;## columnar text file.&lt;br /&gt;## Specifically, it can generate a variable declaration for GRASS &lt;br /&gt;## input.&lt;br /&gt;{ &lt;br /&gt;    # Set the types to default values if not defined&lt;br /&gt;    if(str=="") str="var"&lt;br /&gt;    if(it=="") it="integer"&lt;br /&gt;    if(dbl=="") dbl="double precision"&lt;br /&gt;&lt;br /&gt;    # Get the column name from the 1st row&lt;br /&gt;    if(NR==1){&lt;br /&gt; for(i=1; i &amp;#60= NF; i++){&lt;br /&gt;     type[i,1]=$(i)&lt;br /&gt; }&lt;br /&gt;    }else if(NR==2){&lt;br /&gt;&lt;br /&gt;        # Get the column type from the 2nd row&lt;br /&gt; j=0&lt;br /&gt; for(i=1; i &amp;#60= NF; i++){&lt;br /&gt;     if($i ~ /[^-.0-9]/){&lt;br /&gt;  type[i,2]=str&lt;br /&gt;  strf[++j]=i&lt;br /&gt;  if(str=="var"){&lt;br /&gt;      max[i]=length($i)&lt;br /&gt;      type[i,2]=sprintf("%s(%.0f)",str,max[i])&lt;br /&gt;  }&lt;br /&gt;     }else if($i ~ /\./){&lt;br /&gt;  type[i,2]=dbl&lt;br /&gt;     }else{&lt;br /&gt;  type[i,2]=it&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;    }else{&lt;br /&gt;        # Get the maximum column width for strings&lt;br /&gt; if(str=="var"){&lt;br /&gt;     for( k in strf ){&lt;br /&gt;  if(length($(k)) &gt; max[k]) {&lt;br /&gt;      max[k] = length ($k)&lt;br /&gt;      type[strf[k],2]=sprintf("%s(%.0f)",str,max[k])&lt;br /&gt;  }&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;END{&lt;br /&gt;    ORS=""&lt;br /&gt;    out=sprintf("%s %s",type[1,1],type[1,2])&lt;br /&gt;    for(l=2;l&amp;#60i;l++){&lt;br /&gt; out=sprintf("%s, %s %s",out,type[l,1],type[l,2])&lt;br /&gt;    }&lt;br /&gt;    print out&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;An Example&lt;/h2&gt;&lt;br /&gt;Here's an example of using the typeVars.awk script.  We begin with a csv file of different variable types.  typeVars.awk is executed on this csv and the output is a single string of comma separated column names with variable types.  By default the output is formatted for use in GRASS and strings are labelled with their maximum length.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;bash-3.2$ cat trial.csv&lt;br /&gt;astring,aint,adbl,plot,x,y&lt;br /&gt;abc123,1234,1.23,zyx1234,54,1.45&lt;br /&gt;abc1235,1234,2.587,z*x1234,360,1.45&lt;br /&gt;abc12,12345,1.23,zy.1234,1,1.45&lt;br /&gt;abc1,999999,1.23,zx1234,0,1.45&lt;br /&gt;bash-3.2$ echo `typeVars.awk -F, trial.csv`&lt;br /&gt;astring var(7), aint integer, adbl double precision, plot var(6), x integer, y double precision&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice -F parameter is specified in the call to typeVars.awk.  Whitespace is the default field separator in awk, so in order to parse a csv this needs to be set to a ",".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8557918062988474595-7382480169972045849?l=www.unc.edu%2F%7Etoddjobe%2Fblog'/&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/7382480169972045849/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/05/extract-variable-types-from-text-file.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/7382480169972045849'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/7382480169972045849'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/05/extract-variable-types-from-text-file.html' title='Extract variable types from a text file using awk'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02619324934317304202'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8557918062988474595.post-2237818456860558642</id><published>2009-05-14T11:47:00.003-04:00</published><updated>2009-05-26T11:22:27.658-04:00</updated><title type='text'>Time for a blog</title><content type='html'>&lt;div xmlns="http://www.w3.org/1999/xhtml"&gt;I've started this blog mainly as a place to post the various things I do with computers at my job.  I am a postdoctoral associate in the Geography Department at the University of North Carolina at Chapel Hill.  Though I work in a Geography department right now, I am really an ecologist.  Specifically, I am a quantitative community ecologist.  I think about how and why species occur together.  The "quantitative" part of that description just means that I typically answer ecological questions with the tools of mathematics, computer simulation modelling, and statistics.&lt;p&gt;Anyway, I think a lot about the best ways to analyze and present ecological data, and people constantly ask me questions about it.  I hope this blog can be a "pre-emptive strike" for answering these questions.  I plan to post lots of source code and comment on my own particular style of doing things.  Take what you like.  Leave the rest.&lt;/p&gt;    &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8557918062988474595-2237818456860558642?l=www.unc.edu%2F%7Etoddjobe%2Fblog'/&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/2237818456860558642/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/05/time-for-blog.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/2237818456860558642'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8557918062988474595/posts/default/2237818456860558642'/><link rel='alternate' type='text/html' href='http://www.unc.edu/~toddjobe/blog/2009/05/time-for-blog.html' title='Time for a blog'/><author><name>toddjobe</name><uri>http://www.blogger.com/profile/10065244958204541807</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02619324934317304202'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry></feed>