NAME
GO::TermFinder - identify GO nodes that annotate a group of genes with a significant p-value
DESCRIPTION
This package is intended to provide a method whereby the P-values of a set of GO annotations can be determined for a set of genes, based on the number of genes that exist in the particular genome (or in a selected background distribution from the genome), and their annotation, and the frequency with which the GO nodes are annotated across the provided set of genes. The P-value is simply calculated using the hypergeometric distribution as the probability of x or more out of n genes having a given annotation, given that G of N have that annotation in the genome in general. We chose the hypergeometric distribution (sampling without replacement) since it is more accurate, though slower to calculate, than the binomial distribution (sampling with replacement).
In addition, a corrected p-value can be calculated, to correct for multiple hypothesis testing. The correction factor used is the total number of nodes to which the provided list of genes are annotated, excepting any nodes which have only a single annotation in the background, as a priori, we know that these cannot be significantly enriched. The client has access to both the corrected and uncorrected values. It is also possible to correct the p-value using 1000 simulations, which control the Family Wise Error Rate - using this option suggests that the Bonferroni correction is in fact somewhat liberal, rather than conservative, as might be expected. Finally, the False Discovery Rate can also be calculated.
The general idea is that a list of genes may have been identified for some reason, e.g. they are co-regulated, and TermFinder can be used to find out if any nodes annotate the set of genes to a level which is extremely improbable if the genes had simply been picked at random.
TODO
1. May want the client to decide the behavior for ambiguous names, rather than having it hard coded (e.g. always ignore; use if standard name (current implementation); use all databaseIds for the ambiguous name; decide on a case by case basis (potentially useful if running on command line)).
2. Create new GO::Hypothesis and GO::HypothesisSet objects, so that it is easier to access the information generated about the p-value etc. of any particular GO node that annotates a set of genes.
3. Instead of all the global variables, $k..., replace them with constants, which may improve runtime, as the optimizer should optimize the hash look ups to look like hard-coded strings at runtime, rather than variable lookups.
4. Lots of other stuff....
Instance Constructor
new
This is the constructor. It expects to be passed named arguments for an annotationProvider, and an ontologyProvider. In addition, it must be told the aspect of the ontology provider, so that it knows how to query the annotationProvider.
There are also some additional, optional arguments:
population:
This argument allows a client to indicate the population that should used to calculate a background distribution of GO terms. In the absence of population argument, then the background distribution will be drawn from all genes in the annotationProvider. This should be provided as an array reference, and no ambiguous names should be provided (see AnnotationProvider for details of name ambiguity). This option is particularly pertinent in a case where for example you assayed only 2000 genes in a two hybrid experiment, and found 20 interesting ones. To find significant terms, you need to do it in the context of the genes that you assayed, not in the context of all genes with annotation.
Note, new in version 0.71, if you provided a population as the background distribution from which genes have been drawn, any genes provided to the findTerms method that are not in the background distribution will be discarded from the calculations. The identity of these genes can be retrieved using the discardedGenes() method, after the findTerms() method has been called.
totalNumGenes:
This argument allows a client to indicate that the size of the background distribution is in fact larger that the number of genes that exist in the annotation provider, and the extra genes are merely assumed to be entirely unannotated.
NB: This is an API change, as totalNumGenes was previously required.
Thus - if using 'population', the total number of genes considered as the background will be the number of genes in the provided population. If not using 'population', then the number of genes that will be considered as the total population will be the number of genes in the annotationProvider. However, if the totalNumGenes argument is provided, then that number will be used as the size of the population. If it is not larger than the total number of genes in the annotationParser, then the number of genes in the annotationParser will be used. The totalNumGenes and the population arguments are mutually exclusive, and both should not be provided at the same time.
Usage ($num is larger than the number of genes with annotations):
my $termFinder = GO::TermFinder->new(annotationProvider=> $annotationProvider,
ontologyProvider => $ontologyProvider,
totalNumGenes => $num,
aspect => <P|C|F>);
Usage (use all annotated genes as population):
my $termFinder = GO::TermFinder->new(annotationProvider=> $annotationProvider,
ontologyProvider => $ontologyProvider,
aspect => <P|C|F>);
Usage (use a subset of genes as the background population):
my $termFinder = GO::TermFinder->new(annotationProvider=> $annotationProvider,
ontologyProvider => $ontologyProvider,
population => \@genes,
aspect => <P|C|F>);
Instance Methods
findTerms
This method returns an array of hash references, one for each GO::Node that was tested as a hypothesis, that indicates which terms annotate the list of genes with what P-values. The contents of the hashes in the returned array depend on some of the run time options. They are:
key value
-------------------------------------------------------------------------
Always Present:
NODE A GO::Node
PVALUE The P-value for having the observed number of
annotations that the provided list of genes
has to that node.
NUM_ANNOTATIONS The number of genes within the provided list that
are annotated to the node.
TOTAL_NUM_ANNOTATIONS The number of genes in the population (total
or provided) that are annotated to the node.
ANNOTATED_GENES A hash reference, whose keys are the
databaseIds that are annotated to the node,
and whose values are the original name
supplied to the findTerms() method.
Present if corrected p-values are calculated:
CORRECTED_PVALUE The CORRECTED_PVALUE is the PVALUE, but corrected
for multiple hypothesis testing, due to the
fact that you are more likely to generate
significant looking p-values if you test a
lot of hypotheses. See below for details of
how this pvalue is calculated, and the
options associated with it.
Present if p-values were corrected by simulation:
NUM_OBSERVATIONS The number of simulations in which a p-value as
good as this one, or better, was observed.
Present if the False Discovery Rate is calculated:
FDR_RATE The False Discovery Rate - this is a fraction
of how many of the nodes with p-values as good or
better than the node with this FDR would be expected
to be false positives.
FDR_OBSERVATIONS The average number of nodes during simulations
that had an uncorrected p-value as good or better
than the p-value of this node.
EXPECTED_FALSE_POSITIVES The expected number of false positives if this node
is chosen as the cut-off.
The entries in the returned array are sorted by increasing p-value (i.e. least likely is first). If there is a tie in the p-value, then the sort order is determined by GOID, using a cmp comparison.
findTerm() expects to be passed, by reference, a list of gene names for which terms will be found. If a passed in name is ambiguous (see AnnotationProvider), then the following will occur:
1) If the name can be used as a standard name, it will assume that
it is that.
2) Otherwise it will not use it.
Currently a warning will be printed to STDOUT in the case of an ambiguous name being used.
The passed in gene names are converted into a list of databaseIds. If a gene does not map to a databaseId, then an undef is put in the list - however, if the same gene name, which does not map to a databaseId, is used twice then it will produce only one undef in the list. If more than one gene name maps to the same databaseId (either because you used the same name twice, or you used an alias as well), then that databaseId is only put into the list once, and a warning is printed.
If a gene name does not have any information returned from the AnnotationProvider, then it is assumed that the gene is entirely unannotated. For these purposes, TermFinder annotates such genes to the root node (Gene_Ontology), its immediate child (which indicates the aspect of the ontology (such as biological_process), and a dummy go node, corresponding to unannotated. This node will have a goid of 'GO:XXXXXXX', and a term name of 'unannotated'. No other information will be set up for this GO::Node, so you should not count on being able to retrieve it. What it does mean is that you can determine if the predominant feature of a set of genes is that they have no annotation.
If more genes are provided that have been indicated exist in the genome (as provided during object construction), then an error message will be printed out, and an empty list will be returned.
In addition, it is possible that for a small list of genes, that no hypotheses will be tested - in this case, those genes will only have annotated nodes with a count of 1, other than the Gene_Ontology node itself, and the node corresponding to the aspect of the ontology. Neither of these are considered for p-value testing, as a priori they must have a p-value of 1.
MULTIPLE HYPOTHESIS CORRECTION
An optional argument, 'correction' may be used, which indicates what method of multiple hypothesis correction should be used. Multiple hypothesis correction attempts to keep the overall chance of getting any false positives at the same level (e.g. 0.05). Acceptable values are:
bonferroni, none, simulation
: 'bonferroni' will correct the p-values by using as the correction
factor the total number of nodes to which the provided list of
genes are annotated, either directly or indirectly, excepting any
nodes that are annotated only once in the background distribution,
as, a priori, these cannot be overrepresented.
: 'none' will perform no multiple hypothesis correction
: 'simulation' will run 1000 simulations with random lists of genes
(the same size as the originally provided gene list), and determine
a corrected value by how many simulations produced a p-value better
than the p-value associated with one of the real hypotheses.
E.g. if a node from the real data has a p-value of 0.05, but a
p-value that good or better is generated in 500 out of 1000 trials,
the corrected pvalue will be 0.5. In the case that a p-value
generated from a real list of genes is never seen in the
simulations, it will be given a corrected p-value of < 0.001, and
the NUM_OBSERVATIONS attribute of the hypothesis will be 0. Using
this option takes 1000 time as long!
The default for this argument, if not provided, is bonferroni.
FALSE DISCOVERY RATE
As a way of preempting the potential problems of using p-values corrected for multiple hypothesis testing, the False Discovery Rate can instead be calculated, and you can instead set your cutoff based on an acceptable false discovery rate, such as 0.01 (1%), or 0.05 (5%) etc. Thus, the optional argument 'calculateFDR' can be used. A non-zero value means that the False Discovery Rate will be calculated for each node, such that you can determine, if you chose your p-value cut-off at that node, what the FDR would be. The FDR is calculated by running 50 simulations, and counting the average number of times a p-value as good or better that a p-value generated from the real data is seen. This is used as the numerator. The denominator is the number of p-values in the real data that are as good or better than it.
Usage example - in this example, the default (Bonferroni) correction is used to calculate a corrected p-value, and in addition, the False Discovery Rate is also calculated:
my @pvalueStructures = $termFinder->findTerms(genes => \@genes,
calculateFDR => 1);
my $hypothesis = 1;
foreach my $pvalue (@pvalueStructures){
print "-- $hypothesis of ", scalar @pvalueStructures, "--\n",
"GOID\t", $pvalue->{NODE}->goid, "\n",
"TERM\t", $pvalue->{NODE}->term, "\n",
"P-VALUE\t", $pvalue->{PVALUE}, "\n",
"CORRECTED P-VALUE\t", $pvalue->{CORRECTED_PVALUE}, "\n",
"FALSE DISCOVERY RATE\t", $pvalue->{FDR_RATE}, "\n",
"NUM_ANNOTATIONS\t", $pvalue->{NUM_ANNOTATIONS}, " (of ", $pvalue->{TOTAL_NUM_ANNOTATIONS}, ")\n",
"ANNOTATED_GENES\t", join(", ", values (%{$pvalue->{ANNOTATED_GENES}})), "\n\n";
$hypothesis++;
}
If a background population had been provided when the object was constructed, you should check to see if any of your genes for which you are finding terms were discarded, due to not being found in the background population, e.g.:
my @pvalueStructures = $termFinder->findTerms(genes => \@genes,
calculateFDR => 1);
my @discardedGenes = $termFinder->discardedGenes;
if (@discardedGenes){
print "The following genes were not considered in the pvalue
calculations, as they were not found in the provided background
population.\n\n", join("\n", @discardedGenes), "\n\n";
}
discardedGenes
This method returns an array of genes which were discarded from the pvalue calculations, because they could not be found in the background population. It should only be called after findTerms. It will either return an empty list, if no genes were discarded, or an array of genes that were discarded.
Usage:
my @pvalueStructures = $termFinder->findTerms(genes => \@genes,
calculateFDR => 1);
my @discardedGenes = $termFinder->discardedGenes;
if (@discardedGenes){
print "The following genes were not considered in the pvalue
calculations, as they were not found in the provided background
population.\n\n", join("\n", @discardedGenes), "\n\n";
}
genesDatabaseIds
This method returns an array of databaseIds corresponding to the genes that were used for the findTerms() method. Thus it allows a client to find out how many actual entities their list of genes that were passed in mapped to, e.g. they may have passed in the same thing with two different names. Using this method, immediately following use of the findTerms method, they will determine how many genes their list collapsed to.
totalNumGenes
This returns the total number of genes that are in the background set of genes from which the genes of interest were drawn. Unannotated genes are included in this count.
aspect
Returns the aspect with the the GO::TermFinder object was constructed.
Usage:
my $aspect = $termFinder->aspect;
Authors
Gavin Sherlock; sherlock@genome.stanford.edu
Elizabeth Boyle; ell@mit.edu
Ihab Awad; ihab@genome.stanford.edu