數據整理(Tidy Data)—翻譯Hadley Wickham的一篇論文

摘要

nn

清洗數據使其可以被分析,需要花費大量的精力,但是關於儘可能簡單和有效地清洗數據的方法很少被人研究。這篇論文是關於數據清洗中很小而非常重要的一個構成:數據整理。整齊的(經過整理的)數據資料可以方便地被操作、建模和可視化處理,而且擁有特定的結構:每個變數對應一列,每個觀察值對應一行,每一類觀察單元對應一個表格(table)。這個框架讓整理混亂的數據資料變得簡單,因為只需要一小部分工具,就可以處理很大範圍的待整理的數據資料。這個結構還使得開發用於數據分析的整齊工具(輸入和輸出整齊的數據)更加容易。本文通過一個搞定現實世界數據操作瑣事的案例,展示了一致的數據結構和匹配工具的優勢。

nn

關鍵詞:數據清洗,數據整理,關係資料庫,R。

nn

譯者註:本文中提到的「數據清洗」,對應英文原文的data cleaning。然而其他更多的地方也有data cleansing 的說法,個人感覺後者和「數據清洗」的譯法更加對應。譯者是數據分析的初學者,認為在本篇中翻譯成「數據清洗」也是說得通的。

nn

1. 介紹

nn

人們通常認為數據分析中80%的時間用於數據清洗和準備的過程(Dasu and Johnson 2003)。數據準備並不僅僅是第一步,而是當出現新問題或收集新數據時,在分析過程中會被重複很多次。儘管這是個耗時的工作,關於如何清洗數據的研究卻少得驚人。部分挑戰在於數據清洗涉及的面非常寬:從離群值檢驗,到日期的解析,到缺失值的插補。為了給解決問題提供抓手,此論文聚焦於數據清洗中不是很大,但很重要的部分,我稱之為數據整理:將數據結構化以便於分析。

nn

數據整理的原則提供了在一組數據中組織數據值的標準方式。一個通用的標準讓初始的數據清洗更容易,因為你不必從頭開始和白費力氣地做重複工作。數據整理標準的設計是為了初始的數據研究和分析,以及簡化各個一起使用的數據分析工具的開發。當前的工具通常需要轉化。你必須花時間對一個工具的輸出進行再加工,之後才能作為另一個工具的輸入。整齊的數據集和整齊工具可以共同協作,讓數據分析更簡單,讓你能關注有趣領域的問題,而非枯燥的算術運算。

nn

數據整理的原則基於關係資料庫、以及科德的關係代數(Codd 1990)的原則,但是由統計學家熟悉的語言構成。計算機科學家也為數據清洗的研究做出了巨大貢獻。例如,Laksh-manan, Sadri, and Subramanian (1996) 給SQL定義了一個延伸,讓它能操作混亂的數據集,Raman and Hellerstein (2001) 提供了一個清洗數據集的框架, Kandel, Paepcke, Hellerstein, and Heer (2011) 開發了一個擁有友好用戶界面的互動工具,能自動生成清洗數據的代碼。這些工具很有用,但它們是由大多數統計學家都陌生的語言呈現的,它們不能給出關於數據集應如何構建的很多建議,且缺少和數據分析工具的關聯。

nn

整齊工具的開發是由我處理真實世界數據集的經驗所驅動的。這些數據集的組建基本沒有、或很少有任何約束限制,還經常以奇怪的方式構建。我曾花費無數時間掙扎,讓這些數據集的組建方式變得能夠被分析,更不用說簡化分析。我也曾下大力氣把這些技能教給我的學生,讓他們能夠獨立處理真實世界的數據集。在這些努力的過程中,我開發了reshape 和reshape2包(Wickhamn2007)。縱使我能夠憑藉直覺使用工具,並通過案例交給他們,但缺少詳述我的直覺的框架。此論文提供了這個框架,以及一個廣泛的「數據的哲學」:我的plyr (Wickham 2011) 和 ggplot2(Wickham 2009)包都以此為基礎。

nn

此論文的結構是:第2部分以定義整齊數據集的三個特徵為開篇。因大部分真實世界的數據集都不整齊,第3部分描述了讓混亂數據變整齊所需的操作,並通過一系列真實案例詳述了相關技巧。第4部分定義了整齊工具—輸入和輸出整齊數據集的工具,並討論了整齊數據和整齊工具的組合如何讓數據分析變得容易。這些原則在第5部分通過對一個小案例的研究來詳述。第6部分通過討論此框架的欠缺和在哪些方向繼續研究能有所收穫而結尾。

nn

2.整齊數據的定義

nn

幸福的家庭都相似,不幸的家庭各有各的不幸。—列n托爾斯泰。

nn

就像家庭,整齊的數據集都相似,但混亂的數據集各有各的亂法。整齊的數據集通過語義(其含義)提供了一個連接此數據集結構(其物理布局)的標準化方式。在這部分,我將提供一些標準的辭彙來描述一個數據集的結構和語義,然後使用這些定義來對整齊的數據進行定義。

