pythonでのntile(decile)関数

# やったこと
デシル分析の際にSQLでよく使っていたntile関数が検索しても、
出てこなかったので、関数を作成した。

SQLで「ntile最高。　で、なんでPythonにねーんだよ..」と感じた諸兄達に捧げる。

# ntile関数 (ちょい編集した)
まあ、使い方としては、
df=pandas dataframeを入れてもらい、
col=デシル分析したい対象のカラム名を入れるべし。
n=10としているが、100グループにしたい場合はn=100等にしてちょ。
n_tile=Trueとすると、decile数だけカラムとして付与するdataframeを返す、
Falseにすると各種集約関数付きdataframeを返す
agg=上記n_tile Falseにした場合の使いたい集約関数、
「sumだけでええわ」という方は、引数にagg=["sum"]として使う。
※コメントを英語にしてるのは英語の勉強中だから。


```python
def ntile(df, col, n=10, n_tile=True, ascending=False, aggs=["sum", "count", "min", "max", "median", "mean", "std"]):
    # check pandas dataframe type
    if type(df) == pd.core.frame.DataFrame:
        # check int or float
        if type(df[f"{col}"].max()) == np.int64 or type(df[f"{col}"].max()) == np.float64:
            ntile_df = pd.DataFrame()
            length = len(df)
            df = df.sort_values(col, ascending=ascending).reset_index(drop=True)
            div_n = math.ceil(len(df) / n)

            # mark N group 
            for idx, num in enumerate(range(0, length, div_n)):
                n_df = df[num: num+div_n]
                n_df["decile"] = idx + 1
                ntile_df = ntile_df.append(n_df)
            
            # return ntile_df if need only decile data
            if n_tile:
                return ntile_df
            
            # otherwise aggregate col
            agg_df = ntile_df.groupby("decile").agg({f"{col}": aggs}).reset_index()
            #  clean columns
            agg_df.columns = ["".join(c) for c in agg_df.columns]
            # merge two data
            ntile_df = ntile_df.merge(agg_df, on="decile").copy()
            
            return ntile_df
        
        raise TypeError(f"`{col}` must be int or float type")
    raise TypeError(f"`{type(df)}` must be pandas.core.frame.DataFrame type")
```

# サンプルデータを作成

```python
import pandas as pd
import random
# random price的なやつを作成
price = random.sample(range(10000, 900000), k=10000)
# 上記を用いてdataframeを作成する
data = pd.DataFrame(price, columns=["price"])
```

中身はこんな感じ。

![スクリーンショット 2019-11-10 13.14.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180478/2a61ff4c-b922-6bef-08c0-0d07697f3cdc.png)


# こんな感じに使う

```python
# dataを関数に入れちゃう。 n=10のデフォルトのまま
data = ntile(data, "price")
# decileでgroupbyして、各種集約関数を適用してみる。
data.groupby("decile").agg({"price": ["sum", "count", "min", "max", "median", "mean"]}).reset_index()
```

## n=10
![スクリーンショット 2019-11-10 13.17.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180478/0b0379a9-ae0e-bc0d-1b73-e2cafaf5734a.png)

## n=100
![スクリーンショット 2019-11-10 13.17.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180478/8ff0d9db-ed6e-ef8b-697c-a22e78477290.png)

# n_tile=Falseの場合は

```python
data = ntile(data, "price", n=5 , n_tile=False)

data.pricemin.value_counts().sort_index().reset_index().rename(columns={"index": "price_segment", "pricemin": "count"})
```

そしてこれを見て欲しい。

![スクリーンショット 2019-11-10 14.52.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180478/d581c821-ae69-96a4-0f1b-14410cdd8b4c.png)


下記のようなことをやったことはないだろうか？
そして「対象のカラムに階級数を全部n%ずつの階級を作ってくれる関数ってないのかね？？」
と感じたことはないだろうか？
![スクリーンショット 2019-11-10 14.48.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180478/29106d62-b3ac-a877-8e30-ff4c653941e8.png)

私は初心者なので上記を何度か思ったことがある。
毎回ntileを見て、手で修正していたが、今後はn=10の部分を変えるだけで変更が可能になった。
やっぱSQLよりpythonのが小回りが利く。

# 終わりに
検索不足か知らんけど、SQLでのntile関数がpython様に無いのが不思議だが、
まあ色々練習も兼ねて作ってみたから、SQLerからpythonerに転職した方はぜひ使ってみて欲しい。