首页 > 编程技术 > 当使用Python进行大数据分析时, 开发者常犯的错误

[悬赏]当使用Python进行大数据分析时, 开发者常犯的错误 (已翻译100%)

查看 (763次)
英文原文:The Top Mistakes Developers Make When Using Python for Big Data Analytics
标签: Python
admin 发布于 2017-02-21 15:50:28 (共 12 段, 本文赏金: 50元)
参与翻译(7人): 周宇进 CatherineK Laurenwang jeffy greenflute dmwl_sy admin 默认 | 原文

【已悬赏】 赏金: 2元

##1 介绍

Python 语言由于其缩短了开发时间而被普遍赞扬,但是要用它进行有效数据分析并非没有缺陷。这个特征使得开发在开始的时候很简单(动态,宽容型系统),却可能毁了大型系统 、造成库混乱,缓慢的运行时间和不考虑数据的完整性会反而增加开发时间。

该文章描述了在使用Python和大数据的时候最普遍的造成时间浪费的原因,并且提供建议使其回到正轨、花时间到真正需要的事情上:运用创造力和科学的方法从多种多样的大数据中获得见解。

周宇进
翻译于 2017-02-23 12:41:30
 

参与本段翻译用户:
周宇进 CatherineK

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 3元

##2 Mistake #1: 无谓的重复工作

Python社区的数据分析库处于非常好的状态,它功能丰富并且经过了广泛测试,所以(有了它)为什么还要做重复的工作呢?

我经常看见在代码挑战时参与者需要加载一个要处理的csv文件到内存里。相当多的人花大量时间写常规的csv加载函数并且总是以访问缓慢、转变困难的字典结束。这使得他们没有时间把能力用在从数据产生见解上。

实在是没有理由陷在那些早已被解决的问题上,(不如)花几分钟google或者问一个更有经验的开发者找一个数据分析库。顺便一提,其中一个在目前非常普遍使用的库就是Python Pandas。它有处理大数据集的抽象化、很多用于ETL(提取,转变,加载)的功能函数和好的表现性。它通过简明的数据转化的表达、提供加载函数、从不同源以不同形式统一和储存数据减少了开发时间。

周宇进
翻译于 2017-03-10 09:46:01
 

参与本段翻译用户:
CatherineK 周宇进

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 3元

为了说明上述内容,我们来假设有一个csv文件表头为产品,售出项,我们想要找到十个最受欢迎的产品。比较一下在普通Python中的合理操作和在Python Pandas中使用强大的抽象层的操作:

###2.1 普通 Python

from collections import defaultdictheader_skipped = Falsesales = defaultdict(lambda: 0)with open(filename, 'r') as f:    for line in f:        if not header_skipped:           header_skipped = True           continue        line = line.split(",")        product = line[0]        num_sales = int(line[1])        sales[product]  = num_salestop10 = sorted(sales.items(), key=lambda x:x[1], reverse=True)[:10]
###2.2 Pandas



import pandas as pddata = pd.read_csv(filename) # header is conveniently inferred by defaulttop10 = data.groupby("Product")["ItemsSold"].sum().order(ascending=False)[:10]
注释:在普通Python里做这个工作的好处是不需要加载整个文件到内存里,然而pandas的后台工作最优化了输入输出端口和性能。此外,普通Python的保存在内存的结果sales字典也不是轻量级的。


周宇进
翻译于 2017-03-10 10:17:10
 

参与本段翻译用户:
CatherineK 周宇进

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 5元

##3 错误 #2: 没有对性能加以调整
如果程序需要太长的时间才能给出任何输出的话,开发者的节奏和专注就被打破了。太慢的程序还限制了开发者可以进行试验的次数 - 如果你的程序需要10分钟才能输出结果,那么你每天最多能调整和执行程序30次。
所以,如果你发现自己在干坐着等待程序运行,说明你需要去试着找找程序瓶颈了。有一些专门的工具可以帮助开发者对代码性能进行分析以便提速。其中不少都可以在IPython的交互执行环境下执行。
在IPython中最简单的办法当然是使用 %timeitmagic 命令来得到一条IIPython语句的执行时间。Line Profiler则是一个可供下载的更复杂的工具。启动IPython后,键入:



%load_ext line_profiler
%lprun -f function_to_profile statement_that_invokes_the_fuction

然后,你会得到形如下面的输出,描述某个函数每一行代码的执行时间以及所占百分比:



Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================

对于我个人而言,Line Profiler帮助我找到了前面提到过的Pyhon Pandas库中的瓶颈,并通过调整实现从而达到了近10倍的性能提升。
但是,如果你已经到了可以完全确信针对目标问题的算法已经达到了最优的复杂度和实现,也许可以试着把部分代码以cython来实现,以获得更好的性能。然后可以通过 %timeit 命令来比较cython之后和之前的代码。



