Как посчитать метрики командности (GitLab): различия между версиями

Материал из Поле цифровой дидактики
Строка 115: Строка 115:
*​ Mean response minutes: среднее время между коммитами разных авторов (скорость «ответа» на след).
*​ Mean response minutes: среднее время между коммитами разных авторов (скорость «ответа» на след).
== 4. Метрики на уровне проекта ([[IMOI]]–[[Stigmergy]]) ==
Теперь агрегируем данные по проектам, чтобы получить табличку project_metrics: для каждого проекта — структура, процессы и «зрелость» работы
<syntaxhighlight lang="R" line>
author_contrib <- df |>
  group_by(project_id, author_anon) |>
  summarise(author_changes = sum(total_changes), .groups = "drop")
gini_by_project <- author_contrib |>
  group_by(project_id) |>
  summarise(
    gini_contribution = gini_contrib(author_changes),
    .groups = "drop"
  )
project_metrics <- df |>
  arrange(project_id, commit_time) |>
  group_by(project_id) |>
  summarise(
    # Inputs
    n_authors    = n_distinct(author_anon),
    n_commits    = n(),
    total_changes = sum(total_changes),
    duration_days = as.numeric(
      difftime(max(commit_time), min(commit_time), units = "days")
    ),
   
    # Action processes
    succession_ratio = succession_ratio(author_anon),
    burstiness      = burstiness(commit_time),
    commit_rate_per_day = n() / pmax(
      as.numeric(difftime(max(commit_time),
                          min(commit_time), units = "days")),
      0.01
    ),
   
    # Emergent states
    shared_activity_index =
      shared_activity_index(author_anon, commit_date),
   
    # Output: зрелость работы
    refactoring_ratio = sum(deletions) / pmax(sum(total_changes), 1),
   
    # Stigmergy: скорость реакции
    mean_response_min =
      mean_response_minutes(author_anon, commit_time),
   
    .groups = "drop"
  ) |>
  left_join(gini_by_project, by = "project_id")
glimpse(project_metrics)
</syntaxhighlight>
----
----
[[Категория:Lesson]]
[[Категория:Lesson]]

Версия от 17:46, 16 марта 2026

Описание
Область знаний
Область использования (ISTE)
Возрастная категория 21


Поясняющее видео
Близкие рецепту понятия Стигмергия
Среды и средства для приготовления рецепта: R, GitLab


Метрики

Имя переменной Тип Единицы / диапазон Краткое описание Интерпретация в контексте командности
project_id строка произвольный ID Анонимизированный идентификатор проекта (репозитория GitLab). Узел уровня проекта; используется для агрегации коммитов и связи с другими таблицами.
n_authors целое ≥ 1 Количество уникальных авторов (author_anon), сделавших хотя бы один коммит в проект. Размер команды; большее значение означает более многолюдный проект.
n_commits целое ≥ 1 Общее число коммитов во всём проекте. Интенсивность работы; грубый показатель общего объёма действий.
total_changes целое ≥ 0 (строки) Сумма total_changes по всем коммитам (добавления + удаления). Общий объём изменённого кода; прокси для усилий команды.
duration_days число ≥ 0 (дни) Разница между последним и первым коммитом (в днях). Продолжительность активной жизни проекта; длина временного окна совместной работы.
succession_ratio число [0; 1] Доля «смен авторов» в последовательности коммитов: в скольких шагах подряд следующий автор отличается от предыдущего. Индикатор стигмергического обмена: чем выше, тем чаще участники реагируют на следы друг друга.
burstiness число примерно [-1; 1] Коэффициент вспышкообразности по интервалам между коммитами (на основе средней и стандартного отклонения). Характер ритма работы: отрицательные значения ≈ регулярная активность, положительные ≈ чередование пауз и «вспышек».
commit_rate_per_day число ≥ 0 (комм./день) Среднее число коммитов в день за период проекта. Нормированная интенсивность работы; позволяет сравнивать проекты разной длительности.
shared_activity_index число [0; 1] Доля пар авторов, у которых есть хотя бы один общий день активности в проекте. Индикатор синхронности: 0 — авторы работают разрозненно, 1 — все пары хотя бы иногда активны в одни и те же дни.
refactoring_ratio число [0; 1] Отношение общего числа удалённых строк (deletions) к total_changes. Грубый показатель доли переработки/рефакторинга по сравнению с чистым добавлением кода.
mean_response_min число ≥ 0 (минуты) или NA Среднее время (в минутах) между коммитами разных авторов, идущими друг за другом. Скорость отклика на действия коллег; меньшие значения соответствуют более быстрой реакционной командной работе.
gini_contribution число [0; 1] Коэффициент Джини по распределению вкладов (total_changes) между авторами. Баланс участия: 0 — все авторы вносят примерно одинаковый вклад; 1 — весь вклад сосредоточен у одного участника.
balance_score число [0; 1] Нормированное преобразование (min–max) величины 1 − gini_contribution. Компонента индекса командности: 1 соответствует максимально равномерному распределению вклада.
stigmergy_score число [0; 1] Нормированное значение succession_ratio (min–max по выборке проектов). Компонента индекса командности: интенсивность стигмергического обмена следами.
sync_score число [0; 1] Нормированное значение shared_activity_index (min–max по выборке). Компонента индекса командности: уровень синхронности активности авторов.
teamwork_index число [0; 1] Среднее трёх компонент: balance_score, stigmergy_score, sync_score. Интегральный индекс командности IMOI–Stigmergy: чем ближе к 1, тем более сбалансированная и согласованная работа команды.
teamwork_tier фактор уровни: Q1_low, Q2, Q3, Q4_high Квартильная группа по значению teamwork_index (4 равные группы по числу проектов). Категориальная оценка уровня командности; Q1_low — нижний квартиль, Q4_high — верхний.

