数据整理和导入实战

一. 问题背景

参加工作以来我一直从事的是服务器后端的开发工作,做过娱乐直播也做过手游开发。但在我内心深处一直有一个声音告诉我:机器学习才是我真正的兴趣所在。俗话说“少壮不努力,老大徒伤悲”。我希望自己能利用8小时以外的时间为自己的人生创造某种可能。所以我把晚上和周末的时间利用起来,报了Udacity的在线教育课程。作为“数据分析”纳米学位的学员,最近一段时间一直在接触有关数据提取,清洗和质量评估有关的知识。一次偶然的机会,还真就在实际工作中用上了!如果不是因为听过课,我还真不知道处理这样的工作任务应该注意什么问题。所学的课程内容和自己在实际工作中接触到的问题契合度相当高,于是我决定把这次解决问题的过程记录下来,作为自己加入Udacity优等生班要分享的第一篇博客。

最近一段时间以来我一直在负责一个云平台的开发和维护工作,这个平台以API的形式提供与小区物业和房屋租赁有关的云服务。比如房源的录入,租房,支付定金和租金等等。用户可以通过手机App登录然后访问这些服务。现在有一些用户是云平台开发之前就存在了的,大概有几百个,他们的信息需要导入到这个系统中。具体包含如下几类信息:

  • 姓名
  • 性别
  • 公寓名称
  • 房间号
  • 租期
  • 起始时间
  • 到期时间
  • 付租方式
  • 已付租金
  • 已付租数
  • 定金
  • 押金
  • 身份证号
  • 手机号

这些信息以excel表格的形式提供,是人工编制的。要做的工作就是将每个用户的信息拆分成四类数据放入MongoDB数据中。首先是用户数据,用户数据代表的是登录账户,包含用户名(手机号),密码等信息;其次是实名认证数据,比如住址,真实姓名,身份证号,性别等等;然后是租约数据,表示用户当前租赁信息,比如公寓名称,房间号,租期起止时间等等;最后是订单数据,表示用户已经支付和还未支付的定金,租金等信息。

只要是人编辑录入的信息,就有可能出错。所以在清洗,加工和导入数据之前,必须要对数据的质量进行评估,发现有问题的数据。特别是租约数据和订单数据,它们分别代表一个业务系统中必不可少的两类数据,前者代表状态,后者代表流水。流水数据必须要和状态数据对应起来,不能有差错。特别是如果后期要进行用户行为分析的话,录入的数据一定要是正确的,否则会得出错误的结论,进而导致错误的商业决策。所以数据整理和清洗工作绝对是数据分析中极其重要的一个环节。

二. 数据整理

第一步要做的工作是将excel表格中的数据用Python语言读取出来。如果数据一直存放在电子表格中,那就只能靠人工检查数据的正确性,如果数据量很大的话这是不现实的。只要能将数据读出来,我们就能写代码通过程序去做验证,省事又省力,还能保证较高的正确率。

1. 数据的整理和加工

Python语言第三方库xlrd就是专门用来读取excel电子表格的,它提供了强大的功能实现对excel表格的各种操作,具体请参考官方文档。首先来看看如何通过Python语言读取excel表格的数据:

