内容


使用数据科学管理 GitHub 组织中的软件项目,第 1 部分

从零开始创建一个数据科学项目

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用数据科学管理 GitHub 组织中的软件项目,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用数据科学管理 GitHub 组织中的软件项目,第 1 部分

敬请期待该系列的后续内容。

本系列将解决两个问题:如何使用数据科学对围绕软件工程的项目管理进行调查研究,以及如何向 Python Package Index 发布数据科学工具。

数据科学作为一门学科正在飞速发展,许多文章都在讨论诸如使用哪种算法之类的主题的细节。但是,很少有文章介绍如何收集数据、创建项目结构,以及将软件发布到 Python Package Index。本教程将提供这两个主题的详细的实践操作说明。本系列的源代码可在 GitHub 上获得。

软件项目管理问题

尽管软件行业已存在数十年,但它仍受到延迟交付和质量欠佳等挥之不去的问题的困扰。其他问题包括评估团队和各个开发人员的表现。此外,软件行业目前的一种趋势是雇佣自由职业者和合同工,这带来了另一个问题:组织如何评估这些非员工人员的才能?此外,积极主动的专业软件开发人员希望找到更好的工作方式。他们能从全球最优秀的软件开发人员那里借鉴哪些模式?

幸运的是,开发人员创建了行为日志,其中包含可用来帮助回答这些问题的信号。开发人员每次向存储库执行提交时,都会创建一个信号。然后,管理人员可以使用数据科学来探索这些信号。

值得注意的是,除非经过恰当评估,否则这些信号可能具有误导性。只需花几分钟与聪明但又有点狡猾的开发人员讨论源代码元数据,就会听到他或她说,“哦,这没有任何意义。我可以钻制度的空子。”例如,考虑这样一位开发人员:该开发人员的 GitHub 配置文件显示一年内执行了 3,000 次提交,这相当于每天提交约 10 次。这看起来的确很了不起,但是设想一下,如果这些提交可能是一个自动化脚本创建的,或者它们只是向自述文件添加一行代码的“虚假”提交呢。这些就是您必须找出的各种欺骗性信号。

要考虑的探索性问题

评估一个项目和它的开发团队时,以下是最初要考虑的部分问题:

  • 好(或差)的软件开发人员或团队有何特征?
  • 是否有可以预测缺陷软件的信号?
  • 软件项目经理如何发现信号,从而采取行动来扭转陷入困境的项目?
  • 开源项目与闭源项目看起来是否有区别?
  • 是否有信号可以识别“钻制度空子”的开发人员?
  • 您是否有不可靠的开发人员,比如他们的提交有巨大差距?
  • 您是否由于举行太多会议而正在破坏团队的生产力?

在这一部分和随后的第 2 部分中,我将推荐一些能帮助回答这些和其他问题的技术。

创建一个初始数据科学项目框架

在新数据科学解决方案的开发中,一个常常被忽略的部分是项目的初始结构。在开始工作之前,最佳实践是创建一个布局来促进高质量工作和建立合乎逻辑的组织。可通过许多方式设计项目结构,而这里是其中一种建议(参见此列表后的清单,查看实际输出):

  • .circleci 目录:此目录包含使用 CircleCI SaaS 构建服务来构建项目所需的配置。(有许多类似服务可用于开源软件。例如,可以使用 Jenkins 等开源工具。)
  • .gitignore:确保忽略不属于项目的文件。这是一种常见的失策。
  • CODE_OF_CONDUCT.md:在项目中放入一些有关您期望贡献者表现出的行为的信息,这是一个不错的主意。
  • CONTRIBUTING.MD:有关您将如何接受贡献的明确说明会对招聘很有帮助。
  • LICENSE:拥有一个许可(比如 MITBSD)会很有帮助。在某些情况下,如果您没有许可,一个潜在的贡献者可能无法参与项目。
  • Makefile:makefile 是一种运行测试、部署和设置环境的优秀工具。
  • README.md:良好的 README.md 会回答基本问题,比如用户如何构建项目和项目有何用途。此外,README.md 可能提供“徽章”来显示项目的质量,比如一次已通过的构建。
  • 命令行工具:在我的示例中,有一个 dml 命令行工具。拥有一个 cli 接口,这对探索您的库和创建实例来执行测试都很有帮助。
  • 包含 __init__.py 的库目录:在项目的根目录位置,应该创建一个包含 __init__.py 的库目录来表明它是可导入的。在本例中,该库名为 devml
  • ext 目录:此目录是放置 config.json 或 config.yml 文件之类内容的好地方。将非代码内容放在一个可以集中引用的地方会更好。可能还需要一个 data 子目录来创建一些本地删减样本(用于探索)。
  • notebooks 目录:一个包含 Jupyter Notebook 的特定目录,有助于轻松地集中开发与 notebook 相关的代码。此外,它还使得设置 notebook 的自动化测试变得更容易。
  • requirements.txt:一个包含项目所需的包列表的文件。
  • setup.py:一个设置 Python 包的部署方式的配置文件。也可以使用该文件将 Python 包部署到 Python Package Index。
  • tests 目录:一个放置测试数据的目录。

