【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

引言

如果你正在阅读这篇文章,很可能你已经意识到,机器学习模型的性能不仅取决于所选算法,还高度依赖模型训练数据的质量和表现形式。

数据预处理和特征工程是机器学习工作流程中最关键的步骤之一。在 Python 生态中,Pandas 是进行这类数据操作的首选库。掌握几个实用的 Pandas 数据转换技巧,可以显著优化你的工作流程,使代码更清晰高效,并最终提升模型表现。

本教程将通过七个实际场景演示 Pandas 技巧,帮助你在数据准备和特征工程环节提高效率,为下一个机器学习项目打下良好基础。

准备数据

为了演示这些技巧,我们将使用经典的 Titanic 数据集。这个数据集包含数值型和类别型数据,同时存在缺失值,这些都是现实机器学习任务中常见的挑战。

我们可以直接从 URL 将数据集加载到 Pandas DataFrame 中:

import pandas as pd
import numpy as np

# 从 URL 加载 Titanic 数据集
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)

# 输出数据集形状及前 5 行
print("Dataset shape:", df.shape)
print(df.head())

输出示例:

Dataset shape: (891, 12)
   PassengerId  Survived  Pclass                                               Name     Sex   Age  SibSp  Parch            Ticket     Fare Cabin Embarked
0            1         0       3                            Braund, Mr. Owen Harris    male  22.0      1      0         A/5 21171   7.2500   NaN        S
1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1      0          PC 17599  71.2833   C85        C
2            3         1       3                             Heikkinen, Miss. Laina  female  26.0      0      0  STON/O2. 3101282   7.9250   NaN        S
3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1      0            113803  53.1000  C123        S
4            5         0       3                           Allen, Mr. William Henry    male  35.0      0      0            373450   8.0500   NaN        S

我们得到一个包含列如 Survived(目标变量)、Pclass(乘客等级)、Sex、Age 等的 DataFrame。

下面将介绍使用 Pandas 的实用技巧。

1. 使用 query() 实现更清晰的数据过滤

数据过滤是一个永无止境的任务,无论是为了创建训练子集,还是探索特定数据片段。使用布尔索引进行标准过滤在条件较多时容易变得冗长且难以阅读。query() 方法提供了更直观、更易读的替代方案,可通过字符串表达式进行过滤。

标准过滤

# 过滤年龄大于 30 且属于一等舱且存活的乘客
filtered_df = df[(df['Pclass'] == 1) & (df['Age'] > 30) & (df['Survived'] == 1)]
print(filtered_df.head())

使用 query() 过滤

# 使用 query() 方法实现同样的过滤
query_df = df.query('Pclass == 1 and Age > 30 and Survived == 1')
print(query_df.head())

输出结果相同,但 query() 版本更清晰易读,尤其是条件增多时。

此外,query() 支持变量传入,使得过滤条件动态化更简单,例如:

age_threshold = 30
query_df = df.query('Pclass == 1 and Age > @age_threshold and Survived == 1')

这种方式可避免在多处修改代码时出现重复或错误。


2. 使用 cut() 对连续变量进行分箱

某些模型(如线性模型和决策树)在将连续变量离散化后可以更好地捕捉非线性关系。可以使用 pd.cut() 将数据分入自定义区间,例如将年龄分组:

bins = [0, 12, 18, 60, np.inf]
labels = ['Child', 'Teenager', 'Adult', 'Senior']

df['AgeGroup'] = pd.cut(df['Age'], bins=bins, labels=labels, right=False)
print(df['AgeGroup'].value_counts())

输出示例:

Adult       575
Child        68
Teenager     45
Senior       26

新的 AgeGroup 特征是一个强有力的类别变量,可供模型使用。

此外,pd.cut() 可以设置 right=False 或 right=True 来控制区间是否包含右端点,还可以通过 include_lowest=True 确保最小值包含在第一个区间。对连续变量的分箱还能生成哑变量,便于模型处理分类特征:

df = pd.get_dummies(df, columns=['AgeGroup'], prefix='AgeGroup')

3. 使用 .str 提取文本特征

文本列通常包含有价值的结构化信息。.str 访问器提供一系列适用于整个 Series 的字符串处理方法。比如,我们可以用正则表达式从 Name 列提取乘客称谓(如 ‘Mr.’、‘Miss.’、‘Dr.’):

df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
print(df['Title'].value_counts())

输出示例:

Mr        517
Miss      182
Mrs       125
Master     40
Dr          7
...

Title 特征在 Titanic 模型中常常是生存率的重要预测指标。
除此之外,.str 还能实现其他文本操作,如:

  • .str.lower() 或 .str.upper() 转换大小写
  • .str.strip() 去除前后空格
  • .str.contains('pattern') 检查是否匹配模式
  • .str.replace('old','new') 替换文本

这些方法在处理姓名、票号、船舱号等文本字段时非常实用。


4. 使用 transform() 进行高级填充

直接删除缺失值会导致数据损失。更好的策略是分组填充。例如,按 Pclass 填充 Age 缺失值:

median_age_by_pclass = df.groupby('Pclass')['Age'].transform('median')
df['Age'].fillna(median_age_by_pclass, inplace=True)
print("Missing Age values after imputation:", df['Age'].isnull().sum())

输出:

Missing Age values after imputation: 0

