Есть ли эквивалентная функция для anova.lm() в Java?

Я сравниваю две линейные модели в R с Anova, и я хотел бы сделать то же самое в Java. Чтобы упростить его, я взял пример кода из https://stats.stackexchange.com/questions/48854/why-am-i-getting-Different-intercept-values-in-r-and-java.-for-simple-linear-regr и немного изменил его ниже. Модели test_trait ~ geno_A + geno_B и test_trait ~ geno_A + geno_B + geno_A:geno_B. Коэффициенты моделей, реализованных в R и Java, одинаковы. В R я использую anova(fit, fit2), где подгонки являются результатами lm, а в Java я использую TestUtils.oneWayAnovaPValue из org.apache.commons.math3.

С R я получаю pvalue 0.797, а с Java я получаю pvalue 0.817, так что это неправильный метод, но я не могу найти, как это сделать правильно. Есть ли эквивалент R anova.lm в Java?

Полный код ниже.

R

test_trait <- c( -0.48812477 , 0.33458213, -0.52754476, -0.79863471, -0.68544309, -0.12970239,  0.02355622, -0.31890850,0.34725819 , 0.08108851)
geno_A <- c(1, 0, 1, 2, 0, 0, 1, 0, 1, 0)
geno_B <- c(0, 0, 0, 1, 1, 0, 0, 0, 0, 0) 
fit <- lm(test_trait ~ geno_A+geno_B)
fit2 <- lm(test_trait ~ geno_A + geno_B + geno_A:geno_B)

что дает коэффициенты

> fit
Call:
lm(formula = test_trait ~ geno_A + geno_B)

Coefficients:
(Intercept)       geno_A       geno_B  
   -0.03233     -0.10479     -0.60492  

> fit2
Call:
lm(formula = test_trait ~ geno_A + geno_B + geno_A:geno_B)

Coefficients:
  (Intercept)         geno_A         geno_B  geno_A:geno_B  
    -0.008235      -0.152979      -0.677208       0.096383  

И Анова

> anova(fit, fit2) # 0.797 
Analysis of Variance Table

Model 1: test_trait ~ geno_A + geno_B
Model 2: test_trait ~ geno_A + geno_B + geno_A:geno_B
  Res.Df     RSS Df Sum of Sq      F Pr(>F)
1      7 0.77982                           
2      6 0.77053  1 0.0092897 0.0723  0.797

Джава

    double [] y =  {-0.48812477,  0.33458213,  
            -0.52754476, -0.79863471,
            -0.68544309, -0.12970239,
             0.02355622, -0.31890850,
             0.34725819,  0.08108851};
double [][] x = {{1,0}, {0,0},
                 {1,0}, {2,1},
                 {0,1}, {0,0},
                 {1,0}, {0,0},
                 {1,0}, {0,0}};
double [][] xb = {{1,0,0}, {0,0,0},
                  {1,0,0}, {2,1,2},
                  {0,1,0}, {0,0,0},
                  {1,0,0}, {0,0,0},
                  {1,0,0}, {0,0,0}};

OLSMultipleLinearRegression regr = new OLSMultipleLinearRegression();
regr.newSampleData(y, x);
double[] beta = regr.estimateRegressionParameters();   

System.out.printf("First model: y = int + genoA + genoB\n");
System.out.printf("Intercept: %.3f\t", beta[0]);
System.out.printf("beta1: %.3f\t", beta[1]);
System.out.printf("beta2: %.3f\n\n", beta[2]);

regr.newSampleData(y, xb);
double[] betab = regr.estimateRegressionParameters();   

System.out.printf("Second model: y = int + genoA + genoB + genoA:genoB\n");
System.out.printf("Intercept: %.3f\t", betab[0]);
System.out.printf("beta1: %.3f\t", betab[1]);
System.out.printf("beta2: %.3f\t", betab[2]);
System.out.printf("beta2: %.3f\n", betab[3]);

Что дает те же коэффициенты, что и в R

First model: y = int + genoA + genoB
Intercept: -0.032   beta1: -0.105   beta2: -0.605

Second model: y = int + genoA + genoB + genoA:genoB
Intercept: -0.008   beta1: -0.153   beta2: -0.677   beta2: 0.096

Но Anova дает другой результат

List classes = new ArrayList();
classes.add(beta);
classes.add(betab);
double pvalue = TestUtils.oneWayAnovaPValue(classes);
double fvalue = TestUtils.oneWayAnovaFValue(classes);
System.out.println(pvalue); 
System.out.println(fvalue); 

0.8165390406874127
0.05979444576790511