nn

2.1 數據結構

nn

多數統計數據集是矩形的表格,由行和列構成。各列幾乎總帶有標籤,而各行有時也帶有標籤。表1提供了關於一個假想實驗的數據,其格式很常見。表格有兩列三行,行列都有標籤。

nnnn

表1:典型的數據展示.

構建相同的數據可以有很多方式。表2展示的數據和表1相同,但是行列被轉置。數據相同,但是展示的布局不同。我們關於行和列的辭彙不夠豐富,無法描述為何兩個表格展示的數據相同。除了數據的外觀,我們需要一種方式來描述表格所展示數值的語義,或含義。

nnnn

表2:與表1數據相同,但組織形式不同

nnnn

2.2 數據語義

nn

一個數據集是一組「值」的集合,通常不是數字(定量的)就是字元串(定性的)。值通過兩種方式被組織起來。每個值屬於一個變數和一個觀察值。一個變數包含測量各個單元同一內在屬性的所有值(比如高度、溫度、時長)。一個觀察值包含測量同一單元各個屬性的所有值(比如一個人、某一天、或一場比賽)。

nn

表3將表1重新排列,使各個值、變數和觀察值更加清晰。這個數據集包含3個變數、6個觀察值的18個變數。這些變數包括:

nn

1. 人,有3個可能的值(John, Mary, 和 Jane)。

nn

2. 治療方案,有2個可能的值(a 和b)

nn

3. 結果,有5個或6個可能的值,這取決於你如何看待缺失值(-, 16,3, 2, 11, 1)。

nn

這個試驗性的設計告訴我們關於觀察值結構的更多東西。這個試驗里,每個人和治療方案的組合都被測量了,一個完整的交叉設計。這個試驗性的設計還決定了缺失值是否能被安全地捨棄。試驗中,缺失值代表一個本應該存在但不存在的觀察值,所以保留它很重要。結構化的缺失值代表不可能被實施的測量,可以被安全地移除(比如男性懷孕者的統計)。

nnnn

表3:與表1的數據相同,但變數按列排列,觀察按行排列。

nnnn

對於一個給定的數據集,通常可以很容易地判斷什麼是觀察值,什麼是變數。但是總的來說,精確地定義變數和觀察值驚人的困難。比如,如果表1里的2列分別是高度和重量,我們就會稱它們為變數。如果2列分別是高度和寬度,那區分就不是那麼清晰了,因為我們可以認為高度和寬度是某個維度變數的值。如果2列分別是家庭電話和工作電話,我們可以把它們當作兩個變數;但是在一個偵查詐騙的分析里,我們可能想要電話號碼和號碼類別這些變數,因為多人使用同一電話號碼可能暗示著詐騙。一個普遍的首要原則是,在變數之間描述功能性的關係較容易(比如,Z與x和y的組合呈線性關係,密度是重量和體積的比),在各行之間描述則較難;在各組觀察值之間作比較較容易(比如a組的平均值和b組的平均值作比較),在各列之間比較較難。

nn

在某些分析里,可能有不同層次的觀察值。比如在一個新型過敏藥物的試驗中,我們可能有3個觀察的類別:從每個人收集來的人口統計學數據(年齡、性別、人種),從每個人每天收集來的醫療數據(打噴嚏數,是否眼紅),以及從每天收集來的氣象數據(氣溫、花粉濃度)。

nn

2.3整齊的數據

nn

整齊的數據是指數據含義和其結構的標準化的匹配方式。一個數據集是混亂還是整齊,取決於行、列、表格與觀察值、變數和類型如何匹配。在整齊的數據中:

nn

1. 每個變數組成一列。

nn

2. 每個觀察值組成一行。

nn

3. 每個觀察單元的類型組成一個表格。

nn

這是科德的第三範式(Codd 1990),但是包含以統計語言為框架的約束,且聚焦於單一數據集,而不是關係資料庫中常見的很多相互關聯的數據集。任何其他數據的組織方式都是混亂的數據。

nn

表3是表1的整齊版本。每行代表一個觀察,即某一治療方案對某個人的效果,每列是一個變數。

nn

整齊數據讓分析師或計算機更容易地提取所需變數,因為它提供了一個構成數據集的標準方式。對比表3和表1可以看出:在表1里你需要使用不同的方法來提取不同的變數。這會使分析變慢併產生錯誤。如果考慮一下一個變數中的所有值會涉及多少數據分析操作(每一個集合函數(aggregation function)),你就能發現用一個簡單而標準的方式提取這些值有多麼的重要。整齊的數據特別匹配像R這樣的向量化的編程語言,因為這樣的排列保證了屬於同一觀察值的不同變數的值總能很好地匹配。

nn

