导入数据:
import numpy as np
import pandas as pd
import datetime
df=pd.read_excel('/Users/dxn/Desktop/发票2020.xlsx',
header=0,dtype={'发票号码':'str'},index_col=[0,1])
>>> df
日期 供货单位 发票号码 价税合计
0 一月 a 13599232 6450
4 二月 a 13599232 4800
2 三月 a 13599232 4800
11 四月 d 00192513-16 380000
5 五月 e 48693766 9500
7 六月 f 43000223 6400
1 七月 g 04922798 258
6 八月 g 04922798 280
3 九月 bb 04922798 406
10 十月 bb 04922798 36
8 十一月 bb 04922798 48
9 十二月 hh 17620827 600
现在要求,把数据中价税合计小于4000的全部修改为1000。
for idx,row in df.iterrows():
if row['价税合计']<4000:
df.iloc[idx]['价税合计']=1000
Warning (from warnings module):
File "/Users/dxn/Desktop/遍历修改df数据.py", line 16
df.iloc[idx]['价税合计']=1000
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
那么,正确的做法是什么?
我们需要明白以下几个概念。
一、我们要修改目标数据,首先我们要选中目标数据,然后对其进行操作以完成任务。修改数据即赋值,就是重新设置某些变量值的操作,又称为set;返回某些值的操作即访问,又称为get。任何引用数据子集的访问或赋值方法,称为索引;连续使用多个索引的操作,称为链式索引,这意味着对get或set进行多次调用以完成单个操作。
二、pandas中的'view'与'copy'。在pandas中,某些操作有时会返回数据的视图,也就是上面所说的view,有时会返回数据的副本,也就是copy。view是原始数据的子集,copy则是在原始数据的基础上创建的一个新的对象。那么,如果在并不能保证所修改的是view还是copy的时候,我们的操作就会返回不能预见的结果(writing to it will have no effect)。
具体到本例来说:
df.iloc[idx]['价税合计']=1000,这一句里第一个索引是为了get,获取一个DataFrame,其中包含索引为idx的所有行内容;第二个索引是为了set,是在第一个索引下返回的新DataFrame上设置变量值:我们并没有修改到原始的df里的值。
对此问题的解决方法,是用.loc方法将两次索引操作组合成一步操作,确保set操作的是原始的DataFrame,实现修改之所需要修改的数据。
for idx,row in df.iterrows():
if row['价税合计']<4000:
df.loc[df['价税合计']==row['价税合计'],'价税合计']=1000
>>> df
日期 供货单位 发票号码 价税合计
0 一月 a 13599232 6450
1 七月 g 04922798 1000
2 三月 a 13599232 4800
3 九月 bb 04922798 1000
4 二月 a 13599232 4800
5 五月 e 48693766 9500
6 八月 g 04922798 1000
7 六月 f 43000223 6400
8 十一月 bb 04922798 1000
9 十二月 hh 17620827 1000
10 十月 bb 04922798 1000
11 四月 d 00192513-16 380000
获得预期的结果,成功!!!
据大佬们说,以上做法其实并不pandas,没有真正get到pandas处理数据的精要所在。
看批量处理数据时可供备选方法的优先顺序,Pandas核心开发人员给的建议:
- 使用向量化操作:不用for遍历的Pandas方法和函数。
- 使用.apply()方法。
- 使用.itertuples():将DataFrame行作为nametuple类从Python的collections模块中进行迭代。
- 使用.iterrows():将DataFrame行作为(index,pd.Series)元组数组进行迭代。虽然Pandas的Series是一种灵活的数据结构,但将每一行生成一个Series并且访问它,仍然是一个比较大的开销。
- 对逐个元素进行循环,使用df.loc或者df.iloc对每个单元格或者行进行处理。
不用循环的两种方法:
- .loc[]方法
df.loc[df['价税合计']<4000,'价税合计']=1000
#把某一列中符合条件的记录查找出来并修改本列或其他列的数据
#选取(get)目标数据,然后修改(set),确保修改之所需要修改的数据
- apply方法
df['价税合计']=df['价税合计'].apply(lambda x:x if x>4000 else 1000)
pandas擅长向量式的批量处理数据,并不优先选用遍历循环方式实现数据的处理,这是与讲究“遍历在手天下我有”的VBA不同的地方。有没有一种拨云见日的清爽?