はじめに
以前の記事で、小説データを使ってジップの法則を確認しました。しかし、ジップの法則とはかなり乖離していることがわかりました。
それが、小説を題材にしたことによるものなのかどうかを確認するために、他のデータを使うべく、前回の記事において、Yahoo!ニュースの記事をスクレイピングする方法を紹介しました。
今回は、そのニュース記事を用いて、ジップの法則(Zipf’s law)を確認します。
ニュース記事
今回対象とするのは、Yahoo!ニュースのニュース記事です。
前回、Yahoo!ニュースをスクレイピングする方法を紹介しました。紹介した方法では、その時点で掲載された記事をスクレイピングするだけです。それを複数日繰り返して、得られたJSONデータをマージして、一つのデータとして扱います。
例えば、次のようなコードでマージするとよいでしょう。カレントフォルダにある拡張子「txt」のjsonデータをマージします。
# カレントフォルダにある拡張子が「txt」のファイル名の一覧
files = filter(f -> isfile(f)&&occursin(r".txt$", f), readdir("."))
# 複数回に分けられて取得したjsonデータをマージする
urls = [] # 記事の重複判定用
merged = [] # 重複を覗いた記事
days = Dict{String, Int}()
categories = Dict{String, Int}()
for file in files
data = JSON.parsefile(file)
for d in data
url = d["url"]
if !(url in urls)
push!(merged, d)
day = split(d["datetime"])[1]
days[day] = get(days, day, 0) + 1
category = d["category"]
categories[category] = get(categories, category, 0) + 1
push!(urls, url)
end
end
end
以下の実験では、2022年6月21日~7月3日の記事736件を使っています。必ずしもその日のすべての記事が取得できているわけではありません。
テキストデータのクレンジング
Jsonデータにした場合に、テキスト部分は切り出せていますが、そのまま形態素解析する際に都合が悪い、あるいは、効率が悪い状態になっています。それを回避するために文字列を調整します。
- 句点「。」の後で分割
- 行頭の空白文字列を削除
- 空行は削除
# 記事からテキストデータを抽出し、形態素解析できるように加工する
# ・句点で改行させ、不要な空白・空行を除去
function getlines(json_data)
new_lines = []
for j in json_data
# 形態素解析に長文を渡したり、不要な呼び出しをしないように、文字列を調整
## 句点「。」の後で分割する
line = j["detail"]
slines = split(replace(line, r"。" => "。\n"), "\n")
for ll in slines
## 行頭の空白文字列を削除
ll = replace(ll, r"^[ ]+" => "")
## 空行は削除
if length(ll) == 0
continue
end
# 処理済み文字列を格納
push!(new_lines, ll)
end
end
new_lines
end
形態素解析~数え上げ
小説の場合とは、一部変更しています。
小説で実験した場合は、特に文豪と呼ばれる人たちの作品を中心に扱ったのですが、現代に比べて少し古い表現を使ったり、送り仮名が少し違ったりすることが多くありました。それによって発生することが予想される語解析の影響を抑えるために、一文字のひらがな・カタカナ・長音「ー」・波線「~」を除外していました。
ところが、現代の表現で書かれているニュース記事ではそれを考える必要がなく、逆にそれらを除外することによって本来取得できる情報が取れなくなってしまう影響が大きくなります。そのため、上記の処理は今回は行っていません。
※なお、小説データに対して同じような変更を施してグラフを作成しても、ジップの法則から乖離していることには変わりありませんでした。
using Awabi
function countmorph(lines)
# 形態素解析器の設定
## Linux / Mac
#tokenizer = Tokenizer()
## Windows:
#dic = Dict("dicdir" => "C:\\Program Files (x86)\\MeCab\\dic\\ipadic")
#tokenizer = Tokenizer(dic)
## SageMaker Studio Lab
rcfile = "/home/studio-lab-user/mecab/etc/mecabrc"
tokenizer = Tokenizer(rcfile)
# 数え上げ格納領域
word_counts = Dict{String, Int}()
# 形態素解析&数え上げ
for line in lines
# 1文を形態素解析
tokens = tokenize(tokenizer, line)
new_tokens = []
for token in tokens
attr = split(token[2], ",")
hinsi = attr[1]
surface = token[1] # 表記
basic = (attr[7] != "*") ? attr[7] : surface # 形態素の基本形
##
if hinsi in ["名詞", "動詞", "形容詞"]
push!(new_tokens, basic)
end
end
# 形態素数を数え上げ
for surface in new_tokens
word_counts[surface] = get(word_counts, surface, 0) + 1
end
end
word_counts
end
グラフ表示
ここまでに定義した関数を呼び出して、頻度の配列を計算します。
lines = getlines(merged)
word_counts = countmorph(lines)
ordered_count = [c for (w,c) in sort(collect(word_counts), rev = true, by =x -> x[2]) ]
何も考えずに全件を表示しても何もわからないのは前回の実験で分かっているので、上位50件のグラフを作成します。
# ex2:上位50件を表示
using Plots
base = ordered_count[1]
y_limit = base
p2 = plot(ordered_count[1:50], title="Zipf's law", markershape=:circle, linecolor=:blue, ylims=(0,y_limit), xlims=(1,50), label="kokoro")
plot!(p2, x->base/x, linecolor=:red, label="zipf's law", format=:png)
# 保存
savefig(p2, "zipfslaw_yahoo2.png")
少しずれているので、基準となる値を20番目の値で調整します。
# ex3:20番目の値から基準を調整
using Plots
base = ordered_count[20] * 20
y_limit = base
p3 = plot(ordered_count[1:50], title="Zipf's law", markershape=:circle, linecolor=:blue, ylims=(0,y_limit), xlims=(1,50), label="kokoro")
plot!(p3, x->base/x, linecolor=:red, label="zipf's law", format=:png)
# 保存
savefig(p3, "zipfslaw_yahoo3.png")
ニュース記事では小説に比べてジップの法則に近くなっていることが確認できました。
まとめ
上記をまとめたコードを下記に公開しています。ipynb形式です。
なお、この記事で使用したニュース記事データは本サイトでは公開しておりません。必要に応じて、自分で用意してご確認ください。
コメント