Mathematik für Biologiestudierende¶

Wintersemester 2025/26

10.12.2025

© 2025 Prof. Dr. Rüdiger W. Braun

Wiederholung (interaktiv)¶

Gehen Sie auf die Website

  • https://pingo.coactum.de

und geben Sie folgende Zugangsnummer ein

  • 670719

oder scannen Sie den QR-Code

QR-Code

Themen heute¶

  • Wiederholung t-Test
  • p-Wert
  • Z-Test
In [1]:
import numpy as np
np.set_printoptions(legacy='1.21')
import seaborn as sns
sns.set_theme()
sns.set_context('talk')
import pandas as pd
from scipy import stats

Anatomie eines Tests¶

(gilt nicht für den Binomialtest)

Teststatistik¶

  • berechnet sich aus den Daten
  • führt zu einer Zahl, gennant Teststatistik, meist abgekürzt mit $t$
  • $t=0$ bedeutet: überhaupt kein Unterschied zwischen Daten und Nullhypothese

Entscheidungsregel¶

  • benötigt wird ein Quantil
  • zu welcher Verteilung das Quantil bestimmt werden muss, hängt vom Test ab
  • zweiseitiger Test: Die Nullhypothese wird abgelehnt, wenn $|t|$ größer als das Quantil ist
  • einseitiger oberer Test: Die Nullhypothese wird abgelehnt, wenn $t$ größer als das Quantil ist
  • einseitiger unterer Test: Die Nullhypothese wird abgelehnt, wenn $-t$ größer als das Quantil ist

Zusammenfassung: Die Nullhypothese wird abgelehnt, wenn die Teststatistik weit genug weg von der Null ist, wobei bei den einseitigen darauf geachtet werden muss, dass sie "in die richtige Richtung zeigt"

$t$-Tests für Erwartungswerte¶

Wiederholung aus der vorigen Stunde

$t$-Test für unverbundene Stichproben¶

  • $x_j$ und $y_j$ seien Realisierungen, also die Daten
  • Bestimme arithmetische Mittelwerte $$ \overline x = \frac1{n_1} \sum_{j=1}^{n_1} x_j \text{ und } \overline y = \frac1{n_2} \sum_{j=1}^{n_2} y_j $$
  • die Stichprobenstreuungen

$$ s_x = \sqrt{ \frac1{n_1-1} \sum_{j=1}^{n_1} (x_j - \overline x)^2 } \text{ und } s_y = \sqrt{ \frac1{n_2-1} \sum_{j=1}^{n_2} (y_j - \overline y)^2 } $$

  • Bestimme die Standardabweichung der gepoolten Stichproben

$$ s_p = \sqrt{ \frac{(n_1-1) \cdot s_x^2 + (n_2 - 1) \cdot s_y^2}{n_1 + n_2 - 2} } $$

  • Die Teststatistik ist

$$ t = \frac{\overline x - \overline y}{s_p} \sqrt{\frac{n_1 \cdot n_2}{n_1 + n_2}} $$

Beispiel: Pinguine¶

In [2]:
pingus = sns.load_dataset("penguins") 
pingus
Out[2]:
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 Male
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 Female
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 Female
3 Adelie Torgersen NaN NaN NaN NaN NaN
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 Female
... ... ... ... ... ... ... ...
339 Gentoo Biscoe NaN NaN NaN NaN NaN
340 Gentoo Biscoe 46.8 14.3 215.0 4850.0 Female
341 Gentoo Biscoe 50.4 15.7 222.0 5750.0 Male
342 Gentoo Biscoe 45.2 14.8 212.0 5200.0 Female
343 Gentoo Biscoe 49.9 16.1 213.0 5400.0 Male

344 rows × 7 columns

Wir möchten zum Signifikanznieveau $\alpha = 0.05$ die folgende Frage beantworten:

Unterscheiden sich die Flügellängen der Adelie-Pinguine je nach Geschlecht?

  • Pinguine entweder männlich oder weiblich: unverbundene Stichprobe
  • Frage nach Unterschied: zweiseitiger Test
In [3]:
adelie = pingus[pingus.species=="Adelie"]
adelie.head()
Out[3]:
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 Male
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 Female
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 Female
3 Adelie Torgersen NaN NaN NaN NaN NaN
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 Female
In [4]:
adelie.sex.value_counts()
Out[4]:
sex
Male      73
Female    73
Name: count, dtype: int64
In [5]:
am = adelie[adelie.sex=="Male"]
af = adelie[adelie.sex=="Female"]
In [6]:
am.describe()
Out[6]:
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
count 73.000000 73.000000 73.000000 73.000000
mean 40.390411 19.072603 192.410959 4043.493151
std 2.277131 1.018886 6.599317 346.811553
min 34.600000 17.000000 178.000000 3325.000000
25% 39.000000 18.500000 189.000000 3800.000000
50% 40.600000 18.900000 193.000000 4000.000000
75% 41.500000 19.600000 197.000000 4300.000000
max 46.000000 21.500000 210.000000 4775.000000
In [7]:
af.describe()
Out[7]:
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
count 73.000000 73.000000 73.000000 73.000000
mean 37.257534 17.621918 187.794521 3368.835616
std 2.028883 0.942993 5.595035 269.380102
min 32.100000 15.500000 172.000000 2850.000000
25% 35.900000 17.000000 185.000000 3175.000000
50% 37.000000 17.600000 188.000000 3400.000000
75% 38.800000 18.300000 191.000000 3550.000000
max 42.200000 20.700000 202.000000 3900.000000

