數(shù)據(jù)清洗的準則
數(shù)據(jù)集或多或少地會存在數(shù)據(jù)質(zhì)量問題。這里我們使用泰坦尼克號乘客生存預測數(shù)據(jù)集,你可以從GitHub上下載這個數(shù)據(jù)集。
數(shù)據(jù)集格式為 csv,一共有兩種文件:train.csv 是訓練數(shù)據(jù)集,包含特征信息和存活與否的標簽;test.csv 是測試數(shù)據(jù)集,只包含特征信息。
數(shù)據(jù)集中包括了以下字段,具體的含義如下:
訓練集給出了 891 名乘客幸存與否的結果,以及相關的乘客信息。通過訓練集,我們可以對數(shù)據(jù)進行建模形成一個分類器,從而對測試集中的乘客生存情況進行預測。不過今天我們并不講解數(shù)據(jù)分析的模型,而是來看下在數(shù)據(jù)分析之前,如何對數(shù)據(jù)進行清洗。
首先,我們可以通過 Navicat 將 CSV 文件導入到 MySQL 數(shù)據(jù)庫中,然后瀏覽下數(shù)據(jù)集中的前幾行,可以發(fā)現(xiàn)數(shù)據(jù)中存在缺失值的情況還是很明顯的。
數(shù)據(jù)存在數(shù)據(jù)缺失值是非常常見的情況,此外我們還需要考慮數(shù)據(jù)集中某個字段是否存在單位標識不統(tǒng)一,數(shù)值是否合法,以及數(shù)據(jù)是否唯一等情況。要考慮的情況非常多,這里我將數(shù)據(jù)清洗中需要考慮的規(guī)則總結為 4 個關鍵點,統(tǒng)一起來稱之為“完全合一”準則。
好的數(shù)據(jù)分析師必定是一名數(shù)據(jù)清洗高手,要知道在整個數(shù)據(jù)分析過程中,不論是在時間還是功夫上,數(shù)據(jù)清洗大概都占到了 80%。
- 完整性:單條數(shù)據(jù)是否存在空值,統(tǒng)計的字段是否完善。
- 全面性:觀察某一列的全部數(shù)值,比如在 Excel 表中,我們選中一列,可以看到該列的平均值、最大值、最小值。我們可以通過常識來判斷該列是否有問題,比如:數(shù)據(jù)定義、單位標識、數(shù)值本身。
- 合法性:數(shù)據(jù)的類型、內(nèi)容、大小的合法性。比如數(shù)據(jù)中存在非 ASCII 字符,性別存在了未知,年齡超過了 150 歲等。
- 唯一性:數(shù)據(jù)是否存在重復記錄,因為數(shù)據(jù)通常來自不同渠道的匯總,重復的情況是常見的。行數(shù)據(jù)、列數(shù)據(jù)都需要是唯一的,比如一個人不能重復記錄多次,且一個人的體重也不能在列指標中重復記錄多次。
“完全合一”是個通用的準則,針對具體的數(shù)據(jù)集存在的問題,我們還需要對癥下藥,采取適合的解決辦法,甚至為了后續(xù)分析方便,有時我們還需要將字符類型的字段替換成數(shù)值類型,比如我們想做一個 Steam 游戲用戶的數(shù)據(jù)分析,統(tǒng)計數(shù)據(jù)存儲在兩張表上,一個是 user_game 數(shù)據(jù)表,記錄了用戶購買的各種 Steam 游戲,其中數(shù)據(jù)表中的 game_title 字段表示玩家購買的游戲名稱,它們都采用英文字符的方式。另一個是 game 數(shù)據(jù)表,記錄了游戲的 id、游戲名稱等。因為這兩張表存在關聯(lián)關系,實際上在 user_game 數(shù)據(jù)表中的 game_title 對應了 game 數(shù)據(jù)表中的 name,這里我們就可以用 game 數(shù)據(jù)表中的 id 替換掉原有的 game_title。替換之后,我們在進行數(shù)據(jù)清洗和質(zhì)量評估的時候也會更清晰,比如如果還存在某個 game_title 沒有被替換的情況,就證明這款游戲在 game 數(shù)據(jù)表中缺少記錄。
使用 SQL 對預測數(shù)據(jù)集進行清洗
了解了數(shù)據(jù)清洗的原則之后,下面我們就用 SQL 對泰坦尼克號數(shù)據(jù)集中的訓練集進行數(shù)據(jù)清洗,也就是 train.csv 文件。我們先將這個文件導入到 titanic_train 數(shù)據(jù)表中:
檢查完整性
在完整性這里,我們需要重點檢查字段數(shù)值是否存在空值,在此之前,我們需要先統(tǒng)計每個字段空值的個數(shù)。在 SQL 中,我們可以分別統(tǒng)計每個字段的空值個數(shù),比如針對 Age 字段進行空值個數(shù)的統(tǒng)計,使用下面的命令即可:
SELECT COUNT(*) as num FROM titanic_train WHERE Age IS NULL;
當然我們也可以同時對多個字段的非空值進行統(tǒng)計:
SELECT SUM((CASE WHEN Age IS NULL THEN 1 ELSE 0 END)) AS age_null_num,
SUM((CASE WHEN Cabin IS NULL THEN 1 ELSE 0 END)) AS cabin_null_num
FROM titanic_train;
每列空值情況
Age_null_num:177
Cabin_null_num:687
Embarked_null_num:2
Fare_null_num:0
Name_null_num:0
Parch_null_num:0
PassengerId_null_num:0
Pclass_null_num:0
Sex_null_num:0
SibSp_null_num:0
Survived_null_num:0
Ticket_null_num:0
在 titanic_train 數(shù)據(jù)表中,有 3 個字段是存在空值的,其中 Cabin 空值數(shù)最多為 687 個,Age 字段空值個數(shù) 177 個,Embarked 空值個數(shù) 2 個。
既然存在空值的情況,我們就需要對它進行處理。針對缺失值,我們有 3 種處理方式。
- 刪除:刪除數(shù)據(jù)缺失的記錄;
- 均值:使用當前列的均值;
- 高頻:使用當前列出現(xiàn)頻率最高的數(shù)據(jù)。
對于 Age 字段,這里我們采用均值的方式進行填充,但如果直接使用 SQL 語句可能會存在問題,比如下面這樣。
UPDATE titanic_train SET age = (SELECT AVG(age) FROM titanic_train) WHERE age IS NULL;
這時會報錯:
1093 - You can't specify target table 'titanic_train' for update in FROM clause
也就是說同一條 SQL 語句不能先查詢出來部分內(nèi)容,再同時對當前表做修改。
這種情況下,最簡單的方式就是復制一個臨時表 titanic_train2,數(shù)據(jù)和 titanic_train 完全一樣,然后再執(zhí)行下面這條語句:
UPDATE titanic_train SET age = (SELECT ROUND(AVG(age),1) FROM titanic_train2) WHERE age IS NULL;
這里使用了 ROUND 函數(shù),對 age 平均值 AVG(age) 進行四舍五入,只保留小數(shù)點后一位。
針對 Cabin 這個字段,我們了解到這個字段代表用戶的船艙位置,我們先來看下 Cabin 字段的數(shù)值分布情況:
SELECT COUNT(cabin), COUNT(DISTINCT(cabin)) FROM titanic_train;
從結果中能看出 Cabin 字段的數(shù)值分布很廣,而且根據(jù)常識,我們也可以知道船艙位置每個人的差異會很大,這里既不能刪除掉記錄航,又不能采用均值或者高頻的方式填充空值,實際上這些空值即無法填充,也無法對后續(xù)分析結果產(chǎn)生影響,因此我們可以不處理這些空值,保留即可。
然后我們來看下 Embarked 字段,這里有 2 個空值,我們可以采用該字段中高頻值作為填充,首先我們先了解字段的分布情況使用:
SELECT COUNT(*), embarked FROM titanic_train GROUP BY embarked;
我們可以直接用 S 來對缺失值進行填充:
UPDATE titanic_train SET embarked = 'S' WHERE embarked IS NULL;
至此,對于 titanic_train 這張數(shù)據(jù)表中的缺失值我們就處理完了。
檢查全面性
在這個過程中,我們需要觀察每一列的數(shù)值情況,同時查看每個字段的類型。
因為數(shù)據(jù)是直接從 CSV 文件中導進來的,所以每個字段默認都是 VARCHAR(255) 類型,但很明顯 PassengerID、Survived、Pclass 和 Sibsp 應該設置為 INT 類型,Age 和 Fare 應該設置為 DECIMAL 類型,這樣更方便后續(xù)的操作。使用下面的 SQL 命令即可:
ALTER TABLE titanic_train CHANGE PassengerId PassengerId INT(11) NOT NULL PRIMARY KEY;
ALTER TABLE titanic_train CHANGE Survived Survived INT(11) NOT NULL;
ALTER TABLE titanic_train CHANGE Pclass Pclass INT(11) NOT NULL;
ALTER TABLE titanic_train CHANGE Sibsp Sibsp INT(11) NOT NULL;
ALTER TABLE titanic_train CHANGE Age Age DECIMAL(5,2) NOT NULL;
ALTER TABLE titanic_train CHANGE Fare Fare DECIMAL(7,4) NOT NULL;
然后我們將其余的字段(除了 Cabin)都進行 NOT NULL,這樣在后續(xù)進行數(shù)據(jù)插入或其他操作的時候,即使發(fā)現(xiàn)數(shù)據(jù)異常,也可以對字段進行約束規(guī)范。
在全面性這個檢查階段里,除了字段類型定義需要修改以外,我們沒有發(fā)現(xiàn)其他問題。
然后我們來檢查下合法性及唯一性。合法性就是要檢查數(shù)據(jù)內(nèi)容、大小等是否合法,這里不存在數(shù)據(jù)合法性問題。
針對數(shù)據(jù)是否存在重復的情況,我們剛才對 PassengerId 字段類型進行更新的時候設置為了主鍵,并沒有發(fā)現(xiàn)異常,證明數(shù)據(jù)是沒有重復的。
對清洗之后的數(shù)據(jù)進行可視化
我們之前講到過如何通過 Excel 來導入 MySQL 中的數(shù)據(jù),以及如何使用 Excel 來進行數(shù)據(jù)透視表和數(shù)據(jù)透視圖的呈現(xiàn)。
這里我們使用 MySQL For Excel 插件來進行操作,在操作之前有兩個工具需要安裝。
首先是 mysql-for-excel,點擊這里進行下載;然后是 mysql-connector-odbc,點擊這里進行下載。
安裝好之后,我們新建一個空的 excel 文件,打開這個文件,在數(shù)據(jù)選項中可以找到“MySQL for Excel”按鈕,點擊進入,然后輸入密碼連接 MySQL 數(shù)據(jù)庫。
然后選擇我們的數(shù)據(jù)庫以及數(shù)據(jù)表名稱,在下面可以找到 Import MySQL Data 按鈕,選中后將數(shù)據(jù)表導入到 Excel 文件中。
在“插入”選項中找到“數(shù)據(jù)透視圖”,這里我們選中 Survived、Sex 和 Embarked 字段,然后將 Survive 字段放到圖例(系列)欄中,將 Sex 字段放到求和值欄中,可以看到呈現(xiàn)出如下的數(shù)據(jù)透視表:
從這個透視表中你可以清晰地了解到用戶生存情況(Survived)與 Embarked 字段的關系,當然你也可以通過數(shù)據(jù)透視圖進行其他字段之間關系的探索。