В этом отчёте проводится анализ сетевых данных на
основе данных «Камер-фурьерского журнала».
Используется библиотека igraph
для построения и анализа
графов, а также rgexf
для импорта данных.
В ходе исследования выполняются сбор данных, построение графа,
вычисление ключевых метрик, анализ сообществ и визуализация.
library(rgexf) # Импорт GEXF файлов
library(igraph) # Работа с графами
library(dplyr) # Обработка данных
library(stringi) # Нормализация Unicode
На этом этапе импортируем данные и используем цикл для их предобработки.
setwd("C:/Users/schudinova/Desktop/masters/dataverse_files/months_data")
gexf_files <- list.files(pattern = "\\.gexf$")
all_edges <- data.frame(from = character(), to = character(), weight = numeric(), stringsAsFactors = FALSE)
all_nodes <- data.frame(id = character(), label = character(), stringsAsFactors = FALSE)
for (file in gexf_files) {
gexf_data <- read.gexf(file)
if (!is.null(gexf_data$nodes)) {
nodes_df <- gexf_data$nodes
nodes_df$id <- trimws(tolower(as.character(nodes_df$id)))
all_nodes <- rbind(all_nodes, nodes_df)
}
if (!is.null(gexf_data$edges) && nrow(gexf_data$edges) > 0) {
edges_df <- gexf_data$edges
if (!all(edges_df$source %in% nodes_df$id)) {
label_to_id <- setNames(nodes_df$id, trimws(tolower(nodes_df$label)))
edges_df$source <- label_to_id[trimws(tolower(edges_df$source))]
edges_df$target <- label_to_id[trimws(tolower(edges_df$target))]
}
names(edges_df) <- gsub("source", "from", names(edges_df))
names(edges_df) <- gsub("target", "to", names(edges_df))
if (!"weight" %in% names(edges_df)) edges_df$weight <- NA_real_
all_edges <- rbind(all_edges, edges_df[, c("from", "to", "weight")])
}
}
Предварительно очищаем данные…
# Приводим значения в столбцах 'from' и 'to' к нижнему регистру и удаляем лишние пробелы по краям
all_edges$from <- trimws(tolower(as.character(all_edges$from)))
all_edges$to <- trimws(tolower(as.character(all_edges$to)))
# Удаляем строки, где значения 'from' или 'to' пустые
all_edges <- all_edges %>% filter(from != "" & to != "")
# Объединяем уникальные значения из столбцов 'from', 'to' и 'id' из all_nodes, удаляя пробелы
all_node_ids <- unique(c(all_edges$from, all_edges$to, trimws(tolower(all_nodes$id))))
# Создаем датафрейм 'vertices' с уникальными именами вершин и исключаем пустые значения
vertices <- data.frame(name = all_node_ids, stringsAsFactors = FALSE) %>%
filter(name != "") %>% distinct(name)
# Фильтруем 'all_edges', оставляя только те рёбра, оба конца которых есть в 'vertices'
all_edges <- all_edges %>% filter(from %in% vertices$name & to %in% vertices$name)
… и далее строим граф.
# Группируем данные 'all_edges' по парам 'from' и 'to', суммируя веса рёбер
all_edges <- all_edges %>%
group_by(from, to) %>%
summarise(weight = sum(weight, na.rm = TRUE), .groups = "drop")
# Создаем неориентированный граф на основе параллельных рёбер и вершин
g_agg <- graph_from_data_frame(d = all_edges, vertices = vertices, directed = FALSE)
g_agg
## IGRAPH 7b08ee8 UNW- 991 19142 --
## + attr: name (v/c), weight (e/n)
## + edges from 7b08ee8 (vertex names):
## [1] каплун --mariechen зайцев_б --абрамова алданов --абрамович
## [4] ася --абрамович бунин --абрамович варшавский_в--абрамович
## [7] гликберг --абрамович абрамович --головин_а абрамович --головина_а
## [10] зюзя --абрамович иванов --абрамович абрамович --кауфман
## [13] макеев --абрамович мандельштам --абрамович марианна --абрамович
## [16] милочка --абрамович абрамович --михайлов абрамович --нилус
## [19] оллиан --абрамович абрамович --присманова раевский --абрамович
## [22] абрамович --ротштейн
## + ... omitted several edges
cat("Количество вершин:", vcount(g_agg), "\n")
## Количество вершин: 991
cat("Количество рёбер:", ecount(g_agg), "\n")
## Количество рёбер: 19142
cat("Плотность графа:", edge_density(g_agg), "\n")
## Плотность графа: 0.0390219
cat("Число компонент связности:", components(g_agg)$no, "\n\n")
## Число компонент связности: 70
Рассчитываем степень и центральность промежуточных узлов для каждого узла в графе.
V(g_agg)$degree <- degree(g_agg)
V(g_agg)$betweenness <- betweenness(g_agg)
# Рассчитываем номера k-core для каждого узла в графе
core_numbers <- coreness(g_agg)
# Находим максимальное значение номера k-core
max_core <- max(core_numbers)
# Отбираем подграф с узлами, номер k-core которых равен максимальному
subgraph_core <- induced_subgraph(g_agg, vids = which(core_numbers == max_core))
cat("Подграф k-core (max core =", max_core, ") имеет",
vcount(subgraph_core), "вершин и", ecount(subgraph_core), "рёбер.\n\n")
## Подграф k-core (max core = 75 ) имеет 79 вершин и 4016 рёбер.
## null device
## 1
Применяем алгоритм Лувена для анализа сообществ в графе.
louvain_comm <- cluster_louvain(g_agg)
# Рассчитываем модульность разбиения
mod_value <- modularity(louvain_comm)
cat("Модульность разбиения (Louvain):", mod_value, "\n\n")
## Модульность разбиения (Louvain): 0.3255308
Визуализируем граф, раскрашенный по сообществам.
Находим точки сочленения в графе (узлы, удаление которых увеличивает количество компонент связности).
articulation_pts <- articulation_points(g_agg)
cat("Количество точек сочленения:", length(articulation_pts), "\n")
## Количество точек сочленения: 48
if (length(articulation_pts) > 0) {
print(V(g_agg)[articulation_pts][1:min(10, length(articulation_pts))]$name)
}
## [1] "слоним" "якобсон" "гершензон" "николаевский" "поволоцкий"
## [6] "лампен" "черток" "ледницкий" "именитов" "шишмарева"
largest_cliques_list <- largest_cliques(g_agg)
cat("Количество крупнейших кликов:", length(largest_cliques_list), "\n")
## Количество крупнейших кликов: 1
if (length(largest_cliques_list) > 0) {
clique_sizes <- sapply(largest_cliques_list, length)
print(paste("Размеры крупнейших кликов:", paste(head(clique_sizes, 3), collapse=", ")))
}
## [1] "Размеры крупнейших кликов: 51"
В результате описанных выше упражнений получились визуализации, которые однако не в полной мере представляют результаты анализа. Визуализации, к сожалению, не отражают полностью сообщества, графические представления и ключевые узлы и могут не давать полезные инсайты о сети, которая была проанализирована… Посыпаю голову пеплом и иду анализовать дальше.