11.5 案例分析(入门实战)¶

本章用两个小案例,贯通数据准备 → 相关性 → 回归建模 → 预测与解读的完整流程:

  • 案例一:学习时长与成绩
  • 案例二:城市居民生活满意度

学习目标¶

  • 会将现实问题转化为变量并组织成 DataFrame。
  • 用 corr() 计算相关矩阵,理解正/负相关与强弱。
  • 用 LinearRegression(或 numpy 兜底)做线性回归并输出斜率、截距、R²。
  • 能给出自然语言解读与局限性,避免“相关即因果”的误区。

语法要点(本章会用到)¶

  • pd.DataFrame({...}):组装表格数据。
  • df.corr():皮尔逊相关(默认)。
  • groupby().agg(...):分组聚合(案例二使用)。
  • 回归(优先):
    from sklearn.linear_model import LinearRegression
    model = LinearRegression().fit(X, y)  # X 二维
    model.coef_, model.intercept_, model.score(X, y)  # 斜率/截距/R²
    
  • 回归(兜底):numpy.linalg.lstsq 最小二乘。
  • 可视化:matplotlib.pyplot 中的 scatter/plot。

11.5.1 案例一:学习时长与成绩相关性分析¶

问题:自习时间是否与考试成绩相关?能否用来预测?

(1)数据准备¶

In [1]:
import pandas as pd
import numpy as np

