资讯专栏INFORMATION COLUMN

[译] 深入对比数据科学工具箱:Python 和 R 的 C/C++ 实现

jimhs / 395人阅读

摘要:另外一个我们同时使用两种语言的原因是已有的统计学工具与包。对另一些为读者写数据科学工具的人来说他们从一开始就考虑了这些跨语言。和实际上是用实现的这是条阻力最小的路径。无论是哪个赢得这场语言战争,和都将保持在数据科学届的地位。

概述

几周前,我有幸在 Scipy 大会上发表了 Civis如何使用Python和R的演讲。为什么要在一个Python大会上大谈R呢?这是要挑起一个Python和R语言的一场战争吗?不是的!讨论哪个语言比较好简直是浪费时间。在 Civis,我们很愉快地同时使用这两种语言,不仅仅是在我们日常工作中解决数据科学问题,也用它们来写一些其他工具。下面是我在SciPy 大会上的一些讨论。

问题现状

我们 Civis 的同事有着十分不同的学术背景。我效力的研发团队有一个物理学家、一个经济学家、两个统计学家以及一位土木工程师组成。在 Civis,每个人在数据科学上都会做一些不同内容的工作,有的领域是R比较流行,有的领域是Python比较流行,还有的一些是Matlab。在这种场景下只支持一种语言并不是一个明智的选择。迁移到一门新语言上会花费许多时间,抛开在学院或者业界多年的技能回报,允许人们使用它们所熟悉的工具可以确保大家有更高的生成效率。

另外一个我们同时使用两种语言的原因是已有的统计学工具与包。在解决数据科学问题上,我们经常遇到在某些特定管道上需要某一种特定语言。我们的调研管道是一个很好的例子。确保随机样本具有全集代表性是需要一个被称为 raking 的过程的,传统上,Python 在社会科学上并不流行,因此我们只会用R来完成这件事情。当然调查也包括了QA的全文检索,在某种程度上来说R在NLP社区上并不是很流行,因此这个部分将会由Python来完成。而分析调查数据只是我们在Civis解决问题的一个小步骤。

结合许多不同的语言来实现工作流是具有挑战性的。数据科学平台帮助我们可以提交一连串任务节点,然后交由基础设施负责独立调用每个任务节点,且前后不依赖。但是这并不是一个十分理想的情况。原因有二,其一,这样做整个任务就显得破碎化,稍有修改某个任务,往往会导致全局失败,而且任何人在这个分布式系统中所做的工作都只了解局部情况,而不知道全局情况。第二,这样做并不高效。在两个语言中切换通常需要将数据以特定格式加载到磁盘上(通常最坏的情况是csv格式)。这样不仅仅是解析成本比较高,而且还会丢失一些类型信息。

解决方案

我概述了在Civis遇到的种种问题,但是到底什么是理想的状态呢?最完美的解决方案是我们可以无缝切换工具和语言。许多熟悉Python的人喜欢用Python做数据分析,R也是类似的。事实证明这是完全有可能实现,有相当数量的项目已经开始作为跨语言工具:TensorFlow, XGBoost, 和 Stan 都在Civis被广泛运用。移植或安利一个已有的工具也是可能的,我们已经成功地完成了glmnetR包的安利。

对另一些为读者写数据科学工具的人来说,他们从一开始就考虑了这些跨语言。有一些方法可以做到这一点,但我个人最喜欢的是用C语言来写底层,然后使用各自的Python和R C api做一些绑定/封装。Python和R实际上是用C实现的,这是条阻力最小的路径。C是一门古老语言,C语言社区已经演进出了一些强大的工具链。晦涩的编译器错误消息已经成为了过去时,GCC和Clang(最流行的编译器)给友善的消息反馈(Clang网站可以看到栗子)。现在还有各种各样的“消毒液”来辅助捕获内存泄漏等常见错误或未定义的行为(llvm文档)。

案例

下面我们通过一个小例子,用C编写一个函数,使这可调用的Python和R。代码以及幻灯片从我的GitHub上可以找到。

Python

我们将下面的Python函数转换为C:

def tally(s):
    total = 0
    for elm in s:
        total += elm
    return total
C

这是相同的功能用C实现:

#include 

double tally(double *s, size_t n) {
    double total = 0;
    for (size_t i = 0; i < n; i++) {
        total += s[i];
    }
    return total;
}

注意到它看起来并不是都不同的Python函数。当然,除了有一些类型注解和额外的语法噪音大括号外,我们还必须跟踪数组的长度,但整体逻辑是一样的。

接下来,我们需要实现一个Python绑定,允许用户调用这个函数就像任何其他Python函数。

Cython
#include 
#include "Python.h"
#include "tally.h"

static PyObject *tally_(PyObject *self, PyObject *args) {
    PyObject *buf;
    if (!PyArg_ParseTuple(args, "O", &buf)) {
        return NULL;
    }

    Py_buffer view;
    int buf_flags = PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT;
    if (PyObject_GetBuffer(buf, &view, buf_flags) == -1) {
        return NULL;
    }
    
    if (strcmp(view.format,"d") != 0) {
        PyErr_SetString(PyExc_TypeError, "we only take floats :(");
        PyBuffer_Release(&view);
        return NULL;
    }

    double result = tally(view.buf, view.shape[0]);
    PyBuffer_Release(&view);
    return Py_BuildValue("d", result);
}