person Niek de Klein    schedule 14.02.2016    source источник
comment
Включает ли версия Java термин взаимодействия? См. stats.stackexchange.com/questions/48854/   -  person Benjamin    schedule 17.02.2016
comment
@Benjamin включает термин взаимодействия, я проверяю это, сравнивая значения коэффициентов. Я отредактировал это в вопросе   -  person Niek de Klein    schedule 17.02.2016
comment
@NiekdeKlein Также укажите значение F кода Java. У меня есть небольшое подозрение, что Java и R вычисляют вещи немного по-разному.   -  person Joris Meys    schedule 17.02.2016
comment
@Joris Meys 0.05979444576790511 (с double pvalue = TestUtils.oneWayAnovaFValue(classes);)   -  person Niek de Klein    schedule 17.02.2016


Ответы (2)


Вы очень неправильно понимаете ANOVA в случае, когда сравниваете две регрессии. Это не дисперсионный анализ в смысле oneWayAnova. Эквивалентом onewayAnova в R является функция aov. С другой стороны, функция anova реализует множество тестов для сравнения моделей, а название anova, мягко говоря, сбивает с толку...

Если вы сравниваете две регрессионные модели, вы хотите провести F-тест суммы квадратов. То, что вы делаете в своем коде, представляет собой односторонний ANOVA, чтобы увидеть, значительно ли различаются два набора параметров регрессии. Это не то, что вы хотите сделать, но это именно то, что делает ваш код JAVA.

Для того, чтобы рассчитать правильный F-тест, вам необходимо сделать следующее:

  1. рассчитать MSE для наибольшей модели путем деления остаточной суммы квадратов (RSS) на степени свободы (df) (в таблице R: 0,77053/6

  2. рассчитать MSEdifference путем вычитания RSS обеих моделей (результат — «Сумма квадратов» в таблице R), вычитания df для обеих моделей (результат — «Df» в таблице R) и разделить эти числа.

  3. Разделите 2 на 1, и вы получите значение F.

  4. вычислить p-значение, используя значение F в 3 и для df разность df в числителе и df наибольшей модели в знаменателе.

Насколько я знаю, класс OLSMultipleLinearRegression не имеет удобных методов для извлечения количества степеней свободы, поэтому в Java это сделать не так просто. Вам придется вычислить df вручную, а затем использовать класс FDistribution для вычисления значения p.

eg:

OLSMultipleLinearRegression regr = new OLSMultipleLinearRegression();
regr.newSampleData(y, x);
double SSR1 = regr.calculateResidualSumOfSquares();
double df1 = y.length - (x[0].length + 1); 
    //df = n - number of coefficients, including intercept

regr.newSampleData(y, xb);
double SSR2 = regr.calculateResidualSumOfSquares();
double df2 = y.length - (xb[0].length + 1);

double MSE = SSR2/df2; // EDIT: You need the biggest model here!
double MSEdiff = Math.abs ((SSR2 - SSR1) / (df2 - df1));
double dfdiff = Math.abs(df2 - df1);

double Fval = MSEdiff / MSE;

FDistribution Fdist = new FDistribution(dfdiff, df2);
double pval = 1 - Fdist.cumulativeProbability(Fval);

Теперь и значение F, и значение p должны быть именно такими, какие вы видите в таблице anova() для R. df1 и df2 — это столбцы Res.Df в таблице R, разница должна быть Df в таблице R, а MSEdiff должно быть то же самое, что и Sum of Sq., разделенное на Df из таблицы R.

Отказ от ответственности: я плохой программист JAVA, поэтому приведенный выше код является более концептуальным, чем фактический код. Пожалуйста, ищите опечатки или глупые ошибки и проверяйте документацию по классу FDistribution, который я использовал здесь:

https://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/distribution/FDistribution.html#cumulativeProbability%28double%29

И теперь вы знаете, почему статистики используют R вместо Java ;-)


РЕДАКТИРОВАТЬ: FDistribution, используемый в приведенном выше коде, является классом

org.apache.commons.math3.distribution.FDistribution

В JSci также есть FDistribution:

JSci.maths.statistics.FDistribution

Если вы используете его, последняя часть кода станет:

FDistribution Fdist = new FDistribution(dfdiff, df2);
double pval = 1 - Fdist.cumulative(Fval);

В зависимости от конкретной реализации совокупные вероятности могут незначительно отличаться. Я, увы, понятия не имею о разнице и/или о том, кому из них можно доверять лучше.

person Joris Meys    schedule 17.02.2016
comment
Большое спасибо! Сумма квадратов такая же, как и в R, но значение F другое (java: 0,782474, R: 0,797). - person Niek de Klein; 17.02.2016
comment
@NiekdeKlein Вы имеете в виду значение F (0,723) или значение p? (0,797). Я мог ошибиться с длинами при вычислении степеней свободы, но я не могу проверить здесь дома. - person Joris Meys; 17.02.2016
comment
@NiekdeKlein df2 должно быть 6, df1 должно быть 7, а Fval должно быть 0,723. Если они верны, то разница заключается в реализации дистрибутива F в Java. Если нет, вам могут понадобиться x.length и xb.length вместо x[0].length и xb[0].length. Я всегда путаюсь с этими массивами массивов... - person Joris Meys; 17.02.2016
comment
Спасибо за объяснение, df1 и df2 правильные, но pval из вашего редактирования такой же, как и предыдущий, поэтому мне придется покопаться в дистрибутиве F. - person Niek de Klein; 18.02.2016
comment
@NiekdeKlein Моя ошибка! Я использовал меньшую модель вместо большей для расчета MSE. Извини за это. Я отредактировал, и теперь все должно быть правильно. - person Joris Meys; 18.02.2016

