Slides from my R intro seminar

Here are my slides from a short introductory seminar on R (essentially going through part I of the R tutorial) last week. As magic lantern pictures go, they’re hideously ugly, but they were mostly there for future reference. Most of the seminar was spent showing RStudio. This Friday, we’ll practice some uses of qplot and make some linear models.

(I took out the Modern Major-General quote from the presentation, but you can enjoy him here instead. Don’t ask.)

From Uppsala

On a personal note, I had a great time at the Genetics of Adaptations symposium in Uppsala last Saturday. Pretty much everything was interesting, and I particularly enjoyed the following:

  • Bruce Walsh himself explaining G-matrices and how they can put constraints on evolution. The G-matrix is one of those things I’d very much like to understand, and listening to someone like Bruce Walsh certainly helps. (See e.g. this paper by Walsh & Blows)
  • Matt Rockman talked about some serious QTN work on awesomley weird phenotypes: worms depositing copulatory plugs on each other’s and their own heads. (See e.g. Palopoli et al.)
  • Saunak Sen spoke about mapping of function-valued traits, probably the most interesting talk to me. He concentrated on traits that are functions of one variable, namely time. (See Xiong et al.) However the most interesting to me (as a gene expression enthusiast) would be traits that are, as he put it, ”massively multivariate”, like eQTL data. In that case, there’s not really an obvious analogue of time, i.e. something that the values from one individual are a function of. I eagerly await what people might come up with in that regard.

It was a really fun time, and Uppsala is always nice. I’ll have to make sure to be there when the evolution museum is open some time. I always get the feeling that I should be better at, you know, networking at these things, but I had a couple of interesting conversations about making sense of gene expression results (incidentally, something that I’m very likely to blog about in the near future).

Using R: reading tables that need a little cleaning

Sometimes one needs to read tables that are a bit messy, so that read.table doesn’t immediately recognize the content as numerical. Maybe some weird characters are sprinkled in the table (ever been given a table with significance stars in otherwise numerical columns?). Some search and replace is needed. You can do this by hand, and I know this is a task for which many people would turn to a one-liner of perl or awk, but I like to do my scripting in R.

Again, this is in the ”trivial of you only think it out” category of problems. But it gives a starting point for more subtle changes to text files that one might have to do from time to time.

Assume the data look something like this (I made this up):

id;col1;col2
id1; 0,2;0.55*
id2; -,1;,23

Thankfully, R’s conversion from numbers to text is pretty good. It will understand, for instance, that both .5 and 0.5 are numbers. If the problem was just an odd decimal separator, like the Swedish decimal comma, we could just change the dec parameter to read.table. In the above, we have mixed decimal separators. Let’s start out by reading the table as character.

character.data <- read.table("some_data.csv", sep=";",
                   colClasses="character", header=T)
row.names(character.data) <- character.data[,1]
character.data <- character.data[,-1]

This also stores away the sample ids in row names, and removes the first column with the ids. This is optional, but for this example I assume that all columns should be numeric. If that is not the case, the code below can of course be restricted to the numeric columns, and the character (or factor) columns can be added later.

A data.frame is pretty much a list of vectors, so we use plyr to apply over the list and stringr to search and replace in the vectors. After removing characters that aren’t numbers, decimal separators, or the minus sign, we change the decimal separator, and convert the vector to numeric. Finally, we make the list a data.frame, and propagate the row names.

library(stringr)
library(plyr)

data <- data.frame(llply(character.data, function(x) {
  x <- str_replace_all(x, pattern="[^0-9\\.\\,-]", replacement="")
  x <- str_replace(x, pattern="\\,", replacement=".")
  return(as.numeric(x))
}))
row.names(data) <- row.names(character.data)

Gyckelblommorna i Copperopolis och deras vissnande hybrider

Det var inte planerat att jag skulle ha tema gyckelblommor och genetiska bieffekter här, men så kom det ut en alldeles ny artikel från John Willis och hans kollegor: Kevin M. Wright m fl. (2013) Indirect evolution of hybrid lethality due to linkage with selected locus in Mimulus guttatus och det är helt enkelt för bra för att inte blogga om. Om två populationer av någon varelse varit separerade tillräckligt länge och några individer träffar på varandra och parar sig, så kan avkomman ha oväntade egenskaper. Ibland verkar hybriderna ovanligt stora och livskraftiga, och ibland har de olika problem eller är infertila. Det beror på olika sorters interaktioner mellan genetiska varianter från de båda populationerna som vanligtvis inte träffar på varandra. Infertilitet mellan hybrider, vilket kallas hybridinkompatibilitet, är ett steg mot reproduktiv isolering som skiljer arter åt.

