9 數(shù)據(jù)清理第一級--清理表格

9 數(shù)據(jù)清理第一級--清理表格
我們終于到了! 在確保我們擁有必要的技術(shù)技能(本書第一部分)和分析技能(本書第二部分)之后,我們可以開始討論有效的數(shù)據(jù)預處理。我們將通過研究數(shù)據(jù)清洗開始這一旅程。本章將數(shù)據(jù)清洗分為三個層次:第一、第二和第三層次。隨著你在這些層次上的提升,對數(shù)據(jù)清洗概念的學習將變得更深入、更復雜。我們將談論它們是什么,它們有什么不同,以及什么類型的情況需要我們執(zhí)行每個級別的數(shù)據(jù)清理。此外,對于每個級別的數(shù)據(jù)清洗,我們將看到需要不同級別數(shù)據(jù)清洗的數(shù)據(jù)源的例子。
在本章中,我們將重點討論數(shù)據(jù)清理的第一級--清理表格。接下來的兩章也是專門討論數(shù)據(jù)清理的,但是是第二和第三層次的。

1 數(shù)據(jù)清理的層次、工具和目的--第九章、第十章和第十一章的路線圖
在任何數(shù)據(jù)分析項目中,最激動人心的時刻之一是當你有一個數(shù)據(jù)集,你相信它包含了你需要的所有數(shù)據(jù),以有效地滿足項目的目標。這個時刻通常是在以下情況之一出現(xiàn)的。
- 你已經(jīng)完成了為你所想的分析收集數(shù)據(jù)的工作。
- 你已經(jīng)從不同的數(shù)據(jù)源進行了廣泛的數(shù)據(jù)整合。數(shù)據(jù)整合是一個非常重要的技能,我們將在第12章《數(shù)據(jù)融合和數(shù)據(jù)整合》中介紹。
- 數(shù)據(jù)集剛剛與你分享,它包含了你需要的一切。
不管你是如何得到數(shù)據(jù)集的,這都是一個令人興奮的時刻。但要注意的是,更多的時候,在你分析數(shù)據(jù)之前,你還有很多步驟要做。首先,你需要清理數(shù)據(jù)集。
為了學習和執(zhí)行數(shù)據(jù)清洗,我們需要充分了解以下三個方面。
1.?數(shù)據(jù)分析的目的。
我們?yōu)槭裁匆逑磾?shù)據(jù)集?換句話說,一旦數(shù)據(jù)集被清理,我們將如何使用它?
2.數(shù)據(jù)分析的工具。
將使用什么來進行數(shù)據(jù)分析?Python(Matplotlib/sklearn)?Excel?MATLAB?Tableau?
3.數(shù)據(jù)清理的程度。
數(shù)據(jù)集的哪些方面需要清理?我們的清理是在表面層面,即我們只是在清理列的名稱,還是我們的清理更深入,即我們要確保記錄的數(shù)值是正確的?
1.1 數(shù)據(jù)分析的目的
雖然聽起來數(shù)據(jù)清洗可以單獨進行,而不需要我們過多關(guān)注分析的目的,但在本章中,我們將看到,更多時候,情況并非如此。換句話說,在清洗數(shù)據(jù)時,你需要知道你將對數(shù)據(jù)集進行哪些分析。不僅如此,你還需要確切地知道你心目中的分析方法和可能的算法將如何使用和操作數(shù)據(jù)。
到目前為止,在本書中,我們已經(jīng)了解了四個不同的數(shù)據(jù)分析目標:數(shù)據(jù)可視化、預測、分類和聚類。我們了解了這些分析目標以及如何操作數(shù)據(jù)來滿足這些目標。我們需要對這些目標有更深刻的理解和體會,以便它們能夠支持我們的數(shù)據(jù)清理。
通過了解數(shù)據(jù)在我們清理后將如何被使用,我們就能對數(shù)據(jù)的清理方式做出更好的決定。在本章中,我們將看到,我們對分析目標的深入理解將指導我們進行更有效的數(shù)據(jù)清洗。
1.2 數(shù)據(jù)分析的工具
你打算使用的軟件工具對你如何進行數(shù)據(jù)清理也有重要作用。例如,如果您打算使用MATLAB來進行聚類分析,并且您已經(jīng)在Python中完成了數(shù)據(jù)清理,并且擁有Pandas DataFrame格式的完整數(shù)據(jù),您將需要把數(shù)據(jù)轉(zhuǎn)化為MATLAB可以讀取的結(jié)構(gòu)。也許你可以使用.to_csv()將DataFrame保存為.csv文件,并在MATLAB中打開該文件,因為.csv文件幾乎與任何軟件兼容。
1.3 數(shù)據(jù)清理的層次
數(shù)據(jù)清理過程既有高層次的目標,也有許多瑣碎的細節(jié)。不僅如此,從一個項目到另一個項目,需要做的數(shù)據(jù)清理工作可能完全不同。因此,我們不可能就如何進行數(shù)據(jù)清理給出明確的、循序漸進的指示。然而,我們可以粗略地將數(shù)據(jù)清理程序分為三個層次,具體如下。
1. 第一層次:清理表格。
2. 第二級。解除包裝,重組,并重新制定表格。
3. 第三級:評估和糾正數(shù)值。
在本章和接下來的兩章中,我們將了解屬于前述層次之一的各種數(shù)據(jù)清理情況。雖然這些層次在本書中都有專門的章節(jié),但我們將在這里簡單介紹一下它們是什么,以及它們之間有什么不同。
第一級--清理表格
這一級的清理都是關(guān)于表格的外觀。一級清理的數(shù)據(jù)集有三個特點:它是標準的數(shù)據(jù)結(jié)構(gòu),它有可編碼的、直觀的列標題,并且每一行都有一個唯一的標識符。
第二級--重組和重新制定表格
這個級別的清理與你需要你的數(shù)據(jù)集的數(shù)據(jù)結(jié)構(gòu)和格式類型有關(guān),這樣你心中的分析才能完成。大多數(shù)時候,你用于分析的工具決定了數(shù)據(jù)的結(jié)構(gòu)和格式。例如,如果你需要使用plt.boxplot()創(chuàng)建多個箱形圖,你需要為每個箱形圖分開數(shù)據(jù)。例如,請參閱第5章 "數(shù)據(jù)可視化 "中的 "使用箱形圖比較人群 "的例子,在使用函數(shù)繪制多個箱形圖之前,我們對數(shù)據(jù)進行了重組。
第三層次--評估和糾正數(shù)值
這個層次的清理是關(guān)于數(shù)據(jù)集中記錄的數(shù)值的正確性和存在性。在這個層次的清理中,你要確定記錄的數(shù)值是正確的,并以最能支持分析目標的方式呈現(xiàn)。這一層次的數(shù)據(jù)清理是數(shù)據(jù)清理過程中技術(shù)性和理論性最強的部分。我們不僅需要知道我們將要使用的工具需要數(shù)據(jù)是怎樣的,而且我們還需要了解數(shù)據(jù)應該如何被修正、組合或刪除,這是由分析過程的目標所決定的。處理缺失值和處理異常值(outliers)也是這個層次的數(shù)據(jù)清理的主要部分。
到目前為止,我們已經(jīng)看了數(shù)據(jù)清理的三個最重要的維度:數(shù)據(jù)分析的目的,數(shù)據(jù)分析的工具,以及三個數(shù)據(jù)清理級別。
接下來,我們將了解這三個維度--分析目的、分析工具和數(shù)據(jù)清理級別--在有效的數(shù)據(jù)清理方面所發(fā)揮的作用。
1.4 將分析的目的和工具映射到數(shù)據(jù)清理的層面上
下圖顯示了這三個層面的地圖。擁有一個經(jīng)過一級清理的數(shù)據(jù)集是最重要的第一步,花時間確保這個級別的數(shù)據(jù)清理已經(jīng)完成,將使接下來的數(shù)據(jù)清理級別和數(shù)據(jù)分析過程更加容易。雖然我們可以在不知道我們對數(shù)據(jù)集的分析方法的情況下進行I級數(shù)據(jù)清理,但在不知道軟件工具或你打算采用的分析方法的情況下進行任何II級或III級數(shù)據(jù)清理是不明智的。
下圖顯示,二級數(shù)據(jù)清理需要在你了解工具和分析目標的情況下進行,而三級數(shù)據(jù)清理需要在你了解數(shù)據(jù)分析目標后執(zhí)行。

