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?

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

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

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')

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']]

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

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


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

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.