def load_data_excel(excel):
    # load_data_excel loads data from excel into a list of dicts
    workbook = xlrd.open_workbook(excel)
    sheet = workbook.sheet_by_index(0)
    headers = sheet.row_values(0)
    data = []
    for i in range(sheet.nrows):
        if i == 0:
            continue
        row = sheet.row_values(i)
        entry = {}
        for j in range(len(row)):
            if headers[j] in [u'起始时间', u'到期时间']:
                # entry[headers[j]] = xlrd.xldate.xldate_as_datetime(float(row[j]), 0).replace(tzinfo=tz.tzlocal())
                entry[headers[j]] = datetime.datetime.strptime(row[j], '%Y-%M-%d').replace(tzinfo=tz.tzlocal())
            elif headers[j] == u'手机号':
                entry[headers[j]] = str(int(row[j]))
            elif headers[j] == u'租期':
                if row[j][-1] == u'年':
                    entry[headers[j]] = int(row[j][0]) * 12
                elif row[j][-1] == u'月':
                    entry[headers[j]] = int(row[j][0])
            elif headers[j] in [u'公寓名称', u'房间号']:
                entry[headers[j]] = row[j].strip()
            elif headers[j] == u'性别':
                entry[headers[j]] = 'male' if row[j].strip() == u'男' else 'female'
            elif headers[j] == u'付租方式':
                d = {
                    u'月付': 1,
                    u'季付': 3,
                    u'半年付': 6,
                    u'年付': 12,
                }
                entry[headers[j]] = d[row[j]]
            elif headers[j] == u'定金':
                entry[headers[j]] = 0 if row[j] == '' else int(row[j] * 100)  # convert to fen
            elif headers[j] in [u'押金', u'已付租金']:
                entry[headers[j]] = int(row[j] * 100)  # convert to fen
            else:
                entry[headers[j]] = row[j]
        data.append(entry)
    return data

load_excel_data()这个函数读取excel中的数据,然后将这些数据转换成一个列表,列表中的每个元素是一个字典,代表一个用户的各项数据,例如:

[
  {
    u'姓名': '张三',
    u'性别': '男',
    u'公寓名称': 'XX公寓',
    u'房间号': 'YY房间',
    u'租期': 12,
    u'起始时间': datetime('2017-05-30'),
    u'到期时间': datetime('2018-05-29'),
    u'付租方式': 1,
    u'已付租金': 80000,
    u'已付租数': 1,
    u'定金': 20000,
    u'押金': 112000,
    u'身份证号': 'XXX',
    u'手机号': 'ZZZ',
  },
  ...
]

其中部分数据需要加工处理,比如涉及到日期的数据,要转换成Python的datetime.datetime;性别数据要使用英文,转换成male和female;租期要转换为以月为单位的整数;为了后面审核数据质量的需要,公寓名称和房间号要注意去掉多余的空格;所有涉及到金额的数据要转换成以分为单位的数字。

2. 数据的审核

有关数据质量的评估,Udacity课程介绍了五个评估指标:

  1. 有效性(validity):数据模式符合定义或满足其他约束
  2. 精确性(accuracy):与权威数据相符合
  3. 完整性(completeness):是全部记录吗?
  4. 一致性(consistency):多个数据之间保持一致
  5. 统一性(uniformity):是否使用相同单位

关于用户数据,至少可以对其有效性,精确性和一致性进行审核。比如性别数据取值只能是male或者female,否则的话原始数据一定存在错误;“起始时间”,“到期时间”和“租期”之间一定要满足一致性,也就是说“起始时间+租期=到期时间”;特别重要的是,一定要检查公寓名称和房间名称在数据库中是否已存在(精确性)。如果任何一条数据不满足以上要求,都不能进行导入,必须修正。

三. 数据导入

只要数据经过了审核,导入工作就相对来说比较简单了,直接按照各不同collection的schema进行导入即可。

1. 用户数据

def store_users(data, local_db):
    users = []
    for d in data:
        user = {
            'createdAt': datetime.datetime.utcnow(),
            'avartarID': '',
            'nickname': d[u'手机号'],
            'phone': d[u'手机号'],
        }
        users.append(user)
    local_db.users.insert_many(users)

2. 实名认证数据

def store_real_names(data, local_db):
    # find all users' _id and phone, make them a dict whose keys are phones
    users = {}
    for u in local_db.users.find({}, {"_id": 1, "phone": 1}):
        users[u['phone']] = u

    real_names = []
    for d in data:
        real_name = {
            'createdAt': datetime.datetime.utcnow(),
            'userID': users[d[u'手机号']]['_id'],
            'name': d[u'姓名'],
            'gender': d[u'性别'],
            'certNumber': d[u'身份证号'],
        }
        real_names.append(real_name)
    local_db.real_name_record.insert_many(real_names)