在本章的其余部分,我們將通過提供往往經(jīng)常出現(xiàn)的數(shù)據(jù)清洗實例,更詳細地介紹數(shù)據(jù)清洗級別I。在接下來的幾章中,我們將對數(shù)據(jù)清洗級別II和III做同樣的事情。
2 數(shù)據(jù)清理第一級--清理表格
數(shù)據(jù)清理第一級的數(shù)據(jù)預處理步驟最不深入。大多數(shù)時候,你可以不在第一級進行數(shù)據(jù)清洗。然而,如果有一個經(jīng)過第一級清洗的數(shù)據(jù)集,將是非常有意義的,因為這將使其余的數(shù)據(jù)清洗過程和數(shù)據(jù)分析變得更加容易。
當數(shù)據(jù)集具有以下特征時,我們將考慮I級數(shù)據(jù)集的清潔。
- 它的數(shù)據(jù)結(jié)構(gòu)是標準的和首選的。
- 它有可編碼的、直觀的列標題。
- 每一行都有一個唯一的標識符。
下面的三個例子至少有一個特征,或者是前面的特征的組合,以方便學習。
2.1 例子1--不理智的數(shù)據(jù)收集
時不時地,你可能會遇到沒有以最佳方式收集和記錄的數(shù)據(jù)來源。這些情況發(fā)生在數(shù)據(jù)收集過程是由不具備適當?shù)臄?shù)據(jù)庫管理技能的人或團體完成的。不管為什么會出現(xiàn)這種情況,你獲得的數(shù)據(jù)源需要進行大量的預處理,才能放到一個標準的數(shù)據(jù)結(jié)構(gòu)中。
例如,想象一下,你受雇于一個選舉活動,利用數(shù)據(jù)的力量來幫助推動選舉。奧米德在你之前剛被雇用,他對選舉的政治方面了解很多,但對數(shù)據(jù)和數(shù)據(jù)分析了解不多。你被指派加入奧米德,幫助處理他所負責的工作。在你們的第一次會議上,你意識到任務是分析美國第45任總統(tǒng)唐納德-特朗普的演說。為了讓你了解情況,他微笑著告訴你,他已經(jīng)完成了數(shù)據(jù)收集過程,現(xiàn)在需要做的就是分析;他向你展示了他電腦上的一個文件夾,其中包含唐納德-特朗普在2019年和2020年的每一次演講的文本文件(.txt)。下面的截圖顯示了奧米德電腦上的這個文件夾。