Det finns populationer av Mimulus guttatus som anpassat sig inte bara till livet vid kusten utan också till livet vid en koppargruva där jorden är full av koppar: som i Copperopolis, California. Genetiska undersökningar från 70-talet och framåt har kommit fram till att det finns ett dominant mendelianskt anlag (ja, det har blivit ganska många av dem också på sista tiden) för koppartolerans som utmärker den här populationen. Det är nästan helt fixerat — det vill säga nästan alla individer har det — i Copperopolis, men väldigt ovanligt i andra populationer i närheten.

journal.pbio.1001498.g001

(Foto: Kevin Wright, CC:BY, figur 1 i en kommentar i samma tidskrift)

Men förutom koppartolerans orsakar det också hybridinkompatibilitet i korsningar med en del andra populationer. Hybriderna vissnar och lever sällan till reproduktiv ålder. Det verkar som att vissnandet är en genetisk bieffekt av koppartoleransvarianten. Men resultaten i den här artikeln visar att effekten beror av två olika varianter som ligger mycket nära varandra i genomet. Så istället för pleiotropi, samma variant som gör två saker, är det två länkade varianter. Men eftersom de är nära varandra ärvs de oftast tillsammans.

Hur tar en reda på om det är frågan om en eller flera varianter? Jo, gör korsningar, kartlägger egenskaperna och tittar efter rekombinationer mellan dem, naturligtvis! Jag ska bespara er detaljerna (inte för att de är så svåra; kolla artikeln om du är intresserad!), men författarna började med att kartlägga koppartolerans. De utgick från en korsning mellan koppartoleranta blommor och blommor från en populations om inte tål koppar, och backkorsade koppartolerant avkomma med blommor från Stinson Beach, California (inte heller koppartolerant) i flera generationer. I varje generation fanns så klart blommor som tålde koppar och de som inte gjorde det, och de kan titta efter genetiska markörer som är homozygota bland de icke-toleranta och heterozygota bland de toleranta. Varianten är ju dominant, så de icke-toleranta plantorna måste sakna den, medan de toleranta bör ha en kopia. På så sätt fick de fram 42 möjliga markörer, och när de sedan korsade samman avkomman och tittade efter associationer mellan markörer och tolerans fanns det bara en kvar som var kopplad till koppartolerans.

Så, när de placerat toleransvarianten på sin genetiska karta (rätt nära markören som heter MgSTS242) vände de sig till inkompatibiliteten. Samma variant eller en annan? De valde ut några plantor ur sin experimentpopulation som hade en rekombination mellan markören och toleransvarianten och korsade dem med en inkompatibel genotyp. Om varianterna ärvs tillsammans borde de flesta koppartoleranta få vissnande avkomma och de som inte tål koppar borde få frisk avkomma. Men några av dem, två rekombinanta blommor av nio, avvek från mönstret: en var koppartolerant men fick livskraftig avkomma; en annan var intolerant men gav upphov till vissnande hybrider (panel C i Figur 3, nedan: titta på 25_E01 och 25_E11). Det går alltså att skilja de två egenskaperna med rekombination — de kan alltså inte vara samma anlag!

journal.pbio.1001497.g003

(Figur 3 från Wright et al. CC:BY.)

Varianten för koppartolerans verkar ha varit med om naturligt urval i Copperopolispopulationen (åh, vilket ord!) de senaste 150 åren och naturligt urval lämnar (i alla fall under ideala förhållanden) spår i genomet. Urvalet betyder ju att en viss variant sprids på bekostnad av de andra allelerna, så den genetiska variationen minskar. Och på grund av länkning kommer varianter som ligger fysiskt nära varandra att tendera att ärvas tillsammans; det kallas hitch-hiking: metaforen är att de liftar med den urvalda varianten. Alltså minskar variationen i ett område kring den utvalda varianten. Författarna mätte hur mycket markörer nära tolerans- och inkompatibilitetsvarianterna varierade i Copperopolis och i två närliggande populationer som utan koppartolerans och jämförde det med andra markörer långt från toleransvarianten. Och mycket riktigt, regionen bär märken av starkt naturligt urval; och de uppskattar att inkompatibilitetsvarianten ligger tillräckligt nära toleransvarianten för att det är realistiskt att den bara har hängt med på naturligt urval för koppartolerans.

