本章用两个小案例,贯通数据准备 → 相关性 → 回归建模 → 预测与解读的完整流程:
- 案例一:学习时长与成绩
- 案例二:城市居民生活满意度
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。问题:自习时间是否与考试成绩相关?能否用来预测?
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 (
| 姓名 | 每周自习时间 | 考试成绩 | |
|---|---|---|---|
| 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 |
# 计算皮尔逊相关系数矩阵
corr_mat = df[['每周自习时间', '考试成绩']].corr()
corr_mat
| 每周自习时间 | 考试成绩 | |
|---|---|---|
| 每周自习时间 | 1.000000 | 0.991594 |
| 考试成绩 | 0.991594 | 1.000000 |
# 线性回归建模(优先 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
# 预测成绩(学习时间 = 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
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()
一句话解读:在该样本中,自习时间与成绩呈显著正相关,简单线性模型的预测在样本内表现良好。
注意:相关不代表因果;还需控制基础能力、课程难度等潜在混杂因素。
问题:满意度与哪些因素相关?收入与教育是否有关联?
# 模拟调查数据
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
| 年龄 | 性别 | 教育水平 | 月收入 | 满意度评分 | |
|---|---|---|---|---|---|
| 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 |
# 按教育水平分组,查看平均收入与平均满意度
grouped = df2.groupby('教育水平')[['月收入', '满意度评分']].mean().round(2)
grouped
| 月收入 | 满意度评分 | |
|---|---|---|
| 教育水平 | ||
| 本科 | 7166.67 | 6.67 |
| 硕士及以上 | 17666.67 | 9.00 |
| 高中及以下 | 3750.00 | 4.50 |
# 收入与满意度的相关性(皮尔逊)
corr2 = df2[['月收入', '满意度评分']].corr()
corr2
| 月收入 | 满意度评分 | |
|---|---|---|
| 月收入 | 1.000000 | 0.958885 |
| 满意度评分 | 0.958885 | 1.000000 |
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
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(案例一):
在原数据中加入两名学生记录(自拟时间与成绩),重新计算相关系数与回归参数,并预测 每周自习=11 的成绩。
练习 2(案例二):
将“教育水平”重新编码为有序数值(如 高中及以下=1,本科=2,硕士及以上=3),与“满意度评分”一起计算Spearman 等级相关(若无 scipy,请用 corr(method='spearman'))。
练习 3(可视化):
分别为两个案例画散点 + 回归线,并用一句话解读图形特征(是否有离群点、线性趋势是否明显)。
# === 在此动手做题(你可以多建几个单元格)===
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 练习 1:案例一扩展
# 练习 2:案例二 Spearman 等级相关
# 练习 3:两案例可视化 + 一句话解读
说明:答案仅示例;请用自然语言把数值含义讲清楚。
# 练习 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
# 练习 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
# 练习 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 图与多元回归,并尝试使用交叉验证评估泛化能力(进阶)。