data = {
    '姓名': ['学生A','学生B','学生C','学生D','学生E','学生F','学生G','学生H','学生I','学生J'],
    '每周自习时间': [5, 7, 4, 9, 6, 10, 8, 3, 7, 6],
    '考试成绩': [75, 82, 70, 90, 78, 92, 88, 68, 81, 79]
}
df = pd.DataFrame(data)
df
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\computation\expressions.py:21: UserWarning: Pandas requires version '2.8.4' or newer of 'numexpr' (version '2.8.3' currently installed).
  from pandas.core.computation.check import NUMEXPR_INSTALLED
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\arrays\masked.py:60: UserWarning: Pandas requires version '1.3.6' or newer of 'bottleneck' (version '1.3.5' currently installed).
  from pandas.core import (
Out[1]:
姓名 每周自习时间 考试成绩
0 学生A 5 75
1 学生B 7 82
2 学生C 4 70
3 学生D 9 90
4 学生E 6 78
5 学生F 10 92
6 学生G 8 88
7 学生H 3 68
8 学生I 7 81
9 学生J 6 79

(2)相关性分析¶

In [2]:
# 计算皮尔逊相关系数矩阵
corr_mat = df[['每周自习时间', '考试成绩']].corr()
corr_mat
Out[2]:
每周自习时间 考试成绩
每周自习时间 1.000000 0.991594
考试成绩 0.991594 1.000000

(3)线性回归分析¶

In [3]:
# 线性回归建模(优先 sklearn,若不可用则 numpy 兜底)
X = df[['每周自习时间']].to_numpy(dtype=float)
y = df['考试成绩'].to_numpy(dtype=float)

try:
    from sklearn.linear_model import LinearRegression
    model = LinearRegression().fit(X, y)
    slope = float(model.coef_[0])
    intercept = float(model.intercept_)
    r2 = float(model.score(X, y))
except Exception as e:
    Xd = np.column_stack([np.ones(len(X)), X.flatten()])  # [1, x]
    beta, *_ = np.linalg.lstsq(Xd, y, rcond=None)
    intercept = float(beta[0]); slope = float(beta[1])
    y_hat = Xd @ beta
    rss = float(((y - y_hat)**2).sum()); tss = float(((y - y.mean())**2).sum())
    r2 = 1 - rss/tss
    class _Model:
        def predict(self, Xnew):
            Xnew = np.asarray(Xnew, dtype=float).reshape(-1)
            return slope*Xnew + intercept
    model = _Model()

print("回归系数(斜率):", round(slope, 2))
print("截距:", round(intercept, 2))
print("R²:", round(r2, 3))
回归系数(斜率): 3.68
截距: 56.36
R²: 0.983

(4)预测示例:小杨每周自习 12 小时¶

In [4]:
# 预测成绩(学习时间 = 12)
predicted_score = model.predict([[12]]) if hasattr(model, 'predict') else (slope*12 + intercept)
pred = float(predicted_score[0]) if hasattr(predicted_score, '__len__') else float(predicted_score)
print("预测成绩:", round(pred, 2))
预测成绩: 100.55

(5)可视化与结果解读¶

  • 散点图 + 回归线,直观看到正相关。
  • R² 显示模型能解释多少变异;但样本较小,仅做入门示例。
In [6]:
import matplotlib.pyplot as plt
import numpy as np

# 设置支持中文的字体,例如黑体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 或者 'Microsoft YaHei', 'FangSong', 'KaiTi' 等
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

x = X.flatten()
plt.figure()
plt.scatter(x, y)  # 散点
x_line = np.linspace(x.min(), x.max(), 100)
y_line = slope * x_line + intercept
plt.plot(x_line, y_line)  # 回归线
plt.xlabel("每周自习时间(小时)")
plt.ylabel("考试成绩")
plt.title("学习时长 vs 考试成绩(含回归线)")
plt.show()

一句话解读:在该样本中,自习时间与成绩呈显著正相关,简单线性模型的预测在样本内表现良好。
注意:相关不代表因果;还需控制基础能力、课程难度等潜在混杂因素。

11.5.2 案例二:城市居民生活满意度¶

问题:满意度与哪些因素相关?收入与教育是否有关联?

(1)数据加载与初步探索¶

In [7]:
# 模拟调查数据
df2 = pd.DataFrame({
    '年龄': [25, 32, 40, 29, 38, 45, 50, 28],
    '性别': ['女','男','女','女','男','男','男','女'],
    '教育水平': ['本科','硕士及以上','本科','高中及以下','本科','硕士及以上','硕士及以上','高中及以下'],
    '月收入': [6000, 18000, 8000, 3000, 7500, 20000, 15000, 4500],
    '满意度评分': [6, 9, 7, 4, 7, 10, 8, 5]
})

desc = df2.describe(include='all')
desc
Out[7]:
年龄 性别 教育水平 月收入 满意度评分
count 8.000000 8 8 8.000000 8.00
unique NaN 2 3 NaN NaN
top NaN 女 本科 NaN NaN
freq NaN 4 3 NaN NaN
mean 35.875000 NaN NaN 10250.000000 7.00
std 8.838835 NaN NaN 6480.740698 2.00
min 25.000000 NaN NaN 3000.000000 4.00
25% 28.750000 NaN NaN 5625.000000 5.75
50% 35.000000 NaN NaN 7750.000000 7.00
75% 41.250000 NaN NaN 15750.000000 8.25
max 50.000000 NaN NaN 20000.000000 10.00

(2)分组与聚合分析¶

In [8]:
# 按教育水平分组,查看平均收入与平均满意度
grouped = df2.groupby('教育水平')[['月收入', '满意度评分']].mean().round(2)
grouped
Out[8]:
月收入 满意度评分
教育水平
本科 7166.67 6.67
硕士及以上 17666.67 9.00
高中及以下 3750.00 4.50

(3)相关性分析¶

In [9]:
# 收入与满意度的相关性(皮尔逊)
corr2 = df2[['月收入', '满意度评分']].corr()
corr2
Out[9]:
月收入 满意度评分
月收入 1.000000 0.958885
满意度评分 0.958885 1.000000

(4)简单线性回归分析(收入 → 满意度)¶

In [10]:
X2 = df2[['月收入']].to_numpy(dtype=float)
y2 = df2['满意度评分'].to_numpy(dtype=float)

try:
    from sklearn.linear_model import LinearRegression
    model2 = LinearRegression().fit(X2, y2)
    coef2 = float(model2.coef_[0])
    intercept2 = float(model2.intercept_)
    r2_2 = float(model2.score(X2, y2))
except Exception as e:
    Xd2 = np.column_stack([np.ones(len(X2)), X2.flatten()])
    beta2, *_ = np.linalg.lstsq(Xd2, y2, rcond=None)
    intercept2, coef2 = float(beta2[0]), float(beta2[1])
    yhat2 = Xd2 @ beta2
    rss2 = float(((y2 - yhat2)**2).sum()); tss2 = float(((y2 - y2.mean())**2).sum())
    r2_2 = 1 - rss2/tss2
    class _Model2:
        def predict(self, Xnew):
            Xnew = np.asarray(Xnew, dtype=float).reshape(-1)
            return coef2*Xnew + intercept2
    model2 = _Model2()

print("回归系数:", round(coef2, 4))
print("截距:", round(intercept2, 2))
print("R-squared:", round(r2_2, 2))
回归系数: 0.0003
截距: 3.97
R-squared: 0.92

(5)可视化与启示¶

  • 用散点 + 回归线直观查看正向关系。
  • 局限:样本小、未控制其他变量(如住房、健康、婚姻等),结论仅供参考。
In [11]:
import matplotlib.pyplot as plt

x2 = X2.flatten()
plt.figure()
plt.scatter(x2, y2)
x_line2 = np.linspace(x2.min(), x2.max(), 100)
y_line2 = coef2 * x_line2 + intercept2
plt.plot(x_line2, y_line2)
plt.xlabel("月收入")
plt.ylabel("满意度评分")
plt.title("收入与满意度(含回归线)")
plt.show()

注意事项(常见坑)¶

  1. 相关不等于因果:可能存在共同原因或反向因果。
  2. 样本量与代表性:小样本结论不稳健;调查需关注抽样方法与偏差。
  3. 变量定义与测量:满意度的量表区间、收入的单位与范围需明确。
  4. 模型假设:线性、独立、同方差与残差正态性只是理想化假设,实际研究要做诊断。

练习(建议先做再看答案)¶

练习 1(案例一):
在原数据中加入两名学生记录(自拟时间与成绩),重新计算相关系数与回归参数,并预测 每周自习=11 的成绩。

练习 2(案例二):
将“教育水平”重新编码为有序数值(如 高中及以下=1,本科=2,硕士及以上=3),与“满意度评分”一起计算Spearman 等级相关(若无 scipy,请用 corr(method='spearman'))。

练习 3(可视化):
分别为两个案例画散点 + 回归线,并用一句话解读图形特征(是否有离群点、线性趋势是否明显)。

In [ ]:
# === 在此动手做题(你可以多建几个单元格)===
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 练习 1:案例一扩展


# 练习 2:案例二 Spearman 等级相关


# 练习 3:两案例可视化 + 一句话解读

参考答案(对照自检)¶

说明:答案仅示例;请用自然语言把数值含义讲清楚。

In [12]:
# 练习 1 参考答案
import numpy as np
import pandas as pd

df_ext = df.copy()
df_ext.loc[len(df_ext)] = ['学生K', 11, 89]
df_ext.loc[len(df_ext)] = ['学生L', 12, 94]

X_ext = df_ext[['每周自习时间']].to_numpy(dtype=float)
y_ext = df_ext['考试成绩'].to_numpy(dtype=float)

try:
    from sklearn.linear_model import LinearRegression
    m_ext = LinearRegression().fit(X_ext, y_ext)
    slope_e = float(m_ext.coef_[0]); inter_e = float(m_ext.intercept_)
    r2_e = float(m_ext.score(X_ext, y_ext))
    pred11 = float(m_ext.predict([[11]])[0])
except Exception:
    Xd = np.column_stack([np.ones(len(X_ext)), X_ext.flatten()])
    beta, *_ = np.linalg.lstsq(Xd, y_ext, rcond=None)
    inter_e, slope_e = float(beta[0]), float(beta[1])
    yhat = Xd @ beta
    rss = float(((y_ext - yhat)**2).sum()); tss = float(((y_ext - y_ext.mean())**2).sum())
    r2_e = 1 - rss/tss
    pred11 = slope_e*11 + inter_e

print("相关系数:\n", df_ext[['每周自习时间','考试成绩']].corr())
print("回归:斜率=", round(slope_e,2), "截距=", round(inter_e,2), "R²=", round(r2_e,3))
print("预测(每周自习=11):", round(pred11,2))
相关系数:
           每周自习时间      考试成绩
每周自习时间  1.000000  0.964944
考试成绩    0.964944  1.000000
回归:斜率= 2.98 截距= 60.31 R²= 0.931
预测(每周自习=11): 93.09
In [13]:
# 练习 2 参考答案(Spearman)
df2_ = df2.copy()
edu_map = {'高中及以下':1, '本科':2, '硕士及以上':3}
df2_['教育编码'] = df2_['教育水平'].map(edu_map)

try:
    from scipy import stats
    rho, p = stats.spearmanr(df2_['教育编码'], df2_['满意度评分'])
    print("Spearman ρ:", round(float(rho),3), " p值:", round(float(p),4))
except Exception:
    print(df2_[['教育编码','满意度评分']].corr(method='spearman'))
Spearman ρ: 0.951  p值: 0.0003
In [14]:
# 练习 3 参考答案(画图)
import matplotlib.pyplot as plt
import numpy as np

# 案例一
x1 = df_ext['每周自习时间'].to_numpy(dtype=float)
y1 = df_ext['考试成绩'].to_numpy(dtype=float)
Xd1 = np.column_stack([np.ones_like(x1), x1])
b1, *_ = np.linalg.lstsq(Xd1, y1, rcond=None)
b0_, b1_ = float(b1[0]), float(b1[1])

plt.figure()
plt.scatter(x1, y1)
xline = np.linspace(x1.min(), x1.max(), 100)
yline = b0_ + b1_*xline
plt.plot(xline, yline)
plt.xlabel("每周自习时间")
plt.ylabel("考试成绩")
plt.title("案例一扩展:散点与回归线")
plt.show()

# 案例二
x2 = df2['月收入'].to_numpy(dtype=float)
y2 = df2['满意度评分'].to_numpy(dtype=float)
Xd2 = np.column_stack([np.ones_like(x2), x2])
b2, *_ = np.linalg.lstsq(Xd2, y2, rcond=None)
c0_, c1_ = float(b2[0]), float(b2[1])

plt.figure()
plt.scatter(x2, y2)
xline2 = np.linspace(x2.min(), x2.max(), 100)
yline2 = c0_ + c1_*xline2
plt.plot(xline2, yline2)
plt.xlabel("月收入")
plt.ylabel("满意度评分")
plt.title("案例二:散点与回归线")
plt.show()

print("解读示例:两图均呈现正向线性趋势,案例二因样本少、点位分散,线性关系的不确定性更高。")
解读示例:两图均呈现正向线性趋势,案例二因样本少、点位分散,线性关系的不确定性更高。

小结¶

  • 将研究问题转为变量与表格是第一步。
  • 相关性用于先验探索,回归用于定量关系与预测。
  • 报告时需说明:样本量、统计方法、估计值与不确定性、局限与假设。

下一步:在案例中加入残差诊断/QQ 图与多元回归,并尝试使用交叉验证评估泛化能力(进阶)。