greenflute
翻译于 2017-03-16 16:51:12
 

参与本段翻译用户:
CatherineK greenflute

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 2元

###3.1 未采用C扩展的Python版本

把这复制到IPython里:
 

def sum_uncythonized():
    a = 0
    for i in range(100000):
        a  = i
    return a

###3.2 采用C扩展的Python版本

如果还没cythonmagic,在IPython输入下面的内容来安装:


%load_ext cythonmagic


 然后把下面的文本复制-粘贴为一个单独的代码块



%%cython
def sum_cythonized():
    cdef long a = 0 # 直接定一个C变量,让变量的效率提高为C语言级别
    cdef int i = 0
    for i in range(100000):
        a  = i
    return a

 查看效果:



 %timeit sum_cythonized()
>>>10000次循环,三局两胜:每次循环耗时52.3微秒(总耗时=52.3微秒 * 10000 = 523000)
 %timeit sum_uncythonized()
>>>100次循环,三局两胜:每次循环耗时3.28毫秒(总耗时=3280微秒 * 100 = 328000)!???居然比Cython快

 我们仅通过定义类型就提速了2个量级。Cython魔法,名不虚传。(看了原文结果,表示很怀疑)
dmwl_sy
翻译于 2017-04-09 00:01:00
 

参与本段翻译用户:
CatherineK dmwl_sy

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 9元

##4 错误e #3: 没有正确理解时间和时区
对于首次接触时间的程序员来说,Epoch(纪元)是个比较难以掌握的概念。需要理解的基本概念是,在这个世界上,无论何时何地,Epoch时间都是一个一致的值,但是如和把这个值转换为当天的小时和分钟,则取决于所在时区以及季节(例如夏时制)。在Python中,这个转换工作是由datetime和pytz模块来完成的。
Pyhon的内置时间处理模块无论在函数命名上还是在不同的事件表示形式之前转换上都让人容易产生困惑,这是因为它内部其实是基于C的time库实现的,所以给人的感觉也很像似。能够正确的处理时间是很重要的,因为在时间序列中包含的数据是一种非常常见的形式。下列语句就体现了一个常见的误解:



dt = datetime.fromtimestamp(utc_number)

而当初次接触时区这个概念的时候,人们以为会返回UTC格式的日期和时间。而事实上,返回的是运行该代码的机器所在时区的日期和时间。这就让代码变得不可移植了。而我是在把同样的代码分别部署到本地和国外远程的机器上之后,才从发生了奇怪的偏移的数据图中才痛苦地意识到这个问题。而pytz模块配合datetime模块就能提供良好的时区支持。它可以在已知UTC时间的时候创建本地时间,并能正确地遵守夏时制。我们可以通过下列代码验证一下:



from datetime import datetime
import pytz
ams = pytz.timezone('Europe/Amsterdam')
winter = datetime(2000, 11, 1, 10, 0, tzinfo=pytz.utc)
summer = datetime(2000, 6, 1, 10, 0, tzinfo=pytz.utc)
print summer.astimezone(ams) # CET time is  2 hours
>>>2000-06-01 12:00:00 02:00 
print winter.astimezone(ams) # CEST time is  1 hour
>>>2000-11-01 11:00:00 01:00

但是,正如官方文档中所描述的,由于跟datetime模块的互操作性尚不完美,这个模块在好几种情况下都会返回出人意料的结果。例如在下面的例子中,我们知道阿姆斯特丹的冬令时与UTC时间的时差为一个小时,然而,下面代码显示的结果却不是:



td = datetime(2000, 11, 1, 10, 0, tzinfo=pytz.utc) - datetime(2000, 11, 1, 10, 0, tzinfo=ams)
 print td.total_seconds() 
>>>1200 # 20 minutes ? (somehow pytz falls back to a long outdated timezone setting for Amsterdam)

总的来说,Python的本地时间支持有的时候很缺乏创造力有的时候甚至完全没有。目前的解决办法管用,但是暴露出来的API很让人困惑。在一个类似Java中的JodaTime那样的库被开发出来以前,程序员们需要非常小心的处理日期和时间,并且要严格的测试以确保日期时间相关的方法能够返回他们需要的结果,比如是UTC还是本地时间,并且尽量在可能的情况下使用UTC格式存储时间。

最近涌现出了一批库如Delorean,times和arrow,试图跳出迷宫,通过提供更有创造性的API的库来解决Python的这个问题。


greenflute
翻译于 2017-03-16 21:27:25
 

参与本段翻译用户:
Laurenwang greenflute

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 7元


##5 错误#4 人工整合重量级的技术或其他脚本

为了能分析几十GB的数据,类似Python这样的脚本语言,无论如何优化,都可能不够有力。因此,下面这种情况是并不少见:开发人员选择一种更快速的框架来处理重量级的数据提取(基本过滤和切片操作),然后当进行数据的探索分析时,再利用Python的灵活性来处理这个结果(更小的)数据集。

