nlp4j-local-searchで国交省の自動車不具合情報を検索・分析する ― ニッサンで相対的に多い故障装置を探す (Python)
はじめに
nlp4j-local-search は、Pythonから Apache Lucene をローカルに利用できる検索ライブラリです。
Version 0.6.1.1 では、次のような対話型CLIも利用できます。
nlp4j-local-search --lang ja
検索サーバーを別途立ち上げる必要はなく、
JSONL
↓
nlp4j-local-search
↓
Apache Lucene
という形で、ローカルのJSONLデータをそのまま検索・分析できます。
今回は、国土交通省の自動車不具合情報を加工したサンプルデータを使って、
- データをロードする
- 「ニッサン」に関する文書を検索する
-
malfunction_device_sを集計する - 全体と比較して、相対的に頻度が高い故障装置を探す
- 気になった故障装置に絞って再検索する
という流れを試します。
単純な全文検索だけではなく、
検索結果の特徴を見つけ、その特徴を使ってさらに検索する
という使い方の例です。
使用するデータ
今回のサンプルデータはこちらです。
JSONL:
https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl
gzip圧縮版:
https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl.gz
今回はgzip版を使います。
wget https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl.gz
nlp4j-local-search は .jsonl.gz を直接読み込めるため、展開する必要はありません。
インストール
Version 0.6.1.1 を使います。
pip install -q nlp4j-local-search==0.6.1.1
CLIを起動します。
nlp4j-local-search --lang ja
すると対話モードになります。
nlp4j-local-search
Language: ja
Auto analyze: False
Type 'help' or '?' for help.
>>
今回は日本語データなので、
--lang ja
を指定しています。
データをロードする
まず、国交省データを読み込みます。
>> load("mlit_202501-202512.jsonl.gz")
今回のデータでは、次のように約2,800件が読み込まれます。
Loaded 2,782 documents in 0.98 seconds (2,834 docs/sec).
これでApache Luceneのインデックスが作成され、検索可能になります。
データの例
JSONLの1行は、例えば次のようになっています。
{
"id": "2025-12-31_1",
"date": "2025-12-31",
"address_s": "大阪府",
"via_s": "HP",
"maker_s": "ニッサン",
"registration_s": "2018/09",
"type_s": "DAA-GFC27",
"name_s": "セレナ",
"odo_meter_s": "85,305 Km",
"engine_type_s": "MR20-SM24",
"malfunction_device_s": "燃料装置",
"event_date_s": "",
"text_ja": "ニッサン セレナ 燃料装置 インジェクターの故障により、エンジン警告ランプが点灯し、馬力不足になった。"
}
text_ja は日本語全文検索用のフィールドです。
一方、
maker_s
malfunction_device_s
name_s
address_s
などは、文字列をそのまま扱うKeywordフィールドです。
まず「ニッサン」を検索する
通常の全文検索から始めます。
>> search("text_ja:ニッサン")
例えば次のような結果が得られます。
[2025-07-29_1195] score=1.2078
ニッサン スカイライン その他 エアコンの温度調整が出来ない。
[2025-07-18_1276] score=1.2078
ニッサン セレナ エンジン 交差点でエンストした。
[2025-01-17_2672] score=1.2078
ニッサン ROOX エンジン 運転中、ノッキングすることがある。
...
この検索では420件の文書が該当しました。
しかし、420件を一件ずつ読んでいくのは大変です。
そこで次に、
ニッサンの不具合情報では、どの故障装置が全体と比較して多いのか?
を見てみます。
view() で故障装置を分析する
malfunction_device_s を、
text_ja:ニッサン
に該当する文書だけで集計してみます。
>> view("malfunction_device_s","text_ja:ニッサン")
結果は次のようになりました。
View: malfunction_device_s
Lucene query: text_ja:ニッサン
Matched documents: 420 / 2,782
Values are ordered by relative rate.
Rank Value Count All Count Relative Rate
---- -------------------- -------- ---------- --------------
1 電動機(モーター) 26 65 2.65x
2 緩衝装置 25 66 2.51x
3 燃料装置 35 122 1.90x
4 制動装置 83 292 1.88x
5 その他 45 229 1.30x
6 動力伝達 47 277 1.12x
7 非装置 1 6 1.10x
8 電気装置 21 149 0.93x
9 排ガス・騒音 8 65 0.82x
10 かじ取り 11 92 0.79x
ここで注目したいのが、
電動機(モーター)
です。
Relative Rateとは
今回、
電動機(モーター)
は、
ニッサン検索結果: 26件 / 420件
全データ: 65件 / 2,782件
でした。
nlp4j-local-search の view() では、この比率を比較してRelative Rateを計算します。
概念的には、
ニッサン文書での出現率
------------------------
全データでの出現率
です。
今回の場合、
(26 / 420) / (65 / 2782)
となり、約、
2.65x
です。
つまり、
「電動機(モーター)」という故障装置は、データ全体と比較すると、ニッサンに関する文書では約2.65倍の割合で現れている
と読めます。
単純な件数だけを見ると、
制動装置 83件
の方が多いですが、Relative Rateを見ることで、
そのメーカーに特有、あるいは相対的に特徴的な項目は何か
という観点からデータを見ることができます。
なぜ「電動機(モーター)」が多いのか?
この結果を見ると、
電動機(モーター) 2.65x
という値は少し興味深く見えます。
ニッサンは、
- e-POWER
- EV
など、電動モーターを駆動に利用する車種を比較的多く販売しています。
そのため、
ニッサン車では、他メーカーと比較して「電動機(モーター)」という故障装置分類が出やすいのではないか
という仮説は考えられます。
ただし、このデータだけから、
e-POWERやEVだから故障が多い
と結論づけることはできません。
Relative Rateが示しているのは、あくまで、
今回の不具合情報データの中で、その分類が相対的に多く現れている
という事実です。
車種構成、販売台数、登録台数、報告のされ方なども考慮する必要があります。
したがって、この結果は因果関係の証明ではなく、
次に詳しく調べるべき対象を見つけるための手がかり
として扱うのが適切です。
実際に「電動機(モーター)」で絞り込む
気になるカテゴリが見つかったので、今度はその文書を実際に検索します。
今回の値には、
(
)
が含まれています。
Lucene Query Parserでは括弧は特殊文字なので、そのまま、
malfunction_device_s:電動機(モーター)
と書くと、意図したKeyword検索になりません。
今回は次のように検索できます。
>> search('text_ja:ニッサン AND malfunction_device_s:""電動機(モーター)""')
これで、
ニッサン
AND
故障装置 = 電動機(モーター)
という条件で検索できます。
特殊文字をエスケープする方法もある
Lucene Query Syntaxとして括弧をエスケープする方法もあります。
>> search("text_ja:ニッサン AND malfunction_device_s:電動機\(モーター\)")
この方法でも検索できます。
このように、Lucene Query Syntaxをそのまま利用できるため、
全文検索
+
Keywordフィールド
+
AND条件
を一つのクエリで表現できます。
「検索 → 分析 → 再検索」
今回の操作で面白いのは、この流れです。
search("text_ja:ニッサン")
↓
ニッサンの文書を取得
↓
view("malfunction_device_s", "text_ja:ニッサン")
↓
特徴的な故障装置を発見
↓
電動機(モーター) = 2.65x
↓
search(
ニッサン
AND
電動機(モーター)
)
↓
実際の文書を確認
最初から、
電動機(モーター)
を検索しようと思っていたわけではありません。
まずデータを検索し、
view()
で特徴を確認し、
そこから新しい検索条件を見つけています。
これは、全文検索エンジンを単なる「文書検索」だけではなく、
テキストデータを探索するための道具
として使う例だと思います。
件数だけでは分からないこと
例えば今回、
制動装置
はニッサン文書で83件あります。
一方、
電動機(モーター)
は26件です。
単純な件数だけなら、
83 > 26
なので制動装置の方が目立ちます。
しかしデータ全体では、
制動装置 292件
電動機(モーター) 65件
です。
全体の分布を考慮すると、
電動機(モーター) 2.65x
制動装置 1.88x
となります。
このようにRelative Rateを見ることで、
件数が多い項目
と、
その検索条件に対して特徴的な項目
を分けて見ることができます。
Luceneをローカル分析にも使う
Apache Luceneというと、
検索キーワード
↓
検索結果一覧
という使い方をイメージしがちです。
しかし、検索結果に対して集計を組み合わせると、
検索
↓
部分集合
↓
フィールド集計
↓
全体との比較
↓
特徴発見
という使い方もできます。
nlp4j-local-search ではこれを、
search()
view()
というシンプルなCLIで試せるようにしています。
サーバーを立てなくてもLuceneを試せる
従来、このような検索・集計を試そうとすると、
- Elasticsearch
- OpenSearch
- Apache Solr
などをインストールしたり、検索サーバーを起動したりする構成が一般的でした。
これらは非常に強力ですが、ローカルのJSONLを少し分析したいだけなら、もっと小さな構成でも十分な場合があります。
nlp4j-local-search では、
pip install nlp4j-local-search
して、
nlp4j-local-search --lang ja
を起動するだけです。
あとは、
load(...)
search(...)
view(...)
でLuceneを利用できます。
今回のコマンドまとめ
インストール
pip install -q nlp4j-local-search==0.6.1.1
データ取得
wget https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl.gz
CLI起動
nlp4j-local-search --lang ja
データロード
>> load("mlit_202501-202512.jsonl.gz")
ニッサンを検索
>> search("text_ja:ニッサン")
故障装置を分析
>> view("malfunction_device_s","text_ja:ニッサン")
電動機(モーター)でさらに絞り込む
>> search('text_ja:ニッサン AND malfunction_device_s:""電動機(モーター)""')
または、
>> search("text_ja:ニッサン AND malfunction_device_s:電動機\(モーター\)")
まとめ
今回は国土交通省の自動車不具合情報を使って、
検索
↓
特徴分析
↓
再検索
という流れを試しました。
ニッサンに関する420件を分析すると、
電動機(モーター)
は、
26 / 420
件。
全体では、
65 / 2,782
件でした。
その結果、Relative Rateは、
2.65x
となりました。
これは、
ニッサンに関する文書では、「電動機(モーター)」という分類がデータ全体より相対的に多い
ことを示しています。
e-POWERやEVなど、ニッサンの商品構成との関連は考えられそうですが、このデータだけで因果関係を結論づけることはできません。
むしろ重要なのは、
検索結果から「気になる特徴」を発見し、それを次の検索につなげられる
という点です。
nlp4j-local-search では、Apache Luceneをローカルで利用しながら、
search()
view()
だけで、このような探索を手軽に試すことができます。
Links
PyPI
https://pypi.org/project/nlp4j-local-search/0.6.1.1/
GitHub
https://github.com/oyahiroki/nlp4j-local-search
MLIT sample data (JSONL)
https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl
MLIT sample data (gzip)
https://nlp4j-2.sakura.ne.jp/data/mlit/mlit_202501-202512.jsonl.gz
Qiita tags
Python
Lucene
全文検索
NLP4J
データ分析