Pandas.at против .loc

Я изучал, как оптимизировать свой код, и пробежал метод pandas .at. В документации

Быстрый сканирующий аксессуар на основе меток

Аналогично loc, при предоставлении скалярных запросов на основе ярлыков. Вы также можете установить эти индексы.

Итак, я запустил несколько образцов:

Настройка

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 выглядит следующим образом:

            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

Позволяет использовать .at и .loc и гарантировать, что я получаю то же самое

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

Проверить скорость с помощью .loc

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

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

Скорость тестирования с использованием .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

Это выглядит огромным увеличением скорости. Даже на этапе кэширования 6.11 * 8 выполняется намного быстрее, чем 180

Вопрос

Каковы ограничения .at? Я мотивирован использовать его. В документации говорится, что она похожа на .loc, но она не ведет себя аналогично. Пример:

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

print sdf.loc[:, :]

          A         B
a  0.444939  0.407554
b  0.460148  0.465239

где as print sdf.at[:, :] приводит к TypeError: unhashable type

Таким образом, очевидно, что это не так, даже если намерение должно быть аналогичным.

Тем не менее, кто может дать указания о том, что можно и не может сделать с помощью метода .at?

Ответ 1

Обновление: df.get_value устарело с версии 0.21.0. Рекомендуется использовать df.at или df.iat.


df.at может получать только одно значение за раз.

df.loc может выбирать несколько строк и/или столбцов.

Обратите внимание, что существует также df.get_value, что может быть еще быстрее при доступе к одиночным значениям:

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

Под капотом df.at[...] вызывает df.get_value, но также некоторые типы проверки на клавишах.

Ответ 2

Как вы спросили об ограничениях .at, вот одна вещь, с которой я недавно столкнулся (используя панд 0.22). Позвольте использовать пример из документации:

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

Если я сейчас сделаю

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

результат выглядит как ожидалось

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

Тем не менее, когда я пытаюсь сделать

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

похоже, что .at пытается сохранить тип данных (здесь: int):

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

Это похоже на разницу с .loc:

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

дает желаемый

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

Рискованным в приведенном выше примере является то, что это происходит тихо (преобразование из float в int). Когда вы пытаетесь сделать то же самое со строками, выдается ошибка:

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

ValueError: недопустимый литерал для int() с основанием 10: 'a_string'

Однако это сработает, если использовать строку, в которой int() действительно работает, как отмечено @n1k31t4 в комментариях, например

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

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