敲黑板了啊,答疑时间到。如果你没有良好的Python编程基础,在尝试应用数据科学方法时遇到了问题和困难,又不知道该如何有效解决,那么这篇文章就是为你写的。请务必认真阅读哟。
错误
几个月以来,我一直在发布数据科学类的应用案例文章。我的目标是帮助初学者建立信心,激发兴趣。从反馈来看,确实吸引了不少“文科生”来尝试数据科学方法。
这里所谓文科生,就是没有编程经验,却又对数据科学感兴趣,甚至不得不实践数据科学(这种压力往往来自于导师、老板和同侪)的人。
我承认,这么定义“文科生”,有些利用刻板印象简化问题的成分。实际上,现在不少文科专业学生,都是高素质复合型人才,编程玩儿得比IT类专业都要熟练。如果你恰巧就是个复合型人才,觉得这个称呼冒犯了你,请你谅解。但至少你得承认,相当多的文科专业同学,还是对技术不够熟悉,有抵触甚至是恐惧心理的。
我收到了不少读者留言和来信,提出了许多疑问。其中有很大一部分,是在实践编程环节,遇到了错误提示,向我求助。
对于这一类的反馈,我秉持着“能帮就帮”的原则。一两句话能够说明的问题,我就旋即答复;对于那些需要深入调查的问题,我会让读者把Jupyter Notebook或者R notebook文件以及数据发给我。这里真得感谢文学化编程环境的提供者们,给我和读者这样便捷沟通、重现问题的方式。
但是,按照反馈的情况来看,还有不少读者遇到了问题,没有能够解决,就直接放弃了。
我这样确信,是因为前些日子给一年级的硕士研究生布置了同样的练习作业,重现我一系列文章中的结果。他们很快就遭遇到了问题,但是长时间自己瞎折腾,没有跟我及时沟通。直到最近的一次的工作坊,我用了几分钟的时间,消除了一直困扰他们的“疑难杂症”,做出了预期的结果。看他们一个个喜上眉梢的样子,很享受。
我能够理解他们的心情。我们总想留给别人聪明、勤奋和积极主动的印象。轻易提出看似异常简单的“傻问题”,可能会让我们的自我评价受挫,觉得自己没有能力,又被别人看作“懒惰”。所以许多情况下,我们遇到问题,喜欢自己先折腾一番。
动手折腾并不是坏事。以正确的方法尝试解决问题,会帮你积累认知。所谓的“编程经验”,很多就是从各种失败尝试中提炼出来的。但是如果你面对错误,尝试使用的方法低效,甚至根本不得其法,那就得不偿失了。我们时常揶揄的“从入门到放弃”,往往就是这么来的。
本着“授人以鱼不如授人以渔”的原则,我今天跟你谈谈,文科生该如何应对数据科学Python编程中可能出现的问题。
许多程序员和专业人士可能会对这样的主题不以为然,甚至嗤之以鼻——除错(debug)是一门专业的学问,你打算一篇文章讲清楚?吹吧!
诚然,我不可能用一篇文章讲清楚如何编程除错。我只想给文科生一些建议,因为他们的情况比较特殊。
对他们来说,直接列一个清单,说明如何除错是不够满足需求的。咱们得结合具体的场景来谈。
文科生遭遇Python编程问题的场景该如何分类呢?
我根据长期的观察和思考,认为可以分成3类:
- 照葫芦画葫芦;
- 照葫芦画瓢;
- 找葫芦画瓢。
你可能看得不知所云。简单解释一下。
文科生使用Python编程,往往没有程序设计的基础训练。他们不是从基础关键词、语法、数据结构、算法的路径学下来的。他们拿到一个任务,一般都有明确的时限,却没有解法清单,唯一的线索是“这个问题可以用Python (或者R)来解决”。
有人说,这就像是某人被塞了一把伞,然后推到台风中心。我觉得挺形象的。
所以,他们首先寻找的,不会是Python(或者R)的基础教科书,而是样例。
如果恰好有个样例,讲如何绘制词云,如何做中文情感分析,如何用决策树分类,如何抽取海量文本的主题……恰好跟他们的任务一致,那他们自然如释重负。
于是他们就开始了第一步,照葫芦画葫芦,先把样例中的代码重复实践一遍,确定本地可以运行。
做好了第一步,出了正确的结果,他们也就来了信心。下面需要做的,是把自己的数据扔进去,看能否出预期的结果,这一部分,就算作“照葫芦画瓢”。
许多人只需要前两步,就能完成任务,高高兴兴收工了。但是如果很不幸,你的任务和样例有一些区别,那你就得在样例基础上,添加新的代码,调用新的软件包来尝试完成任务。你无法自己从头造瓢出来,这一部分就得“自己找葫芦画瓢”了。
对大部分“文科生”来说,场景就是这三类了。出离这样的要求,要么外包,要么自己从头学编程。等他扎扎实实学会了,也就不算文科生了。
下面咱们分别看看,在这三种不同的情境下,文科生遇到Python编程中的问题,该如何有效尝试解决。
说明一下,虽然本文以Python为例讲解方法,但是其中的原理同样使用于大部分数据科学类编程语言和工具,例如R等。学习时请举一反三。
画葫芦
我们先看第一种场景,也就是“照葫芦画葫芦”。
例如说,你打算用决策树做分类,于是找到了我这篇《贷还是不贷:如何用Python和机器学习帮你决策?》,开始实践,重现结果。
前面还好,一直很顺利。你的信心在逐渐积累。听说下面这段代码可以帮你绘制出决策树的图形,你异常欣喜,期待的心情,就如同小时候等着父母出差回家给你带来玩具一样。
with open("safe-loans.dot", 'w') as f:
f = tree.export_graphviz(clf,
out_file=f,
max_depth = 3,
impurity = True,
feature_names = list(X_train),
class_names = ['not safe', 'safe'],
rounded = True,
filled= True )
from subprocess import check_call
check_call(['dot','-Tpng','safe-loans.dot','-o','safe-loans.png'])
from IPython.display import Image as PImage
from PIL import Image, ImageDraw, ImageFont
img = Image.open("safe-loans.png")
draw = ImageDraw.Draw(img)
img.save('output.png')
PImage("output.png")
可是事与愿违,运行后图形没有出来,却见到了一大堆错误信息。
看到错误信息,你已经很紧张了。更要命的是,它们还是英文的。
于是,你一下子茫然无措了。
喝了一杯水,缓了口气,你往后翻文章,到了讨论区。发现其他人也遇到了同样的问题,你眼前一亮。
赶紧往后翻,看看有没有解决办法,你看到了作者的答复:
好像装了Graphviz,问题就可以解决。你赶紧搜索这个软件,并且下载安装了。
安装结束后,你的开始菜单里面,可以看到Graphviz目录。证明你安装成功。
你回到Jupyter Notebook下面,重新执行到这一步。按下“Shift+Enter”按键之前,你又激动不已了。
然而,你看到的执行结果,竟然还是这样子的:
你后悔自己肯定遗漏了讨论区里面的一些重要信息,赶紧返回寻找,你看到了这样的对话:
看到别人安装了Graphviz后,问题依然没有解决。你于是决定放弃了。而且可能还会怀疑,那个叫做大卫的家伙,应该是作者的托儿吧。
其实你冤枉大卫了。安装了Graphviz以后,他确实成功做出了结果。问题到底处在哪儿?请你往下看。
我先说说面对程序给你的这一大堆报错,你该怎么办?
首先你要看看,错误出现在哪里。
WindowsError Traceback (most recent call last)
in ()
10
11 from subprocess import check_call
---> 12 check_call(['dot','-Tpng','safe-loans.dot','-o','safe-loans.png'])
13
14 from IPython.display import Image as PImage
错误提示的第一段已经告诉你了,问题发生在check_call
这一行,行号为12。
这就意味着,前面11行,其实都没有问题。这一大段代码用空行分割,一共是3个部分。前面10行是第一部分。中间2行第二部分,后面是第三部分。我们把它拆分成3个Jupyter中的代码段落,单独执行。
上面的运行结果,证明我们的猜测是对的。第一段运行起来没问题,第二段只有两句,第一句不报错,只有check_call
这一行报错。这样问题就聚焦了。
这种拆分复杂问题到简单部分,然后各个击破的方法,可以追溯到笛卡尔。他老人家曾经说过:
Divide each difficulty into as many parts as is feasible and necessary to resolve it.
注意,这种方法适合于我们此处展示的线性环节。所谓线性,就是顺序执行的若干步骤。前面的改动会对后面有影响,但是后面的改动对前面没有影响。
如果你遭遇的是个循环问题,那就要小心了。这种解决方法可能会失效。
check_call
这一行到底遇到了什么问题呢?我们还是要回到报错信息里,寻找线索。
这么长的报错信息,该看哪里呢?我的经验是,问题发生位置要看开头(我们刚才已经做完了),问题症结十有八九要看末尾。
我们看看报错信息的末尾是什么:
C:\Users\user\Anaconda2\lib\subprocess.pyc in _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, to_close, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite)
638 env,
639 cwd,
--> 640 startupinfo)
641 except pywintypes.error, e:
642 # Translate pywintypes.error to WindowsError, which is
WindowsError: [Error 2]
你可能又晕了,这一大堆的术语,我如何懂得?
你不需要懂那些东西,看最后的报错信息,叫做“WindowsError: [Error 2]
”。
这是一个错误代码,但是包含信息不够。我们需要查询一下,2号Windows错误代码,究竟是什么意思。
这时候,就该搜索引擎出场了。我们搜索“WindowsError: [Error 2]
”,结果如下:
我知道,你的吸引力立刻就被图中的中文文字抓住了。但是我告诉你,更应该看的,不是语言种类,而是信息来源。你会注意到,其中一些搜索结果,来自于“stackoverflow.com”这个网站。
这个网站,汇集了全世界程序设计中遇到的各种问题和可能的解决办法。你可以把它想想成全球程序员的“知乎”。
打开其中的第二条搜索结果。
标题信息就已经非常清楚地告诉了你,所谓的“WindowsError: [Error 2]
”,是指“系统找不到你指定的文件”。
这样我们再次回头审视出问题的代码句:
check_call(['dot','-Tpng','safe-loans.dot','-o','safe-loans.png'])
其实,我们是让Python调用一个Graphviz的命令,叫做dot
,用它来把我们前面生成的 safe-loans.dot文件,转换成png格式的图片。
系统找不到什么文件呢?我们打开当前的demo目录,你会看到 safe-loans.dot文件赫然在目。而png文件此时还没有生成。因此,我们锁定了问题,系统找不到的,是dot这个命令。
这就是为什么你必须要先安装Graphviz包。你不安装的话,Python当然找不到dot文件。
但是为什么你明明装了Graphviz包,还会遭遇报错呢?
你确定这时候Python可以找到dot包了吗?
我们尝试一下。到命令提示符下面,执行dot
试试看。
真相大白了。你在命令提示符下,自己都找不到dot命令,你能指望Python有多智能呢?
怎么办?方法其实并不难,只需要加上必要的路径,让电脑知道dot
这个命令在哪里,就可以了。
我们到C盘的Program Files,或者Program Files(x86)目录下,去找Graphviz安装目录。
你会发现,路径为:
C:\Program Files(x86)\Graphviz2.38\bin\
于是这次你执行:
你会看到,没有报错信息了。
再接再厉,这次你把完整的命令输入:
这次不但没有报错,而且你想要的png文件已经生成了。
不过,这些都是你手动完成的,咱们还是需要用程序来完成,不是吗?
于是我们回到Jupyter Notebook里,尝试给dot命令一样加上路径。
执行!
报错信息又来了!而且一模一样啊!
注意我们做了改动,但是改动并没有成功。我们就得想想原因是什么了。
这次我们搜索执行的Python命令( check_call ),以及输入路径中的特征部分(Program Files)。把这几个关键词放到搜索引擎里,结果如下:
注意第一条结果是帮助文档,我们把目光聚焦在第二条搜索结果上。因为它依然来自stackoverflow.com。
我们点击打开这个链接。
提问者问,为什么使用(跟我们类似的)完全路径,Python依然找不到命令。
被赞同的答题者回答:你应该用斜杠(/),而不是反斜杠(\)。
看了这个答案,你可能觉得恍然大悟了吧。于是回到Jupyter Notebook里面,把C:\Program Files(x86)\Graphviz2.38\bin\dot
改成了C:/Program Files(x86)/Graphviz2.38/bin/dot
。
再次运行,这部分不再报错了:
你战战兢兢,尝试一直就没能正确运行到的最后一段代码:
是不是有一种想要仰天大笑的感觉?
现在我们来回答一下,为什么评论里大卫的问题获得了解决,而其他读者似乎没能解决问题呢?
罪魁祸首在于操作系统环境差异。大卫用的是macOS。安装Graphviz之后,mac操作系统记录下来了Graphviz的各项可执行命令。Python因此也知道了dot这个命令在哪里。所以调用起来没有任何问题。
我写作该文的时候,操作系统也是macOS,所以并没有意识到Windows上运行环境的这种差异。甚至因为2年多以前我就安装了Graphviz,所以在初稿写作的时候甚至都没有把Graphviz作为环境准备的必要组成部分。
顺便说一句,根据部分读者反映,他们在Windows上安装了Graphviz后,只需要重启一遍,系统就会自动识别dot命令的完全路径,所以根本就不必修改代码内容。但是其他读者反映这样做了无效。你看,同样是Windows,环境差异都是如此之大的。
操作系统、Python软件版本、调用的相关软件包版本……这些环境差异可能直接导致你“照葫芦画葫芦”时候出现严重问题,也是最容易踩到的坑。不过通过咱们前面的叙述,相信你已经找到了从坑里爬起来,甚至是避开坑的方法了。
画瓢
当你完整重现样例或教程中的运行结果后,就该开始照着葫芦画瓢了。毕竟你需要分析的,是自己独特的数据。
但是一定要注意,务必在“画葫芦”完成后,开始画瓢。来信和留言中的许多问题,都是读者在没有完整重现教程结果时,就开始了改动,把不同层次的问题混杂在了一起,就很难发现和解决了。
这里咱们举的例子,是这位读者的来信。
他看了我那篇《如何用Python做舆情时间序列可视化?》之后,完全重现了结果。然后灌入了自己的数据。我展示的样例用的是饭馆点评信息,他用的是外卖评论信息。
这是我原文中读入后数据的样例:
这是他的数据:
看起来很相似,不是吗?可是前面情感分析等环节都没有问题。到了最后的绘制图形,又是一堆报错信息。
复习一下,我们说过报错信息的开头和结尾最为重要。开头是确定位置。因为这里本来就只有一行语句,所以可以忽略。那我们看看结尾吧。
注意,这里提示的是取值错误(ValueError),并且标出了问题,就是评论时间,例如“2017-02-27”。
回顾一下,在原文中,评论时间的格式为Python可以识别的时间单位,这样最后绘出的图形才是这样的:
而这里,时间显示为“2017-02-27”,应该没错啊。
数据框中的时间是从新到旧排列的。我们显示最后一条数据,就是“2017-02-27”。
这里我们就需要记住一条非常重要的命令:
type()
它可以帮助我们搞清楚取值的类型。
这一下子,原形毕露了。数据框里面的每一个时间条目,存储的格式都不是Python日期,而是简单的字符串!难怪当我们需要绘制时间序列图形的时候,会报错。
明白了问题,方法也就容易找到了。我们再次用搜索引擎,查找Pandas里,把字符串转换为日期的方法。
其实Google已经非常聪明地把最相关的结果摆在前面了。但是我们依然可以用老方法,找stackoverflow链接,并且点击进入。
选定答案里面清晰明白地告诉我们——使用Pandas数据框的to_datetime
函数,并且给出了详细的样例。
好的,我们试试看。
df.time = pd.to_datetime(df.time)
然后我们重新执行刚才的两条语句:
看,这次Python正确识别出日期格式。
然后我们再绘图:
虽然由于数据量过大,后半部分看不大清楚。不过结果已经初步显现了。下面就是分段截取数据,细致地进行可视化的工作了。
问题出在哪里呢?对比一下原文使用的excel数据文件,和读者来信里面附带的数据文件,你就能看出端倪了。
这是原文使用的餐馆评论原始数据:
这是读者使用的外卖评论原始数据:
你会看到,原先数据里面不仅有日期,还有时间。虽然时间不过都是些“00:00:00”,但Pandas在读入的时候,会将其自动转化为日期时间格式。
然而读者数据里只有日期,没有具体的时间。Pandas读入数据时,不确定要不要做转化,默认就当成字符串来处理了。
所以你看,如果你需要“照葫芦画瓢”,一定要仔细对比数据格式,即便是这样微小的差异,都会造成后续运行结果的区别,乃至报错。
找葫芦
如果样例里面没有提供某个功能,但是你确实需要用到它,怎么办?
这个时候似乎手头没有葫芦可以照着画,你得自己找葫芦。
例如读完了我那篇《如何用Python做词云?》后,有读者在微信公众号后台留言,询问我如何在绘制词云的时候,把词云变成需要的形状。
读者想要的,其实是这样的效果:
但是我那篇文章里,并没有提供这样的样例,只能做出下面这种四四方方的词云图。
如果你也遇到了类似的问题,我的建议是,按图索骥查询原始文档。
你先看看文中,我们究竟用了哪个词云绘制工具包。
from wordcloud import WordCloud
wordcloud = WordCloud().generate(mytext)
这里,我们注意wordcloud这个关键词,然后结合python,到搜索引擎里面查找。
这回我们不再专门去找stackoverflow网站链接了。因为我们遇到的,不是报错信息,而是一项暂时还不懂得如何使用的功能。
搜索结果里的第一项,就是wordcloud词云包的官方github站点。我们点开看看。
Github是目前全球最主要的代码托管与分享站点。我也曾经把思维导图秒变成幻灯的代码发布在github上面。右上方几个统计数字很重要,尤其是Star,说明了该项目受欢迎程度。wordcloud软件包的受欢迎数字超过3000,可以说是非常棒的。相较之下,我的代码Star数量只有20,相形见绌啊。
我们把网页往下翻,可以找到Example部分。
你是不是眼前一亮啊?对,你需要的绘图结果就在这里,而且人家有专门链接直达使用方式。
点击一下,你就能看到官方的masked词云样例代码了。
浏览代码,你会发现这一段有集中注释:
# read the mask image
# taken from
# http://www.stencilry.org/stencils/movies/alice%20in%20wonderland/255fk.jpg
alice_mask = np.array(Image.open(path.join(d, "alice_mask.png")))
这里告诉你,如果打算把词云绘制成特殊的图形外观,你需要在这里指定一个mask图像文件。样例里面的文件叫做alice_mask.png。
我们来看看,这个文件是什么样子的。因为源代码就在这里,指定的文件也没有加入完全路径,因此它只可能放在样例代码文件的相同目录下。
我们点击页面上方的路径链接,返回到上层目录。
目录里面所有的文件都在这里了。我们找到alice_mask.png,点开看看。
原来你需要提供这样一张黑白图像,词云会显示在其中的黑色区域内。
但是这样的图像需要我自己来绘制吗?
这就考验你看代码的时候是不是细致了。有没有注意到这一句?
# http://www.stencilry.org/stencils/movies/alice%20in%20wonderland/255fk.jpg
这是alice_mask.png图像文件原始地址。我们点击看看。
原来这个网站上的文件直接就可以用来做词云图像设置。我们看到上方的路径按钮,点击上一层,进入“movies”。
居然有这么多的电影图像可以使用啊。
我们再进入到上一层,看看还有哪些其他类别图像可用。
看了之后,是不是有一种“芝麻开门”的感觉?
可惜的是,tv类别下面,并没有原文提到的“Yes, minister”可供选择。我们随便选一张世界地图来试试看。
回到Jupyter Notebook里面,按照我们从原始说明文档里找到的新葫芦,开始画瓢。
如果你做出了下面的结果,那么恭喜你,“找葫芦画瓢”工作圆满完成。
小结
小结一下,对文科生来说,编程中遇到的问题,需要依据不同的场景,分别采取不同的思考清单来尝试有效解决。
对于“照葫芦画葫芦”类场景,方法如下:
- 确认你的运行环境尽量和作者的运行环境一致(安装相同的软件版本)。如果环境不一致(例如操作系统差异),遇到问题的时候要时刻意识到这种差异可能是造成无法重复结果的元凶。
- 把遇到问题的代码拆解开。聚焦到实际产生了问题的代码片段上。这就是所谓的“分而治之”。
- 认真阅读报错信息,里面有非常重要的线索。尤其是开头和结尾部分。
- 善用搜索引擎,输入可以准确定义问题的关键字。
- 明白stackoverflow网站的重要性,其中被支持的答案可能一语道破你百思不得其解的问题。
对于“照葫芦画瓢”类场景,方法如下:
- 确认你已经圆满完成了“照葫芦画葫芦”过程。
- 确保你自己的数据格式与样例中数据格式一致。
- 认真阅读报错信息,从中找到问题的大致方向。
- 利用搜索引擎查找类似问题的已知有效解决方法。尤其要注意stackoverflow网站的相关链接。
对于“找葫芦画瓢”类场景,方法如下:
- 依照类似的功能,按图索骥找到提供相应功能的软件包。
- 阅读其官方说明文档,最好能找到特定功能的样例代码。
- 读代码的时候务必注意注释信息,其中包含了注意事项和重要资源。
- 自己实践的第一步,是用找到的“新葫芦”画出“葫芦”来,然后再尝试“画瓢”。
不论对于哪一类场景,你都要明白遇到的问题可能会成为你未来的财富。但前提是你必须把它们及时记录下来,并且养成定期回顾的好习惯。
如果实在解决不了问题,最后的一招,就是提问了。但是请你在发问之前,确定自己已经通过上述步骤和流程尝试了可能有效的方法。这不仅是《提问的智慧》里面要求的,更是为了你自己能够学有所得。提问的时候,要注意提供你的运行代码(最好是ipynb这样的格式,包含完整的报错信息)、用到的实际数据,以及你的尝试过程等。信息越详细,别人就越有能力帮助你。
向谁提问呢?当然可以问老师、问作者。但是别忘了你一直在使用的stackoverflow网站,本来就是一个让你提问的好地方啊。通过观察别人的问题和答案,你应该不难发现,网站上的高手们,大都非常热心助人。
另外,不要满足于永远当一个“文科生”。如果你打算在数据科学的路径上走得足够远、足够稳,夯实基础就是必要的。毕竟人工智能都要进入中小学课程体系了,不是吗?
如果你打算好好学习Python基础知识,欢迎阅读《如何高效学Python?》一文。
讨论
你用Python或者其他编程语言做过数据科学分析任务吗?中间遇到过问题或障碍吗?你是如何处理的,成功了吗?有什么独特的好经验?欢迎留言,分享你的经验和心得,我们一起交流讨论。
喜欢请点赞。还可以微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。
如果你对数据科学感兴趣,不妨阅读我的系列教程索引贴《如何高效入门数据科学?》,里面还有更多的有趣问题及解法。