R數據分析:數據清洗的思路和核心函數介紹


好多同學把統計和數據清洗搞混,直接把原始數據發給我,做個統計吧,這個時候其實很大的工作量是在數據清洗和處理上,如果數據很雜亂,清洗起來是很費工夫的,反而清洗好的數據做統計分析常常就是一行代碼的事情。

Data scientists only spend 20% of their time creating insights, the rest wrangling data.

想想今天就給大家寫一篇數據處理的常用函數介紹吧。全是自己的一丟丟經驗,肯定不會是最優的,僅僅是給個參考,因為在R中同一個目的的實現方法太多了,找到適合自己的才是最好的。我爭取盡量清晰地一步一步給大家展示一下整個清洗數據的流程。

在R語言中我們會用一系列的方法把我們的數據清洗過程連起來,整個的思路就是從原始數據開始,一步一步形成我們的最終可以用來做統計的數據。

整體上我們數據處理的步驟可以包含下面5個部分,也是有順序的5步:

  • Importing of data(數據導入)
  • Column names cleaned or changed(列名的清洗轉換)
  • De-duplication(去重)
  • Column creation and transformation (e.g. re-coding or standardising values)(生成新變量)
  • Rows filtered or added(數據選擇)

本文就帶着大家一步一步走一遍,中間會詳細說明一些核心函數的用法,希望對大家有點幫助。當然了,以下內容默認您已經理解dplyr包的基礎,比如Pipes%>%符號。

數據導入

數據導入大家應該都沒有問題,就算有問題網上搜搜一般都可以解決,導入數據的方法有很多種,這兒會推薦大家直接用右上角的菜單import data,或者使用rio包的import函數。我現在有一個已經導入的原始數據raw如下圖:

 

數據導入進來之后我們首先應該整體上看看變量類型,變量名稱都是如何的,以此決定我們是不是將變量名初步改改,或者變量類型也得改改,就是首先得有個整體把握,此時推薦大家運行一下skim函數,這個是幫助我們了解數據整體形式的十分方便的函數,請大家把summary忘掉,直接運行skim:

skimr::skim(raw)

函數的輸出包括整體的數據有多少行多少列,列的不同類型(數值,字符,時間)有幾種,然后不同的列的類型還會輸出我們關心的指標,比如字符型的列都會有每一列的缺失比例,極值,非重復值,空白等:

 

比如對於每一個數值型的列都會有缺失數量,均值標准差百分位數,直方圖等等我們關心的東西:

 

反正就是大家用skim函數就可以從整體上把握住我們的數據的樣子了。

另外還會推薦大家用names函數看看數據的列名,有了對數據集的整體把握和全部的變量名,我們可以緊接着進行下一步:變量名的轉換。

列名的清洗轉換

轉化的目的就是使得之后的操作調用變量可以更加的清晰和方便,我可以瞅瞅我的原始數據的列名:

names(raw)

 

