Eliminar filas de un DataFrame de pandas basado en una expresión condicional que involucra len (string) dando KeyError

303

Tengo un DataFrame de pandas y quiero eliminar filas de él donde la longitud de la cadena en una columna particular es mayor que 2.

Espero poder hacer esto (según esta respuesta ):

df[(len(df['column name']) < 2)]

pero acabo de recibir el error:

KeyError: u'no item named False'

¿Qué estoy haciendo mal?

(Nota: sé que puedo usar df.dropna()para deshacerme de las filas que contienen alguna NaN, pero no vi cómo eliminar filas en función de una expresión condicional).

sjs
fuente

Respuestas:

169

Cuando lo hace len(df['column name']), solo obtiene un número, es decir, el número de filas en el DataFrame (es decir, la longitud de la columna en sí). Si desea aplicar lena cada elemento de la columna, use df['column name'].map(len). Así que intenta

df[df['column name'].map(len) < 2]
BrenBarn
fuente
3
Se me ocurrió una forma de utilizar una lista de comprensión: df[[(len(x) < 2) for x in df['column name']]]pero la tuya es mucho más agradable. ¡Gracias por tu ayuda!
sjs
13
En caso de que alguien necesite una comparación más compleja, siempre se puede usar una lambda. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto
1
Por alguna razón, ninguna de las otras opciones me ha funcionado, excepto la publicada por @ 4lberto. Estoy en pandas 0.23.4Python 3.6
goelakash
1
.copy()Agregaría un al final, en caso de que luego desee editar este marco de datos (por ejemplo, la asignación de nuevas columnas generaría la advertencia "Un valor está tratando de establecerse en una copia de un segmento de un Marco de datos"
PlasmaBinturong
807

Para responder directamente al título original de esta pregunta "Cómo eliminar filas de un DataFrame de pandas basado en una expresión condicional" (lo que entiendo no es necesariamente el problema del OP pero podría ayudar a otros usuarios a encontrar esta pregunta) una forma de hacerlo es usar El método de caída :

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Ejemplo

Para eliminar todas las filas donde la columna 'puntuación' es <50:

df = df.drop(df[df.score < 50].index)

Versión en el lugar (como se señala en los comentarios)

df.drop(df[df.score < 50].index, inplace=True)

Condiciones múltiples

(ver Indización booleana )

Los operadores son: |para or, &para andy ~para not. Estos deben agruparse usando paréntesis.

Para eliminar todas las filas donde la columna 'puntuación' es <50 y> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)

Usuario
fuente
32
Solo quiero comentar que la función drop admite el reemplazo in situ. Es decir,. su solución es la misma que df.drop (df [df.score <50] .index, inplace = True). Sin embargo, no conocía el truco del "índice". Me ayudó mucho
Quickbeam2k1
99
Solo quiero señalar que antes de usar este truco de índice, debe asegurarse de que sus valores de índice sean únicos (o de llamada reset_index()). Descubrí esto de la manera difícil cuando se eliminaron muchas filas de mi marco de datos.
Jay
3
¿Cómo elimino todas las filas donde el tipo de columna es str? Solo quiero mantener los tipos de columna de lista. He intentado test = df.drop(df[df['col1'].dtype == str].index)pero recibo el error KeyError: False que también he intentado df.drop(df[df.col1.dtype == str].index)y df.drop(df[type(df.cleaned_norm_email) == str].index)parece que nada funciona. ¿Alguien puede aconsejar? ¡Gracias! @Usuario
PyRsquared
1
Esta es una vieja pregunta, pero ... @ pez desafiado acuáticamente es mucho más rápido que este. Tenga en cuenta que calcula df[(df.score < 50) & (df.score > 20)]como parte de su respuesta. Si invierte esto df = df[(df.score >= 50) | (df.score <= 20)], obtendrá su respuesta mucho más rápido.
Roobie Nuby
1
@RoobieNuby: no son la misma condición.
Nguai al
106

Puede asignar el DataFramea una versión filtrada de sí mismo:

df = df[df.score > 50]

Esto es más rápido que drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Kabard
fuente
¿Cómo verifico el uso o la condición de varias columnas?
Piyush S. Wanare
9

Expandiré la solución genérica de @ User para proporcionar una dropalternativa gratuita. Esto es para las personas que se dirigen aquí según el título de la pregunta (no el problema de OP)

Digamos que desea eliminar todas las filas con valores negativos. Una solución de revestimiento es: -

df = df[(df > 0).all(axis=1)]

Paso a paso Explicación: -

Generemos un marco de datos de distribución normal aleatorio de 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Deje que la condición elimine negativos. Un df booleano que satisface la condición: -

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Una serie booleana para todas las filas que satisfacen la condición Tenga en cuenta que si algún elemento de la fila falla la condición, la fila se marca como falsa

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Finalmente, filtre las filas del marco de datos según la condición

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Puede asignarlo de nuevo a df para eliminar realmente vs filtrar hecho anteriormente
df = df[(df > 0).all(axis=1)]

Esto se puede ampliar fácilmente para filtrar las filas que contienen NaN s (entradas no numéricas):
df = df[(~df.isnull()).all(axis=1)]

Esto también se puede simplificar para casos como: Eliminar todas las filas donde la columna E es negativa

df = df[(df.E>0)]

Me gustaría terminar con algunas estadísticas de perfiles sobre por qué la dropsolución de @ User es más lenta que la filtración basada en columnas sin procesar: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Una columna es básicamente una matriz, Serieses decir NumPy, puede indexarse ​​sin costo alguno. Para las personas interesadas en cómo la organización de la memoria subyacente juega con la velocidad de ejecución, aquí hay un gran enlace para acelerar los pandas :

Zakir
fuente
6

En pandas, puede hacer str.lencon su límite y usar el resultado booleano para filtrarlo.

df[df['column name'].str.len().lt(2)]
YOBEN_S
fuente
3

Si desea soltar filas de marco de datos sobre la base de alguna condición complicada en el valor de la columna, escribir eso de la manera que se muestra arriba puede ser complicado. Tengo la siguiente solución más simple que siempre funciona. Supongamos que desea colocar la columna con 'encabezado', así que primero obtenga esa columna en una lista.

text_data = df['name'].tolist()

ahora aplique alguna función en cada elemento de la lista y póngalo en una serie de panda:

text_length = pd.Series([func(t) for t in text_data])

en mi caso solo estaba tratando de obtener el número de tokens:

text_length = pd.Series([len(t.split()) for t in text_data])

ahora agregue una columna adicional con la serie anterior en el marco de datos:

df = df.assign(text_length = text_length .values)

ahora podemos aplicar condiciones en la nueva columna como:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
jayanti prasad
fuente