在查看了這個文件夾后,你立刻意識到在考慮任何分析之前,必須進行數(shù)據(jù)預處理。為了與Omid建立良好的工作關(guān)系,你沒有直接告訴他需要做一個巨大的數(shù)據(jù)預處理任務;相反,你評論了他的數(shù)據(jù)收集中很好的方面,可以作為數(shù)據(jù)預處理的基石。你提到,這些文件的命名遵循一個可預測的順序,這很好。這個順序是:城市名稱在前,接著是作為三個字母的月份名稱,然后是作為一個或兩個數(shù)字的日子,最后是作為四個數(shù)字的年份。
由于你精通Pandas DataFrame,你建議將數(shù)據(jù)處理成DataFrame,Omid急于學習,接受了建議。
你可以執(zhí)行以下步驟將數(shù)據(jù)處理成一個DataFrame。
1. 首先,我們需要訪問文件名,這樣我們就可以用它們來打開和讀取每個文件。注意:我們可以自己輸入文件名,因為只有35個文件。然而,我們必須使用編程來做這件事,因為我們正在努力學習可擴展的技能;想象一下,我們有一百萬個文件,而不是35個。下面的代碼顯示了如何使用os模塊中的listdir()函數(shù)可以非常容易地為我們做到這一點。
from os import listdir
FileNames = listdir('/Users/peanut123/Desktop/Hands-On-Data-Preprocessing-in-Python-main/Chapter09/Speeches')
FileNames

2. 接下來,我們需要為我們的數(shù)據(jù)創(chuàng)建一個占位符。在這一步,我們需要想象一下在這個數(shù)據(jù)清理過程完成后,我們的數(shù)據(jù)集會是什么樣子。我們希望有一個DataFrame,包含每個文件的名稱和內(nèi)容。下面的代碼使用pandas模塊來創(chuàng)建這個占位符。
import pandas as pd
speech_df = pd.DataFrame(index=range(len(FileNames)),columns=['File Name','The Content'])
speech_df.head()

