在迭代行时更新pandas数据框
我有一个很大的 pandas 数据框,长得像这样:
date exer exp ifor mat
1092 2014-03-17 American M 528.205 2014-04-19
1093 2014-03-17 American M 528.205 2014-04-19
1094 2014-03-17 American M 528.205 2014-04-19
1095 2014-03-17 American M 528.205 2014-04-19
1096 2014-03-17 American M 528.205 2014-05-17
现在我想逐行遍历这个数据框。在查看每一行的时候,ifor
的值可能会根据一些条件发生变化,这时我需要查找另一个数据框。
那么,在我遍历的时候,怎么才能更新这个值呢?我试过几种方法,但都没有成功。
for i, row in df.iterrows():
if <something>:
row['ifor'] = x
else:
row['ifor'] = y
df.ix[i]['ifor'] = x
这些方法似乎都不管用。我没有看到数据框里的值被更新。
9 个回答
你应该用 df.ix[i, 'exp']=X
或者 df.loc[i, 'exp']=X
来给值赋值,而不是用 df.ix[i]['ifor'] = x
。
否则你是在操作一个视图,这样会收到一个警告:
-c:1: SettingWithCopyWarning: 你试图在一个DataFrame的切片副本上设置值。
请尝试使用 .loc[行索引, 列索引] = 值 来代替
而且,正如 @Phillip Cloud 所建议的,循环最好用一些向量化的算法来替代,这样可以更好地利用 DataFrame
。
使用 df.apply()
来调用 lambda
函数会更好 -
df["ifor"] = df.apply(lambda x: {value} if {condition} else x["ifor"], axis=1)
你可以使用一个叫做 itertuples()
的方法,它可以让你逐行遍历数据表(DataFrame),每一行会以命名元组的形式出现,元组的第一个元素是行的索引值。这个方法比 iterrows()
快得多。使用 itertuples()
时,每一行的数据都会包含它在数据表中的索引,你还可以用 loc
来设置值。
for row in df.itertuples():
if <something>:
df.at[row.Index, 'ifor'] = x
else:
df.at[row.Index, 'ifor'] = x
df.loc[row.Index, 'ifor'] = x
在大多数情况下,itertuples()
的速度比 iat
或 at
要快。
感谢 @SantiStSupery, 使用 .at
的速度比 loc
快得多。
Pandas的DataFrame对象可以理解为一系列的Series。换句话说,你可以把它看作是由多列组成的。这样理解很重要,因为当你使用pd.DataFrame.iterrows
时,你是在逐行遍历这些数据,而每一行都是一个新的Series。但要注意,这些并不是DataFrame里原本存储的Series,而是在遍历时临时创建的新Series。这意味着,如果你对这些新Series进行修改,这些修改不会反映到原来的DataFrame中。
好了,了解这些之后,我们该怎么做呢?
在这篇帖子之前,有一些建议:
pd.DataFrame.set_value
在Pandas版本0.21中已经被弃用pd.DataFrame.ix
也已经被弃用pd.DataFrame.loc
是可以用的,但它可以处理数组索引,你可以找到更好的方法
我的推荐
使用pd.DataFrame.at
for i in df.index:
if <something>:
df.at[i, 'ifor'] = x
else:
df.at[i, 'ifor'] = y
你甚至可以把它改成:
for i in df.index:
df.at[i, 'ifor'] = x if <something> else y
对评论的回应
如果我需要用到上一行的值来做条件判断怎么办?
for i in range(1, len(df) + 1):
j = df.columns.get_loc('ifor')
if <something>:
df.iat[i - 1, j] = x
else:
df.iat[i - 1, j] = y
你可以使用 df.at
来进行操作:
for i, row in df.iterrows():
ifor_val = something
if <condition>:
ifor_val = something_else
df.at[i,'ifor'] = ifor_val
如果你使用的版本在 0.21.0 之前,可以用 df.set_value
:
for i, row in df.iterrows():
ifor_val = something
if <condition>:
ifor_val = something_else
df.set_value(i,'ifor',ifor_val)
如果你不需要行的值,其实可以直接遍历 df
的索引,不过我保留了原来的 for 循环,以防你需要行值来做其他事情,这里没有展示出来。