《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.3 数据清洗和变量格式化...
本節書摘來自華章計算機《數據科學R語言實踐:面向計算推理與問題求解的案例研究法》一書中的第2章,第2.3節,作者:[美] 德博拉·諾蘭(Deborah Nolan) 鄧肯·坦普·朗(Duncan Temple Lang) 更多章節內容可以訪問云棲社區“華章計算機”公眾號查看。
2.3 數據清洗和變量格式化
本節我們考慮如何將特征矩陣列表menResMat轉換為合適的格式以便于數據分析。目前,這些數據值都是字符型,這對于諸如找到參賽者年齡的中位數這樣的數據分析是無益的。但是,我們可以利用as.numeric()函數很容易地將年齡轉換為數值型。我們需要將整個矩陣都轉換為數值型矩陣嗎?事實并非如此,比如將參賽者的名字轉換為數值型就毫無意義。為此,我們需要創建一個可以允許擁有不同類型變量的數據框。現在我們有6個變量:參賽者姓名、居住地、年齡以及3種類型的時間。正如剛才所說,我們將年齡轉換為數值型,而名字依然保留為字符型。那么其他的變量呢?我們或許也要將居住地保留為字符型。
時間被存儲為一個字符串,其格式為hh:mm:ss。為了更容易地生成概要和進行建模,我們需要將時間轉換為數值型。一種可行的方式是將時間轉換為分鐘數,即hh*60+mm+ ss/60。要執行這樣的計算,我們必須將時間字段分割為成組片段,然后把每組片段轉換為數值型。strsplit()函數能夠幫助我們在諸如冒號的地方對字符串進行分割,另外,我們需要讓3種不同類型的記錄時間(比賽時間、凈時間和平常的終場時間)保持一致。凈時間被認為要比比賽時間更加準確,所以當可以獲得凈時間時,我們就使用凈時間,否則,使用比賽時間或終場時間中被記錄的任何一種。當然我們也可以保存全部3種不同類型的時間,由分析師來判斷它們之間的關系并決定使用哪種時間,但是現在為了處理簡便,我們僅考慮為每個參賽者記錄一種時間。
在將字符串轉換為數值型值之前,我們還要考慮是否應該創建一些新的變量。如果要將所有14年的記錄數據整合到一個數據框中,那么我們就應該記錄這些數據的年份。同樣,如果我們將男女參賽選手的數據整合到一起,那么就需要一個變量來表示選手的性別。使用rep()函數可以很簡單地實現這些操作。
我們首先使用as.numeric()函數來創建數值型變量—年齡,以2012年的男選手數據為例:
我們收到了一條警告信息,提示在將年齡從字符型轉化到數值型的過程中出現了NA值,這意味著一些值并不對應相應的數字。要想進一步探究出現這些信息的具體原因,首先我們要檢查age。
我們根據參賽選手每年的年齡分布,創建并排式箱線圖以快速地檢查年齡值的合理性。
圖2-4揭示了其中2個年份數據中的問題。2003年中所有參賽選手的年齡均小于10,而2006年超過1/4的參賽選手的年齡小于10,顯然這是有問題的。
圖2-4 歷年年齡數據的箱線圖。這些并排的年齡箱線圖表明2003年和2006年的數據存在一些問題。這幾年的參賽選手異乎尋常地年輕
下面讓我們來檢查2003年和2006年的原始文本。
我們注意到在2003年中,年齡值列相較于它對應的‘=’字符向右偏移了一位,這就意味著我們只取了年齡值中十位上的數字。而在2006年,某些行(并不是所有行)的年齡值向外溢出了一個字符。
我們可以簡單地通過將列之間的空白字符包含到列值中來解決這兩個問題,在執行數據抽取時,通過改變每個變量結束的索引就可以實現這個操作。也就是說,修改selectCols()函數中定位每一列結束位置的那行代碼以包含空白字符,即
當我們在selectCols()函數中使用這個修改計算后,每個字段后的空白字符將會被包含進來。而這并不影響我們將文本數據轉換為數值型,而且如果不想讓字符型變量以空白符結尾,我們也可以通過正則表達式很容易地移除這些空白字符。
在確認年齡從字符型轉換為數值型的過程中,發現了數據抽取中存在的問題。我們需要修改2.2節中的輔助函數selectCols()來處理這個問題。由于我們要不斷去檢查得到的數據是否合理,因此該處理是個迭代的過程。當發現無意義的結果時,需要進一步研究它們,這可能使得我們需要折回到前面的步驟以清理臟數據。
在修改了selectCols()函數中的這一行代碼后,我們將更新后的函數版本再次應用于比賽結果表中,使用箱線圖重新對數據進行匯總統計,此時我們會發現存在過多年輕選手的問題已經清除(見圖2-5)。
現在我們轉向上面處理字符型年齡轉換為數值型時顯示的警告消息,有幾條消息為“NA introduced by coercion”。統計每年數據中NA值的個數:
圖2-5 歷年年齡數據的箱線圖。這些并排的年齡箱線圖顯示了一個合理的年齡分布。例如,所有年度年齡范圍的下四分位數都在29到32之間。之前發現的2003年和2006年的數據問題已被解決
2001年中有61個值為NA的年齡,我們需要探討此問題。為了更便于工作,我們使用如下方式,將2001年的年齡數據賦值到名為age2001的向量中:
下面我們來檢查與向量age2001中某個NA值對應的原始文件中的行。回想一下,我們在抽取變量值之前舍棄了原始文件的頭部,因此,為了能夠從原始數據表中讀取到正確的行,我們需要給age2001中NA所在行的位置加上一個偏移量。采用如下方法確定偏移量:
然后我們采用下列方法,找到在原始文件中年齡值為壞數據的行:
除了最后一行外,其余的行全部為空串。最后一行對應的是腳注,它定義了“#”標識符的含義。那么這些空白行在表格中又處于什么位置呢?
可見這些空白行分散在文件中的各個位置。我們可以在數據抽取過程中使用正則表達式檢測這些空白行并把它們移除。
上述表達式將定位所有只包含空格的行。grep的第一個參數采用幾個元字符指定我們將要匹配的字符串模式。其中,“^”錨定字符串的開始位置,“$”錨定字符串的結尾,“[[:blank:]]”指代的是空格符或Tab符的等價類,“”表示空格符可以出現0次或多次。整個表達式“^[[:blank:]]$”表示能夠匹配從開頭到結尾含有任意個空格符的字符串。也就是只含有空格字符的行。
采用一個簡單的表達式可以定位注解行,即以“#”或“*”開始的行。通過修改extractVariables()函數移除那些我們不想要的行,在此我們將該任務留作練習。通過添加上述代碼對數據表進行額外的清洗后,2001年中的61個NA都被消除了,同時其他年份中的很多(但不是所有的)NA也被消除了。
繼續考察圖2-5所暴露的另一個問題:2001、2002、2003年中年齡的最小值都很小,接近于0,這顯然是不可能的。下面讓我們找出這些年齡值小于5的參賽選手,并從原始數據表中找出他們的參賽信息。以2001年數據為例:
顯然有許多參賽選手的年齡輸入為0!鑒于這些都是表中的實際值,我們在后面分析數據時,可以根據需要來決定如何處理這些選手的數據。至此,我們已經成功地創建了年齡變量。然而,由于一個變量基于位置上的錯誤往往會導致在其他變量上產生錯誤,一般我們需要對所有變量同時進行清洗。這樣,在清洗其他變量時,我們可能也需要再次檢查年齡數據,以確保年齡值仍然有效。
接下來我們進行時間變量的創建。如本節開頭所述,時間格式顯示為hh:mm:ss,我們希望將它轉換成分鐘數。然而,為了執行轉換,我們必須將時間字段分割為分組片段的形式。此外,一些參賽選手的比賽用時不到一個小時,這樣他們的時間顯示稍有不同,即為mm:ss,因此,該過程中我們需要能夠處理上述兩種格式。為了簡便起見,我們同樣從轉換某一年的時間變量開始,如以2012年的數據為例。編寫如下代碼來創建向量:
處理過程中字符串中的“:”將被舍棄,從strsplit()函數返回的是一個字符向量列表。每個輸入的時間字符串對應一個向量,向量中的元素為時間字符串中被每個“:”分開的片段。我們通過檢查第一個和最后一個時間來確認分割是否正確,即
顯然我們的時間轉換是正確的。在前面我們看到2012年最快的參賽選手完成比賽用時為45分15秒,也就是45.25分鐘;最慢的參賽者用時為2小時30分鐘59秒,也就是將近151分鐘。在此我們留一練習:將該轉換過程封裝到名為convertTime()的函數中。
下面我們將這些轉換過程打包到一個函數中,并將此函數應用于menResMat中的字符矩陣,然后返回一個包含所有變量的數據框以用于分析,我們將這一函數命名為createDF()。除了將字符串轉換為數值外,我們還另外創建兩個新的變量,year和sex。為了做到這一點,必須從輸入參量得知我們正在清洗的是哪一年份的數據,以及結果是關于男選手還是女選手的。最后,我們還要以凈時間優先的方式,從3種可用的時間變量中選擇將哪一種時間包含到數據框中。函數定義如下:
將這個新函數createDF(),應用于所有男選手的結果數據,如下:
對收到的警告消息進行檢查:
以上警告中提示的轉換問題很可能來自于將時間由字符串轉換成分鐘數的轉換過程中,因為前面我們已經處理了年齡的轉換問題。下面檢查runTime中出現的NA值的個數:
可以看到在2007年、2009年和2010年出現了大量的NA,并且顯示在2006年數據中所有跑步時間的值都是NA。
下面讓我們先檢查幾個在2007、2009 和2010年中跑步時間為NA值的記錄。我們發現這是由于有的選手只完成了一半的比賽而沒有最后的終場時間,另外就是一些選手的時間后面帶有腳注符號,例如:
我們可以簡單通過修改createDF()函數來消除時間信息中帶有的腳注符(“#”和“*”),并去掉那些沒有完成比賽的選手記錄。函數修正如下:
當我們將修改后的函數應用于menResMat以創建數據框后,除了2006年的數據之外,其余年份中所有在時間上的缺失值都消失了。
對2006年文件的頭部進行仔細觀察,我們便可發現問題所在,但是為了內容簡潔,我們還是將此問題留作練習。
最后,對于作為輸入的數據框列表,用do.call()函數去調用rbind(),把所有年份的比賽結果和男選手的數據整合到一個數據框中。方法如下:
do.call()函數讓我們很方便地將列表中的元素作為單個參量傳入一個函數中。例如,rbind()函數的第一個參量是“…”,即
“…”參量表示允許調用者向此函數傳入任意數量的參量,就rbind()函數而言,這些傳入的參量被合并成為一個對象。我們也可以使用以下方法調用rbind()函數:
但這樣的話就有些繁瑣,并且需要提前知道menDF包含14個數據框。使用do.call()函數,可以將這些輸入作為一個列表提供給rbind()函數作為參量,然后do.call()為我們一起調用rbind()函數。
檢查合并后的數據框維度:
另外,我們對cbMen中變量的概要進行檢查,查看是否還有問題出現,如在將各個數據框強制綁定到一起時可能產生的問題。
在這14年的比賽中,有70 070名男選手完成了櫻花公路賽。另外,也有70 000多名女選手完成了該項賽事。這里我們將對女選手比賽結果的處理留作練習。在下一節,我們將進一步查看比賽結果。
總結
以上是生活随笔為你收集整理的《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.3 数据清洗和变量格式化...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软向丰田授权专利 欲成为车联网技术关键
- 下一篇: ORA-01925:maximum of