下面给出了一个列出上面讨论的特定组件的 ls 命令的输出。

(.devml) ➜ devml git:(master) ✗ ls -la 
drwxr-xr-x  3 noahgift staff   96 Oct 14 15:22 .circleci
-rw-r--r--  1 noahgift staff  1241 Oct 21 13:38 .gitignore
-rw-r--r--  1 noahgift staff  3216 Oct 15 11:44 CODE_OF_CONDUCT.md
-rw-r--r--  1 noahgift staff  357 Oct 15 11:44 CONTRIBUTING.md
-rw-r--r--  1 noahgift staff  1066 Oct 14 14:10 LICENSE
-rw-r--r--  1 noahgift staff  464 Oct 21 14:17 Makefile
-rw-r--r--  1 noahgift staff 13015 Oct 21 19:59 README.md
-rwxr-xr-x  1 noahgift staff  9326 Oct 21 11:53 dml
drwxr-xr-x  4 noahgift staff  128 Oct 20 15:20 ext
drwxr-xr-x  7 noahgift staff  224 Oct 22 11:25 notebooks
-rw-r--r--  1 noahgift staff  117 Oct 18 19:16 requirements.txt
-rw-r--r--  1 noahgift staff  1197 Oct 21 14:07 setup.py
drwxr-xr-x 12 noahgift staff  384 Oct 18 10:46 tests

类似这样的指标的一个要点是,它显示了参与信息。在最优秀的开源开发人员中,存在一些富有魅力的相似之处。

收集并转换数据

跟平常一样,问题的最糟糕部分是确定如何收集数据并将其转换为有用的格式。这个问题有多个部分需要解决。首先是如何收集一个存储库,并利用该存储库创建一个 pandas DataFrame。为了完成此任务,可以在 devml 目录中创建一个名为 mkdata.py 的新模块。此模块可以解决将 git 存储库的元数据转换为 pandas Dataframe 的问题。

这是我的 mkdata.py 模块的一部分。log_to_dict 函数为磁盘上的一次 git 检出指定路径,然后转换 git 命令的输出。

def log_to_dict(path): 
  """Converts Git Log To A Python Dict"""
  
  os.chdir(path) #change directory to process git log
  repo_name = generate_repo_name()
  p = Popen(GIT_LOG_CMD, shell=True, stdout=PIPE)
  (git_log, _) = p.communicate()
  try: 
    git_log = git_log.decode('utf8').strip('\n\x1e').split("\x1e")
  except UnicodeDecodeError: 
    log.exception("utf8 encoding is incorrect, trying ISO-8859-1")
    git_log = git_log.decode('ISO-8859-1').strip('\n\x1e').split("\x1e")
    
  git_log = [row.strip().split("\x1f") for row in git_log]
  git_log = [dict(list(zip(GIT_COMMIT_FIELDS, row))) for row in git_log]
  for dictionary in git_log: 
    dictionary["repo"]=repo_name
  repo_msg = "Found %s Messages For Repo: %s" % (len(git_log), repo_name)
  log.info(repo_msg)
  return git_log

在接下来的 2 个函数中,使用磁盘上的一个路径来调用上面的函数。请注意,日志存储为一个列表中的项,此列表被用于在 pandas 中创建一个 DataFrame:

def create_org_df(path): 
  """Returns a Pandas Dataframe of an Org"""
  original_cwd = os.getcwd()
  logs = create_org_logs(path)
  org_df = pd.DataFrame.from_dict(logs)
  #convert date to datetime format
  datetime_converted_df = convert_datetime(org_df)
  #Add A Date Index
  converted_df = date_index(datetime_converted_df)
  new_cwd = os.getcwd()
  cd_msg = "Changing back to original cwd: %s from %s" % (original_cwd, new_cwd)
  log.info(cd_msg)
  os.chdir(original_cwd)
  return converted_df
def create_org_logs(path): 
  """Iterate through all paths in current working directory,
  make log dict"""
  combined_log = []
  for sdir in subdirs(path): 
    repo_msg = "Processing Repo: %s" % sdir
    log.info(repo_msg)
    combined_log += log_to_dict(sdir)
  log_entry_msg = "Found a total log entries: %s" % len(combined_log)
  log.info(log_entry_msg)
  return combined_log