Så: frågan om länkning eller pleiotropi har i det här fallet ett definitivt svar: nej, är inte samma genetiska variant som orsakar koppartolerans som får hybriderna att vissna. Att ta reda på vilka gener och vilka varianter det är som ligger bakom är däremot inte helt enkelt. Gyckelblomman har tyvärr ingen riktigt bra referenssekvens, utan en stor samling fragment (jämför Darwinfinkarna). Författarna försökte bäst de kunde att kartlägga de bitar som finns men gick i stort sett bet. Det är också svårt att veta precis vad det är i området som gynnats av naturligt urval, men det verkar i alla fall tydligt att naturligt urval har ägt rum, och sannolikt har det gällt koppartolerans, som verkar helt avgörande för att kunna leva i Copperopolis. Hybridinkompatibiliteten, däremot, verkar inte ha någon direkt nytta, och den är antagligen bara en genetisk liftare.

Litteratur

Wright KM, Lloyd D, Lowry DB, Macnair MR, Willis JH (2013) Indirect Evolution of Hybrid Lethality Due to Linkage with Selected Locus in Mimulus guttatus. PLoS Biol 11(2): e1001497. doi:10.1371/journal.pbio.1001497

ENCODE, 80% och varför det mesta av skräpet fortfarande är skräp

ENCODE, encyclopedia of DNA elements, är på tapeten igen: det är några som skrivit en rätt elak kritisk artikel. Den är i och för sig open access så att alla kan läsa den, men jag rekommenderar den här i stället: Sean R Eddy, The C-value paradox, junk DNA, and ENCODE. Den är skriven i faq-/katekesform och är mer pedagogisk än Graur & co.

Vad är det då folk är så arga på? Tja, den här lilla filmen sammanfattar hypen kring ENCODE-projektet ganska väl: en gigantisk robot som slår cancer på käften. Och hela genomet är fullt av aktivitet ”even the parts we used to think of as junk”. Suck.

(Själv samlar jag mod för att redigera eller åtminstone diskutera svenska Wikipedias sida som är lika missvisande.)

Å andra sidan: den här artikeln ger en ganska fin sammanfattning av vad projektet egentligen gjorde. Alltså, precis som namnet antyder, är det fråga om en encyklopedi över dna-element i det mänskliga genomet. För ett par andra förträffliga varelser se modENCODE. Det ENCODE (och många andra) mätte var olika typer av aktivitet: olika saker som fäster vid, skriver av eller modifierar dna. Åtminstone en del av resultaten finns tillgängliga i UCSC-genomläsaren så att vi kan titta på vad som försiggår kring våra favoritgener.

Jag har skrivit lite om genetiskt skräp förut: i korthet så är det en väldigt liten del av dna-sekvensen i en stor flercellig organism som faktiskt innehåller instruktioner för några biomolekyler (proteiner och rna). Ytterligare en del innehåller icke-kodande reglerande sekvenser som styr när generna uttrycks. Men lejonparten av genomet är varken eller. Och det är inte bara så att ingen vet vad de gör — många av sekvenerna är tydligt trasiga virussekvenser och andra omflyttningsbara element. Det visar sig att räknar en generöst är det omkring 80% av sekvensen som någon gång skrivs av, interagerar med ett protein eller har vissa modifikationer (som också brukar bäras av dna som används till något). Därmed inte sagt att de gör någon direkt nytta för organismen.

Sean Eddy:

The question that the “junk DNA” concept addresses is not whether these sequences are biochemically “active”, but whether they’re there primarily because they’re useful for the organism. Sequence conservation analyses, including ENCODE’s, consistently indicate that only around 5-20% of the human genome is under detectable selective pressure. Some additional fraction of sequences has probably evolved new human-specific regulatory functions that are not conserved with other closely related species, but ENCODE’s publicized interpretation would require that such nonconserved regulatory sequences account for 80-95% of the genome, far outnumbering evolutionary conserved regulatory sequences. Given the C-value paradox, mutational load, and the massive impact of transposons, the data remain consistent with the view that the nonconserved 80-95% of the human genome is mostly composed of nonfunctional decaying transposons: “junk”.

Litteratur

The ENCODE Project Consortium (2011) A User’s Guide to the Encyclopedia of DNA Elements (ENCODE). PLOS Biology 9 e1001046. doi:10.1371/journal.pbio.1001046

Sean R Eddy (2012) The C-value paradox, junk DNA, and ENCODE (pdf från hans hemsida)