这种基于分组的填充通常比全局平均值更准确。

另外,transform() 也可配合 lambda 函数 或自定义函数实现更灵活的填充策略,例如按性别和舱位组合计算中位数:

df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].transform(lambda x: x.fillna(x.median()))

此方法可保持分组内部的特征一致性,尤其在不同类别之间年龄差异明显时非常有效。


5. 使用方法链和 pipe() 简化流程

机器学习预处理通常涉及多步操作。链式调用可以提高代码可读性,并避免产生不必要的中间 DataFrame。pipe() 方法进一步允许将自定义函数集成到链中。

# 自定义函数删除列
def drop_cols(df, cols_to_drop):
    return df.drop(columns=cols_to_drop)

# 自定义函数编码 Sex 列
def encode_sex(df):
    df['Sex'] = df['Sex'].map({'male': 0, 'female': 1})
    return df

# 创建链式管道
processed_df = (df.copy()
                  .pipe(drop_cols, cols_to_drop=['Ticket', 'Cabin', 'Name'])
                  .pipe(encode_sex)
               )

print(processed_df.head())

输出示例:

   PassengerId  Survived  Pclass  Sex   Age  SibSp  Parch     Fare Embarked AgeGroup Title
0            1         0       3    0  22.0      1      0   7.2500        S    Adult    Mr
1            2         1       1    1  38.0      1      0  71.2833        C    Adult   Mrs
2            3         1       3    1  26.0      0      0   7.9250        S    Adult  Miss
3            4         1       1    1  35.0      1      0  53.1000        S    Adult   Mrs
4            5         0       3    0  35.0      0      0   8.0500        S    Adult    Mr

使用 pipe() 可以轻松将任意自定义处理函数加入链条中,使得整个数据处理流程更模块化、可读性更强,也便于团队协作与代码复用。

此外,方法链还可结合 assign()、dropna()、sort_values() 等 Pandas 方法,实现一步到位的数据清洗、特征生成和排序操作。

例如:

processed_df = (df.copy()
                  .assign(Fare_scaled = lambda x: x['Fare']/x['Fare'].max())
                  .pipe(drop_cols, cols_to_drop=['Ticket', 'Cabin', 'Name'])
                  .pipe(encode_sex)
                  .sort_values(by='Age', ascending=True)
               )

这样就可以在一条链中完成标准化、删除列、编码和排序操作,保持代码整洁高效。


6. 使用 map() 高效映射有序类别

对于名义型类别,通常使用独热编码(one-hot encoding);但对于有序类别(ordinal data),更适合将其映射为整数。使用字典和 map() 方法是快速而明确的方式。比如,我们假设 Embarked 有顺序关系:S > C > Q。

# 假设 Embarked 顺序为 S > C > Q
embarked_mapping = {'S': 2, 'C': 1, 'Q': 0}
df['Embarked_mapped'] = df['Embarked'].map(embarked_mapping)

print(df[['Embarked', 'Embarked_mapped']].head())

输出示例:

  Embarked  Embarked_mapped
0        S              2.0
1        C              1.0
2        S              2.0
3        S              2.0
4        S              2.0

这种方法快速、明确,便于模型学习类别间的顺序关系。
此外,map() 也可结合 fillna() 实现对缺失值的补充:

df['Embarked_mapped'] = df['Embarked'].map(embarked_mapping).fillna(-1)

7. 使用 astype() 优化内存

在处理大型数据集时,内存占用可能成为瓶颈。Pandas 默认使用较大数据类型(如 int64 和 float64),但通常可以使用较小类型而不丢失信息。将 object 类型转换为 category 是一种常用方法。

# 查看原始内存占用
print("Original memory usage:")
print(df.info(memory_usage='deep'))

# 优化数据类型
df_optimized = df.copy()
df_optimized['Pclass'] = df_optimized['Pclass'].astype('int8')
df_optimized['Sex'] = df_optimized['Sex'].astype('category')
df_optimized['Age'] = df_optimized['Age'].astype('float32')
df_optimized['Embarked'] = df_optimized['Embarked'].astype('category')

# 查看优化后内存占用
print("\nOptimized memory usage:")
print(df_optimized.info(memory_usage='deep'))

优化效果显著,可以在大数据集上训练模型而不易崩溃。
另外,astype() 也可以用于批量优化类型,例如:

categorical_cols = ['Sex', 'Embarked', 'AgeGroup', 'Title']
for col in categorical_cols:
    df_optimized[col] = df_optimized[col].astype('category')

这种批量处理在列较多时非常高效。


总结

机器学习的成功始于高质量、充分准备的数据。尽管算法复杂度、超参数调优以及模型构建常常成为关注焦点,但高效的数据处理才是发挥模型真正威力的关键。

本文介绍的七个 Pandas 技巧不仅是编程捷径,更是清洗数据、工程特征、构建稳健、可复现模型的强大策略。通过熟练掌握这些方法,你的机器学习工作流将更加高效、可维护,并有助于提升模型表现。

额外扩展建议:

  • 将上述技巧与 scikit-learn 的 Pipeline 相结合,实现端到端自动化数据预处理。
  • 对缺失值、异常值和类别分布进行可视化分析,提前发现数据问题。
  • 在大数据环境下,可考虑 Dask 或 Modin 等库,实现分布式、高效的 Pandas 操作。
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