1. Сюжет урока и данные

В этом занятии мы изучаем, как по следам коммитов в GitLab можно оценивать командность и совместность работы в проектах. Источник данных — анонимизированный датасет df_rich_team, где каждая строка описывает один коммит: кто (анонимный ID), когда, в какой проект и с каким объёмом изменений.

Датасет доступен в формате CSV на GitHub (анонимные идентификаторы авторов и проектов, без имён и e-mail)

2. Подготовка среды и загрузка данных

Задача этого шага — подключить нужные пакеты, загрузить датасет и привести время к удобному формату.


library(tidyverse)
library(lubridate)
library(ineq)
library(scales)
library(knitr)

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

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

glimpse(df)
  • author_anon — анонимный код участника (например, A0001);
  • project_id — ID проекта;
  • commit_time, commit_date — временная метка и дата;
  • total_changes, additions, deletions — объём изменений в коммите.

3. Функции-метрики командной работы

На этом шаге мы определяем набор функций, которые превращают последовательность коммитов в индикаторы командности на уровне проекта.

gini_contrib <- function(changes_vec) {
  if (length(changes_vec) < 2 || sum(changes_vec) == 0) return(0)
  ineq::Gini(changes_vec)
}

succession_ratio <- function(authors_vec) {
  n <- length(authors_vec)
  if (n < 2) return(NA_real_)
  sum(authors_vec[-1] != authors_vec[-n]) / (n - 1)
}

burstiness <- function(times_vec) {
  times_vec <- sort(times_vec)
  if (length(times_vec) < 3) return(NA_real_)
  deltas <- as.numeric(diff(times_vec), units = "secs")
  deltas <- deltas[deltas > 0]
  if (length(deltas) < 2) return(NA_real_)
  mu <- mean(deltas)
  sigma <- sd(deltas)
  if ((sigma + mu) == 0) return(NA_real_)
  (sigma - mu) / (sigma + mu)
}

shared_activity_index <- function(author_vec, date_vec) {
  authors <- unique(author_vec)
  n <- length(authors)
  if (n < 2) return(0)
  
  dates_by_author <- split(date_vec, author_vec)
  pairs <- combn(authors, 2, simplify = FALSE)
  
  overlap_count <- sum(vapply(pairs, function(p) {
    length(intersect(dates_by_author[[p[1]]],
                     dates_by_author[[p[2]]])) > 0
  }, logical(1)))
  
  overlap_count / length(pairs)
}

mean_response_minutes <- function(authors_vec, times_vec) {
  n <- length(authors_vec)
  if (n < 2) return(NA_real_)
  intervals <- numeric(0)
  for (i in seq(2, n)) {
    if (authors_vec[i] != authors_vec[i - 1]) {
      delta <- as.numeric(difftime(times_vec[i],
                                   times_vec[i - 1],
                                   units = "mins"))
      intervals <- c(intervals, delta)
    }
  }
  if (length(intervals) == 0) return(NA_real_)
  mean(intervals)
}
  • Gini по вкладам: насколько равномерно распределён вклад по авторам; близко к 0 — равномерно, близко к 1 — один делает почти всё.
  • Succession ratio: доля коммитов, где следующий коммит делает уже другой автор (степень «переклички» действий).
  • Burstiness: насколько вспышкообразен режим работы (долгие паузы + короткие bursts).

​* Shared activity index: доля пар авторов, у которых есть хотя бы один общий день активности в проекте (синхронность).

  • ​ Mean response minutes: среднее время между коммитами разных авторов (скорость «ответа» на след).

4. Метрики на уровне проекта (IMOIStigmergy)

Теперь агрегируем данные по проектам, чтобы получить табличку project_metrics: для каждого проекта — структура, процессы и «зрелость» работы

author_contrib <- df |>
  group_by(project_id, author_anon) |>
  summarise(author_changes = sum(total_changes), .groups = "drop")

gini_by_project <- author_contrib |>
  group_by(project_id) |>
  summarise(
    gini_contribution = gini_contrib(author_changes),
    .groups = "drop"
  )

project_metrics <- df |>
  arrange(project_id, commit_time) |>
  group_by(project_id) |>
  summarise(
    # Inputs
    n_authors     = n_distinct(author_anon),
    n_commits     = n(),
    total_changes = sum(total_changes),
    duration_days = as.numeric(
      difftime(max(commit_time), min(commit_time), units = "days")
    ),
    
    # Action processes
    succession_ratio = succession_ratio(author_anon),
    burstiness       = burstiness(commit_time),
    commit_rate_per_day = n() / pmax(
      as.numeric(difftime(max(commit_time),
                          min(commit_time), units = "days")),
      0.01
    ),
    
    # Emergent states
    shared_activity_index =
      shared_activity_index(author_anon, commit_date),
    
    # Output: зрелость работы
    refactoring_ratio = sum(deletions) / pmax(sum(total_changes), 1),
    
    # Stigmergy: скорость реакции
    mean_response_min =
      mean_response_minutes(author_anon, commit_time),
    
    .groups = "drop"
  ) |>
  left_join(gini_by_project, by = "project_id")

glimpse(project_metrics)