3. 最后,我們需要打開每個文件,將其內(nèi)容插入我們在上一步創(chuàng)建的speech_df中。下面的代碼循環(huán)瀏覽FineNames的元素。由于每個元素都是其中一個文件的名稱,可以用來打開和讀取文件,我們可以在這里使用open()和.readlines()函數(shù)。
dir_url = '/Users/peanut123/Desktop/Hands-On-Data-Preprocessing-in-Python-main/Chapter09/Speeches/'
for _,f_name in enumerate(FileNames):
??? f = open(dir_url+f_name,'r',encoding='utf-8')
??? f_content = f.readlines()
??? f.close()
??? speech_df.at[_,'File Name'] = f_name
??? speech_df.at[_,'The Content'] = f_content
??? break
speech_df.head()

完成這三個步驟后,運行Print(speech_df),并在繼續(xù)前進前研究它。在這里,你可以看到speech_df具有第一級清理過的數(shù)據(jù)的三個特征中的兩個。該數(shù)據(jù)集具有第一個特征,因為它現(xiàn)在是一個標準的數(shù)據(jù)結(jié)構(gòu),這也是你的首選。
數(shù)據(jù)集在被處理成speech_df后,也有第三個特征,因為每一行都有一個唯一的索引。你可以運行 speech_df.index 來調(diào)查這一點。
你可能會驚喜地發(fā)現(xiàn),我們并沒有做任何事情來獲得這個清洗特征。這是由Pandas自動為我們完成的。
然而,關(guān)于第二個特征,我們本可以做得更好。文件名和內(nèi)容列的名稱足夠直觀,但它們并不像它們可以被編碼的那樣。我們可以使用df['ColumnName']方法訪問它們,但不能使用df.ColumnName,如圖所示。
1. 首先,運行 speech_df['File Name']和 speech_df['The Content'];你會看到,用這種方法可以很容易地訪問每一列。
2. 第二,運行 speech_df.File Name 和 speech_df.The Content;你會得到錯誤。為什么?為了喚起你的記憶,請回到第一章,回顧NumPy和Pandas的核心模塊,找到DataFrame訪問列的部分,并研究圖1.16所示的錯誤。這里的錯誤原因非常相似。
所以,在使用Pandas DataFrame時,為了使列的標題可以編排,我們只需要遵循一些準則,如下所示。
- 盡量縮短列的標題,不要讓它們變得不直觀。例如,"The Content "可以簡單地稱為 "Content"。
- 避免在列的名稱中使用空格和可能的編程運算符,如-、+、=、%和&。如果你必須有一個以上的詞作為列的名稱,要么使用駝峰命名(FileName),要么使用下劃線(File_Name)。
你可能已經(jīng)注意到,在倒數(shù)第二段代碼中,我可以使用更多可編碼的列標題;我可以使用 columns=['FileName', 'Content'] 而不是 columns=['File Name', 'The Content'] 。你是對的。我應該在那里這樣做的;我這樣做只是為了事后能夠提出這個觀點。所以,在繼續(xù)前行之前,先去改進代碼。另外,你也可以用下面的代碼將列名改為可編碼的版本。
speech_df.columns = ['FileName','Content']
speech_df.head()

現(xiàn)在我們已經(jīng)完成了這個例子,讓我們回顧一下這個例子中的數(shù)據(jù)源需要的一級數(shù)據(jù)清洗的特征。這個數(shù)據(jù)源需要I級數(shù)據(jù)清洗的所有三個特征來進行改進。我們必須明確地采取行動,確保數(shù)據(jù)是標準的數(shù)據(jù)結(jié)構(gòu),同時具有直觀的、可編碼的列名。另外,我們使用的工具,Pandas,自動給每一行一個唯一的標識符。
2.2 實例2--重新索引(多級索引)
在這個例子中,我們想對TempData.csv進行一級數(shù)據(jù)清理。下面的截圖顯示了如何使用Pandas將數(shù)據(jù)讀入一個DataFrame。
air_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter09/TempData.csv')
air_df.head()

