Oktober 2016
Band 31, Nummer 10
Dieser Artikel wurde maschinell übersetzt.
Testlauf: Varianzanalyse (ANOVA) mit C#
Von James McCaffrey
Varianzanalyse (Analysis of Variance, ANOVA) ist ein klassisches statistisches Verfahren, das zum Ermitteln verwendet wird, ob die Mittelwerte (Durchschnittswerte) von mindestens drei Gruppen gleich sind, wenn nur Stichprobendaten vorliegen. Angenommen, an einer Universität werden drei Einführungsseminare für Informatik angeboten. Jedes Seminar wird vom gleichen Seminarleiter unterrichtet. Es werden jedoch ein jeweils anderes Lehrbuch und eine andere Unterrichtsphilosophie verwendet. Sie möchten nun wissen, ob die Lernerfolge gleich sind oder ob es Unterschiede gibt.
Sie verfügen über einen schriftlichen Test, der die Informatikkenntnisse auf einer Skala von eins bis -15 bewertet. Da dieser Test jedoch sehr teuer und zeitaufwändig ist, können nur sechs zufällig ausgewählte Studenten in jedem Seminar an ihm teilnehmen. Sie sind für den Test verantwortlich und führen Varianzanalyse für die Stichproben aus, um zu ermitteln, ob die Mittelwerte aller drei Seminare gleich sind oder ob es Unterschiede gibt.
Wenn Sie mit Varianzanalyse nicht vertraut sind, kann der Name dieses Verfahrens etwas verwirrend sein, weil das Ziel darin besteht, die Mittelwerte von Datasets zu analysieren. Die Varianzanalyse trägt ihren Namen, weil hinter den Kulissen Varianzen analysiert werden, um Ableitungen zu Mittelwerten ausführen zu können.
Wenn Sie eine Vorstellung davon bekommen möchten, um was es sich bei der Varianzanalyse handelt und welche Intention dieser Artikel verfolgt, sollten Sie sich das Demoprogramm in Abbildung 1 ansehen. Im Demoprogramm werden hartcodierte Bewertungen für drei Gruppen eingerichtet. Beachten Sie, dass nur vier Bewertungen in Gruppe 1 und nur fünf Bewertungen in Gruppe 3 vorhanden sind. Stichprobengrößen sind häufig ungleich, weil die Testkandidaten aufgeben oder Daten verlorengehen oder beschädigt werden.
Abbildung 1: Demoprogramm „Varianzanalyse mit C#“
Für Varianzanalyse sind zwei Hauptschritte erforderlich. Im ersten Schritt werden ein statistischer F-Wert und ein Wertepaar namens „Freiheitsgrade” (Degrees of Freedom, df) mithilfe der Stichprobendaten berechnet. Im zweiten Schritt werden die Werte von F und df zum Ermitteln der Wahrscheinlichkeit verwendet, dass die Mittelwerte aller Grundgesamtheiten gleich sind (p-value). Der erste Schritt ist relativ einfach. Der zweite Schritt ist sehr schwierig.
Im Demoprogramm ist der Wert von F 15,884. Im Allgemeinen gilt: je größer F ist, desto weniger wahrscheinlich ist es, dass die Mittelwerte aller Grundgesamtheiten gleich sind. Ich werde kurz erläutern, warum df = (2, 12) ist. Unter Verwendung von F und df wird p-value als 0,000425 berechnet. Dies ist ein sehr kleiner Wert. Ich schließe daraus, dass die Mittelwerte der Grundgesamtheiten wahrscheinlich nicht alle gleich sind. An diesem Punkt könnten Sie weitere statistische Tests ausführen, um zu ermitteln, welche Mittelwerte der Grundgesamtheiten voneinander abweichen. Für die Demodaten scheint zu gelten, dass Gruppe 1 (Stichprobenmittelwert = 4,50) schlechter als Gruppe 2 (Mittelwert = 9,67) und Gruppe 3 (Mittelwert = 10,60) ist.
Das Demoprogramm
Um das Demoprogramm zu erstellen, habe ich Visual Studio gestartet, auf „Datei“ > „Neu“ > „Projekt“ geklickt und dann die Option „C#-Konsolenanwendung“ ausgewählt. Das Demoprogramm hat keine nennenswerten .NET-Abhängigkeiten, sodass jede Version von Visual Studio funktionieren sollte. Nach dem Laden des Vorlagencodes habe ich im Fenster des Projektmappen-Explorers mit der rechten Maustaste auf „Program.cs“ geklickt, die Datei in „AnovaProgram.cs“ umbenannt und Visual Studio die automatische Umbenennung der Klasse „Program“ gestattet.
Oben im Editor-Fenster habe alle überflüssigen using-Anweisungen außer der Anweisung gelöscht, die auf den Namespace „System“ der obersten Ebene verweist. Abbildung 2 zeigt die Gesamtstruktur des Programms. Das Demoprogramm ist zu lang, um es im Ganzen darzustellen, aber der vollständige Demoquellcode ist in dem Download enthalten, der zu diesem Artikel gehört.
Abbildung 2: Struktur des Demoprogramms
using System;
namespace Anova
{
class AnovaProgram
{
static void Main(string[] args)
{
Console.WriteLine("Begin ANOVA using C# demo");
// Set up sample data
// Use data to calculate F and df
// Use F and df to calculate p-value
Console.WriteLine("End ANOVA demo");
}
static double Fstat(double[][] data, out int[] df) { . . }
static double LogGamma(double z) { . . }
static double BetaIncCf(double a, double b, double x) { . . }
static double BetaInc(double a, double b, double x) { . . }
static double PF(double a, double b, double x) { . . }
static double QF(double a, double b, double x) { . . }
static void ShowData(double[][] data, string[] colNames) { . . }
}
}
Die statische Methode Fstat berechnet eine F-statistic basierend auf den in einem array-of-arrays-Objekt gespeicherten Daten und gibt diese zurück. Die Methode berechnet außerdem zwei df-Werte in einem array out-Parameter und gibt diese ebenfalls zurück. Die Funktion „ShowData“ ist nur eine kleine Hilfsfunktion zum Anzeigen der Stichprobenmittelwerte.
Die verbleiben fünf Methoden werden alle verwendet, um den p-value zu berechnen. Die Methode „QF“ ist die primäre Methode. Sie ruft die Methode „PF“ auf, die ihrerseits die Methode BetaInc aufruft, die dann die Methoden „BetaIncCf“ und „LogGamma“ aufruft.
Nach einigen vorbereitenden WriteLine-Meldungen schaltet sich die Main-Methode ein und zeigt die Stichprobendaten an:
double[][] data = new double[3][]; // 3 groups
data[0] = new double[] { 3, 4, 6, 5 };
data[1] = new double[] { 8, 12, 9, 11, 10, 8 };
data[2] = new double[] { 13, 9, 11, 8, 12 };
string[] colNames = new string[] { "Group1", "Group2", "Group3" };
ShowData(data, colNames);
In einem echten Szenario würden Ihre Daten wahrscheinlich in einer Textdatei gespeichert, und Sie würden eine Hilfsfunktion zum Lesen und Laden der Daten in ein „array-of-arrays“ schreiben.
F-statistic und df werden wie folgt berechnet:
int[] df = null;
double F = Fstat(data, out df);
Für die Varianzanalyse ist df für ein Dataset ein Wertepaar. Der erste Wert ist K - 1. Dabei ist K die Anzahl der Gruppen. Der zweite Wert ist N - K. Dabei ist N die Gesamtzahl der Stichprobenwerte. Für die Demodaten gilt: df = (K-1, N-K) = (3-1, 15-3) = (2, 12).
Der p-value wird wie folgt berechnet und angezeigt:
double pValue = QF(df[0], df[1], F);
Console.Write("p-value = ");
Zusammengefasst lässt sich sagen, dass die aufrufenden Anweisungen bei einer Varianzanalyse sehr einfach sind. Hinter den Kulissen werden jedoch zahlreiche Aufgaben ausgeführt.
Berechnen von F-statistic
Für das Berechnen des Werts von F-statistic sind mehrere Teilschritte erforderlich. Angenommen, die Stichprobendatenwerte stammen aus dem Demoprogramm:
Group1: 3.00, 4.00, 6.00, 5.00
Group2: 8.00, 12.00, 9.00, 11.00, 10.00, 8.00
Group3: 13.00, 9.00, 11.00, 8.00, 12.00
Der erste Teilschritt besteht im Berechnen der Mittelwerte jeder Gruppe sowie des Gesamtmittelwerts aller Stichprobenwerte. Folgendes gilt für die Demodaten:
means[0] = (3.0 + 4.0 + 6.0 + 5.0) / 4 = 4.50
means[1] = (8.0 + 12.0 + 9.0 + 11.0 + 10.0 + 8.0) / 6 = 9.67
means[2] = (13.0 + 9.0 + 11.0 + 8.0 + 12.0) / 5 = 10.60
gMean = (3.0 + 4.0 + . . . + 12.0) / 15 = 8.60
Die Definition der Methode „Fstat“ beginnt wie folgt:
static double Fstat(double[][] data, out int[] df)
{
int K = data.Length; // Number groups
int[] n = new int[K]; // Number items each group
int N = 0; // total number data points
for (int i = 0; i < K; ++i) {
n[i] = data[i].Length;
N += data[i].Length;
}
...
An diesem Punkt enthält das lokale Array „n“ die Anzahl der Werte in jeder Gruppe, „K“ enthält die Anzahl der Gruppen, und „N“ ist die Gesamtzahl der Werte in allen Gruppen. Im nächsten Schritt werden die Gruppenmittelwerte in ein Array namens „means“ berechnet, und der Gesamtmittelwert wird in die Variable „gMean“ berechnet:
double[] means = new double[K];
double gMean = 0.0;
for (int i = 0; i < K; ++i) {
for (int j = 0; j < data[i].Length; ++j) {
means[i] += data[i][j];
gMean += data[i][j];
}
means[i] /= n[i];
}
gMean /= N;
Der nächste Teilschritt ist die Berechnung der „Quadratsumme zwischen Gruppen“ (Sum of Squares between Groups, SSb) und des „quadratischen Mittelwerts zwischen Gruppen“ (Mean Square between Groups, MSb). SSb ist die gewichtete Summe der quadratischen Abweichungen zwischen dem Mittelwert der einzelnen Gruppen und dem Gesamtmittelwert. MSb = SSb / (K-1). Dabei ist „K“ die Anzahl der Gruppen. Folgendes gilt für die Demodaten:
SSb = (4 * (4.50 - 8.60)^2) +
(6 * (9.67 - 8.60)^2) +
(5 * (10.60 - 8.60)^2) = 94.07
MSb = 94.07 / (3-1) = 47.03
Der folgende Code berechnet SSb und MSb:
double SSb = 0.0;
for (int i = 0; i < K; ++i)
SSb += n[i] * (means[i] - gMean) * (means[i] - gMean);
double MSb = SSb / (K - 1);
Der nächste Teilschritt besteht in der Berechnung der „Quadratsumme in Gruppen (Sum of Squares within Groups, SSw) und des „quadratischen Mittelwerts in Gruppen“ (Mean Square within Groups, MSw). SSw ist die Summe der quadratischen Abweichungen zwischen jedem Stichprobenwert und seinem Gruppenmittelwert. MSw = SSw / (N-K). Folgendes gilt für die Demodaten:
SSw = (3.0 - 4.50)^2 + . . + (8.0 - 9.67)^2 +
. . + (12.0 - 10.60)^2 = 35.53
MSw = 35.53 / (15-3) = 2.96
Der folgende Code berechnet SSw und MSw:
double SSw = 0.0;
for (int i = 0; i < K; ++i)
for (int j = 0; j < data[i].Length; ++j)
SSw += (data[i][j] - means[i]) * (data[i][j] - means[i]);
double MSw = SSw / (N - K);
Im letzten Teilschritt werden die beiden df-werte und F-statistic berechnet. Die beiden df-Werte sind K - 1 und N - K. Außerdem gilt: F = MSb / MSw. Folgendes gilt für die Demodaten:
df = (K-1, N-K) = (3-1, 15-3) = (2, 12)
F = 47.03 / 2.96 = 15.88.
Der folgende Democode berechnet df und F:
...
df = new int[2];
df[0] = K - 1;
df[1] = N - K;
double F = MSb / MSw;
return F;
} // Fstat
Sie stimmen mir sicherlich zu, dass das Berechnen von F-statistic und df-Werten aus einer Datenmenge ein mechanischer und relativ einfacher Vorgang ist, wenn die mathematischen Gleichungen bekannt sind.
Berechnen von p-value
Das Konvertieren von F-statistic und den df-Werten in einen p-value, der die Wahrscheinlichkeit beinhaltet, dass alle Mittelwerte der Grundgesamtheiten basierend auf den Stichprobendaten gleich sind, die zu F und df führen, ist im Prinzip einfach, in der Praxis aber extrem schwierig. Ich werde mich so kurz wie möglich fassen und dabei zahlreiche Details vernachlässigen, die umfangreiche Erklärungen nach sich ziehen würden. Sehen Sie sich das Diagramm in Abbildung 3 an.
Abbildung 3: Berechnen von p-value aus F-statistic und df-Werten
Jedes mögliche Paar von df-Werten führt zu einem Diagramm, das als F-distribution (F-Verteilung) bezeichnet wird. Der Verlauf einer F-Verteilung ist abhängig von den Werten für df sehr unterschiedlich. Das Diagramm in Abbildung 3 zeigt eine F-Verteilung für df = (4, 12). Ich habe df = (4, 12) (und nicht df = (2, 12)) aus den Demodaten verwendet, weil der Verlauf der F-Verteilung von df = (2, 12) ausgesprochen atypisch ist.
Der Gesamtbereich unter jeder F-Verteilung ist genau 1,0. Wenn Sie den Wert von F-statistic kennen, ist p-value der Bereich unter der F-Verteilung von F bis positive infinity. Der Bereich unter der F-Verteilung von null bis F-statistic wird häufig als PF bezeichnet und der Bereich unter der F-Verteilung von F-statistic bis positive infinity (der den p-value darstellt) als QF (etwas verwirrend, ich weiß). Da der Gesamtbereich unter der Verteilung 1 ist, gilt: PF + QF = 1. In der Praxis ist es etwas einfacher, PF als QF zu berechnen. Um p-value (QF) zu ermitteln, wird daher normalerweise PF berechnet und dieser Wert dann von 1 subtrahiert, um QF zu erhalten.
Das Berechnen von PF ist extrem schwierig. Glücklicherweise sind jedoch seit Jahrzehnten Magic Estimation-Gleichungen bekannt. Diese mathematischen Gleichungen (und Hunderte andere Gleichungen) finden Sie im bekannten Standardwerk „Handbook of Mathematical Functions“ von M. Abramowitz und I. Stegun. Dieses Nachschlagewerk wird von Programmierern häufig einfach „A&S“ genannt. Jede A&S-Gleichung ist mit einer ID versehen.
Im Demoprogramm ist die Methode „PF“ einfach nur ein Wrapper um die Methode „BetaInc“:
static double PF(double a, double b, double x)
{
double z = (a * x) / (a * x + b);
return BetaInc(a / 2, b / 2, z);
}
Der Name der Methode „BetaInc“ bedeutet „unvollständiges Beta“. Die Methode „BetaInc“ verwendet die A&S-Gleichungen 6.6.2 und 26.5.8. Diese Gleichungen rufen eine LogGamma-Funktion und eine BetaIncCf-Funktion auf. Die LogGamma-Funktion ist nur sehr schwer zu erklären und zu implementieren. Kurz gesagt, erweitert die mathematische Gammafunktion die Vorstellung der Fakultät auf reelle Zahlen. Ebenso wie Fakultäten können die Werte der Gammafunktion astronomisch groß werden. Der Einfachheit halber wird daher der Logarithmus der Gammafunktion berechnet, damit die Werte kleiner sind.
Die Berechnung von „LogGamma“ ist sehr kompliziert, und es stehen mehrere Algorithmen zur Verfügung, die Sie verwenden können. Das Demoprogramm verwendet einen Algorithmus mit dem Namen Lanczos-Approximation mit (g=5, n=7). Das A&S-Nachschlagewerk nennt andere Algorithmen, die LogGamma berechnen können. Die Lanczos-Approximation, die bei der Veröffentlichung von A&S noch nicht bekannt war, führt aber zu genaueren Ergebnissen.
Der Name der Methode „BetaIncCf“ bedeutet „unvollständiges Beta, berechnet durch Kettenbruch“ (incomplete Beta computed by continued fraction). Das Demoprogramm verwendet die A&S-Gleichung 26.5.8 für die Methode „BetaIncCf“.
Zusammenfassung
Ein Varianzanalysetest geht von drei mathematischen Annahmen aus: Die Gruppendatenelemente sind mathematisch voneinander unabhängig, die Datasets, die die Grundgesamtheit bilden, sind normal verteilt (wie in der Normalverteilung), und sie weisen gleiche Varianzen auf.
Diese Annahmen können auf verschiedene Weise getestet werden. Die Interpretation ihrer Ergebnisse stellt jedoch eine Herausforderung dar. Das Problem besteht darin, dass es höchst unwahrscheinlich ist, dass echte Daten genau der Normalverteilung entsprechen und genau gleiche Varianzen aufweisen. Die Varianzanalyse funktioniert allerdings auch dann, wenn die Daten von der Normalverteilung abweichen oder ungleiche Varianzen aufweisen. Quintessenz: Es ist ausgesprochen schwierig, Annahmen der Varianzanalyse zu beweisen. Sie sollten beim Interpretieren der Ergebnisse daher sehr konservativ vorgehen.
Die Varianzanalyse ist eng mit dem t-test verwandt. Der t-test ermittelt, ob die Mittelwerte der Grundgesamtheit von genau zwei Gruppen gleich sind, wenn nur Stichprobendaten vorliegen. Wenn drei Gruppen vorhanden sind (wie im Demoprogramm), könnten Sie anstelle der Varianzanalyse ggf. auch drei t-tests ausführen und die Gruppen 1 und 2, die Gruppen 1 und 3 und die Gruppen 2 und 3 vergleichen. Diese Herangehensweise wird jedoch nicht empfohlen, weil ein Typ 1-Fehler (ein falsch positives Ergebnis) auftritt.
Die in diesem Artikel vorgestellte Varianzanalyse wird als einfache (oder einfaktorielle) ANOVA bezeichnet. Ein anderes Verfahren (als zweifache ANOVA bezeichnet) wird verwendet, wenn zwei Faktoren vorhanden sind.
Die Varianzanalyse basiert auf dem berechneten Wert von F-statistic aus einem Dataset. Es sind weitere statistische Tests verfügbar, die F-statistic verwenden. Sie können z. B. F-statistic verwenden, um zu ermitteln, ob die Varianzen von zwei Datengruppen gleich sind.
Dr. James McCaffreyist in Redmond (Washington) für Microsoft Research tätig. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem an Internet Explorer und Bing. Dr. McCaffrey erreichen Sie unter jammc@microsoft.com.
Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Chris Lee und Kirk Olynk