File size: 6,682 Bytes
64b5d29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# src/analysis/network_analysis.py

import networkx as nx
import pandas as pd
import logging

# Topluluk tespiti için Louvain metodu (önce 'pip install python-louvain community' yapılmalı)
try:
    import community.community_louvain as community_louvain
    community_lib_available = True
except ImportError:
    logging.warning("'community' (python-louvain) kütüphanesi bulunamadı. Topluluk tespiti yapılamayacak. Kurulum için: pip install python-louvain community")
    community_lib_available = False

# Yerel modüller
from src.data_management import storage

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def calculate_centrality(graph: nx.Graph) -> dict:
    """

    Graf üzerindeki düğümler için merkeziyet metriklerini hesaplar.



    Args:

        graph (nx.Graph): Analiz edilecek NetworkX grafı.



    Returns:

        dict: {node_id: {'degree': float, 'betweenness': float, 'eigenvector': float (veya None)}}

              formatında metrikleri içeren sözlük.

    """
    metrics = {}
    if not graph or graph.number_of_nodes() == 0:
        return metrics

    try:
        degree_centrality = nx.degree_centrality(graph)
    except Exception as e:
        logging.error(f"Degree Centrality hesaplanırken hata: {e}")
        degree_centrality = {}

    try:
        betweenness_centrality = nx.betweenness_centrality(graph)
    except Exception as e:
        logging.error(f"Betweenness Centrality hesaplanırken hata: {e}")
        betweenness_centrality = {}

    try:
        # Eigenvector centrality bağlantısız (disconnected) graflarda veya bazı durumlarda hata verebilir
        # max_iter artırılabilir veya hata yakalanabilir
        eigenvector_centrality = nx.eigenvector_centrality(graph, max_iter=500, tol=1e-06)
    except Exception as e:
        logging.warning(f"Eigenvector Centrality hesaplanırken hata (graf bağlantısız olabilir): {e}")
        eigenvector_centrality = {} # Hata durumunda boş bırak

    # Metrikleri birleştir
    for node in graph.nodes():
        metrics[node] = {
            'degree_centrality': degree_centrality.get(node, 0.0),
            'betweenness_centrality': betweenness_centrality.get(node, 0.0),
            'eigenvector_centrality': eigenvector_centrality.get(node, None) # Hata durumunda None olabilir
        }
    logging.info("Merkeziyet metrikleri hesaplandı.")
    return metrics

def detect_communities(graph: nx.Graph) -> dict | None:
    """

    Louvain algoritması kullanarak graf üzerindeki toplulukları tespit eder.



    Args:

        graph (nx.Graph): Analiz edilecek NetworkX grafı.



    Returns:

        dict | None: {node_id: community_id} formatında bölümleme sözlüğü veya hata/kütüphane yoksa None.

    """
    if not community_lib_available:
        return None # Kütüphane yoksa hesaplama yapma
    if not graph or graph.number_of_nodes() == 0:
        return None # Boş graf

    # Louvain metodu yönlendirilmemiş graflarda daha iyi çalışır.
    # Eğer graf yönlü ise, yönlendirilmemişe çevir (veya uyarı ver).
    # Bizim grafımız zaten yönlendirilmemiş (nx.Graph).
    # Ağırlıklı kenarları kullanabilir (varsayılan weight='weight')
    try:
        partition = community_louvain.best_partition(graph, weight='weight') # Kenar ağırlıklarını dikkate al
        num_communities = len(set(partition.values()))
        logging.info(f"Louvain ile topluluk tespiti tamamlandı. {num_communities} topluluk bulundu.")
        return partition
    except Exception as e:
        logging.exception(f"Topluluk tespiti sırasında hata oluştu: {e}")
        return None


def get_network_analysis_results(graph: nx.Graph) -> pd.DataFrame | None:
    """

    Merkeziyet ve topluluk analizlerini yapar ve sonuçları bir DataFrame'de birleştirir.



    Args:

        graph (nx.Graph): Analiz edilecek NetworkX grafı.



    Returns:

        pd.DataFrame | None: 'concept_id', 'name', 'degree_centrality', 'betweenness_centrality',

                             'eigenvector_centrality', 'community_id' sütunlarını içeren DataFrame

                             veya hata durumunda None.

    """
    if not graph or graph.number_of_nodes() == 0:
        logging.warning("Analiz için boş veya geçersiz graf sağlandı.")
        return None

    logging.info("Ağ analizi metrikleri hesaplanıyor...")
    centrality_metrics = calculate_centrality(graph)
    community_partition = detect_communities(graph)

    # Sonuçları bir DataFrame'e dönüştür
    analysis_data = []
    concepts_df = storage.load_dataframe('concepts', storage.CONCEPT_COLUMNS) # İsimler için yükle

    for node_id, metrics in centrality_metrics.items():
        node_data = {
            'concept_id': node_id,
            'name': graph.nodes[node_id].get('name', 'N/A'), # Graf düğümünden al
            'degree_centrality': metrics.get('degree_centrality'),
            'betweenness_centrality': metrics.get('betweenness_centrality'),
            'eigenvector_centrality': metrics.get('eigenvector_centrality'),
            'community_id': community_partition.get(node_id, -1) if community_partition else -1 # Topluluk yoksa -1
        }
        analysis_data.append(node_data)

    if not analysis_data:
        logging.warning("Ağ analizi sonucu veri üretilemedi.")
        return None

    analysis_df = pd.DataFrame(analysis_data)

    # Eğer graf düğümlerinde isim yoksa, concepts_df'ten almayı dene (yedek)
    if 'N/A' in analysis_df['name'].values and concepts_df is not None:
         analysis_df = analysis_df.drop(columns=['name']) # Eski 'name' sütununu sil
         analysis_df = pd.merge(analysis_df, concepts_df[['concept_id', 'name']], on='concept_id', how='left')
         # Sütun sırasını ayarla
         cols = ['concept_id', 'name'] + [col for col in analysis_df.columns if col not in ['concept_id', 'name']]
         analysis_df = analysis_df[cols]


    logging.info("Ağ analizi sonuçları DataFrame'e dönüştürüldü.")
    return analysis_df


def save_network_analysis(analysis_df: pd.DataFrame):
    """ Ağ analizi sonuçlarını Parquet dosyasına kaydeder. """
    if analysis_df is not None and not analysis_df.empty:
        storage.save_dataframe(analysis_df, storage.NETWORK_ANALYSIS_FILENAME)
        logging.info(f"Ağ analizi sonuçları '{storage.NETWORK_ANALYSIS_FILENAME}.parquet' olarak kaydedildi.")
    else:
        logging.warning("Kaydedilecek ağ analizi sonucu bulunamadı.")