Dan Graur , Yichen Zheng, Nicholas Price, Ricardo B. R. Azevedo, Rebecca A. Zufall, Eran Elhaik. (2013) On the immortality of television sets: “function” in the human genome according to the evolution-free gospel of ENCODEGenome Biology and Evolution doi:10.1093/gbe/evt028

Gyckelblommor och en annan inverterad kromosombit

Mer inversioner! Det här är en artikel jag valde till en faktisk journal club den här veckan: David Lowry & John Willis (2010) A Widespread Chromosomal Inversion Polymorphism Contributes to a Major Life-History Transition, Local Adaptation, and Reproductive Isolation. Jag tycker det kunde vara roligt att titta på den här också; den har ett annat exempel på en inversion med intressanta genetiska effekter. Möt gyckelblomman: Mimulus guttatus. M. guttatus finns tydligen här och där i Sverige där den rymt från någon trädgård, men framför allt bor den i Nordamerika. Den har både fleråriga varianter och annuella varianter, alltså sådana som blommar en säsong och inte övervintrar. Att leva som perenn eller annuell verkar vara en lokal anpassning till miljön. Lowry & Willis har tittat på gyckelblommor längs USA:s och Kanadas östkust. Perennvarianterna finns mest längs kusten (några populationer i inlandet), medan annuella varianter bara finns i inlandet.

Yellow_monkeyflower

(Foto: Christophermluna [CC-BY-3.0], via Wikimedia Commons)

Inversioner — alltså en variant där en bit kromosom vänt sig och sitter i omvänd riktning. Det speciella med dem är att de hindrar rekombination mellan den omvända och den ursprungliga varianten och skapar ett stort block som ärvs tillsammans. Det finns flera exempel på inversioner som gör olika saker och är föremål för naturligt urval; jag vet inte om det är för att just inversioner är särskilt viktiga eller för att de är en typ av stor mutation som går att se på en genetisk karta. Om en ser en grupp markörer som hela tiden ärvs tillsammans tänker en genast på en inversion. Den här artikeln är bara en i en lång serie arbeten med genetisk kartläggning i olika korsningar av M. guttatus, och vid någon punkt har de sett ett stort svart hål i den genetiska kartan och tänkt: aha, inversion.

Så de korsade alltså gyckelblommor från olika populationer med olika levnadssätt och tittade efter rekombinationer och efter markörernas ordning i den inverterade regionen, och mycket riktigt: inversionen är minst två miljoner baser lång och associerad med levnadssätt: perenner har en variant, annueller den andra. Den här artikeln är rolig för att den använder två klassiska typer av experiment för att titta på lokala anpassningars genetik: gemensam trädgård och transplantation. Om en planterar växter från olika miljöer tillsammans, i samma miljö, kan en se hur stor del av skillnaden dem emellan som beror på ärftliga faktorer. Exempel: Björkallén i den genetiska trädgården vid Ultuna. Jag har aldrig varit där, men Erik i Ulleråker har bloggat och tagit en bild. Allén består av björkar från olika delar av Sverige, ordnade från syd till norr. Även om de står i samma miljö så fäller de sina blad vid olika tid och illustrerar björkars lokala anpassning till olika klimat.

Så de prövade att korsa olika populationer med varandra för att testa effekten av inversionen. (Det är alltså länkningskartläggning igen, fast nu bara med en markör) Figur 2 nedan visar tid till blomning i ett gäng olika korsningar: BB är de som har två kopior av perennallelen, AB är en av varje och AA är två kopior av annuellallelen. (Erkännande: Ja, jag skrev ”allel” istället för variant bara för att få skriva ”annuellallelen”.) De med perennvarianten blommar betydligt senare och inversionen verkar ha en additiv effekt (det vill säga, de med allel av varje ligger ungefär mitt emellan de med två lika) och inversionen förklarar 20-45% av skillnaden mellan föräldrarna. Lägg märke till skalan på y-axeln: i medeltal är det lite olika blomningstider i de olika korsningarna, men det är inte helt lätt att jämföra när de har börjat axeln på olika ställen … Det är en viss skillnad i utseende också: se fotografiet.

mimulus_fig2

(Figur 2, Lowry & Willis CC-BY)

Transplantation då: att flytta växter mellan olika miljöer för att se om hur bra de klarar sig. Om de inhemska växterna klarar sig och förökar sig bättre tyder det på att lokal anpassning är i faggorna. Så Lowry & Willis (och de mängder folk som hjälpt till och tackas i Acknowledgements) tog fram korsningar av gyckelblommor från olika ställen med olika varianter av inversionen och planterade ut dem vid kusten respektive inlandet. Och mycket riktigt, de mätte hur många växter av olika genotyp som överlevde, när de blommade och hur många blommor de hade. Gyckelblommor från kusten klarar sig betydligt bättre vid kusten än de från inlandet, och en femtedel eller så av den skillnaden verkar förklaras av inversionen. Svårast är det för kustplantor med perennallelen att klara sig i inlandet: det är för varmt och säsongen för kort för att de ska hinna med att blomma.