static PyMethodDef MethodTable[] = {
    {"tally", &tally_, METH_VARARGS, "Compute the sum of an array"}, 
    { NULL, NULL, 0, NULL}
};

static struct PyModuleDef tally_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "tally_",
    .m_size = -1,
    .m_methods = MethodTable
};


PyMODINIT_FUNC PyInit_tally_(void) {
    return PyModule_Create(&tally_module);
}

这里有很多实现的方法,但大多数只是Python模块的一部分样板代码。
在最上面,我们定义了一个函数,它接受一个Python对象,并且检查这是一个适当类型的数组,再调用我们的计数功能,然后返回结果。其余的代码模块定义,告诉Python解释器我们计数功能的名称和它的参数类型。

R

R的过程非常相似,但是更加简洁:

#include 
#include 
#include 
#include "tally.h"

SEXP tally_(SEXP x_) {
  double *x = REAL(x_);
  int n = length(x_);
  
  SEXP out = PROTECT(allocVector(REALSXP, 1));
  REAL(out)[0] = tally(x, n);
  UNPROTECT(1);
  
  return out;
}

static R_CallMethodDef callMethods[] = {
  {"tally_", (DL_FUNC)&tally_, 1},
  {NULL, NULL, 0}
};

void R_init_tallyR(DllInfo *info) {
  R_registerRoutines(info, NULL, callMethods, NULL, NULL);
}

这里需要的代码量显著减少,因为R和Python类型系统有所不同,没有真正标量类型,所以我们不需要做相同级别的检验/验证用户输入我们在上面的Python示例。剩下的代码大致相同,我们定义一个组函数可在R编译。

总结

一个真实世界的例子一定会更加复杂,但整个过程并不是那么困难。在编写跨语言工具时有几件事要记住:

如果你打算在两者之间共享函数就不要依赖宿主语言的api(R或Python)代码。

使用错误码来传递异常提示,不要直接调用退出或者在宿主语言里面才处理异常。

最好用宿主语言负责内存分配和重分配,这意味着你的C/C++代码应该要略过预先分配的内存和输出过程。

相信编译器,你也应该重视编译器的错误和警告。如果代码在编译时有警告那代码就不算写完。

无论是哪个“赢得”这场语言战争,Python和R都将保持在数据科学届的地位。这意味着为工具开发者不能忽视的另外一门语言,构建有用的工具就得保证这两种语言都可以使用。一个简单的方式是用C或c++编写大量代码,然后用 C 的 API的为两种语言提供封装。

参考资料

GitHub Scipy 2016

Linkedin: Python or R?

原作者:Bill Lattner 翻译:Harry Zhu
英文原文地址:Forget Python vs. R: how they can work together

作为分享主义者(sharism),本人所有互联网发布的图文均遵从CC版权,转载请保留作者信息并注明作者 Harry Zhu 的 FinanceR专栏:https://segmentfault.com/blog...,如果涉及源代码请注明GitHub地址:https://github.com/harryprince。微信号: harryzhustudio
商业使用请联系作者。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38123.html

相关文章

  • [原]深入对比数据科学具箱PythonR之争[2016版]

    摘要:概述在真实的数据科学世界里,我们会有两个极端,一个是业务,一个是工程。偏向业务的数据科学被称为数据分析,也就是型数据科学。所以说,同时学会和这两把刷子才是数据科学的王道。 showImg(https://segmentfault.com/img/bVAgki?w=980&h=596); 概述 在真实的数据科学世界里,我们会有两个极端,一个是业务,一个是工程。偏向业务的数据科学被称为数据...

    whidy 评论0 收藏0
  • [原] 深入对比数据科学具箱Python R 异常处理机制

    摘要:对于异常机制的合理运用是直接关系到码农饭碗的事情所以,本文将具体介绍一下和的异常处理机制,阐明二者在异常处理机制上的异同。下面将具体介绍二者的异常处理机制。 概述 showImg(https://segmentfault.com/img/remote/1460000006760426); 异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正...

    FreeZinG 评论0 收藏0
  • [原]深入对比数据科学具箱PythonR Web 编辑器

    摘要:概述工欲善其事必先利其器,如果现在要评选数据科学中最好用的编辑器注意一定是可以通过访问的,和一定是角逐的最大热门,正确使用编辑器可以很大地提升我们的工作效率。 概述 showImg(https://segmentfault.com/img/bVAdol); 工欲善其事必先利其器,如果现在要评选数据科学中最好用的Web 编辑器(注意一定是可以通过Web访问的),RStudio和Jupyt...

    RobinQu 评论0 收藏0
  • [原]深入对比数据科学具箱PythonR 非结构化数据结构化

    摘要:则在读取数据时将两个中文字段混淆成了一个字段,导致整个数据结构错乱。三条路子全军覆没,这让我情何以堪,好在使用的经验颇丰,通过中文的转换和切割就轻松解决了这个问题。 概述 showImg(https://segmentfault.com/img/bVylLL); 在现实场景中,由于数据来源的异构,数据源的格式往往是难以统一的,这就导致大量具有价值的数据通常是以非结构化的形式聚合在一起的...

    leiyi 评论0 收藏0

发表评论

0条评论

jimhs

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<