テキスト文書からキーワードにフラグを立てる(SPSS Modeler データ加工逆引き2-18)Python拡張ノード利用

# テキスト文書からキーワードにフラグを立てる（形態素解析）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/bd56513f-4729-87d7-de59-f79ee6ca56dd.png)


## 1.想定される利用目的
・アンケート結果や業務日誌などのテキストデータを分析に活用する。
・Modeler 18.0以前のバージョンでテキスト分析の機能で分かち書きを行っていた場合の代替案

## 2.サンプルストリームのダウンロード

#### サンプルストリーム
https://github.com/hkwd/210224PythonExtension/blob/master/231213janome/janome4.str

#### サンプルデータ
https://raw.githubusercontent.com/hkwd/210224PythonExtension/master/231213janome/music_survey.csv

- テスト環境
    - SPSS Modeler 18.4
    - Windows 11 64bit
    - Python 3.8.10
    - numpy 1.23.5
    - janome 0.5.0

この加工ではpythonの拡張ノードを使います。事前に以下の記事を参考に拡張ノードでPythonを利用可能な設定を行っておいてください。また、pipなどでjanomeを導入しておいてください。

#### SPSS Modelerの拡張ノードでPythonを利用する（ミニマム設定） 

https://qiita.com/kawada2017/items/d8f7db0cb1d15c1f47f4

## 3.サンプルストリームの説明

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b5a9b9a9-daed-f14d-3207-0eb93110668f.png)


### a.入力するデータは以下の通りです。
この[レビュー]列を形態素解析してキーワード抽出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/44447810-8ea4-5797-5b40-6dee4e51d87a.png)

### b.[フィルター]ノードを編集します。
ここではわかりやすくするために[回答者ID]と[レビュー]の列のみを残しています（必須ではありません）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/57226e16-dcaf-7392-64b9-2ac75012abc1.png)


### c.[拡張の変換]ノードを利用して、形態素解析をします。
[Python for Spark]を選び[Pythonのシンタックス]に以下のpythonコードを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/6ee80da2-1870-864c-b6d4-cdfe2f7fe76a.png)

```python:形態素解析
#ライブラリのインポート
import spss.pyspark.runtime
from pyspark.sql.types import *

import numpy as np
#janomeを利用
import janome
from janome.tokenizer import Tokenizer

print(np.version.version)
print(janome.__version__)

# コンテキストオブジェクト定義
cxt = spss.pyspark.runtime.getContext() 

# 形態素解析関数
def getTokens(row):
    # 元行を保存
    org = tuple([x for x in row])
    
    # 「レビュー」列を分かち書き
    t = Tokenizer()
    tokens=t.tokenize(row["レビュー"]) 
    # 名詞、動詞、形容詞、形容動詞を抜き出し
    words = [token.base_form for token in tokens \
         if token.part_of_speech.split(",")[0] in ["名詞","動詞","形容詞","形容動詞"]]
    
    # 元行に分かち書きしたデータを縦持ちにして追加
    res = []
    for word in words:
        res.append(org + (word,))
    return(res)
      
if  cxt.isComputeDataModelOnly():   
    #スキーマ設定。「Words」という新しい列を追加
    inputSchema = cxt.getSparkInputSchema() 
    outputSchema = StructType(inputSchema.fields \
        + [StructField('Words', StringType(), True)])
    cxt.setSparkOutputSchema(outputSchema)

else:
    #データ読込
    df_in = cxt.getSparkInputData()  
    #形態素解析
    df_out = df_in.rdd.flatMap(getTokens).toDF()
    #データ書出し
    cxt.setSparkOutputData(df_out)
```
プレビューをすると[Words]という新しい列ができており、分かち書きされた言葉が抽出されています。また、元のデータは[回答者ID]で一意でしたが、抽出された行数分出力されています。下の例ですと、[回答者ID]＝１のレコードは[小さい][軽い]の2レコードになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/c8d6296b-51f8-71a3-f596-502ac9b0213e.png)

次からこのデータを横持データに変換します。

### d.[データ型]ノードで[値の読み込み]をクリックし、[Words]をインスタンス化します。
![スクリーンショット 2022-06-16 13.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286322/8937fa31-27e7-60c9-ca48-7c89f7f02b87.png)

