本中你將學習在R中數據處理簡潔的方法,稱為tidy data。將數據轉換為這種格式需要一些前期工作,但這些工作從長遠來看是值得的。一旦你有了整潔的數據和一些包提供的整潔工具,您將花費很少時間將數據從一種表示轉換到另一種,從而可以將更多的時間花在分析問題上。
本文將為您提供整理數據的實用介紹以及tidyr包中附帶的工具。如果你想了解更多的基本理論,你可能會喜歡發表在《統計軟體雜誌》上的tidy data論文[1]。
本文框架數據清洗案例我們主要通過一個案例,來了解如何整潔數據,並將案例中的各個有用函數進行詳細解讀。該例子來自《R for data science》[2],案例數據來自tidyr::who,其包含按年份,國家,年齡,性別和診斷方法細分的結核病(TB)病例。數據來自2014年世界衛生組織《全球結核病報告》[3]。
library(tidyverse) #加載包
who #數據展示這是一個非常典型的現實示例數據集。它包含冗餘列,奇數變量代碼和許多缺失值。我們需要採取多個步驟來對其進行整理。
不是變量的列匯集在一起首先將不是變量的列聚集在一起。所包含的列包括:
country,iso2和iso3是三個指定國家/地區的變量。
變量名中給出的結構(例如new_sp_m014,new_ep_m014,new_ep_f014)可能是值,而不是變量。
因此,我們需要將從new_sp_m014到newrel_f65的所有列匯總在一起。我們用通用名稱"key"來表示他們。我們知道單元格代表案件數,因此我們將變量數存儲在cases中,並用na.rm去除含有缺失值的行。這裡使用pivot_longer()將數據變長,具體見後面函數詳情。
who1 <- who %>%
pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = 'key',
values_to = 'cases',
values_drop_na = T
)
who1對key進行計數,我們可以得到一些有關值結構的提示:
who1 %>% count(key)其中key的具體含義,查閱可得:
sn 代表無法通過肺部塗片診斷(塗片陰性)的肺結核病例
sp 代表可被診斷為肺部塗片(塗片陽性)的肺結核病例
第六字母:結核病患者的性別。男性(m)和女性(f)
其餘數字給出了年齡段。數據集將案例分為七個年齡組:
替換數據我們需要對列名稱的格式進行較小的修正:將new_rel替換為newrel(很難在這裡找到它,但是如果您不修正它,我們將在後續步驟中出錯)。這裡使用了stringr包中的str_replace(),將newrel替換new_rel。
who2 <- who1 %>%
mutate( names_from = stringr::str_replace(key,'newrel','new_rel')
)
who2
字符分割接下來就是將key中的字符進行分割,我們使用separate()對字符進行兩次分割。
1.將在每個下劃線處拆分代碼。
who3 <- who2 %>%
separate(key,c('new','type','sexage'),sep = '_')
who3利用select()刪除沒用的列:new,iso2,iso3。
who3 %>% count(new)
who4 <- who3 %>% select(-new,-iso2,-iso3)
who4
將分離sexage到sex和age通過的第一個字符後拆分:who5 <- who4 %>%
separate(sexage,c('sex','age'),sep=1)
who5這時,who數據集整潔!
可視化數據清洗完畢,就可以做一些初步的可視化,探索性分析.這裡簡單繪製了前幾個國家不同年份,不同性別的結核病病例總數。
who5 %>% group_by(country,year,sex) %>% filter(year<2003) %>%
count() %>%
head(100) %>%
ggplot(aes(x=as.factor(year),y=n,fill=country))+geom_col() +facet_wrap(~sex,nrow = 1)+
scale_fill_brewer(palette = "Paired")
複雜的管道函數事實上你可以直接只用管道函數構建一個複雜的函數,這樣做去除了中間變量,而且可讀性很強,強烈推薦。
who %>%
pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = "key",
values_to = "cases",
values_drop_na = TRUE
) %>%
mutate(
key = stringr::str_replace(key, "newrel", "new_rel")
) %>%
separate(key, c("new", "var", "sexage")) %>%
select(-new, -iso2, -iso3) %>%
separate(sexage, c("sex", "age"), sep = 1)
所用函數詳細解釋pivot_longer()、poivot_wider()pivot_longer() 將在列中列名(數值)轉換到一列上。具體可見下圖,將列變量轉化為數據存在year列名中,相當於把數據變長(longer).
函數主要參數:
names_to 字符串,指定要從數據的列名中存儲的數據創建的列的名稱。values_to 字符串,指定要從存儲在單元格值中的數據創建的列的名稱。values_drop_na 如果為真,將刪除value_to列中只包含NAs的行。例子如上面例子:將new_sp_m014到newrel_f65之間的列選取,匯總到key列名中,值存在cases列名中,並將含有缺失值的行進行刪除。
who1 <- who %>%
pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = 'key',
values_to = 'cases',
values_drop_na = T
)當然還有一個和它相反功能的函數poivot_wider()。具體見下圖,相當於把key中的值變為列名,對應的values數據轉化到population中.下面是簡單的例子。
library(tidyverse)
stocks <- tibble(
year = c(2015, 2015, 2016, 2016),
half = c( 1, 2, 1, 2),
return = c(1.88, 0.59, 0.92, 0.17)
)
stocks我們將數據變寬,將year變為列名,對應在return中的數據進行填充。
stocks %>%
pivot_wider(names_from = year,values_from = return)
separate()該函數可將字符進行分割,具體案例如上.
默認情況下,當separate()看到非字母數字字符(即不是數字或字母的字符)時,它將分割值。可以用裡面的參數sep。比如:sep='_'。他還有一個功能,當sep=2時,可通過第二個位置進行分割,使用在省份市級,等數據上。例如以下函數,其中into = c("century", "year")將原始分割後的數據導入兩個新列上,分別叫century和year。
table3 %>%
separate(year, into = c("century", "year"), sep = 2)注意:默認情況下,會轉化成字符形式,你可以用參數convert=T,將數據轉化最佳結構。
unite是separate()的反函數,這裡做個補充。
默認情況下,sep='_'如果我們不需要任何分隔符可以使用sep=''。
參考資料[1]tidy data論文: http://www.jstatsoft.org/v59/i10/paper
[2]《R for data science》: https://r4ds.had.co.nz/tidy-data.html
[3]《全球結核病報告》: http://www.who.int/tb/country/data/download/en/