Как представить графы совместной деятельности в R

Материал из Поле цифровой дидактики
## Урок: разные представления месячной сети авторов GitLab-проектов

library(dplyr)
library(lubridate)
library(igraph)
library(tidygraph)
library(ggraph)

URL <- paste0(
  "https://raw.githubusercontent.com/patarakin/stat-data/",
  "1118a56e7544839d7df91a60df2a25ba577c4dd4/datasets/csv/df_rich_team.csv"
)

df <- readr::read_csv(URL, show_col_types = FALSE) |>
  mutate(
    commit_time = ymd_hms(commit_time, tz = "UTC"),
    commit_date = as.Date(commit_time)
  )

glimpse(df)

## 1. Добавляем месяц и функцию для сети авторов за месяц

df_month <- df %>%
  mutate(month = floor_date(commit_time, unit = "month"))

glimpse(df_month)

author_network_for_month <- function(data_month) {
  edges_ap <- data_month %>%
    distinct(author_anon, project_id)
  
  g_bip <- graph_from_data_frame(edges_ap, directed = FALSE)
  V(g_bip)$type <- V(g_bip)$name %in% edges_ap$author_anon
  
  proj <- bipartite_projection(g_bip, multiplicity = TRUE)
  g_auth <- proj[[2]]
  g_auth
}

## 2. Статистика по всем месяцам (для выбора интересных периодов)

months_vec <- sort(unique(df_month$month))

month_stats <- lapply(months_vec, function(m) {
  g_m <- df_month %>%
    filter(month == m) %>%
    author_network_for_month()
  
  comp <- components(g_m)
  
  data.frame(
    month = m,
    n_authors        = vcount(g_m),
    n_edges          = ecount(g_m),
    n_components     = comp$no,
    max_component_sz = max(comp$csize)
  )
}) |> bind_rows()

month_stats

## Выбираем одну дату для примеров (при необходимости заменить)
target_month <- month_stats$month[which.max(month_stats$n_authors)]
target_month

g_target <- df_month %>%
  filter(month == target_month) %>%
  author_network_for_month()

g_target

## 3. Базовый plot() igraph

plot(
  g_target,
  vertex.size  = 4,
  vertex.label = NA,
  edge.width   = pmin(E(g_target)$weight, 5),
  edge.color   = "grey80",
  layout       = layout_with_fr(g_target),
  main         = paste("Сеть авторов за", target_month, "(layout_with_fr)")
)

## 4. Несколько разных layout’ов igraph

par(mfrow = c(2, 2), mar = c(1, 1, 3, 1))

plot(g_target,
     layout = layout_with_fr(g_target),
     vertex.size = 3, vertex.label = NA,
     main = "Fruchterman–Reingold")

plot(g_target,
     layout = layout_with_kk(g_target),
     vertex.size = 3, vertex.label = NA,
     main = "Kamada–Kawai")

plot(g_target,
     layout = layout_in_circle(g_target),
     vertex.size = 3, vertex.label = NA,
     main = "Circle")

plot(g_target,
     layout = layout_with_mds(g_target),
     vertex.size = 3, vertex.label = NA,
     main = "MDS")

par(mfrow = c(1, 1))

## 5. Перевод в tidygraph + ggraph (force-directed)

g_tbl <- as_tbl_graph(g_target) %>%
  activate(nodes) %>%
  mutate(
    deg = centrality_degree()
  )

set.seed(42)
ggraph(g_tbl, layout = "fr") +
  geom_edge_link(aes(width = pmin(weight, 5)),
                 alpha = 0.15, colour = "grey70") +
  geom_node_point(aes(size = deg),
                  colour = "steelblue") +
  scale_edge_width(range = c(0.2, 1.5)) +
  scale_size(range = c(1, 6)) +
  labs(
    title = paste("ggraph::layout = 'fr' |", target_month)
  ) +
  theme_graph()

## 6. ggraph: кластеризация и раскраска по сообществам

cl <- igraph::cluster_louvain(g_target)
membership_vec <- igraph::membership(cl)

g_tbl <- g_tbl %>%
  activate(nodes) %>%
  mutate(
    community = factor(membership_vec)
  )

set.seed(43)
ggraph(g_tbl, layout = "fr") +
  geom_edge_link(alpha = 0.1, colour = "grey80") +
  geom_node_point(aes(color = community, size = deg)) +
  scale_size(range = c(1, 5)) +
  labs(
    title = paste("Месячные подкоманды (Louvain communities) |", target_month)
  ) +
  theme_graph()

## 7. ggraph: круговая диаграмма по степеням

set.seed(44)
ggraph(g_tbl, layout = "linear") +
  geom_edge_arc(alpha = 0.1, colour = "grey70") +
  geom_node_point(aes(size = deg, color = community)) +
  coord_fixed() +
  labs(
    title = paste("Круговое представление сети авторов |", target_month)
  ) +
  theme_graph()

## 8. ggraph: фильтрация по «ядру» сети и более плотное представление

g_core <- g_tbl %>%
  activate(nodes) %>%
  filter(deg >= quantile(deg, 0.75, na.rm = TRUE))

set.seed(45)
ggraph(g_core, layout = "fr") +
  geom_edge_link(alpha = 0.2, colour = "grey70") +
  geom_node_point(aes(size = deg, color = community)) +
  scale_size(range = c(3, 8)) +
  labs(
    title = paste("Ядро месячной сети авторов (top 25% по degree) |", target_month)
  ) +
  theme_graph()

###--- Изменяем месяц для графа
##
target_month <- as.Date("2023-06-01")
target_month
###



Примеры


Глаголы TidyR

Функция Что делает С чем работает Простой пример (R)
select() Выбирает (оставляет) нужные столбцы датафрейма; остальные отбрасывает. Столбцы (переменные)
students |> 
  select(username, n_total_edits, discipline)
filter() Оставляет строки, которые удовлетворяют логическому условию (фильтрация наблюдений). Строки (наблюдения)
students |> 
  filter(n_total_edits > 50, discipline == "Филология")
mutate() Добавляет новые столбцы или изменяет существующие, вычисляя их из других переменных. Столбцы (новые или изменённые переменные)
students |>
  mutate(
    edit_per_day = n_total_edits / days_active,
    pct_articles = edits_articles / n_total_edits * 100
  )
group_by() Задаёт группировку по одной или нескольким переменным; изменяет "структуру вычислений", но не сами данные. Группы строк (по категориям)
students |>
  group_by(discipline)
summarise() Строит сводку по группам: сворачивает много строк в одну строку на группу (средние, суммы и т.п.). Группы (после group_by())
students |>
  group_by(discipline) |>
  summarise(
    mean_edits = mean(n_total_edits, na.rm = TRUE),
    n_students = n()
  )