高效利用Pandas与NumPy根据键值条件映射DataFrame多列数据

花韻仙語
发布: 2025-11-26 14:40:17
原创
652人浏览过

高效利用Pandas与NumPy根据键值条件映射DataFrame多列数据

本教程探讨了如何高效地根据dataframe中“键”列的值,有条件地映射和修改多列数据。针对重复使用`numpy.select`的低效性,文章提供了两种优化的矢量化解决方案:一是利用`pandas.get_dummies`创建布尔掩码并结合`dataframe.mask`进行批量替换;二是采用数据重塑(`melt`、`merge`、`unstack`)的方法实现灵活的数据过滤与填充,旨在提升数据处理性能和代码可读性

在数据分析和处理中,我们经常需要根据某一“键”列的值,有选择性地更新或保留DataFrame中其他多列的数据。例如,如果key列的值是'key1',我们可能只关心colA和colD的值,而其他列则应被标记为'NA'。传统上,这可能通过为每个目标列单独调用numpy.select来实现,但这在处理大量列时效率低下且代码冗余。本教程将介绍两种更高效、更具Pythonic风格的矢量化方法来解决这一问题。

问题场景概述

假设我们有一个DataFrame,其中包含一个key列和若干数据列(如colA到colD)。我们的目标是:

  • 对于每一行,如果key列的值与特定条件匹配,则保留某些指定列的原始值。
  • 如果key列的值不匹配,则将这些列的值设置为'NA'(或任何其他默认值)。
  • 一个key值可能对应多个需要保留的列。

以下是原始的低效实现示例:

import pandas as pd
import numpy as np

# 创建示例DataFrame
data = {
    'key': ['key1', 'key2', 'key3', 'key1', 'key2'],
    'colA': ['value1A', 'value2A', 'value3A', 'value4A', 'value5A'],
    'colB': ['value1B', 'value2B', 'value3B', 'value4B', 'value5B'],
    'colC': ['value1C', 'value2C', 'value3C', 'value4C', 'value5C'],
    'colD': ['value1D', 'value2D', 'value3D', 'value4D', 'value5D']
}
df = pd.DataFrame(data)

# 低效的重复调用 numpy.select
df['colA'] = np.select([df['key'] == 'key1'], [df['colA']], default='NA')
df['colD'] = np.select([df['key'] == 'key1'], [df['colD']], default='NA')
df['colB'] = np.select([df['key'] == 'key2'], [df['colB']], default='NA')
df['colC'] = np.select([df['key'] == 'key3'], [df['colC']], default='NA')

print("原始DataFrame和低效处理结果:")
print(df)
登录后复制

这种方法的问题在于,每当需要处理一个新列或新的key-column映射时,都需要添加一行新的np.select代码,这在列数很多时难以维护且效率低下。

解决方案一:利用 get_dummies 和 mask 创建布尔掩码

此方法的核心思想是首先构建一个布尔掩码,该掩码指示了DataFrame中每个单元格是否应该保留其原始值。然后,使用DataFrame.mask方法根据此掩码批量替换不符合条件的值。

1. 定义键与列的映射关系

首先,我们需要一个字典来明确每个key值对应哪些列应该被保留。

d = {'key1': ['colA', 'colD'],
     'key2': ['colB'],
     'key3': ['colC']}
登录后复制

2. 生成布尔掩码

接下来,我们将这个字典转换为一个布尔DataFrame,其中行索引是key值,列是数据列名。True表示该key值对应的行,该列应保留数据;False则表示应替换为'NA'。

# 将字典转换为Series并展开
s = pd.Series(d).explode()
# 使用get_dummies创建布尔矩阵,指示每个key对应哪些列
mask_df = pd.get_dummies(s, dtype=bool).groupby(level=0).max()
登录后复制

mask_df的结构将如下所示:

GitHub Copilot
GitHub Copilot

GitHub AI编程工具,实时编程建议

GitHub Copilot 387
查看详情 GitHub Copilot
       colA   colB   colC   colD
key1   True  False  False   True
key2  False   True  False  False
key3  False  False   True  False
登录后复制

3. 应用掩码到DataFrame