实际上,在没有将数据收集到 DataFrame 的情况下运行时,此代码类似于:

In [5]: res = create_org_logs("/Users/noahgift/src/flask")
2017-10-22 17:36:02,380 - devml.mkdata - INFO - Found repo: /Users/noahgift/src/flask/flask
In [11]: res[0]
Out[11]: 
{'author_email': 'rgerganov@gmail.com',
 'author_name': 'Radoslav Gerganov',
 'date': 'Fri Oct 13 04:53:50 2017',
 'id': '9291ead32e2fc8b13cef825186c968944e9ff344',
 'message': 'Fix typo in logging.rst (#2492)',
 'repo': b'flask'}

第二节(创建该 DataFrame)类似于下面的清单。

res = create_org_df("/Users/noahgift/src/flask")
In [14]: res.describe()
Out[14]: 
    commits
count  9552.0
mean    1.0
std    0.0
min    1.0
25%    1.0
50%    1.0
75%    1.0
max    1.0

总体上讲,这是一种从第三方(比如 Git 日志)获取专用数据的模式。要更详细地分析此模式,请查看完整的源代码。

与整个 GitHub 组织进行通信

将磁盘上的 Git 存储库就地转换为 DataFrame 的代码后,下一步自然是收集一个组织的所有存储库。分析一个存储库的一个关键问题是,在组织上下文中,它不是分析的完整的数据。修复此问题的一种方法是,连接 GitHub API 并以编程方式获取所有存储库。我使用了 fetch_repo.py 来执行此工作,下面给出了代码的重点部分。

def clone_org_repos(oath_token, org, dest, branch="master"): 
  """Clone All Organizations Repositories and Return Instances of Repos.
  """
  if not validate_checkout_root(dest): 
    return False
  repo_instances = []
  repos = org_repo_names(oath_token, org)
  count = 0
  for name, url in list(repos.items()): 
    count += 1
    log_msg = "Cloning Repo # %s REPO NAME: %s , URL: %s " %\
             (count, name, url)
    log.info(log_msg)
    try: 
      repo = clone_remote_repo(name, url, dest, branch=branch)
      repo_instances.append(repo)
    except GitCommandError: 
      log.exception("NO MASTER BRANCH...SKIPPING")
  return repo_instances

PyGithub 和 GitPython 包都被用来执行大量繁重的工作。当运行此代码时,它会以迭代方式利用该 API 查找每个 repo 并克隆它。然后,可以使用上面的代码创建一个组合的 DataFrame。

创建特定于领域的统计数据

完成所有这些工作是为了实现一个目的:探索收集的数据并创建特定于领域的统计结果。为此,可以创建一个 stats.py 文件。要展示的最相关部分是一个名为 author_unique_active_days 的函数。此函数展示了给定开发人员用于处理 DataFrame 中的记录的活跃天数。这是一个特定于领域的独特统计指标,在涉及源代码存储库的统计讨论中很少提及它。

主要函数如下所示。

def author_unique_active_days(df, sort_by="active_days"): 
  """DataFrame of Unique Active Days by Author With Descending Order
  author_name	unique_days
  46	Armin Ronacher	271
  260	Markus Unterwaditzer	145
  """
  author_list = []
  count_list = []
  duration_active_list = []
  ad = author_active_days(df)
  for author in ad.index: 
    author_list.append(author)
    vals = ad.loc[author]
    vals.dropna(inplace=True)
    vals.drop_duplicates(inplace=True)
    vals.sort_values(axis=0,inplace=True)
    vals.reset_index(drop=True, inplace=True)
    count_list.append(vals.count())
    duration_active_list.append(vals[len(vals)-1]-vals[0])
  df_author_ud = DataFrame()  
  df_author_ud["author_name"] = author_list
  df_author_ud["active_days"] = count_list
  df_author_ud["active_duration"] = duration_active_list
  df_author_ud["active_ratio"] = \
    round(df_author_ud["active_days"]/df_author_ud["active_duration"].dt.days, 2)
  df_author_ud = df_author_ud.iloc[1:]#first row is =
  df_author_ud = df_author_ud.sort_values(by=sort_by, ascending=False)
  return df_author_ud

通过 IPython 使用时,此代码会生成以下输出。

In [18]: from devml.stats import author_unique_active_days
In [19]: active_days = author_unique_active_days(df)
In [20]: active_days.head()
Out[20]: 
       author_name active_days active_duration active_ratio
46     Armin Ronacher     241    2490 days     0.10
260 Markus Unterwaditzer      71    1672 days     0.04
119      David Lord      58    710 days     0.08
352      Ron DuPlain      47    785 days     0.06
107   Daniel Neuhäuser      19    435 days     0.04

