ENH/GBY: add nlargest/nsmallest to Series.groupby
State: closed

closes #7053


Labels:
name: Enhancement
name: Groupby

Comments:
maybe add an example in groupby.rst docs (example section?)

yep

wonder if `nlargest`/`nsmallest` make sense for `DataFrame`, like:

``` python
df.nlargest('column_name', 3)
```

impl like:

``` python
def nlargest(self, col, n):
    return self.loc[self[col].nlargest(n).index]
```

just apply it

same with value_counts (which FYI is still awaiting impl, but a native one).

i don't think `value_counts` would make sense bc you get a different sized `Series` for every column in the general case

https://github.com/pydata/pandas/pull/5381 (not sure why we are not just applying it); though this may be MUCH faster

you can't really just apply it, tho maybe for multiple columns since it's a partial sort you have to pick a column to sort by, really these methods are more general versions of `sort`. you also can't apply nlargest/nsmallest to  object dtype ATM

alright i GUESS `value_counts` makes sense ... not sure what the heck i would do with t result

```
In [8]: df = DataFrame(np.arange(10*10).reshape(-1,10))

In [9]: df
Out[9]: 
    0   1   2   3   4   5   6   7   8   9
0   0   1   2   3   4   5   6   7   8   9
1  10  11  12  13  14  15  16  17  18  19
2  20  21  22  23  24  25  26  27  28  29
3  30  31  32  33  34  35  36  37  38  39
4  40  41  42  43  44  45  46  47  48  49
5  50  51  52  53  54  55  56  57  58  59
6  60  61  62  63  64  65  66  67  68  69
7  70  71  72  73  74  75  76  77  78  79
8  80  81  82  83  84  85  86  87  88  89
9  90  91  92  93  94  95  96  97  98  99

In [10]: df.apply(lambda x: x.nlargest(3))
Out[10]: 
    0   1   2   3   4   5   6   7   8   9
9  90  91  92  93  94  95  96  97  98  99
8  80  81  82  83  84  85  86  87  88  89
7  70  71  72  73  74  75  76  77  78  79

In [11]: df.apply(lambda x: x.nlargest(3),axis=1)
Out[11]: 
    9   8   7
0   9   8   7
1  19  18  17
2  29  28  27
3  39  38  37
4  49  48  47
5  59  58  57
6  69  68  67
7  79  78  77
8  89  88  87
9  99  98  97
```

I agree if its partially sorted then would be much faster

Makes a cool pattern

```
In [13]: df.apply(lambda x: x.value_counts())
Out[13]: 
     0   1   2   3   4   5   6   7   8   9
0    1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1  NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
2  NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
3  NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
4  NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
5  NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
6  NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
7  NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
8  NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
9  NaN NaN NaN NaN NaN NaN NaN NaN NaN   1
10   1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
11 NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
12 NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
13 NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
14 NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
15 NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
16 NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
17 NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
18 NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
19 NaN NaN NaN NaN NaN NaN NaN NaN NaN   1
20   1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
21 NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
22 NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
23 NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
24 NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
25 NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
26 NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
27 NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
28 NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
29 NaN NaN NaN NaN NaN NaN NaN NaN NaN   1
..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..
70   1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
71 NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
72 NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
73 NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
74 NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
75 NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
76 NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
77 NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
78 NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
79 NaN NaN NaN NaN NaN NaN NaN NaN NaN   1
80   1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
81 NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
82 NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
83 NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
84 NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
85 NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
86 NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
87 NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
88 NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
89 NaN NaN NaN NaN NaN NaN NaN NaN NaN   1
90   1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
91 NaN   1 NaN NaN NaN NaN NaN NaN NaN NaN
92 NaN NaN   1 NaN NaN NaN NaN NaN NaN NaN
93 NaN NaN NaN   1 NaN NaN NaN NaN NaN NaN
94 NaN NaN NaN NaN   1 NaN NaN NaN NaN NaN
95 NaN NaN NaN NaN NaN   1 NaN NaN NaN NaN
96 NaN NaN NaN NaN NaN NaN   1 NaN NaN NaN
97 NaN NaN NaN NaN NaN NaN NaN   1 NaN NaN
98 NaN NaN NaN NaN NaN NaN NaN NaN   1 NaN
99 NaN NaN NaN NaN NaN NaN NaN NaN NaN   1

[100 rows x 10 columns]

```

my point is that `df.apply(lambda x: x.nlargest(3))` has to choose an index ... like if you had 

```
In [26]: df
Out[26]:
    a   b
0   1  10
1   3   6
2   5   4
3   7   0
4   2   9
5   9   2
6   0   7
7   4   5
8   6   3
9  10   1

In [27]: df.apply(lambda x: x.nlargest(3))
Out[27]:
    a   b
0 NaN  10
3   7 NaN
4 NaN   9
5   9 NaN
6 NaN   7
9  10 NaN
```

it uses the last index

ahh, so it essentially needs a `.reset_index(drop=True)` as it doesn't make sense to keep the index at all; kind of like what `as_index=False` does in groupby, (maybe that should be an option then?)

it _does_ make sense to keep the index, just like in `df.sort('column_name')`

@jreback ok after a doc blurb in groupby.rst and v0.14.1.txt?