我們對數(shù)據(jù)集的第一次評估顯示,數(shù)據(jù)是在一個標準的數(shù)據(jù)結(jié)構(gòu)中,列的標題是直觀的和可編碼的,而且每一行都有一個唯一的標識符。然而,仔細觀察后,Pandas分配的默認索引是唯一的,但對識別行沒有幫助。年、月、日和時間列作為行的索引會更好。所以,在這個例子中,我們想用不止一列來重新索引DataFrame。我們將使用Pandas的特殊能力,即多級索引。我們在Pandas的多級索引部分第一章 "回顧NumPy和Pandas的核心模塊 "中介紹了這一點。
這可以通過使用Pandas DataFrame的.set_index()函數(shù)來輕松實現(xiàn)。
然而,在這之前,我們先把年份這一欄刪除,因為它的值只有2016。為了檢查這一點,運行air_df.Year.unique()。在下面一行代碼中,為了不丟失說明這個數(shù)據(jù)集是2016年的信息,我們將把DataFrame的名稱改為air2016_df。
air2016_df = air_df.drop(columns=['Year'])
air2016_df.head()

現(xiàn)在,不必要的列已經(jīng)被刪除,我們可以使用.set_index()函數(shù)來重新索引DataFrame。
air2016_df.set_index(['Month','Day','Time'],inplace=True)
air2016_df.head()
如果你在運行前面的代碼后打印air2016_df,你會得到具有多級索引的DataFrame,如下面的截圖所示。

我們在這里的成就是,不僅每一行都有一個唯一的索引,而且索引可以用來有意義地識別每一行。例如,你可以運行air2016_df.loc[2,24,'00:30:00']來獲得2月24日午夜后30分鐘的溫度值。
在這個例子中,我們專注于第一級數(shù)據(jù)清理的第三個特征:每一行都有一個唯一的標識符。在下面的例子中,我們將重點關(guān)注第二個特征:有一個可編碼的、直觀的列名。
2.3 例3--直觀但長的列標題
在這個例子中,我們將使用OSMI 2019年技術(shù)中的心理健康調(diào)查。
csv從https://osmihelp.org/research。下面的截圖顯示了將數(shù)據(jù)集讀入 response_df 的代碼,然后使用 .head() 函數(shù)顯示數(shù)據(jù)的第一行。
response_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter09/OSMI%20Mental%20Health%20in%20Tech%20Survey%202019.csv')
response_df.head()

從編程和可視化的角度來看,處理一個列標題很長的數(shù)據(jù)集會很困難。例如,如果你想訪問數(shù)據(jù)集的第六列,你將不得不輸入以下一行代碼。
response_df['Do you know the options for mental health care available under your employer-provided health coverage?']
對于我們不能為列設(shè)置簡短而直觀的標題的情況,我們需要使用一個列字典。我們的想法是用一個鍵來代替每一個列的完整標題,這在一定程度上是直觀的,但明顯更短。如果需要的話,這個字典也會通過相關(guān)的鍵提供對全標題的訪問。
下面的代碼使用Pandas系列創(chuàng)建了一個列字典。
keys = ['Q{}'.format(i) for i in range(1,83)]
columns_dic = pd.Series(response_df.columns,index=keys)
columns_dic.head()

前面的代碼將創(chuàng)建字典欄的過程分成兩步。
1. 首先,代碼創(chuàng)建了keys變量,它是列標題的簡短替代物的列表。這是用一個列表理解技術(shù)完成的。
2. 其次,代碼創(chuàng)建了一個名為columns_dic的Pandas系列,其索引是key,其值是response_df.columns。
一旦前面的代碼運行成功,columns_dic熊貓系列就可以作為一個字典來使用。例如,如果你運行columns_dic['Q4'],它將給你第四列(第四個問題)的完整標題。
接下來,我們需要更新response_df的列,這可以用一行簡單的代碼完成:response_df.columns = keys。一旦你完成了這項工作,response_df就會有簡短而有點直觀的列標題,其完整的描述可以很容易地訪問。下面的截圖顯示了在執(zhí)行了前面的步驟后,response_df的轉(zhuǎn)換版本。
response_df.columns = keys
response_df.head()

在這個例子中,由于數(shù)據(jù)集在第一和第三特征方面處于良好狀態(tài),所以我們采取了措施來確保第一級數(shù)據(jù)清理的第二個特征已經(jīng)得到滿足。
到目前為止,你已經(jīng)通過實例學習并實踐了一些數(shù)據(jù)清洗的例子。在下一章,我們將學習并看到二級數(shù)據(jù)清洗的例子。