pandas: filtro complejo en filas de DataFrame

85

Me gustaría filtrar filas por una función de cada fila, por ejemplo

def f(row):
  return sin(row['velocity'])/np.prod(['masses']) > 5

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, f)]

O para otro ejemplo más complejo y artificial,

def g(row):
  if row['col1'].method1() == 1:
    val = row['col1'].method2() / row['col1'].method3(row['col3'], row['col4'])
  else:
    val = row['col2'].method5(row['col6'])
  return np.sin(val)

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, g)]

¿Como lo puedo hacer?

digno de pato
fuente

Respuestas:

121

Puede hacer esto usando DataFrame.apply, que aplica una función a lo largo de un eje dado,

In [3]: df = pandas.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [4]: df
Out[4]: 
          a         b         c
0 -0.001968 -1.877945 -1.515674
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

In [6]: df[df.apply(lambda x: x['b'] > x['c'], axis=1)]
Out[6]: 
          a         b         c
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168
digno de pato
fuente
15
No es necesario applyen esta situación. Un índice booleano normal funcionará bien. df[df['b] > df['c']]. Hay muy pocas situaciones que realmente lo requieran applye incluso pocas que lo necesiten conaxis=1
Ted Petrou
@TedPetrou ¿Qué sucede si no está seguro de que todos los elementos de su marco de datos sean del tipo correcto? ¿Un índice booleano regular admite el manejo de excepciones?
D. Ror.
13

Supongamos que tengo un DataFrame de la siguiente manera:

In [39]: df
Out[39]: 
      mass1     mass2  velocity
0  1.461711 -0.404452  0.722502
1 -2.169377  1.131037  0.232047
2  0.009450 -0.868753  0.598470
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289

Puedo usar sin y DataFrame.prod para crear una máscara booleana:

In [40]: mask = (np.sin(df.velocity) / df.ix[:, 0:2].prod(axis=1)) > 0

In [41]: mask
Out[41]: 
0    False
1    False
2    False
3     True
4     True

Luego use la máscara para seleccionar del DataFrame:

In [42]: df[mask]
Out[42]: 
      mass1     mass2  velocity
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289
Chang ella
fuente
2
en realidad, este fue probablemente un mal ejemplo: np.sinse transmite automáticamente a todos los elementos. ¿Qué pasa si lo reemplazo con una función menos inteligente que solo pueda manejar una entrada a la vez?
Dudoso
5

No puedo comentar sobre la respuesta de Duckworthd , pero no funciona perfectamente. Se bloquea cuando el marco de datos está vacío:

df = pandas.DataFrame(columns=['a', 'b', 'c'])
df[df.apply(lambda x: x['b'] > x['c'], axis=1)]

Salidas:

ValueError: Must pass DataFrame with boolean values only

Para mí, parece un error en pandas, ya que {} es definitivamente un conjunto válido de valores booleanos. Para obtener una solución, consulte la respuesta de Roy Hyunjin Han .

cglacet
fuente
3

El mejor enfoque que he encontrado es, en lugar de usarlo reduce=Truepara evitar errores para df vacío (ya que este argumento está obsoleto de todos modos), simplemente verifique que df size> 0 antes de aplicar el filtro:

def my_filter(row):
    if row.columnA == something:
        return True

    return False

if len(df.index) > 0:
    df[df.apply(my_filter, axis=1)]
usuario553965
fuente
0

Puede usar la locpropiedad para dividir su marco de datos.

Según documentación , locpuede tener callable functioncomo argumento.

In [3]: df = pandas.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [4]: df
Out[4]: 
          a         b         c
0 -0.001968 -1.877945 -1.515674
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

# define lambda function
In [5]: myfilter = lambda x: x['b'] > x['c']

# use my lambda in loc
In [6]: df1 = df.loc[fif]

si desea combinar su función de filtro fifcon otros criterios de filtro

df1 = df.loc[fif].loc[(df.b >= 0.5)]
Pierock
fuente