Criação condicional do Pandas de uma coluna de série / dataframe

314

Eu tenho um quadro de dados ao longo das linhas abaixo:

    Type       Set
1    A          Z
2    B          Z           
3    B          X
4    C          Y

Eu quero adicionar outra coluna ao quadro de dados (ou gerar uma série) do mesmo comprimento que o quadro de dados (= número igual de registros / linhas) que define uma cor verde se Set = 'Z' e 'red' se Set = caso contrário .

Qual é a melhor forma de fazer isso?

user7289
fonte

Respostas:

712

Se você tiver apenas duas opções para escolher:

df['color'] = np.where(df['Set']=='Z', 'green', 'red')

Por exemplo,

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
df['color'] = np.where(df['Set']=='Z', 'green', 'red')
print(df)

rendimentos

  Set Type  color
0   Z    A  green
1   Z    B  green
2   X    B    red
3   Y    C    red

Se você tiver mais de duas condições, usenp.select . Por exemplo, se você quiser colorser

  • yellow quando (df['Set'] == 'Z') & (df['Type'] == 'A')
  • caso contrário, bluequando(df['Set'] == 'Z') & (df['Type'] == 'B')
  • caso contrário, purplequando(df['Type'] == 'B')
  • caso contrário black,

então use

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
conditions = [
    (df['Set'] == 'Z') & (df['Type'] == 'A'),
    (df['Set'] == 'Z') & (df['Type'] == 'B'),
    (df['Type'] == 'B')]
choices = ['yellow', 'blue', 'purple']
df['color'] = np.select(conditions, choices, default='black')
print(df)

que produz

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black
unutbu
fonte
1
não funciona se eu colocar duas condições dentro da cláusula where com e
Amol Sharma
2
df ['color'] = list (np.where (df ['Set'] == 'Z', 'green', 'red')) suprimirá o aviso dos pandas: Um valor está tentando ser definido em uma cópia de uma fatia de um DataFrame. Tente usar .loc [row_indexer, col_indexer] = value em vez disso
denson 19/10/16
3
'verde' e 'vermelho' também podem ser substituídos por aritmética da coluna. por exemplo ,df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Alejandro
o np.where cria uma nova coluna? Eu usei esse código e quando eu faço df.color.head () eu recebo: 'numpy.ndarray' objeto não tem nenhum atributo 'head'
vvv
3
É uma pena que eu não possa votar isso várias vezes. Um voto positivo não parece suficiente.
Harper
120

A compreensão da lista é outra maneira de criar outra coluna condicionalmente. Se você estiver trabalhando com tipos de objeto em colunas, como no seu exemplo, as compreensões de lista geralmente superam a maioria dos outros métodos.

Exemplo de compreensão da lista:

df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]

% timeit testes:

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
%timeit df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color'] = np.where(df['Set']=='Z', 'green', 'red')
%timeit df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

1000 loops, best of 3: 239 µs per loop
1000 loops, best of 3: 523 µs per loop
1000 loops, best of 3: 263 µs per loop
cheekybastard
fonte
4
Observe que, com quadros de dados muito maiores ( pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})tamanho de pensamento ), numpy.whereultrapassa map, mas a compreensão da lista é excelente (cerca de 50% mais rápida que numpy.where).
BlackSite
3
O método de compreensão da lista pode ser usado se a condição precisar de informações de várias colunas? Estou procurando algo parecido com isto (isso não funciona):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi
2
Adicione as seguintes linhas ao quadro de dados para acessar várias colunas via linha: ['red' if (linha ['Set'] == 'Z') & (linha ['Type'] == 'B') else 'verde 'para index, row in em df.iterrows ()]
cheekybastard 14/01/19
1
Observe que essa boa solução não funcionará se você precisar obter valores de substituição de outra série no quadro de dados, comodf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux
@cheekybastard Ou não, pois .iterrows()é notoriamente lento e o DataFrame não deve ser modificado durante a iteração.
AMC
21

Outra maneira pela qual isso pode ser alcançado é

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')
acharuva
fonte
Boa abordagem, isso pode ser memorizado para maior eficiência (em conjuntos de dados maiores), embora exija uma etapa adicional.
Yaakov Bressler
21

Aqui está mais uma maneira de esfolar esse gato, usando um dicionário para mapear novos valores nas chaves da lista:

def map_values(row, values_dict):
    return values_dict[row]

values_dict = {'A': 1, 'B': 2, 'C': 3, 'D': 4}

df = pd.DataFrame({'INDICATOR': ['A', 'B', 'C', 'D'], 'VALUE': [10, 9, 8, 7]})

df['NEW_VALUE'] = df['INDICATOR'].apply(map_values, args = (values_dict,))

Como é:

df
Out[2]: 
  INDICATOR  VALUE  NEW_VALUE
