Mettre à jour une trame de données dans les pandas tout en itérant ligne par ligne

214

J'ai un cadre de données pandas qui ressemble à ceci (c'est un assez gros)

           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 

maintenant, je voudrais itérer ligne par ligne et au fur et à mesure que je parcours chaque ligne, la valeur de ifor dans chaque ligne peut changer en fonction de certaines conditions et je dois rechercher une autre trame de données.

Maintenant, comment puis-je mettre à jour cela pendant que j'itère. J'ai essayé quelques choses, aucune d'entre elles n'a fonctionné.

for i, row in df.iterrows():
    if <something>:
        row['ifor'] = x
    else:
        row['ifor'] = y

    df.ix[i]['ifor'] = x

Aucune de ces approches ne semble fonctionner. Je ne vois pas les valeurs mises à jour dans la trame de données.

AMM
la source
2
Je pense que tu veux df.ix[i,'ifor']. df.ix[i]['ifor']est problématique car il s'agit d'une indexation chaînée (ce qui n'est pas fiable chez les pandas).
Karl D.
1
Pouvez-vous fournir l'autre cadre ainsi que le <something>. La possibilité de vectoriser votre code dépendra de ces éléments. En général, évitez iterrows. Dans votre cas, vous devez absolument l' éviter car chaque ligne sera un objectdtype Series.
Phillip Cloud
Vous feriez mieux de créer un masque booléen pour votre condition, mettez à jour toutes ces lignes, puis définissez le reste sur l'autre valeur
EdChum
Veuillez ne pas utiliser iterrows (). C'est un catalyseur flagrant du pire anti-modèle de l'histoire des pandas.
cs95

Réponses:

232

Vous pouvez affecter des valeurs dans la boucle à l'aide de 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)

Si vous n'avez pas besoin des valeurs de ligne, vous pouvez simplement itérer sur les indices de df, mais j'ai conservé la boucle for d'origine au cas où vous auriez besoin de la valeur de ligne pour quelque chose qui n'est pas affiché ici.

mettre à jour

df.set_value () est obsolète depuis la version 0.21.0, vous pouvez utiliser df.at () à la place:

for i, row in df.iterrows():
    ifor_val = something
    if <condition>:
        ifor_val = something_else
    df.at[i,'ifor'] = ifor_val
rakke
la source
6
Voir pandas.pydata.org/pandas-docs/stable/generated/… , deuxième puce: "2.Vous ne devez jamais modifier quelque chose que vous répétez"
Davor Josipovic
32
Je ne sais pas si nous lisons exactement la même chose. Si vous regardez dans mon pseudo code je fais la modification sur le dataframe, pas sur la valeur de l'itérateur. La valeur de l'itérateur n'est utilisée que pour l'index de la valeur / de l'objet. Ce qui échouera est la ligne ['ifor'] = some_thing, pour les raisons mentionnées dans la documentation.
rakke
3
Merci pour la clarification.
Davor Josipovic
8
maintenant set_value est également déprécié, et devrait utiliser .at (ou .iat), donc ma boucle ressemble à ceci: pour i, ligne dans df.iterrows (): ifor_val = quelque chose si <condition>: ifor_val = something_else df.at [ i, 'ifor'] = ifor_val
complexM
2
set_value est obsolète et sera supprimé dans une prochaine version. Veuillez utiliser les accesseurs .at [] ou .iat [] à la place
RoyaumeIX
75

L'objet Pandas DataFrame doit être considéré comme une série de séries. En d'autres termes, vous devriez y penser en termes de colonnes. La raison pour laquelle cela est important est que lorsque vous utilisez, pd.DataFrame.iterrowsvous parcourez les lignes en tant que série. Mais ce ne sont pas les séries que la trame de données stocke et ce sont donc de nouvelles séries qui sont créées pour vous pendant que vous itérez. Cela implique que lorsque vous tentez de les affecter, ces modifications ne se retrouveront pas reflétées dans le bloc de données d'origine.

Ok, maintenant que c'est à l'écart: que faisons-nous?

Les suggestions avant ce post incluent:

  1. pd.DataFrame.set_valueest obsolète à partir de la version 0.21 de Pandas
  2. pd.DataFrame.ixest obsolète
  3. pd.DataFrame.locest bien, mais peut fonctionner sur les indexeurs de tableau et vous pouvez faire mieux

Ma recommandation
Utilisationpd.DataFrame.at

for i in df.index:
    if <something>:
        df.at[i, 'ifor'] = x
    else:
        df.at[i, 'ifor'] = y

Vous pouvez même changer cela en:

for i in df.index:
    df.at[i, 'ifor'] = x if <something> else y

Réponse au commentaire

et si j'ai besoin d'utiliser la valeur de la ligne précédente pour la condition if?

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
piRSquared
la source
et si j'ai besoin d'utiliser la valeur de la ligne précédente pour la condition if? ajouter une colonne décalée à l'OG df?
Yuca
en termes d'efficacité, votre approche est-elle meilleure par rapport à l'ajout d'une colonne décalée ou l'effet est-il négligeable pour les petits ensembles de données? (<10 000 lignes)
Yuca
Ça dépend. Je choisirais d'utiliser une colonne décalée. Cette réponse montre quoi faire si vous devez boucler. Mais si vous n'avez pas à boucler, alors ne le faites pas.
piRSquared
Je l'ai aussi, s'il est possible d'avoir vos commentaires pour stackoverflow.com/q/51753001/9754169, alors ce serait génial: D
Yuca
Agréable pour contraster .at [] avec les alternatives plus anciennes
Justas
35

Une méthode que vous pouvez utiliser consiste itertuples()à itérer sur les lignes DataFrame en tant que couples nommés, avec la valeur d'index comme premier élément du tuple. Et c'est beaucoup plus rapide par rapport à iterrows(). Pour itertuples(), chacun rowcontient son Indexdans le DataFrame, et vous pouvez utiliser locpour définir la valeur.

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

Dans la plupart des cas, itertuples()est plus rapide que iatou at.

Merci @SantiStSupery, l' utilisation .atest beaucoup plus rapide queloc .

GoingMyWay
la source
3
Comme vous ne pointez que sur un index précis, vous pouvez penser à utiliser .at au lieu de .loc pour améliorer vos performances. Voir cette question pour plus d'informations à ce sujet
SantiStSupery
bizarre pense mais df.loc[row.Index, 3] = xne fonctionne pas. D'un autre côté, df.loc[row.Index, 'ifor'] = xça marche!
seralouk
19

Vous devez attribuer une valeur par df.ix[i, 'exp']=Xou df.loc[i, 'exp']=Xau lieu de df.ix[i]['ifor'] = x.

Sinon, vous travaillez sur une vue et vous devriez obtenir un réchauffement:

-c:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_index,col_indexer] = value instead

