Pakete einlesen¶
# Datenanalyse & Tabellenverarbeitung
import pandas as pd # Für den Umgang mit Datenrahmen (DataFrames)
import numpy as np # Für numerische Operationen, Arrays, mathematische Funktionen
# Visualisierung
import matplotlib.pyplot as plt # Zum Erstellen von Diagrammen
import seaborn as sns # Erweiterung für schönere statistische Visualisierungen
# Statistische Tests
from scipy.stats import wilcoxon # Wilcoxon-Test für verbundene Stichproben
from scipy.stats import norm # Zugriff auf Standardnormalverteilung (für z-Werte, p-Werte etc.)
Daten einlesen¶
# Daten einlesen
wilcoxen = pd.read_excel("wilcoxen.xlsx") # Pfad ggf. anpassen
Differenzspalte anlegen¶
wilcoxen["Differenz"] = wilcoxen["Vorher"] - wilcoxen["Nachher"]
print(wilcoxen.head(5))
ID Vorher Nachher Differenz 0 1 30 40 -10 1 2 31 30 1 2 3 32 33 -1 3 4 31 33 -2 4 5 29 33 -4
Überblick¶
print(wilcoxen.info()) # zeigt Spaltennamen, Nicht-Null-Werte, Datentypen
print(wilcoxen.sample(5)) # gibt 5 zufällige Zeilen aus dem DataFrame – für einen schnellen Datenüberblick
print(wilcoxen.columns) # listet alle Spaltennamen auf – gut zur Orientierung oder für Zugriff per Index
print(wilcoxen.isnull().sum()) # zählt pro Spalte, wie viele fehlende Werte (NaN) vorhanden sind
<class 'pandas.core.frame.DataFrame'> RangeIndex: 34 entries, 0 to 33 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID 34 non-null int64 1 Vorher 34 non-null int64 2 Nachher 34 non-null int64 3 Differenz 34 non-null int64 dtypes: int64(4) memory usage: 1.2 KB None ID Vorher Nachher Differenz 29 30 33 32 1 9 10 30 40 -10 28 29 31 29 2 31 32 24 38 -14 18 19 29 39 -10 Index(['ID', 'Vorher', 'Nachher', 'Differenz'], dtype='object') ID 0 Vorher 0 Nachher 0 Differenz 0 dtype: int64
Hypothese¶
H1: Es gibt einen Unterschied in den Verkaufszahlen für die Aktionfigur "Held2000"vor dem Rabatt und nach dem Rabatt. $M_{vorher} \ne M_{nacher}$
H01: Es gibt keinen Unterschied in den Verkaufszahlen für die Aktionfigur "Held2000" vor dem Rabatt und nach dem Rabatt. $M_{vorher} = M_{nachher}$
Voraussetzungen für den Wilcoxon-Test¶
Die abhängige Variable ist mindestens ordinalskaliert -> Die Variable ist eigentlich metrisch.
Es liegen zwei verbundene Stichproben oder Gruppen vor, aber die verschiedenen Messwertpaare sind voneinander unabhängig -
- Verbunden, weil wir den gleichen Store pro Zeile vergleichen
- z.B Store A und Store B sind unabhängig
Boxplots zur Darstellung der Werte¶
# Boxplots einzeln
plt.figure(figsize=(5, 4))
sns.boxplot(y=wilcoxen["Vorher"], color="hotpink")
plt.title("Boxplot vor dem Rabatt")
plt.ylabel("Vorher")
plt.grid(False)
plt.show()
plt.figure(figsize=(5, 4))
sns.boxplot(y=wilcoxen["Nachher"], color="deepskyblue")
plt.title("Boxplot nach dem Rabatt")
plt.ylabel("Nachher")
plt.grid(False)
plt.show()
# Boxplot - zusammen
plt.figure(figsize=(6, 4))
sns.boxplot(data=[wilcoxen["Vorher"], wilcoxen["Nachher"]], palette=["hotpink", "deepskyblue"])
plt.xticks([0, 1], ["(links) Vorher", "(rechts) Nachher"])
plt.ylabel("Anzahl der verkauften Actionfiguren")
plt.title("Boxplots zu den Verkaufszahlen")
plt.grid(False)
plt.tight_layout()
plt.show()
Vor dem Rabatt zeigt sich, dass drei Ausreißer auftraten (Median = 31). Jedoch da ein Wicoxen-Test für abhängige Stichproben durch geführt wird, können diese vernachlässigt werden.
Der Median der Variable „Nachher“ liegt bei 34. Auf den ersten Blick könnte der Boxplot nahelegen, dass ein t-Test für abhängige Stichproben geeignet wäre. Bei genauerer Betrachtung zeigt sich jedoch, dass dies keine sinnvolle Wahl ist: Die Varianzen unterscheiden sich deutlich, und es sind sowohl große als auch kleine Stores vertreten. Aus diesem Grund ist der Wilcoxon-Test die passendere Methode.
Berechnung der Mediane¶
# Median der Spalte "Vorher"
median_vorher = wilcoxen["Vorher"].median()
print(f"Median für Vor dem Rabatt: {median_vorher:.2f}")
# Median der Spalte "Nachher"
median_nachher = wilcoxen["Nachher"].median()
print(f"Median für Nach dem Rabatt: {median_nachher:.2f}")
# Anzahl der Datenzeilen insgesamt
anzahl_zeilen = len(wilcoxen)
print(f"Anzahl der Daten: {anzahl_zeilen}")
Median für Vor dem Rabatt: 31.00 Median für Nach dem Rabatt: 34.00 Anzahl der Daten: 34
ALTERNATIV¶
# Statistische Übersicht für "Vorher" und "Nachher"
mediane = wilcoxen[["Vorher", "Nachher"]].agg(["count","median", "min", "max"])
print("Überblick über zentrale Kennwerte:")
print(mediane)
Überblick über zentrale Kennwerte: Vorher Nachher count 34.0 34.0 median 31.0 34.0 min 17.0 23.0 max 39.0 44.0
Es gibt einen Unterschied in der zentralen Tendenz zwischen zwei Messzeitpunkten. Vorher dem Rabatt wurden 31 Artikel gekauft (n=34); nach dem Rabatt wurden 34 Artikel verkauft (n=34).
# Wilcoxon-Test für gepaarte Stichproben (zweiseitig, ohne exakte Berechnung)
stat, p_value = wilcoxon(wilcoxen["Vorher"], wilcoxen["Nachher"], alternative="two-sided", zero_method="wilcox")
# Ausgabe der Teststatistik
print(f"Wilcoxon-Test (gepaart, zweiseitig):")
print(f"W-Wert: {stat:.2f}")
print(f"p-Wert: {p_value:.4f}")
Wilcoxon-Test (gepaart, zweiseitig): W-Wert: 69.50 p-Wert: 0.0008
Ist dies nicht der Fall, so wird die exakte Signifikanz verwendet.¶
filtered = wilcoxen[wilcoxen["Vorher"] != wilcoxen["Nachher"]]
# Wilcoxon-Test mit gepaarten Daten (exakt möglich, da keine Null-Differenzen)
stat, p_value = wilcoxon(filtered["Vorher"], filtered["Nachher"], alternative="two-sided", zero_method="pratt", correction=False)
print("exakter Wilcoxon-Test:")
print(f"W-Wert: {stat:.2f}")
print(f"p-Wert: {p_value:.8f}")
exakter Wilcoxon-Test: W-Wert: 69.50 p-Wert: 0.00046011
Hinweis und Erklärung¶
In R kann beim Wilcoxon-Test über das Argument exact = TRUE festgelegt werden, dass der p-Wert exakt berechnet wird. Dies ist jedoch nur dann möglich, wenn keine Paare mit einer Differenz von null vorhanden sind. Ist das der Fall oder handelt es sich um größere Stichproben, verwendet R standardmäßig exact = FALSE und liefert eine asymptotische Näherung des p-Werts. In Python bietet die Funktion scipy.stats.wilcoxon keine direkte Option zur exakten Berechnung. Stattdessen basiert die p-Wert-Berechnung immer auf einer asymptotischen Approximation, was funktional dem Verhalten in R mit exact = FALSE entspricht. Möchte man in Python möglichst nah an die exakte Berechnung herankommen, sollte man alle Null-Differenzen vorab entfernen und den Parameter zero_method="wilcox" setzen. Eine tatsächlich exakte Berechnung wie in R ist in Python nur über externe Schnittstellen wie rpy2 möglich, die es erlauben, R-Funktionen direkt in Python zu verwenden. ** Ist hier nicht umgesetzt*
Die Teststatistik beträgt V = 69.5 und der zugehörige Signifikanzwert p = .000. Damit ist der Unterschied signifikant: Die zentralen Tendenzen der beiden Messzeitpunkte unterscheiden sich (Wilcoxon-Test: V = 69.5, Z = -3.20, p = .000, n = 34).
Berechnung der Effektstärke¶
$$r=\left| \frac{z}{\sqrt{n}} \right|$$
Der z - Wert¶
# Anzahl gültiger Paare (ohne Null-Differenz)
nk = np.sum(wilcoxen["Vorher"] != wilcoxen["Nachher"])
# Wilcoxon-Test ohne Null-Differenzen
filtered = wilcoxen[wilcoxen["Vorher"] != wilcoxen["Nachher"]]
stat, p_val = wilcoxon(filtered["Vorher"], filtered["Nachher"], alternative="two-sided", zero_method="wilcox")
# z-Wert (asymptotisch, wie in R mit qnorm)
mu_w = nk * (nk + 1) / 4
sigma_w = np.sqrt(nk * (nk + 1) * (2 * nk + 1) / 24)
z_stat = (stat - mu_w) / sigma_w
print(f"Z-Wert für den WSR: {z_stat:.2f}")
Z-Wert für den WSR: -3.35
Anzahl der Daten aus dem Datensatz¶
print(f"Anzahl ohne Null: {nk:.0f}")
Anzahl ohne Null: 30
Die Anzahl kann auch unter Datenmartix ausgelesen werden. In dem vorliegenden Beispiel sind es 30 Datensätze.
eff = round(abs(z_stat) / np.sqrt(nk), 1)
print(f"Effektstärke: {eff:.1f}")
Effektstärke: 0.6
Zur Beurteilung der Grösse des Effektes dient die Einteilung von Cohen (1988):
$$ \begin{align} \text{Schwacher Effekt: } 0.10 &< ||r|| < 0.25 \\ \text{Schwacher bis mittlerer Effekt: } 0.25 &= ||r|| \\ \text{Mittlerer Effekt: } 0.25 &< ||r|| < 0.40 \\ \text{Mittlerer bis starker Effekt: }0.40 &= ||r|| \\ \text{Starker Effekt: } 0.40 &< ||r|| \end{align} $$
Damit entspricht die Effektstärke von .6 einem starken Effekt.
Eine Aussage¶
Die Verkaufszahlen der Aktionfigur “Held2000” sind nach der Gewährung eines Rabattes signifikant höher (Median = 34.00) als davor (Median = 31.00; Wilcoxon-Test: z-Wert: -3.2036, p = .000, n = 34). Die Effektstärke nach Cohen (1988) liegt bei r = .6 und entspricht einem starken Effekt. H0 kann verworfen werden.