¿Hay una forma mejor y más legible de coalesar columnas en pandas?

A menudo necesito una nueva columna que es lo mejor que puedo lograr de otras columnas y tengo una lista específica de prioridades de preferencia. Estoy dispuesto a tomar el primer valor no nulo.

def coalesce(values): not_none = (el for el in values if el is not None) return next(not_none, None) df = pd.DataFrame([{'third':'B','first':'A','second':'C'}, {'third':'B','first':None,'second':'C'}, {'third':'B','first':None,'second':None}, {'third':None,'first':None,'second':None}, {'third':'B','first':'A','second':None}]) df['combo1'] = df.apply(coalesce, axis=1) df['combo2'] = df[['second','third','first']].apply(coalesce, axis=1) print df 

Resultados

  first second third combo1 combo2 0 ACBAC 1 None CBCC 2 None None BBB 3 None None None None None 4 A None BAB 

Este código funciona (y el resultado es lo que quiero), pero no es muy rápido.
Puedo elegir mis prioridades si necesito [[” segundo ‘,’ tercero ‘,’ primero ‘]]

Coalesce algo así como la función del mismo nombre de tsql.
Sospecho que puedo haber pasado por alto una manera fácil de lograrlo con un buen rendimiento en grandes DataFrames (+400,000 filas)

Sé que hay muchas formas de completar los datos faltantes que uso a menudo en axis = 0, esto es lo que me hace pensar que me he perdido una opción fácil para axis = 1

¿Puede sugerir algo mejor / más rápido … o confirmar que esto es lo mejor?

Podría usar pd.isnull para encontrar los valores nulos, en este caso None :

 In [169]: pd.isnull(df) Out[169]: first second third 0 False False False 1 True False False 2 True True False 3 True True True 4 False True False 

y luego use np.argmin para encontrar el índice del primer valor no nulo. Si todos los valores son nulos, np.argmin devuelve 0:

 In [186]: np.argmin(pd.isnull(df).values, axis=1) Out[186]: array([0, 1, 2, 0, 0]) 

Luego, puede seleccionar los valores deseados de df usando NumPy integer-indexing:

 In [193]: df.values[np.arange(len(df)), np.argmin(pd.isnull(df).values, axis=1)] Out[193]: array(['A', 'C', 'B', None, 'A'], dtype=object) 

Por ejemplo,

 import pandas as pd df = pd.DataFrame([{'third':'B','first':'A','second':'C'}, {'third':'B','first':None,'second':'C'}, {'third':'B','first':None,'second':None}, {'third':None,'first':None,'second':None}, {'third':'B','first':'A','second':None}]) mask = pd.isnull(df).values df['combo1'] = df.values[np.arange(len(df)), np.argmin(mask, axis=1)] order = np.array([1,2,0]) mask = mask[:, order] df['combo2'] = df.values[np.arange(len(df)), order[np.argmin(mask, axis=1)]] 

rendimientos

  first second third combo1 combo2 0 ACBAC 1 None CBCC 2 None None BBB 3 None None None None None 4 A None BAB 

Usar argmin en lugar de df3.apply(coalesce, ...) es significativamente más rápido si el DataFrame tiene muchas filas:

 df2 = pd.concat([df]*1000) In [230]: %timeit mask = pd.isnull(df2).values; df2.values[np.arange(len(df2)), np.argmin(mask, axis=1)] 1000 loops, best of 3: 617 µs per loop In [231]: %timeit df2.apply(coalesce, axis=1) 10 loops, best of 3: 84.1 ms per loop 

El Pandas equivalente a COALESCE es el método fillna() :

 result = column_a.fillna(column_b) 

El resultado es una columna donde cada valor se toma de column_a si esa columna proporciona un valor no nulo; de lo contrario, el valor se toma de column_b . Así que tu combo1 puede ser producido con:

 df['first'].fillna(df['second']).fillna(df['third']) 

dando:

 0 A 1 C 2 B 3 None 4 A 

Y tu combo2 se puede producir con:

 (df['second']).fillna(df['third']).fillna(df['first']) 

que devuelve la nueva columna:

 0 C 1 C 2 B 3 None 4 B 

Si desea una operación eficiente llamada coalesce , simplemente podría combinar columnas con fillna() de izquierda a derecha y luego devolver el resultado:

 def coalesce(df, column_names): i = iter(column_names) column_name = next(i) answer = df[column_name] for column_name in i: answer = answer.fillna(df[column_name]) return answer print coalesce(df, ['first', 'second', 'third']) print coalesce(df, ['second', 'third', 'first']) 

lo que da:

 0 A 1 C 2 B 3 None 4 A 0 C 1 C 2 B 3 None 4 B