The matrix k contains counts in the form of a contingency table. More about
that in a moment.
The rowSums and colSums functions compute the row and column sums of the
matrix k. The result is a vector.
When R applies log to a matrix, it does element-wise evaluation of the
function to each element of the matrix, likewise with * (matrix
multiplication is %*%). The sum function adds up all elements.
The attached image has it in mathematical notation.
I have attached a Java implementation as well (which you are free to use,
but which needs to have its dependency on Jama removed).
On Mon, Jun 2, 2008 at 6:08 PM, Sean Owen <[EMAIL PROTECTED]> wrote:
> Sorry I am still not clear on the algorithm. What is rowSums,
> columnSums, and what is k? k seem to be used as both a scalar, and
> vector (argument to a function sum()?)
>
> Maybe it is just that I don't know R. Can you give me a hint or
> forward any Java implementations? I can figure it out from there.
> Don't want to be a bother but I do want to make sure I understand what
> you're saying.
>
> On Mon, Jun 2, 2008 at 8:56 PM, Ted Dunning <[EMAIL PROTECTED]> wrote:
> > Hmmm... may not have sent that to you.
> >
> > function(k) {
> > return (2 * (h(k) - h(rowSums(k)) - h(colSums(k))));
> > }
> >
> > function(k) {
> > p = k / sum(k) + (k == 0);
> > return (sum(k * log(p)));
> > }
> >
> > This is the pithiest implementation that I know of.
> >
> > On Mon, Jun 2, 2008 at 5:50 PM, Sean Owen <[EMAIL PROTECTED]> wrote:
> >
> >> PS what's the R code? sorry if I am missing something basic.
> >>
> >> On Sun, Jun 1, 2008 at 8:32 PM, Ted Dunning <[EMAIL PROTECTED]>
> wrote:
> >> > The R code is instructive here. Everywhere you are about to take the
> log
> >> of
> >> > a zero probability, you should add 1. This is equivalent to saying
> that
> >> >
> >> > lim_{x -> 0} x log x = 0
> >>
> >> OK true but...
> >>
> >> > One easy way to do this is to wrap your log function:
> >> >
> >> > safeLog = {x -> return (x==0)?0:Math.log(x)}
> >>
> >> ... log(x) approaches -infinity as x approaches 0 so why is 0 a good
> >> substitute for log(0)?
> >>
> >> But I would buy that, for purposes of this function, "log(p)" can be
> >> replaced with "log(p+1)" so at least the output value is >= 0 and less
> >> than 1?
> >>
> >
> >
> >
> > --
> > ted
> >
>
--
ted
/**
* Copyright 2004, Ted Dunning
* Distribution allowed under GPL v2. Other terms available on request.
* @author Ted Dunning
*/
package com.tdunning.stat;
import Jama.Matrix;
/**
* Defines several matrix functions related to Shannon entropy.
*/
public class Entropy {
/**
* Returns the generalized log-likelihood ratio test for row and
* column independence for a contingency table k. Typically, k
* represents observed counts for multinomial processes. Each row
* represents a separate process and each column represents a
* symbol. This test gives an indication of how likely these
* observations were to have come from an identical multinomial
* distribution. The output in the case that the observations
* come from the same distribution is asymptotically chi^2
* distributed with (n-1) (m-1) degrees of freedom where n is the
* number of rows and m the number of columns.
* @param k
* @return The result of the llr test.
*/
public static double llrMultinomial(Matrix k) {
return 2 * (entropy(k.rowSum()) + entropy(k.columnSum()) - entropy(k));
}
/**
* Returns a signed version of the generalized log-likelihood ratio test
* for a two-dimensional table of counts. Each row contains counts for
* a different context that some condition might hold, each column contains
* the number of times the condition held or not. The result of this
* test is positive if the first row of the table had more times where
* the condition held than would be expected compared to the second row.
*
* Fractional counts are allowed because the counts may have been estimated
* by some odd process rather than actually counted.
* @param k1a Number of times condition or event "a" occured in context 1.
* @param k1notA Number of times something other than "a" occured in context 1.
* @param k2a Number of times condition or event "a" occured in context 2.
* @param k2notA Number of times something other than "a" occured in context 2.
* @return A signed score that is positive when "a" occurs more often in condition 1 than in condition 2. If
* the probability of getting "a" is the same in either condition and whatever difference we see is due purely
* to chance, then this score will be asymptotically normally distributed with mean 0 and variance 1.
*/
public static double signedLlrFrequencyTest(double k1a, double k1notA, double k2a, double k2notA) {
double llr = Math.sqrt(llrMultinomial(new Matrix(new double[]{k1a, k2a, k1notA, k2notA}, 2)));
if (k1a / (k1a + k1notA) > k2a / (k2a + k2notA)) {
return llr;
} else {
return -llr;
}
}
/**
* Returns the generalized log-likelihood ratio test for a
* contingency table k with row times in the last column.
* Typically, k represents observed counts for mixed Poisson
* processes. Each row represents a separate process and each
* column represents a symbol except for the last which contains
* the total observation time for the row. This test gives an
* indication of how likely these observations were to have come
* from an identical mixed Poisson distribution. This test can be
* used on normal Poisson processes by giving it a matrix with
* exactly two columns. The output in the case that the
* observations are taken from the same mixed Poisson distribution
* is chi^2 distributed with (n-1) (m-1) degrees of freedom where
* n is the number of rows and m is the number of columns in the
* matrix (note that one of these columns is not a countAll).
* @param k
* @return The results of the llr test.
*/
public static double llrPoisson(Matrix k) {
int rows = k.getRowDimension();
int cols = k.getColumnDimension();
Matrix counts = k.getMatrix(0, rows-1, 0, cols-2);
Matrix times = k.getMatrix(0, rows-1, cols-1, cols-1);
return 2 * (entropy(times) + entropy(k.columnSum()) - entropy(counts));
}
/**
* Computes the entropy of a matrix. Strictly speaking, this
* returns sum_ij k_ij log p_ij where p_ij = k_ij / sum_ij k_ij.
* If sum_ij k_ij = 1, then this does no harm and it is convenient
* other computations.
*
* @param k
* @return The entropy in base e. Divide by log(2) to get bits.
*/
public static double entropy(Matrix k) {
double total = k.columnSum().rowSum().get(0, 0);
if (k.columnMin().rowMin().get(0, 0) < 0) {
throw new IllegalArgumentException("Cannot have negative count");
}
if (total <= 0) {
throw new IllegalArgumentException("Must have non-zero count");
}
// normalize p and add one where zero. Adding one will have no effect since we
// multiply by the original value later. Doing this just avoids log(0) errors.
Matrix p = k.times(1/total).plusEquals(k.lessEqual(0.0));
return -k.arrayTimes(p.log()).rowSum().columnSum().get(0, 0);
}
}