什麼是數據清洗
數據清洗是指在數據處理過程中對原始數據進行篩選、轉換和修正,以確保數據的準確性、一致性和完整性的過程。它是數據預處理的一部分,旨在處理和糾正可能存在的錯誤、缺失值、異常值和不一致性等數據質量問題。
爲什麼要數據清洗
Excel在數據採集場景中非常常用。作爲一款電子表格軟件,它提供了豐富的功能和易用的界面,使其成爲大部分人首選的數據採集工具之一。
而在數據採集的過程中,因爲採集渠道多樣,數據格式也多種多樣,從而會出現部分數據的丟失和不準確的情況,因此爲了處理掉這些 “垃圾”數據,需要對數據進行清洗。
哪些數據需要進行清洗
通常在這幾種情況下需要進行數據清洗。
1.缺失數據處理:數據在採集或遷移的過程中,出現數據的遺漏。
2.錯誤數據判斷:數據在採集或遷移的過程中與原數據不一致。
3.重複數據處理:一條數據重複出現多次。
4.數據格式轉換:數據在採集或遷移的過程中出現了亂碼。
數據清洗都需要做些什麼
下面讓我們看一下數據清洗都會涉及的處理步驟:
- 分析需求:通過對數據原本的格式,特徵進行分析,規劃數據清洗的業務規則及需求。
- 打開文件:把Excel文件打開,通常這一步需要依賴Excel組件庫,比如使用POI,GcExcel,EasyExcel等。
- 讀取數據:通過Excel庫中的API,讀取需要操作的數據,這裏比較一下三個產品的特點:
GcExcel提供了**IRange(區域)**的概念,可以通過API快速的讀取有數據的區域。POI和EasyExcel(POJO註解)則需要遍歷每一個單元格。
根據業務需求,可以選擇使用API,也可以選擇遍歷所有單元格。
- 數據清洗:根據需求,結合Excel庫的API,進行數據清洗。如:用默認值填寫缺失數據的單元格,刪除整個空行,刪除重複數據,把不符合範圍的數據刪除掉,或者把日期數字的格式統一起來,等等。
- 數據持續化:把處理好的數據回存至Excel文件,或者保存在數據庫中或者CSV文件中。
如何使用GcExcel實現數據清洗
GcExcel有IRange的API,可以讓數據清洗時代碼寫的更簡單,因此下面我們選擇用GcExcel的代碼爲例解決上面提到的幾個場景。
基於IRange,GcExcel提供一些快速查找的API,如下(在文件中查找特殊單元格):
Workbook workbook = new Workbook();
workbook.open("data.xlsx");
IWorksheet sheet = workbook.getActiveSheet();
//尋找sheet中,使用到的所有單元格
IRange usedRange = sheet.getUsedRange();
//尋找所有的公式單元格
IRange allFormulas = sheet.getCells().specialCells(SpecialCellType.Formulas);
//尋找所有的常量單元格
IRange allConstants = sheet.getCells().specialCells(SpecialCellType.Constants);
雖然GcExcel提供了API,但數據清洗時,也可能有需求需要遍歷,下面是GcExcel遍歷單元格的代碼,後面我們就有可能會用到。
public void FetchCellBasedOnRange(IRange area) {
for (int column = 0; column < area.getColumns().getCount(); column++) {
for (int row = 0; row < area.getRows().getCount(); row++) {
IRange cell = area.get(row, column);
//獲取單元格的值
Object val = cell.getValue();
}
}
}
場景一:缺失數據處理
假如有一個Excel的數據,現在藍色的格子是空的,我們需要對不同列下的藍色格子做不同的處理,例如姓名的空格子替換爲匿名,年齡替換成-1,身份證號填寫N/A,住址填寫爲未知。
代碼如下:
public void replaceBlankCell() {
Workbook workbook = new Workbook();
workbook.open("resources/BlankCells.xlsx");
IWorksheet sheet = workbook.getActiveSheet();
IRange blankRanges = sheet.getCells().specialCells(SpecialCellType.Blanks);
for (IRange area : blankRanges.getAreas()) {
for (int column = 0; column < area.getColumns().getCount(); column++) {
for (int row = 0; row < area.getRows().getCount(); row++) {
IRange cell = area.get(row, column);
Object defaultVal = getDefaultVal(cell.getColumn());
cell.setValue(defaultVal);
}
}
}
workbook.save("Result.xlsx");
}
private Object getDefaultVal(int column) {
switch (column) {
case 1:
return "匿名";
case 2:
return -1;
case 3:
return "N/A";
case 4:
return "未知";
}
return null;
}
要注意的是,sheet.getCells().specialCells(SpecialCellType.Blanks);返回的區域是多個,因此我們需要遍歷通過遍歷areas來對每一個區域進行遍歷。
cell.getColumn()可以獲取到當前格子對應到sheet上的第幾列,因此獲取默認值時使用該方法。
場景二:錯誤數據判斷
錯誤數據的判斷,與缺失數據處理相似,通過制定一些規則找出錯誤的值,對於錯誤值可以通過修改背景顏色進行高亮處理,用來提示,進行人工修改。
通常規則可以有兩種選擇:
- 使用Java直接編寫判斷邏輯。
- 使用數據校驗(Datavalidation)功能,或者條件格式(ConditionFormat)來進行處理。
假如我們有下面一份數據,其中聯繫電話中有兩條是錯誤的,位數不夠,貨物ID有兩條是錯誤的,貨物ID不能小於0,我們需要把他們找出來。
public void MarkErrorData(){
Workbook workbook = new Workbook();
workbook.open("resources/ErrorData.xlsx");
IWorksheet sheet = workbook.getActiveSheet();
IRange telRange = sheet.getRange("C2:D5");
for (int r=0; r<telRange.getRows().getCount();r++){
IRange cell = telRange.get(r,0);
if(cell.getValue().toString().length() != 11){
cell.getInterior().setColor(Color.GetOrangeRed());
}
}
IFormatCondition condition =
(IFormatCondition) sheet.getRange("D2:D5").getFormatConditions().
add(FormatConditionType.CellValue, FormatConditionOperator.Less, 1, null);
condition.getInterior().setColor(Color.GetOrangeRed());
workbook.save("Result.xlsx");
}
在代碼中,我們對C2:C5進行遍歷,判斷字符串長度,然後對長度不合法的數據進行顏色標記。
而對於貨物,設置了條件格式,可以讓Excel在打開時,自行標記錯誤的數據。
場景三:重複數據處理
假如我們有一份數據,其中有一些行數據是完全重複的,我們需要刪除這些行,如圖所示。
public void RemoveDuplicateData() {
Workbook workbook = new Workbook();
workbook.open("resources/DuplicateRows.xlsx");
IWorksheet sheet = workbook.getActiveSheet();
IRange usedRange = sheet.getUsedRange();
HashSet<String> set = new HashSet<>();
Stack<IRange> deleteRows = new Stack<>();
for (int r = 1; r < usedRange.getRows().getCount(); r++) {
IRange row = usedRange.getRows().get(r);
StringBuilder rowKey = new StringBuilder();
for (int c = 0; c < row.getColumns().getCount(); c++) {
rowKey.append(usedRange.get(r, c).getValue().toString());
}
if (set.contains(rowKey.toString())) {
deleteRows.push(row);
} else {
set.add(rowKey.toString());
}
}
while (!deleteRows.isEmpty()) {
deleteRows.pop().delete();
}
workbook.save("Result.xlsx");
}
可以看到,重複的行被移除掉了。代碼中用到了哈希set和棧,其中我們用哈希set來查找重複的行。
另外使用棧來記錄需要被刪除的行,這裏特地用了棧,而沒有使用隊列,數組或者ArraryList的原因是,GcExcel在刪除一行時,會讓這行下面的數據上移,這樣我們之前記錄的行就會便宜,導致刪除錯誤的行。
簡而言之,我們需要從下向上刪除,來避免行位移導致刪錯的問題。
場景四:數據格式轉換
例如我們有一些日期數據,或者貨幣數據,在數據採集時數據格式不同,我們需要分別統一訂單日期,金額的格式。
代碼如下:
public void unifyFormat() {
Workbook workbook = new Workbook();
workbook.open("resources/DifferentFormat.xlsx");
IWorksheet sheet = workbook.getActiveSheet();
IRange usedRange = sheet.getUsedRange();
for (int row = 1; row < usedRange.getRows().getCount(); row++) {
IRange dateCell = usedRange.get(row, 1);
IRange priceCell = usedRange.get(row, 2);
dateCell.setValue(parseDate(dateCell.getValue()));
dateCell.setNumberFormat("yyyy年MM月dd日");
priceCell.setValue(parsePrice(priceCell.getValue()));
priceCell.setNumberFormat("¥0.00");
}
sheet.getRange("B1").setNumberFormat("");
workbook.save("Result.xlsx");
}
private Double parsePrice(Object value) {
if (value == null)
return null;
String val = value.toString();
if (val.startsWith("$") || val.startsWith("¥")) {
val = val.substring(1);
}
return Double.parseDouble(val);
}
private LocalDateTime parseDate(Object value) {
if (value == null)
return null;
if (value instanceof LocalDateTime) {
return (LocalDateTime) value;
}
DateTimeFormatter[] formatters = {
DateTimeFormatter.ofPattern("yyyy/MM/dd"),
DateTimeFormatter.ofPattern("MM-dd-yyyy"),
DateTimeFormatter.ofPattern("yyyy年MM月dd日"),
DateTimeFormatter.ofPattern("yyyy.MM.dd")
};
LocalDate datetime = null;
for (DateTimeFormatter formatter : formatters) {
try {
datetime = LocalDate.parse(value.toString(), formatter);
break;
} catch (DateTimeParseException e) {
e.printStackTrace();
}
}
assert datetime != null;
return datetime.atStartOfDay();
}
需要注意的是在處理日期和金額時,由於value的類型不太一致,需要寫特定的方法來進行處理。
<br>
擴展鏈接: