Countvectorizerをしたら単語(1文字)が消える、不要後にされてしまう件について

# はじめに
日本語文章でBOWをするためにCountvectorizerを試していたのですが、どうもfitした単語とtransformしたあとのカラム(get_feature_names()で獲得が可能)が一致せず、fitに利用した単語が一部消えていることが判明しました。解決策を知らず、検索もなかなか時間がかかったため、まとめたいと思います。

# 環境
- python3.8
- Docker
- docker_image : Datascience notebook
- Jupyter Lab / jupyter notebook

# 追記

```
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(tokenizer=lambda txt: txt.split())
```

こちらが一番良かったです。正規表現は「......」のようなものが消えてしまいました。


# 問題
```
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

# ベクトル化する文字列
sample = np.array(['今日 晴れ 明日 曇'])

# CountVectorizer
vec_count = CountVectorizer()
# ベクトル化
vec_count.fit(sample)
X = vec_count.transform(np.array(["今日　雨　明日　雨　会社　電車　晴れ　会社　歩く"]))

print('Vocabulary size: {}'.format(len(vec_count.vocabulary_)))
print('Vocabulary content: {}'.format(vec_count.vocabulary_))
pd.DataFrame(X.toarray(), columns=vec_count.get_feature_names()) 
```

結果がこのように出力される
![bandicam 2021-04-03 23-33-49-114.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/329b7d36-015d-edd7-8c51-50af64d3d976.jpeg)

本来であれば「**雲**」という単語もカラムになってほしいが、なぜか府用語とされてしまう。

調べてみると、`すべての単一文字トークンは、デフォルトのトークナイザによって無視される`とのこと

英語が前提でできているため、「a」などは削除されるようにできているらしい。詳しくは[こちらの記事](https://www.javaer101.com/ja/article/3379703.html)を見ていただきたい。

# 解決策

https://analytics-note.xyz/machine-learning/bow-tfidf-one-character/

こちらの記事を参考にしたところ解決した。
やったこととしては、Countvectorizer(`token_pattern='(?u)\\b\\w+\\b'`)としただけ

```
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

# ベクトル化する文字列
sample = np.array(['今日 晴れ 明日 曇'])

# CountVectorizer
vec_count = CountVectorizer(token_pattern='(?u)\\b\\w+\\b')
# ベクトル化
vec_count.fit(sample)
X = vec_count.transform(np.array(["今日　雨　明日　雨　会社　電車　晴れ　会社　歩く"]))

print('Vocabulary size: {}'.format(len(vec_count.vocabulary_)))
print('Vocabulary content: {}'.format(vec_count.vocabulary_))
pd.DataFrame(X.toarray(), columns=vec_count.get_feature_names()) 
```

結果  
![bandicam 2021-04-03 23-47-05-916.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/9b95e71d-4a2b-c883-9fe4-1c643a3bce98.jpeg)


うまくいきました！


# 余談
[1つ目の記事](https://www.javaer101.com/ja/article/3379703.html)には`単一の文字トークンを語彙に含めたい場合は、衣装トークナイザーを使用する必要があります。`

```
vectorizer = CountVectorizer(tokenizer=lambda txt: txt.split())
```
とあり、こちらで解決したのですが、別の状態(入力が単純なnumpy配列じゃない?)のときにうまくいかず、すべてのカラムに1以上の値が割り当てられているという謎の現象が起きました。こちらについては`現在調査中`ですので、わかり次第追記したいと思います。

今回のサンプルコードであれば問題なく動いています。

```
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

# ベクトル化する文字列
sample = np.array(['今日 晴れ 明日 曇'])

# CountVectorizer
vec_count = CountVectorizer(tokenizer= lambda sample: sample.split())
# ベクトル化
vec_count.fit(sample)
X = vec_count.transform(np.array(["今日 雨　明日　雨　会社　電車　晴れ　会社　歩く"]))

print('Vocabulary size: {}'.format(len(vec_count.vocabulary_)))
print('Vocabulary content: {}'.format(vec_count.vocabulary_))
pd.DataFrame(X.toarray(), columns=vec_count.get_feature_names()) 
```
結果
![bandicam 2021-04-03 23-47-56-580.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/d7a31457-507c-a3b4-b343-521c73de97d0.jpeg)

`Warning`はでていますが、1文字の単語も機能しています。

# 最後に
日本語文章をCountvectorizerすると疎のベクトルになりやすいので、やはり扱いは難しいなと感じました。1文字が消えてしまう問題に気付かないで行ってしまう人もいそうですね (私も今になって気づきました)