Statystyki opisowe i testowanie normalności
Oblicz skośność i kurtozę za pomocą scipy.stats, wykonaj test Shapiro–Wilka normalności i zinterpretuj wartość p.
Statystyki opisowe i testowanie normalności to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Pandas & NumPy Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Statystyka opisowa a inferencyjna
Statystyka opisowa podsumowuje to, co znajduje się w danych: średnią, medianę, odchylenie standardowe i skośność. Statystyka inferencyjna formułuje twierdzenia o populacji na podstawie próbki: testy hipotez, przedziały ufności i wartości p. Moduł SciPy scipy.stats łączy te obszary — udostępnia zarówno miary opisowe wykraczające poza describe() biblioteki Pandas, jak i pełny zestaw klasycznych testów hipotez. Połączenie Pandas do manipulowania danymi i SciPy do testowania statystycznego jest standardowym sposobem pracy w Pythonie w dziedzinie data science.
Rozszerzone statystyki opisowe
describe() biblioteki Pandas zwraca liczbę obserwacji, średnią, odchylenie standardowe, minimum/maksimum i kwartyle. scipy.stats dodaje skośność (asymetrię rozkładu) oraz kurtozę (grubość ogonów). Skośność równa 0 oznacza symetrię, a dodatnia skośność — dłuższy prawy ogon (wiele małych wartości i kilka bardzo dużych). Kurtoza równa 3 (lub 0 w wersji nadmiarowej) jest typowa dla rozkładu normalnego; wyższe wartości wskazują na grubsze ogony i więcej wartości odstających niż w rozkładzie normalnym.
import pandas as pd
from scipy import stats
import numpy as np
np.random.seed(0)
data = np.concatenate([
np.random.exponential(scale=2, size=500), # right-skewed
np.random.normal(loc=5, scale=1, size=500)
])
print('Mean:', round(data.mean(), 3))
print('Std:', round(data.std(), 3))
print('Skewness:', round(stats.skew(data), 3))
print('Kurtosis (excess):', round(stats.kurtosis(data), 3))Zrozumienie skośności
Skośność ma znaczenie przy wyborze testów statystycznych i transformacji. Rozkład prawoskośny (o dodatniej skośności) ma średnią większą od mediany; jest typowy dla danych o dochodach, czasach reakcji i kwotach sprzedaży. Rozkład lewoskośny (o ujemnej skośności) ma średnią mniejszą od mediany. Praktyczna zasada: |skewness| < 0.5 oznacza w przybliżeniu rozkład symetryczny, 0.5–1.0 — umiarkowaną skośność, a > 1.0 — dużą skośność, która może uzasadniać zastosowanie transformacji logarytmicznej lub pierwiastkowej przed użyciem testów zakładających normalność.
import numpy as np
from scipy import stats
# Right-skewed: income-like data
right_skewed = np.random.lognormal(mean=1, sigma=1, size=1000)
print('Right skew:', round(stats.skew(right_skewed), 3))
# After log transform
print('After log transform:', round(stats.skew(np.log(right_skewed)), 3))
# Symmetric normal
normal_data = np.random.normal(0, 1, 1000)
print('Normal skew:', round(stats.skew(normal_data), 3))Dlaczego testowanie normalności ma znaczenie
Wiele testów statystycznych — testy t, ANOVA i korelacja Pearsona — zakłada, że dane (lub reszty) mają rozkład normalny (gaussowski). Jeśli przy małych próbkach założenie to nie jest spełnione, wartości p testu mogą być niedokładne. Testowanie normalności sprawdza, czy założenie jest spełnione. W przypadku dużych próbek (n > 100) testy normalności stają się bardzo czułe i niemal zawsze odrzucają normalność z powodu nieistotnych odchyleń — w takiej sytuacji należy polegać na centralnym twierdzeniu granicznym (średnie z próbek są w przybliżeniu normalne), zamiast testować surowe dane.
Test Shapiro-Wilka
Test Shapiro-Wilka (scipy.stats.shapiro(data)) jest najskuteczniejszym testem normalności dla prób o liczebności do około 5000 obserwacji. Zwraca statystykę W oraz wartość p. Jeśli p > 0.05, nie ma podstaw do odrzucenia normalności — dane są zgodne z rozkładem normalnym. Jeśli p ≤ 0.05, odrzucamy normalność. Należy pamiętać, że brak podstaw do odrzucenia normalności nie dowodzi, że dane mają rozkład normalny; oznacza jedynie, że nie ma wystarczających dowodów, aby stwierdzić coś przeciwnego.
import numpy as np
from scipy import stats
np.random.seed(42)
# Normal data
normal = np.random.normal(0, 1, 100)
stat, p = stats.shapiro(normal)
print(f'Normal data: W={stat:.4f}, p={p:.4f}')
print('Conclusion:', 'Looks normal' if p > 0.05 else 'Not normal')
# Skewed data
skewed = np.random.exponential(1, 100)
stat2, p2 = stats.shapiro(skewed)
print(f'Skewed data: W={stat2:.4f}, p={p2:.4f}')
print('Conclusion:', 'Looks normal' if p2 > 0.05 else 'Not normal')Test Kołmogorowa-Smirnowa
Test Kołmogorowa-Smirnowa (K-S) (scipy.stats.kstest(data, 'norm', args)) sprawdza, czy próbka pochodzi z określonego rozkładu. W przeciwieństwie do testu Shapiro-Wilka działa dla dowolnego rozkładu (nie tylko normalnego) oraz dla dużych próbek. Oblicza maksymalną różnicę między empiryczną dystrybuantą danych a dystrybuantą teoretyczną. Odmiana tego testu, dwupróbkowy test K-S (stats.ks_2samp(a, b)), sprawdza, czy dwie próbki pochodzą z tego samego rozkładu — jest przydatna przy porównywaniu rozkładów przed i po zmianie.
import numpy as np
from scipy import stats
np.random.seed(0)
data = np.random.normal(loc=5, scale=2, size=200)
# One-sample K-S test against N(5, 2) distribution
stat, p = stats.kstest(data, 'norm', args=(5, 2))
print(f'K-S test: stat={stat:.4f}, p={p:.4f}')
print('Follows N(5,2)?', 'Yes' if p > 0.05 else 'No')
# Two-sample K-S test
data2 = np.random.normal(loc=5.5, scale=2, size=200)
stat2, p2 = stats.ks_2samp(data, data2)
print(f'Two-sample K-S: stat={stat2:.4f}, p={p2:.4f}')Wizualna kontrola normalności: wykres Q-Q
Wykres Q-Q (kwantyl-kwantyl) jest wizualną kontrolą normalności: przedstawia kwantyle danych względem kwantyli rozkładu normalnego. Jeśli dane mają rozkład normalny, punkty układają się na prostej przekątnej. Odchylenia od tej prostej wskazują na odejście od normalności — krzywe w kształcie litery S wskazują na kurtozę, a wygięcia na skośność. Testy statystyczne informują, czy odchylenia są istotne, natomiast wykresy Q-Q pokazują ich charakter i położenie. Do wygenerowania danych wykresu Q-Q użyj scipy.stats.probplot().
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
np.random.seed(1)
data = np.random.exponential(2, 200)
# Q-Q plot
(osm, osr), (slope, intercept, r) = stats.probplot(data, dist='norm')
print(f'R-squared of Q-Q fit: {r**2:.4f}') # Close to 1 = normal
# Visual inspection: if plotting, points on the line = normal
# plt.figure()
# stats.probplot(data, dist='norm', plot=plt)
# plt.show()Centralne twierdzenie graniczne w praktyce
Centralne twierdzenie graniczne (CLT) mówi, że rozkład średnich z próbek zbliża się do rozkładu normalnego wraz ze wzrostem liczebności próbki, niezależnie od rozkładu bazowego. Dla n ≥ 30 średnia z próbki jest w przybliżeniu normalna, nawet jeśli pojedyncze obserwacje są skośne. Dlatego testy t są odporne dla dużych próbek: sprawdzają, czy średnia się różni, a średnia jest w przybliżeniu normalna, nawet gdy surowe dane takie nie są. W przypadku małych próbek z populacji o rozkładzie nienormalnym należy użyć testów nieparametrycznych.
import numpy as np
from scipy import stats
np.random.seed(0)
# Population: heavily right-skewed (exponential)
population = np.random.exponential(scale=1, size=10000)
print('Population skewness:', round(stats.skew(population), 3))
# Sample means are approximately normal (CLT)
sample_means = [np.random.choice(population, 50).mean()
for _ in range(1000)]
print('Sample means skewness:', round(stats.skew(sample_means), 3))
_, p = stats.shapiro(sample_means)
print('Shapiro p-value for means:', round(p, 4))Normalność w podziale na grupy
W testach porównywania grup (test t, ANOVA) normalność jest wymagana w każdej grupie, a nie w całym zbiorze danych. Badając, czy sprzedaż różni się w zależności od regionu, należy osobno sprawdzić normalność sprzedaży w każdym regionie. Rozkład, który ogólnie nie jest normalny, może nadal spełniać warunek normalności w podgrupach. Zastosuj stats.shapiro() do każdej grupy, używając groupby() biblioteki Pandas, i iteruj po grupach, aby systematycznie sprawdzić to założenie.
import pandas as pd
import numpy as np
from scipy import stats
np.random.seed(0)
df = pd.DataFrame({
'region': ['N']*50 + ['S']*50 + ['E']*50,
'sales': np.concatenate([
np.random.normal(100, 20, 50),
np.random.normal(110, 25, 50),
np.random.normal(95, 15, 50)
])
})
for region, group in df.groupby('region'):
stat, p = stats.shapiro(group['sales'])
print(f'Region {region}: W={stat:.4f}, p={p:.4f} -> '
f'{"Normal" if p>0.05 else "Not normal"}')Alternatywy nieparametryczne
Gdy normalność nie jest spełniona, należy użyć testów nieparametrycznych, które nie wymagają żadnych założeń dotyczących rozkładu. Test U Manna-Whitneya (stats.mannwhitneyu) zastępuje test t dla prób niezależnych, test rang podpisanych Wilcoxona (stats.wilcoxon) zastępuje test t dla prób zależnych, a test Kruskala-Wallisa (stats.kruskal) zastępuje jednoczynnikową analizę ANOVA. Testy te wykorzystują rangi zamiast surowych wartości i są odporne na wartości odstające, ale mają mniejszą moc statystyczną niż ich parametryczne odpowiedniki, gdy normalność faktycznie jest spełniona.
import numpy as np
from scipy import stats
np.random.seed(0)
# Non-normal data
group_a = np.random.exponential(2, 40)
group_b = np.random.exponential(2.5, 40)
# Parametric would be wrong here; use non-parametric
stat, p = stats.mannwhitneyu(group_a, group_b, alternative='two-sided')
print(f'Mann-Whitney U test: U={stat:.1f}, p={p:.4f}')
print('Groups differ?' , 'Yes' if p < 0.05 else 'No')Podsumowywanie statystyk dla wielu kolumn
W EDA często trzeba jednocześnie sprawdzić normalność i skośność wszystkich kolumn liczbowych. Połącz select_dtypes biblioteki Pandas z pętlą po kolumnach, wywołującą stats.shapiro() i stats.skew(). Zbuduj podsumowującą ramkę danych z kolumnami zawierającymi skośność, kurtozę i wartość p testu Shapiro-Wilka, aby uzyskać ogólny obraz tego, które kolumny nie mają rozkładu normalnego i wymagają transformacji przed modelowaniem. Jest to część systematycznej listy kontrolnej jakości danych.
import pandas as pd
import numpy as np
from scipy import stats
np.random.seed(0)
df = pd.DataFrame({
'age': np.random.normal(35, 10, 200),
'income': np.random.lognormal(10, 1, 200),
'score': np.random.uniform(0, 100, 200)
})
rows = []
for col in df.select_dtypes(include='number').columns:
s = stats.skew(df[col])
_, p = stats.shapiro(df[col][:200])
rows.append({'column': col, 'skewness': round(s, 3),
'shapiro_p': round(p, 4), 'normal': p > 0.05})
print(pd.DataFrame(rows).to_string(index=False))Szybki sprawdzian
Sprawdź swoją znajomość pojęć związanych z analizą danych z tej lekcji.
Podsumowanie lekcji
W tej lekcji dowiedziałeś się, że: scipy.stats.skew() i kurtosis() opisują kształt rozkładu dokładniej niż funkcja describe(), scipy.stats.shapiro() testuje normalność, a p > 0.05 oznacza brak dowodów przeciwko normalności, natomiast centralne twierdzenie graniczne sprawia, że testy t są odporne dla dużych próbek, nawet gdy dane nie mają rozkładu normalnego. W następnej części zastosujemy testowanie hipotez za pomocą testów t do porównywania średnich grup.
Ucz się Python dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 30
- Lekcje
- 120
Często zadawane pytania
Czy lekcja „Statystyki opisowe i testowanie normalności” jest bezpłatna?
Tak — pełny tekst „Statystyki opisowe i testowanie normalności” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Pandas & NumPy Academy, przejdź na CoddyKit PRO. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Statystyki opisowe i testowanie normalności”?
Oblicz skośność i kurtozę za pomocą scipy.stats, wykonaj test Shapiro–Wilka normalności i zinterpretuj wartość p. Ćwiczysz Pandas & NumPy Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Pandas & NumPy Academy?
Nie wymagamy żadnego doświadczenia. Pandas & NumPy Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Statystyki opisowe i testowanie normalności”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Pandas & NumPy Academy?
Tak. Każda lekcja Pandas & NumPy Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Statystyki opisowe i testowanie normalności
- Testy t do porównywania średnich
- Test chi-kwadrat niezależności
- ANOVA i testy post-hoc