Как посчитать метрики командности (GitLab)

Материал из Поле цифровой дидактики
Описание
Область знаний
Область использования (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)
}