ENH: Series.idxnonzero() to return the index of nonzero entries
State: closed

Added a straightforward Serie function '`idxnonzero`', similar to `idxmin`/`idxmax`

When filtering data, a `nonzero` pattern is actually very handy; yet panda’s version returns the iloc (for compatibility with numpy), which prevent to use the result on a differently-shaped dataframe, defeating the power of pandas indexing.

This simple patch propose a version of nonzero which returns an actual pandas Index.


Labels:
name: API Design

Comments:
You are proposing a function to do this

```
In [1]: dat = pd.Series(index=list("abcde"), data=[0, 1, 2, "3", False])

In [2]: dat
Out[2]: 
a        0
b        1
c        2
d        3
e    False
dtype: object

In [6]: dat.index[dat.values.nonzero()[0]]
Out[6]: Index([u'b', u'c', u'd'], dtype='object')
```

Why wouldn't a user simply do this?

```
In [7]: dat[dat!=0]
Out[7]: 
b    1
c    2
d    3
dtype: object

In [8]: dat[dat!=0].index
Out[8]: Index([u'b', u'c', u'd'], dtype='object')
```

For the same reason that nonzero is used in the first place, even though boolean masking works well: different usage pattern.

On non-trivial filters (e.g. `dfB.loc[dfA.groupby(["some", "criterions"]).count().idxnonzero()]` ), using the bracket masking syntax requires to define a new temporary variable [or worse, define a whole new function when affectation is forbidden (think list comprehension, groupby...)] with no real semantic value. In some cases, those temporaries variables reduce the code readability.
And on interactive ipython sessions, where a lot of trial and error occurs when forging/analysing frames, it was more comfortable after i added this idxnonzero() shortcut.

The current pandas nonzero matches the raw numpy version for compatibility purpose, so i think altering it would be difficult and not necessary better. Thus the new suggested function, which is really a small one-liner with a quite-explicit name, therefore no risk of cluttering pandas api IMHO.

I'm not _strongly_ opposed to adding this method, but it's not something that I've missed. I think @jreback's alternative is pretty nice.

Well, for the trivial case above, obviously `dat[dat != 0]` is the most natural syntax. But on cases where "dat" doesn't exist as such (e.g. is the result of a call), it requires to affect it to a new temporary variable, with all the drawback stated above.

I propose this PR because i saw the benefit often on my work pattern. I'm not proposing that idxnonzero should be used instead of masking, more i'm proposing to improve pandas.nonzero() for those case where it fits.

@bthyreau you have a nice idea but I think more general soln might be to do this. It is slightly more verbose, but more general.

we add a new method `.select_data()` and just deprecate `.select()` for now (and really change it in the next version - this would be a hard break).

`.select()`, this is just a wrapper around `.reindex()` and `.filter()` is currently a good way to do everything it does (`.filter()` technically uses `.select()` but this is easily fixed)
Right now there are 2 + ways to limit columns/items (`.filter/.select/reindex/.loc`) which is way too many.

```
def select_data(self, f, axis=0)
""" select the data according to the provided criteria, return the new data """
```

and usage (in this case)

`.select_data(lambda x: x[x!=0].index)`

so you can generically do chained selection (this is analagous to the new metthod `.assign()` in #9239), or using your example:
`dfB.loc[dfA.groupby(["some", "criterions"]).count().select_data(lambda x: x[x!=0].index)]`

@shoyer 
@jorisvandenbossche 

note: we could technically just do this with `.loc()`, not sure if that is too magical!
`dfB.loc[dfA.groupby(["some", "criterions"]).count().loc(lambda x: x[x!=0].index)]`

see also #8717 

we could call this `.having(...)` which is a nice clean name for this 

which could work for both groupby and series/frames

Thinking again about this, we already have `.where()` (and the related discussion in #8717 was about extending groupby to have a `.where()` to do exactly this.....

We could extend `.where()` to accept a callable (making up the syntax below a bit)

```
In [12]: (s<10).where(lambda x: x!=0).dropna().index
Out[12]: Index([u'b', u'c', u'd'], dtype='object')
```

few more thought, after hiting that use case a few more times this morning.

The point of having `idxnonzero()` (or `idxtrue`) is to avoid the heavy generic machinery on the specific - but common - case of getting the index of TRUE entries from a Series. A functionally-equivalent alternative for me would be a `.dropfalse()`, (similar to `dropna()`), or `.dropvalue(condition)`, which would be a bit similar to the `where+dropna` suggestion above, but more practical. 

It was surprised to read that it was not missed, but i noticed that i need that function mostly during IPython data exploration and analysis sessions, not when writting a full, final program/notebook (for eg. interactively working out a new filtering condition currently need to go through affect+mask+see at every change). 

The proposal for a generic `select_data` could be convenient for some cases, yet i'm not sure yet what that would be adding to the existing pandas functions, such as boolean masking, df.query(), the dropna() trick, etc. They are already powerful, generic mechanism. From my POV of course.

closing pls reopen if/when updated