Så någonstans i den här två miljoner baser långa dna-biten finns något som har en avsevärd effekt på växtens blomningstid, form och överlevnad i olika miljöer: hela dess livsstil. Om det är en eller flera varianter i inversionen eller om det är en effekt av inversionen själv är inte lätt att veta. Antagligen finns det någon genetisk variant som bidrar till lokal anpassning som råkat hamna på en inverterad kromosombit och så sprids inversionen som en bieffekt av naturligt urval för den varianten. Åter igen: det är ironiskt att innehållet i en inversion blir extra intressant och samtidigt, just för att inversionen hindrar rekombination, väldigt mycket svårare att studera genetiskt.

Litteratur

Lowry DB, Willis JH (2010) A Widespread Chromosomal Inversion Polymorphism Contributes to a Major Life-History Transition, Local Adaptation, and Reproductive Isolation. PLOS Biology 8 e1000500. doi:10.1371/journal.pbio.1000500

A slightly different introduction to R, part IV

Now, after reading in data, making plots and organising commands with scripts and Sweave, we’re ready to do some numerical data analysis. If you’re following this introduction, you’ve probably been waiting for this moment, but I really think it’s a good idea to start with graphics and scripting before statistical calculations.

We’ll use the silly comb gnome dataset again. If you saved an Rdata file in part II, you can load it with

load("comb_gnome_data.Rdata")

If not, you can run this. Remind yourself what the changes to the melted data mean:

data <- read.csv("comb_gnome_data.csv")
library(reshape2)
melted <- melt(data, id.vars=c("id", "group", "treatment"))
melted$time <- 0
melted$time[which(melted$variable=="mass10"] <- 10
melted$time[which(melted$variable=="mass25")] <- 25
melted$time[which(melted$variable=="mass50")] <- 50
melted$id <- factor(melted$id)

We’ve already looked at some plots and figured out that there looks to be substantial differences in mass between the green and pink groups, and the control versus treatment. Let’s try to substantiate that with some statistics.

9. Mean and covariance

Just like anything in R, statistical tools are functions. Some of them come in special packages, but base R can do a lot of stuff out of the box.

Comparsion of two means: We’ve already gotten means from the mean() function and from summary(). Variance and standard deviation are calculated with var() and sd() respectively. Comparing the means betweeen two groups with a t-test or a Wilcoxon-Mann-Whitney test is done with t.test() and wilcox.test(). The functions have the word test in their names, but t-test gives not only the test statistics and p-values, but also estimates and confidence intervals. The parameters are two vectors of values of each group (i.e. a column from the subset of a data frame), and some options.

Looking back at this plot, I guess no-one is surprised by a difference in birthweigh between pink and green comb gnomes:

plot2

t.test(subset(data, group=="pink")$mass0, subset(data, group=="green")$mass0)
	Welch Two Sample t-test

data:  subset(data, group == "pink")$mass0 and subset(data, group == "green")$mass0 
t = -5.397, df = 96.821, p-value = 4.814e-07
alternative hypothesis: true difference in means is not equal to 0 
95 percent confidence interval:
 -69.69294 -32.21577 
sample estimates:
mean of x mean of y 
 102.3755  153.3298

That is, we feed in two pieces of data (two vectors, really, which is what you get pulling out a column from a data frame). The above is the typical situation when you have all data points in one column and a group indicator in another. Hence you begin by subsetting the data frame to get the right rows, and pull out the right columns with the $. t.test also does paired tests, with the additional parameter paired=T.

wilcox.test(subset(data, group=="pink")$mass50, subset(data, group=="green")$mass50)
	Wilcoxon rank sum test with continuity correction

data:  subset(data, group == "pink")$mass50 and subset(data, group == "green")$mass50 
W = 605, p-value = 1.454e-05
alternative hypothesis: true location shift is not equal to 0

Recalling histograms for the comb gnome weights, the use of the Wilcoxon-Mann-Whitney for masses at tim 50 and a t-test for the masses at birth (t=0) probably makes sense. However, we probably want to make use of all the time points together rather than doing a test for each time point, and we also want to deal with both the colour and the treatment at the same time.

Before we get there, let’s look at correlation:

cor(data$mass10, data$mass25)
cor(data$mass0, data$mass50, method="spearman")
cor.test(data$mass10, data$mass25)

The cor() function gives you correlation coefficients, both Pearson, Spearman and Kendall. If you want the covariance, cov() is the function for that. cor.test() does associated tests and confidence intervals. One thing to keep in mind is missing values. This data set is complete, but try this:

some.missing <- data$mass0
some.missing[c(10, 20, 30, 40:50)] <- NA
cor(some.missing, data$mass25)
cor(some.missing, data$mass10, use="pairwise")

The use parameter decides what values R should include. The default is all, but we can choose pairwise complete observations instead.

If you have a big table of variables that you’d like to correlate with each other, the cor() function works for them as well. (Not cor.test(), though. However, the function can be applied across the rows of a data frame. We’ll return to that.)

10. A couple of simple linear models

Honestly, most of the statistics in biology is simply linear models fit with least squares and tested with a normal error model. A linear model looks like this

yi = b0 + b1x1i + b2x2i + … bnxni + ei

where y is the response variable, the xs are predictors, i is an index over the data points, and ei are the errors. The error is the only part of the equations that is a random variable. b0, …, bn are the coefficients — your main result, showing how the mean difference in the response variable between data points with different values of the predictors. The coefficients are fit by least squares, and by estimating the variance of the error term, we can get some idea of the uncertainty in the coefficients.

Regression coefficients can be interpreted as predictions about future values or sometimes even as causal claims (depending on other assumptions), but basically, they describe differences in mean values.

This is not a text on linear regression — there are many of those; may I suggest the books by Faraway or Gelman and Hill — suffice to say that as long as the errors are independent and have equal variance, least squares is the best unbiased estimate. If we also assume that the errors are normally distributed, the least squares is also the maximum likelihood estimate. (And it’s essentially the same as a Bayesian version of the linear model with vague priors, just for the record.)

In R, the lm() function handles linear models. The model is entered as a formula of the type response ~ predictor + predictor * interacting predictors. The error is implicit, and assumed to be normally distributed.

model <- lm(mass0 ~ group + treatment, data=data)
summary(model)
Call:
lm(formula = mass0 ~ group + treatment, data = data)

Residuals:
    Min      1Q  Median      3Q     Max 
-86.220 -32.366  -2.847  35.445  98.417 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)      141.568      7.931  17.850  < 2e-16 ***
grouppink        -49.754      9.193  -5.412 4.57e-07 ***
treatmentpixies   23.524      9.204   2.556   0.0122 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 

