## TTD TANK PPT WETWT ## Min. : 8.0 Min. : 1.0 Min. : 0.0 Min. :0.024 ## 1st Qu.:24.0 1st Qu.: 4.0 1st Qu.:10.3 1st Qu.:0.068 ## Median :72.0 Median : 7.0 Median :11.6 Median :0.092 ## Mean :63.3 Mean : 7.5 Mean :11.7 Mean :0.135 ## 3rd Qu.:97.0 3rd Qu.:11.0 3rd Qu.:15.8 3rd Qu.:0.129 ## Max. :97.0 Max. :14.0 Max. :20.1 Max. :1.489 ## NA's :70 ## STDLGTH ## Min. :1.20 ## 1st Qu.:1.60 ## Median :1.70 ## Mean :1.87 ## 3rd Qu.:2.00 ## Max. :4.90 ## NA's :70
The data consists of 5 columns:
TTD : Time to death
TANK : Tank
PPT : NaCl Concentration
WETWT : wet weight
STDLGTH : Standard length
Columns 4 and 5 have 70 NA’s (no data available due to measurement error), but we won’t use these in this example. The observations with TTD = 97 are ‘survivors’, since the experiment run only 96 hours.
First we need to create a column FLAG for the status of the animal (dead/alive):
1
TOXICITY$FLAG <- ifelse(TOXICITY$TTD >96,1,2)
So 1 denotes alive and 2 dead.
Then we can plot the data. Each line is a tank and colors denote the NaCl concentrations.
1234
require(survival)mod <- survfit(Surv(TTD, FLAG) ~ PPT + strata(TANK), data = TOXICITY)plot(mod, col = rep(1:7, each =2), mark.time =FALSE, xlab ="Hours", ylab ="% Survival")legend("bottomleft", legend = sort(unique(TOXICITY$PPT)), col =1:7, lty =1)
We see a clear relationship between concentration and the survival curves. In this example we are interested in differences between the duplicates. We see that the two curves for the 11.6 g/L concentration are quite similar, while there is more divergence between tanks in the 13.2 g/L treatment.
We can test for differences using the survdiff function. With the rho argument we can specify the type of test: rho = 0 is a log-rank test and rho = 1 is equivalent to the Peto & Peto modification of the Gehan-Wilcoxon test.
First the log-rank test for each concentration:
1
survdiff(Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT ==10.3,], rho =0)
## ## 10.3 ## Call:## survdiff(formula = Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT == ## i, ], rho = 1)## ## N Observed Expected (O-E)^2/E (O-E)^2/V## TANK=9 40 5.41 8.21 0.952 2.28## TANK=10 38 10.09 7.29 1.071 2.28## ## Chisq= 2.3 on 1 degrees of freedom, p= 0.131 ## ## 10.8 ## Call:## survdiff(formula = Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT == ## i, ], rho = 1)## ## N Observed Expected (O-E)^2/E (O-E)^2/V## TANK=7 41 8.75 10.33 0.240 0.596## TANK=8 39 10.81 9.24 0.269 0.596## ## Chisq= 0.6 on 1 degrees of freedom, p= 0.44 ## ## 11.6 ## Call:## survdiff(formula = Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT == ## i, ], rho = 1)## ## N Observed Expected (O-E)^2/E (O-E)^2/V## TANK=5 37 15.4 14.8 0.0205 0.0524## TANK=6 41 15.9 16.5 0.0184 0.0524## ## Chisq= 0.1 on 1 degrees of freedom, p= 0.819 ## ## 13.2 ## Call:## survdiff(formula = Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT == ## i, ], rho = 1)## ## N Observed Expected (O-E)^2/E (O-E)^2/V## TANK=3 38 17.8 23.5 1.37 5.02## TANK=4 40 24.9 19.2 1.68 5.02## ## Chisq= 5 on 1 degrees of freedom, p= 0.0251 ## ## 15.8 ## Call:## survdiff(formula = Surv(TTD, FLAG) ~ TANK, data = TOXICITY[TOXICITY$PPT == ## i, ], rho = 1)## ## N Observed Expected (O-E)^2/E (O-E)^2/V## TANK=1 39 22.6 27.0 0.707 3.15## TANK=2 40 27.8 23.4 0.814 3.15## ## Chisq= 3.1 on 1 degrees of freedom, p= 0.076
Basically we get the same results as in the book:
None of the log-rank tests is statistically significant (at the 0.05 level).
The wilcoxon test for the 13.2 g/L treatment shows a p < 0.05.
This is also in agreement with the plot.
The $\chi^2$ values differ slightly but share the same trend - I suspect this is due to different data used.
With this dataset we can do much more. We already saw that there might be a relationship between survival time and concentration, but more on this later (example 4.10).
Code and data are available on my github-repo under file name ‘p176’.
The we can easily run fisher’s Exact test for this 2x2 table using the function fisher.test():
1
fisher.test(TEST)
1234567891011
## ## Fisher's Exact Test for Count Data## ## data: TEST ## p-value = 0.09148## alternative hypothesis: true odds ratio is not equal to 1 ## 95 percent confidence interval:## 0.0025451 1.2461419 ## sample estimates:## odds ratio ## 0.12883
1
fisher.test(TEST, alternative ="greater")
1234567891011
## ## Fisher's Exact Test for Count Data## ## data: TEST ## p-value = 0.9958## alternative hypothesis: true odds ratio is greater than 1 ## 95 percent confidence interval:## 0.0051465 Inf ## sample estimates:## odds ratio ## 0.12883
1
fisher.test(TEST, alternative ="less")
1234567891011
## ## Fisher's Exact Test for Count Data## ## data: TEST ## p-value = 0.04574## alternative hypothesis: true odds ratio is less than 1 ## 95 percent confidence interval:## 0.00000 0.96589 ## sample estimates:## odds ratio ## 0.12883
The data consists of number of dead animals (DEAD) from all animals (TOTAL) exposed to different concentrations (CONC).
First we create a new column with the proportion of dead animals:
1
NAP$PROP <- NAP$DEAD/NAP$TOTAL
Here is a plot of the data. Note the use of expression() (greek letters in the axis labels).
We can estimate the mean control mortality and the confidence interval for the mean using the t.test function:
12
contr_m <- t.test(NAP$PROP[NAP$CONC ==0])contr_m
1234567891011
## ## One Sample t-test## ## data: NAP$PROP[NAP$CONC == 0] ## t = 1.5811, df = 5, p-value = 0.1747## alternative hypothesis: true mean is not equal to 0 ## 95 percent confidence interval:## -0.0080228 0.0336638 ## sample estimates:## mean of x ## 0.012821
These can be also easily extracted from the t.test object:
12
## extract the values from t.test-object meancontr_m$estimate
This gives nearly the same values as in the book. I don’t know what the SAS option OPTC is doing or computing, however it seems like it is the mean +- CI for the control group.
Abbott’s formula
We could adjust for control mortality using Abbott’s formula:
with $p_c$ = the corrected, p = original and $p_0$ = control mortality.
plot(mod2, broken =TRUE, type ="all", bp =500, xt = seq(500,3000,500))mtext("Corrected mortalities - W1.2",3)
A model without fixed lower limit
The two-parameter log-logistic model from above (mod1) performs quite good. However its lower limit is fixed to 0 and the upper limit to 1.
Since we have a small amount of control mortality we could check if a model with varying lower limit (will be estimated) makes sense.
Let’s fit a three parameter log-logistic function, where the lower limit is an additional parameter:
123
mod3 <- drm(PROP ~ CONC, data = NAP, fct = LL.3u())plot(mod3, broken =TRUE, type ="all", bp =500, xt = seq(500,3000,500))mtext("Free (estimated) lower limit - LL3.u",3)
However looking at the summary we see that the lower limit is estimated as $0.007 \pm 0.02$ and is statistically not significant.
1
summary(mod3)
12345678910111213
## ## Model fitted: Log-logistic (ED50 as parameter) with upper limit at 1 (3 parms)## ## Parameter estimates:## ## Estimate Std. Error t-value p-value## b:(Intercept) -13.71026 1.11140 -12.33603 0.00## c:(Intercept) 0.00686 0.01958 0.35045 0.73## e:(Intercept) 2099.76948 13.38536 156.87056 0.00## ## Residual standard error:## ## 0.051716 (27 degrees of freedom)
Since the lower limit (=control mortality) is so low we could also stick with mod1 since it brings no improvement.
1
mselect(mod3, fctList = list(LL.2(), LL2.3u()))
1234
## logLik IC Lack of fit Res var## LL.2 47.803 -89.607 0.015649 0.0025908## LL.3u 47.872 -87.744 0.014383 0.0026746## LL2.3u 47.794 -87.587 0.014212 0.0026885
All three considered models give nearly the same $LC_{50}$ around 2100:
Recently I moved this blog from tumblr to github since I feeled I need more liberty.
I have no experience with web programming, so I chose Octopress as blogging framework since it nicely integrates with github.
However after relocation my workflow for generating posts was quite laborious - it was similar to the tumblr-workflow:
Create an .Rmd file
knit the file
move the file to octopress/source/_posts/
move the figures to octopress/source/figure/
and so on…
This was non-satisfying and I thought that this must be automated into a smooth workflow!
Last week I took a look at the new Rcpp Gallery and noticed that this is also hosted on github. I skimmed through their code and found a lot of useful stuff. Shamelessly I copied some files from the and modified them for my octopress needs.
Basically I have now a folder for my .Rmd files (octopress/source/src). Invoking make in this folder creates the markdown-files in _posts and sets up the right figure paths (in _posts/figure). My workflow now:
rake new_rmd[test], this sets up an empty .Rmd file in /src, according to the octopress naming-scheme and header.
add my post to this .Rmd file
invoke make in /src, this sets up the .markdown file in _posts and figures
the usual rake gen_deploy
I am quite happy at the moment with this workflow. I can also use R-Studio.
Here are the changes I made to my octopress:
1) I added a new function rake new_rmd to my Rakefile. This mimics the behavior of rake new_post
and creates a .Rmd-file in source/src. Simply add these lines to your Rakefile:
Rakefile
1234567891011121314151617181920212223
# usage rake new_rmd[my-new-rmd] or rake new_post['my new rmd'] or rake new_rmd (defaults to "new-rmd")desc"Begin a new post in #{source_dir}/#{src_dir}"task:new_rmd,:titledo|t,args|raise"### You haven't set anything up yet. First run `rake install` to set up an Octopress theme."unlessFile.directory?(source_dir)mkdir_p"#{source_dir}/#{src_dir}"args.with_defaults(:title=>'new-rmd')title=args.titlefilename="#{source_dir}/#{src_dir}/#{Time.now.strftime('%Y-%m-%d')}-#{title.to_url}.#{new_src_ext}"ifFile.exist?(filename)abort("rake aborted!")ifask("#{filename} already exists. Do you want to overwrite?",['y','n'])=='n'endputs"Creating new post: #{filename}"open(filename,'w')do|post|post.puts"---"post.puts"layout: post"post.puts"title: \"#{title.gsub(/&/,'&')}\""post.puts"date: #{Time.now.strftime('%Y-%m-%d %H:%M')}"post.puts"comments: true"post.puts"categories: "post.puts"published: false"post.puts"---"endend
2) I modified and cleaned knit.sh from the Rcpp Gallery for my needs. This runs knitr on the .Rmd files and saves the output to _posts. Put this file into source/_scripts!
3) Modified and cleaned the Makefile from the Rcpp Gallery for my needs. This runs knit.sh on the /src folder, returns the .markdown files to _posts/ and cleans up the .md and .hmtl files created by R-Studio. Put this file into source/src!
You can skim through my github-repo for this site and copy the files from there. I have not tested this very extensively - in case you find any bugs or have improvements, please let me know.
Edit 01.03.2013
I had to add this line of code
to source/_includes/custom/head.html so that the figures are displayed properly, see
here.
This is about example 4.3 on page 147 of Quantitative Ecotoxicology. This example is about Calculations of $LC_{50}$ values.
In this post I won’t reproduce the SAS-Code since I do not have any experience with SAS PROC PROBIT and I do not fully understand whats happening there.
Instead I will fit Dose-Response-Models using the drc-package to this data.
So the data consists of number of dead animals (DEAD) from all animals (TOTAL) exposed to a concentration (CONC).
First we create a new column with the proportion of dead animals:
1
salt$prop <- salt$DEAD/salt$TOTAL
Lets have a look at the raw data (note that I use a logarithmic scale for the x-axis):
Code and data is available on my github-repo under file name ‘p147’.
References
Ritz C (2010).
“Toward A Unified Approach to Dose-Response Modeling in Ecotoxicology.”
Environmental Toxicology And Chemistry, 29.
ISSN 07307268, http://dx.doi.org/10.1002/etc.7.