はじめに
本記事では、グラフデータベース Neo4j と Python を使用して、より複雑で現実的なカレー好きのソーシャルネットワークを分析する方法を解説します。この高度な分析を通じて、カレー愛好家たちの関係性、好みのパターン、人気のあるカレー店、そして個人の影響力などを詳細に可視化し、深い洞察を得ることを目指します。
Neo4jとは?
Neo4jは、ノード(点)とリレーションシップ(線)でデータを表現するグラフデータベースの一種です。グラフデータベースは、複雑なデータ間の関係性を効率的に扱うことができ、従来のリレーショナルデータベースとは異なり、データ同士のリンクや関係性の解析に特化しています。Neo4jは、高速なクエリ処理やパフォーマンス、視覚的にデータを表現するための機能を持ち、ソーシャルネットワーク、推薦システム、詐欺検出など、関係性が重要となる多くのシナリオで使用されています。
Neo4jでは、Cypherという独自のクエリ言語を用いて、データの操作や検索を行います。CypherはSQLに似た文法で、グラフのノードやリレーションシップを簡単に操作することができ、特にネットワーク分析に適しています。
環境設定
前提条件
- Python 3.7以上
- Neo4j(ローカルインストールまたはDockerで実行)
- 必要なPythonライブラリ:neo4j、networkx、matplotlib
セットアップ
-
Neo4jのインストール:
- Neo4j Desktopをダウンロードしてインストール
- または、Dockerを使用して Neo4j コンテナを実行
-
必要なPythonライブラリのインストール:
pip install neo4j networkx matplotlib
データモデル
今回のカレー好きネットワークは以下の要素で構成されています:
-
ノード:
- Person: 名前、カレーの好み
- Restaurant: 名前、専門のカレー、価格帯
-
関係:
- FAVORITE: 人物がお気に入りのレストラン(評価、訪問回数を含む)
- VISITED: 人物が訪問したレストラン(評価、訪問回数を含む)
- RECOMMENDS: 人物が別の人物を推薦する関係
主要な実装と出力例
Neo4jへの接続
from neo4j import GraphDatabase
class Neo4jConnection:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def query(self, query, parameters=None):
with self.driver.session() as session:
try:
result = session.run(query, parameters)
return [record for record in result]
except Exception as e:
print(f"Query execution error: {e}")
print(f"Problematic query: {query}")
return None
def connect_to_neo4j():
uri = input("Enter Neo4j URI (Default: bolt://localhost:7687): ") or "bolt://localhost:7687"
user = input("Enter username (Default: neo4j): ") or "neo4j"
password = input("Enter password (Default: password): ") or "password"
return Neo4jConnection(uri, user, password)
# 接続例
conn = connect_to_neo4j()
print("Connected to Neo4j successfully!")
出力例:
Enter Neo4j URI (Default: bolt://localhost:7687):
Using default value: bolt://localhost:7687
Enter username (Default: neo4j):
Using default value: neo4j
Enter password (Default: password):
Using default value: password
Connected to Neo4j successfully!
ネットワークデータの作成
def create_curry_network(conn):
query = """
// Create People
CREATE (alice:Person {name: 'Alice', preference: 'Spicy'})
CREATE (bob:Person {name: 'Bob', preference: 'Mild'})
CREATE (charlie:Person {name: 'Charlie', preference: 'Veggie'})
CREATE (david:Person {name: 'David', preference: 'Meaty'})
CREATE (eve:Person {name: 'Eve', preference: 'Spicy'})
CREATE (frank:Person {name: 'Frank', preference: 'Mild'})
CREATE (grace:Person {name: 'Grace', preference: 'Veggie'})
CREATE (henry:Person {name: 'Henry', preference: 'Meaty'})
// Create Restaurants
CREATE (spicyKing:Restaurant {name: 'Spicy King', specialty: 'Vindaloo Curry', price_range: 'Medium'})
CREATE (greenCurry:Restaurant {name: 'Green Curry House', specialty: 'Vegetable Curry', price_range: 'Low'})
CREATE (meatLover:Restaurant {name: 'Meat Lovers', specialty: 'Keema Curry', price_range: 'High'})
CREATE (curryPalace:Restaurant {name: 'Curry Palace', specialty: 'Butter Chicken', price_range: 'Medium'})
CREATE (spiceHeaven:Restaurant {name: 'Spice Heaven', specialty: 'Phaal Curry', price_range: 'High'})
// Create FAVORITE relationships with ratings and visit counts
CREATE (alice)-[:FAVORITE {rating: 5, visits: 10}]->(spicyKing)
CREATE (bob)-[:FAVORITE {rating: 4, visits: 5}]->(greenCurry)
CREATE (charlie)-[:FAVORITE {rating: 5, visits: 8}]->(greenCurry)
CREATE (david)-[:FAVORITE {rating: 4, visits: 6}]->(meatLover)
CREATE (eve)-[:FAVORITE {rating: 5, visits: 12}]->(spiceHeaven)
CREATE (frank)-[:FAVORITE {rating: 4, visits: 7}]->(curryPalace)
CREATE (grace)-[:FAVORITE {rating: 5, visits: 9}]->(greenCurry)
CREATE (henry)-[:FAVORITE {rating: 4, visits: 6}]->(meatLover)
// Create additional VISITED relationships
CREATE (alice)-[:VISITED {rating: 3, visits: 2}]->(greenCurry)
CREATE (bob)-[:VISITED {rating: 3, visits: 1}]->(meatLover)
CREATE (charlie)-[:VISITED {rating: 4, visits: 3}]->(curryPalace)
CREATE (david)-[:VISITED {rating: 5, visits: 4}]->(spicyKing)
CREATE (eve)-[:VISITED {rating: 4, visits: 3}]->(meatLover)
CREATE (frank)-[:VISITED {rating: 5, visits: 5}]->(spicyKing)
CREATE (grace)-[:VISITED {rating: 3, visits: 2}]->(spiceHeaven)
CREATE (henry)-[:VISITED {rating: 4, visits: 3}]->(curryPalace)
// Create RECOMMENDS relationships
CREATE (alice)-[:RECOMMENDS]->(bob)
CREATE (bob)-[:RECOMMENDS]->(charlie)
CREATE (charlie)-[:RECOMMENDS]->(david)
CREATE (david)-[:RECOMMENDS]->(eve)
CREATE (eve)-[:RECOMMENDS]->(frank)
CREATE (frank)-[:RECOMMENDS]->(grace)
CREATE (grace)-[:RECOMMENDS]->(henry)
CREATE (henry)-[:RECOMMENDS]->(alice)
"""
result = conn.query(query)
if result is not None:
print("Curry lovers network data created.")
# 実行例
create_curry_network(conn)
出力例:
Curry lovers network data created.
お気に入りカレー店の分析
def count_favorites(conn):
query = """
MATCH (r:Restaurant)<-[f:FAVORITE]-(p:Person)
RETURN r.name AS restaurant, r.specialty AS specialty, r.price_range AS price_range,
COUNT(p) AS favorite_count, AVG(f.rating) AS avg_rating, SUM(f.visits) AS total_visits
ORDER BY favorite_count DESC
"""
result = conn.query(query)
if result:
print("Favorite counts and ratings:")
for record in result:
print(f"{record['restaurant']} ({record['specialty']}, {record['price_range']}):")
print(f" Favorites: {record['favorite_count']}")
print(f" Average Rating: {record['avg_rating']:.2f}")
print(f" Total Visits: {record['total_visits']}")
else:
print("Failed to retrieve favorite counts.")
# 実行例
count_favorites(conn)
出力例:
Favorite counts and ratings:
Green Curry House (Vegetable Curry, Low):
Favorites: 3
Average Rating: 4.67
Total Visits: 22
Meat Lovers (Keema Curry, High):
Favorites: 2
Average Rating: 4.00
Total Visits: 12
Spicy King (Vindaloo Curry, Medium):
Favorites: 1
Average Rating: 5.00
Total Visits: 10
Curry Palace (Butter Chicken, Medium):
Favorites: 1
Average Rating: 4.00
Total Visits: 7
Spice Heaven (Phaal Curry, High):
Favorites: 1
Average Rating: 5.00
Total Visits: 12
カレー中心性の計算
def calculate_curry_centrality(conn):
query = """
MATCH (p:Person)
OPTIONAL MATCH (p)-[f:FAVORITE]->(r:Restaurant)
OPTIONAL MATCH (p)-[v:VISITED]->(vr:Restaurant)
OPTIONAL MATCH (p)-[:RECOMMENDS]->(friend:Person)
RETURN p.name AS name,
p.preference AS preference,
COUNT(DISTINCT r) AS favorite_restaurants,
COUNT(DISTINCT vr) AS visited_restaurants,
COUNT(DISTINCT friend) AS recommendations,
AVG(f.rating) AS avg_favorite_rating,
AVG(v.rating) AS avg_visit_rating,
SUM(f.visits) + SUM(v.visits) AS total_visits
ORDER BY total_visits DESC
"""
result = conn.query(query)
if result:
print("\nCurry centrality scores:")
for record in result:
centrality = (record['favorite_restaurants'] + record['visited_restaurants'] + record['recommendations']) / 3
print(f"{record['name']} (Preference: {record['preference']}):")
print(f" Centrality score = {centrality:.2f}")
print(f" Favorite Restaurants: {record['favorite_restaurants']}")
print(f" Visited Restaurants: {record['visited_restaurants']}")
print(f" Recommendations: {record['recommendations']}")
print(f" Avg Favorite Rating: {record['avg_favorite_rating']:.2f}")
print(f" Avg Visit Rating: {record['avg_visit_rating']:.2f}")
print(f" Total Visits: {record['total_visits']}")
else:
print("Failed to calculate centrality.")
# 実行例
calculate_curry_centrality(conn)
出力例:
Curry centrality scores:
Eve (Preference: Spicy):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 5.00
Avg Visit Rating: 4.00
Total Visits: 15
Alice (Preference: Spicy):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 5.00
Avg Visit Rating: 3.00
Total Visits: 12
Frank (Preference: Mild):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 4.00
Avg Visit Rating: 5.00
Total Visits: 12
Charlie (Preference: Veggie):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 5.00
Avg Visit Rating: 4.00
Total Visits: 11
Grace (Preference: Veggie):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 5.00
Avg Visit Rating: 3.00
Total Visits: 11
David (Preference: Meaty):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 4.00
Avg Visit Rating: 5.00
Total Visits: 10
Henry (Preference: Meaty):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 4.00
Avg Visit Rating: 4.00
Total Visits: 9
Bob (Preference: Mild):
Centrality score = 1.00
Favorite Restaurants: 1
Visited Restaurants: 1
Recommendations: 1
Avg Favorite Rating: 4.00
Avg Visit Rating: 3.00
Total Visits: 6
ネットワークの可視化
import networkx as nx
import matplotlib.pyplot as plt
def visualize_curry_network(conn):
query = """
MATCH (n)
OPTIONAL MATCH (n)-[r]->(m)
RETURN n.name AS source, m.name AS target, TYPE(r) AS relation, LABELS(n)[0] AS source_type, LABELS(m)[0] AS target_type,
CASE WHEN TYPE(r) IN ['FAVORITE', 'VISITED'] THEN r.rating ELSE NULL END AS rating,
CASE WHEN TYPE(r) IN ['FAVORITE', 'VISITED'] THEN r.visits ELSE NULL END AS visits
"""
result = conn.query(query)
if result:
G = nx.DiGraph()
for record in result:
source = record['source']
target = record['target']
relation = record['relation']
source_type = record['source_type']
target_type = record['target_type']
rating = record['rating']
visits = record['visits']
G.add_node(source, node_type=source_type)
if target:
G.add_node(target, node_type=target_type)
G.add_edge(source, target, relation=relation, rating=rating, visits=visits)
plt.figure(figsize=(16, 12))
# カスタムレイアウトの作成
pos = nx.spring_layout(G, k=0.5, iterations=50)
# ノードの描画
person_nodes = [node for node, data in G.nodes(data=True) if data['node_type'] == 'Person']
restaurant_nodes = [node for node, data in G.nodes(data=True) if data['node_type'] == 'Restaurant']
nx.draw_networkx_nodes(G, pos, nodelist=person_nodes, node_color='lightblue', node_size=3000, alpha=0.8, label='Person')
nx.draw_networkx_nodes(G, pos, nodelist=restaurant_nodes, node_color='lightgreen', node_size=3000, alpha=0.8, label='Restaurant')
# エッジの描画
favorite_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'FAVORITE']
visited_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'VISITED']
recommend_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'RECOMMENDS']
nx.draw_networkx_edges(G, pos, edgelist=favorite_edges, edge_color='r', arrows=True, label='Favorite')
nx.draw_networkx_edges(G, pos, edgelist=visited_edges, edge_color='g', arrows=True, label='Visited')
nx.draw_networkx_edges(G, pos, edgelist=recommend_edges, edge_color='b', style='dashed', arrows=True, label='Recommends')
# ラベルの描画
nx.draw_networkx_labels(G, pos, font_size=8, font_family='sans-serif')
# エッジラベルの描画 (評価とビジット回数)
edge_labels = {(u, v): f"R:{d['rating']}, V:{d['visits']}" for (u, v, d) in G.edges(data=True) if 'rating' in d and 'visits' in d}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=6)
# 凡例
plt.legend(loc='upper left', bbox_to_anchor=(0.02, 0.98))
plt.title("Curry Lovers Network")
plt.axis('off')
plt.tight_layout()
plt.savefig("curry_network.png", dpi=300, bbox_inches='tight')
print("Network graph saved as 'curry_network.png'.")
else:
print("Failed to retrieve network data. Visualization cannot be performed.")
# 実行例
visualize_curry_network(conn)
出力例:
Network graph saved as 'curry_network.png'.
分析結果の解釈
-
お気に入りカレー店の分析:
- Green Curry House が最も人気があり、3人のお気に入りを獲得しています。また、平均評価も高く(4.67)、総訪問回数も最多(22回)です。
- Spicy King と Spice Heaven は平均評価が最も高い(5.00)ですが、お気に入りとして選んだ人は各1人のみです。
- 価格帯による明確な傾向は見られませんが、低価格のGreen Curry Houseが最も人気があることは注目に値します。
-
カレー中心性スコア:
- すべての人物の中心性スコアが1.00となっており、ネットワーク内での活動レベルが均等であることを示しています。
- しかし、総訪問回数に注目すると、Eve(15回)とAlice(12回)が最も活発に店を訪れていることがわかります。
- 辛いカレーを好む人(Eve, Alice)が最も活発に店を訪れる傾向があります。
-
ネットワーク可視化:
- 人物とレストランの関係性が視覚的に表現され、誰がどのレストランを好み、訪れているかが一目で分かります。
- 推薦関係(RECOMMENDS)が円環状になっており、情報やトレンドが循環する可能性を示唆しています。
高度な分析と応用
-
嗜好パターンの特定:
人物の好みとお気に入りのレストランの専門カレーを比較し、一致度を分析します。MATCH (p:Person)-[f:FAVORITE]->(r:Restaurant) RETURN p.name, p.preference, r.name, r.specialty, f.rating ORDER BY f.rating DESC
-
価格帯と人気度の相関:
レストランの価格帯と人気度(お気に入り数や平均評価)の関係を分析します。MATCH (r:Restaurant)<-[f:FAVORITE]-() RETURN r.name, r.price_range, COUNT(f) as favorite_count, AVG(f.rating) as avg_rating ORDER BY favorite_count DESC
-
影響力のある人物の特定:
推薦数と、推薦した人物のお気に入りレストランの重複度を分析し、影響力を測定します。MATCH (p1:Person)-[:RECOMMENDS]->(p2:Person) MATCH (p1)-[:FAVORITE]->(r:Restaurant)<-[:FAVORITE]-(p2) RETURN p1.name, COUNT(DISTINCT p2) as recommendations, COUNT(DISTINCT r) as shared_favorites ORDER BY recommendations DESC, shared_favorites DESC
-
コミュニティ検出:
類似した好みや行動パターンを持つ人物グループを特定します。これには、NetworkXのコミュニティ検出アルゴリズムを使用できます。 -
時系列分析:
訪問データに時間情報を追加し、人気トレンドの変化や個人の好みの変遷を追跡します。
結論
この高度なカレー好きのソーシャルネットワーク分析を通じて、以下のような深い洞察を得ることができました:
- 最も人気のあるカレー店は「Green Curry House」で、特に高い平均評価を得ています。
- 辛いカレーを好む人(Eve, Alice)が最も活発に店を訪れる傾向があります。
- すべての参加者が同程度のネットワーク中心性を持っていますが、訪問回数には個人差があります。
- 価格帯による明確な人気の差は見られませんが、低価格帯の店(Green Curry House)が最も人気があります。
これらの洞察は、以下のような実践的な応用が可能です:
- レストラン経営者:メニュー開発、価格設定、マーケティング戦略の立案に活用できます。
- マーケッター:インフルエンサーマーケティングの戦略立案や、ターゲット顧客の特定に役立ちます。
- 食品業界の研究者:消費者行動や食の嗜好の傾向分析に応用できます。
グラフデータベースとPythonを組み合わせることで、複雑なネットワーク構造を持つデータを効率的に分析し、有益な洞察を得ることができます。この手法は、カレー好きのネットワーク以外にも、様々な分野でのソーシャルネットワーク分析や関係性分析に応用可能です。
今後の発展として、より大規模なデータセットの分析、時系列データの導入、機械学習アルゴリズムとの統合などが考えられます。これらの発展により、さらに深い洞察や予測モデルの構築が可能になるでしょう。
実装(動作確認用)
import sys
import io
from neo4j import GraphDatabase
import networkx as nx
import matplotlib.pyplot as plt
import random
# エンコーディング設定
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
class Neo4jConnection:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def query(self, query, parameters=None):
with self.driver.session() as session:
try:
result = session.run(query, parameters)
return [record for record in result]
except Exception as e:
print(f"Query execution error: {e}")
print(f"Problematic query: {query}")
return None
def get_input_with_default(prompt, default):
user_input = input(f"{prompt} (Default: {default}): ").strip()
if not user_input:
print(f"Using default value: {default}")
return default
return user_input
def connect_to_neo4j():
uri = get_input_with_default("Enter Neo4j URI", "bolt://localhost:7687")
user = get_input_with_default("Enter username", "neo4j")
password = get_input_with_default("Enter password", "password")
return Neo4jConnection(uri, user, password)
def create_curry_network(conn):
query = """
// Create People
CREATE (alice:Person {name: 'Alice', preference: 'Spicy'})
CREATE (bob:Person {name: 'Bob', preference: 'Mild'})
CREATE (charlie:Person {name: 'Charlie', preference: 'Veggie'})
CREATE (david:Person {name: 'David', preference: 'Meaty'})
CREATE (eve:Person {name: 'Eve', preference: 'Spicy'})
CREATE (frank:Person {name: 'Frank', preference: 'Mild'})
CREATE (grace:Person {name: 'Grace', preference: 'Veggie'})
CREATE (henry:Person {name: 'Henry', preference: 'Meaty'})
// Create Restaurants
CREATE (spicyKing:Restaurant {name: 'Spicy King', specialty: 'Vindaloo Curry', price_range: 'Medium'})
CREATE (greenCurry:Restaurant {name: 'Green Curry House', specialty: 'Vegetable Curry', price_range: 'Low'})
CREATE (meatLover:Restaurant {name: 'Meat Lovers', specialty: 'Keema Curry', price_range: 'High'})
CREATE (curryPalace:Restaurant {name: 'Curry Palace', specialty: 'Butter Chicken', price_range: 'Medium'})
CREATE (spiceHeaven:Restaurant {name: 'Spice Heaven', specialty: 'Phaal Curry', price_range: 'High'})
// Create FAVORITE relationships with ratings and visit counts
CREATE (alice)-[:FAVORITE {rating: 5, visits: 10}]->(spicyKing)
CREATE (bob)-[:FAVORITE {rating: 4, visits: 5}]->(greenCurry)
CREATE (charlie)-[:FAVORITE {rating: 5, visits: 8}]->(greenCurry)
CREATE (david)-[:FAVORITE {rating: 4, visits: 6}]->(meatLover)
CREATE (eve)-[:FAVORITE {rating: 5, visits: 12}]->(spiceHeaven)
CREATE (frank)-[:FAVORITE {rating: 4, visits: 7}]->(curryPalace)
CREATE (grace)-[:FAVORITE {rating: 5, visits: 9}]->(greenCurry)
CREATE (henry)-[:FAVORITE {rating: 4, visits: 6}]->(meatLover)
// Create additional VISITED relationships
CREATE (alice)-[:VISITED {rating: 3, visits: 2}]->(greenCurry)
CREATE (bob)-[:VISITED {rating: 3, visits: 1}]->(meatLover)
CREATE (charlie)-[:VISITED {rating: 4, visits: 3}]->(curryPalace)
CREATE (david)-[:VISITED {rating: 5, visits: 4}]->(spicyKing)
CREATE (eve)-[:VISITED {rating: 4, visits: 3}]->(meatLover)
CREATE (frank)-[:VISITED {rating: 5, visits: 5}]->(spicyKing)
CREATE (grace)-[:VISITED {rating: 3, visits: 2}]->(spiceHeaven)
CREATE (henry)-[:VISITED {rating: 4, visits: 3}]->(curryPalace)
// Create RECOMMENDS relationships
CREATE (alice)-[:RECOMMENDS]->(bob)
CREATE (bob)-[:RECOMMENDS]->(charlie)
CREATE (charlie)-[:RECOMMENDS]->(david)
CREATE (david)-[:RECOMMENDS]->(eve)
CREATE (eve)-[:RECOMMENDS]->(frank)
CREATE (frank)-[:RECOMMENDS]->(grace)
CREATE (grace)-[:RECOMMENDS]->(henry)
CREATE (henry)-[:RECOMMENDS]->(alice)
"""
result = conn.query(query)
if result is not None:
print("Curry lovers network data created.")
def count_favorites(conn):
query = """
MATCH (r:Restaurant)<-[f:FAVORITE]-(p:Person)
RETURN r.name AS restaurant, r.specialty AS specialty, r.price_range AS price_range,
COUNT(p) AS favorite_count, AVG(f.rating) AS avg_rating, SUM(f.visits) AS total_visits
ORDER BY favorite_count DESC
"""
result = conn.query(query)
if result:
print("Favorite counts and ratings:")
for record in result:
print(f"{record['restaurant']} ({record['specialty']}, {record['price_range']}):")
print(f" Favorites: {record['favorite_count']}")
print(f" Average Rating: {record['avg_rating']:.2f}")
print(f" Total Visits: {record['total_visits']}")
else:
print("Failed to retrieve favorite counts.")
def calculate_curry_centrality(conn):
query = """
MATCH (p:Person)
OPTIONAL MATCH (p)-[f:FAVORITE]->(r:Restaurant)
OPTIONAL MATCH (p)-[v:VISITED]->(vr:Restaurant)
OPTIONAL MATCH (p)-[:RECOMMENDS]->(friend:Person)
RETURN p.name AS name,
p.preference AS preference,
COUNT(DISTINCT r) AS favorite_restaurants,
COUNT(DISTINCT vr) AS visited_restaurants,
COUNT(DISTINCT friend) AS recommendations,
AVG(f.rating) AS avg_favorite_rating,
AVG(v.rating) AS avg_visit_rating,
SUM(f.visits) + SUM(v.visits) AS total_visits
ORDER BY total_visits DESC
"""
result = conn.query(query)
if result:
print("\nCurry centrality scores:")
for record in result:
centrality = (record['favorite_restaurants'] + record['visited_restaurants'] + record['recommendations']) / 3
print(f"{record['name']} (Preference: {record['preference']}):")
print(f" Centrality score = {centrality:.2f}")
print(f" Favorite Restaurants: {record['favorite_restaurants']}")
print(f" Visited Restaurants: {record['visited_restaurants']}")
print(f" Recommendations: {record['recommendations']}")
print(f" Avg Favorite Rating: {record['avg_favorite_rating']:.2f}")
print(f" Avg Visit Rating: {record['avg_visit_rating']:.2f}")
print(f" Total Visits: {record['total_visits']}")
else:
print("Failed to calculate centrality.")
def visualize_curry_network(conn):
query = """
MATCH (n)
OPTIONAL MATCH (n)-[r]->(m)
RETURN n.name AS source, m.name AS target, TYPE(r) AS relation, LABELS(n)[0] AS source_type, LABELS(m)[0] AS target_type,
CASE WHEN TYPE(r) IN ['FAVORITE', 'VISITED'] THEN r.rating ELSE NULL END AS rating,
CASE WHEN TYPE(r) IN ['FAVORITE', 'VISITED'] THEN r.visits ELSE NULL END AS visits
"""
result = conn.query(query)
if result:
G = nx.DiGraph()
for record in result:
source = record['source']
target = record['target']
relation = record['relation']
source_type = record['source_type']
target_type = record['target_type']
rating = record['rating']
visits = record['visits']
G.add_node(source, node_type=source_type)
if target:
G.add_node(target, node_type=target_type)
G.add_edge(source, target, relation=relation, rating=rating, visits=visits)
plt.figure(figsize=(16, 12))
# カスタムレイアウトの作成
pos = nx.spring_layout(G, k=0.5, iterations=50)
# ノードの描画
person_nodes = [node for node, data in G.nodes(data=True) if data['node_type'] == 'Person']
restaurant_nodes = [node for node, data in G.nodes(data=True) if data['node_type'] == 'Restaurant']
nx.draw_networkx_nodes(G, pos, nodelist=person_nodes, node_color='lightblue', node_size=3000, alpha=0.8, label='Person')
nx.draw_networkx_nodes(G, pos, nodelist=restaurant_nodes, node_color='lightgreen', node_size=3000, alpha=0.8, label='Restaurant')
# エッジの描画
favorite_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'FAVORITE']
visited_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'VISITED']
recommend_edges = [(u, v) for (u, v, d) in G.edges(data=True) if d['relation'] == 'RECOMMENDS']
nx.draw_networkx_edges(G, pos, edgelist=favorite_edges, edge_color='r', arrows=True, label='Favorite')
nx.draw_networkx_edges(G, pos, edgelist=visited_edges, edge_color='g', arrows=True, label='Visited')
nx.draw_networkx_edges(G, pos, edgelist=recommend_edges, edge_color='b', style='dashed', arrows=True, label='Recommends')
# ラベルの描画
nx.draw_networkx_labels(G, pos, font_size=8, font_family='sans-serif')
# エッジラベルの描画 (評価とビジット回数)
edge_labels = {(u, v): f"R:{d['rating']}, V:{d['visits']}" for (u, v, d) in G.edges(data=True) if 'rating' in d and 'visits' in d}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=6)
# 凡例
plt.legend(loc='upper left', bbox_to_anchor=(0.02, 0.98))
plt.title("Curry Lovers Network")
plt.axis('off')
plt.tight_layout()
plt.savefig("curry_network.png", dpi=300, bbox_inches='tight')
print("Network graph saved as 'curry_network.png'.")
else:
print("Failed to retrieve network data. Visualization cannot be performed.")
def drop_database(conn):
query = "MATCH (n) DETACH DELETE n"
result = conn.query(query)
if result is not None:
print("All nodes and relationships have been deleted from the database.")
else:
print("Failed to delete the database contents.")
def main():
print("Welcome to the Curry Lovers Network Analysis Script!")
conn = connect_to_neo4j()
while True:
print("\nSelect an operation:")
print("1: Create curry lovers network data")
print("2: Count favorite curry restaurants")
print("3: Calculate curry centrality")
print("4: Visualize network")
print("5: Delete all data")
print("6: Exit")
choice = get_input_with_default("Choose (1-6)", "6")
if choice == '1':
create_curry_network(conn)
elif choice == '2':
count_favorites(conn)
elif choice == '3':
calculate_curry_centrality(conn)
elif choice == '4':
visualize_curry_network(conn)
elif choice == '5':
confirm = input("Are you sure you want to delete all data? (yes/no): ").lower()
if confirm == 'yes':
drop_database(conn)
else:
print("Data deletion cancelled.")
elif choice == '6':
print("Exiting the program.")
conn.close()
sys.exit(0)
else:
print("Invalid choice. Please enter a number between 1 and 6.")
input("\nPress Enter to continue...")
if __name__ == "__main__":
main()