Residual standard error: 45.67 on 96 degrees of freedom
Multiple R-squared:  0.28,    Adjusted R-squared: 0.265 
F-statistic: 18.67 on 2 and 96 DF,  p-value: 1.418e-07

The summary gives the coefficients, their standard errors, the p-value of a t-test of the regression coefficient, and R squared for the model. Factors are encoded as dummy variables, and R has picked the green group and the control treatment as baseline so the coefficient ”grouppink” describes how the mean of the pink group differs from the green. Here are the corresponding confidence intervals:

confint(model)
                     2.5 %    97.5 %
(Intercept)     125.825271 157.31015
grouppink       -68.001759 -31.50652
treatmentpixies   5.254271  41.79428

(These confidence intervals are not adjusted to control the family-wise error rate, though.) With only two factors, the above table is not that hard to read, but let’s show a graphical summary. Jared Lander’s coefplot gives us a summary of the coefficients:

install.packages("coefplot") ##only the first time
library(coefplot)
coefplot(model)

statplot3

The bars are 2 standard deviations. This kind of plot gives us a quick look at the coefficients, and whether they are far from zero (and therefore statistically significant). It is probably more useful for models with many coefficients.

There is a bunch of diagnostic plots that you can make to check for gross violations of the above assumptions of the linear model. Two useful ones are the normal quantile-quantile plot of residuals, and the residuals versus fitted scatterplot:

library(ggplot2)
qplot(sample=residuals(model), stat="qq")

statplot1

The quantile plot compares the distribution of the residual to the quantiles of a normal distribution — if the residuals are normally distributed it will be a straight line.

qplot(fitted(model), residuals(model))

statplot2

Variance should be roughly equal fitted values, and there should not be obvious patterns in the data.

If these plots look terrible, a common approach is to try to find a transformation of the data that allows the linear model to be used anyway. For instance, it often helps to take the logarithm of the response variable. Why is that so useful? Well, with some algebraic magic:

