Как посчитать метрики командности (GitLab): различия между версиями
Patarakin (обсуждение | вклад) |
Patarakin (обсуждение | вклад) Нет описания правки |
||
| Строка 8: | Строка 8: | ||
{{Шаблон:Team metrics}} | {{Шаблон:Team metrics}} | ||
== | == Сюжет урока и данные == | ||
В этом занятии мы изучаем, как по следам коммитов в GitLab можно оценивать командность и совместность работы в проектах. Источник данных — анонимизированный датасет df_rich_team, где каждая строка описывает один коммит: кто (анонимный ID), когда, в какой проект и с каким объёмом изменений. | В этом занятии мы изучаем, как по следам коммитов в GitLab можно оценивать командность и совместность работы в проектах. Источник данных — анонимизированный датасет df_rich_team, где каждая строка описывает один коммит: кто (анонимный ID), когда, в какой проект и с каким объёмом изменений. | ||
| Строка 14: | Строка 14: | ||
Датасет доступен в формате CSV на GitHub (анонимные идентификаторы авторов и проектов, без имён и e-mail) | Датасет доступен в формате CSV на GitHub (анонимные идентификаторы авторов и проектов, без имён и e-mail) | ||
== | == Подготовка среды и загрузка данных == | ||
Задача этого шага — подключить нужные пакеты, загрузить датасет и привести время к удобному формату. | Задача этого шага — подключить нужные пакеты, загрузить датасет и привести время к удобному формату. | ||
| Строка 46: | Строка 46: | ||
* total_changes, additions, deletions — объём изменений в коммите. | * total_changes, additions, deletions — объём изменений в коммите. | ||
== | == Функции-метрики командной работы == | ||
На этом шаге мы определяем набор функций, которые превращают последовательность коммитов в индикаторы командности на уровне проекта. | На этом шаге мы определяем набор функций, которые превращают последовательность коммитов в индикаторы командности на уровне проекта. | ||
| Строка 115: | Строка 115: | ||
* Mean response minutes: среднее время между коммитами разных авторов (скорость «ответа» на след). | * Mean response minutes: среднее время между коммитами разных авторов (скорость «ответа» на след). | ||
| | ||
== | == Метрики на уровне проекта ([[IMOI]]–[[Stigmergy]]) == | ||
Теперь агрегируем данные по проектам, чтобы получить табличку project_metrics: для каждого проекта — структура, процессы и «зрелость» работы | Теперь агрегируем данные по проектам, чтобы получить табличку project_metrics: для каждого проекта — структура, процессы и «зрелость» работы | ||
| Строка 170: | Строка 170: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Индекс командности и квартильные группы == | ||
<syntaxhighlight lang="R" line> | <syntaxhighlight lang="R" line> | ||
Версия от 18:15, 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 — верхний. |
Сюжет урока и данные
В этом занятии мы изучаем, как по следам коммитов в GitLab можно оценивать командность и совместность работы в проектах. Источник данных — анонимизированный датасет df_rich_team, где каждая строка описывает один коммит: кто (анонимный ID), когда, в какой проект и с каким объёмом изменений.
Датасет доступен в формате CSV на GitHub (анонимные идентификаторы авторов и проектов, без имён и e-mail)
Подготовка среды и загрузка данных
Задача этого шага — подключить нужные пакеты, загрузить датасет и привести время к удобному формату.
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 — объём изменений в коммите.
Функции-метрики командной работы
На этом шаге мы определяем набор функций, которые превращают последовательность коммитов в индикаторы командности на уровне проекта.
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: среднее время между коммитами разных авторов (скорость «ответа» на след).
Теперь агрегируем данные по проектам, чтобы получить табличку 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)
Индекс командности и квартильные группы
minmax_norm <- function(x) {
rng <- range(x, na.rm = TRUE)
if (diff(rng) == 0) return(rep(0, length(x)))
(x - rng) / diff(rng)[1]
}
project_metrics <- project_metrics |>
mutate(
balance_score = minmax_norm(1 - gini_contribution),
stigmergy_score = minmax_norm(succession_ratio),
sync_score = minmax_norm(shared_activity_index),
teamwork_index = (balance_score + stigmergy_score + sync_score) / 3,
teamwork_tier = ntile(teamwork_index, 4) |>
factor(levels = 1:4,
labels = c("Q1_low", "Q2", "Q3", "Q4_high"))
)
glimpse(project_metrics)
- Здесь
- balance_score — чем ближе к 1, тем ровнее распределён вклад;
- stigmergy_score — на основе succession ratio (интенсивность обмена следами);
- sync_score — синхронность по совместным дням;
- teamwork_index — их среднее;
- teamwork_tier — квартильная группа (от Q1_low до Q4_high)
tier_summary <- project_metrics |>
group_by(teamwork_tier) |>
summarise(
n_projects = n(),
avg_authors = mean(n_authors),
avg_commits = mean(n_commits),
avg_gini = mean(gini_contribution),
avg_succession = mean(succession_ratio),
avg_shared = mean(shared_activity_index),
avg_refactoring = mean(refactoring_ratio),
median_resp_min = median(mean_response_min, na.rm = TRUE),
.groups = "drop"
)
kable(tier_summary, digits = 3,
caption = "Характеристики проектов по квартилям командности")
glimpse(tier_summary)
