数据转换
数据转换是数据处理的重要环节,包括类型转换、分布变换和自定义转换(如基于业务逻辑的复杂计算)。
读取数据
# 读取空气质量数据
aqi_data = pd.read_csv("data/aqi_day.csv")
# 读取城市信息数据
city_data = pd.read_csv("data/city.csv")
# 查看数据结构
print("空气质量数据形状:", aqi_data.shape)
print("城市数据形状:", city_data.shape)
空气质量数据形状: (534176, 10)
城市数据形状: (383, 7)
数据类型转换
常见的 CSV 格式是没有数据类型信息的,通常程序在读取时对数据类型进行猜测。如果程序猜测错误,那么数据的类型就不是期望的类型。此时,就需要对数据类型进行转换。
日期类型
日期的格式比较复杂,通常都是以字符串的方式进行读取。
可以使用 pandas 提供的 to_datetime 函数进行转换。
# 原数据类型
print("time_point列类型:", type(aqi_data['time_point'].iloc[0]))
# 将字符日期转换为日期类型
aqi_data['time'] = pd.to_datetime(aqi_data['time_point'])
# 验证转换结果
print("time列类型:", type(aqi_data['time'].iloc[0]))
time_point列类型: <class 'str'>
time列类型: <class 'pandas.Timestamp'>
pd.to_datetime() 该函数的主要参数及说明如下表所示:
format: 手动指定日期时间的格式字符串(如 '%Y-%m-%d')
dayfirst: 解析歧义日期(如”01/02/2025”)时,是否以“日”为优先[reference:5]。
yearfirst: 解析歧义日期时,是否以“年”为优先。其优先级高于 dayfirst[reference:6]。
utc: 控制时区处理[reference:7]:
True: 始终返回UTC时区的时间,将无时区输入“本地化”为UTC,有时区输入则“转换”为UTC。
False: (默认) 保持输入的时区属性。
unit: 指定整数/浮点数类型 arg 的时间单位[reference:8]。常用单位有:'D'(天), 's'(秒), 'ms'(毫秒), 'us'(微秒), 'ns'(纳秒)。
其他类型
# 将AQI转换为整数类型(原为数值型,但可能包含小数)
aqi_data['aqi_int'] = aqi_data['aqi'].astype(int)
# 查看转换后的列
print("\nAQI转换前后对比:")
print(aqi_data[['aqi', 'aqi_int']].head())
AQI转换前后对比:
aqi aqi_int
0 195 195
1 197 197
2 260 260
3 154 154
4 158 158
分布变换
一个常见的操作是将左偏的数据变换为正态化的数据。
aqi_data['aqi_log'] = np.log(aqi_data['aqi'] + 1)
通过直方图观察数据分布情况
fig, facets = plt.subplots(1, 2)
facets[0].hist(aqi_data['aqi'], bins=30, edgecolor='black', alpha=0.7, color='steelblue')
facets[0].set_xlabel('AQI', fontsize=12)
facets[0].set_ylabel('Frequency', fontsize=12)
facets[1].hist(aqi_data['aqi_log'], bins=30, edgecolor='black', alpha=0.7, color='steelblue')
facets[1].set_xlabel('log(AQI)', fontsize=12)
facets[1].set_ylabel('Frequency', fontsize=12)
plt.tight_layout()
plt.show()
自定义转换
自定义转换允许根据业务需求创建新变量。例如,根据AQI值划分污染等级,或计算多个污染物的综合指数。
条件转换
使用np.select()或pd.cut()进行条件赋值。
bins = [0, 50, 100, 150, 200, 300, float('inf')]
labels = ['优', '良', '轻度污染', '中度污染', '重度污染', '严重污染']
pd.cut(aqi_data['aqi'], bins=bins, labels=labels)
0 中度污染
1 中度污染
2 重度污染
3 中度污染
4 中度污染
...
534171 良
534172 良
534173 良
534174 优
534175 良
Name: aqi, Length: 534176, dtype: category
Categories (6, str): ['优' < '良' < '轻度污染' < '中度污染' < '重度污染' < '严重污染']
多变量综合计算
创建综合污染指数,例如加权平均多个污染物浓度。
# 计算综合污染指数(假设权重)
aqi_data['composite_index'] = (
aqi_data['pm25'] * 0.3 + aqi_data['pm10'] * 0.2 +
aqi_data['so2'] * 0.1 + aqi_data['co'] * 0.1 +
aqi_data['no2'] * 0.2 + aqi_data['o3'] * 0.1
)
aqi_data[['pm25', 'pm10', 'so2', 'co', 'no2', 'o3', 'composite_index']].head()
| 0 |
146 |
173 |
72 |
2.3 |
107 |
47 |
111.93 |
| 1 |
148 |
176 |
70 |
2.1 |
113 |
41 |
113.51 |
| 2 |
210 |
187 |
71 |
1.7 |
83 |
25 |
126.77 |
| 3 |
118 |
148 |
56 |
1.4 |
83 |
65 |
93.84 |
| 4 |
121 |
142 |
50 |
1.6 |
87 |
65 |
93.76 |
应用函数进行转换
使用apply()与自定义函数进行复杂转换。
pollutant_names = ['pm25', 'pm10', 'so2', 'co', 'no2', 'o3']
def major_pollutant(cityname, time_point, aqi, pm25, pm10, so2, co, no2, o3, rank):
pollutants = [pm25, pm10, so2, co, no2, o3]
major_index = pollutants.index(max(pollutants))
return pollutant_names[major_index]
# 应用函数
aqi_data.apply(
lambda row: major_pollutant(*row), axis=1
)
- apply 通常比向量化计算慢,但比使用
for 进行迭代要快。
- 只有当难以使用向量化计算时再考虑使用
apply。
应用函数进行转换
0 pm10
1 pm10
2 pm25
3 pm10
4 pm10
...
534171 pm10
534172 pm10
534173 pm10
534174 o3
534175 pm10
Length: 534176, dtype: str
数据归一化与标准化
数据归一化是将数据按比例缩放,使之落入一个特定区间,消除量纲影响,使不同特征具有可比性。标准化是将数据转换为均值为0、标准差为1的分布。以下是几种常用的归一化和标准化方法:
1. Min-Max归一化(最小-最大标准化)
\[x' = \frac{x - \min(x)}{\max(x) - \min(x)}\]
3. 小数定标标准化
\[x' = \frac{x}{10^j}\] 其中\(j\)是使最大绝对值小于1的最小整数。通过移动小数点进行标准化,计算简单。
2. Z-score标准化(标准差标准化)
\[x' = \frac{x - \mu}{\sigma}\]
4. Robust标准化(稳健标准化)
\[x' = \frac{x - \text{median}(x)}{\text{IQR}(x)}\]
其中median是中位数,IQR是四分位距(Q3-Q1)。对异常值不敏感,适用于包含异常值的数据。
Min-Max 归一化
def min_max_normalize(x):
"""对Series进行Min-Max归一化"""
return (x - x.min()) / (x.max() - x.min())
aqi_data[['pm25', 'pm10', 'co']].apply(min_max_normalize).head()
| 0 |
0.081428 |
0.019635 |
0.000300 |
| 1 |
0.082543 |
0.019975 |
0.000274 |
| 2 |
0.117122 |
0.021223 |
0.000222 |
| 3 |
0.065811 |
0.016797 |
0.000183 |
| 4 |
0.067485 |
0.016116 |
0.000209 |
或使用 scikit-learn 中的函数
MinMaxScaler().fit_transform(aqi_data[['pm25', 'pm10', 'co']])
array([[8.14277747e-02, 1.96345477e-02, 2.99967395e-04],
[8.25432236e-02, 1.99750312e-02, 2.73883274e-04],
[1.17122142e-01, 2.12234707e-02, 2.21715031e-04],
...,
[1.84049080e-02, 8.51208716e-03, 6.52103032e-05],
[1.11544897e-02, 4.76676881e-03, 6.52103032e-05],
[2.90016732e-02, 8.17160368e-03, 1.17378546e-04]])
Z-score 标准化
def z_score_normalize(x):
"""对Series进行Z-score标准化"""
return (x - x.mean()) / x.std()
aqi_data[['pm25', 'pm10', 'co']].apply(z_score_normalize).head()
| 0 |
2.466409 |
1.375130 |
0.104391 |
| 1 |
2.515985 |
1.419904 |
0.087549 |
| 2 |
4.052852 |
1.584075 |
0.053864 |
| 3 |
1.772340 |
1.002014 |
0.028600 |
| 4 |
1.846704 |
0.912466 |
0.045442 |
或使用 scikit-learn 中的函数
StandardScaler().fit_transform(aqi_data[['pm25', 'pm10', 'co']])
array([[ 2.46641086, 1.37513125, 0.10439155],
[ 2.51598725, 1.41990518, 0.08754893],
[ 4.05285551, 1.58407627, 0.05386369],
...,
[-0.33465547, -0.08748389, -0.04719202],
[-0.65690204, -0.57999715, -0.04719202],
[ 0.13632028, -0.13225782, -0.01350678]])
稳健标准化
def robust_normalize(x):
"""对Series进行Robust归一化"""
return (x - x.median()) / (x.quantile(0.75) - x.quantile(0.25))
aqi_data[['pm25', 'pm10', 'co']].apply(robust_normalize).head()
| 0 |
3.055556 |
1.770492 |
2.8 |
| 1 |
3.111111 |
1.819672 |
2.4 |
| 2 |
4.833333 |
2.000000 |
1.6 |
| 3 |
2.277778 |
1.360656 |
1.0 |
| 4 |
2.361111 |
1.262295 |
1.4 |
或使用 scikit-learn 中的函数
RobustScaler().fit_transform(aqi_data[['pm25', 'pm10', 'co']])
array([[ 3.05555556, 1.7704918 , 2.8 ],
[ 3.11111111, 1.81967213, 2.4 ],
[ 4.83333333, 2. , 1.6 ],
...,
[-0.08333333, 0.16393443, -0.8 ],
[-0.44444444, -0.37704918, -0.8 ],
[ 0.44444444, 0.1147541 , 0. ]])
函数参数说明
min() / max(): 计算最小值/最大值。
mean(): 计算均值。
std(): 计算标准差。
median(): 计算中位数。
quantile(): 计算分位数,参数q指定分位点(0-1之间)。
np.ceil(): 向上取整。
np.log10(): 计算以10为底的对数。
MinMaxScaler(): scikit-learn的Min-Max归一化器,fit_transform()同时拟合参数并转换数据。
StandardScaler(): scikit-learn的Z-score标准化器。
RobustScaler(): scikit-learn的稳健标准化器。
应用场景建议
- Min-Max归一化: 当数据分布范围已知且无极端异常值时使用,如图像处理、神经网络输入。
- Z-score标准化: 当数据近似正态分布时使用,如回归分析、聚类分析。
- Robust标准化: 当数据包含异常值时使用,如金融数据、传感器数据。
- 小数定标标准化: 当需要简单快速标准化时使用,计算复杂度低。
缺失值处理
实际数据中经常存在缺失值,需要识别、分析和处理缺失值,以保证数据分析的可靠性。
识别缺失值
# 查看各列缺失值数量
missing_summary = aqi_data.isnull().sum().sort_values(ascending=False)
print("各列缺失值数量:")
print(missing_summary[missing_summary > 0])
各列缺失值数量:
rank 4868
dtype: int64
# 计算缺失值比例
missing_percent = (aqi_data.isnull().sum() / len(aqi_data) * 100).sort_values(ascending=False)
print("\n各列缺失值比例(%):")
print(missing_percent[missing_percent > 0])
各列缺失值比例(%):
rank 0.91131
dtype: float64
可视化缺失值分布
msno.matrix(aqi_data, figsize=(10, 6))
plt.title('Missing Matrix')
plt.show()
处理缺失值
常见处理方法包括删除、插补(均值、中位数、众数、模型预测等)。
删除
# 删除任何列包含缺失值的行
aqi_complete = aqi_data.dropna()
# 删除特定列缺失的行
aqi_complete_pm25 = aqi_data.dropna(subset=['pm25'])
# 比较删除前后的行数
print(f"原始数据行数: {len(aqi_data)}")
print(f"删除所有缺失行后行数: {len(aqi_complete)}")
print(f"只删除pm25缺失行后行数: {len(aqi_complete_pm25)}")
原始数据行数: 534176
删除所有缺失行后行数: 529308
只删除pm25缺失行后行数: 534176
插补
均值插补
aqi_imputed = aqi_data.copy()
# 均值插补(仅对数值列)
numeric_cols = aqi_imputed.select_dtypes(include=[np.number]).columns
for col in numeric_cols:
if aqi_imputed[col].isnull().any():
mean_val = aqi_imputed[col].mean()
aqi_imputed[col].fillna(mean_val, inplace=True)
分组均值插补
aqi_group_imputed = aqi_data.copy()
if 'cityname' in aqi_group_imputed.columns:
numeric_cols = aqi_group_imputed.select_dtypes(include=[np.number]).columns
for col in numeric_cols:
if aqi_group_imputed[col].isnull().any():
aqi_group_imputed[col] = aqi_group_imputed.groupby('cityname')[col].transform(
lambda x: x.fillna(x.mean())
)
向前/向后填充
对于时间序列数据,可以使用前后值进行填充。
# 按城市分组,对每个污染物进行向前填充
aqi_filled = aqi_data.copy()
# 确保数据按时间排序
if 'time_point' in aqi_filled.columns and 'cityname' in aqi_filled.columns:
aqi_filled = aqi_filled.sort_values(['cityname', 'time_point'])
# 向前填充(使用前一个非空值)
fill_cols = ['pm25', 'pm10', 'so2', 'co', 'no2', 'o3']
for col in fill_cols:
if col in aqi_filled.columns:
aqi_filled[col] = aqi_filled\
.groupby('cityname')[col]\
.ffill()
函数参数说明
isnull(): 检查缺失值,返回布尔DataFrame。
sum(): 对布尔值求和,统计True(缺失)的数量。
dropna(): 删除包含缺失值的行,subset参数指定只检查特定列。
fillna(): 填充缺失值,参数为填充值或填充方法(如mean()、ffill、bfill)。
groupby(): 按指定列分组,transform()对每个组应用函数并返回与原始数据相同形状的结果。
ffill(): 向前填充,使用前一个非缺失值填充当前缺失值。
基础数据分析
基础数据分析包括汇总统计、回归分析、方差分析等,帮助我们理解数据分布和变量间关系。
汇总统计
对数据进行描述性统计,包括均值、中位数、标准差、分位数等。
均值(Mean): 所有观测值的算术平均值。当数据分布对称且无极端值时使用 \[\bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i\]
df['col'].mean() 或 np.mean(array)
标准差(Standard Deviation): 数据离散程度的度量,表示数据点与均值的平均距离。衡量数据的波动性、风险等。 \[s = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2}\]
df['col'].std(ddof=1) 或 np.std(array, ddof=1)
方差(Variance): 标准差的平方,表示数据离散程度的平方度量 \[s^2 = \frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2\]
df['col'].var(ddof=1) 或 np.var(array, ddof=1)
偏度(Skewness): 数据分布不对称程度的度量 \[g_1 = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^3}{\left(\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2\right)^{3/2}}\]
- 意义:
- 偏度 > 0:右偏(正偏),均值 > 中位数
- 偏度 < 0:左偏(负偏),均值 < 中位数
- 偏度 ≈ 0:分布对称
- 应用: 检验数据分布的正态性
df['col'].skew() 或 scipy.stats.skew(array)
众数(Mode): 数据中出现次数最多的值
df['col'].mode()[0] 或 stats.mode(array)
单变量统计
# 对AQI进行描述性统计
aqi_data['aqi'].describe()
count 534176.000000
mean 80.658974
std 49.362288
min 0.000000
25% 50.000000
50% 69.000000
75% 97.000000
max 1210.000000
Name: aqi, dtype: float64
# 计算分位数
aqi_data['aqi'].quantile([0.25, 0.5, 0.75])
0.25 50.0
0.50 69.0
0.75 97.0
Name: aqi, dtype: float64
# 计算多个变量的统计量
aqi_data[['aqi', 'pm25', 'pm10']].agg(['mean', 'median', 'std', 'min', 'max'])
| mean |
80.658974 |
46.500597 |
80.861707 |
| median |
69.000000 |
36.000000 |
65.000000 |
| std |
49.362288 |
40.341817 |
67.003335 |
| min |
0.000000 |
0.000000 |
0.000000 |
| max |
1210.000000 |
1793.000000 |
8811.000000 |
分组汇总统计
aqi_data.groupby('cityname')['aqi'].agg(
mean_aqi='mean',
median_aqi='median',
sd_aqi='std',
days='count'
).reset_index().sort_values('mean_aqi', ascending=False)
| 60 |
和田地区 |
176.027140 |
131.0 |
138.693014 |
1437 |
| 67 |
喀什地区 |
169.638947 |
133.0 |
119.155084 |
1443 |
| 28 |
保定 |
140.108093 |
120.0 |
80.835950 |
1841 |
| 272 |
邢台 |
138.219446 |
117.0 |
81.173437 |
1841 |
| 225 |
石家庄 |
135.435256 |
112.0 |
84.017301 |
1838 |
| ... |
... |
... |
... |
... |
... |
| 322 |
黔西南州 |
43.258065 |
41.0 |
15.911391 |
1457 |
| 186 |
海口 |
43.250675 |
38.0 |
20.994899 |
1851 |
| 16 |
丽江 |
42.597804 |
41.0 |
11.852409 |
1457 |
| 267 |
迪庆州 |
39.720467 |
38.0 |
12.356759 |
1456 |
| 1 |
三亚 |
38.243139 |
33.0 |
17.971288 |
1822 |
325 rows × 5 columns
多变量交叉统计
# 按城市和月份交叉统计
aqi_data['month'] = pd.to_datetime(aqi_data['time_point']).dt.month
aqi_data.groupby(['month', 'cityname']).agg(
mean_aqi=('aqi', 'mean')
).reset_index().sort_values(['month', 'cityname'])
| 0 |
1 |
七台河 |
96.822581 |
| 1 |
1 |
三亚 |
50.051613 |
| 2 |
1 |
三明 |
53.508065 |
| 3 |
1 |
三门峡 |
146.851613 |
| 4 |
1 |
上海 |
96.270968 |
| ... |
... |
... |
... |
| 3895 |
12 |
黔东南州 |
64.958678 |
| 3896 |
12 |
黔南州 |
63.850000 |
| 3897 |
12 |
黔西南州 |
47.049587 |
| 3898 |
12 |
齐齐哈尔 |
80.447368 |
| 3899 |
12 |
龙岩 |
49.421488 |
3900 rows × 3 columns
回归分析
回归分析用于研究变量之间的因果关系。多元线性回归分析多个自变量与一个因变量之间的线性关系。
回归分析理论基础
1. 数学模型
多元线性回归模型的一般形式为: \[y_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_p x_{ip} + \varepsilon_i\] 其中:
- \(y_i\):第\(i\)个观测的因变量(响应变量)
- \(x_{i1}, x_{i2}, \ldots, x_{ip}\):第\(i\)个观测的\(p\)个自变量(解释变量)
- \(\beta_0\):截距项(常数项)
- \(\beta_1, \beta_2, \ldots, \beta_p\):回归系数,表示各自变量对因变量的边际影响
- \(\varepsilon_i\):随机误差项,满足\(\varepsilon_i \sim N(0, \sigma^2)\)
矩阵形式:
\(Y = X\beta + \varepsilon\)
其中\(Y\)为\(n \times 1\)的因变量向量,\(X\)为\(n \times (p+1)\)的设计矩阵,\(\beta\)为\((p+1) \times 1\)的系数向量。
4. 回归结果的关键统计量
- R-squared(\(R^2\)):模型解释的方差比例,\(R^2 = 1 - \frac{SS_{res}}{SS_{tot}}\),范围[0,1],越大越好
- Adjusted R-squared:调整的\(R^2\),考虑自变量个数对\(R^2\)的影响
- F-statistic:整体模型显著性检验,检验所有系数是否同时为0
- Coefficients:回归系数\(\hat{\beta}_j\),表示\(x_j\)每变化1单位,\(y\)的平均变化量
- t-statistic:单个系数显著性检验,\(t = \frac{\hat{\beta}_j}{SE(\hat{\beta}_j)}\)
- p-value:系数显著性的概率,通常\(p < 0.05\)认为显著
- 标准误(SE):系数估计的标准误差,衡量估计精度
- 置信区间:系数的95%置信区间
5. 模型诊断与检验
- 残差分析:检查残差是否随机分布、满足同方差性和正态性
- 多重共线性诊断:方差膨胀因子(VIF),通常VIF > 10表示严重共线性
- 异方差性检验:Breusch-Pagan检验、White检验
- 正态性检验:残差的Q-Q图、Shapiro-Wilk检验
- 异常值检测:Cook距离、杠杆值、学生化残差
6. 注意事项
- 样本量要求:通常每个自变量需要10-15个观测值
- 变量选择:避免过度拟合(变量太多)和欠拟合(变量太少)
- 数据预处理:需要进行缺失值处理、异常值处理、变量转换等
- 模型验证:使用交叉验证、训练集-测试集分割验证模型泛化能力
- 结果报告:应报告系数、标准误、\(p\)值、\(R^2\)、样本量等关键信息
数据预处理
在开始前,一般先检查通过相关性矩阵检查数据分布和变量之间的相关性。如果有非正态分布的变量,要注意将其正态化。
pm25_tem_pre = aqi_weather[['pm25', 'temperature', 'precipitation']]
pm25_tem_pre = pm25_tem_pre[(pm25_tem_pre['precipitation'] > 0) & (pm25_tem_pre['pm25'] > 0)]
fig, axis = plt.subplots(1, 3)
for i in range(3):
axis[i].hist(pm25_tem_pre.iloc[:, i], bins=30)
plt.tight_layout()
plt.show()
此处演示三种正态化方法:
- 取对数:用于纠正右偏数据
- Yeo-Johnson 变换:可用于有正有负的数据
- Box-Cox 变换:只能用于正数
pm25_tem_pre_norm = pm25_tem_pre.copy()
pm25_tem_pre_norm['pm25'] = np.log(pm25_tem_pre_norm['pm25'])
pm25_tem_pre_norm['temperature'] = stats.yeojohnson(pm25_tem_pre_norm['temperature'])[0]
pm25_tem_pre_norm['precipitation'] = stats.boxcox(pm25_tem_pre_norm['precipitation'])[0]
pm25_tem_pre_norm[['temperature', 'precipitation']] = StandardScaler().fit_transform(pm25_tem_pre_norm[['temperature', 'precipitation']])
fig, axis = plt.subplots(3, 3)
for i in range(3):
for j in range(3):
if i == j:
axis[i, j].hist(pm25_tem_pre_norm.iloc[:, i], bins=30)
axis[i, j].set_yticks([])
elif i > j:
axis[i, j].scatter(pm25_tem_pre_norm.iloc[:, i], pm25_tem_pre_norm.iloc[:, j], s=0.1)
else:
axis[i, j].text(0.5, 0.5, '{:.2f}'.format(np.corrcoef(pm25_tem_pre_norm.iloc[:, i], pm25_tem_pre_norm.iloc[:, j])[0][1]))
if i != 2:
axis[i, j].set_xticks([])
if j != 0:
axis[i, j].set_yticks([])
plt.show()
构建回归分析模型
statsmodels 包提供了构建回归分析模型的工具。输入因变量和自变量,即可得到模型结果。
注意对自变量使用 sm.add_constant 函数增加一个截距项。
model_aqi = sm.OLS(
pm25_tem_pre_norm['pm25'],
sm.add_constant(pm25_tem_pre_norm[['temperature', 'precipitation']])
).fit()
model_aqi.summary()
构建回归分析模型
OLS Regression Results
| Dep. Variable: |
pm25 |
R-squared: |
0.175 |
| Model: |
OLS |
Adj. R-squared: |
0.175 |
| Method: |
Least Squares |
F-statistic: |
5.570e+04 |
| Date: |
Mon, 06 Apr 2026 |
Prob (F-statistic): |
0.00 |
| Time: |
14:38:50 |
Log-Likelihood: |
-5.2384e+05 |
| No. Observations: |
526481 |
AIC: |
1.048e+06 |
| Df Residuals: |
526478 |
BIC: |
1.048e+06 |
| Df Model: |
2 |
|
|
| Covariance Type: |
nonrobust |
|
|
|
coef |
std err |
t |
P>|t| |
[0.025 |
0.975] |
| const |
3.5877 |
0.001 |
3977.660 |
0.000 |
3.586 |
3.589 |
| temperature |
-0.0988 |
0.001 |
-95.745 |
0.000 |
-0.101 |
-0.097 |
| precipitation |
-0.2403 |
0.001 |
-232.793 |
0.000 |
-0.242 |
-0.238 |
| Omnibus: |
688.451 |
Durbin-Watson: |
0.554 |
| Prob(Omnibus): |
0.000 |
Jarque-Bera (JB): |
714.098 |
| Skew: |
-0.073 |
Prob(JB): |
8.62e-156 |
| Kurtosis: |
3.107 |
Cond. No. |
1.70 |
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
回归诊断
对于回归分析的诊断通常从残差着手。通常模型要求残差具有
首先获取残差值和拟合值。
model_aqi_res = model_aqi.resid
model_aqi_pred = model_aqi.fittedvalues
通过以下几幅图进行分析
残差图
残差图是指以残差为纵坐标,以任何其他指定的量为横坐标的散点图。残差不应该成片的很高或很低,而是在拟合值的范围内,残差应该以0为中心。
plt.scatter(model_aqi_pred, model_aqi_res, alpha=0.5)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('Predicted')
plt.ylabel('Residual')
plt.title('Residual vs Predicted')
plt.tight_layout()
plt.show()
Q-Q图
Q-Q 是一种用于比较两个概率分布是否相似的统计图形工具。通过绘制残差的各分位数和标准正态分布的各分位数,可以看到残差是否服从正态分布。
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
stats.probplot(model_aqi_res, dist="norm", plot=ax)
ax.set_title("Q-Q Plot")
plt.tight_layout()
plt.show()
残差直方图
残差直方图也是一种判断残差是否正态分布的方法。
plt.hist(model_aqi_res, bins=30, edgecolor='black', alpha=0.7, density=True)
# 添加正态分布曲线参考
x = np.linspace(model_aqi_res.min(), model_aqi_res.max(), 100)
plt.plot(x, stats.norm.pdf(x, model_aqi_res.mean(), model_aqi_res.std()), 'r-', lw=2)
plt.xlabel('Residual')
plt.ylabel('Density')
plt.title('Histogram of Residuals')
plt.tight_layout()
plt.show()
拟合值 vs 观测值
model_aqi_obs = pm25_tem_pre_norm['pm25']
plt.scatter(model_aqi_res, model_aqi_obs, alpha=0.5)
plt.xlabel('Fitted')
plt.ylabel('Observed')
plt.title('Fitted vs Observed')
plt.tight_layout()
plt.show()
统计诊断
除了对残差进行绘图分析之外,还可以进行一些统计诊断,分析模型的建模效果。
通常进行以下检验:
- 异方差检验
- 正态性检验
- 多重共线性检验
- 异常值检测
异方差性检验(Breusch-Pagan检验)
from statsmodels.stats.diagnostic import het_breuschpagan
bp_test = het_breuschpagan(model_aqi_res, model_aqi.model.exog, robust=True)
print(f"Breusch-Pagan异方差检验:")
print(f" LM统计量: {bp_test[0]:.4f}, p值: {bp_test[1]:.4f}")
print(f" F统计量: {bp_test[2]:.4f}, p值: {bp_test[3]:.4f}")
if bp_test[1] < 0.05:
print(" → 拒绝同方差原假设,存在异方差问题")
else:
print(" → 不能拒绝同方差原假设")
Breusch-Pagan异方差检验:
LM统计量: 14630.4075, p值: 0.0000
F统计量: 7524.2540, p值: 0.0000
→ 拒绝同方差原假设,存在异方差问题
正态性检验(Shapiro-Wilk检验)
shapiro_stat, shapiro_p = stats.shapiro(model_aqi_res.sample(5000)) # Shapiro检验最多支持5000个样本
print(f"\nShapiro-Wilk正态性检验:")
print(f" 统计量: {shapiro_stat:.4f}, p值: {shapiro_p:.4f}")
if shapiro_p < 0.05:
print(" → 拒绝正态性原假设,残差不服从正态分布")
else:
print(" → 不能拒绝正态性原假设")
Shapiro-Wilk正态性检验:
统计量: 0.9988, p值: 0.0009
→ 拒绝正态性原假设,残差不服从正态分布
多重共线性诊断(VIF)
from statsmodels.stats.outliers_influence import variance_inflation_factor
print(f"\n方差膨胀因子(VIF)检验:")
vif_data = pd.DataFrame()
vif_data["Variable"] = pm25_tem_pre_norm.columns[1:]
vif_data["VIF"] = [variance_inflation_factor(pm25_tem_pre_norm.values[:, 1:], i)
for i in range(pm25_tem_pre_norm.values[:, 1:].shape[1])]
print(vif_data)
high_vif = vif_data[vif_data["VIF"] > 10]
if len(high_vif) > 0:
print(f" → 警告: {len(high_vif)}个变量VIF > 10,存在多重共线性问题")
else:
print(" → VIF值正常,无严重多重共线性")
方差膨胀因子(VIF)检验:
Variable VIF
0 temperature 1.309916
1 precipitation 1.309916
→ VIF值正常,无严重多重共线性
异常值检测(Cook距离)
print(f"\n异常值检测(Cook距离):")
cooks_d = model_aqi.get_influence().cooks_distance[0]
high_influence = np.where(cooks_d > 4 / len(cooks_d))[0] # Cook距离 > 4/n
if len(high_influence) > 0:
print(f" 发现{len(high_influence)}个高影响点(Cook距离 > 4/n)")
print(f" 最大Cook距离: {cooks_d.max():.4f}")
# 显示前10个高影响点
print(f" 前10个高影响点索引: {high_influence[:10]}")
else:
print(" 未发现高影响点")
异常值检测(Cook距离):
发现26645个高影响点(Cook距离 > 4/n)
最大Cook距离: 0.0001
前10个高影响点索引: [ 2 6 8 15 16 23 24 25 27 28]
方差分析
方差分析(ANOVA)的核心思想是比较不同来源的变异,从而判断分组变量是否对结果(如体重)产生了显著影响。
先对空气质量数据进行处理,提取出季节、年份的信息。
seasons = ['Spring', 'Summer', "Fall", "Winter"]
aqi_season = aqi_data[['aqi', 'cityname', 'time_point']].groupby('time_point').agg({
'aqi': 'mean'
}).reset_index()
aqi_season['year'] = pd.to_datetime(aqi_season['time_point']).dt.year
aqi_season['month'] = pd.to_datetime(aqi_season['time_point']).dt.month
aqi_season['season'] = np.select([
(aqi_season['month'] >= 3) & (aqi_season['month'] <= 5),
(aqi_season['month'] >= 6) & (aqi_season['month'] <= 8),
(aqi_season['month'] >= 9) & (aqi_season['month'] <= 11),
(aqi_season['month'] >= 12) | (aqi_season['month'] <= 2),
], seasons, default='Unknown')
aqi_season = aqi_season[aqi_season['season'] != 'Unknown']
aqi_season
方差分析
| 0 |
2013-12-01 |
157.055556 |
2013 |
12 |
Winter |
| 1 |
2013-12-02 |
176.259843 |
2013 |
12 |
Winter |
| 2 |
2013-12-03 |
191.551181 |
2013 |
12 |
Winter |
| 3 |
2013-12-04 |
183.436508 |
2013 |
12 |
Winter |
| 4 |
2013-12-05 |
171.842520 |
2013 |
12 |
Winter |
| ... |
... |
... |
... |
... |
... |
| 1847 |
2018-12-23 |
69.236923 |
2018 |
12 |
Winter |
| 1848 |
2018-12-24 |
72.209231 |
2018 |
12 |
Winter |
| 1849 |
2018-12-25 |
78.160000 |
2018 |
12 |
Winter |
| 1850 |
2018-12-26 |
63.227692 |
2018 |
12 |
Winter |
| 1851 |
2018-12-27 |
59.526154 |
2018 |
12 |
Winter |
1852 rows × 5 columns
单因素方差分析
研究单个因素是否对空气质量产生了显著影响。
# 进行单因素方差分析
model_anova = ols('aqi ~ C(season)', data=aqi_season).fit()
sm.stats.anova_lm(model_anova, typ=2)
| C(season) |
310283.438289 |
3.0 |
249.91207 |
4.101987e-136 |
| Residual |
764807.389210 |
1848.0 |
NaN |
NaN |
# 可视化组间差异
plt.figure(figsize=(10, 6))
sns.boxplot(x='season', y='aqi', data=aqi_season,
order=seasons)
plt.xlabel('Season')
plt.ylabel('AQI')
plt.show()
双因素方差分析
# 双因素方差分析(包含交互效应)
model_two_way = ols('aqi ~ C(year) + C(season)', data=aqi_season).fit()
sm.stats.anova_lm(model_two_way, typ=2)
| C(year) |
176000.138836 |
5.0 |
110.178078 |
5.085237e-102 |
| C(season) |
228100.042092 |
3.0 |
237.988678 |
1.665362e-130 |
| Residual |
588807.250374 |
1843.0 |
NaN |
NaN |
总结
本章介绍了基础数据处理的核心技术:数据转换、缺失值处理、数据连接和基础数据分析。通过实际空气质量数据案例,演示了各类操作的Python实现方法。掌握这些基础技能是进行更复杂空间分析的前提。