##はじめに
!ネットワーク分析 初学者の方へ向けた記事になります。!
こんにちは、wankomaです。
私は映画がとても好きなのですが、視聴してみたら思った映画じゃなかったということが多々あります。原因として、SFとカテゴライズされている映画の中でもコメディ寄りや、ドラマ寄りなど振り幅が大きいことが挙げられるのではと考えています。そこで今回は、カテゴリーの組み合わせにおいて、どのような傾向があるのか調べてみようと思いました。映画のカテゴリータグを元に、カテゴリーの関連性をネットワーク分析してみました。
出来上がりはこんな感じのグラフです。
##ネットワーク分析とは?
物や人など様々な事象をグラフ化し、関係性を調べることを目的としています。このペンギン相関図-京都水族館-がわかりやすい一例で、相関図というグラフを用いてペンギンの関係性を示しています。このようなソーシャルネットワークの他にも、経路探索や、ネットワーク回線、Webページリンクの構成、細胞のたんぱく質反応などに応用されています。
#グラフ理論の基本用語
ネットワークのグラフについて基本用語を説明していきます。
用語 | 対象 | (例)ペンギン相関図 |
---|---|---|
ノード | 点 | ペンギン |
エッジ | 線 | 関係性 |
モノや人に相当する点がノード、その点(ノード)と点を結ぶ線がエッジです。このノード、エッジを替えていくことにより、全く異なる分野でも同じようにネットワークとして表現することができます。
また、ネットワークのグラフには向きが存在します。
向き | 説明 |
---|---|
有向グラフ | 一方通行なエッジが存在する |
無向グラフ | すべてのエッジが双方向である |
・有向グラフ
向きが存在するグラフです。
例えば、以下の通りぺん奈さんとぺん香さんが「一方的に」ぺん次郎さんのことが好きという場合。夫婦のように双方向に働いていないため、有向グラフになります。
例)・論文の引用
・電子メールでAさんがBさんにメールを送信する
・無向グラフ
有向グラフと反対に、向きが存在しないグラフです。
基本用語の個所で用いたペンギンの相関図では、ぺん美さんとぺん太さんが夫婦で、ぺん美さんとぺん子さんが姉妹の関係でした。
これらの関係には向きが存在しないので、無向グラフとなります。
例)・電力の供給網
・家系図
重み付きグラフ
また、エッジ部分に重み情報が付与されたネットワークもあります。それを重み付きグラフといいます。
経路探索などに応用されています。
今回は無向かつ重み付きグラフを作成していきます。
手順
- データのダウンロード
- データ加工
- 重みの計算
- グラフ描画
- グラフ保存
データのダウンロード
映画のカテゴリーを複数タグ付けされたデータセットが欲しかったので,
今回はMovieLensのmovies.csvデータセットを利用しました。
MovieLensさんが提供している映画のデータセットは138,000人のユーザーによる27,000本の映画, 465,000件のタグ付けが含まれているため,ネットワーク分析を行うには十分なデータを持っています.
データの構造はこんな↓感じです。
1 | 2 | 3 |
---|---|---|
独自の映画ID | タイトル | 映画のカテゴリー |
movieId,title,genres
1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
2,Jumanji (1995),Adventure|Children|Fantasy
映画のタグは、以下のとおり(タグなしも含めて)19個あり、複数タグ付けされています。
Action, Adventure, Animation, Children's, Comedy, Crime, Documentary, Drama, Fantasy, Film-Noir, Horror, Musical, Mystery, Romance, Sci-Fi, Thriller, War, Western, (no genres listed)
データ加工
ネットワーク分析を行いやすいようにタグなしの行削除、また最近の傾向を知りたかったので2015年公開以降のデータをピックアップしていきます。
ピックアップしたものはCSVファイルとして書き出します。
import csv
df = pd.read_csv('../last_leport/ml-25m/ml-25m/movies.csv',header=None)
# 不必要な1行目と、タグなしを削除する
df = df.drop(df.index[0])
df = df.drop(df.index[df[2] == '(no genres listed)'])
movie_list = []
for title, tags in zip(df[1], df[2]):
if title[-5:-1].isdecimal():
# 2015年以降公開の映画をピックアップ
if int(title[-5:-1]) > 2014:
#tag = tags.split('|')
movie_list.append([title, tags])
with open('../last_leport/ml-25m/ml-25m/tags.csv', 'w', encoding="utf-8_sig") as f:
writer = csv.writer(f, lineterminator="\n")
writer.writerows(movie_list)
最終的にはこのようなデータ構造で保存しています。
[['Mission: Impossible - Rogue Nation (2015)', 'Action|Adventure|Thriller'], ['Jupiter Ascending (2015)',...
重みの計算
それぞれのタグに対し、ノードとエッジを作成するため重みを計算してきます。
import pandas as pd
import numpy as np
import networkx as nx
import collections
import itertools
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
tag_items = 15 # ノードのアイテム数
graph_size = 18 # 出力グラフのサイズ
graph_dpi = 300 # 出力グラフの画素数
node_density = 4.5 # ノードの重み係数
edge_density = 0.02 # ノードの重み係数
font_size = 40 # 出力グラフのサイズ
tags_list = []
# CSV読み込み
df = pd.read_csv('../last_leport/ml-25m/ml-25m/tags.csv',header=None)
# カテゴリータグ部分のみ使用。また|区切りの文字列なのでリスト変換
for data in df[1]:
tags_list.append(data.split('|'))
# Graphオブジェクトの初期化
G = nx.Graph()
# タグの個数により重みづけを行い、ノードに反映する
tag_count = collections.Counter(itertools.chain.from_iterable(tags_list)).most_common(tag_items)
G.add_nodes_from([(tag, {"count":count}) for tag, count in tag_count])
# タグの総組み合わせに対し、エッジを作成していく
for tags in tags_list:
for tag0, tag1 in itertools.combinations(tags, 2):
# 比較するタグがなければスキップし、あれば重みを追加する
if not G.has_node(tag0) or not G.has_node(tag1):
continue
if G.has_edge(tag0, tag1):
G[tag0][tag1]["weight"] += 1
else:
G.add_edge(tag0, tag1, weight=1)
- tag_count = collection---
の部分では、データ全体に対してカテゴリータグがどのくらい存在しているかカウントしています。以下のように、リスト型で返却してくれます。
[('Drama', 3805), ('Comedy', 2599), ('Thriller', 1712),....
-
G.add_nodes_from---
の部分では、先ほど計算した各々のカテゴリータグの存在数(重み)・カテゴリータグ名をノードとして作成しています。 -
for tags in tags_list:--
の部分では、それぞれのカテゴリータグの組み合わせに対して、総当たりで重みを計算し、エッジを作成します。タグの組み合わせが複数存在していたら、weightを1ずつ増やしていきます。
グラフ描画
ノード、エッジを定義したので、いよいよグラフを作成していきます。ここでは、どのようなグラフを描くのか定義を行っていきます。
# グラフのサイズを設定する
plt.figure(figsize=(graph_size, graph_size), dpi=graph_dpi)
# ノードを描画する位置を設定する
pos = nx.spring_layout(G) # ノード間の反発力を定義。値が小さいほど密集する
# ノードサイズを重みによって設定する
node_size = [ d['count']*node_density for (n,d) in G.nodes(data=True)]
# ノードのラベルやスタイルを設定する
nx.draw_networkx_nodes(G, pos, node_color='pink', alpha=0.9, node_size=node_size, font_weight="bold")
nx.draw_networkx_labels(G, pos)
# エッジのスタイルを設定する
edge_width = [ d['weight']*edge_density for (u,v,d) in G.edges(data=True)]
nx.draw_networkx_edges(G, pos, alpha=1, edge_color='gray', width=edge_width)
-
node_size
ノードの丸のサイズを指定します。今回はtag_countにて計算したタグ自体の重みによってサイズを変更したいので、countを利用します。ただそのままではサイズが小さいので、node_densityを掛けています。 -
nx.draw_networkx_nodes
ノードのスタイルを設定します。引数は以下の通りです。
引数 | 説明 |
---|---|
node_color | ノードの背景色 |
alpha | 背景色の透明度 (0~1) |
node_size | ノードのサイズ |
font_wight | ノードに描画するテキストのサイズ |
-
edge_width
ノードと同じようにエッジの太さを指定します。タグ同士関連性の重みによって太さを変更したいので、weightを利用します。こちらはそのままでは太すぎるので、edge_densityを掛けています。 -
nx.draw_networkx_edges
エッジのスタイルを設定します。引数は以下の通りです。
引数 | 説明 |
---|---|
alpha | 線の透明度 (0~1) |
edge_color | 線の色 |
width | 線の太さ |
グラフ保存
描画したグラフを保存します
# グラフ出力
plt.savefig('movie_tags.png')
結果
このように映画カテゴリの関連性についてのネットワーク可視化することができました!
Dramaカテゴリーが多かったり、ThrillerとHorrorが近い関係性だったりするのは頷けますね。Fantasyは割とまんべんなく関係性があるのも…ファンタジーものの映画を選ぶのは難しいわけですね。
DramaとComedyの関連が強いところは、真逆のイメージだったので意外でした!Animation x Mysteryなど、結構ありそうなもののまだ開拓の余地がある…そして、Documentary x Sci-Fiという相反する(?)性質を持つ映画がなんなのか気になります。
おまけ
今回は2015年以降の映画を対象としたのですが、年代によってもグラフが変わるのでは?と気になったので、追加調査を行いました。
こちらは20年前の1984年以前のデータです。
データ数が異なるので、エッジの太さが異なったりはありますが…
印象的なのは戦争映画の多さ。そして、2015年以降には出てこなかったwestenカテゴリー。Dramaと周辺のカテゴリーも、2015年ではまんべんなく関係性を持っていたものの、この時代ではRmanceがかなりの割合を占めていますね。時代を感じます。
他の年代と比べてみると面白い!
さいごに
今回は映画カテゴリーの相互関係に着目し、
・カテゴリーの出現頻度:カテゴリー自体の重み
・カテゴリーの組み合わせ回数:カテゴリー結びつきの重み
として分析を行いました。
これを応用し、
例えば、あるグループチャットに着目すると、
・グループチャットでの発言回数:発言者自体の重み
・メンション送信者と受信者の組み合わせ回数:メンバー同士の結びつきの重み
と置き換えてネットワーク分析を行うと、グループチャット内で誰がどのくらいキーパーソンなのか明らかにすることができます。
他にも、
・SNSの友人関係性
・企業間の関係性
・経路探索
…
などにも応用することが可能です。
少しでもネットワーク分析って面白いと感じてくださると嬉しいです!
ネットワーク分析を実践するぞ!と意気込んで、一番難しかったのがデータ加工でした。どのようなデータ構造にしなければならないのか?そもそも、どういうデータを可視化していきたいのか?と負のループに陥ってなかなか抜け出せませんでした…
そう思うとネタ探しも難しいかもしれない…
今後も他の分析手法を学んで、もう少しネットワーク分析をやっていきたいなと思います!
参考文献
PythonでQiitaタグのネットワークを可視化する
ネットワーク科学―ひと・もの・ことの関係性をデータから解き明かす新しいアプローチ―
【11個掲載】機械学習に使える映画データセットまとめ