Berechnung der Standardabweichung der gepoolten Stichproben

$n_1-1 = 72$ und $n_2-1=72$

In [8]:
zaehler = 72*6.599**2 + 72*5.595**2
nenner = 72 + 72
sp = np.sqrt(zaehler/nenner)
sp
Out[8]:
6.117631322660757
In [9]:
faktor = (73*73) / (73+73)
faktor
Out[9]:
36.5

Teststatistik

In [10]:
t = (192.4-187.8) / sp * np.sqrt(faktor)
t
Out[10]:
4.542772238713508

Signifikanzniveau $\alpha=0.05$

Dann $1-\frac\alpha2 = 0.975$

In [11]:
P = stats.t(72+72)
P.ppf(0.975)
Out[11]:
1.9765750658185364

Die Nullhypothese wird abgelehnt: Männliche Adelie-Pinguine haben längere Flügel

Dasselbe mit stats¶

In [12]:
stats.ttest_ind(am.flipper_length_mm, af.flipper_length_mm)
Out[12]:
TtestResult(statistic=4.5588666963515765, pvalue=1.08977531716496e-05, df=144.0)

Die Werte der Teststatistik unterscheiden sich etwas.

Woran liegt das?

Rundungsfehler

Hier zur Sicherheit die präzisen Zahlen:

In [13]:
z2 = 72*af.flipper_length_mm.var() + 72*am.flipper_length_mm.var()
sp2 = np.sqrt(z2/nenner)
sp2
Out[13]:
6.117818375391429
In [14]:
t = (am.flipper_length_mm.mean() - af.flipper_length_mm.mean()) / sp2 * np.sqrt(faktor)
t
Out[14]:
4.5588666963515765

Der p-Wert¶

  • Der p-Wert ist das kleinste Signifikanzniveau, zu dem die Nullhypothese noch abgelehnt werden kann
  • Um ihn zu bestimmen, benötigen wir die Verteilungsfunktion der Statistik
  • wir bezeichnen sie mal kurz mit $F$
  • in scipy erhalten wir sie durch P.cdf()
  • eim einseitigen oberen Test ist der p-Wert gleich $1 - F(t)$
  • beim einseitigen unteren Test ist der p-Wert gleich $1 - F(-t)$
  • beim zweiseitigen Test ist der p-Wert gleich $2(1 - F(|t|)$

Zurück zum Pinguin-Beispiel

In [15]:
2*(1 - P.cdf(abs(t)))
Out[15]:
1.0897753171645874e-05

zum Vergleich

In [16]:
stats.ttest_ind(am.flipper_length_mm, af.flipper_length_mm).pvalue
Out[16]:
1.08977531716496e-05

Beispiel: Schadstoffkonzentration¶

In der letzten Stunde hatten wir den p-Wert mit scipy.stats bestimmt. Jetzt machen wir das zu Fuß.

In [17]:
u = "https://www.math.uni-duesseldorf.de/~braun/bio2324/data/schadstoffe.csv"
schadstoffe = pd.read_csv(u, index_col=0)
schadstoffe.head()
Out[17]:
Messstelle Konzentration
0 5 0.000867
1 3 0.000490
2 1 0.000589
3 1 0.000950
4 4 0.001152
In [18]:
schadstoffe.describe()
Out[18]:
Messstelle Konzentration
count 80.000000 80.000000
mean 2.987500 0.000905
std 1.409675 0.000341
min 1.000000 0.000061
25% 2.000000 0.000701
50% 3.000000 0.000938
75% 4.000000 0.001158
max 5.000000 0.001605
In [19]:
t = (0.000905 - 0.0008) / 0.000341 * np.sqrt(80)
t
Out[19]:
2.7541013212607366
  • einseitig oberer Test
  • 79 Freiheitsgrade
In [20]:
P = stats.t(79)
In [21]:
1 - P.cdf(t)
Out[21]:
0.003650577575303182

Zum Vergleich:

In [22]:
res = stats.ttest_rel(schadstoffe.Konzentration, 0.0008, alternative="greater")
res.pvalue
Out[22]:
0.0035114445640696246
In [23]:
res.statistic
Out[23]:
2.768040010585661

Unterschiede beruhen auf Rundungsfehlern