有了mask_df,我们可以将其重新索引到原始DataFrame的key列,生成一个与原始DataFrame数据部分形状相同的布尔数组。然后,使用DataFrame.mask方法,它会根据布尔条件替换值为True的位置上的数据(注意:mask方法默认替换True,where方法默认替换False)。为了达到我们的目的,即替换不符合条件(False)的值,我们可以直接使用where方法,或者对mask_df取反后使用mask。这里我们直接使用where方法,它在条件为True时保留原始值,条件为False时替换为指定值。

# 筛选出需要处理的数据列
cols_to_process = df.columns.difference(['key'])

# 根据df['key']重新索引mask_df,生成与df数据部分形状一致的布尔数组
# .to_numpy() 转换为NumPy数组以提高性能
aligned_mask = mask_df.reindex(df['key']).to_numpy()

# 使用where方法进行条件替换
df[cols_to_process] = df[cols_to_process].where(aligned_mask, 'NA')
登录后复制

完整代码示例:

import pandas as pd
import numpy as np

data = {
    'key': ['key1', 'key2', 'key3', 'key1', 'key2'],
    'colA': ['value1A', 'value2A', 'value3A', 'value4A', 'value5A'],
    'colB': ['value1B', 'value2B', 'value3B', 'value4B', 'value5B'],
    'colC': ['value1C', 'value2C', 'value3C', 'value4C', 'value5C'],
    'colD': ['value1D', 'value2D', 'value3D', 'value4D', 'value5D']
}
df = pd.DataFrame(data)

d = {'key1': ['colA', 'colD'],
     'key2': ['colB'],
     'key3': ['colC']}

# 1. 创建键与列的映射Series
s = pd.Series(d).explode()

# 2. 生成布尔掩码DataFrame
# get_dummies将s转换为one-hot编码形式的DataFrame
# groupby(level=0).max() 合并相同key的行,确保所有对应列都为True
mask_df = pd.get_dummies(s, dtype=bool).groupby(level=0).max()

# 3. 筛选出需要处理的数据列
cols_to_process = df.columns.difference(['key'])

# 4. 根据df['key']对mask_df进行reindex,使其与原始DataFrame的行对齐
# to_numpy() 转换为NumPy数组,提高后续操作效率
aligned_mask = mask_df.reindex(df['key']).to_numpy()

# 5. 使用where方法进行条件替换:
# 当aligned_mask为True时,保留df[cols_to_process]的原始值
# 当aligned_mask为False时,替换为'NA'
df[cols_to_process] = df[cols_to_process].where(aligned_mask, 'NA')

print("\n解决方案一结果:")
print(df)
登录后复制

解决方案二:利用数据重塑(melt, merge, unstack)

第二种方法通过将数据从宽格式(wide format)转换为长格式(long format),进行过滤,然后再转换回宽格式来实现。这种方法在处理更复杂的数据过滤和聚合场景时非常强大。

1. 定义键与列的映射关系

与方法一相同,我们首先定义映射字典:

d = {'key1': ['colA', 'colD'],
     'key2': ['colB'],
     'key3': ['colC']}
登录后复制

2. 数据重塑为长格式并合并过滤

  • melt: 将原始DataFrame的数据列转换为行,创建variable(列名)和value列。同时保留原始索引和key列。
  • 创建映射DataFrame: 将映射字典d也转换为长格式,包含key和variable。
  • merge: 将熔化后的原始数据与映射DataFrame合并。只有当原始数据的key和variable(列名)组合在映射字典中存在时,数据才会被保留。
  • set_index: 设置新的索引,为后续的unstack做准备。
# 1. 准备映射数据
map_df = pd.Series(d).explode().rename_axis('key').reset_index(name='variable')

# 2. 熔化原始DataFrame,保留'index'和'key'作为id_vars
melted_df = df.reset_index().melt(['index', 'key'])

# 3. 将熔化后的数据与映射数据合并,实现过滤
# 只有在map_df中存在的(key, variable)组合才会被保留
filtered_df = melted_df.merge(map_df)

