He estado explorando cómo optimizar mi código y encontré el método pandas .at. Según la documentación

Accesor escalar rápido basado en etiquetas

De manera similar a loc, at proporciona búsquedas escalares basadas en etiquetas. También puede establecer el uso de estos indexadores.

Así que corrí algunas muestras:

Preparar

import pandas as pd
import numpy as np
from string import letters, lowercase, uppercase

lt = list(letters)
lc = list(lowercase)
uc = list(uppercase)

def gdf(rows, cols, seed=None):
    """rows and cols are what you'd pass
    to pd.MultiIndex.from_product()"""
    gmi = pd.MultiIndex.from_product
    df = pd.DataFrame(index=gmi(rows), columns=gmi(cols))
    np.random.seed(seed)
    df.iloc[:, :] = np.random.rand(*df.shape)
    return df

seed = [3, 1415]
df = gdf([lc, uc], [lc, uc], seed)

print df.head().T.head().T

df se ve así:

            a                                        
            A         B         C         D         E
a A  0.444939  0.407554  0.460148  0.465239  0.462691
  B  0.032746  0.485650  0.503892  0.351520  0.061569
  C  0.777350  0.047677  0.250667  0.602878  0.570528
  D  0.927783  0.653868  0.381103  0.959544  0.033253
  E  0.191985  0.304597  0.195106  0.370921  0.631576

Vamos a utilizar .at y .loc y asegurarme de que obtengo lo mismo

print "using .loc", df.loc[('a', 'A'), ('c', 'C')]
print "using .at ", df.at[('a', 'A'), ('c', 'C')]

using .loc 0.37374090276
using .at  0.37374090276

Pruebe la velocidad con .loc

%%timeit
df.loc[('a', 'A'), ('c', 'C')]

10000 loops, best of 3: 180 µs per loop

Pruebe la velocidad con .at

%%timeit
df.at[('a', 'A'), ('c', 'C')]

The slowest run took 6.11 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 8 µs per loop

Esto parece ser un gran aumento de velocidad. Incluso en la etapa de almacenamiento en caché 6.11 * 8 es mucho más rápido que 180

Pregunta

¿Cuáles son las limitaciones de .at? Estoy motivado para usarlo. La documentación dice que es similar a .loc pero no se comporta de manera similar. Ejemplo:

# small df
sdf = gdf([lc[:2]], [uc[:2]], seed)

print sdf.loc[:, :]

          A         B
a  0.444939  0.407554
b  0.460148  0.465239

Donde como print sdf.at[:, :] da como resultado TypeError: unhashable type

Así que obviamente no es la misma incluso si la intención es ser similar.

Dicho esto, ¿quién puede proporcionar orientación sobre qué se puede y qué no se puede hacer con el método .at?

66
piRSquared 13 may. 2016 a las 20:57

3 respuestas

La mejor respuesta

Actualización: df.get_value está en desuso a partir de la versión 0.21.0. Usar df.at o df.iat es el método recomendado en el futuro.


df.at solo puede acceder a un único valor a la vez.

df.loc puede seleccionar múltiples filas y / o columnas.

Tenga en cuenta que también hay df.get_value , que puede ser aún más rápido para acceder a valores individuales:

In [25]: %timeit df.loc[('a', 'A'), ('c', 'C')]
10000 loops, best of 3: 187 µs per loop

In [26]: %timeit df.at[('a', 'A'), ('c', 'C')]
100000 loops, best of 3: 8.33 µs per loop

In [35]: %timeit df.get_value(('a', 'A'), ('c', 'C'))
100000 loops, best of 3: 3.62 µs per loop

Debajo del capó, df.at[...] llamadas {{X1 }}, pero también algún tipo comprobar en las teclas.

48
unutbu 30 dic. 2017 a las 12:04

.at es un método de acceso a datos optimizado en comparación con .loc.

.loc de un marco de datos selecciona todos los elementos ubicados por indexed_rows y Label_columns como se indica en su argumento. Insetad, .at selecciona un elemento particular de un marco de datos posicionado en el dado indexed_row y etiquetado_column.

Además, .at toma una fila y una columna como argumento de entrada, mientras que .loc puede tomar varias filas y columnas. Oputput usando .at es un elemento único y usando .loc puede ser una Serie o un Marco de Datos.

0
Vikranth Inti 20 nov. 2019 a las 14:52

Como preguntaste sobre las limitaciones de .at, aquí hay una cosa con la que me topé recientemente (usando pandas 0.22). Usemos el ejemplo de la documentación:

df = pd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], index=[4, 5, 6], columns=['A', 'B', 'C'])
df2 = df.copy()

    A   B   C
4   0   2   3
5   0   4   1
6  10  20  30

Si ahora lo hago

df.at[4, 'B'] = 100

El resultado se ve como se esperaba

    A    B   C
4   0  100   3
5   0    4   1
6  10   20  30

Sin embargo, cuando trato de hacer

 df.at[4, 'C'] = 10.05

parece que .at intenta conservar el tipo de datos (aquí: int) :

    A    B   C
4   0  100  10
5   0    4   1
6  10   20  30

Eso parece ser una diferencia para .loc:

df2.loc[4, 'C'] = 10.05

Produce el deseado

    A   B      C
4   0   2  10.05
5   0   4   1.00
6  10  20  30.00

Lo arriesgado en el ejemplo anterior es que sucede en silencio (la conversión de float a int). Cuando uno intenta lo mismo con cadenas, arrojará un error:

df.at[5, 'A'] = 'a_string'

ValueError: literal no válido para int () con base 10: 'a_string'

Sin embargo, funcionará si uno usa una cadena en la que int() realmente funciona como lo indica @ n1k31t4 en los comentarios, p.

df.at[5, 'A'] = '123'

     A   B   C
4    0   2   3
5  123   4   1
6   10  20  30
26
Cleb 3 jul. 2019 a las 20:06