log(yi) = b0 + b1x1i + b2x2i + … + bnxni + ei, and as long as no y:s are zero,

yi = exp(b0) * exp(b1x1i) * exp(b2x2i) * .. * exp(bnxni) * exp(ei)

We have gone from a linear model to a model where the b:s and x:es multiplied together. For some types of data, this will stabilise the variance of the errors, and make the distribution closer to a normal distribution. It’s by no means a panacea, but in the comb gnome case, I hope the plots we made in part II have already convinced you that an exponential function might be involved.

Let’s look at a model where these plots look truly terrible: the weight at time 50.

model.50 <- lm(mass50 ~ group + treatment, data=data)
qplot(sample=residuals(model.50), stat="qq")
qplot(fitted(model.50), residuals(model.50))

statplot4

statplot5

Let’s try the log transform:

model.log.50 <- lm(log(mass50) ~ group + treatment, data=data)
qplot(sample=residuals(model.log.50), stat="qq")
qplot(fitted(model.log.50), residuals(model.log.50))
coefplot(model.log.50)

statplot6

statplot7

coefplot_log50

In both the above models both predictors are categorical. When dealing with categorical predictors, you might prefer the analysis of variance formalism. Anova is the same kind of linear model as regression (but sometimes parameterised slightly differently), followed by F-tests to check whether each predictor explains a significant amount of the variance in the response variable. In all the above models, the categorical variables only have two levels each, so interpretation is easy by just looking a coefficients. When you get to bigger models with lots of levels, F-tests let you test the effect of a ‘batch’ of coefficients corresponding to a variable. To see the (type II, meaning that we test each variable against the model including all other variables) Anova table for a linear model in R, do this:

comb.gnome.anova <- aov(log(mass50) ~ group + treatment, data=data)
drop1(comb.gnome.anova)
Single term deletions

Model:
log(mass50) ~ group + treatment
          Df Sum of Sq     RSS     AIC F value    Pr(>F)    
<none>                  47.005 -67.742                      
group      1    30.192  77.197 -20.627  61.662 5.821e-12 ***
treatment  1    90.759 137.764  36.712 185.361 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Fragment av ett finkgenom: att passa och pussla dna

Häromdagen var det Charles Darwins födelsedag och ett gäng genetiker passade på att publicera en genomsekvens för Geospiza magnirostris, en av de fågelarterna på Galapagos som Darwin träffade på under sin resa med the Beagle. Nu var tjocknäbbade markfinkar kanske inte Darwins viktigaste inspiration, men fåglarna på Galapagos har blivit ett populärt exempel på lokal anpassning med sina specialiserade näbbar.

1839_Zoology_F8.11_fig067

Den som vill bläddra i Rands’ & co genomsekvens för G. magnirostris kan ladda hem en 1.09 Gb zippad fil från fighshare. Men innan vi tänker på att leka med den: vad kan vi vänta oss av en modern genomsekvens? Först och främst: sekvensen är ett utkast som alla från djur och växter. Den är totalt 991 miljoner baser lång men består av strax under 13 000 bitar. G. magnirostris har givetvis inte 13 000 kromsomer utan det är så det blir när en gör modern genomsekvensering. Att sekvensera är mycket snabbare och billigare än det mödosamma arbete som krävdes för att göra de tidiga referenssekvenerna, som människans, hönans m fl. Men det blir ett fragmenterat genom. Det flesta bitarna finns nog där någonstans, men ingen vet i vilken ordning de passar ihop.

Sekvensering kan betyda lite olika saker. När någon pratar om att ”sekvensera tusentals mänskliga genom” eller ”sekvensera området kring SLCO1B3” handlar det om omsekvensering av organismer där det finns en referenssekvens. Efter sekvenseringen, som kan täcka hela genomet eller bara en viss del gäller det att passa in de avlästa dna-bitarna och se var de passar i referenssekvensen, och på vilka ställen det finns genetiska varianter. Passningsproblemet (alignment) är lite besvärligt, särskilt med de miljontals korta sekvenser som kommer ur en modern maskin, men det pussel (assembly) som uppstår när en vill rekonstruera ett genom utan att det finns någon känd referenssekvens är sju resor värre.

Dels genererar en modern maskin väldigt mycket sekvensdata, som sagt, men det är nog inte så farligt jämfört med datamängder som folk i andra branscher hanterar. Tyvärr råkar pusselproblemet dessvärre vara omöjligt. Tänk på dna-sekvenser som upprepas mer än en gång i genomet. Om den avlästa sekvensens (femtio till några hundra baser beroende på teknik) är längre än den upprepade sekvensen är det inget problem. Men om den upprepade sekvensen är mycket längre än den avlästa, och sådana finns det gott om, finns det bitar som inte går att sätta ihop ordentligt.

