テーブルデータのカテゴリ特徴量の組み合わせ方


# 何が書かれているか
テーブルデータのカテゴリ特徴量を組み合わせることで、相互作用（積）を表す新たなカテゴリ特徴量を作る方法です。
Kaggleのテーブルコンペ参加中に利用しました。

#具体的な方法

下記のようなカテゴリ特徴量cat1, cat2, cat3を持つ訓練データtrainを考えます。

||cat1|cat2|cat3|cat4|
|-:|---:|---:|---:|---:|
|0|A|A|C|B|
|1|B|A|D|C|
|2|C|B|A|D|
|3|D|B|A|C|
|4|E|C|B|A|

目標はそれぞれの特徴量の組み合わせ(積)を作ることです。

||combo_1_2|combo_1_3|combo_1_4|combo_2_3|combo_2_4|combo_3_4|
|-:|-:|-:|-:|-:|-:|-:|
|0|AA|AC|AB|AC|AB|CB|
|1|BA|BD|BC|AD|AC|DC|
|2|CB|CA|CD|BA|BD|AD|
|3|DB|DA|DC|BA|BC|AC|
|4|EC|EB|EA|CB|CA|BA|

また、テストデータとして以下のtestがあるとします。
testにしか含まれない値があります。

||cat1|cat2|cat3|cat4|
|-:|---:|---:|---:|---:|
|0|A|A|C|<font color="Red">E</font>|
|1|B|A|D|C|
|2|C|B|A|D|
|3|D|B|A|C|
|4|E|C|B|A|

[itertools](https://docs.python.org/3/library/itertools.html#itertools.combinations)というビルトインのモジュールを使います。
カラム同士の組み合わせを作ることで特徴量同士の組み合わせを表せます。

itertoolsの使い方は以下の記事を参考にしました。
[すごいぞitertoolsくん](https://qiita.com/anmint/items/37ca0ded5e1d360b51f3)

```combination.py
import pandas as pd
import itertools

train = pd.DataFrame({
    'cat1': ['A', 'B', 'C', 'D', 'E'],
    'cat2': ['A', 'A', 'B', 'B', 'C'],
    'cat3': ['C', 'D', 'A', 'A', 'B'],
    'cat4': ['B', 'C', 'D', 'C', 'A']
})

test = pd.DataFrame({
    'cat1': ['A', 'B', 'C', 'D', 'E'],
    'cat2': ['A', 'A', 'B', 'B', 'C'],
    'cat3': ['C', 'D', 'A', 'A', 'B'],
    'cat4': ['E', 'C', 'D', 'C', 'A']
})

#特徴量のリストを作る
cat_features = ['cat1', 'cat2', 'cat3', 'cat4']

#特徴量の組み合わせ
#引数はリスト(iterable)と組み合わせの長さ
combinations = itertools.combinations(cat_features, 2)

#組み合わせの特徴量の作成
for c in combinations:
    train[f'combo_{c[0]}_{c[1]}'] = train[c[0]].add(train[c[1]])
    test[f'combo_{c[0]}_{c[1]}'] = test[c[0]].add(test[c[1]])

print(train.columns)
--->
 Index(['cat1', 'cat2', 'cat3', 'cat4', 'combo_cat1_cat2', 'combo_cat1_cat3',
       'combo_cat1_cat4', 'combo_cat2_cat3', 'combo_cat2_cat4','combo_cat3_cat4'],
        dtype='object')

#作成した特徴量の確認
combo_features = [col for col in table.columns if 'combo' in col]

print(train[combo_features])
--->
 combo_cat1_cat2 combo_cat1_cat3 combo_cat1_cat4 combo_cat2_cat3  \
0             AA              AC              AB              AC   
1             BA              BD              BC              AD   
2             CB              CA              CD              BA   
3             DB              DA              DC              BA   
4             EC              EB              EA              CB   

  combo_cat2_cat4 combo_cat3_cat4  
0              AB              CB  
1              AC              DC  
2              BD              AD  
3              BC              AC  
4              CA              BA  

print(test[combo_features])
--->
  combo_cat1_cat2 combo_cat1_cat3 combo_cat1_cat4 combo_cat2_cat3  \
0              AA              AC              AE              AC   
1              BA              BD              BC              AD   
2              CB              CA              CD              BA   
3              DB              DA              DC              BA   
4              EC              EB              EA              CB   

  combo_cat2_cat4 combo_cat3_cat4  
0              AE              CE  
1              AC              DC  
2              BD              AD  
3              BC              AC  
4              CA              BA  
```
テストデータにしか含まれない値もあるため、trainとtestそれぞれに固有の値を含むカラムを抽出してみます。

```combination.py

unmatched_features = []

for col in combo_features:
    train_unique = set(train[col].unique())
    test_unique = set(test[col].unique())

    if train_unique != test_unique:
        print(col)
        print('values only in train')
        print(train_unique - test_unique)
        print('values only in test')
        print(test_unique - train_unique)
        print('+'*30)
        unmatched_features.append(col)

--->
combo_cat1_cat4
values only in train
{'AB'}
values only in test
{'AE'}
++++++++++++++++++++++++++++++
combo_cat2_cat4
values only in train
{'AB'}
values only in test
{'AE'}
++++++++++++++++++++++++++++++
combo_cat3_cat4
values only in train
{'CB'}
values only in test
{'CE'}
++++++++++++++++++++++++++++++

print(unmatched_features)
--->
['combo_cat1_cat4', 'combo_cat2_cat4', 'combo_cat3_cat4']

#モデル学習用に使える特徴量を残します。
train = train.drop(unmatched_columns, axis=1)

print(train)
--->
  cat1 cat2 cat3 cat4 combo_cat1_cat2 combo_cat1_cat3 combo_cat2_cat3
0    A    A    C    B              AA              AC              AC
1    B    A    D    C              BA              BD              AD
2    C    B    A    D              CB              CA              BA
3    D    B    A    C              DB              DA              BA
4    E    C    B    A              EC              EB              CB

```
この後はカテゴリ変数として変換するのですが、target encodingが有効と考えられます。
目的変数の平均を計算するグループがより細分化されるため、より特徴的な傾向を捉えられる可能性が高くなるからです。（出典:[Kaggleで勝つデータ分析の技術](https://gihyo.jp/book/2019/978-4-297-10843-4)）


より簡単な方法や便利な方法もあると思いますので、教えていただけますと嬉しいです。
Twitter: https://twitter.com/nicephosphorus