Atualizar um quadro de dados nos pandas enquanto itera linha por linha

214

Eu tenho um quadro de dados de pandas que se parece com isso (é bem 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 

agora eu gostaria de iterar linha por linha e, à medida que ifor passo cada linha, o valor de cada linha pode mudar dependendo de algumas condições e preciso procurar outro quadro de dados.

Agora, como atualizo isso à medida que itero. Tentei algumas coisas que nenhum deles funcionou.

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

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

Nenhuma dessas abordagens parece funcionar. Não vejo os valores atualizados no quadro de dados.

AMM
fonte
2
Eu acho que você quer df.ix[i,'ifor']. df.ix[i]['ifor']é problemático porque é indexado em cadeia (o que não é confiável em pandas).
28414 Karl
1
Você pode fornecer o outro quadro, bem como o <something>. Se o seu código pode ser vetorizado dependerá dessas coisas. Em geral, evite iterrows. No seu caso, você deve definitivamente evitá-lo, pois cada linha será um objecttipo Series.
Phillip Cloud
Você seria melhor criar uma máscara booleano para a sua condição, atualizar todas as linhas e, em seguida, definir o resto para o outro valor
EdChum
Por favor, não use iterrows (). É um habilitador flagrante do pior anti-padrão da história dos pandas.
cs95

Respostas:

232

Você pode atribuir valores no loop 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 você não precisar dos valores da linha, basta iterar sobre os índices de df, mas mantive o loop for original, caso você precise do valor da linha para algo não mostrado aqui.

atualizar

O df.set_value () está obsoleto desde a versão 0.21.0, você pode usar o df.at ():

for i, row in df.iterrows():
    ifor_val = something
    if <condition>:
        ifor_val = something_else
    df.at[i,'ifor'] = ifor_val
rakke
fonte
6
Veja pandas.pydata.org/pandas-docs/stable/generated/… , segundo marcador: "2. Você nunca deve modificar algo sobre o qual está repetindo" "
Davor Josipovic
32
Não sei se lemos exatamente da mesma forma. Se você olhar no meu pseudo-código, eu faço a modificação no quadro de dados, não no valor do iterador. O valor do iterador é usado apenas para o índice do valor / objeto. O que falhará é a linha ['ifor'] = some_thing, pelos motivos mencionados na documentação.
Rakke
3
Obrigado pelo esclarecimento.
Davor Josipovic
8
agora set_value também é depreciado e deve usar .at (ou .iat), então meu loop fica assim: para i, linha em df.iterrows (): ifor_val = algo se <condição>: ifor_val = algo_se df.at [ i, 'ifor'] = ifor_val
complexM 08/02/19
2
set_value foi descontinuado e será removido em uma versão futura. Por favor, use acessadores .at [] ou .iat [] em vez disso
RoyaumeIX
75

O objeto DataFrame do Pandas deve ser considerado como uma série de séries. Em outras palavras, você deve pensar nisso em termos de colunas. A razão pela qual isso é importante é porque, quando você usa, pd.DataFrame.iterrowsestá iterando pelas linhas como Série. Mas essas não são as séries que o quadro de dados está armazenando e, portanto, são novas séries criadas para você enquanto você itera. Isso implica que, quando você tentar atribuir a eles, essas edições não serão refletidas no quadro de dados original.

Ok, agora que está fora do caminho: o que fazemos?

As sugestões anteriores a esta postagem incluem:

  1. pd.DataFrame.set_valuefoi descontinuado a partir da versão 0.21 do Pandas
  2. pd.DataFrame.ixestá obsoleto
  3. pd.DataFrame.locé bom, mas pode funcionar em indexadores de matriz e você pode fazer melhor

Minha recomendação
Usepd.DataFrame.at

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

Você pode até mudar isso para:

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

Resposta ao comentário

e se eu precisar usar o valor da linha anterior para a condição 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
fonte
e se eu precisar usar o valor da linha anterior para a condição if? adicionar uma coluna atrasada ao OG df?
Yuca
Em termos de eficiência, sua abordagem é melhor do que adicionar uma coluna atrasada ou o efeito é insignificante para pequenos conjuntos de dados? (<10k linhas)
Yuca
Depende. Eu usaria uma coluna atrasada. Esta resposta está mostrando o que fazer se você precisar fazer um loop. Mas se você não precisa fazer um loop, não precisa.
amigos estão dizendo sobre piemonte
Entendi, também, se é possível ter o seu feedback para stackoverflow.com/q/51753001/9754169, então seria incrível: D
Yuca
Agradável para contrastar .at [] com as alternativas mais velhos
Justas
35

Um método que você pode usar é itertuples(): ele itera sobre as linhas do DataFrame como nomeduplos, com o valor do índice como primeiro elemento da tupla. E é muito, muito mais rápido em comparação com iterrows(). Para itertuples(), cada um rowcontém seu Indexno DataFrame, e você pode usar locpara definir o valor.

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

Na maioria dos casos, itertuples()é mais rápido que iatouat .

Obrigado @SantiStSupery, o uso .até muito mais rápido queloc .

Indo à minha maneira
fonte
3
Como você aponta apenas para um índice preciso, pense em usar .at em vez de .loc para melhorar seu desempenho. Veja esta pergunta para obter mais informações sobre isso
SantiStSupery
pensamento estranho, mas df.loc[row.Index, 3] = xnão funciona. Por outro lado, df.loc[row.Index, 'ifor'] = xfunciona!
seralouk
19

Você deve atribuir valor por df.ix[i, 'exp']=Xou em df.loc[i, 'exp']=Xvez de df.ix[i]['ifor'] = x.

Caso contrário, você está trabalhando em uma exibição e deve receber um aquecimento:

-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

Mas, certamente, o loop provavelmente deveria ser melhor substituído por algum algoritmo vetorizado para fazer o uso completo DataFramecomo o @Phillip Cloud sugeriu.

CT Zhu
fonte
10

Bem, se você deseja iterar de qualquer maneira, por que não usar o método mais simples de todos, df['Column'].values[i]

df['Column'] = ''

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

Ou se você deseja comparar os novos valores com os antigos ou algo assim, por que não armazená-los em uma lista e depois anexá-los ao final.

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

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

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

É melhor usar lambdafunções usando df.apply()-

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

Incremente o número MAX de uma coluna. Por exemplo :

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

Minha saída:

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 

Agora, preciso criar uma coluna no df2 e preencher os valores da coluna que incrementam o MAX.

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

Nota: o df2 conterá inicialmente apenas a Coluna1 e a Coluna2. precisamos que a coluna Sortid seja criada e incremental do MAX do df1.

Shazir Jabbar
fonte