敲黑板了啊,答疑時間到。如果你沒有良好的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)
<ipython-input-14-da79615505d3> in <module>()
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)。
如果你對數據科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數據科學?》,里面還有更多的有趣問題及解法。