Created
May 11, 2020 03:32
-
-
Save aaossa/ef7ec4a98703c24cb45bd20920848607 to your computer and use it in GitHub Desktop.
Análisis de commits para IIC2233 2020-1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Procesamiento de *commits* de alumnes" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"/home/aaossa/.local/lib/python3.8/site-packages/pandas/compat/__init__.py:117: UserWarning: Could not import the lzma module. Your installed Python is incomplete. Attempting to use lzma compression will result in a RuntimeError.\n", | |
" warnings.warn(msg)\n" | |
] | |
} | |
], | |
"source": [ | |
"import re\n", | |
"from collections import Counter\n", | |
"\n", | |
"import pandas as pd" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"df_commits = pd.read_csv(\"commits (inc may 9).csv\")\n", | |
"df_commits = df_commits.rename(columns={\"commiter\": \"committer\"})\n", | |
"df_commits[\"message\"] = df_commits[\"message\"].fillna(\"null\") # Commit con 'message': 'null'" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Agregar columna con username (registrado en el curso)\n", | |
"df_commits[\"username\"] = df_commits[\"repo_name\"].map(lambda x: x[8:-15])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Quitar commits de usuarios del equipo docente\n", | |
"equipo_docente = [\n", | |
" \"entamburini\",\n", | |
" \"bimartinez\",\n", | |
" \"aaossa\",\n", | |
" \"cruz\",\n", | |
" \"Drpinto1\",\n", | |
" \"fdoflorenzano\",\n", | |
" \"lily416\",\n", | |
" \"NevadaStreets\",\n", | |
"]\n", | |
"df_commits = df_commits[~(df_commits[\"committer\"].isin(equipo_docente))]\n", | |
"df_commits = df_commits[~(df_commits[\"author\"].isin(equipo_docente))]\n", | |
"df_commits = df_commits.reset_index(drop=True)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Agregar cantidad de commits por repositorio\n", | |
"df_commits[\"history_size\"] = df_commits.groupby(\"repo_name\")[\"message\"].transform(len)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Detección de inconsistencias usando 'author' y 'committer'" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Notar que \"web-flow\" como *committer* indica que el cambio se hizo a través de la página de GitHub, y no con git" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Diferencia entre *author* y *commiter*" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div>\n", | |
"<style scoped>\n", | |
" .dataframe tbody tr th:only-of-type {\n", | |
" vertical-align: middle;\n", | |
" }\n", | |
"\n", | |
" .dataframe tbody tr th {\n", | |
" vertical-align: top;\n", | |
" }\n", | |
"\n", | |
" .dataframe thead th {\n", | |
" text-align: right;\n", | |
" }\n", | |
"</style>\n", | |
"<table border=\"1\" class=\"dataframe\">\n", | |
" <thead>\n", | |
" <tr style=\"text-align: right;\">\n", | |
" <th></th>\n", | |
" <th>repo_name</th>\n", | |
" <th>url</th>\n", | |
" <th>author</th>\n", | |
" <th>committer</th>\n", | |
" <th>message</th>\n", | |
" <th>username</th>\n", | |
" <th>history_size</th>\n", | |
" </tr>\n", | |
" </thead>\n", | |
" <tbody>\n", | |
" </tbody>\n", | |
"</table>\n", | |
"</div>" | |
], | |
"text/plain": [ | |
"Empty DataFrame\n", | |
"Columns: [repo_name, url, author, committer, message, username, history_size]\n", | |
"Index: []" | |
] | |
}, | |
"execution_count": 6, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# Copio df_commits\n", | |
"test_1 = df_commits.copy()\n", | |
"# Ignoro commits hechos via web\n", | |
"test_1 = test_1[test_1[\"committer\"] != \"web-flow\"]\n", | |
"# Selecciono commits con 'author' distinto de 'committer'\n", | |
"test_1 = test_1[test_1[\"author\"] != test_1[\"committer\"]]\n", | |
"\n", | |
"test_1" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Último *commit* no tiene como *committer* a usuario de GitHub" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 100.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 97.96% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 97.67% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 97.67% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 97.22% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.97% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.88% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.83% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.59% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.3% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 96.23% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 95.83% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 95.74% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.87% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.74% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.67% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.62% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.59% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.59% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.44% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 94.44% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 93.94% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 93.62% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 93.02% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 90.74% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 89.74% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 89.29% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 89.19% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 87.88% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 87.5% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 87.1% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 85.71% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 85.07% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 84.78% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 83.64% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 82.4% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 82.35% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 79.49% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 77.78% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 72.09% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 56.12% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 53.85% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 50.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 47.27% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 46.88% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 25.0% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 8.82% de sus últimos commits\n", | |
"Usuario REDACTED firmó como REDACTED en el 8.0% de sus últimos commits\n" | |
] | |
} | |
], | |
"source": [ | |
"# Copio df_commits\n", | |
"test_2 = df_commits.copy()\n", | |
"# Ignoro los commits hecho via web\n", | |
"test_2 = test_2[test_2[\"committer\"] != \"web-flow\"]\n", | |
"# Cuento cuantos commits inconsistentes hay\n", | |
"test_2[\"anomaly_size\"] = test_2.groupby([\"repo_name\", \"committer\"])[\"username\"].transform(len)\n", | |
"# Tomo el último commit en cada repo para determinar si la inconsistencia se mantiene\n", | |
"test_2 = test_2.groupby([\"repo_name\"]).head(1)\n", | |
"# Me lo quedo si es inconsistente\n", | |
"test_2 = test_2[test_2[\"committer\"] != test_2[\"username\"]]\n", | |
"# Calcular proporcion errónea\n", | |
"test_2[\"error_rate\"] = test_2[\"anomaly_size\"] / test_2[\"history_size\"]\n", | |
"# Ordenar por tasa de error\n", | |
"test_2 = test_2.sort_values([\"error_rate\"], ascending=False)\n", | |
"# Output definitivo\n", | |
"test_2 = test_2[[\"username\", \"committer\", \"error_rate\"]]\n", | |
"\n", | |
"# Imprimir\n", | |
"for _, (user, committer, error_rate) in test_2.iterrows():\n", | |
" user = \"REDACTED\"\n", | |
" committer = \"REDACTED\"\n", | |
" print(f\"Usuario {user} firmó como {committer} en el {round(100 * error_rate, 2)}% de sus últimos commits\")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Datos interesantes" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Les alumnes han realizado 17710 commits durante el curso\n" | |
] | |
} | |
], | |
"source": [ | |
"print(f\"Les alumnes han realizado {len(df_commits)} commits durante el curso\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Un 5.47% de commits ha sido via web\n" | |
] | |
} | |
], | |
"source": [ | |
"int_1 = df_commits.copy()\n", | |
"int_1 = int_1[int_1[\"committer\"] == \"web-flow\"]\n", | |
"print(f\"Un {round(100 * len(int_1) / len(df_commits), 2)}% de commits ha sido via web\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Les 10 alumnes con más commits representan un 9.92% del total de commits de alumnes\n" | |
] | |
} | |
], | |
"source": [ | |
"sum_top_10 = df_commits.groupby(\"repo_name\").count()[\"history_size\"].sort_values(ascending=False)[:10].sum()\n", | |
"print(f\"Les 10 alumnes con más commits representan un {round(100 * sum_top_10 / len(df_commits), 2)}% del total de commits de alumnes\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Gráfico # commits / alumne\n" | |
] | |
}, | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<Figure size 432x288 with 1 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"ax = df_commits.groupby(\"repo_name\").count()[\"history_size\"].sort_values().plot(use_index=False)\n", | |
"ax.set_xlabel(\"Alumnes ordenados por # commits\")\n", | |
"ax.set_ylabel(\"# commits por alumne\")\n", | |
"print(\"Gráfico # commits / alumne\")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## *Commits* curiosos" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"El largo promedio de un mensaje de commit es 21.59\n" | |
] | |
} | |
], | |
"source": [ | |
"df_commits[\"message_len\"] = df_commits[\"message\"].apply(len)\n", | |
"commits_largo = df_commits[\"message_len\"]\n", | |
"print(f\"El largo promedio de un mensaje de commit es {round(commits_largo.mean(), 2)}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Hay 682 commits solo escritos con mayúsculas\n" | |
] | |
} | |
], | |
"source": [ | |
"df_commits[\"is_upper\"] = df_commits[\"message\"].apply(lambda m: m.isupper())\n", | |
"commits_mayusculas = df_commits[df_commits[\"is_upper\"]]\n", | |
"print(f\"Hay {len(commits_mayusculas)} commits solo escritos con mayúsculas\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Hay 2 commits con que coinciden con ^AC[0]?0$ como mensaje\n", | |
"Hay 17 commits con que coinciden con ^AF[0]?1$ como mensaje\n", | |
"Hay 40 commits con que coinciden con ^AF[0]?2$ como mensaje\n", | |
"Hay 41 commits con que coinciden con ^AF[0]?3$ como mensaje\n", | |
"Hay 0 commits con que coinciden con ^AF[0]?4$ como mensaje\n", | |
"Hay 58 commits con que coinciden con ^AS[0]?1$ como mensaje\n", | |
"Hay 77 commits con que coinciden con ^AS[0]?2$ como mensaje\n", | |
"Hay 69 commits con que coinciden con ^AS[0]?3$ como mensaje\n", | |
"Hay 0 commits con que coinciden con ^AS[0]?4$ como mensaje\n", | |
"Hay 13 commits con que coinciden con ^T[0]?0$ como mensaje\n", | |
"Hay 74 commits con que coinciden con ^T[0]?1$ como mensaje\n", | |
"Hay 1 commits con que coinciden con ^T[0]?2$ como mensaje\n" | |
] | |
} | |
], | |
"source": [ | |
"match_evaluaciones = [\n", | |
" # ^ y $ indican match de inicio a fin\n", | |
" # El primer 0 es opcional (por eso el '?')\n", | |
" r\"^AC[0]?0$\", # AC00 \n", | |
" r\"^AF[0]?1$\", # AF01 \n", | |
" r\"^AF[0]?2$\", # AF02 \n", | |
" r\"^AF[0]?3$\", # AF03 \n", | |
" r\"^AF[0]?4$\", # AF04\n", | |
" r\"^AS[0]?1$\", # AS01\n", | |
" r\"^AS[0]?2$\", # AS02\n", | |
" r\"^AS[0]?3$\", # AS03\n", | |
" r\"^AS[0]?4$\", # AS04\n", | |
" r\"^T[0]?0$\", # T00\n", | |
" r\"^T[0]?1$\", # T01\n", | |
" r\"^T[0]?2$\", # T02\n", | |
"]\n", | |
"\n", | |
"for evaluacion in match_evaluaciones:\n", | |
" coincidencias = df_commits[\"message\"][df_commits[\"message\"].str.count(evaluacion)==1]\n", | |
" print(f\"Hay {len(coincidencias)} commits con que coinciden con {evaluacion} como mensaje\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Hay 362 commits con emojis provenientes de 28 repositorios\n", | |
"Los 10 emojis más usados son:\n", | |
":star:, 70 veces\n", | |
":bug:, 33 veces\n", | |
":tada:, 28 veces\n", | |
":hammer:, 20 veces\n", | |
":star2:, 16 veces\n", | |
":smile:, 15 veces\n", | |
":sparkles:, 15 veces\n", | |
":bookmark:, 15 veces\n", | |
":heart:, 15 veces\n", | |
":pencil:, 11 veces\n" | |
] | |
} | |
], | |
"source": [ | |
"commits_con_emojis = df_commits[df_commits[\"message\"].str.count(r\":[\\w]*:\") > 0]\n", | |
"repos_con_emojis = len(commits_con_emojis.groupby(\"repo_name\"))\n", | |
"print(f\"Hay {len(commits_con_emojis)} commits con emojis provenientes de {repos_con_emojis} repositorios\")\n", | |
"\n", | |
"emojis_usados = Counter()\n", | |
"for c in commits_con_emojis[\"message\"]:\n", | |
" emojis_usados.update(re.findall(r\":[\\w]*:\", c))\n", | |
"\n", | |
"print(\"Los 10 emojis más usados son:\")\n", | |
"for emoji, apariciones in emojis_usados.most_common(10):\n", | |
" print(f\"{emoji}, {apariciones} veces\")" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.8.1" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment