ENH: add .iloc attribute to provide location-based indexing
State: closed

Updated to include new indexers:

`.iloc` for pure integer based indexing
`.loc` for pure label based indexing
`.iat` for fast scalar access by integer location
`.at` for fast scalar access by label location

Much updated docs, test suite, and example

In the new `test_indexing.py`, you can change the `_verbose` flag to True to get more test output
anybody interested can investigate a couple of cases marked `no comp` which are where the new
indexing behavior differs from `.ix` (or `.ix` doesn't work); this doesn't include cases where a `KeyError/IndexError` is raised (but `.ix` let's these thru)

Also, I wrote `.iloc` on top of `.ix` but most methods are overriden, it is possible that this let's something thru that should not, so pls take a look

_Please try this out and let me know if any of the docs or interface semantics are off_


Labels:
name: Enhancement

Comments:
Rad. I am really looking forward to this being merged in master.

can you give a try....let me know any issues?

also...here's a 'feature' that is included

if you specify a 'label'  to .loc it will throw a ValueError; This is true EVEN IF the label actually exists and is in the requested axis...'label' means not (integer or slice)

```
df.loc['a',:] 
df.loc['1',:]
```

will ALWAYS fail, no matter the index

anyone have an issue with that?

Actually, it does what I expect even with a multiIndex, what is it that you think is missing?
It's consistent with .ix in that picking a single col/row returns a series raher then a dataframe,
but I think both cases are a wart.

Great stuff, I'd use this all the time.

I hate to bikeshed but what are people's thought on what this should be called? Either `at` or `loc` prolly works. We'd thought about `iix` also "integer `ix`" but I dunno about that

do you think a purely label based is needed at all?
(I ask because what would u call that, `label`?)

if not i'd vote for `at` or `loc`, maybe not use `iix`...too much issue of typos

I actually like iix.

`crop`?

@y-p i was missing test cases for mi

I liked `loc`, because it is pretty clear as to what it performs, but using this is then an departure from the use of `i` nomenclature for other exiting lookup methods (i.e., `irow` and `icol`). Whatever the call, the method names should all fall in line for consistency.

+1 for loc and iloc.  

I also think a purely label-based equivalent (eg one with strict flags) would be helpful.  Of course, it just comes down to overloading the amount of slicing options present to new users.

As has already been discussed on the mailing list, when labels are numerical, having a clear option for slicing by row and slicing by value, and raising errors for misuse is quite helpful.

For example, imagine I have spectral data running from 400.0 - 700.0nm.  If users are slicing by value, it's too easy for them to do [400:700] when they mean [400.0:700.0] or [400:700.0] and I'd prefer my programs bring this to their attention than assume their intentions.  

how about `iloc`?

or `il`?

+1 for decree from wes.

Also like suggested `iloc` or `il`. Sounds like _integer location_.  Nice. 

Edit: I meant integer.

any consensus on the name?

`il` : too short, not clear on meaning
`iix` : pretty good, except can accidently invode `ix`
`at` : ok
`iat` : ?
`loc` : ok
`iloc` : keeps the nomeclature of `irow,icol,iget_value`
`crop` : keeps meaning?

so...if choose between:

`loc`, `iloc` , `at`

+1 `iloc`

In detail: I gave this a day to think about and while I do like `loc`, I am leaning now more towards `iloc`. This is because if I am ever looking to do any sort of advanced indexing, I immediately hammer out `.i<Tab>` to view all the methods. I didn't even notice how much I do this until I started to actively think about it and it's clear that I've hardwired myself based off of this existing nomenclature! 

On that note, I think using `iloc` maintains consistency with other indexing methods and also, as a method name, indicates what operation is performed.

Jeff, you had mentioned that you were possibly going to have 2 functions here... one that does strict by-index slicing and one that does strict by-name slicing.  If so, why not call the by-index version "iloc" and the by-name version "loc"?

I wouldn't call it `loc`, would be then too confusing and easy to accident. Is there something that DOESN't work using `.ix`? 

If you _know_ that you are looking for a label (and have chosen `.ix` for that reason, this is a label based indexer. I guess there might be the case where the label doesn't exist and its an integer and you have integers as the index and you want to raise a `KeyError` instead of returning based on the location?

I wouldn't call it `loc` either but I think it'd be useful: there are lots of weird corner cases (see #2727, for example) where the choice of integer vs label-based indexing is basically impossible to predict without looking at the code. (the monotonicity of the index has an effect, too, which mean that two indices with all the same values except one can silently result in different behavior, even if the changed value is not part of the index expression).

so the proposal is then:

`iloc` stricly integer-based
`lab` or `label`  strickly label-based
`ix` try label, fall back to integer (backward-compatibly)

I think can get this by essentially making `ix` the label guy, and having it raise instead of falling back,
which the new `ix` can catch and then call `iloc`

and suggestions for label getter?

?

well, it would be nice that the existing `ix` behavior were clean enough to factor into a big `try: label_based(); except: position_based()`, but I actually really doubt that's the case right now...the logic is pretty spaghetti and when i tried make is more sane while fixing #2727 lots of tests broke (which is why the final behavior is still weird, although consistent between `__getitem__` and `__setitem__`)...so I think the safest thing to do is to put up with the leaving the spaghetti in `ix` and the possible code duplication between three indexers. 

i could easily be wrong though :)

your are welcome to have a go!

but he basic idea to have a label based getter? (and only labels)

well i think the idea is good i just think it'll necessarily have to be a new, third, code path (even if it's a mostly trivial one)...I'm looking at the positional/label choice logic in `_convert_to_indexer` right now and I don't think it's possible to encapsulate it with a big try-except (for one thing, there are cases right now that integer indexing is preferred for whatever reason even if label-based indexing could be possible, like in #2727...example `df.ix[1.0:4]` is positional on a floating index even though the underlying `Index` will happily take an integer label and cast it up to a float if you ask it to, and IIRC it breaks tests when you change this)

ok...the obvious question then....

should we cause an API change where by `.ix` _does not fall back_ ? (and instead raises), then this _de factor_ is the label based indexer

```
>>> df = DataFrame(np.random.randn(8, 4), index=range(0, 16, 2))
>>> df
           0         1         2         3
0   0.730481  1.529788 -0.581710  0.616712
2   0.453565 -0.859765 -1.271082 -0.818614
4  -0.923394 -0.887154  2.681521 -1.367626
6  -0.214502 -0.044165 -0.027145 -1.204357
8   0.332970 -1.202543  1.275269  0.197951
10 -0.715645  0.262102 -1.950028  0.807226
12  1.283404  0.106109 -1.635975 -0.480751
14  1.582952 -1.137347 -0.163757  0.120903

>>> df.ix[3]
KeyError: 3

#### essentially causing this not to work ####
>>> df.ix[2]
0    0.453565
1   -0.859765
2   -1.271082
3   -0.818614
Name: 2
```

well, it'd have to be an clearly documented API change then, for sure :)

@wesm care to chime in?

(i'm not sure I understand your example by the way...which line do you mean not working? `df.ix[2]` would work still because it's label-based and `df.ix[3]` already fails because the "fallback" logic avoids location-based indexing on an integer indexer, because of the ambiguity...)

you are right my example is wrong....what I actually mean is that integers would ONLY be for labels that match, and not have any positional meaning (float indicies are another issue)....

btw, I was trying to generate an example of something that's currently a fallback to position-based and would become disallowed and discovered this...

``` python
In [91]: df = DataFrame(np.random.randn(8, 4), index=[2, 4, 6, 8, 'null', 10, 12, 14])

In [92]: df
Out[92]: 
             0         1         2         3
2    -0.951922  0.502621  0.346998 -0.784631
4     1.073580 -1.030964  0.783075  0.283990
6    -0.290176  0.236777 -0.042059 -2.613214
8     0.082795  1.196050 -1.983549  2.973472
null -0.345000 -0.998171  1.035359  1.378678
10    1.762567 -0.706646 -1.591715  0.344561
12   -0.219641 -0.786794  0.228584 -0.808036
14    0.411628  0.427615  0.270707  0.160328

In [93]: df.ix[2] # <-- position-based???
Out[93]: 
0   -0.290176
1    0.236777
2   -0.042059
3   -2.613214
Name: 6, Dtype: float64

In [94]: df.ix['null']
Out[94]: 
0   -0.345000
1   -0.998171
2    1.035359
3    1.378678
Name: null, Dtype: float64
```

so I presume if you had some big csv where a string happened to pop in where it wasn't supposed to for some reason and you didn't notice it, you'd silently change the semantics of all your integer indexes... :/ (unless there's some explicit data sanitation logic somewhere to handle this...) 

all the more reason to provide a way to eliminate the ambiguity if possible (just not sure if breaking `ix` is worth it vs making a new attribute...)

here's the fallbacker

```
>>> df.ix[1]
0   1.073580 
1  -1.030964  
2   0.783075  
3   0.283990
Name: 4, Dtype: float64
```