整个过程看上去像是这样的:开发人员在一个数据集上启动一个Java的Map/Reduce Job,使用指令来过滤某一类产品的订单。等待它结束,人后使用命令行来将结果从HDFS复制到本地文件系统。然后再启动一个Python脚本,在这个数中找到最受欢迎的产品和日期。这将再产生另外一个结果文件,从这个结果文件再运行第二个python脚本或许可以被可视化。

使用这种方法的问题在于人工干预不能规模化(在任务的数量上),拷贝文件这种简单的动作:

  1. 重复而使人厌烦;
  2. 引起更多的人为错误(文件放错地方/起错名字,又或是简单地忘记刷新文件);并且
  3. 如果合作者需要重复开发人员的最终结果,则必须详细的记录文档。(即使是拷贝文件这种细小的动作)

解决方案:自动化。把不同技术与/或不同分析步骤的整合当做一个能凭自身力量被解决的问题。有许多框架可以用来管理一个复杂的数据分析管道 。 如果你喜欢Python,你或许想要尝试下Spotify的Luigi

使用Luigi,你能将不同种类的任务(Java Map/Reduce,Spark,Python,bash scripts)捆绑在一起,并创建你自己的个性化任务。它通过让你定义一个依赖于任务的图与输入、输出和每个任务的动作来工作。

用最后一个你想要运行的任务的名字(在我们的例子中,可视化最受欢迎的商品),来唤醒Luigi Scheduler,当必需的任务一个接着一个被启动(并行,当可能的时候)来产生你的最终结果时,你能放松一下。一键生成数据分析结果报告是令人满意并且有效率的。这样能为你解放出不少时间,从而在数据分析中更具有创造性。

jeffy
翻译于 2017-02-28 23:34:24
 

参与本段翻译用户:
jeffy

显示原文内容

【已悬赏】 赏金: 3元

##6 Mistake #5: 没有对数据类型和结构数据保留可跟踪记录

当处理多个数据源的时候,能够确保数据是有效的以及在数据无效时能够及时报错退出,是保证你的分析完整性以及能及时采取正确措施的两个重要前提。在这种情况下,数据完整性比灵活性更重要。

Python并不提供数据类型验证,事实上它是被设计成不做类型验证的。 这导致了错误发生以后很久代码才会报错,或者产生完全不相干的结果。在分析数据的时候,我们可能会碰到两个数据源的数据无法基于一个共有列进行jion操作的错误,其原因可能仅仅是因为在流水线中的某个点,该列数据被隐式地转换为了其他类型(str vs int)。

或者在若干步骤之后需要去访问某个字段时,才会发现这个字段根本不在数据集里,这使得调试变得更加困难,而且需要重新计算好几个步骤。结果就是这类问题在处理数据尤其是大数据的时候,很有可能成为浪费时间的主因。

greenflute
翻译于 2017-04-23 01:42:27
 

参与本段翻译用户:
Laurenwang dmwl_sy admin greenflute

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 7元

Python在设计时,允许子类不用实现父类的【未实现方法】,但依然可以被实例化(当在运行时中访问这些【未实现方法】时又失败报错)。现在首次正式地在这里介绍一下这种甲乙丙模块(除非有人用了)。下面就是说明这种甲乙丙模块工作方式的例子:


from abc import ABCMeta, abstractmethod  # 从abc模块导入ABCMeta, abstractmethod这两个部分

class MyABC:                             # 定义新类 MyABC:
    __metaclass__ = ABCMeta              # 将MyABC的元类定义为ABCMeta (元类: 用来创建类的类)

    @abstractmethod                      # 抽象方法修饰符(子类必须定义,但不一定需要实现)
    def foo():                           # 定义抽象方法foo()
       pass                              # 空语句,不做任何事,仅保证结构完整

bar = MyABC()    # 实例化失败,因为类至少有一个方法是抽象方法

 这种快速客制化概念的失败,可以通过清爽地定义规则来有效减少这种类型追溯问题。

 这个解决方法的本质就是断言编程。流水线中的每个步骤,其前置条件和后缀条件的数据在范围边界内必须被完全校验。这带来的额外效果就是为代码提供了说明文档,比

 简单的几个句子要强得多。Python的地道做法是用修饰符去检查每个做数据转换的函数的输入和输出的属性。下面是个具体实现的例子:

def check_args(*types):                                # 定义修饰符方法:检查函数的参数类型
    def real_decorator(func):
        def wrapper(*args, **kwargs):
            for val, typ in zip(args, types):
                assert isinstance(val, typ), "Value {} is not of expected type {}".format(val, typ)
            return func(*args, **kwargs)                
        return wrapper
    return real_decorator

