Nous allons récupérer, pour chaque communauté, la liste des auteurs de packages. Nous essayerons ensuite de faire un merging des identités, et d'identifier la taille de chaque communauté en terme d'auteurs, ainsi que les auteurs qui sont présents dans plusieurs communautés.
In [1]:
import pandas
github = pandas.read_csv('../data/github-description.csv')
cran = pandas.read_csv('../data/cran-description.csv')
bioconductor = pandas.read_csv('../data/bioconductorsvn_description.csv')
r_forge = pandas.read_csv('../data/r-forge_description.csv')
rforge = pandas.read_csv('../data/rforge_description.csv')
Nous récupérons, pour chaque package de chaque communauté, le champ Author. A noter que le champ Authors existe aussi dans certains cas (CRAN, GitHub) mais en faible proportion (8 packages sur CRAN, 42 sur GitHub par exemple).
In [2]:
# Drop packages belonging to "cran" user on GitHub
github = github[github['owner'] != 'cran']
github = github[github['owner'] != 'rpkg']
# Pivot on key->value
_packages = github[github['key'] == 'Package'].rename(columns={'value': 'Package'})[['owner', 'repository', 'Package']]
_author = github[github['key'] == 'Author'].rename(columns={'value': 'Author'})
_github = _packages.merge(_author, how='left', on=('owner', 'repository'))
github = _github.set_index('Package')[['Author']]
# Indexes
bioconductor = bioconductor.set_index('Package')[['Author']]
rforge = rforge.set_index('Package')[['Author']]
r_forge = r_forge.set_index('Package')[['Author']]
# Pivot & index
_cran = cran.drop_duplicates(('package', 'key'), take_last=True)
_author = _cran[_cran['key'] == 'Author'].rename(columns={'value': 'Author'})
_cran = _cran.merge(_author, how='left', on='package')
cran = _cran.rename(columns={'package': 'Package'}).drop_duplicates('Package').set_index('Package')[['Author']]
In [3]:
sources = [
('github', github),
('cran', cran),
('bioconductor', bioconductor),
('rforge', rforge),
('r-forge', r_forge)
]
Nous allons devoir parcourir les champs Author et récupérer la liste des auteurs. Ensuite, il conviendra de "nettoyer" chaque auteur pour obtenir une sorte d'identité canonique qui servira de base pour le matching. L'approche est discutable, mais a le mérite d'être relativement simple. Nous pourrons manuellement attester de la qualité de cette approche.
La fonction split_author_list
se base sur l'expression régulière suivant pour "décomposer" la liste des auteurs (format textuel) en une liste d'auteurs. Les séparateurs sont listés dans RE_SPLIT_AUTHORS
.
In [4]:
import re
RE_SPLIT_AUTHORS = re.compile(r'(,|;|( \- )| | & |(and)|(with)|(contributions from))')
def split_author_list(authors):
candidates = re.split(RE_SPLIT_AUTHORS, authors)
return map(lambda x: x.strip(), filter(lambda x: x is not None and len(x) > 5, candidates))
La fonction clean_name
utilise l'expression RE_GREP_AUTHOR
pour identifier les auteurs. Grosso-modo, on procède comme suit :
In [5]:
RE_GREP_AUTHOR = re.compile(r'^([a-z\-]{3,}).?$')
def clean_name(name):
name = name.replace('\n', ' ')
_ = name.lower().split(' ')
names = []
for e in _:
for f in re.findall(RE_GREP_AUTHOR, e):
names.append(f)
names.sort()
return names
Le bloc suivant va composer le dictionnaire name
. Ce dictionnaire contiendra comme clés les noms canoniques des auteurs. Chaque auteur est associé à un dictionnaire contenant, pour chaque source, l'ensemble des packages pour lesquels cet auteur est repris. Une entrée supplémentaire names
contient également la liste des noms qui ont été associés à cet auteur (pour vérification manuelle, par exemple).
In [6]:
names = {}
for source_name, source in sources:
for index, value in source.fillna('').iterrows():
for dirty in split_author_list(value['Author']):
author = ' '.join(clean_name(dirty))
if len(author) > 0:
d_author = names.setdefault(author, {})
d_author.setdefault('names', set()).add(dirty)
d_author.setdefault(source_name, set()).add(index)
La fonction suivante (un peu moche, et qui nécessite des variables globales) va retourner un dictionnaire avec comme clés chaque source (CRAN, GitHub, etc.). Comme valeur associé à cette clé, un ensemble des auteurs présents dans cette source. Le paramètre N
permet de définir un "nombre minimum de packages distincts à développer pour être considéré comme auteur".
In [24]:
def _author_sets(N = 1):
authors = {source[0]: set() for source in sources}
for name, dname in names.iteritems():
pkg = set()
for k,p in dname.iteritems():
if k != 'names':
pkg = pkg.union(p)
if len(pkg) >= N:
for key in dname:
if key != 'names':
authors[key].add(name)
return authors
In [25]:
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib_venn import venn3
figure, axes = plt.subplots(5, 2, figsize=(15,30))
for i in range(5):
authors = _author_sets(i+1)
venn3((authors['github'], authors['cran'], authors['bioconductor']), ('github', 'cran', 'bioconductor'), ax=axes[i][0])
venn3((authors['github'], authors['cran'], authors['r-forge']), ('github', 'cran', 'r-forge'), ax=axes[i][1])