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

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

高效利用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的结构将如下所示:

新CG儿
新CG儿

数字视觉分享平台 | AE模板_视频素材

新CG儿 412
查看详情 新CG儿
       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号