为何Pandas中apply会破坏category类型而for循环不会?最优写法探讨
Pandas中整行apply破坏Category类型的原因及解决方案
问题复现
初始创建带有Category类型列的DataFrame:
import pandas as pd df = pd.DataFrame({"A": ['Apple','Strawberry','Cherry'],"B":['yes','yes','no'],"C":['green','red','red']}) df.B = df.B.astype("category") df_cpy = df.copy()
- for循环修改(保留Category类型)
仅修改C列,未改动的B列仍保持Category类型:
for i, row in df.iterrows(): if row["C"] == 'green': df.at[i, 'C'] = 'yellow'
- 整行apply修改(破坏Category类型)
处理整行后,原本的Category列B会变为object类型:
def changer(row): if row["C"] == 'green': row["C"] = 'yellow' return row df_cpy = df_cpy.apply(changer, axis='columns')
- 针对单列的apply(保留Category类型)
仅对C列使用apply,B列的Category类型不受影响:
df["C"] = df.apply(lambda x: 'yellow' if x["C"] == 'green' else x["C"], axis=1)
核心原因
- for循环+at修改:属于原地修改,仅对目标列C的指定行进行值替换,其他列(如B)完全未被触碰,因此其Category类型保持不变。
- 整行apply:
apply(axis='columns')会逐行将DataFrame的行转换为Series进行处理。当你从DataFrame中取出一行时,该行的Category列元素是普通标量值(而非带有Category类型标记的对象),处理后返回的Series中,B列的元素已经是普通字符串。当pandas将所有处理后的行Series拼接成新DataFrame时,会自动推断列类型,此时B列会被识别为object类型,丢失原有的Category信息。 - 单列apply:仅针对C列进行操作,直接替换C列的值,其他列完全未被操作,因此B列的Category类型得以保留。
多列修改时保留Category类型的最优写法
优先使用向量化操作,这类操作不仅效率远高于循环或整行apply,还能完美保留原有列的类型:
1. 原地批量修改(loc)
适合需要直接修改原DataFrame的场景,可同时修改多列:
# 示例:当C列为green时,修改C为yellow,同时修改A为'Yellow Apple' df.loc[df['C'] == 'green', ['C', 'A']] = ['yellow', 'Yellow Apple']
2. 创建新DataFrame(assign)
适合不希望修改原DataFrame的场景,链式调用更清晰:
import numpy as np df_new = df.assign( C=np.where(df['C'] == 'green', 'yellow', df['C']), A=np.where(df['C'] == 'green', 'Yellow Apple', df['A']) )
3. 针对多列的自定义函数(复杂逻辑场景)
如果修改逻辑复杂,无法用简单的向量化操作实现,可针对列单独使用apply,避免整行处理:
def modify_c(c_val): return 'yellow' if c_val == 'green' else c_val def modify_a(a_val, c_val): return 'Yellow Apple' if c_val == 'green' else a_val df['C'] = df['C'].apply(modify_c) df['A'] = df.apply(lambda x: modify_a(x['A'], x['C']), axis=1)
这种方式仅修改目标列,其他列(如B)的Category类型不受影响。
内容的提问来源于stack exchange,提问作者teq508
相关产品推荐
相关产品推荐

