pandas .at versus .loc
<p>I've been exploring how to optimize my code and ran across <code>pandas</code> <code>.at</code> method. Per the <a href="http://pandas.pydata.org/pandas-docs/version/0.17.0/generated/pandas.DataFrame.at.html#pandas.DataFrame.at" rel="noreferrer">documentation</a></p>
<blockquote>
<p>Fast label-based scalar accessor</p>
<p>Similarly to loc, at provides label based scalar lookups. You can also set using these indexers.</p>
</blockquote>
<p>So I ran some samples:</p>
<h1>Setup</h1>
<pre><code>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):
    &quot;&quot;&quot;rows and cols are what you'd pass
    to pd.MultiIndex.from_product()&quot;&quot;&quot;
    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
</code></pre>
<p><code>df</code> looks like:</p>
<pre><code>            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
</code></pre>
<p>Lets use <code>.at</code> and <code>.loc</code> and ensure I get the same thing</p>
<pre><code>print &quot;using .loc&quot;, df.loc[('a', 'A'), ('c', 'C')]
print &quot;using .at &quot;, df.at[('a', 'A'), ('c', 'C')]

using .loc 0.37374090276
using .at  0.37374090276
</code></pre>
<p>Test speed using <code>.loc</code></p>
<pre><code>%%timeit
df.loc[('a', 'A'), ('c', 'C')]

10000 loops, best of 3: 180 µs per loop
</code></pre>
<p>Test speed using <code>.at</code></p>
<pre><code>%%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
</code></pre>
<p>This looks to be a huge speed increase.  Even at the caching stage <code>6.11 * 8</code> is a lot faster than <code>180</code></p>
<h1>Question</h1>
<p>What are the limitations of <code>.at</code>?  I'm motivated to use it.  The documentation says it's similar to <code>.loc</code> but it doesn't behave similarly.  Example:</p>
<pre><code># small df
sdf = gdf([lc[:2]], [uc[:2]], seed)

print sdf.loc[:, :]

          A         B
a  0.444939  0.407554
b  0.460148  0.465239
</code></pre>
<p>where as <code>print sdf.at[:, :]</code> results in <code>TypeError: unhashable type</code></p>
<p>So obviously not the same even if the intent is to be similar.</p>
<p>That said, who can provide guidance on what can and cannot be done with the <code>.at</code> method?</p>