Проблема в том, что методы, которые вы сравниваете, не делают то же самое.

anova() в R фактически выполняет тест отношения правдоподобия, чтобы проверить, соответствует ли ваша вторая модель значительно улучшается за счет добавления новой переменной: дополнительная информация в ответе здесь

С другой стороны, oneWayAnovaPValue() в java просто запускает t-тест, чтобы проверить, является ли разница в средних значениях между группами значительной. В этом случае вы сравниваете, значительно ли среднее значение вашего первого набора коэффициентов отличается от второго набора, что не имеет значения.

Насколько я знаю, в java нет функции, легко доступной для легкого выполнения теста отношения правдоподобия. Но вы можете создать его довольно легко. В R вы можете сделать следующее

anova(fit, fit2,test="Chisq")
#p: 0.788

#or manually:
df.diff = fit$df.residual - fit2$df.residual
vals <- (sum(residuals(fit)^2) - sum(residuals(fit2)^2))/sum(residuals(fit2)^2) * fit2$df.residual 
pchisq(vals, df.diff, lower.tail = FALSE)
#p: 0.7879634

так что вы можете использовать тот же подход в java. Короткий поиск в Google дал мне реализацию pchisq в java здесь (обратите внимание, что команда lower.tail=FALSE аналогична команде 1-pchisq(lower.tail=TRUE), поэтому нам эта опция не нужна).

Это позволяет нам сделать следующее

public void regressionRun(){
OLSMultipleLinearRegression regr = new OLSMultipleLinearRegression();
OLSMultipleLinearRegression regr2 = new OLSMultipleLinearRegression();

double[] y = new double[] { -0.48812477, 0.33458213, -0.52754476,
        -0.79863471, -0.68544309, -0.12970239, 0.02355622, -0.31890850,
        0.34725819, 0.08108851 };
double[][] x = new double[10][];
double[][] x2 = new double[10][];
x[0] = new double[] { 1, 0 };
x[1] = new double[] { 0, 0 };
x[2] = new double[] { 1, 0 };
x[3] = new double[] { 2, 1 };
x[4] = new double[] { 0, 1 };
x[5] = new double[] { 0, 0 };
x[6] = new double[] { 1, 0 };
x[7] = new double[] { 0, 0 };
x[8] = new double[] { 1, 0 };
x[9] = new double[] { 0, 0 };
//
x2[0] = new double[] { 1, 0, 0 };
x2[1] = new double[] { 0, 0, 0 };
x2[2] = new double[] { 1, 0, 0 };
x2[3] = new double[] { 2, 1, 2 };
x2[4] = new double[] { 0, 1, 0 };
x2[5] = new double[] { 0, 0, 0 };
x2[6] = new double[] { 1, 0, 0 };
x2[7] = new double[] { 0, 0, 0 };
x2[8] = new double[] { 1, 0, 0 };
x2[9] = new double[] { 0, 0, 0 };

regr.newSampleData(y, x);
double[] b = regr.estimateResiduals();

regr2.newSampleData(y, x2);
double[] b2 = regr2.estimateResiduals();

//calculate sum of squares
double sumsq_b = 0;
double sumsq_b2 = 0;
for (double res : b){
    sumsq_b += res**2;
}
for (double res : b2){
    sumsq_b2 += res**2;
}
//calculate degrees of freedom
int df_b = y.length-(x[0].length+1);
int df_b2 = y.length-(x2[0].length+1);

double vals = (sumsq_b-sumsq_b2)/sumsq_b2*df_b2;

double pvalue = 1-pchisq(vals,df_b-df_b2);
System.out.println(pvalue);
}
//0.7879633810167291
person Jonas Coussement    schedule 17.02.2016
comment
В случае lm R по умолчанию использует F-тест, а не критерий хи-квадрат. Тест F более подходит, и вы можете видеть это, сравнивая значение p, которое вы получаете, со значением, возвращаемым anova. см. также ?anova.lm - person Joris Meys; 17.02.2016
comment
Однако подход был бы в основном таким же, не так ли? - person Jonas Coussement; 17.02.2016
comment
Если вы имеете в виду вычислить это вручную, потому что JAVA отстой для статистики, то абсолютно :-) Я добавил пример кода, показывающий, как я буду это делать, используя соответствующие классы распределения в JAVA. - person Joris Meys; 17.02.2016