儘管變數和觀察值的順序不會對分析有所影響,但好的順序讓快速瀏覽原始值更加容易。排列變數的一種方法是根據它們在分析中的角色:值是否是根據數據收集的設計而事先確定,還是在試驗的過程中被測量?事先確定的變數描述了試驗的設計,在開始就是已知的。計算機科學家通常把事先確定的變數稱為維度,(統計學家通常使用在隨機變數加下標的方式表示事先確定的變數)。被測量的變數是我們在研究中真正測量的。事先確定的變數應該在先,然後才是被測量的變數,各個相關的變數相鄰排列。行的排列可以根據第一個變數,排列完第一個變數再排第二個,然後以此類推拍列後面的(事先確定的)變數。這是本文中展示的表格所採用的一貫方式。

nn

3.整理混亂的數據集

nn

真實的數據集將會,而且經常會以各種可能的方式違反整齊數據的3個原則。雖然有時你確實能得到一個可以立即開展分析的數據集,但這其實是個案,而非常態。這部分將介紹混亂數據的5個最常見的問題,已經相關解決方案:

nn

l n列標題是值,而非變數名。

nn

l n多個變數存儲在一列中。

nn

l n變數既在列中存儲,又在行中存儲。

nn

l n多種觀察單元存儲在同一表中。

nn

l n一個觀察單元存儲在多個表中。

nn

讓人吃驚的是,多數混亂的數據集,包括上面沒有明確描述出的混亂方式,都可以通過一小部分工具進行整理:融合、字元串分解、以及重鑄。以下部分通過我所遇到過的一組組真實數據分別闡釋了每個問題,並展示了如何對它們進行整理。完整的數據和用來整理數據的R代碼在網站上可以找到(hadley/tidy-data),也在此論文的在線補充材料裡面。

nn

3.1 列標題是值,而非變數名

nn

一種常見的混亂數據集是為展示而設計的表格數據,變數既構成列又構成行,列標題是值,而非變數名。雖然我把這樣的安排稱作混亂,但在某些情況下這樣可能很有用。它為完全的交叉設計提供了有效的存儲,此外,如果期望的操作可被表達為矩陣操作,可被用於非常高效的計算。這個問題將在第6部分討論。

nn

表4展示了這類典型數據集的一個子集。這個數據集探索的是在美國收入和宗教信仰的關係。數據來源於Pew研究中心所做的一個報告。Pew研究中心是一個收集人們對從宗教信仰到互聯網等主題態度數據的美國智囊團,製作了很多含有這樣格式數據集的報告。

nnnn

表4:PEW論壇中收入與宗教信仰數據的前10行。$75-100k, $100-150k and >150k,這3列已被忽略。

nnnn

這個數據集有3個變數,宗教,收入和頻率。對其進行整理,我們需要把它融合,或堆疊。換句話,我們需要把列轉換為行。儘管這常被描述為將寬的數據集變長或變高,我將避免這些說法,因為他們不夠準確。融合是通過已經是變數的各列的列表(簡稱為colvars)進行參數化。其他的列被轉換為兩個變數:一個被稱作列(column)的新變數,它包含重複的列標題;以及一個被稱作值(value)的新變數,它包含從之前分離的列中提供聯繫的數據值。表5通過一個簡單的數據集進行了說明。融合的結果是一個「熔化」的數據集。

nnnn

表5:融合的簡單例子。(a)含有一個colvar和行,被融合後得到(b)。兩個表格裡面的信息是一樣的,只是通過不同方式存儲。

nnnn

Pew數據集有一個colvar,宗教信仰,對其融合的結果在表6中展示。為了更好地反映它們在這個數據集中的角色,變數的那一列被重命名為income,值的那一列重命名為freq。這個表格是整齊的,因為每一列代表一個變數,每一行代表一個觀察值,在本例中一個人口統計單元對應一個宗教信仰和收入的組合。

nnnn

表6:整理過的Pew關於收入和宗教調查數據的前10行。列被重命名為income, value和freq.

nnnn

這個數據格式的又一個常見用途,是長期記錄定期存儲的觀察值。比如表7中展示的Billboard數據集記錄了一首歌第一次上榜Billboard前100的日期。它包含藝術家,音軌,上榜日期,排名和周等變數。歌曲上榜前100後的各周的排名,在75列中記錄(從wk1到wk75)。如果歌曲上榜前100的時間不到75周,剩餘的各列被缺失值填充。這個形式的存儲不整齊,但是對於數據的錄入很有用。這樣減少了重複,因為換個方式的話,每首歌在每周都需要它自己的一行(被重新記錄一遍),而像標題和藝術家等歌曲的元數據將被重複,此問題將在3.4節裡面詳述。

nnnnn

表7:2000年billboard上榜前8歌曲。Wk4至wk75列略。

nn

這個數據集有year, artist, track, time, 和 date.entered等colvars. 對其融合得到表8. 整理之外我還做了一些清洗工作:通過提取數字,列被轉換為week,date通過date.entered 和week計算得出。

