Creación condicional de pandas de una columna de serie / marco de datos

314

Tengo un marco de datos en la línea de abajo:

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

Quiero agregar otra columna al marco de datos (o generar una serie) de la misma longitud que el marco de datos (= igual número de registros / filas) que establece un color verde si Set = 'Z' y 'rojo' si Set = de lo contrario .

¿Cuál es la mejor manera de hacer esto?

usuario7289
fuente

Respuestas:

712

Si solo tiene dos opciones para elegir:

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

Por ejemplo,

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)

rendimientos

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

Si tiene más de dos condiciones, úselasnp.select . Por ejemplo, si quieres colorser

  • yellow cuando (df['Set'] == 'Z') & (df['Type'] == 'A')
  • de lo contrario bluecuando(df['Set'] == 'Z') & (df['Type'] == 'B')
  • de lo contrario purplecuando(df['Type'] == 'B')
  • de lo contrario black,

luego usa

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)

cuyos rendimientos

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black
unutbu
fuente
1
no funciona si pongo dos condiciones dentro de la cláusula con y
Amol Sharma
2
df ['color'] = list (np.where (df ['Set'] == 'Z', 'green', 'red')) suprimirá la advertencia de pandas: se está intentando establecer un valor en una copia de una porción de un DataFrame. Intente usar .loc [row_indexer, col_indexer] = value en su lugar
denson
3
'verde' y 'rojo' también se pueden reemplazar con aritmética de columna. por ejemplo ,df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Alejandro
¿np.where crea una nueva columna? Usé este código y cuando hago df.color.head () obtengo: el objeto 'numpy.ndarray' no tiene atributo 'head'
vvv
3
Es una pena que no pueda votar esto varias veces. Un voto a favor no parece suficiente.
Harper
120

La comprensión de la lista es otra forma de crear otra columna condicionalmente. Si está trabajando con tipos de objetos en columnas, como en su ejemplo, las comprensiones de listas generalmente superan a la mayoría de los otros métodos.

Ejemplo de comprensión de la lista:

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

% de pruebas de tiempo:

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
bastardo descarado
fuente
44
Tenga en cuenta que, con marcos de datos mucho más grandes (pensar- pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})tamaño), numpy.wheresupera map, pero la comprensión de la lista es el rey (aproximadamente un 50% más rápido que numpy.where).
blacksite el
3
¿Se puede usar el método de comprensión de la lista si la condición necesita información de varias columnas? Estoy buscando algo como esto (esto no funciona):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi
2
Agregue iterrows al marco de datos, luego puede acceder a varias columnas a través de la fila: ['red' if (row ['Set'] == 'Z') & (row ['Type'] == 'B') else 'green 'para índice, fila en df.iterrows ()]
cheekybastard
1
Tenga en cuenta que esta buena solución no funcionará si necesita tomar valores de reemplazo de otra serie en el marco de datos, comodf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux
@cheekybastard O no lo hagas, ya que .iterrows()es notoriamente lento y el DataFrame no debe modificarse durante la iteración.
AMC
21

Otra forma de lograrlo es

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')
acharuva
fuente
Buen enfoque, esto se puede memorizar para una eficiencia más rápida (en conjuntos de datos más grandes), aunque requeriría un paso adicional.
Yaakov Bressler
21

Aquí hay otra forma de desollar este gato, usando un diccionario para asignar nuevos valores a las teclas de la 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,))

Cómo se ve:

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

Este enfoque puede ser muy poderoso cuando tiene muchas ifelsedeclaraciones de tipo para hacer (es decir, muchos valores únicos para reemplazar).

Y, por supuesto, siempre puedes hacer esto:

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

Pero ese enfoque es más de tres veces más lento que el applyenfoque desde arriba, en mi máquina.

Y también puedes hacer esto, usando dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]
sitio negro
fuente
Me gusta esta respuesta porque muestra cómo hacer múltiples reemplazos de valores
Monica Heddneck
Pero ese enfoque es más de tres veces más lento que el enfoque aplicado desde arriba, en mi máquina. ¿Cómo comparaste estos? Según mis mediciones rápidas, la .map()solución es ~ 10 veces más rápida que .apply().
AMC
Actualización: en 100,000,000 filas, 52 valores de cadena, .apply()toma 47 segundos, en comparación con solo 5.91 segundos .map().
AMC
19

Lo siguiente es más lento que los enfoques cronometrados aquí , pero podemos calcular la columna adicional en función del contenido de más de una columna, y se pueden calcular más de dos valores para la columna adicional.

Ejemplo simple usando solo la columna "Establecer":

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

Ejemplo con más colores y más columnas tomadas en cuenta:

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): Usar plydata

También es posible usar plydata para hacer este tipo de cosas (esto parece aún más lento que usar assigny apply, sin embargo).

from plydata import define, if_else

Simple 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

Anidado 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
fuente
10

Tal vez esto ha sido posible con las actualizaciones más recientes de Pandas, pero creo que la siguiente es la respuesta más corta y quizás la mejor para la pregunta, hasta ahora. Puedes usar el.loc método y usar una o varias condiciones según su necesidad.

Resumen de 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"

Explicación:

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

agregue una columna 'color' y establezca todos los valores en "rojo"

df['Color'] = "red"

Aplica tu única condición:

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

o múltiples condiciones si quieres:

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

Puede leer sobre los operadores lógicos de Pandas y la selección condicional aquí: Operadores lógicos para la indexación booleana en Pandas

Hossein
fuente
2
El mejor hasta ahora. Probablemente podría agregar más condiciones que serían el códigodf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Salvador Vigo
2
Esta debería ser la respuesta aceptada. Realmente idiomático y extensible.
AMC
1

Un revestimiento con .apply()método es el siguiente:

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

Después de eso, dfel marco de datos se ve así:

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

Si está trabajando con datos masivos, un enfoque memorable sería lo mejor:

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

Este enfoque será más rápido cuando tenga muchos valores repetidos. Mi regla general es recordar cuando: data_size> 10**4& n_distinct<data_size/4

Ej. Memorizar en un caso 10,000 filas con 2,500 o menos valores distintos.

Yaakov Bressler
fuente
Muy bien, entonces con solo 2 valores distintos para mapear, 100,000,000 filas, se necesitan 6.67 segundos para ejecutarse sin "memorización", y 9.86 segundos con.
AMC
100,000,000 filas, 52 valores distintos, donde 1 de esos mapas al primer valor de salida, y los otros 51 corresponden al otro: 7.99 segundos sin memorización, 11.1 segundos con.
AMC
¿Están sus valores en orden aleatorio? ¿O están de espaldas? La alta velocidad de los pandas podría deberse al almacenamiento en caché @AMC
Yaakov Bressler
1
¿Están sus valores en orden aleatorio? ¿O están de espaldas? Los valores son aleatorios, seleccionados usando random.choices().
AMC