Mais certainement, la boucle devrait probablement être remplacée par un algorithme vectorisé pour utiliser pleinement DataFramecomme l'a suggéré @Phillip Cloud.

CT Zhu
la source
10

Eh bien, si vous voulez répéter de toute façon, pourquoi ne pas utiliser la méthode la plus simple de toutes, df['Column'].values[i]

df['Column'] = ''

for i in range(len(df)):
    df['Column'].values[i] = something/update/new_value

Ou si vous voulez comparer les nouvelles valeurs avec des anciennes ou quelque chose comme ça, pourquoi ne pas les stocker dans une liste, puis les ajouter à la fin.

mylist, df['Column'] = [], ''

for <condition>:
    mylist.append(something/update/new_value)

df['Column'] = mylist
Pranzell
la source
7
for i, row in df.iterrows():
    if <something>:
        df.at[i, 'ifor'] = x
    else:
        df.at[i, 'ifor'] = y
Duane
la source
0

Il vaut mieux utiliser des lambdafonctions en utilisant df.apply()-

df["ifor"] = df.apply(lambda x: {value} if {condition} else x["ifor"], axis=1)
Prachit Patil
la source
-3

Incrémentez le nombre MAX d'une colonne. Par exemple :

df1 = [sort_ID, Column1,Column2]
print(df1)

Ma sortie:

Sort_ID Column1 Column2
12         a    e
45         b    f
65         c    g
78         d    h

MAX = df1['Sort_ID'].max() #This returns my Max Number 

Maintenant, je dois créer une colonne dans df2 et remplir les valeurs de colonne qui incrémentent le MAX.

Sort_ID Column1 Column2
79      a1       e1
80      b1       f1
81      c1       g1
82      d1       h1

Remarque: df2 ne contiendra initialement que les colonnes Colonne1 et Colonne2. nous avons besoin que la colonne Sortid soit créée et incrémentielle du MAX de df1.

Shazir Jabbar
la source