0Pricing
Pandas & NumPy Academy · 课时

描述性统计与正态性检验

使用 scipy.stats 计算偏度和峰度,运行 Shapiro-Wilk 正态性检验,并解读 p 值

描述性统计与正态性检验 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Descriptive vs Inferential Statistics

Descriptive statistics summarise what is in your data: mean, median, standard deviation, skewness. Inferential statistics make claims about a population based on a sample: hypothesis tests, confidence intervals, p-values. SciPy's scipy.stats module bridges these worlds — it provides both descriptive measures that go beyond Pandas' describe() and the full suite of classical hypothesis tests. Combining Pandas for data manipulation and SciPy for statistical testing is the standard Python data science workflow.

Extended Descriptive Statistics

Pandas describe() gives count, mean, std, min/max, and quartiles. scipy.stats adds skewness (asymmetry of the distribution) and kurtosis (tail heaviness). Skewness of 0 means symmetric; positive skewness means a longer right tail (many small values, few very large). Kurtosis of 3 (or 0 in excess form) is normal; higher values indicate heavier tails with more extreme outliers than a normal distribution would produce.

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))

Understanding Skewness

Skewness matters for choosing statistical tests and transformations. A right-skewed (positive skew) distribution has its mean larger than its median, typical of income data, reaction times, and sales amounts. A left-skewed (negative skew) distribution has its mean smaller than its median. Rule of thumb: |skewness| < 0.5 is approximately symmetric; 0.5–1.0 is moderately skewed; > 1.0 is highly skewed and may benefit from a log or square-root transformation before applying tests that assume normality.

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))

Why Normality Testing Matters

Many statistical tests — t-tests, ANOVA, Pearson correlation — assume that the data (or the residuals) follow a normal (Gaussian) distribution. If this assumption is violated with small samples, the test's p-values may be inaccurate. Normality testing checks whether the assumption holds. For large samples (n > 100), normality tests become very sensitive and will almost always reject normality for trivial deviations — in that case, rely on the Central Limit Theorem (sample means are approximately normal) rather than testing the raw data.

The Shapiro-Wilk Test

The Shapiro-Wilk test (scipy.stats.shapiro(data)) is the most powerful normality test for sample sizes up to about 5,000. It returns a W-statistic and a p-value. If p > 0.05, you fail to reject normality — the data is consistent with a normal distribution. If p ≤ 0.05, you reject normality. Remember: failing to reject normality does not prove the data is normal; it just means you do not have enough evidence to say it is not.

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')

The Kolmogorov-Smirnov Test

The Kolmogorov-Smirnov (K-S) test (scipy.stats.kstest(data, 'norm', args)) tests whether a sample follows a specified distribution. Unlike Shapiro-Wilk, it works for any distribution (not just normal) and for large samples. It computes the maximum difference between the empirical CDF of your data and the theoretical CDF. A variation, the two-sample K-S test (stats.ks_2samp(a, b)), tests whether two samples come from the same distribution — useful for comparing before/after distributions.

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}')

Visual Normality Check: QQ Plot

The Q-Q (quantile-quantile) plot is a visual normality check: it plots the quantiles of your data against the quantiles of a normal distribution. If the data is normal, the points fall on a straight diagonal line. Deviations from the line indicate departure from normality — S-curves indicate kurtosis, bends indicate skewness. Statistical tests tell you if deviations are significant; Q-Q plots show you the nature and location of deviations. Use scipy.stats.probplot() to generate Q-Q plot data.

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()

The Central Limit Theorem in Practice

The Central Limit Theorem (CLT) states that the distribution of sample means approaches normality as sample size grows, regardless of the underlying distribution. For n ≥ 30, the sample mean is approximately normal even if individual observations are skewed. This is why t-tests are robust for large samples: you are testing whether the mean differs, and the mean is approximately normal even when the raw data is not. For small samples from non-normal populations, use non-parametric tests instead.

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))

Normality by Group

In group comparison tests (t-test, ANOVA), normality is required within each group, not in the overall data. When testing whether sales differ by region, test the normality of sales within each region separately. A distribution that is not normal overall can still satisfy normality within subgroups. Apply stats.shapiro() to each group using Pandas groupby() and iterate over groups to check the assumption systematically.

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"}')

Non-Parametric Alternatives

When normality is violated, use non-parametric tests that make no distributional assumptions. The Mann-Whitney U test (stats.mannwhitneyu) replaces the independent t-test; the Wilcoxon signed-rank test (stats.wilcoxon) replaces the paired t-test; the Kruskal-Wallis test (stats.kruskal) replaces one-way ANOVA. These tests use ranks instead of raw values and are robust to outliers, but they have less statistical power than their parametric counterparts when normality actually holds.

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')

Summarising Stats for Multiple Columns

In EDA, you often need to check normality and skewness for all numeric columns at once. Combine Pandas select_dtypes with a loop over columns calling stats.shapiro() and stats.skew(). Build a summary DataFrame with columns for skewness, kurtosis, and Shapiro p-value to get a birds-eye view of which columns are non-normal and need transformation before modelling. This is part of a systematic data quality checklist.

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))

Quick Check

Test your understanding of Data Analysis concepts from this lesson.

Lesson Recap

In this lesson you learned: scipy.stats.skew() and kurtosis() describe the shape of a distribution beyond what describe() provides, scipy.stats.shapiro() tests normality with p > 0.05 meaning no evidence against normality, and the Central Limit Theorem makes t-tests robust for large samples even with non-normal data. Next up we apply hypothesis testing with t-tests to compare group means.

常见问题解答

「描述性统计与正态性检验」课时是免费的吗?

是的 — 「描述性统计与正态性检验」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。

「描述性统计与正态性检验」这节课中我会学到什么?

使用 scipy.stats 计算偏度和峰度,运行 Shapiro-Wilk 正态性检验,并解读 p 值 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Pandas & NumPy Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「描述性统计与正态性检验」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?

能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 描述性统计与正态性检验
  2. 用于比较均值的 t 检验
  3. 独立性的卡方检验
  4. ANOVA 与事后检验
← 返回 Pandas & NumPy Academy