0
1

グラフデータベースを用いたカレー好きのソーシャルネットワーク分析:Neo4j と Python の統合

Posted at

はじめに

image.png

本記事では、グラフデータベース Neo4j と Python を使用して、より複雑で現実的なカレー好きのソーシャルネットワークを分析する方法を解説します。この高度な分析を通じて、カレー愛好家たちの関係性、好みのパターン、人気のあるカレー店、そして個人の影響力などを詳細に可視化し、深い洞察を得ることを目指します。

Neo4jとは?

napkin-selection (20).png

Neo4jは、ノード(点)とリレーションシップ(線)でデータを表現するグラフデータベースの一種です。グラフデータベースは、複雑なデータ間の関係性を効率的に扱うことができ、従来のリレーショナルデータベースとは異なり、データ同士のリンクや関係性の解析に特化しています。Neo4jは、高速なクエリ処理やパフォーマンス、視覚的にデータを表現するための機能を持ち、ソーシャルネットワーク、推薦システム、詐欺検出など、関係性が重要となる多くのシナリオで使用されています。

Neo4jでは、Cypherという独自のクエリ言語を用いて、データの操作や検索を行います。CypherはSQLに似た文法で、グラフのノードやリレーションシップを簡単に操作することができ、特にネットワーク分析に適しています。

環境設定

前提条件

  • Python 3.7以上
  • Neo4j(ローカルインストールまたはDockerで実行)
  • 必要なPythonライブラリ:neo4j、networkx、matplotlib

セットアップ

  1. Neo4jのインストール:

    • Neo4j Desktopをダウンロードしてインストール
    • または、Dockerを使用して Neo4j コンテナを実行
  2. 必要なPythonライブラリのインストール:

    pip install neo4j networkx matplotlib
    

データモデル

image.png

今回のカレー好きネットワークは以下の要素で構成されています:

  1. ノード:

    • Person: 名前、カレーの好み
    • Restaurant: 名前、専門のカレー、価格帯
  2. 関係:

    • 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'.

curry_network.png

Neo4j Browserでの確認
image.png

分析結果の解釈

napkin-selection (17).png

  1. お気に入りカレー店の分析

    • Green Curry House が最も人気があり、3人のお気に入りを獲得しています。また、平均評価も高く(4.67)、総訪問回数も最多(22回)です。
    • Spicy King と Spice Heaven は平均評価が最も高い(5.00)ですが、お気に入りとして選んだ人は各1人のみです。
    • 価格帯による明確な傾向は見られませんが、低価格のGreen Curry Houseが最も人気があることは注目に値します。
  2. カレー中心性スコア

    • すべての人物の中心性スコアが1.00となっており、ネットワーク内での活動レベルが均等であることを示しています。
    • しかし、総訪問回数に注目すると、Eve(15回)とAlice(12回)が最も活発に店を訪れていることがわかります。
    • 辛いカレーを好む人(Eve, Alice)が最も活発に店を訪れる傾向があります。
  3. ネットワーク可視化

    • 人物とレストランの関係性が視覚的に表現され、誰がどのレストランを好み、訪れているかが一目で分かります。
    • 推薦関係(RECOMMENDS)が円環状になっており、情報やトレンドが循環する可能性を示唆しています。

高度な分析と応用

  1. 嗜好パターンの特定
    人物の好みとお気に入りのレストランの専門カレーを比較し、一致度を分析します。

    MATCH (p:Person)-[f:FAVORITE]->(r:Restaurant)
    RETURN p.name, p.preference, r.name, r.specialty, f.rating
    ORDER BY f.rating DESC
    
  2. 価格帯と人気度の相関
    レストランの価格帯と人気度(お気に入り数や平均評価)の関係を分析します。

    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
    
  3. 影響力のある人物の特定
    推薦数と、推薦した人物のお気に入りレストランの重複度を分析し、影響力を測定します。

    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
    
  4. コミュニティ検出
    類似した好みや行動パターンを持つ人物グループを特定します。これには、NetworkXのコミュニティ検出アルゴリズムを使用できます。

  5. 時系列分析
    訪問データに時間情報を追加し、人気トレンドの変化や個人の好みの変遷を追跡します。

結論

この高度なカレー好きのソーシャルネットワーク分析を通じて、以下のような深い洞察を得ることができました:

  1. 最も人気のあるカレー店は「Green Curry House」で、特に高い平均評価を得ています。
  2. 辛いカレーを好む人(Eve, Alice)が最も活発に店を訪れる傾向があります。
  3. すべての参加者が同程度のネットワーク中心性を持っていますが、訪問回数には個人差があります。
  4. 価格帯による明確な人気の差は見られませんが、低価格帯の店(Green Curry House)が最も人気があります。

napkin-selection (18).png

これらの洞察は、以下のような実践的な応用が可能です:

  • レストラン経営者:メニュー開発、価格設定、マーケティング戦略の立案に活用できます。
  • マーケッター:インフルエンサーマーケティングの戦略立案や、ターゲット顧客の特定に役立ちます。
  • 食品業界の研究者:消費者行動や食の嗜好の傾向分析に応用できます。

グラフデータベースとPythonを組み合わせることで、複雑なネットワーク構造を持つデータを効率的に分析し、有益な洞察を得ることができます。この手法は、カレー好きのネットワーク以外にも、様々な分野でのソーシャルネットワーク分析や関係性分析に応用可能です。

今後の発展として、より大規模なデータセットの分析、時系列データの導入、機械学習アルゴリズムとの統合などが考えられます。これらの発展により、さらに深い洞察や予測モデルの構築が可能になるでしょう。

napkin-selection (19).png

実装(動作確認用)

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()
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1