nnnnn

表8:billboard數據集整理後的前15行。Date列在原表格中沒有,但是可以通過date.entered 和week計算得出。

nnnn

3.2多個變數存儲在一列中

nn

融合之後,列變數名常變為多個變數名的組合。這在tuberculosis(TB)數據集(表9展示了式樣)當中進行了說明。此數據集來源於國際衛生組織,按照國家、年和人口統計學分組記錄了確診的肺結核(tuberculosis)病例數。人口統計學分組根據性別(m, f) 、年齡((0-14, 15-25, 25-34, 35-44, 45-54,n55-64, unknown)劃分。

nnn

表9:原始TB數據集。帶有m的列表示男性,帶有f的列表示女性。這並不是為了節省空間的展示。請注意0值和缺失值(-)的混合。這源於數據的收集過程,做出區分對這個數據集很重要。

nnnn

在這樣的格式中,列標題常通過(., -, _, :)等字元分隔。儘管使用這些字元作為分隔工具,字元串可以被拆分開,在另一些案例中,比如這個數據集,需要更加仔細的字元串處理。比如,變數名可以由一個lookup表格匹配,這個表格將單一複合的值轉換成多個組成部分的值。

nn

表10(a)展示了對TB數據集進行融合的結果,表10(b)展示了將單一列分開為兩個真正變數的結果。

nn

表10:整理TB數據集需要先融合,再將column列分割成兩個變數:sex和age。

nnnn

在這個表格里存儲值,解決了原始數據的另一個問題。我們想要比較比率,而非計數。但為了計算比率,我們需要知道人口。在原始格式里,沒有添加人口變數的簡單方法。這個數據一定是存儲在一個單獨的表格中,這樣就讓人口與計數進行正確的匹配很難。在整齊的表格里,添加人口和比率變數很容易,它們只是額外的列而已。

nn

3.3變數既在列中存儲,又在行中存儲。

nn

當變數既在列中存儲,又在行中存儲時,就會出現最複雜形式的混亂數據。表11展示了來源於全球歷史氣象網的墨西哥MX17004氣象站2010年5個月的每日天氣數據。它在各單獨列(id,year,month)有變數,分散於各列(day,d1-d31)和各行(tmin,tmax)(最低和最高氣溫)。天數不足31天的月份,在月末的最後幾天存在結構性缺失值。Element列不是變數,它存儲變數的名稱。

nnnnn

表11:原始天氣數據集。月中的每一天是一列。為節省空間省略了D9到d31列。

nnnn

整理這個數據集我們要先按照id,year,month和含有變數名的element列等colvars將其融合。由此得出表12(a)。為了展示,我們省略了缺失值,使他們更隱蔽而不是更明顯。這不是問題,因為我們知道每個月有多少天,可以很容易地重建明確的缺失值。

nn

這個數據集基本上整齊了,但我們有兩個變數存儲在行里:tmin和tmax,觀察的種類。沒在此例中展示的包括其他氣象學變數prcp(冰雹)和snow(降雪)。解決此問題需要重鑄或拆分操作。這是通過將element變數轉出到列中的逆向的融合操作(表12(b))。這個形式是整齊的。每列里有一個變數,每行代表一天的觀察值。重鑄操作在Wickhamn(2007)中有深入的描述。

nnnn

表12:(a)融合的天氣數據,除了值基本是整齊的。Element列包含了多個變數的名稱。為了節省空間將缺失值忽略。(b)整齊的天氣數據。每一行代表一天的氣象觀測。有兩個觀測的變數最低氣溫(tmin)和最高氣溫(tmax);所有其他變數是固定的。

nnnn

3.4多種觀察單元存儲在同一表中。

nn

數據集經常包含在多個水平收集的,關於不同種類觀察單元的值。在整理的過程中,每一類的觀察單元應被存儲在它自己的表中。這與資料庫的規範化概念緊密相關,每個事實只在一個地方表達。如果缺少此項操作,可能會出現不一致的問題。

nn

表8中的Billboard數據集實際上包含兩種觀察單元的觀察值:歌曲和其每周的排名。這通過關於歌曲事實的重複證明了這一點:每首歌的藝術家和時間在每周不斷被重複。這個數據集需要被拆分為兩個數據集:一個存儲藝術家、歌曲名、時間等歌曲數據集,和一個給出每周排名的排名數據集。表13展示了這樣兩個數據集。你還應該想像一個記錄周背景信息的周數據集,可能是售出歌曲總數,或類似的人口統計學信息。

nnnn

表13:規範化的billboard數據集,分為歌曲數據集(左)和排名數據集(右)。展示了每個數據集的前15行;歌曲數據中的風格和排名數據中的周被省略。

nnnn

規範化對於整理數據和消除不一致很有幫助。然而,很少有直接處理關係數據的數據分析工具,所以分析中經常還需要逆規範化處理,即將數據集重新整合為一個表。

nn

3.5一個觀察單元存儲在多個表中。

nn

關於同一類觀察單元的數值分散在多個表格或文件中也很常見。這些表格和文件常被另一個變數分開,以便於每個代表某一年、某個人或某個地點。只要每個記錄的格式是一致的,這個問題很好解決:

nn

1.將文件讀取為一個表格的列表。

nn

2.對於每個表格,添加一個新列記錄—原始文件名(因為文件名常是重要變數的值)。

nn

3.將所有表格組合為一個表格。

nn

在R中plyr包讓此成為一個簡單的任務。以下代碼在一個目錄中(data/)生成了一個文件名的向量,匹配於一個正則表達式(以.csv結尾)。下一步我們用文件的名稱給向量的各元素命名。我們這樣做因為plyr將在下步保留名稱,確保每一行在最終的數據框中標註了其來源。最後,ldply()在每個路徑循環,讀取csv文件並將結果組合為一個單獨的數據框。

nn

R> paths <-ndir("data", pattern = ".csv$", full.names = TRUE)

nn

R> names(paths) <- basename(paths)

nn

R> ldply(paths, read.csv, stringsAsFactors = FALSE)

nn

一旦你有了一個單獨的表格,你可以根據需要進行更多的整理操作。這種清洗的一個例子可以在這個網址找到hadley/data-baby-names。 例子是關於美國社保局提供的129個按年度劃分的嬰兒名字表格,並將這些表格組合為一個文件。

nn

更複雜的情況出現於數據集的結構隨時間而變化。例如,數據集中包含不同的變數,名稱不同的相同變數,不同的文件格式,或對缺失值不同的慣例。你可能需要單獨地整理每個文件(或者,如果你幸運的話,整理為各小組),然後將它們一次組合為整齊的數據。這類整理的一個例子在網址hadley/data-fuel-economy中有所描述,例子展示了對1978至2008年50,000輛汽車EPA燃油經濟數據的整理。原始數據可以在網上找到,但每年的數據存儲在單獨的文件中,而且有4種主要格式及很多小的變化,這些讓對此數據集的整理極具挑戰。

nnnn

4. 整齊工具

nn

一旦你得到了整齊的數據集,你能用它來做什麼呢?整齊數據只有在能讓分析更簡單時才有價值。這個部分談論整齊工具,即將整齊的數據集作為輸入並輸出整齊數據集的工具。整齊工具很有用,因為一個工具的輸出可以用作另一個工具的輸入。這讓你可以更簡單、更容易地組合多個工具來解決問題。整齊的數據還保證了變數以一致和清晰的方式存儲。這讓每個工具更簡單,而不需要像瑞士軍刀那樣的一大組參數來處理不同的數據結構。

nn

工具變得複雜往往出於2個原因:它們用混亂數據作為輸入(混亂輸入工具),或它們產生混亂數據的輸出(混亂輸出工具)。混亂輸入工具通常比整齊輸入工具更加複雜,因為它們需要包含整理的過程。這對於通常形式的混亂數據有幫助,但常常使得函數更加複雜,不易使用和維護。混亂輸出工具讓人抓狂,讓分析變慢,因為它們很難被編寫,你還得經常惦記著如何從一種格式轉換到另一種格式。在後面的部分我們將看看這兩方面的例子。

nn

下面,我為分析中三個重要的組成列舉出整齊工具和混亂工具的幾個例子:數據操縱,可視化和建模。我將特別集中在R提供的工具(R Development Core Team 2011),因為有很多現有的整齊工具,但我也會涉及其他統計編程環境。

nn

4.1. 數據操縱

nn

數據操縱包含變數到變數的變形(比如log或sqrt),以及整合,過濾和重新排列。根據我的經驗,以下是數據操縱的四個基礎動作:

nn

l 過濾:基於某些條件創建觀察值的子集或刪除某些觀察值。

nn

l 變形:添加或調整變數。這些調整可以包含單個變數(比如log變形)或多個變數(比如從重量和容量計算密度)。

nn

l 整合:將多個值整合為一個單一值(比如求和或求均值)。

nn

l 分類:改變觀察值的排序。

nn

當存在一致的方法提取變數時,所有這些操作都會變得更加容易。整齊數據可以做到這些,因為每個變數存儲在屬於它的列中。

nn

在R中,過濾和變形通過R的基礎函數subset()和transform()來實現。這些函數的輸入和輸出是整齊的。aggregate()函數實現組間的整合,是輸入整齊工具,如果只使用了單一的整合方法,它還是一個輸出整齊的工具。plyr包為整合和分類提供了summarise() 和arrange()整齊函數。

nn

這四個動詞可以且經常通過介詞「by」來調整。我們經常需要組間的整合,變形和求子集,選出各組間的最大值,對重複數據求均值等等。分別將四個動詞中的每一個與by進行組合,它們就可以在一個數據框的各子集上進行操作。大多數SAS PROCs 擁有一個BY語句,讓操作可適用於組,且基本是輸入整齊的。基礎 R 擁有by()函數,是輸入整齊的,但輸出不整齊,因為它產生一個列表。plyr包中的ddply()函數是它的一個整齊工具的代替。

nn

當我們處理多個數據集時還需要其他工具。整齊數據的一個優勢是其可以很容易地和其他整齊數據集進行組合。所需要的僅僅是一個連接工具,將共同的變數進行匹配,並添加新的列。這在基礎R中通過merge()函數實現,或通過plyr包中的join()函數實現。將這些連接工具和組合在數組中存儲的數據集的困難進行比較(有的一拼)。在矩陣操作可被使用前,這項工作通常需要艱難的校準,而且會產生很難發現的錯誤。

nnnn

4.2. 可視化

nn

整齊的可視化工具只需要輸入整齊,因為它們的輸出是視圖。領域特定語言(Domain specific languages) 對整齊數據的可視化處理非常適用,因為它們可將可視化描述為變數和圖形美觀屬性的匹配(比如位置,大小,形狀和顏色)。這是圖形語法(Wilkinsonn2005)和圖形分層語法(Wickhamn2010)所涵蓋的概念,是特別為R語言量身定製的延展。

nn

R中大多數圖形工具是輸入整齊的,包括基礎的plot()函數,lattice家族中的圖形工具(Sarkarn2008),以及ggplot2(Wickham 2009)。有些專用工具是為了混亂數據的可視化。一些R基礎函數,如barplot(), matplot(), dotchart(),以及mosaicplot(),可用於變數分散存儲在多個列中的混亂數據集。類似地,平行協同圖形(parallel coordinates plot)(Wegman 1990; Inselberg 1985)可被用於為每個時間點構成一列的混亂數據集創建時間序列圖形。

nn

4.3. 建模

nn

正是建模促使我想寫這篇論文,因為多數建模工具對於整齊數據集都特別適用。每種統計語言都有一種將模型描述為不同變數間聯繫的方式。領域特定語言(Domain specific languages)將反應(responses)與預測(predictors)相連接:

nn

l R (lm()): y ~ a + b + c * d.

nn

l SAS (PROC GLM): y = a + b + c + d + c * d.

nn

l SPSS (glm): y BY a b c d / DESIGN a b c dnc * d.

nn

l Stata (regress): y a b c#d.

nn

這並不是說,整齊數據是用於內部計算回歸的格式。(需要一個)重大的變形來產生一個數字矩陣,這個矩陣可以容易地提供給標準線性代數列程(routines)。常見的變形包括添加一個攔截列(由1組成的一列),將分類變數轉變為多個二進位虛擬變數,以及將數據投射到樣條函數的適當基礎。

nn

已經有一些適應特定類型混亂數據的建模函數的嘗試。比如,SASs proc glm,如果展示了重複的(repeated)關鍵詞,等式結果一邊(responsenside)的多個變數將被解釋為重複的測量。對於原始Billboard數據來說,我們可以用wk1-wk76 = track的形式構建一個模型,而非整齊數據的rank = weekn* track形式。

nn

另一個有趣的例子是經典配對t檢驗,根據數據如何存儲,可通過兩種方式計算。如果數據以表14(a)的方式存儲,那麼配對t檢驗就是應用於x和y之間均值差異的t檢驗。如果數據以表14(b)的方式存儲,那麼可以通過使一個混合效果模型和一個代表變數的混合虛擬變數以及一個對每個id的隨機攔截相匹配,產生出相等的結果。(在R中的lmer4注釋,這表示為valuen~ variable + (1 | id))。無論哪種建模,數據產生的結果相同。如果沒有更多信息,我們不能說哪種數據整齊:如果x和y代表左右胳膊的長度,那麼表14(a)整齊,如果x和y代表對第一天和第十天的觀測,那麼表14(b)整齊。

nnnn

表14:兩組數據用於實施相同的檢驗

nn

儘管模型的輸入通常需要整齊的輸入值,這種對細節的關注不會延伸到模型輸出。像預測和預估係數這樣的輸出不總是整齊的。

nn

這使得從多個模型組合結果更加困難。例如,在R中模型係數的系統默認值不整齊,因為它沒有一個明確的變數為每個預估記錄變數名,它們被作為行名記錄。在R中行名必須是獨特的,所以從多個模型中組合係數(例如從bootstrap resamples, ornsubgroups)需要避免丟失重要信息的方法。這讓你的分析很不順暢,並使得從多個模型中組合結果更加困難。目前我還不知道哪個包能解決這個問題。

nnnn

5. 案例分析

nn

以下案例研究展示了整齊數據和整齊工具如何通過簡化在操縱、可視化和建模間的轉換,讓數據分析變得容易。你將不會看到只為了將一個函數的輸出轉換為適應另一個函數的輸入而存在的任何代碼。我將展示分析所使用的R代碼,但即使你不熟悉R語言,或我使用到的俚語,我已通過將解釋語言穿插在代碼和結果之間,使這些例子更易懂。

nn

這個案例分析使用了墨西哥個體級別的死亡率數據。目標是找到一天之中死亡原因與非正常時態模式的聯繫。圖1展示了所有死因的時態模式,每小時死亡數。我的目標是找到與這種模式最不相同的疾病。

nn

完整的數據包括墨西哥2008年539,530起死亡的信息,有55個變數,包括死亡的地點和時間、死因,以及過世者的人口統計信息。表15展示了數據的一小部分樣本,聚焦於死亡時間相關的變數(年、月、日、小時),以及死因(cod)。

nnnnnn

圖1:所有死因的時間模式

nn

表15:從原始數據集539,530行和55列抽取的16行15列的樣本。

nnnn

為了達到我們找到非正常時態模式的目標,採取了以下方法。首先,使用整齊的計數函數,統計每個死因(cod)的每小時死亡數量(hod)。

nn

R> hod2 <- count(deaths, c("hod", "cod"))

nn

然後我們使用subset函數移除缺失值(沒有對我們的目標有用的信息)。

nn

R> hod2 <- subset(hod2, !is.na(hod))

nn

這得到表16(a)。為了給疾病提供信息標籤,下一步我們通過cod變數將此數據集與codes數據集連接。這添加了一個新變數,disease,如表16(b)所示。

nn

R> hod2 <- join(hod2, codes, by = "cod")

nn

每個原因的死亡總數按照不同順序的重要性而不同。心臟病導致了46,794起死亡,但雪崩只導致10起。這意味著,比較每小時的死亡比率比死亡總數更有意義。我們通過以下方法來進行上述計算:使用cod將數據分解,然後使用transform()函數添加新的一列prop—每小時的死亡數(freq)除以這個死因的總數。這個新列如表16(c)所示。

nn

ddply()函數通過其第二個(cod變數)將第一個變數(hod2)拆分,然後在每個結果後面添加第三個變數(transform)。第四個變數(prop= freq / sum(freq))被傳遞到transform()。

nn

R> hod2 <- ddply(hod2, "cod", transform, prop = freq /nsum(freq))

nn

然後我們計算每個小時的整體平均死亡率,並將其整合回原始數據。這得出表 16(d)。

nnnnn

表16:從hod2數據框抽取的四種疾病四個小時的樣本

nnnn

通過比較prop與prop_all,我們可以很容易地比較每個疾病與整體的事故模式。

nn

首先,我們計算出每小時死亡的人數,通過hod將hod2拆分,計算每個死因的總數。

nn

R> overall <-nddply(hod2, "hod", summarise, freq_all = sum(freq))

nn

接下來,我們計算出每小時死亡的總體比率。:

nn

R> overall <-ntransform(overall, prop_all = freq_all / sum(freq_all))

nn

最後,我們將總體數據與個體數據相結合,使兩者的比較更容易:

nn

R> hod2 <- join(hod2,noverall, by = "hod")

nn

接下來,我們計算每個死因的時態模式與總體時態模式的距離。有很多方法可測量這個距離,但我找到了一個簡單的「均值平方背離」很有啟發。我們還記錄了樣本大小,死因對應的死亡總數。為了保證我們所研究的疾病有足夠的代表性,我們將只關注死亡總數大於50的疾病(每小時大於兩例)。

nn

R> devi <- ddply(hod2,n"cod", summarise,

nn

R> n = sum(freq),

nn

R> dist = mean((prop -nprop_all)^2))

nn

R>

nn

R> devi <- subset(devi,nn > 50)

nn

我們不知道這個預測工具的變化特徵,但我們可以通過繪製n和deviation(背離值)的圖形,由觀察圖形來對其進行研究,見圖2(a)。在線性的程度上,這個圖表除了變化程度隨樣本大小遞減外,所體現的信息不多。但在log-log的程度上,見圖2(b),存在清晰的模式。這個模式在我們從一個線性模型添加最佳匹配線時,特別容易看出來。

nn

R> ggplot(data = devi,naes(x = n, y = dist) + geom_point()

nn

R>

nn

R> last_plot() +

nn

R> scale_x_log10() +

nn

R> scale_y_log10() +

nn

R> geom_smooth(method =n"rlm", se = F)

nnnnnn

(a)線性程度Linearnscales (b)log程度log scales

nn

圖2:(a)n 對應dist(偏離)的圖表。偏離的變化程度由樣本大小決定:小樣本的變化程度大。(b)log-log圖使看清變化模式和異常高的值更加容易。藍色線條是最佳匹配線。

nn

我們對相對於其相鄰的x所擁有高y值的點感興趣。這些點控制著死亡數量,他們代表與整體模式相差最大的疾病。

nn

為了找到這些異常點,我們做了一個強線性模型並繪圖了剩餘點,如圖3。圖3展示出大約resid(剩餘點)為1.5的一個空白區域。我們強制選擇剩餘值大於1.5的疾病。用2步來實現:首先,我們從devi(每個疾病一行)選擇恰當的那些行,然後從原始匯總數據集中(每個疾病24行)找到匹配的時間過程信息。

nn

R> devi$resid <-nresid(rlm(log(dist) ~ log(n), data = devi))

nn

R> unusual <-nsubset(devi, resid > 1.5)

nn

R> hod_unusual <-nmatch_df(hod2, unusual)

nnnnnn

圖3:通過log(n)預測log(dist)的強線性模型的剩餘點。1.5刻度位置的水平線展示了進一步探索的入口。

nn

最後,我們繪製每個異常病因的時間過程,如圖4。因為差異性的不同,我們將疾病分成2個圖,上面圖形顯示死亡數量超過350的疾病,下面顯示低於350的疾病。死因可基本分為3組:謀殺,溺水,運輸相關。謀殺在夜間發生更多,溺水在下午發生更多,運輸相關的死亡在上下班時間發生較多。背景上灰白色的線顯示所有疾病的時間過程。

nn

R> ggplot(data =nsubset(hod_unusual, n > 350), aes(x = hod, y = prop)) +

nn

R> geom_line(aes(y =nprop_all), data = overall, colour = "grey50") +

nn

R> geom_line() +

nn

R> facet_wrap(~ disease,nncol = 3)

nnnnnnnn

圖4:非正常時間過程的死因。整體的每小時死亡率用灰線表示。上方的圖表是每年大於350例死亡的死因。下方的圖表是每年少於350例死亡的死因。請注意y軸的刻度不同。

nnnn

6.討論

nn

數據清洗是個很重要的問題,但在統計研究中是個不太常見的課題。這篇論文描述了較小但重要的一部分數據清洗工作,我稱之為數據整理:將數據集結構化,以適應於操縱,可視化和建模。仍然有很多工作要做。隨著我們對整齊數據和整齊工具的理解更加深入,以及我們解決整理數據難題能力的提高,我們內在的能力會提升。

nn

探索整齊數據的替代方案可能會產生更大的改進。關於整齊數據存在一個雞生蛋的問題:如果整齊數據只是和對其處理的工具同樣重要,那麼整齊工具與整齊數據將是密不可分的。這將很容易受困於局部的最大值,單獨改變數據結構或數據工具將無法改進工作。打破這個局部最大值的問題很難,需要長期的努力,還會遭受很多錯誤。我希望整齊數據框架不是這些錯誤中的一個,我也不認為它就是最終的解決方案。我希望有人能在此文的基礎上開發出存儲數據的更好方案和更好的工具。

nn

讓人吃驚的是,不論從統計還是認知的因素來看,我很難找到指導整齊數據設計的原則。至今為止,我的工作是由我做數據分析的經驗、我關於關係資料庫設計的知識、以及我自己對數據分析工具的反思所驅動的。人為因素,以用戶為中心的設計,人機互動社區或許對這個問題能有所貢獻,但數據和數據處理工具的設計在這些領域裡並不是熱門的研究話題。未來,我希望使用這些領域的方法論(user-testing, ethnography,ntalk-aloud protocols)來改進我們對數據分析認知方面的理解,並進一步改進我們設計恰當工具的能力。

nn

可能存在其他整齊數據的方案。例如,構建一套處理存儲在多維度數組中的值的工具是可行的。這是微數組或fMRIs 產生的大型生物醫學數據集的常見存儲格式。這對於基於矩陣操縱的很多多變數方法也很有必要。幸運的是,因為有很多有效的工具處理高維度數組,甚至是很罕見的那些數組,這樣的數組整齊格式不僅會非常緊湊和高效,還應很容易地與統計的數學基礎建立聯繫。事實上,這是Pandas python數據分析圖書館(McKinney 2010)所採用的方法。更有趣的是,我可以考慮這樣的整齊工具,他們能忽略下層的數據表現,在數組整齊和數據框整齊格式間自動選擇以優化內存使用和性能。

nn

除了整理數據,清洗數據還包括很多其他任務:解析日期和數字,查找缺失值,更正字元編碼(對於國際數據來說),查找相似但不相同的值(由打字錯誤產生),證實實驗設計,以及填入結構性缺失值,更不用說識別可疑值的基於模型的數據清洗。我們是否能開發出其他框架讓這些工作更容易?

nn

7.致謝(略)

reference:

1.英文原文2.文中相關數據
推薦閱讀:

MaxCompute Studio使用心得系列3——可視化分析作業運行
告別盲目建設大數據 國家即將出台兩項大數據重要標準

TAG:大数据 | 数据分析 | 数据清洗 |