0         A     10          1
1         B      9          2
2         C      8          3
3         D      7          4

Essa abordagem pode ser muito poderosa quando você tem muitas ifelseinstruções de tipo a fazer (ou seja, muitos valores exclusivos a serem substituídos).

E é claro que você sempre pode fazer isso:

df['NEW_VALUE'] = df['INDICATOR'].map(values_dict)

Mas essa abordagem é mais do que três vezes mais lenta que a applyabordagem de cima, na minha máquina.

E você também pode fazer isso usando dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]
Zona de Perigo
fonte
Eu gosto desta resposta, porque mostra como fazer várias substituições de valores
Monica Heddneck
Mas essa abordagem é três vezes mais lenta que a abordagem de aplicação acima, na minha máquina. Como você os avaliou? De minhas medições rápidas, a .map()solução é ~ 10 vezes mais rápida que .apply().
AMC
Atualização: em 100.000.000 de linhas, 52 valores de sequência, .apply()leva 47 segundos, contra apenas 5,91 segundos .map().
AMC
19

A seguir, é mais lento que as abordagens cronometradas aqui , mas podemos calcular a coluna extra com base no conteúdo de mais de uma coluna e mais de dois valores podem ser calculados para a coluna extra.

Exemplo simples usando apenas a coluna "Set":

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Exemplo com mais cores e mais colunas consideradas:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    elif row["Type"] == "C":
        return "blue"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C   blue

Editar (21/06/2019): Usando plydata

Também é possível usar plydata para fazer esse tipo de coisa (isso parece ainda mais lento do que usar assigne apply, no entanto).

from plydata import define, if_else

Simples if_else:

df = define(df, color=if_else('Set=="Z"', '"red"', '"green"'))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Aninhado if_else:

df = define(df, color=if_else(
    'Set=="Z"',
    '"red"',
    if_else('Type=="C"', '"green"', '"blue"')))

print(df)                            
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B   blue
3   Y    C  green
bli
fonte
10

Talvez isso tenha sido possível com as atualizações mais recentes do Pandas, mas acho que a seguinte é a resposta mais curta e talvez a melhor para a pergunta, até agora. Você pode usar o .locmétodo e usar uma condição ou várias, dependendo da sua necessidade.

Resumo do código:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))
df['Color'] = "red"
df.loc[(df['Set']=="Z"), 'Color'] = "green"

#practice!
df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Explicação:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))

# df so far: 
  Type Set  
0    A   Z 
1    B   Z 
2    B   X 
3    C   Y

adicione uma coluna 'cor' e defina todos os valores para "vermelho"

df['Color'] = "red"

Aplique sua única condição:

df.loc[(df['Set']=="Z"), 'Color'] = "green"


# df: 
  Type Set  Color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

ou várias condições, se você desejar:

df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Você pode ler sobre operadores lógicos do Pandas e seleção condicional aqui: Operadores lógicos para indexação booleana no Pandas

Hossein
fonte
2
O melhor até agora. Você provavelmente poderia adicionar para mais condições que seriam o códigodf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Salvador Vigo
2
Essa deve ser a resposta aceita. Na verdade, idiomático e extensível.
AMC
1

Um revestimento com o .apply()método é o seguinte:

df['color'] = df['Set'].apply(lambda set_: 'green' if set_=='Z' else 'red')

Depois disso, o dfquadro de dados fica assim:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red
Jaroslav Bezděk
fonte
0

Se você estiver trabalhando com dados massivos, uma abordagem memorizada seria melhor:

# First create a dictionary of manually stored values
color_dict = {'Z':'red'}

# Second, build a dictionary of "other" values
color_dict_other = {x:'green' for x in df['Set'].unique() if x not in color_dict.keys()}

# Next, merge the two
color_dict.update(color_dict_other)

# Finally, map it to your column
df['color'] = df['Set'].map(color_dict)

Essa abordagem será mais rápida quando você tiver muitos valores repetidos. Minha regra geral é memorizar quando: data_size> 10**4& n_distinct<data_size/4

Ex Memoize em um caso de 10.000 linhas com 2.500 ou menos valores distintos.

Yaakov Bressler
fonte
Tudo bem, então, com apenas 2 valores distintos para mapear, 100.000.000 de linhas, leva 6,67 segundos para executar sem "memorização" e 9,86 segundos com.
AMC
100.000.000 de linhas, 52 valores distintos, em que um desses mapas é o primeiro valor de saída e os outros 51 correspondem ao outro: 7,99 segundos sem memorização, 11,1 segundos com.
AMC
Seus valores estão em ordem aleatória? Ou eles estão de costas? Alta velocidade de pandas pode ser devido ao cache @AMC
Yaakov Bressler
1
Seus valores estão em ordem aleatória? Ou eles estão de costas? Os valores são aleatórios, selecionados usando random.choices().
AMC