Problemet kan inte avhjälpas med mer sekvensering, utan kräver att dna prepareras på särskilda sätt. I det här sammanhanget betyder ordet ”bibliotek” en samling korta dna-molekyler från genomet i fråga (antingen i vattenlösning eller inuti en population genmodifierade bakterier). All modern sekvensering använder bibliotek där dna-bitar fragmenteras och paketeras för sekvensering. För att sekvensera över stora repetitiva områden finns det mate-pair eller jump libraries, bibliotek där varje dna-bit är ihopklippt av två kortare med ett hopp i mitten. Långa hopp täcker en längre sekvens utan att behöva läsa av längre bitar och är användbart både för att täcka upprepade sekvenser och sätta ihop korta bitar av ihoppusslad sekvens. Rands & co använde tre typer av bibliotek: enkla fragmenterade sekvenser (300-400 baser långa, 454-metoden) och hopp på 2500 och 4900 baser.

Hur komplett blev det då? Det är väldigt svårt att säga hur bra en genomsekvens är men det går att jämföra litegrann med de fåglar som redan har referenssekvenser: hönan, zebrafinken och kalkonen. Det blev totalt 991 miljoner baser, vilket är ungefär i samma storlekordning som de sekvenserade delarna av andra fågelgenom, och uppskattningsvis 80-90% av genomet. Men när de istället tittade efter kända gener, sådan som både finns hos människa och zebrafink och rimligen borde finnas hos G. magnirostris, så stod ungefär 70% av dem att finna i sekvensen. Så, 70-90% komplett, beroende på om mängden sekvens är en överskattning eller om antalet gener är en underskattning.

Litteratur

Rands, C. M., Darling, A., Fujita, M., Kong, L., Webster, M. T., Clabaut, C., et al (2013). Insights into the evolution of Darwin’s finches from comparative analysis of the Geospiza magnirostris genome sequence. BMC Genomics 14 doi:10.1186/1471-2164-14-95

Nagarajan, N., & Pop, M. (2013). Sequence assembly demystified. Nature Reviews Genetics. doi:10.1038/nrg3367

More Haskell: a bootstrap

So my playing around with Haskell goes on. You can follow the progress of the little bootstrap exercise on github. Now it’s gotten to the point where it actually does a bootstrap interval for the mean of a sample. Consider the following R script:

n <- 100
fake.data <- data.frame(group=rep(1, n), data=rpois(n, 10))
write.table(fake.data, quote=F, row.names=F, col.names=F,
            sep=",", file="fake_data.csv")

library(plyr)
bootstrap.replicates <- llply(vector("list", 100),
                              sample, x=fake.data$data,
                              replace=T, size=n)
bootstrap.means <- unlist(llply(bootstrap.replicates, mean))
print(mean(fake.data$data))
print(quantile(bootstrap.means, c(0.025, 0.975)))
[1] 10.31
    2.5%    97.5% 
 9.72475 10.85200

So, that was a simple bootstrap in R: we get some draws from a Poisson distribution, sample 100 times from the data with replacement, and summarise the replicates.  This is my Haskell thing running in GHCi:

*Main> main
"boot"
"will eventually bootstrap, if martin knows his stuff"
fake_data.csv
[8,6,11,16,5,11,12,12,7,9,13,13,12,7,13,7,7,11,9,14,14,13,10,14,17,12,8,
10,15,12,13,13,7,10,9,6,7,8,10,12,10,10,10,12,11,8,16,12,13,13,12,15,7,
7,8,9,5,7,13,10,12,11,8,6,12,14,12,14,6,9,10,9,10,6,9,7,6,12,13,7,11,7,
13,15,10,10,9,12,12,6,10,6,8,10,13,8,9,13,12,13]
10.31
(9.8,10.83)

It’s certainly not the prettiest thing in the world (for one thing, it will crash if there is an extra line break at the end of the file). Next stop: type declarations! Haskell will infer the types for me, but it is probably a good idea to declare the intended types. Or at least to be able to do so is. Then the plan is to make some use of the first column in the data file, i.e. group the sample belongs to, to add a second sample and make a comparison between the means. And then it’s pretty much done and maybe I’ll move on to something more useful. I’m thinking that implementing least squares linear models would be a decent exercise?