这些统计结果创建了一个名为 active_ratio 的比率,这是开发人员积极提交代码的时间占他参与项目的总时间的百分比。这样一个指标的一个关键之处是,它显示了参与情况。在最优秀的开源开发人员中,存在一些富有魅力的相似之处。在下一节中,我会将这些核心组件连接到一个命令行工具中,并使用创建的代码来比较两个开源项目。

将数据科学项目连接到 CLI 中

在此部分的前面,我展示了如何创建组件来了解可以运行的分析。在本节,我将展示如何将它们连接到一个使用 Click 框架的灵活的命令行工具中。可以查看 dml 的完整源代码。除此之外,下面给出了重要部分。

首先,导入库和 Click 框架。

#!/usr/bin/env python
import os
import click
from devml import state
from devml import fetch_repo
from devml import __version__
from devml import mkdata
from devml import stats
from devml import org_stats
from devml import post_processing

然后连接前面的代码。

@gstats.command("activity")
@click.option("--path", default=CHECKOUT_DIR, help="path to org")
@click.option("--sort", default="active_days", help="can sorty by: active_days, active_ratio, active_duration")
def activity(path, sort): 
  """Creates Activity Stats
  Example is run after checkout: 
  python dml.py gstats activity --path /Users/noah/src/wulio/checkout
  """
  org_df = mkdata.create_org_df(path)
  activity_counts = stats.author_unique_active_days(org_df, sort_by=sort)
  click.echo(activity_counts)

要使用此工具,它在命令行上看起来类似以下代码。

# Linux Development Active Ratio

dml gstats activity --path /Users/noahgift/src/linux --sort active_days
           author_name             active_days active_duration active_ratio
14541      Takashi Iwai            1677    4590 days           0.370000
4382       Eric Dumazet            1460    4504 days           0.320000
3641       David S. Miller         1428    4513 days           0.320000
7216       Johannes Berg           1329    4328 days           0.310000
8717       Linus Torvalds          1281    4565 days           0.280000
275        Al Viro                 1249    4562 days           0.270000
9915       Mauro Carvalho Chehab   1227    4464 days           0.270000
9375       Mark Brown              1198    4187 days           0.290000
3172       Dan Carpenter           1158    3972 days           0.290000
12979      Russell King            1141    4602 days           0.250000
1683       Axel Lin                1040    2720 days           0.380000
400        Alex Deucher            1036    3497 days           0.300000

# CPython Development Active Ratio

           author_name             active_days active_duration active_ratio
146        Guido van Rossum        2256    9673 days           0.230000
301        Raymond Hettinger       1361    5635 days           0.240000
128        Fred Drake              1239    5335 days           0.230000
47         Benjamin Peterson       1234    3494 days           0.350000
132        Georg Brandl            1080    4091 days           0.260000
375        Victor Stinner          980     2818 days           0.350000
235        Martin v. Löwis         958     5266 days           0.180000
36         Antoine Pitrou          883     3376 days           0.260000
362        Tim Peters              869     5060 days           0.170000
164        Jack Jansen             800     4998 days           0.160000
24         Andrew M. Kuchling      743     4632 days           0.160000
330        Serhiy Storchaka        720     1759 days           0.410000
44         Barry Warsaw            696     8485 days           0.080000
52         Brett Cannon            681     5278 days           0.130000
262        Neal Norwitz            559     2573 days           0.220000

在此分析中,Python 的 Guido 在一天内有 23% 的概率会工作,而 Linux 的 Linus 的工作概率为 28%。这种特定分析形式的最迷人之处在于,它显示了较长时间以来的行为。对于 CPython,许多作者也拥有全职工作,所以输出展示了更加令人难以置信的观察结果。另一个重要的分析是,查看一个组织的开发人员的历史背景(组合所有可用的存储库)。我注意到,在某些情况下,如果全职工作的话,一些非常资深的开发人员能够以约 85% 的活跃率输出代码。

结束语

在本系列的第 1 部分中,我展示了如何创建一个基本的数据科学框架并解释了各个部分。逐个构建了这些组件,以便从第三方位置获取数据,转换数据,分析数据,然后通过一个命令行接口以灵活的方式运行数据。在第 2 部分中,我将使用第 1 部分中构建的代码,使用 Jupyter Notebook 深入探索数据。最后,我将展示如何将项目部署到 Python Package Index。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Big data and analytics, Open source
ArticleID=1055309
ArticleTitle=使用数据科学管理 GitHub 组织中的软件项目,第 1 部分: 从零开始创建一个数据科学项目
publish-date=12122017