在查看列名時我們需要關注一下列名是不是符合以下要求:

  • 在可讀性高的情況下盡可能短
  • 沒空格
  • 沒有特殊字符(&, #, <, >, …)
  • 樣式統一(e.g. all date columns named like date_onset, date_report, date_death…)

對照上面的標准我們其實就可以知道目前我的原始數據raw的列名中,infection date中出現了空格,date onset . infection date這兩個列名的也有空格而且形式應該統一以下比較好;然后…28這個列名有特殊符號,這些最好都先得改改。

當我們的數據集非常大的時候,比如有好幾千個變量的時候,改列名或者列名統一也是一件比較繁瑣的事情,這個時候會推薦大家用clean_names()函數自動修改一下,之后再寫代碼微調,這樣對大型數據庫處理起來可以節省大量時間,對我們的數據raw我們可以寫出如下代碼:

data <- raw %>% janitor::clean_names()

運行上面的代碼后我們再看data的列名就可以發現至少特殊字符和空格的問題統統都沒有了:

 

上面屬於粗獷的處理,但是還有其它的問題,反正實際情況中大家也免不了需要改列名的,此時可以用rename函數進行列名的手動修改,基本格式是新名=舊名,如下:

raw %>% janitor::clean_names() %>% rename(date_infection = infection_date, date_hospitalisation = hosp_date, date_outcome = date_of_outcome)

運行代碼后我們就可以看到所有設定的列名都被改好了。

很多時候我們是從一個很大的微觀數據庫中摘變量來分析的,就是常常我們只需要那些我們用得着的變量,這時可以用select函數,這個大家用的比較多,這兒分享幾個在使用select的時候的輔助函數,將這些輔助函數和select結合起來會使得效率更高,這些函數有一個統一的名字叫做“tidyselect” helper functions,常見的如下圖:

 

比如我就想選擇所有的數值型變量來分析我就可以寫出如下代碼:

select(where(is.numeric))

比如我就想找變量名中包含某個字符的變量,我就可以用contain函數,比如我現在手里是一個母子配對數據集,變量既有母親的也有孩子的,我就可以用contain方便滴篩選出來母子的年齡:

 select(contains("age"))

上面的代碼會將所有包含“age”這個字符串的變量都篩出來;同樣的道理我們還可以用ends_with() and starts_with()篩出來大型數據集中以某個字符開頭和結尾的列,比如一個縱向隨訪數據集每一波的cesd都是以cesd開頭為列名的我們就可以用starts_with()將所有隨訪的cesd都篩出來。還有matches()函數也可以幫助實現列名的匹配篩選,比如在raw數據中,用如下代碼就可以篩選出所有列名中含有“onset”,“hosp”,“fev”的列

raw %>% select(matches("onset|hosp|fev")) %>% names()

 

上面一步就實現了將fever的發病時間,入院時間,住院時長這些變量都篩出來,指導這些操作在處理大型數據庫的時候就會省事很多。

反正,整體的操作都是非常靈活的,會有很多細節需要學習,但是大家要掌握的是我知道有這么一個函數可以解決這個問題,我就先記住函數名,具體細節可以邊用邊查,整體的學習過程就是這樣。

新變量生成和變量轉換

在數據處理中我們還會涉及到變量的改變和根據原有變量生成新變量,變量生成和轉換都可以用mutate來實現,具體規則就是:

mutate(new_column_name = value or transformation)

就上面這個式子,用起來可就是包羅萬象,比如在你的數據中有身高體重,我想計算一個新的變量叫做bmi,則可以寫出代碼如下:

  mutate(bmi = wt_kg / (ht_cm/100)^2)

還有很多的新變量生成和轉換的應用場景,比如完全復制一個變量,新列全是7,用另外的變量計算,兩個變量的值貼一起形成新變量:

 raw%>%                       
  mutate(
    new_var_dup    = case_id,             # 完全復制 new_var_static = 7, # 新列全是7 new_var_static = new_var_static + 5, #用另外的變量計算新變量 new_var_paste = stringr::str_glue("{hospital} on ({date_hospitalisation})") # 兩個變量的值貼一起形成新變量 ) 

還有很多很多的操作都是在mutate中完成的。

我們常常還會有的需求是一次性處理好多個變量,比如一次性將所有的變量都轉換為字符類型,這個時候為了代碼的整潔統一我們依然可以用mutate和across,結合.cols和.fns參數就行,比如下的代碼就是將3個列全部轉換為字符串,大家不用特意再去用lapply或者寫循環什么的:

raw %>% 
  mutate(across(.cols = c(temp, ht_cm, wt_kg), .fns = as.character))

還有幾個小技巧,比如我想將數據庫的所有列都進行某一個操作,我不用將所有的列名都敲出來,只需要用everything函數就可以,比如用下面的代碼就實現將數據的所有列轉換為字符型:

raw %>% 
  mutate(across(.cols = everything(), .fns = as.character))

大家把握住一個原則就是列的生成轉換就是用mutate就行,然后涉及到選擇的時候我們一定記得要結合輔助函數“tidyselect” helper functions。要有這個意識。

還有一個函數要給大家介紹一下就是coalesce()

很多時候我們一個變量有兩種測量方式,比如有自我報告的體重,還有物理測量的體重,我們通常的想法是以物理測量的為准,當物理測量有缺失我們用自我報告的數據來填補,這么一個過程我們就可以用coalesce函數一步搞定,如下:

 

所以說我們在使用mutate的時候我們可以根據需要結合coalesce函數,比如下面的代碼就實現了在raw數據集中當village_detection缺失時用village_residence的值填補:

raw %>% mutate(village = coalesce(village_detection, village_residence))
  • 變量重新編碼

變量重新編碼也是常見的操作,它也是屬於變量轉換的大框框里面的,所以我們依然是用mutate,比如在我們的raw數據中,我們有個變量hospital,這個變量有很多的水平,其實好多水平是一樣的:

table(raw$hospital, useNA = "always") 

 

這種情況在我們自己錄入的數據庫中經常會出現

就是"Mitylira Hopital"和"Mitylira Hospital",和"Military Hopital"其實都可以看成是錄入的時候錄錯了,其實他們都是"Military Hospital",這個時候我們要做的就是重新編碼變量,可以用mutate和recode實現我們的需求:

raw%>% mutate(hospital = recode(hospital, # for reference: OLD = NEW "Mitylira Hopital" = "Military Hospital", "Mitylira Hospital" = "Military Hospital", "Military Hopital" = "Military Hospital", "Port Hopital" = "Port Hospital", "Central Hopital" = "Central Hospital", "other" = "Other", "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)" ))

上面的代碼運行完,我們再看相應的錯誤的錄入都正確地歸為相應水平了

大家還應該掌握的使用邏輯判斷進行變量重新編碼的方法,這個時候需要用到replace(),ifelse()andif_else()和case_when(),給大家寫一個case_when的例子,這個函數就是在我們需要根據某個變量的值生成新變量的時候使用,比如我們根據age_unit的不同取值,生成新變量age_years,我們就可以用case_when():

raw %>% mutate(age_years = case_when( age_unit == "years" ~ age, age_unit == "months" ~ age/12, # 年齡單位為月,age_years就等於年齡/12 is.na(age_unit) ~ age, # 年齡單位缺失的話,默認成“年”,age_years就等於age TRUE ~ NA_real_)) #其余所有情況都歸為age_years缺失

在使用case_when的時候我們可以將想設定的都設定好,余下的情形都可以用關鍵字TRUE代表,就想上面代碼的最后一行那樣,對於age_unit這個變量的其余的所有情況我們都認為age_years為缺失。

  • 缺失值替換

缺失值轉換依然可以在mutate中完成,因為它依然是在變量轉化的框架里:

因為我們的hospital這個變量其實是有很多的缺失值的,我們希望將相應的缺失值都替換成missing,我們就可以寫出如下代碼:

raw %>% mutate(hospital = replace_na(hospital, "Missing"))

有一種情況需要注意,就是因子變量中有NA,我們如果用replace_na會報錯,上面代碼中hospital變量是字符型的,所以沒有問題。就是對於一個因子來講,它本身水平就是固定的,有了NA,我們將NA進行替代,比如替代成missing,其實missing它並不是因子原來本身的一個水平,所以會報錯,這個時候我們可以用fct_explicit_na()函數。

fct_explicit_na()函數會直接將因子變量中的NA進行相應的替換,替換的值也自動成為該因子的一個水平。

  • 數值變量轉分類變量

就是說我們有時候想將連續變量轉成分類變量分析,這個時候常常會用到的函數有age_categories(),cut(),quantile(),ntile()

看一個age_categories()的例子:

raw%>% mutate( age_cat = age_categories( age_years, breakers = c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70)))

上面的代碼就將age_years這個連續變量化成了分類變量,分的節點就是breakers參數的向量中,quantile(),ntile()則可以幫助我們快速地划分節點。分類過后就可以用table函數查看每個類別的數量,上面的代碼就是將連續變量age_years用breakers參數中的點進行了划分,划分后形成了分類變量,結果如下:

 

有時候我們對划分的結果會不放心,比如這個類別到底是開區間還是閉區間,當然這些都是有參數可以調的,為了確認我們也可以做交叉表格,我么可以把原來的連續變量和生成的分類變量進行交叉:

table("Numeric Values" = raw$age_years, "Categories" = raw$age_cat, useNA = "always") 

通過這么樣一個操作我們就可以判斷是不是相應的連續變量都被正確地划分到了相應的類別中:

 

還有一種比較特別的需求,我們雖然想按連續變量分組,但是我想每個組的人數相同,這個時候結合ntile()就可以實現,比如我想把age_years化成分類變量,且規定每一類人數相同,我就可以寫出如下代碼:

ntile_data <- raw %>% mutate(even_groups = ntile(age_years, 10))

去重

去重大家就去研究一個函數,叫distinct就行。

行的過濾和添加

給數據庫的行進行改變大家都會,但是要在原先的數據框中間插入一行怎么辦呢?

可以用addrow,比如我想在原來的數據集raw的第二行之前插入一行,我可以用如下代碼:

raw %>% 
  add_row(row_num = 666, case_id = "abc", generation = 4, `infection date` = as.Date("2020-10-10"), .before = 2)

該行的每一個變量都需要規定一下,沒設定的都會空着,.before = 2的意思就是在原來數據框的第二行之前插入。

  • 按規則進行行的選擇

選擇行也是用的比較多的,比如我就想選性別為女的行,或者我就想選擇某些變量沒有缺失的行等等,選擇行我們是用filter,但是在以是否缺失為條件的時候大家不要去用filter(!is.na(column) & !is.na(column))這個時候推薦大家用drop_na,通過drop_na就可以將某個變量有確實的行全拿掉。

  • 橫向計算

正常我們計算變量都是縱向依次計算的,比如最開始寫的BMI計算的例子,有時候我們需要對一個觀測的多個變量進行計算,比如一個病人有好多症狀,我想對每個病人症狀個數求和,本質上這是一個橫向計算的問題,我就可以使用rowwise()函數,用完之后記得ungroup()一下:

row %>% rowwise() %>% mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == "yes")) %>% ungroup() %>% select(fever, chills, cough, aches, vomit, num_symptoms) 

比如上面的代碼就計算好了每一個病人的症狀個數。

小結

今天給大家寫了數據處理中的一些函數和處理的一般流程:導入數據后先整體把握,第二步規范列名,列搞定之后第三步就是去重,去完重就是生成新變量,變量轉換;最后一步就是行的選擇和添加。每一個步驟中給大家寫了一點點例子,感謝大家耐心看完,自己的文章都寫的很細,重要代碼都在原文中,希望大家都可以自己做一做,請轉發本文到朋友圈后私信回復“數據鏈接”獲取所有數據和本人收集的學習資料。如果對您有用請先記得收藏,再點贊分享。

也歡迎大家的意見和建議,大家想了解什么統計方法都可以在文章下留言,說不定我看見了就會給你寫教程哦,有疑問歡迎私信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM