记一次简单的数据去重
探索数据
拿到数据的第一件事很自然的就是探索,看看数据的组成方式,所包含的信息。
这次拿到的是一份txt和一份csv,存储形式都是类似excel的表格形式。
数据清洗
换行符
先使用最熟悉的node.js,使用linebylin模块尝试分行读取txt中的数据,结果很出乎意料。读出的数据与txt中显示的不符,似乎一下子就跳到了中间的数据,忽略了最开始的数据。
其实原因很简单,只不过因为缺乏经验而没有第一时间注意到这个问题。
原因是txt的换行符既不是linux下的\n
,也不是windows下的\r\n
,而是使用了老mac os的\r
。这个在notepad中就有显示的信息,居然花了近半个小时才注意到。不得不说灯下黑。
最常使用的换行符其实就只有两种,LF(Line Feed, ASCII 10, \n)或者CR-LF(Carriage Return,ASCII 13, \r)。
在node的linebyline模块中,它是这样判断的:
1 | .on('data', function(data) { |
emit的条件是碰到\n,不管是linux还是windows,这段代码都能正常工作。但如果文件中只有\r,那么很显然会失效。
解决办法是修改linebyline模块,或者使用兼容性更好的readline。
\x00
csv格式的文件读取,更习惯用python。
一开始很顺利的读出数据,但却在某一行抛出_csv.Error: line contains NULL byte
异常。
好在问题很常见,搜索一下就能发现有相当多人遇到同样的问题。
解决方法也很简单,在将数据交给csv reader之前,先过滤掉NULL byte
。
1 | with open(CSV_FILENAME, 'r', encoding='utf8') as f: |
在检查过程中,还发现存在”\x01”这样的数据,都属于不正常数据,需要修正或剔除。
数据一致性和完整性
txt和csv都能正常读取,但数据清洗并没完。还需要检查数据完整性和一致性。
对比发现,csv读取到的条数与文件行数不符。
检查发现原因是存在不正常换行,如
1 | "101","a","0","2019-03-31" |
对于这样的数据,可以另外写段脚本进行修正。
数据去重
数据读取完成后,开始进行数据去重。
去重的简单思路是先写入数据库,然后通过数据库命令进行去重。
数据去重在逻辑上无非两部分,一是判断是否重复,二是去除。
mongodb
如使用mongodb,有两种方法:
使用aggregate聚合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36var duplicates = [];
db.collectionName.aggregate([
{
$match: {
name: { "$ne": '' } // discard selection criteria
}
},
{
$group: {
_id: { name: "$name" }, // can be grouped on multiple properties
dups: { "$addToSet": "$_id" },
count: { "$sum": 1 }
}
},
{
$match: {
count: { "$gt": 1 } // Duplicates considered as count greater than one
}
}
],
{ allowDiskUse: true } // For faster processing if set is larger
) // You can display result until this and check duplicates
.forEach(function (doc) {
doc.dups.shift(); // First element skipped for deleting
doc.dups.forEach(function (dupId) {
duplicates.push(dupId); // Getting all duplicate ids
}
)
})
// If you want to Check all "_id" which you are deleting else print statement not needed
printjson(duplicates);
// Remove all duplicates in one go
db.collectionName.remove({ _id: { $in: duplicates } })使用mapreduce
1
2
3
4
5
6
7
8
9
10
11const o = {}
o.map = function () {
emit(this.phone, 1)
}
o.reduce = function(k, vals) {
return Array.sum(vals)
}
o.out = {
replace: 'mapreduce_demo'
}
const result = await Item.mapReduce(o)mapreduce完成后,在生成的新的collection是搜索出现次数超过1次的,再保留第一个,删除其它。
mysql
如使用mysql,也有两种方式,具体请参考这篇文章
使用
DELETE JOIN
使用中间数据库
超大数据
使用数据来进行去重操作似乎是个常规选项,但当遇上超大数据时,就显得有点为了吃匹萨自己做个烤箱了。
- 首先是内存问题
如果是node,可以使用这样的命令来启动:
1 | node --max-old-space-size=8192 app.js |
如果是mongodb,可以加上使用硬盘缓冲的选项:
1 | const result = await model.aggregate([{ |
- 其次是速度
如果数据量过千万,即使是一个简单的分组查询动作,也要几十分钟才能完成。
结局
最终,写了个简单的脚本,一边从源文件读取并加工数据,另一边输出到目标文件,放弃使用数据库。
简单的一个去重数据操作,最终花耗了超过4个小时的时间。
虽然曾经上过Udacity的数据分析纳米课程,但一旦真正上手,还是问题不断,感叹学海无涯。
结语
数据去重这步操作最好还是在入库前做,是将一个耗时巨长的操作分散在每一时刻,还是忙时尽量快,闲时再做长时间操作。
或者再多思考下到底是出于什么目的进行数据去重。有索引的帮助,查询其实很快。