# 4. 设置索引并堆叠,将'value'列重新转换为宽格式
result_df = filtered_df.set_index(['index', 'key', 'variable'])['value'] \
                       .unstack('variable', fill_value='NA') \
                       .reset_index('key') \
                       .rename_axis(index=None, columns=None)
登录后复制

完整代码示例:

import pandas as pd
import numpy as np

data = {
    'key': ['key1', 'key2', 'key3', 'key1', 'key2'],
    'colA': ['value1A', 'value2A', 'value3A', 'value4A', 'value5A'],
    'colB': ['value1B', 'value2B', 'value3B', 'value4B', 'value5B'],
    'colC': ['value1C', 'value2C', 'value3C', 'value4C', 'value5C'],
    'colD': ['value1D', 'value2D', 'value3D', 'value4D', 'value5D']
}
df = pd.DataFrame(data)

d = {'key1': ['colA', 'colD'],
     'key2': ['colB'],
     'key3': ['colC']}

# 1. 将原始DataFrame的索引重置,并将其和'key'列作为标识符,将其他数据列“熔化”为长格式
# 'index'列用于后续重构原始DataFrame的顺序
melted_df = df.reset_index().melt(['index', 'key'])

# 2. 将映射字典d转换为一个DataFrame,其中包含'key'和'variable'(列名)
map_df = pd.Series(d).explode().rename_axis('key').reset_index(name='variable')

# 3. 将熔化后的数据与映射DataFrame合并
# 只有当melted_df中的(key, variable)组合在map_df中存在时,该行才会被保留
merged_df = melted_df.merge(map_df, on=['key', 'variable'])

# 4. 设置新的多级索引,然后使用unstack将'variable'列重新转换为列
# fill_value='NA'用于填充那些没有匹配到的单元格
# reset_index('key') 将key列从索引中移回普通列
# rename_axis(index=None, columns=None) 清理索引和列的名称,使其更美观
result_df = merged_df.set_index(['index', 'key', 'variable'])['value'] \
                     .unstack('variable', fill_value='NA') \
                     .reset_index('key') \
                     .rename_axis(index=None, columns=None)

# 5. 将处理后的数据合并回原始DataFrame,或者直接使用result_df
# 为了保持原始DataFrame的结构,这里可以将key列也考虑进去
final_df = df[['key']].merge(result_df, left_index=True, right_index=True, how='left')
# 确保列顺序与原始问题一致,并且没有重复的key列
final_df = final_df[['key'] + [col for col in df.columns if col not in ['key']]]

print("\n解决方案二结果:")
print(final_df)
登录后复制

注意:在实际应用中,如果只是需要最终结果,可以直接使用result_df。如果需要确保原始key列的位置和所有列的顺序与原始df完全一致,可能需要额外的列重排操作。上述代码中,为了保持与原始df的key列和列顺序一致,进行了一次merge和列重排。

总结与注意事项

这两种矢量化方法都比重复调用numpy.select更高效、更简洁,尤其是在处理大量列和复杂映射关系时。

  • 方法一(get_dummies + mask)
    • 优点:代码相对直观,直接构建布尔掩码进行条件替换。对于只需要根据条件替换现有DataFrame中值的情况,效率很高。
    • 适用场景:当你需要基于key列的值,有条件地保留或替换DataFrame中现有列的值时。
  • 方法二(melt + merge + unstack)
    • 优点:非常灵活,通过将数据重塑为长格式,可以更容易地进行过滤、聚合和更复杂的条件操作。
    • 适用场景:当你需要执行更复杂的数据转换,例如不仅是替换值,还可能涉及到根据key进行分组计算、聚合,或者从外部源合并数据来决定哪些值应该被保留时。它提供了一种更通用的数据操作范式。

在选择哪种方法时,可以根据具体需求和个人偏好来决定。通常,如果任务只是简单的条件替换,get_dummies和mask的组合可能更直接。如果数据操作涉及到更复杂的重组或与外部数据源的交互,melt/merge/unstack的管道会更具优势。无论选择哪种,都应优先考虑使用Pandas和NumPy提供的矢量化操作,以最大化数据处理的效率和可维护性。

以上就是高效利用Pandas与NumPy根据键值条件映射DataFrame多列数据的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号