Aggiorna un dataframe in Panda durante l'iterazione riga per riga


238

Ho un frame di dati dei panda che assomiglia a questo (è piuttosto grande)

           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 

ora vorrei iterare riga per riga e mentre passo attraverso ogni riga, il valore di ifor in ogni riga può cambiare a seconda di alcune condizioni e ho bisogno di cercare un altro dataframe.

Ora, come aggiorno questo mentre itero. Ho provato alcune cose, nessuna di loro ha funzionato.

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

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

Nessuno di questi approcci sembra funzionare. Non vedo i valori aggiornati nel dataframe.


2
Penso che tu voglia df.ix[i,'ifor']. df.ix[i]['ifor']è problematico perché è indicizzazione concatenata (che non è affidabile nei panda).
Karl D.

1
Puoi fornire l'altra cornice oltre al file <something>. Se il tuo codice può essere vettorializzato dipenderà da queste cose. In generale, evitare iterrows. Nel tuo caso, dovresti assolutamente evitarlo poiché ogni riga sarà un objectdtype Series.
Phillip Cloud


Si prega di non utilizzare iterrows (). È un palese abilitatore del peggior anti-modello nella storia dei panda.
cs95

Risposte:


257

Puoi assegnare valori nel ciclo usando 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)

Se non hai bisogno dei valori di riga, potresti semplicemente iterare sugli indici di df, ma ho mantenuto il ciclo for originale nel caso in cui avessi bisogno del valore di riga per qualcosa non mostrato qui.

aggiornare

df.set_value () è stato deprecato dalla versione 0.21.0, puoi usare df.at () invece:

for i, row in df.iterrows():
    ifor_val = something
    if <condition>:
        ifor_val = something_else
    df.at[i,'ifor'] = ifor_val

8
Vedi pandas.pydata.org/pandas-docs/stable/generated/… , secondo punto: "2.Non dovresti mai modificare qualcosa su cui stai ripetendo"
Davor Josipovic,

33
Non sono sicuro che lo leggiamo esattamente allo stesso modo. Se guardi nel mio pseudo codice faccio la modifica sul dataframe, non sul valore dell'iteratore. Il valore dell'iteratore viene utilizzato solo per l'indice del valore / oggetto. Quello che fallirà è la riga ['ifor'] = some_thing, per i motivi menzionati nella documentazione.
rakke

3
Grazie per il chiarimento.
Davor Josipovic

11
ora anche set_value è deprecato e dovrebbe usare .at (o .iat), quindi il mio ciclo ha questo aspetto: for i, row in df.iterrows (): ifor_val = something if <condition>: ifor_val = something_else df.at [ i, "ifor"] = ifor_val
complexM

2
set_value è deprecato e verrà rimosso in una versione futura. Utilizza invece gli accessor .at [] o .iat []
RoyaumeIX

86

L'oggetto Pandas DataFrame dovrebbe essere pensato come una serie di serie. In altre parole, dovresti pensarlo in termini di colonne. Il motivo per cui questo è importante è perché quando lo usi pd.DataFrame.iterrowsstai iterando le righe come Serie. Ma queste non sono le serie che il data frame sta memorizzando e quindi sono nuove serie che vengono create per te mentre iterate. Ciò implica che quando si tenta di assegnarli, quelle modifiche non finiranno per riflettersi nel frame di dati originale.

Ok, ora che è tolto di mezzo: cosa facciamo?

I suggerimenti precedenti a questo post includono:

  1. pd.DataFrame.set_valueè deprecato a partire dalla versione 0.21 di Pandas
  2. pd.DataFrame.ixè deprecato
  3. pd.DataFrame.locva bene ma può funzionare con gli indicizzatori di array e puoi fare di meglio

La mia raccomandazione
Usapd.DataFrame.at

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

Puoi anche modificarlo in:

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

Risposta al commento

e cosa succede se devo utilizzare il valore della riga precedente per la condizione 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

e cosa succede se devo utilizzare il valore della riga precedente per la condizione if? aggiungere una colonna ritardata all'OG df?
Yuca

dal punto di vista dell'efficienza, il tuo approccio è migliore rispetto all'aggiunta di una colonna ritardata o l'effetto è trascurabile per piccoli set di dati? (<10k righe)
Yuca

Dipende. Preferirei utilizzare una colonna ritardata. Questa risposta mostra cosa fare se è necessario eseguire il ciclo. Ma se non devi eseguire il loop, non farlo.
piRSquared l'

Capito, anche se è possibile avere il tuo feedback per stackoverflow.com/q/51753001/9754169 , sarebbe fantastico: D
Yuca

Bello per contrastare .at [] con le alternative più vecchie
Justas

41

Un metodo che puoi usare è itertuples()che itera su righe DataFrame come namedtuples, con il valore di indice come primo elemento della tupla. Ed è molto molto più veloce rispetto a iterrows(). Per itertuples()ciascuna rowcontiene la sua Indexnel dataframe, e si può usare locper impostare il valore.

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

Nella maggior parte dei casi, itertuples()è più veloce di iato at.

Grazie @SantiStSupery, l' utilizzo .atè molto più veloce diloc .


3
Poiché punti solo a un indice preciso, potresti pensare di utilizzare .at invece di .loc per migliorare le tue prestazioni. Vedi questa domanda per maggiori informazioni su questo
SantiStSupery

strano pensare ma df.loc[row.Index, 3] = xnon funziona. D'altra parte, df.loc[row.Index, 'ifor'] = xfunziona!
seralouk

20

È necessario assegnare un valore con df.ix[i, 'exp']=Xo df.loc[i, 'exp']=Xinvece di df.ix[i]['ifor'] = x.

Altrimenti stai lavorando su una vista e dovresti ottenere un riscaldamento:

-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

Ma certamente, il ciclo dovrebbe probabilmente essere sostituito da un algoritmo vettorializzato per sfruttare appieno DataFramecome suggerito da @Phillip Cloud.


13

Bene, se hai intenzione di iterare comunque, perché non utilizzare il metodo più semplice di tutti, df['Column'].values[i]

df['Column'] = ''

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

Oppure, se vuoi confrontare i nuovi valori con quelli vecchi o qualcosa del genere, perché non memorizzarli in un elenco e poi aggiungerli alla fine.

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

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

df['Column'] = mylist

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

2

È meglio usare le lambdafunzioni usando df.apply():

df["ifor"] = df.apply(lambda x: {value} if {condition} else x["ifor"], axis=1)

1
questa dovrebbe essere la nuova risposta aggiornata. gli altri sembrano risalire all'inizio del decennio. Chi usa ancora i loop yikes
iqbal125 il

-3

Incrementa il numero MAX da una colonna. Per esempio :

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

La mia uscita:

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 

Ora, ho bisogno di creare una colonna in df2 e riempire i valori della colonna che incrementa il MAX.

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

Nota: df2 inizialmente conterrà solo Column1 e Column2. abbiamo bisogno della colonna Sortid da creare e incrementale del MAX da df1.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.