def do_long_computation(name):                         # 定义个耗时的方法
    """ dummy function """
    time.sleep(10)
    return "FruitMart"

@check_args(str, int, int)                             # 用修饰符来检查下方方法的参数类型
def print_fruit(name, apples, oranges):                # 定义个普通的带参数方法用于测试
    store_name = do_long_computation(name) 
    fruit = apples   oranges
    print "{} who works at {} sold {} pieces of fruit".format(name, store_name, str(fruit))

print_fruit("Sally", 12, 5)
>>>Sally bought 17 pieces of fruit  
print_fruit("Sally", "1", 6)
>>> 修饰符函数中断言报错,因此我们免去了在失败报错前额外等待耗时运算的结束

注意: 这个解决方法中没有使用kwargs(key-value型的变长参数)。你们中某些人可能认为在Python里作类型检查就是个异端 - 没人逼你每个地方每个变量都用。这取决于你判断程序中哪些关键点作类型验证能获取价值(比如,在一个尤其耗资源的运算前面)。

dmwl_sy
翻译于 2017-04-04 22:38:06
 

参与本段翻译用户:
dmwl_sy

显示原文内容

【已悬赏】 赏金: 4元

##7 缺陷 #6: 没数据出处追溯

数据分析工作流里很重要的一点就是追溯哪个结果是由哪个版本的数据和算法来生成的。如果不这样做,想想伴随着一个永远在变化的代码库和输入数据,你最终有了一些结果文件,但是当两周或三个月后需要重建时,你已记不清它们是怎么生成的了,并且也没有了什么好的办法来解决。另外,这也让确定或毫无疑问地证明引起数据质量问题的原因是异常或输入数据缺失而不是分析过程中的错误变得困难。这就是为什么要保留所有输入和输出的全部元数据是很重要的原因。

使用 #5 中描述的luigi框架将为你的任务集和【任务集的输入集】提供一个有向无环图(记录执行的节点和路径)。这为将【追溯所有输入数据和代码】作为生成特定输出的一部分提供了可能,就是使用简单的图形算法(这里用的是一个简单的反向深度优先搜索)。所以,作为对你未来的礼貌,请自行保存这些信息到文本文件里然后交给输出。

当上司两周后回来问你:“嘿,卡罗莱,10佳产品_20141112.csv看起来不靠谱,你确定我们11月12号一个蓝色饰品都没卖出去吗?” 这时你可以看看10佳产品_20141112.csv.metadata,知道用到的输入文件是外部直接提供的数据源 销售数据1112.json,里面没提到任何蓝色饰品,因此能确定地推断出你关于10佳的运算不是问题所在。

dmwl_sy
翻译于 2017-04-08 21:06:02
 

参与本段翻译用户:
jeffy dmwl_sy

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 3元

##8 错误 #7:没(回归)测试

在某些方面,测试数据分析流水线比普通的软件测试要复杂,因为数据勘探的性质导致或者不存在100%固定的“正确”答案。尽管如此,输入X输出Y的代码必须做些不情愿的改变或者输出必须按照给定的一些客制化但定义良好的标准用某种方式来改变从而变得更好和可接受。

在一个小数据集上做功能性的单元测试很有用但不够-用足量的真实数据来有规律地测试整个应用,尤其是发生重大变更的时候是确保应用零破坏的唯一方式。当处理大数据时,这意味着测试将跑很长时间,所以我发现比较有效的方法是通过一个持续集成系统自动化这个过程,去运行测试来应对每个拉取的请求。结合 #5,那意味着去运行整个流水线,看看各单元是否依然是按照预期地彼此交互。

dmwl_sy
翻译于 2017-04-08 23:13:46
 

参与本段翻译用户:
Laurenwang dmwl_sy

显示原文内容 | 显示全部版本

【已悬赏】 赏金: 2元

##9 总结

想要很好的使用任何工具就要了解它们的短处(尤其是那些对你的情况影响最多的因素)。这篇文章就是在实际大数据应用中经验的结果,也是对“还有其他更好的方式吗?”不停的追问的结果。

当一个人发现他们在做一件事情时所花的时间与他们的最终目标不成比例时(举例来说,在一知半解时去下载CSV文件或者尝试使用日期时间数据库),这时你应该退后一步,检查你的过程并且看一看是否有另外一种更有效的方式完成这件事。一个人可以找到现有的工具并在它们的基础之上开发来解决他们的问题(就像我曾经在luigi库的基础之上建立一个工具帮助记录数据起源)

所以,祝你好运并享受编程的乐趣!

Laurenwang
翻译于 2017-02-28 11:14:44
 

参与本段翻译用户:
Laurenwang

显示原文内容

GMT+8, 2018-9-25 01:12 , Processed in 0.051852 second(s), 11 queries .