3. 租约数据

def store_rents(data, local_db):
    utc_zone = tz.gettz('UTC')

    ...
    online_db = client.service_cloud

    # find all users' _id and phone, make them a dict whose keys are phones
    users = {}
    for u in local_db.users.find({}, {"_id": 1, "phone": 1}):
        users[u['phone']] = u

    rents = []
    for d in data:
        apartment = online_db.apartments.find_one({'name': d[u'公寓名称']})
        rooms = online_db.apartments.find_one({"name": d[u'公寓名称'], "rooms.name": d[u'房间号']}, {"rooms.name": 1, "rooms._id": 1})
        for r in rooms['rooms']:
            if r['name'] == d[u'房间号']:
                roomID = r['_id']
                break
        rent = {
            'name': '{}-{}'.format(d[u'公寓名称'], d[u'房间号']),
            'createAt': datetime.datetime.utcnow(),
            'apartmentID': apartment['_id'],
            'roomID': roomID,
            'renterID': users[d[u'手机号']]['_id'],
            'rentBegin': d[u'起始时间'].astimezone(utc_zone),
            'rentEnd': d[u'到期时间'].astimezone(utc_zone),
        }
        rents.append(rent)
    local_db.rent.insert_many(rents)

4. 订单数据

def store_orders(data, local_db):
    utc_zone = tz.gettz('UTC')
    # find all users' _id and phone, make them a dict whose keys are phones
    users = {}
    for u in local_db.users.find({}, {'_id': 1, 'phone': 1}):
        users[u['phone']] = u
    # find all rents' _id and renterID, make them a dict whose keys are renterIDs
    rents = {}
    for r in local_db.rent.find({}, {'_id': 1, 'renterID': 1, 'apartmentID': 1, 'roomID': 1, 'ownerID': 1}):
        rents[r['renterID']] = r

    orders = []
    for d in data:
        renter_id = users[d[u'手机号']]['_id']
        rent_id = rents[renter_id]['_id']
        if d[u'定金'] > 0:
            order = {
                'name': '{}-{} reserved'.format(d[u'公寓名称'], d[u'房间号']),
                'createdAt': datetime.datetime.utcnow(),
                'rentID': rent_id,
                'apartmentID': rents[renter_id]['apartmentID'],
                'roomID': rents[renter_id]['roomID'],
                'ownerID': rents[renter_id]['ownerID'],
                'renterID': renter_id,
                'rentBegin': d[u'起始时间'].astimezone(utc_zone),
                'rentEnd': d[u'起始时间'].astimezone(utc_zone),
                'rentStyle': d[u'付租方式'],
                'fee': d[u'定金'],
            }
            orders.append(order)
        time_pairs = calculate_time_pairs(d[u'起始时间'], d[u'到期时间'], d[u'付租方式'], d[u'租期'])
        for i in range(len(time_pairs)):
            order = {
                'name': '{}-{} signed'.format(d[u'公寓名称'], d[u'房间号']),
                'createdAt': datetime.datetime.utcnow(),
                'rentID': rent_id,
                'apartmentID': rents[renter_id]['apartmentID'],
                'roomID': rents[renter_id]['roomID'],
                'ownerID': rents[renter_id]['ownerID'],
                'renterID': renter_id,
                'rentBegin': time_pairs[i][0].astimezone(utc_zone),
                'rentEnd': time_pairs[i][1].astimezone(utc_zone),
                'rentStyle': d[u'付租方式'],
                'fee': d[u'定金'],
            }
            if i == 0:
                order['depositFee'] = d[u'押金']

            orders.append(order)
    local_db.orders.insert_many(orders)

四. 后记

数据分析工作涉及到的面比较广,据说经常跟数据打交道的工程师,一大半时间都花在了数据整理上,这个过程虽然繁琐,不如构建模型那样酷炫吸引人,但它是很重要的一个环节,一定要掌握好。

你可能感兴趣的:(数据整理和导入实战)