Paket einlesen¶
import pandas as pd # Tabellenmanipulation
import numpy as np # Numerische Funktionen
import warnings # Warnungen unterdrücken
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt # Plot-Erstellung
import seaborn as sns # Statistische Plots
from scipy.stats import probplot # QQ-Plot
from scipy.stats import levene # Levene-Test
from scipy.stats import f_oneway # Klassische Einweg-ANOVA
import scipy.stats as stats # Weitere Tests (t-Test, Shapiro, …)
import statsmodels.api as sm # OLS-Modelle, ANOVA
from statsmodels.formula.api import ols
from statsmodels.stats.multicomp import pairwise_tukeyhsd # Tukey HSD
import pingouin as pg # Welch-ANOVA, Games-Howell, Effektstärken
Daten einlesen¶
# Excel-Datei laden
df = pd.read_excel("anovaohne.xlsx")
df.columns = df.columns.str.strip()
df.head()
ID | Trainingsarten | Ausdauertest | |
---|---|---|---|
0 | 1 | Trainingsart 1 | 45 |
1 | 2 | Trainingsart 1 | 41 |
2 | 3 | Trainingsart 1 | 40 |
3 | 4 | Trainingsart 1 | 44 |
4 | 5 | Trainingsart 1 | 33 |
Hypothese¶
H1: Es gibt einen Mittelwertsunterschied zwischen der Trainingsarten (Training1, Training 2, Training 3, Training 4) und der Ausdauer.
$M_{1} \neq M_{2} \neq M_{3} \neq M_{4} \text { für min. einen Vergleich}$
H0: Es gibt keinen Mittelwertsunterschied zwischen der Trainingsarten (Training1, Training 2, Training 3, Training 4) und der Ausdauer.
$M_{1} = M_{2}= M_{3}= M_{4}$
Voraussetzungen der einfaktoriellen Varianzanalyse ohne Messwiederholung¶
✓ Die abhängige Variable ist intervallskaliert -> Ausdauer ist metrisch
✓ Die unabhängige Variable (Faktor) ist kategorial (nominal- oder ordinalskaliert) -> Die Trainingsarten sind nominal-skaliert.
✓ Die durch den Faktor gebildeten Gruppen sind unabhängig Jeder TeilnehmerIn hat ausschliesslich in seiner oder ihrer Gruppe trainiert.
✓ Die abhängige Variablen ist normalverteilt innerhalb jeder der Gruppen (Ab > 25 Probanden pro Gruppe sind Verletzungen in der Regel unproblematisch) -> siehe Histogramm und QQplot
✓ Homogenität der Varianzen: Die Gruppen stammen aus Grundgesamtheiten mit annähernd identischen Varianzen der abhängigen Variablen -> siehe Levene-Test
Boxplots¶
farben = ["lightgreen", "deepskyblue", "tomato", "orange"]
# Boxplot erstellen
sns.boxplot(
data=df,
x="Trainingsarten",
y="Ausdauertest",
palette=farben
)
# Titel und Achsenbeschriftungen
plt.title("Boxplots zum Vergleich")
plt.xlabel("Trainingsmethode")
plt.ylabel("Ausdauer")
plt.show()
Boxplot zeigt keine Ausreisser. Die Verteilungen scheinen sich von einander zu unterscheiden, allerdings nicht so eindeutig bei Training 3 und Training 4.
Normalverteilung -> Prüfung mittels Histogramm¶
Um einen ersten Überblick über die Daten zu gewinnen, empfiehlt es sich Histogrammm zu erstellen.
# Facettiertes Histogramm nach Trainingsart
g = sns.displot(
data=df,
x="Ausdauertest",
col="Trainingsarten",
hue="Trainingsarten",
bins=20,
kde=False,
palette="pastel"
)
# Achsen- und Gesamtbeschriftung
g.set_axis_labels("Ausdauertest", "Anzahl")
g.set_titles("Trainingsart: {col_name}")
g.fig.suptitle("Histogramme nach Trainingsarten", y=1.05)
plt.show()
Die Daten sind normalverteilt, wenn auch nicht perfekt.
Alternativ QQPlot¶
# Trainingsgruppen extrahieren
gruppen = df["Trainingsarten"].unique()
fig, axes = plt.subplots(1, len(gruppen), figsize=(5 * len(gruppen), 4))
# QQ-Plots für jede Gruppe zeichnen
for ax, gruppe in zip(axes, gruppen):
werte = df[df["Trainingsarten"] == gruppe]["Ausdauertest"]
probplot(werte, dist="norm", plot=ax)
ax.set_title(f"QQ-Plot: {gruppe}")
ax.set_xlabel("Theoretische Quantile")
ax.set_ylabel("Beobachtete Werte")
plt.tight_layout()
plt.show()
Die Daten sind normalverteilt.
Prüfung der Varianzhomogenität (Levene-Test)¶
Für die Durchführung eines t-Tests für unabhängige Gruppen ist die Annahme der Varianzhomogenität erforderlich.
Wenn jedoch Varianzheterogenität – also ungleiche Varianzen – vorliegt, müssen unter anderem die Freiheitsgrade des t-Wertes angepasst werden.
Ob die Varianzen tatsächlich gleich sind, lässt sich mit dem Levene-Test überprüfen.
Der Levene-Test geht von der Nullhypothese aus, dass sich die Varianzen nicht unterscheiden.
Ein nicht signifikantes Ergebnis spricht daher dafür, dass die Varianzen als gleich angenommen werden können – es liegt also Varianzhomogenität vor.
Ist das Testergebnis hingegen signifikant, deutet dies auf Varianzheterogenität hin – die Annahme gleicher Varianzen muss dann verworfen werden.
# Gruppen nach 'Trainingsarten' extrahieren
gruppen = [gruppe["Ausdauertest"].values for name,
gruppe in df.groupby("Trainingsarten")]
# Levene-Test mit Mittelwert-Zentrierung
stat, p = levene(*gruppen, center="mean")
# Freiheitsgrade berechnen
k = len(gruppen) # Anzahl Gruppen
N = sum(len(g) for g in gruppen) # Gesamtanzahl Beobachtungen
df_between = k - 1 # Zählerfreiheitsgrad
df_within = N - k # Nennerfreiheitsgrad
# Tabelle mit Ergebnissen erstellen
levene_ergebnisse = pd.DataFrame([{
"Test": "Levene-Test",
"W-Wert": f"{stat:.2f}",
"p-Wert": f"{p:.4f}",
"df (zwischen)": df_between,
"df (innerhalb)": df_within
}])
# Tabelle anzeigen
print("Ergebnisse des Levene-Tests:")
print(levene_ergebnisse.to_string(index=False))
Ergebnisse des Levene-Tests: Test W-Wert p-Wert df (zwischen) df (innerhalb) Levene-Test 2.91 0.0376 3 115
Im vorliegenden Beispiel ist der Levene-Test signifikant(F(3,115) = 2.908, p = .037), so dass von Varianzhetrogenität ausgegangen werden kann. Das heisst - es muss eine Welch-Korrektur durchgeführt werden.
Mit Welch-Korrektur: p < 0.05 => Ergebnis Signifikant --> Varianzen heterogen
Ohne Welch-Korrektur: p > 0.05 => Ergebnis nicht Signifikant --> Varianzen homogen --> H0 mit Annahme Var1=Var2=... -> Var_n wird angenommen
Deskriptive Statistiken¶
Die Tabelle in Abbildung gibt die Mittelwerte, Standardabweichungen und Grössen aller vier Gruppen wieder. Diese Informationen werden für die Berichterstattung verwendet.
# Gruppierte deskriptive Statistik
statistik = (
df.groupby("Trainingsarten")["Ausdauertest"]
.agg(
Anzahl="count",
Mittelwert="mean",
Median="median",
Standardabweichung="std"
)
.round(2)
)
display(statistik)
Anzahl | Mittelwert | Median | Standardabweichung | |
---|---|---|---|---|
Trainingsarten | ||||
Trainingsart 1 | 29 | 38.83 | 40.0 | 3.99 |
Trainingsart 2 | 30 | 48.17 | 48.5 | 3.45 |
Trainingsart 3 | 30 | 25.10 | 25.0 | 3.06 |
Trainingsart 4 | 30 | 22.03 | 22.0 | 2.43 |
Es gibt einen Mittelwertsunterschied zwischen den Gruppen. Trainingsart 2 (M = 48.16, SD = 3.45, n = 30) zeigt die besten Ausdauerergebnisse, gefolgt von Trainingsgruppe 2 (M 38.82, SD = 3.99,n = 29). Wie bereits beim Boxplot zu erkennen war, ist der Abstand der Mittelwert bei Trainingsart 3 (M = 25.10, SD = 3.05, n = 30) und Trainingsart 4( M = 22.03, SD = 2.42, n = 30) ähnlich ausgefallen.
Ergebnisse der einfaktoriellen Varianzanalyse¶
Modell¶
Das Modell wird im Post-Hoc als auch beim Eta^2 verwendet.
model = ols("Ausdauertest ~ C(Trainingsarten)", data=df).fit()
# ANOVA-Tabelle erzeugen (Typ-1-ANOVA)
anova_table = sm.stats.anova_lm(model, typ=1)
# Ausgabe der ANOVA-Ergebnisse
print("Ergebnisse der einfaktoriellen ANOVA:")
print(anova_table)
Ergebnisse der einfaktoriellen ANOVA: df sum_sq mean_sq F PR(>F) C(Trainingsarten) 3.0 13337.759828 4445.919943 414.337682 1.838159e-61 Residual 115.0 1233.971264 10.730185 NaN NaN
mit Welch-Korrektur¶
# Welch-ANOVA durchführen
welch_anova = pg.welch_anova(dv="Ausdauertest",
between="Trainingsarten", data=df)
# Ausgabe der Ergebnisse
print("Welch-ANOVA-Ergebnisse:")
print(welch_anova)
Welch-ANOVA-Ergebnisse: Source ddof1 ddof2 F p-unc np2 0 Trainingsarten 3 62.679724 446.849941 3.070231e-42 0.915317
Das Gesamtergebnis der Analyse ist signifikant (F(3, 62.68) = 446.85, p < .001). Dies zeigt, dass es insgesamt Unterschiede zwischen den vier Gruppen gibt. Allerdings verrät dieser Test noch nicht, zwischen welchen Gruppen die Unterschiede tatsächlich bestehen. Es ist z. B. möglich, dass sich nur ein einzelnes Gruppenpaar signifikant unterscheidet, während die übrigen Gruppen ähnlich abschneiden. Um dies genauer zu untersuchen, wird ein Post-hoc-Test durchgeführt, der paarweise Vergleiche ermöglicht.
Post-hoc-Tests¶
Im Allgemeinen sollen durch Post-hoc Tests folgende drei Fragestellungen addressiert werden:
Frage 01 Welcher Vergleich wird signifikant und welcher nicht?
Frage 02 Welche Gruppen sind unabhängig und welche nicht?
Frage 03 Optional: Sind Gruppenbildungen möglich/ sinnvoll? - Wenn ja, welche?
Der F-Test weist darauf hin, dass die Trainingsart einen Einfluss auf die Ergebnisse im Ausdauertest hat. Er zeigt jedoch nicht, welche konkreten Trainingsmethoden sich voneinander unterscheiden. Um festzustellen, zwischen welchen Gruppen tatsächlich signifikante Unterschiede bestehen, sind weiterführende Post-hoc-Analysen notwendig.
$$\frac{k\cdot(k-1)}{2} =\frac{4\cdot(4-1)}{2} = \frac{12}{2} = 6$$ mit $k$ = Ausprägungen/ Gruppen/ Stufen
Bei Post-hoc-Tests wird im Grunde für jede mögliche Paarung von Gruppen ein t-Test durchgeführt, um ihre Mittelwerte miteinander zu vergleichen. In unserem Fall mit vier Gruppen ergeben sich daraus sechs Paarvergleiche.
Das Problem dabei: Je mehr Tests durchgeführt werden, desto größer wird die Wahrscheinlichkeit, dass fälschlich ein signifikanter Unterschied festgestellt wird – also ein sogenannter Alpha-Fehler (Fehler 1. Art).
Ein einzelner t-Test mit einem Signifikanzniveau von 0.05 bedeutet, dass mit 95 %iger Wahrscheinlichkeit kein Alpha-Fehler auftritt. Werden aber sechs unabhängige Tests durchgeführt, liegt die Wahrscheinlichkeit, dass kein einziger dieser Tests einen Alpha-Fehler macht, nur noch bei $0.95^6 = 0.735$. Daraus ergibt sich eine Wahrscheinlichkeit von $1 - 0.735 = 0.2649$ für mindestens einen Alpha-Fehler – also rund 26.5 %.
Diese kumulierte Fehlerwahrscheinlichkeit über mehrere Tests hinweg wird als Familywise Error Rate (FWER) bezeichnet. Um diesen Effekt zu kontrollieren, verwendet man in der Regel Korrekturverfahren wie z. B. die Bonferroni-Korrektur.
Um dieses Problem zu beheben kann zum Beispiel die Turkey angewendet werden. RStudio rechnet das neue Niveau ein, daher können wir weiter auf 0.05 testen.
tukey = pairwise_tukeyhsd(
endog=df["Ausdauertest"], # abhängige Variable
groups=df["Trainingsarten"], # Gruppenfaktor
alpha=0.05 # Signifikanzniveau
)
# Ergebnisse anzeigen
print(tukey.summary())
Multiple Comparison of Means - Tukey HSD, FWER=0.05 ====================================================================== group1 group2 meandiff p-adj lower upper reject ---------------------------------------------------------------------- Trainingsart 1 Trainingsart 2 9.3391 0.0 7.1152 11.563 True Trainingsart 1 Trainingsart 3 -13.7276 0.0 -15.9515 -11.5037 True Trainingsart 1 Trainingsart 4 -16.7943 0.0 -19.0181 -14.5704 True Trainingsart 2 Trainingsart 3 -23.0667 0.0 -25.2716 -20.8617 True Trainingsart 2 Trainingsart 4 -26.1333 0.0 -28.3383 -23.9284 True Trainingsart 3 Trainingsart 4 -3.0667 0.0024 -5.2716 -0.8617 True ----------------------------------------------------------------------
Hinweis für Frage 01 und 02 Die Post-hoc Tests zeigen, dass sich alle drei Faktorstufen signifikant unterscheiden (fuer jede Kombination p adj < 0.05).
tukey = pairwise_tukeyhsd(endog=df["Ausdauertest"],
groups=df["Trainingsarten"], alpha=0.05)
# Ergebnisse als DataFrame extrahieren
tukey_df = pd.DataFrame(data=tukey._results_table.data[1:],
columns=tukey._results_table.data[0])
# Plot vorbereiten
fig, ax = plt.subplots(figsize=(8, 6))
for i, row in tukey_df.iterrows():
# Mittelwertsdifferenz und Konfidenzintervall
group_label = f"{row['group1']} – {row['group2']}"
lower = float(row['lower'])
upper = float(row['upper'])
mean_diff = float(row['meandiff'])
# Konfidenzintervall zeichnen
ax.plot([lower, upper], [i, i], color="red", linewidth=2)
# Mittelwertsdifferenz markieren
ax.plot(mean_diff, i, 'ro') # roter Punkt
# Achsen formatieren
ax.axvline(0, color='gray',
linestyle='--') # vertikale Linie bei 0
ax.set_yticks(range(len(tukey_df)))
ax.set_yticklabels([f"{row['group1']} – {row['group2']}" for _,
row in tukey_df.iterrows()])
ax.set_xlabel("Differenz der Mittelwerte")
ax.set_title("Tukey HSD – 95%-Konfidenzintervalle für Gruppenvergleiche",
fontsize=12)
plt.grid(axis='x', linestyle='--', alpha=0.4)
plt.tight_layout()
plt.show()
Die Konfidenzintervalle der drei Kombinationen unterscheiden sich und haben keine Ueberlappungen.
Hinweis für Frage 03
Es wird ersichtlich, dass sich die Trainingsmethoden 1 und 2 sowie 3 und 4 bezüglich der Ausdauertest signifikant unterscheiden. (p < .05). Es können also vier unabhängige/ generalisierbare Gruppen von Trainingsmethoden gebildet werden.
Hinweis: Sie sollten folgende Fragen beantworten:
- Welcher Vergleich wird signifikant und welcher nicht?
Es wird ersichtlich, dass sich die Trainingsmethoden 1 und 2 sowie 3 und 4 bezüglich der Ausdauertest signifikant unterscheiden (p < .05).
- Welche Gruppen sind unabhängig und welche nicht?
Es gibt also vier unabhängige Gruppen von Trainingsmethoden.
- Optional: Sind Gruppenbildungen möglich/ sinnvoll? - Wenn ja, welche?
Es werden vier Gruppen gebildet - kein Veränderung.
Der TukeyHSD ist für homogene Varianzen sehr gut geeignet, aber nicht für hetrogene Varianzen.
games_result = pg.pairwise_gameshowell(dv="Ausdauertest",
between="Trainingsarten", data=df)
# Ausgabe
print(games_result)
A B mean(A) mean(B) diff se \ 0 Trainingsart 1 Trainingsart 2 38.827586 48.166667 -9.339080 0.973306 1 Trainingsart 1 Trainingsart 3 38.827586 25.100000 13.727586 0.927675 2 Trainingsart 1 Trainingsart 4 38.827586 22.033333 16.794253 0.863681 3 Trainingsart 2 Trainingsart 3 48.166667 25.100000 23.066667 0.842046 4 Trainingsart 2 Trainingsart 4 48.166667 22.033333 26.133333 0.770977 5 Trainingsart 3 Trainingsart 4 25.100000 22.033333 3.066667 0.712505 T df pval hedges 0 -9.595220 55.258316 1.384670e-12 -2.471824 1 14.797845 52.451662 0.000000e+00 3.819823 2 19.444979 45.937352 0.000000e+00 5.036640 3 27.393581 57.144729 0.000000e+00 6.981135 4 33.896368 52.028241 0.000000e+00 8.638342 5 4.304066 55.186023 3.934332e-04 1.096872
Der Games-Howell ist ein Post-Hoc-Test, der optimiert für hetrogene Daten ist. Daher der Levene-Test eine Verletzung der Homogenotät nahliegt, sollte in Verfahren verwendet werden, welche dieses Verletzung bei dem multiple Tests berücksichtigt.
Wie der Tabelle zu entnehmen ist, gibt es keinen Unterschied- daher bestätigt der Games-Howell-Test das Ergebnis des TukeyHSDs.
Profildiagramm¶
Spannend ist auch sich die Mittelwerte hilfe dieses Plots anzeigen zu lassen.
# Mittelwert + Konfidenzintervall plotten
plt.figure(figsize=(6, 4))
sns.pointplot(
data=df,
x="Trainingsarten",
y="Ausdauertest",
ci=95, # 95%-Konfidenzintervall
capsize=0.2, # Breite der Fehlerbalken
errwidth=0.8, # Dicke der Balken
join=True, # Linien zwischen Punkten
markers='o',
dodge=False
)
# Achsentitel und Stil
plt.xlabel("Trainingsart")
plt.ylabel("Ausdauer")
sns.despine()
plt.grid(False)
plt.tight_layout()
plt.show()
Wie der Plot in Abbildung erkennen lassen, bestehen bezüglich der vier Trainingsmethoden unterschiede im Mittelwert.
Das partielle Eta-Quadrat¶
Das partielle Eta-Quadrat (partielles η²) ist ein Maß für die Effektstärke, das die durch einen Faktor erklärte Variation ins Verhältnis zurjenigen setzt, die nicht durch die übrigen Faktoren im Modell erklärt wird. Anders gesagt, es betrachtet ausschließlich den Anteil der Gesamtvariation, der nach Herausrechnung der Effekte aller anderen Faktoren übrig bleibt, und zeigt, welchen Anteil davon der betrachtete Faktor erklärt. Bei einer einfaktoriellen Varianzanalyse entspricht das partielle η² genau dem Anteil der korrigierten Gesamtvariation, den das Modell (bzw. der Faktor) erklärt.
$$\eta^2 =\frac{QS_{Zwischen}}{QS_{total}}$$ $$\eta^2_{par.} =\frac{QS_{Zwischen}}{QS_{zwischen}+QS_{innerhalb}}$$
# ANOVA + Effektstärke (eta-squared) berechnen
aov_result = pg.anova(dv="Ausdauertest",
between="Trainingsarten",
data=df, detailed=True)
# Ausgabe
print(aov_result[["Source", "DF", "F", "p-unc", "np2"]])
Source DF F p-unc np2 0 Trainingsarten 3 414.337682 1.838159e-61 0.915317 1 Within 115 NaN NaN NaN
Hinweis: Im vorliegenden Beispiel beträgt das partielle Eta-Quadrat .92. Das heisst, es wird 92% der Variation in Ausdauertest durch Trainingsarten aufgeklärt. Das partielle Eta² wird gerundet."90% CI" beschreibt das Konfidenzintervall für 90 %. Dieses liegt hier zwischen 89% und 93%.
Effektstärke¶
Aus dem partiellen Eta-Quadrat wird hier die Effektstärke nach Cohen (1988) berechnet. Dabei kann der Wert theoretisch von 0 bis ins Unendliche steigen.
Um die Bedeutsamkeit eines Ergebnisses zu beurteilen, werden Effektstärken berechnet.
$$f=\sqrt\frac{eta^{2}}{1-eta^{2}}$$
# Partielle Eta² aus ANOVA-Ergebnissen
eta_sq = aov_result["np2"].values[0]
# Effektstärke (Cohen's f) berechnen
cohen_f = np.sqrt(eta_sq / (1 - eta_sq))
# Ausgabe
print(f"Die Effektstärke liegt bei: {cohen_f:.2f}")
Die Effektstärke liegt bei: 3.29
Um zu beurteilen, wie gross dieser Effekt ist, kann man sich an der Einteilung von Cohen (1988) orientieren:
$$ \begin{align} \text{Schwacher Effekt: } 0.10 &< ||f|| < 0.25 \\ \text{Schwacher bis mittlerer Effekt: } 0.25 &= ||f|| \\ \text{Mittlerer Effekt: } 0.25 &< ||f|| < 0.40 \\ \text{Mittlerer bis starker Effekt: }0.40 &= ||f|| \\ \text{Starker Effekt: } 0.40 &< ||f|| \end{align} $$
Hinweis: Diese Beispiel ist sehr sauber und etwas "zu" eindeutig. Damit entspricht eine Effektstärke von 3.29 einem starken Effekt.
Eine Aussage¶
Die Auswahl der Trainingsmethode hat einen signifikanten Einfluss auf die Ausdauer (F(3,62.68) = 446.85 , p = .000). 92% der Streuung der Ausdauer-Werte um den Gesamtmittelwert kann durch die Trainingsmethoden erklärt werden. Die Effektstärke nach Cohen (1988) liegt bei f = 3.28 und entspricht einem starken Effekt. H0 wird abgelehnt, H1 angenommen.
Post-hoc-Tests mit Tukey zeigen, dass sich vier Gruppen von Trainingsarten bilden lassen (alle p < .05):
Trainingsart 1 (M = 38.82, SD = 3.99, n = 29), Trainingsart 2 (M = 48.16, SD = 3.45, n = 30) Trainingsart 3 (M = 25.10, SD = 3.05, n = 30) und Trainingsart 4 (M = 22.03, SD = 2.42, n = 30) bilden jede für sich eine eigene Gruppe.
Damit kann festgehalten, werden, dass alle vier Gruppen unabhängige Gruppen bilden und sich signifikant unterscheiden. Trainingsart 2 ist am effektivsten und die Trainingsart 4 am schlechtesten für die Ausdauer der Senioren.