なお、尺度が[名義型]ではなく[不明]になった場合は、[Words]の種類が250を超えています。[この記事](https://qiita.com/416nishimaki/items/0522f72e897fcb222a61#%E5%B0%BA%E5%BA%A6%E4%B8%8D%E6%98%8E%E3%82%92%E5%90%8D%E7%BE%A9%E5%9E%8B%E3%81%AB%E3%81%99%E3%82%8B
)にあるように[ツール]>[ストリームのプロパティ]>[オプション]から以下の[名義型の最大メンバー]を256以上に変更します。


### e.[フラグ設定]ノードで縦横変換します。
[セット型フィールド]に[Words]を選択します。
[利用可能なセット値]を[→]ですべて[フラグ型フィールドを作成]のボックスにうつします（欲しいキーワードだけに絞ってもかまいません）。
[集計キー]にチェックをします。
[回答者ID]と[レビュー]を選択します。
これで[回答者ID]（と[レビュー]で）で1レコードになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e143c449-01ac-1512-5dc5-48307bfe9e4f.png)

### f.[テーブル]を確認します。
レビュー内のキーワードにフラグが立てられたことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/2e4275e4-93e9-5c5e-04af-572de8aaef0c.png)


#### 注意事項
- 単なるフラグではなく、出現回数にする場合には[再構成]ノードと[集計]ノードで件数にすることもできます。
- 単なるフラグではなく、重みも付けたい場合は、TF-IDF、Word2VecなどをつかったPythonコードにすることもできます。
- データソースがRDBの場合、CLOB型はModelerでは読み出せませんので、VARCHAR型への変換が必要です。

## 4.pythonコードの解説
Pythonコードを少し解説します。
分かち書きしたい列を業務に合わせて変更したり、抜き出したい品詞を選んで修正して利用してください。

def getTokens(row)という関数で形態素解析を行っています。


以下で、[回答者ID]の[レビュー]の列を取り出しています。
```python:元行を保存
org = tuple([x for x in row])
```

以下で、[レビュー]列を分かち書き（トークン化）しています。
例えば、「小さい、軽い」というテキストから「小さい」と「軽い」の２つの語(トークン)
を抜き出しています。
```python:「レビュー」列を分かち書き
t = Tokenizer()
tokens=t.tokenize(row["レビュー"]) 
```

以下は抜き出した語から名詞、動詞、形容詞、形容動詞のみを選んでいます。逆にいうと副詞、助詞、記号などは捨てています。用途に合わせて、抜き出したい品詞を選んでください。

また、token.base_formで活用されていない基本形(原形）を取り出しています。そのため「おシャレ」が「シャレる」という語（トークン）として抜き出されています。これは活用によってバリエーションが増え過ぎしまうとカテゴリ型として意味がなくなる可能性があるために行っています。

```python:名詞、動詞、形容詞、形容動詞を抜き出し
    words = [token.base_form for token in tokens \
         if token.part_of_speech.split(",")[0] in ["名詞","動詞","形容詞","形容動詞"]]
```

#### 参考：Python, Janomeで日本語の形態素解析、分かち書き（単語分割）
https://note.nkmk.me/python-janome-tutorial/

「words」変数に抜き出した語(トークン)が入っていますので、元行と一語ずつをつなげて、1行にして出力しています。
このため、[回答者ID]＝１のレコードが[小さい][軽い]の2レコードになります。

```python:元行に分かち書きしたデータを縦持ちにして追加
res = []
for word in words:
    res.append(org + (word,))
```


## 5.参考情報

#### 【リレー連載】わたしの推しノード – 一流エージェント「拡張ノード」。必要なタレントは外から連れてくれば良い！ | IBM ソリューション ブログ 

https://www.ibm.com/blogs/solutions/jp-ja/spssmodeler-push-node-22/

この記事は上の千代田さんの記事のPythonコードを利用しています。

#### janomeマニュアル

https://mocobeta.github.io/janome/

#### SPSS Modeler ノードリファレンス目次

https://qiita.com/416nishimaki/private/503037195081d3323eee

#### SPSS Modeler 逆引きストリーム集（データ加工）

https://qiita.com/416nishimaki/private/4bf813e6cba84b2a1008