数据处理与特征工程实战
在数据处理和分析领域,我们常常会遇到数据缺失、特征不平衡等问题,同时也需要对数据进行特征工程以更好地进行建模和分析。下面将详细介绍相关的处理方法和实际操作。
1. 趋势插补评估练习
在这个练习中,我们要评估不同插补趋势的质量,并且思考如何处理在多个特征上可能存在不平衡的数据,而不仅仅是单个特征。
1.1 替代趋势插补
以氪石示例来说,我们使用二阶多项式拟合输入激光频率来插补每平方米坎德拉的缺失值。其实,使用局部插值、前向填充或后向填充会更简单,大多数数据框库都能直接实现这些局部插补。
以下是一个数据集示例:
| price_buy | price_maintain | doors | passengers | trunk | s | rating |
| — | — | — | — | — | — | — |
| 4304 | 1.0 | 0.158397 | 2.158397 | 6.0 | 2.0000 | Very Good |
| 337 | 3.0 | 0.000000 | 3.000000 | 4.0 | 0.0000 | Unacceptable |
| 2360 | 2.0 | 2.000000 | 3.247795 | 4.0 | 2.0000 | Acceptable |
| 3352 | 0.0 | 1.000000 | 2.123895 | 4.0 | 2.0000 | Good |
| 2064 | 0.0 | 3.000000 | 4.000000 | 6.0 | 1.8577 | Acceptable |
| 4058 | 1.0 | 0.000000 | 3.075283 | 6.0 | 2.0000 | Very Good |
该数据集可从 https://www.gnosis.cx/cleaning/excited-kryptonite.fwf 获取。
我们需要量化不同插补方法之间的差异,均方根偏差(RMSD)是一个很好的衡量样本差异的指标,在这个练习中我们就使用它。由于我们不知道缺失值的正确答案,所以只是评估各种方法之间的差异程度。具体需要测量和比较的差异如下:
- 所有点(原始点和插补点)与二阶多项式拟合函数本身之间的RMSD。
- 原始点与以下各项之间的RMSD:
- 对它们进行的线性回归。
- 二阶多项式拟合。
- 三阶多项式拟合。
- 其他你认为相关的回归(可能来自机器学习库)。
- 仅插补点在一阶、二阶和三阶多项式拟合以及仅基于相邻点的局部插值之间的RMSD。
- 仅插补点在不同阶数多项式拟合和简单前向填充之间的RMSD。
此外,还需要描述你认为击败讨厌的超人的最佳策略。
1.2 多特征平衡
人类身高/体重数据在身高数值上存在不平衡,体重也有类似的分布情况。带有一个奇特目标的该数据集版本可从 https://www.gnosis.cx/cleaning/height-weight-color.csv 获取。这个数据新增了一个名为 Favorite 的列,它大致是平衡的,是从 {red, green, blue} 集合中随机生成的。
以下是部分数据示例:
import pandas as pd
humcol = pd.read_csv('data/height-weight-color.csv')
humcol.sample(6, random_state=1)
| Height | Weight | Favorite | |
|---|---|---|---|
| 21492 | 176.958650 | 72.604585 | red |
| 9488 | 169.000221 | 79.559843 | blue |
| 16933 | 171.104306 | 71.125528 | red |
| 12604 | 174.481084 | 79.496237 | blue |
| 8222 | 171.275578 | 77.094118 | green |
| 9110 | 164.631065 | 70.557834 | green |
我们的任务是探索根据身高和体重预测最喜欢颜色的模型。根据假设,身高和体重可能对最喜欢的颜色有预测作用,同时我们还假设身体质量指数(BMI)也可能有预测作用,其计算公式为:$BMI=\frac{体重(kg)}{身高(m)^2}$。
具体操作步骤如下:
1. 创建一个包含合成样本的新数据集,使身高、体重和BMI各自的观测值数量相对均衡。假设身高、体重和BMI各自分为五类,例如“非常矮”、“矮”、“中等”、“高”、“非常高”等。
2. 一种简单的方法是复制现有行以增加其量化类别的代表性,你可以先尝试这种方法。
3. 还可以使用如SMOTE、ADASYN或ROSE等技术生成代表其身高、体重或BMI类别的新颖合成样本。在生成这些合成样本时,需要分配适当的最喜欢的颜色(复制行时很直接,但根据不同平衡要求创建新颖合成行时会更微妙)。
4. 由于类别不平衡比例约为100:1,但每个特征仅在五类之间进行平衡,每个特征的平衡操作会使数据集大小增加约4倍。需要考虑是否需要链式相乘来生成约为原始大小64倍的数据集,实际上可以独立按特征进行平衡,将扩展限制为约原始大小的12倍。
5. 进行过采样,最多到300,000行数据通常是可行的。如果初始观测值超过25,000个,乘法扩展可能会有问题。假设初始过采样产生约300,000行数据,将这些大部分为合成样本的数据下采样到100,000行,同时保持大致的类别平衡(不要求精确平衡,目标是每类行数差异小于25%)。
6. 最后,如果有能力的话,尝试创建一个关于身高、体重、BMI和目标最喜欢颜色之间关系的实际模型。思考模型的预测效果如何、做出了什么预测、哪些人更喜欢哪种颜色以及偏好的强度如何,还有哪个特征的预测能力最强。
以下是体重的分布情况:
pd.cut(humcol.Weight, 5).value_counts().sort_index()
| 区间 | 数量 |
|---|---|
| (44.692, 55.402] | 125 |
| (55.402, 66.06] | 3708 |
| (66.06, 76.717] | 14074 |
| (76.717, 87.375] | 6700 |
| (87.375, 98.033] | 393 |
2. 特征工程
特征工程主要关注基于原始数据集创建合成特征,与插补不同,它是关于改变数据的表示形式,且通常是确定性的和信息保留的(例如可逆)。以下是一些常见的特征工程方法:
2.1 数据类型转换
- 对于日期时间,我们可以将其数字或字符串表示转换为原生表示,这样能使许多操作更方便。
- 对于字符串,我们可以生成规范表示并将其视为类别(也称为因子),而且一个字符串通常包含多个有意义但独立的信息片段,将它们作为单独的变量处理会更有用。
- 对于数值,有时将它们转换为不同的范围,进而转换为有序值,有助于揭示被过多精度掩盖的模式。不过,量化不是可逆转换,良好的实践仍然建议对数据进行版本控制并编写转换脚本以确保可重复性。
2.2 系统转换
- 独热编码是一种简单的转换,它将单个分类特征转换为多个数字字段,这通常是特定统计或建模技术所需要的。
- 多项式特征是合成特征,它以一种通常能揭示单变量特征中看不到的有意义交互的方式组合多个原始特征。
- 分解是一种完全系统的转换,主成分分析(PCA)等技术以信息保留的方式转换整个参数空间。这种转换本身不会增加或减少任何信息,但通常会与降维相结合,这样可以从这些转换维度的子集中获取大部分信息。根据你的目的,这种转换可能会使模型更易于处理和/或质量更高。
3. 日期/时间字段处理
在处理日期/时间字段时,我们会遇到一些常见的问题,下面以温度读数数据为例进行说明。
3.1 数据读取与处理
温度数据由多个文件组成,每个文件包含一个不同自动温度计的测量值,通常每三分钟测量一次。以下是读取数据的代码:
temps = pd.read_csv('data/glarp/outside.gz',
sep=' ', header=None,
names=['year', 'month', 'day',
'hour', 'minute', 'degrees'])
temps.head(5)
| Year | month | day | hour | minute | degrees |
|---|---|---|---|---|---|
| 2003 | 7 | 25 | 16 | 4 | 27.5 |
| 2003 | 7 | 25 | 16 | 7 | 27.3 |
| 2003 | 7 | 25 | 16 | 10 | 27.3 |
| 2003 | 7 | 25 | 16 | 13 | 27.4 |
| 2003 | 7 | 25 | 16 | 16 | 27.8 |
3.2 创建日期时间索引
为了让数据更便于处理,我们将其转换为日期时间索引:
ts_fields = ['year', 'month', 'day', 'hour', 'minute']
temps.index = pd.to_datetime(temps[ts_fields])
temps.drop(columns=ts_fields, inplace=True)
temps
| degrees | |
|---|---|
| 2003-07-25 16:04:00 | 27.5 |
| 2003-07-25 16:07:00 | 27.3 |
| 2003-07-25 16:10:00 | 27.3 |
| … | … |
| 2004-07-16 15:28:00 | 16.4 |
可以看到,数据虽然表面上看起来是按时间序列排列的,但实际上可能并非如此。我们可以检查索引是否单调递增:
temps.index.is_monotonic_increasing
结果为 False ,说明索引不是单调的。我们可以查看连续行之间的时间差:
increments = temps.index.to_series().diff()
increments[increments < pd.Timedelta(minutes=0)]
发现有一个向后跳跃,这可能与夏令时调整有关。在进一步处理之前,我们需要对数据按日期时间索引进行排序:
temps.sort_index(inplace=True)
temps.index.is_monotonic_increasing
排序后,索引变为单调递增,结果为 True 。
3.3 施加规律性
我们知道,数据中可能存在缺失的时间戳,我们需要验证这些间隙是否存在,并进行修复以产生更规则的时间序列。
increments = temps.index.to_series().diff()
gaps = increments[increments > pd.Timedelta(minutes=3)]
gaps
结果显示存在一些间隙,大多数间隙是丢失了一次测量(即六分钟而不是预期的三分钟),但也有一些较大的间隙,最长的超过一天。
我们还可以查看测量间隔过短的情况:
small_steps = increments[increments < pd.Timedelta(minutes=3)]
small_steps.sort_values(ascending=False)
发现时间戳中的小间隙数量只有22个,其中除了一个是两分钟的间隙外,其余都是零时间差,即重复的日期时间值。我们可以根据领域判断,决定一分钟的差异对数据分析或建模是否重要。
3.4 重复时间戳处理
在时间序列数据中,重复时间戳是一个常见的问题。在这个数据集中,只有41行存在重复时间戳问题。以下是查看重复行的代码:
temps[temps.index.duplicated(keep=False)]
大多数重复行的值差异较小,但在 2003-12-24 15:10:00 出现了一个特殊情况,有三个不同的值记录,其中一个明显是异常值,根据我们对科罗拉多州12月室外温度的领域知识和数据模式,我们可能会丢弃这个异常值。我们可以使用Pandas的 .drop_duplicates() 方法来处理重复行,但需要根据具体情况选择保留第一行、最后一行或丢弃所有有歧义的行。
综上所述,在数据处理和分析过程中,我们需要综合运用趋势插补、特征工程和日期/时间字段处理等方法,以解决数据缺失、特征不平衡和时间序列不规则等问题,从而为后续的建模和分析提供高质量的数据。
下面是一个简单的流程图,展示了日期/时间字段处理的主要步骤:
graph TD;
A[读取数据] --> B[创建日期时间索引];
B --> C[检查索引单调性];
C -->|非单调| D[排序数据];
C -->|单调| E[检查时间间隙];
D --> E;
E --> F[处理间隙和重复时间戳];
通过以上的操作和分析,我们可以更好地处理和利用数据,为数据科学和数据分析工作打下坚实的基础。
数据处理与特征工程实战
4. 处理重复时间戳的进一步探讨
在处理重复时间戳时,除了使用 .drop_duplicates() 方法,我们还可以根据具体业务需求采用其他策略。例如,如果我们认为重复时间戳的多个测量值都有参考价值,可以对这些值进行聚合操作,如求平均值、中位数等。以下是求重复时间戳对应温度平均值的代码示例:
duplicated_temps = temps[temps.index.duplicated(keep=False)]
grouped = duplicated_temps.groupby(duplicated_temps.index)
avg_duplicated_temps = grouped.mean()
avg_duplicated_temps
这样,我们就得到了重复时间戳对应的平均温度值,在某些情况下,这可以更全面地反映数据的特征。
5. 特征工程的实际应用
在前面我们介绍了特征工程的一些常见方法,下面我们通过一个具体的例子来展示如何应用这些方法。假设我们有一个包含客户信息的数据集,其中有年龄、收入、职业等特征,我们希望通过特征工程来提高对客户购买意愿的预测准确性。
5.1 独热编码的应用
如果职业是一个分类特征,我们可以使用独热编码将其转换为多个数值字段。以下是使用 pandas 进行独热编码的代码:
import pandas as pd
# 假设 data 是包含客户信息的 DataFrame
data = pd.DataFrame({
'age': [25, 30, 35],
'income': [50000, 60000, 70000],
'occupation': ['Engineer', 'Doctor', 'Teacher']
})
encoded_data = pd.get_dummies(data, columns=['occupation'])
encoded_data
通过独热编码,我们将职业特征转换为了多个二进制特征,这样可以更好地用于机器学习模型的训练。
5.2 多项式特征的应用
我们可以使用 sklearn 库来创建多项式特征。假设我们希望考虑年龄和收入之间的交互作用,以下是创建多项式特征的代码:
from sklearn.preprocessing import PolynomialFeatures
import numpy as np
# 提取需要创建多项式特征的列
X = data[['age', 'income']].values
# 创建多项式特征对象,指定阶数为 2
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
# 将多项式特征添加到原始数据中
poly_columns = poly.get_feature_names_out(['age', 'income'])
poly_data = pd.DataFrame(X_poly, columns=poly_columns)
final_data = pd.concat([data, poly_data], axis=1)
final_data
通过创建多项式特征,我们可以捕捉到特征之间的交互作用,从而提高模型的预测能力。
6. 趋势插补与多特征平衡的综合应用
在实际应用中,我们可能需要同时进行趋势插补和多特征平衡。以下是一个简单的示例,假设我们有一个包含销售数据的时间序列,其中存在缺失值,并且不同产品的销售数据存在不平衡的情况。
6.1 趋势插补
我们可以使用二阶多项式拟合来插补销售数据中的缺失值。以下是插补的代码:
import numpy as np
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 假设 sales_data 是包含销售数据的 DataFrame,其中有缺失值
sales_data = pd.DataFrame({
'date': pd.date_range(start='2023-01-01', periods=10),
'sales': [10, np.nan, 12, 15, np.nan, 18, 20, np.nan, 22, 25]
})
# 提取非缺失值的索引和对应的销售数据
non_missing_index = sales_data['sales'].dropna().index
non_missing_sales = sales_data.loc[non_missing_index, 'sales']
non_missing_dates = sales_data.loc[non_missing_index, 'date'].map(pd.Timestamp.timestamp)
# 创建多项式特征对象,指定阶数为 2
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(non_missing_dates.values.reshape(-1, 1))
# 训练线性回归模型
model = LinearRegression()
model.fit(X_poly, non_missing_sales)
# 提取缺失值的索引和对应的日期
missing_index = sales_data['sales'].isna()
missing_dates = sales_data.loc[missing_index, 'date'].map(pd.Timestamp.timestamp)
X_missing_poly = poly.transform(missing_dates.values.reshape(-1, 1))
# 预测缺失值
imputed_sales = model.predict(X_missing_poly)
# 将预测值填充到原始数据中
sales_data.loc[missing_index, 'sales'] = imputed_sales
sales_data
通过二阶多项式拟合,我们成功地插补了销售数据中的缺失值。
6.2 多特征平衡
假设销售数据中不同产品的销售数量存在不平衡的情况,我们可以使用 SMOTE 技术来进行平衡。以下是使用 imblearn 库进行 SMOTE 过采样的代码:
from imblearn.over_sampling import SMOTE
from sklearn.datasets import make_classification
import pandas as pd
# 假设 product_data 是包含不同产品销售数据的 DataFrame
product_data = pd.DataFrame({
'product': ['A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C'],
'sales': [10, 12, 20, 22, 25, 30, 32, 35, 38]
})
# 将产品类别转换为数值编码
product_data['product_encoded'] = pd.factorize(product_data['product'])[0]
# 提取特征和标签
X = product_data[['sales']]
y = product_data['product_encoded']
# 创建 SMOTE 对象
smote = SMOTE()
X_resampled, y_resampled = smote.fit_resample(X, y)
# 将平衡后的数据转换为 DataFrame
resampled_data = pd.DataFrame(X_resampled, columns=['sales'])
resampled_data['product_encoded'] = y_resampled
resampled_data
通过 SMOTE 过采样,我们成功地平衡了不同产品的销售数量,使得数据更适合用于机器学习模型的训练。
7. 总结与展望
在数据处理和分析的过程中,趋势插补、特征工程和日期/时间字段处理等方法是非常重要的。通过趋势插补,我们可以处理数据中的缺失值;通过特征工程,我们可以创建更有意义的特征,提高模型的预测能力;通过日期/时间字段处理,我们可以解决时间序列数据中的不规则问题。
在未来的工作中,我们可以进一步探索这些方法的应用,例如结合深度学习模型进行更复杂的特征工程,或者使用更先进的插补算法来处理缺失值。同时,我们也需要不断关注数据的质量和特征的选择,以确保我们的模型能够准确地反映数据的本质特征。
下面是一个流程图,展示了数据处理和分析的整体流程:
graph TD;
A[数据读取] --> B[趋势插补];
B --> C[特征工程];
C --> D[日期/时间字段处理];
D --> E[数据平衡];
E --> F[模型训练与评估];
通过综合运用这些方法,我们可以更好地处理和分析数据,为实际问题提供更有效的解决方案。希望本文能够对你在数据处理和分析方面有所帮助。
超级会员免费看
10万+

被折叠的 条评论
为什么被折叠?



