用Python实现不一样数据源的对象匹配【实验记录】

任务简介:

现有两份针对同一主题的数据,可是在人物的属性名称及格式上有所不一样,须要对两份数据进行匹配来肯定是同一我的。html

匹配属性:

1 人名
2 出生日期
3 国籍python

原始数据举例

1 数据源1(如下简称S1)面试

id name date of birth nationality
282577 lukas-klunter 1996-05-26 Germany

nationality or place of birth?
应该仍是用nationality,不过度析数据过程当中发现存在诸如' Morocco|Germany '的字段,考虑用分隔后,多国籍分别分组正则表达式

2 数据源2 (如下简称S2)安全

player_id first_name last_name date_of_birth country
18679 Lukas Klünter 26/05/1996 Germany

需求

  • 将两个不一样数据源中的相匹配的记录关联起来markdown

问题分析

  • 匹配的格式问题,包括:app

    • 字段合并,好比S1中的name对应S2中的first_name+last_nameide

    • 语言的格式问题,就人名而言,此例中S1中的klunter与S2中的Klünter就有区别函数

    • 此外,在浏览S2记录时发现两边记录中的first_name和last_name以及name的格式有多种post

    • 日期格式问题以及大小写格式问题

  • 效率问题,S1数据量达到40+W,S2大约是4200+,如何进行二者的匹配是个问题


思路

  • 格式匹配

    • 字段合并的话应该能够将S2中的first_name与last_name用'-'链接,进一步分析数据发现,S1中的name与对应的S2中的first_name及last_name之间并没有规律,考虑匹配的话,一种思路是在构建多级字典后由于范围已经缩小不少考虑直接提取first_name进行匹配,另外一种思路是用S2中的first_name+last_name来contains S1中的name进行匹配,初步考虑第二种思路(更准确)

    • 语言格式化,通过google知,德语字母源于拉丁字母,且有四个变形字母,从stackexchange上我发现可经过Latin->ASCII进行初步转换,而后进行大小写的统一便可进行转换

    • 日期上应该修改格式便可

  • 效率方面,设想是构造多级字典实现数据查找范围的缩小,以期提升效率,具体而言分如下几步:

    • 在对S1的csv文件构建多级字典之后,先从S2中取出country字段对应的值,与S1中nationality对应的值经处理后进行匹配从而缩小范围

    • 再从S2中取date_of_birth字段的值与S1同一nationality下的date of birth(处理事后)进行匹配进一步缩小范围

    • 最后是从S2中取出first_namelast_name的值拼接处理后与S1中同一name下的值进行处理匹配,将成功匹配的S1中的记录输出到新的csv文件

实现

  • 格式匹配

    • 语言格式化,调用unihandecode中的unidecode方法来对Latin字符进行处理转化为ASCII字符,代码实现以下

    # _*_ coding: utf-8 _*_
    import unihandecode
    print(unihandecode.unidecode(u"Lukas;Klünter"))
    • 日期格式转换
      调用time包而后直接转换输出格式便可

    import time
    
    t='1996-05-26'
    timeArray=time.strptime(t,"%Y-%m-%d")
    print time.strftime("%d/%m/%Y",timeArray)
    • 国籍字段的格式化,前面提到进一步分析数据的过程当中我发现S1中存在诸如' Morocco|Germany '(多国籍)的现象,而对应的S2中的同一球员却只拥有一个国籍做为Country的值,因此这里咱们的想法是对创建好的多级字典进行清洗,将值为多国籍的项的键进行分割而后添加到已有重名国籍字段或是新建一个当前字典中没有的国籍字段,且二者的值相同
      经过我的的思考加上stackoverflow上的提问,找到了解决方法,代码以下:

    def country_format(dictionary):
       new_dict = {}
       for item in dictionary:
           # print item
           for index in str.split(item, '|'):
               if not index in new_dict:  # if not exists, create, and then insert or insert directly
                   new_dict[index] = {}
               new_dict[index].update(dictionary[item])
       return new_dict
    • 姓名字段匹配

  • 构建多级字典
    思路是先以两个数据源的国籍为键构造字典将数据分组,缩小范围,而后在每一个一级字典里再次以出生日期为键构造字典(这里有个优先级的问题,是以出生日期仍是国籍做为第一级字典比较好?以国籍的话则分组相对少,但每组对应的元素多,而以日期来分的话则是分组较多,每组元素相对少),最后再以名称进行匹配,参照着stackoverflow上的方法,假设我有以下数据(imitate.csv)

    id,name,date of birth,nationality
    227,alexander-zickler,1974-02-28,Germany,
    229,abdelaziz-ahanfouf,1978-01-14,Morocco|Germany,
    233,perry-brautigam,1963-03-28,Germany,
    232,christian-brand,1972-05-23,Germany,
    455,chen-yang,1974-01-17,China,
    35214,leilei-li,1977-06-30,China,
    35228,yunfei-liu,1978-05-08,China,
    218,paulo-sergio,1969-06-02,Brazil,
    1263,marcio-amoroso,1974-07-05,Brazil,

通过处理后输出结果以下

D:\Anaconda\python.exe E:/PythonWorkspace/PlayerTransfer/nested_dict.py
{'Brazil': {'1969-06-02': {'id': '218', 'name': 'paulo-sergio'}, '1974-07-05': {'id': '1263', 'name': 'marcio-amoroso'}},
'Germany': {'1972-05-23': {'id': '232', 'name': 'christian-brand'}, '1974-02-28': {'id': '227', 'name': 'alexander-zickler'}, '1963-03-28': {'id': '233', 'name': 'perry-brautigam'}}, 
'China': {'1978-05-08': {'id': '35228', 'name': 'yunfei-liu'}, '1977-06-30': {'id': '35214', 'name': 'leilei-li'}, '1974-01-17': {'id': '455', 'name': 'chen-yang'}},
'Morocco|Germany': {'1978-01-14': {'id': '229', 'name': 'abdelaziz-ahanfouf'}}}

Process finished with exit code 0

对应的代码以下

import csv
from collections import defaultdict

def build_dict(source_file):
  projects=defaultdict(dict)
  #headers=['id','name','date of birth','nationality']
  with open(source_file,'rb') as fp:
      reader=csv.DictReader(fp,dialect='excel',skipinitialspace=True)   #若是原始csv文件里不含标题时,须要添加fieldnames=headers以及上述注释掉的headers
      for rowdict in reader:
          if None in rowdict:
              del rowdict[None]
          nationality=rowdict.pop("nationality")
          date_of_birth=rowdict.pop("date of birth")
          projects[nationality][date_of_birth]=rowdict
  return dict(projects)

source_file='imitate.csv'
print build_dict(source_file)

关于这部分代码的理解须要学习Python的dictionary类型相关章节以及关于内置包csv的文档阅读

也有另外一种思路,直接嵌套着构建,代码以下,参考

def build_dict(source_file):
    new_dict = {}
    with open(source_file, 'rb')as csv_file:
        data = csv.DictReader(csv_file, delimiter=",")
        for row in data:
            item = new_dict.get(row['nationality'], dict())
            item[row['date of birth']] = {k: row[k] for k in ('id', 'name')}
            new_dict[row['nationality']] = item
    return new_dict


source_file='imitate.csv'

playerInfo = build_dict(source_file)
print playerInfo

输出结果以下

{'Brazil': {'1969-06-02': {'id': '218', 'name': 'paulo-sergio'}, 
            '1974-07-05': {'id': '1263', 'name': 'marcio-amoroso'}}, 
'Germany': {'1972-05-23': {'id': '232', 'name': 'christian-brand'}, 
            '1974-02-28': {'id': '227', 'name': 'alexander-zickler'}, 
            '1963-03-28': {'id': '233', 'name': 'perry-brautigam'}}, 
'China': {'1978-05-08': {'id': '35228', 'name': 'yunfei-liu'}, 
        '1977-06-30': {'id': '35214', 'name': 'leilei-li'}, 
        '1974-01-17': {'id': '455', 'name': 'chen-yang'}}, 
'Morocco|Germany': 
        {'1978-01-14': {'id': '229', 'name': 'abdelaziz-ahanfouf'}}}
  • 上述结果通过以前提到的国籍的分离操做后获得以下输出结果

{'Brazil': {'1969-06-02': {'id': '218', 'name': 'paulo-sergio'}, 
            '1974-07-05': {'id': '1263', 'name': 'marcio-amoroso'}}, 
'Germany': {'1972-05-23': {'id': '232', 'name': 'christian-brand'}, 
            '1974-02-28': {'id': '227', 'name': 'alexander-zickler'}, 
        '1963-03-28': {'id': '233', 'name': 'perry-brautigam'}, 
        '1978-01-14': {'id': '229', 'name': 'abdelaziz-ahanfouf'}}, 
'China': {'1978-05-08': {'id': '35228', 'name': 'yunfei-liu'}, 
          '1977-06-30': {'id': '35214', 'name': 'leilei-li'}, 
       '1974-01-17': {'id': '455', 'name': 'chen-yang'}}, 
'Morocco': {'1978-01-14': {'id': '229', 'name': 'abdelaziz-ahanfouf'}}}

能够看到以前的' Morocco|Germany '已经成功的分离为两个字段,Germany是添加到已有的字典中,而Morocco则是新创建了一个字典

  • csv文件格式规范的处理
    再进一步将要对两个数据源进行匹配工做以前,我准备开始读入S2的数据,这个时候发现S2的csv文件格式有些不同,其header是以,分割,而常规的row则是以;分割,为了解决这个问题,采用的方法是先读取header部分做为fieldnames,而后对rows经过以;为分隔符进行csv文件的读取以及逐行转化为字典,代码以下:

    with open('test.csv') as src_csv:
       reader=csv.reader(src_csv,delimiter=',')
       fieldnames=next(reader)
    
       reader=csv.DictReader(src_csv,fieldnames=fieldnames,delimiter=';')
    
       for row in reader:
           print row

参考

  • 初步测试在根据S1构建的多级字典中查找S2的键值对应的(S1中的)值,测试的数据集以下:

    • S2中数据test.csv

players.first_name players.last_name players.vis_name players.player_id players.date_of_birth players.role players.team players.country
Dusan Svento Svento 8658 01/08/1985 Midfielder 1. FC Köln Slovakia
Markus Henriksen Henriksen 7687 25/07/1992 Midfielder AZ Norway
Lukas Klünter Klünter 18679 26/05/1996 Defender 1. FC Köln Germany
Roque Santa Cruz Santa Cruz 547 16/08/1981 Forward Málaga Paraguay
Benjamin Kirsten Kirsten 19078 02/06/1987 Goalkeeper N.E.C. Germany
  • S1中数据test2.csv (小插曲,在用markdown构建表格时出现'|'冲突,参考stackoverflow|替换自己的'|'便可)

id name complete name date of birth place of birth age height nationality position foot player's agent agent id current club club id in the team since contract until outfitter
46415 duje-cop 1990-02-01 Jugoslawien (SFR) 26 1.87 m Croatia Striker - Centre Forward right pharos-sport-agency 1358 malaga-cf 1084 Jul 16. 2015 30.06.2016
34543 dusan-svento 1985-08-01 CSSR 30 1.78 m Slovakia Midfield - Left Wing left stars-amp-friends-international-holding-gmbh 10 1-fc-koln 3 Jul 1. 2014 30.06.2016
122011 markus-henriksen 1992-07-25 Norway 23 1.87 m Norway Midfield - Attacking Midfield right jim-solbakken 1292 az-alkmaar 1090 Aug 31. 2012 30.06.2017
49327 bradley-johnson 1987-04-28 England 29 1.78 m England | United States Midfield - Central Midfield right derby-county 22 Sep 1. 2015 30.06.2019
282577 lukas-klunter 1996-05-26 19 1.87 m Germany Defence - Right-Back right sportstotal 199 1-fc-koln-ii 438 Jul 1. 2015 30.06.2017
215 roque-santa-cruz 1981-08-16 Paraguay 34 1.93 m Paraguay | Spain Striker - Centre Forward right jb-sports2business 1043 malaga-cf 1084 Aug 26. 2015 30.06.2016
29903 benjamin-kirsten 1987-06-02 DDR 28 1.82 m Germany Goalkeeper right l-concept-sports-gmbh 2559 unattached 515 Dec 6. 2015 -

代码以下:

import csv
import date_format
import csv2dict
import Country_Format

# read the dest csv file
dest_file = 'test2.csv'
# create the nested dict
playerInfo = csv2dict.build_dict(dest_file)
# print playerInfo
S1=Country_Format.country_format(playerInfo)
# print S1

with open('test.csv') as src_csv:
    reader=csv.reader(src_csv,delimiter=',')
    fieldnames=next(reader)

    reader=csv.DictReader(src_csv,fieldnames=fieldnames,delimiter=';')

    for row in reader:
        # print row
        print S1[row['players.country']][date_format.date_convert(row['players.date_of_birth'])]['name']

输出结果以下:

dusan-svento
markus-henriksen
lukas-klunter
roque-santa-cruz
benjamin-kirsten

可见如今国籍和出生日期的匹配初步测试没有问题,如今问题集中在名称匹配和处理上,以S1中的jose-gimenez对应S2中的José María;Giménez de Vargas为例,分析知大约须要如下几步:

  • 对S2的操做

    • 抽取first_name和last_name并以空格为分隔符分割后存于列表L2

    • Latin->ASCII

    • 转换为全小写

  • 对S1的操做

    • 抽取name的值并以'-'为分隔符分割后存于列表L1

  • 判断L1是否为L2的子集,如果则认为匹配成功,不然认为失败

实现代码以下:

# _*_ coding: utf-8 _*_
import Latin2ASCII

def name_match(s1,s2):
    l1 = []
    l2 = []

    l1 = str.split(str.lower(Latin2ASCII.ud(s1.decode('utf-8', 'ignore'))), ' ')
    l2 = str.split(s2, '-')

    return set(l2).issubset(set(l1))

这其中遇到以前的名称中拉丁文与字符串的转换问题,最终经过google,结合stackoverflow以及Python文档获得解决

  • 输出S1中匹配记录到csv文件

    • 在匹配成功后,将所匹配的记录所对应于S1中的id集中到一个列表white_list

    • 根据white_list中的id值从S1中进行匹配并导出该id所对应的记录到文件white_list.csv
      代码实现以下:参考

import csv

def csv_match(id_list,input_file,output_file):
    with open(input_file, 'rb') as f:
        reader = csv.DictReader(f)
        rows = [row for row in reader if row['id'] in set(id_list)]

    header = rows[0].keys()
    with open(output_file, 'w') as f:
        f.write(','.join(header))
        f.write('\n')
        for data in rows:
            f.write(",".join(data[h] for h in header))
            f.write('\n')

其中抽取含有white_list中指定id的操做利用了set的子集判断,并最终输出全部符合条件的结果,参考

  • 异常处理
    在这一过程当中遇到很多的异常状况,现小结以下:

    • key不存在的异常,因为以前进行测试时的数据是手工提取的能保证找获得的记录进行的沙盒测试,而对于实际数据在匹配过程当中可能存在字典中无该键值的情形,因此前面所述的

    print S1[row['players.country']][date_format.date_convert(row['players.date_of_birth'])]['name']

    是行不通的,需逐级进行判断

    if nationality in S1:
               if date in S1[nationality]:
                   s2=S1[nationality][date]['name']
    • 进一步的,在进行数据匹配过程当中,出现了日期格式不规范的异常,仔细分析发现是S2中存在诸如 '00/00/0000'以及‘22/00/1996’(月份错误)的错误数据,因此对于前面所写的date_convert函数须要优先进行判断,修改后以下 参考

    def date_convert(t):
       if validate_date(t):
           timeArray = time.strptime(t, '%d/%m/%Y')
           return time.strftime('%Y-%m-%d', timeArray)
       else:
           return '0001-01-01'
    
    def validate_date(d):
       try:
           time.strptime(d,'%d/%m/%Y')
           return True
       except ValueError:
           return False

    在对原始的S1,S2数据源进行测试获得最后的white_list以及id_list,通过统计发现,id_list的数目是2406而white_list的数目约为2393左右,然而原始的S2中的记录约有4213条,和指望有所差距,进一步考虑将S2中未找到的记录输出到文件black_list来进一步分析看是数据自己的问题仍是匹配中有问题,思路以下

  • 在前面统计对应于S1中的id_list中加一步统计对应的S2中的id_list

  • 从文件中获取S2全部的id的集合列表

  • 从S2集合列表中刨去匹配成功的列表即为匹配失败的列表black_list

  • 将S2中匹配失败的记录输出到文件black_list.csv并进行分析

  • 问题分析:

    • 多级字典致使的名称覆盖问题,例如以下两个S1中的数据

    282577,lukas-klunter,,1996-05-26,,19,1.87 m,Germany,Defence - Right-Back,right,sportstotal,199,1-fc-koln-ii,438,Jul 1. 2015,30.06.2017,
    
    317484,hassib-sediqi,,1996-05-26,Germany,19,,Germany|Morocco,Striker,,,,unknown,75,Jul 1. 2014,-,

    位于较后面记录覆盖了前面的记录由于他们拥有相同的国籍和出生日期,考虑重构多级字典,在国籍,出生日期之下再加一级name

  • 重整旗鼓

    • 涉及文件

      • `csv2dict.py

      • core.py

      • CountryFormat.py

仍以imitate.csv文件为测试,则代码修改以下

import csv

def build_dict(source_file):
  new_dict = {}
  with open(source_file, 'rb')as csv_file:
      data = csv.DictReader(csv_file, delimiter=",")
      for row in data:
          print row
          item = new_dict.get(row['nationality'], dict())
          print item
          sub_item=item.get(row['date of birth'],dict())
          print sub_item
          # sub_item[row['name']]={k:row[k] for k in ('id')}
          sub_item[row['name']]={'id':row['id']}
          print sub_item
          item[row['date of birth']]=sub_item            
          print item
          new_dict[row['nationality']] = item
  return new_dict

因为imitate文件相对简单,只有四列,因此最深层的(name下一层仅有id属性),在这里对完整数据须要修改成comprehensions语句
测试结果以下

{'Brazil': {'1969-06-02': {'paulo-sergio': {'id': '218'}}, 
          '1974-07-05': {'marcio-amoroso': {'id': '1263'}}}, 
  'China': {'1978-05-08': {'yunfei-liu': {'id': '35228'}}, 
      '1977-06-30': {'leilei-li': {'id': '35214'}}, 
      '1974-01-17': {'chen-yang': {'id': '455'}}}, 
  'Germany': {'1972-05-23': {'christian-brand': {'id': '232'}}, 
          '1974-02-28': {'alexander-zickler': {'id': '227'}}, 
          '1963-03-28': {'perry-brautigam': {'id': '233'}}}, 
  'Morocco|Germany': {'1978-01-14': {'abdelaziz-ahanfouf': {'id': '229'}}}}
  • core.py
    然而在对真实数据进行测试时发现,这一次white_list比上次的数据还要少,因而再次输出black_list进行比对分析,在对core.py中间对于姓名匹配的逐条分析过程当中发现了两个问题:

    • 逻辑上的问题:在对国籍,出生日期进行匹配后,到了name这一层的匹配应该是先取出S2中的first_namelast_name并格式化而后遍历S1当前层级字典下存在的全部name逐一进行匹配判断;而以前的作法是没有遍历,直接取了一个读到的值,这是思惟上的漏洞;

    • 多级字典形成出生日期重复而形成记录覆盖问题:相似于以前的国籍划分提取,在这一过程当中构建多级字典会出现同名的键值,这时须要将记录插入已存在的键值的子字典中,而直接插入会形成覆盖,仿照着以前对country的操做对CountryFormat.py进行修改;
      好比,我用下述数据进行原有函数测试:

    D={'Germany': {'1972-05-23': {'danny':{'id':'1'}}, 
                        '1969-12-27': {'lancelot':{'id':'2'}}},
              'Morocco|Germany': {'1978-01-14':{'tony':{'id':'3'}},
                             '1969-12-27':{'lydia':{'id':'4'}}}}

原始代码以下

def country_format(dictionary):
 new_dict = {}
 for item in dictionary:
     # print item
     for index in str.split(item, '|'):
         if not index in new_dict:  # if not exists, create, and then insert or insert directly
             new_dict[index] = {}
         new_dict[index].update(dictionary[item])
 return new_dict

输出结果对好比下

original dict
 {'Germany': {'1972-05-23': {'danny': {'id': '1'}},
             '1969-12-27': {'lancelot': {'id': '2'}}},
 'Morocco|Germany': {'1978-01-14': {'tony': {'id': '3'}}, 
                     '1969-12-27': {'lydia': {'id': '4'}}}}
 --------------------------------------
 after dict
 {'Morocco': {'1978-01-14': {'tony': {'id': '3'}}, 
             '1969-12-27': {'lydia': {'id': '4'}}},
 'Germany': {'1978-01-14': {'tony': {'id': '3'}}, 
             '1972-05-23': {'danny': {'id': '1'}}, 
         '1969-12-27': {'lydia': {'id': '4'}}}}

能够看到Germany中的lancelot记录被lydia覆盖了,因此在进行country的格式化时须要对于date of birth这一层级字典的构建进行修改
修改后代码

def country_format(dictionary):
 new_dict = {}
 for item in dictionary:
     for index in str.split(item, '|'):
         if not index in new_dict:  # if not exists, create, and then insert or insert directly
             new_dict[index] = {}
             new_dict[index].update(dictionary[item])
         else:
             for k in dictionary[item]:
                 if not k in new_dict[index]:
                     new_dict[index][k]={}
                 new_dict[index][k].update(dictionary[item][k])
 return new_dict

结果对比

original dict
 {'Germany': {'1972-05-23': {'danny': {'id': '1'}}, 
             '1969-12-27': {'lancelot': {'id': '2'}}}, 
 'Morocco|Germany': {'1978-01-14': {'tony': {'id': '3'}}, 
                     '1969-12-27': {'lydia': {'id': '4'}}}}
 --------------------------------------
 after dict
 {'Morocco': {'1978-01-14': {'tony': {'id': '3'}},
             '1969-12-27': {'lydia': {'id': '4'}}},
 'Germany': {'1978-01-14': {'tony': {'id': '3'}}, 
             '1972-05-23': {'danny': {'id': '1'}}, 
         '1969-12-27': {'lydia': {'id': '4'}, 
                       'lancelot': {'id': '2'}}}}

其中须要注意的地方主要在于厘清层级之间的逻辑/流程关系,而后是对于dict.update()方法的理解,对于深层级的重复字段是覆盖,而不一样字段是添加到上一层级子字典下,再者就是在外层再次用update会致使以前的记录被覆盖

  • 接下来就是对于core.py的修改,主要是添加遍历,对于没取出一个S2中的名称,须要遍历S1中符合国籍,出生日期条件的name列表中的全部名称,而后进行匹配;
    修改后中间的代码以下

    if nationality in S1:
          if date in S1[nationality]:
              s1 = row['players.first_name'] + ' ' + row['players.last_name']
              s2=S1[nationality][date].keys()
              for i in s2:
                  if Name_Match.name_match(s1, i):
                      id_list.append(S1[row['players.country']][date_format.date_convert(row['players.date_of_birth'])][i]['id'])
                      temp_list.append(row['players.player_id'])

对真实数据进行测试输出结果以下:

length of id_list: 3578
   length of black list: 613
   length of all id 4213

大为改观,但仍难以让人满意,遂一样地输出black_list.csv进行分析,大约有如下几种情形

  • 国籍不规范

  • 名称不规范

  • 数据自己不一致

  • 特殊名称

  • 日期错误

国籍不规范,例如

39471,brad-guzan,,1984-09-09,United States,31,1.93 m,United States|Poland,Goalkeeper,left,wasserman-media-group,440,aston-villa,405,Aug 1. 2008,30.06.2017,
  
     Guzan;Goalkeeper;USA;Guzan;409;Brad;09/09/1984;A. Villa
  
  265132,kevin-toner,,1996-07-18,Ireland,19,,Ireland,Defence - Centre Back,left,,,aston-villa-u21,12124,Jul 1. 2014,30.06.2016,

  Toner;Midfielder;Republic of Ireland;Toner;20367;Kevin;18/07/1996;A. Villa

可知实际上是有其人,可是根据咱们的判断规则,在两条记录的国籍没法匹配,缘由在于S2中使用了USA,而S1中的记录是United States,因此针对此种特殊情形可能得加一步预处理;

名称不规范,例如

314237,rushian-hepburn-murphy,,1998-08-28,England,17,,England,Striker - Centre Forward,,,,aston-villa-u18,6933,-,-,

  Hepburn-Murphy;Forward;England;Hepburn-Murphy;17821;Rushian;28/08/1998;A. Villa

S2中出现了Hepburn-Murphy这种形式,因此对于其名称匹配要再加一个对于S2中的名称的分隔符为-的判断

数据自己不一致,例如

  • 出生日期不一致

    39908,rudy-gestede,,1988-10-09,France,27,1.93 m,Benin|France,Striker - Centre Forward,right,gallea-gestion-s-a,1628,aston-villa,405,Jul 31. 2015,30.06.2020,
    
      Gestede;Forward;Benin;Gestede;5149;Rudy;10/10/1988;A. Villa
  • 姓名不一致,例如

    4315,johnny-heitinga,,1983-11-15,Netherlands,32,1.80 m,Netherlands|Indonesia,Defence - Centre Back,right,wasserman-netherlands-management,274,end-of-career,123,Feb 1. 2016,-,
    
      Heitinga;Defender;Netherlands;Heitinga;190;John;15/11/1983;Ajax

    并且对于这个记录在S2中重复了三次

可见二者应该是同一我的,但两个数据源的出生日期差了一天,这个就是数据录入的问题了

特殊名称:

其实也算是名称不规范,在S2中存在很多名称是拉丁文,当前采用的处理方法是转为ASCII并采起忽略字符中特殊标记强制转为对应的英文字母并所有转化为小写的方法,例如

é    ñ    Á    á    ó    í    Ó
     e  n  a   a   o  i   o

然而对于如下状况这种方法就失效了,例如

22165,charles-nzogbia,,1986-05-28,France,29,1.71 m,France|Congo DR,Midfield - Right Wing,left,dea-football-investment,3525,aston-villa,405,Jul 30. 2011,30.06.2016,adidas

  N'Zogbia;Midfielder;France;N'Zogbia;422;Charles;28/05/1986;A. Villa

对于其中的Charles N'Zogbia即便通过转化,获得的结果为charles n'zogbia是不会和charles-nzogbia匹配的,若是须要处理的话考虑进一步的对名称的规范化处理

日期错误

以前提到过的错误日期,例如'00/00/0000'以及'12/00/1992'之类的

353935,edgar-alexandre,,1996-11-24,,19,1.80 m,France,Midfield - Central Midfield,right,,,sc-bastia-b,9652,-,-,

  Alexandre;Midfielder;France;Alexandre;17386;Edgar;00/00/0000;Bastia

这种根据判断规则也难以处理,除非用另外一种匹配规则

优化

接下来考虑对于国籍不规范以及姓名不规范这两种状况考虑进一步进行优化

针对姓名不规范

  • 针对S2中存在的first_name或者last_name中存在字符-的情形,经过对于Name_Match.py中对于获取到的名称的分割操做进行修改,经过正则表达式加入其它分隔符划分,代码以下:参考

# before
    # l1 = str.split(str.lower(Latin2ASCII.ud(s1.decode('utf-8', 'ignore'))), ' ')
    # after Latin -> ASCII -> lower case ->split by space, - etc.
    l1=re.findall(r"[\w']+", str.lower(Latin2ASCII.ud(s1.decode('utf-8', 'ignore'))))

测试结果

length of id_list: 3639
length of black list: 556
length of all id 4213

能够看到相较以前有小幅提高

  • 特殊名称,针对S2中某些姓名存在的'Latin2ASCII没法处理的情形,经过简单的替换操做除去后续获得的姓名分割后的列表中元素的'字符,代码以下,参考

l1 = [s.replace("'", '') for s in l1]   # remove the "'" in name

测试结果

length of id_list: 3659
length of black list: 536
length of all id 4213

匹配记录小幅提高

针对国籍不规范

初步对S1所构建的多级字典统计了下国籍数目,大约是246个,因此考虑在得到所构建的多级字典并对国籍进行分割后,将其中的一些名称表示不匹配的国家名创建字典而后循环替换,在拿去和S2进行匹配,代码以下:启发1,启发2

def transfer_country(dict):
    tr = {'United States': 'USA', 'Ireland': 'Republic of Ireland'}
    for row in tr:
        dict[tr[row]] = dict.pop(row)
    return dict

测试结果:

length of id_list: 3697
length of black list: 498
length of all id 4213

小幅提高,须要人工寻找更多可能的国籍字典来添加进去
从新分析black_list.csv可知出去以前的自己数据不一致或者数据错误以及记录找不到的状况,又发现以下状况:

57216,ismael-traore,,1986-08-18,France,29,1.86 m,Cote d'Ivoire|France,Defence - Centre Back,right,soccer-and-more-ltd-,496,sco-angers,1420,Jul 1. 2015,30.06.2017,

Ismael Traoré;Defender;Côte d'Ivoire;Traoré;1058;Ismael;18/08/1986;Angers

即其中的S2中部分国际名称存在拉丁字符例如Côte d'Ivoire,对这一点,相似以前对姓名的处理,须要进行转化,代码以下

def country_name_format(s):
    return Latin2ASCII.ud(s.decode('utf-8', 'ignore'))

结果以下,依然是小幅提高

length of id_list: 3733
length of black list: 466
length of all id 4213

为了将匹配失败的国籍一网打尽,我修改了部分代码,将全部S2中未经过国际匹配的国家名称输出到csv文件以便进行分析和相似于以前的映射处理,代码以下参考

import csv

def list2csv(list,file):
    wr=csv.writer(open(file,'wb'),quoting=csv.QUOTE_ALL)
    for word in list:
        wr.writerow([word])

对应的core部分,在原有基础上添加了输出country_list语句

if nationality in S1:
            if date in S1[nationality]:
                s1 = row['players.first_name'] + ' ' + row['players.last_name']
                # print s1
                # print '+++++++'
                s2=S1[nationality][date].keys()
                # print S1[nationality][date]['name']
                # print s2
                for i in s2:
                    if Name_Match.name_match(s1, i):
                        # print S1[row['players.country']][date_format.date_convert(row['players.date_of_birth'])]['id']
                        # id_list.append(S1[row['players.country']][date_format.date_convert(row['players.date_of_birth'])][i]['id'])
                        id_list.append(S1[nationality][date][i]['id'])
                        # print id_list
                        temp_list.append(row['players.player_id'])
                #     print i
                # print '---------'
        else:
            country_list.append(nationality)

如此即可以收集到全部国籍不一致的国家,而后进行手动添加至字典进行映射处理,此外为使输出结果更加直观,除了输出匹配成功,失败,全部的记录数目,添加了输出匹配成功率的一项格式控制

print 'match rate:',"{:6.2f}%".format((qtt_white*1.0/qtt_all)*100)

再手动添加了剩余国籍映射问题后,输出的country_list.csv中仍有一条信息,仔细分析后,我将S1和S2中对应的这条记录放在下面

//record in S1
225339,queensy-menig,,1995-08-19,Netherlands,20,1.74 m,Netherlands|Suriname,Striker - Left Wing,right,forza-sports-group,2553,pec-zwolle,1269,Jul 25. 2015,30.06.2016,
//record in S2
Queensy;Menig;Menig;15538;19/08/1995;Forward;Zwolle;Netherlands

Queensy;Menig;Menig;15538;19/08/19918653;21/04/1991;Forward;Roda JC;Mexico

能够看到这条数据对应的球员在S2中有两条记录,并且从第二条的状况来看应该是数据错误,而咱们捕捉到的Roda JC便来自于此,而对照S1中可知其实该球员的国际应该是Netherlands|Suriname,因此由此观之,S2中数据自己还存在一些问题,如重复和错误数据;
而当前的测试结果以下:

length of id_list: 3778
length of black list: 423
length of all id: 4213
match rate:  89.67%

因此我决定再匹配之前先对S2数据进行初步清晰,除去id重复的数据,对清洗后的数据进行匹配,代码以下,

import csv

# clean data of S2, eliminate data with same id
def clean_data(in_file,out_file):
    with open(in_file,'rb') as src_csv:
        reader = csv.reader(src_csv, delimiter=',')
        fieldnames = next(reader)
        # print fieldnames
        reader = csv.DictReader(src_csv, fieldnames=fieldnames, delimiter=';')
        writer=csv.writer(open(out_file,'wb'),delimiter=',')

        id=set()
        fieldnames=['players.vis_name','players.role','players.country',
                    'players.last_name','players.player_id', 'players.first_name',
                    'players.date_of_birth','players.team']
        writer.writerow(fieldnames)
        for row in reader:
            # print row

            if row['players.player_id'] not in id:
                # writer.writerow(row)
                # writer.writerow(row.keys())
                writer.writerow(row.values())
                id.add(row['players.player_id'])

in_file='2015players.csv'
out_file='2015players_clean.csv'

clean_data(in_file,out_file)

测试结果以下

length of id_list: 3536
length of black list: 423
length of all id: 3958
match rate:  89.34%

可是在除重的这一过程存在问题,由于我是直接根据id这一项来判断是否重复的,而除掉的重复项仅仅是按照逐行读取csv时的前后顺序,这样对于彻底重复的记录还好,可是对于其余状况确定是不科学的,因此我决定先把全部重复的记录导出来而后分析重复的情形;

因而在以前根据id判重的基础上用id_repeated这么一个列表来收集重复的id并仿照这以前的匹配操做将原始S2中对应列表中全部id的记录导出来获得repeated_id.csv,代码以下

def clean_data_byID(in_file,out_file):
    id_repeated=[]
    with open(in_file,'rb') as src_csv:
        reader = csv.reader(src_csv, delimiter=',')
        fieldnames = next(reader)
        # print fieldnames
        reader = csv.DictReader(src_csv, fieldnames=fieldnames, delimiter=';')
        writer=csv.writer(open(out_file,'wb'),delimiter=',')

        id=set()
        fieldnames = ['players.vis_name', 'players.role', 'players.country',
                      'players.last_name', 'players.player_id', 'players.first_name',
                      'players.date_of_birth', 'players.team']
        writer.writerow(fieldnames)
        for row in reader:
            # print row

            if row['players.player_id'] not in id:
                # writer.writerow(row)
                # writer.writerow(row.keys())
                writer.writerow(row.values())
                id.add(row['players.player_id'])
            else:
                id_repeated.append(row['players.player_id'])
    Csv_Match2.csv_match2(id_repeated,'2015players.csv','repeated_id.csv')

对应的csv_match2代码

# this is for the special csv file with ',' in header ';' in rows
def csv_match2(id_list,input_file,output_file):
    with open(input_file, 'rb') as f:
        # ------------------if the delimiter for header is ',' while ';' for rows
        reader = csv.reader(f, delimiter=',')
        fieldnames = next(reader)

        reader = csv.DictReader(f, fieldnames=fieldnames, delimiter=';')
        # reader = csv.DictReader(f)
        rows = [row for row in reader if row['players.player_id'] in set(id_list)]

    header = rows[0].keys()
    with open(output_file, 'w') as f:
        f.write(','.join(header))
        f.write('\n')
        for data in rows:
            f.write(";".join(data[h] for h in header))
            f.write('\n')

可是这样获得的文件不够直观,因此进一步的我对该文件进行了除去彻底重复的记录以及根据id排序把全部全部相似的记录放到一块儿这么两个操做,代码以下:

  • 除去彻底重复的记录

# eliminated the completely repeated record in repeated file for further analysis
def eliminate_repeated_row(in_file,out_file):
    with open(in_file,'rb') as in_file,open(out_file,'wb')as out_file:
        seen=set()
        for line in in_file:
            # print line
            if line in seen:continue

            seen.add(line)
            out_file.write(line)
  • 根据id排序把全部全部相似的记录放到一块儿

# sort the csv file by column 'id' to put the similar record together for further analysis
def sort_csv_byID(in_file,out_file):
    with open(in_file, 'rb') as f:
        reader = csv.reader(f, delimiter=',')
        fieldnames = next(reader)
        reader = csv.DictReader(f, fieldnames=fieldnames, delimiter=';')
        sorted_list=sorted(reader,key=lambda row:row['players.player_id'],reverse=True)
        # print sorted_list
        List2csv.nestedlist2csv(sorted_list,out_file)

对应的nestedlist2csv代码

# write nested list of dict to csv
def nestedlist2csv(list, out_file):
    with open(out_file, 'wb') as f:
        w = csv.writer(f)
        # it's very anoying that each time the field names changed
        # fieldnames = ['players.vis_name', 'players.first_name', 'players.date_of_birth', 'players.role',
        #               'players.player_id', 'players.team', 'players.country',
        #               'players.last_name']
        fieldnames=list[0].keys()  # solve the problem to automatically write the header
        # for row in list:
        #     print row.keys()
        # print list[0].keys()
        w.writerow(fieldnames)
        for row in list:
            w.writerow(row.values())

代码参考列表

测试结果部分截取以下:

vis_name,role,country,last_name,id,first_name,date_of_birth,team

Khazri,Midfielder,Tunisia,Khazri,989,Wahbi,08/02/1991,Bordeaux
Khazri,Midfielder,Tunisia,Khazri,989,Wahbi,08/02/1991,Sunderland

Kawaya,Forward,Belgium,Kawaya,9631,Andy,23/08/1996,Willem II

Lewis Baker,Midfielder,England,Baker,9574,Lewis,25/04/1995,Vitesse

Nordin Amrabat,Forward,Morocco,Amrabat,9425,Nordin,31/03/1987,Málaga
Nordin Amrabat,Midfielder,Morocco,Amrabat,9425,Nordin,31/03/1987,Watford

Marcelo Díaz,Midfielder,Chile,Diaz,9240,Marcelo,30/12/1986,Celta
Marcelo Díaz,Midfielder,Chile,Díaz,9240,Marcelo,30/12/1986,Hamburg

Vainqueur,Midfielder,France,Vainqueur,9102,William,19/11/1988,Dynamo
Vainqueur,Midfielder,France,Vainqueur,9102,William,19/11/1988,Roma

Kramaric,Forward,Croatia,Kramaric,8726,Andrej,19/06/1991,Hoffenheim
Kramaric,Forward,Croatia,Kramaric,8726,Andrej,19/06/1991,Leicester

Gullón,Midfielder,Spain,Gullón,8449,Marcos,20/02/1989,Roda JC

Iturbe,Forward,Paraguay,Iturbe,8364,Juan,04/06/1993,B'mouth
Iturbe,Forward,Argentina,Iturbe,8364,Juan,04/06/1993,Roma

能够看到除去彻底重复的记录以及以前记录错误的情形,S2中存在很多记录显示的球员的队名不一样,而其余信息一致;
进一步的,随机找了部分S1中对应的球员信息比对球队信息,例如:

S1-current_club S2-team
sunderland-afc Sunderland/Bordeaux
watford-fc Málaga/Watford
celta-de-vigo Celta/Hamburg
... ...

分析过程当中我发现我犯了一个错误,这些重复的数据并不会对咱们以前所构建的多极字典中的匹配形成影响,有如下几点缘由:

  • 首先,咱们是根据国籍出生日期姓名完成匹配,这中间并未涉及到球队名称的匹配,因此即便有两条记录在这里产生不一致也不会对于匹配结果产生影响,由于咱们S1的数据相对规范,并且咱们是遍历数据量相对较小的S2去与以S1为基础构建好的多级字典进行匹配,只要知足国籍出生日期姓名匹配咱们就认为是匹配成功的,而这一过程当中不管是无安全重复的记录亦或只是球队不一样的记录都是能成功匹配的,只是匹配到的是S1中同一条记录而已;

  • 此外,对于匹配率的计算,回顾几个收集id的list,最后的操做都是经过set()来隐式除重因此不管是计算匹配率仍是输出全部没有经过匹配的black_list(由于这里我是取出全部没有经过的S2中记录的id而后对原始S2文件进行遍历输出全部匹配id的记录,因此能够确保全部未经过匹配的记录都被导出到black_list.csv)

length of id_list: 3535
length of black list: 423
length of set(all id): 3958
length of all id: 4213
length of set(id matched in S2): 3535
length of id matched in S2: 3778
match rate:  89.31%

因此重心仍是得放到对于未经过匹配的black_list.csv

进一步的,咱们仿照着相似的情形对于S2中国籍匹配经过而日期未经过的数据导出字典(id为键,date of birth为值)到csv文件进行分析,代码以下,参照

def dict2csv(dict,file):
    with open(file,'wb') as f:

        w=csv.writer(f)
        # write each key/value pair on a separate row
        w.writerows(dict.items())

结果大约导出了49条记录,问题缘由大概出在前面提到的错误日期以及日期不匹配上,因此,考虑用另外一种方法对S1构建多级字典(nationality->position->name),这中间除了自己够贱多级字典能够参照以前的方法之外,对于position须要相似以前对于国籍不匹配的问题进行字典映射,由于S1中的position分类较细(约49种),而S2中对应的role只是简单分为了4中,因此须要将S1中的'postion'利用字典映射到S2中的'role'从而进行匹配;
此外,在对因为日期不匹配的部分数据(约49条)进行匹配后,除了自己须要将数据追加到前面匹配成功的各个数据如temp_list,id_list,white_list,id_map并从black_list里除去之外;对于数据的匹配率也只是小幅提高,难点在于对于姓名不一致的情形的匹配上;

经过对于姓名不匹配的记录的一些分析,我发现大概有如下2种情形

  • 情形一,S1中的姓名比S2中的长:

233782,aissa-bilal-laidouni,,1996-12-13,,19,,France,Midfield,,,,sco-angers-b,16672,-,-,

Laidouni;Midfielder;France;Laidouni;19906;Aissa;13/12/1996;Angers

这个与我以前的匹配规则有关,由于以前对于两边数据源的分析发现S1的name通常而言是截取的球员的部分姓名,是S2的first_name+last_name的一部分,也就是说短于S2的因此当时指定的规则是判断S1的姓名分割后的集合是不是S2的集合的子集;

  • 情形二:两个数据源中的姓名字段有部分字母不同;最多见的不一致是yi的不一致;

4315,johnny-heitinga,,1983-11-15,Netherlands,32,1.80 m,Netherlands|Indonesia,Defence - Centre Back,right,wasserman-netherlands-management,274,end-of-career,123,Feb 1. 2016,-,

Heitinga;Defender;Netherlands;Heitinga;190;John;15/11/1983;Ajax

102045,izu-uzochukwu,,1990-04-11,,26,1.71 m,Nigeria,Midfield - Defensive Midfield,right,ohne-berater,96,odense-boldklub,173,Jan 26. 2016,30.06.2019,

Uzochukwu;Midfielder;Nigeria;Uzochukwu;18237;Izunna;11/04/1990;Amkar

212251,yuri-shafinskiy,,1994-05-06,Russia,21,1.90 m,Russia,Goalkeeper,right,sa-football-agency,2119,anzhi-makhachkala-ii,26567,Aug 12. 2015,30.06.2019,

Shafinsky;Goalkeeper;Russia;Shafinsky;15565;Yuri;06/05/1994;Anzhi

55396,evgeni-pomazan,,1989-01-31,UDSSR,27,1.93 m,Russia,Goalkeeper,right,prosports-management,1330,anzhi-makhachkala,2700,Sep 1. 2011,-,

Pomazan;Goalkeeper;Russia;Pomazan;7457;Evgeny;31/01/1989;Anzhi

对这种不一致个人想法是利用字符串的模糊匹配设置一个阈值来判断达到多少匹配即判断是匹配的;

下面就日期不匹配以及姓名的模糊匹配来进行优化:
结果在导出了两个数据源对应球员position的信息,我以为仍是创建二级字典直接匹配姓名比较合适...

l1 = ['Forward', 'Midfielder', '21/04/1991', 'Goalkeeper', 'Defender']

l2 = ['', 'Defence - Left Midfield', '- Central Midfield', 'Defence', 'Striker - Defensive Midfield',
      'Midfield - Centre Back', 'Defence - Left Wing', '- Attacking Midfield', 'Defence - Right Midfield',
      '- Centre Forward', 'Striker - Right Midfield', 'Striker - Secondary Striker', 'Midfield - Centre Forward',
      'Striker', '- Defensive Midfield', 'Striker - Left Midfield', 'Defence - Centre Back', 'Defence - Centre Forward',
      '- Centre Back', 'Striker - Right Wing', 'Midfield - Sweeper', 'Midfield - Defensive Midfield',
      'Striker - Centre Forward', 'Defence - Defensive Midfield', 'Midfield - Attacking Midfield', '- Left Midfield',
      'Striker - Central Midfield', 'Midfield - Secondary Striker', 'Midfield - Left Wing', 'Defence - Right Wing',
      'Striker - Attacking Midfield', 'Defence - Central Midfield', 'Striker - Right-Back', 'Midfield',
      'Midfield - Right-Back', 'Striker - Centre Back', '- Right Midfield', 'Defence - Right-Back',
      'Striker - Left-Back', 'Midfield - Right Wing', 'Defence - Attacking Midfield', 'Striker - Left Wing',
      'Midfield - Left Midfield', 'Midfield - Central Midfield', 'Midfield - Left-Back', 'Goalkeeper',
      'Defence - Left-Back', 'Midfield - Right Midfield', 'Defence - Sweeper']

我原本想手动将l2中数据映射到l1中,可是除去自己数据量比较大之外,发现不少l2中的字段在l1中没法找到对应的,例如Striker - Centre Back,并且也没法像对姓名那样利用子集匹配,因此我决定直接对这日期匹配失败的49条记录利用nationality->name的二级字典来进行匹配;

这里有两种思路,一种是另写方法来读入未匹配的日期文件与构建的二级字典匹配,将匹配成功的记录根据id分别添加如各个列表并将仍不成功的记录导出;
思路二是直接在原始匹配函数中对匹配不成功的日期除了收集并导出外,添加语句直接经过二级字典进行匹配完成对各个列表的添加,修改操做,一样的导出仍未匹配成功的记录;
(此外在这一过程当中我经过对于S1的id的收集及处理判断其是否惟一,结果显示是惟一的;)
考虑到思路一还要再次读入文件,我决定尝试思路二
代码以下

if nationality in S1:
                if date in S1[nationality]:
                    s1 = row['players.first_name'] + ' ' + row['players.last_name']
                    s2 = S1[nationality][date].keys()
                    for i in s2:
                        if Name_Match.name_match(s1, i):
                            # get id for white list
                            id_list.append(S1[nationality][date][i]['id'])
                            # get matched id in S2
                            temp_list.append(row['players.player_id'])
                            id_dict[row['players.player_id']] = S1[nationality][date][i]['id']
                else:
                    # collect the date mismatched id
                    date_dict[row['players.player_id']] = row['players.date_of_birth']
                    date_list.append(row['players.player_id'])
                    # try to match the date mismatched record by 2-level nested dict
                    s1 = row['players.first_name'] + ' ' + row['players.last_name']
                    s2 = S1_2[nationality].keys()
                    for i in s2:
                        if Name_Match.name_match(s1, i):
                            id_list.append(S1_2[nationality][i]['id'])
                            ignore_date_list_s1.append(S1_2[nationality][i]['id'])
                            # get matched id in S2
                            temp_list.append(row['players.player_id'])
                            ignore_date_list_s2.append(row['players.player_id'])
                            id_dict[row['players.player_id']] = S1_2[nationality][i]['id']
                        # wrong logic here, traverse the data set one by one can only judge the matched one,you can only
                            #  say it's mismatch when traversed all element
                        # else:
                        #     date_still_list.append(row['players.player_id'])

这里犯了一个逻辑错误在于对于判断国籍相同的情形下忽略出生日期后,姓名匹配的记录的id的收集过程当中,收集匹配成功的数据的逻辑是没问题的,在国际已成功匹配可是日期不匹配的数据集中遍历构建好的二级字典,判断其中姓名是否匹配,若匹配则追加到以前的两个数据源的id列表以及对应的映射字典中;但这里我犯的错误时对于不匹配的数据我直接也收集id,这是不对的,由于当前遍历的是根据S1构建好的二级字典,再逐一遍历的过程当中,与S2中姓名匹配的理论上应该是一个,而在此过程当中若统计不匹配的则循环下来我就把全部的id都添加进去了,即便当前id是能匹配到,可是只是这一次遇到的S1中的名称不匹配也会被加进去;
正确的作法是取反也就是从原始由于日期缘由未匹配成功而收集的id列表中除去全部的如今忽略日期而在二级字典中匹配成功的记录便可;
对应的二级字典构建代码以下

# build specific nested dict from csv files(nationality->name)
def build_level2_dict(source_file):
    new_dict = {}
    with open(source_file, 'rb')as csv_file:
        data = csv.DictReader(csv_file, delimiter=",")
        for row in data:
            item = new_dict.get(row['nationality'], dict())
            item[row['name']] = {k: row[k] for k in ('id', 'complete name', 'place of birth',
                                                         'age','position','height', 'date of birth', 'foot',
                                                         "player's agent",'agent id','current club',
                                                         'club id', 'in the team since', 'contract until',
                                                         'outfitter')}
            new_dict[row['nationality']] = item
    return new_dict

而后这样一来匹配的结果以下:

length of id_list: 3841
length of set(id_list): 3594
length of black list: 379
length of set(all id): 3958
length of all id: 4213
length of set(id matched in S2): 3579
length of id matched in S2: 3841
match rate:  90.42%

如今有如下几个问题:

  • 国籍不一致

    • 自己两边的国籍信息不一致

    • S1中的同一国籍有两种不一样写法(出如今不一样记录中)

  • 重复数据形成的S1,S2中的的匹配id数目不一致

  • 姓名不一致

解决思路

  • 国籍不一致

    • 自己两边的国籍信息不一致
      通过当前的优化,原有的49条国籍匹配成功可是日期匹配失败的记录如今只剩下5条不匹配,逐一分析发现,其中有三条记录是因为国际信息不一致致使的,考虑对其进行(date of birth->name构建二级字典进一步匹配)

    • S1中的同一国籍有两种不一样写法(出如今不一样记录中)
      这个应该跟S1的数据录入有关,以下:

255930,aristote-ndongala,,1994-01-19,Zaire,22,1.86 m,Congo DR,Midfield - Left Wing,left,bestway-soccer-limited,2759,fc-nantes-b,10850,-,30.06.2017,

Ndongala;Midfielder;Congo;Ndongala;5258;Aristote;19/01/1994;Nantes

其中

S1-nationality S2-country
Congo DR Congo

起先我觉得只是国际名称的不一致,后来经过对于S1中的Congo的搜索发现其中其余记录也有这种写法,因此根本缘由是S1自己数据的录入不一致,考虑直接将Congo DR映射到Congo结果对S2一搜索发现其中也存在Congo DR的记录,仔细查了维基发现是我孤陋寡闻了,实际是非洲的两个不一样国家,那问这个应该也归类到上面的不一样数据源的国籍信息不一致问题

  • 重复数据形成的S1,S2中的的匹配id数目不一致
    以前提到过S2中存在四百多条数据重复的问题,除去彻底重复之外还有仅仅由于球队名称不一样致使的重复,因为预处理没有更多信息基础难以除重(除非再加一级判断球队名称,可是一来这样更加复杂致使匹配度会受影响并且自己球队名称相似人物名两边写法不一致,实际操做也不太好),因此当前的想法是我先把全部数据匹配起来,由于一来S1中不存在重复数据,二来由上面的结果能够看到,根据咱们三级列表的判断,球队名的不一致所形成的重复是不影响匹配的,因此咱们能够在匹配事后回过头来根据球队对匹配后的数据根据球队名进行再匹配除去S2中的不一致数据;

可是在上面看到此次匹配出现了以前不曾看到的新问题,以前数目一致的除重后的S1,和S2中的匹配id列表在分别追加忽略日期信息的匹配记录后出现了不一致,猜测是由于约束条件放松加上姓名匹配是判断子集可能多个S1中的姓名对应到了S2中相同的姓名,考虑先导出值相同键不一样的重复的记录分析并改进姓名匹配规则

  • 姓名不一致
    考虑用模糊匹配进行处理

在此基础上我将通过二级字典匹配的以前由于日期缘由未匹配成功的数据,因为统计结果显示存在多个S1中id对应同一个S2中id的情形,因此我猜想是跟姓名匹配有关,因此先是根据以前分别收集的id构建了一个字典,而后对该字典进行重构将其中值相同的元素的值取出来做为键,而多个对应的键构成列表做为值构建新的字典,这样我就能很直观地看到哪些S1中的id对应了S2中的同一个id;参考代码

# return a dict with the same value in original as new key and keys as value
def dict_same_value(original_dict):
    new_dict={}
    for k,v in original_dict.iteritems():
        new_dict.setdefault(v,[]).append(k)
    return new_dict

old_dict=core.ignore_date_dict
repeated_dict=dict_same_value(old_dict)
print {k:repeated_dict[k] for k in repeated_dict if len(repeated_dict[k])>=2}

而统计出的结果以下:

{'19559': ['172796', '38588', '354359'],
'18892': ['30846', '26734'],
'4095': ['199330', '349899', '70961', '113138'],
'15144': ['51518', '128702'],
'11506': ['263843', '232143'],
'3946': ['92947', '176427', '92781', '95433'],
'6395': ['150927', '74548', '292349', '270188'],
'18297': ['95497', '251876']}

以其中的第一组为例

Julen;López;Julen López;19559;00/00/0000;Midfielder;Eibar;Spain

172796,julen,Julen Macizo Adan,1989-03-02,Spain,27,,Spain,Goalkeeper,,,,unknown,75,Jul 1. 2011,-,

38588,lopez,Pedro Jesus de Tuleda Lopez,1983-08-25,,32,1.87 m,Spain,Defence - Centre Back,,,,unknown,75,Jul 1. 2008,-,

354359,julen-lopez,,,Spain,,,Spain,Defence - Centre Back,,Agent is known players under 18:,no id,cd-vitoria,50186,Jul 1. 2015,30.06.2016,

可见形成这种情形的缘由是S1中的姓名录入不规范,考虑修改姓名匹配规则;
可是进一步分析其余数据发现,存在以下情形:

Lucas;Evangelista Santana de Oliveira;Evangelista Santana de Oliveira;6395;06/05/1995;Midfielder;Udinese;Brazil

150927,oliveira,Valmir Alves de Oliveira,1979-04-07,,37,,Brazil,Striker,,,,unknown,75,Jul 1. 2010,-,

74548,lucas,Lucas Marcolini Dantas Bertucci,1989-05-06,Brazil,26,1.76 m,Hungary|Brazil,Midfield - Attacking Midfield,

292349,lucas-oliveira,Lucas Dias Pires de Oliveira,1995-06-27,Brazil,20,1.80 m,Brazil,Striker - Left Wing,right,,,ad-estacao,10164,Aug 25. 2014,-,

270188,lucas-evangelista,Lucas Evangelista Santana de Oliveira,1995-02-06,Brazil,21,1.81 m,Brazil,Midfield - Attacking Midfield,right,familienangehoriger,1207,panathinaikos-athens,265,Jan 20. 2016,30.06.2016,

在这一组数据中一个S2中的id对应了4个S1中的id,若是仅仅这种情形咱们可能能够经过控制姓名模糊匹配的阈值来控制;可是对于下面的情形就很差办了:

Antonio Jesús;Cotán Pérez;Cotán Pérez;4095;10/09/1995;Midfielder;Sevilla;Spain

199330,antonio-cotan,,1995-09-19,Spain,20,1.76 m,Spain,Midfield - Central Midfield,,you-first-sports,1406,sevilla-atletico,8519,Jul 1. 2012,30.06.2016,

349899,antonio-perez,,1993-05-24,Spain,22,1.77 m,Spain,Defence - Right-Back,right,,,sd-tarazona,41403,Jul 1. 2015,30.06.2016,

能够看到上述两天记录对应的阈值极有多是相同的,因此仅仅是靠姓名来进行模糊匹配准确率仍是欠佳,可能仍是再加一层日期的模糊匹配,针对这个情形能够考虑取出年-月做为键,可是后续发现其余情形中有月份和日子都对不上的,考虑只用年来区分,但这样效果就又差了

此外在此过程当中还发现了S2中数据的一个错误

Nolan Mbemba;Batina;Batina;19671;19/02/1995;Midfielder;Lille;France
Nolan;Mbemba;Nolan Mbemba;20158;19/02/1995;Midfielder;Lille;France

276768,nolan-mbemba,,1995-02-19,,21,1.81 m,France|Congo DR,Midfield - Defensive Midfield,,,,losc-lille-b,12765,-,-,

前面两条中有一条是不规范的,就大部分S2中数据来看,极有多是第一条有问题,而这样的同一我的却对应了两个id,不得不说S2的数据实在是太粗糙了点

至此,接下来的思路是:

  • 姓名匹配的规则必定要改成模糊匹配,当前判断子集不管是匹配准确度仍是匹配度上都不尽如人意

  • 前面试图追加后期二级字典匹配的的想法如今看来结果反而出现了重复对应的问题,因此考虑仍是分步骤处理,数据是不平等的,因此须要制定规则划分类别;

  • 仅仅国籍-姓名的二级字典匹配准确率欠佳,考虑加入日期模糊匹配,思路有两种:构建三级字典或者后期判断

姓名模糊匹配

使用第三方库fuzzywuzzy,初步经过对于一些数据的测试将匹配率设置在60%,代码以下:(保留了以前对于名称Unicode以及分隔符等等的处理)

# modify the name match rule for fuzz name match,token_sort_ratio>=60
def name_match(s1,s2):
    # Latin -> ASCII -> lower case ->split by space, - etc.
    l1=re.findall(r"[\w']+", str.lower(Latin2ASCII.ud(s1.decode('utf-8', 'ignore'))))
    l1 = [s.replace("'", '') for s in l1]   # remove the "'" in name

    s1=' '.join(l1)
    s2=s2.replace('-',' ')
    # if fuzz.token_sort_ratio(s1,s2)>=60:
    #     return True
    return fuzz.token_sort_ratio(s1,s2)>=60

其中对于将列表中的字符串转化为空格为分割符的字符串,参照

从新进行第一阶段的匹配工做,仍以国籍->出生日期->姓名的三级字典以修改后的姓名匹配规则进行匹配,须要:

  • 导出匹配成功的S1,S2中记录分别到两个不一样的csv文件

  • 构建以S1中id为键,S2中id做为值的映射列表,这里注意,虽然原始的S1中的id是惟一的,可是因为S2中存在重复记录以及部分重复记录形成可能同一个S1的id对应多个S2中的id(也有可能并不会,后续同级两个id列表长度进行分析看看);若是出现上述状况则对值构建嵌套列表,将不一样的id放入其中,而不是覆盖

  • 将这一阶段匹配失败的记录提取出来保存为另外一个文件以做为下一阶段匹配工做的数据源

在直接利用修改后的姓名匹配方法进行配对后输出结果以下:
name_match(60ratio)

表面上看到匹配率和匹配记录都大幅提高了,可是对于除重后的两个数据源的id构成的set的数目进行对比我发现与以前不一样,前面两个数据源经过子集判断匹配姓名所匹配成功的id列表数目以及通过除重后所得的id集合数目都是一致的,这个基本上能够证实没有出现S1中多个姓名对应S2中同一人的错误匹配(固然我后面会进一步判断和处理),以下
name_match_by_subset

而反观对于以60%的比率的模糊匹配方法匹配的结果两个有几十条记录的差别,通过导出id映射字典(这里我对于字典的构建处理方法是以s2的id为键,而后值为列表,添加或建立s1的id,这样就保留了s1中的重复id对应s2中id的状况,能够看看是否出现错误匹配)

id_dict.setdefault(row['players.player_id'], []).append(S1[nationality][date][i]['id'])

而事实确实发生了错误匹配,以其中一条记录为例:

344,"['204298', '37217', '76050', '204298', '37217', '76050', '204298', '37217', '76050']"

而对这些记录的进一步观察发现因为匹配率太低致使很多不一样s1中的人会对应s2中的同一我的,那么若是单纯的考虑提升匹配率呢,这样能够提高精确度,可是会漏掉很多实际是同一人可是因为仅仅输入部分姓名致使匹配率不高的情形,例如:

s2='charles-nzogbia'
 s1="Charles N'Zogbia José  Hepburn-Murphy"

匹配率大约只有60%+
结果以下
name_match(90ratio)

而另外的仅仅经过子集判断可是加入逆子集判断(由于后来发现存在少量S1中姓名包含S2中姓名的情形)也作了一次测试,输出结果以下:
name_match_by_subset2

因此我决定把子集判断与模糊匹配(90%ratio)结合起来
name_match_mix

对应的代码以下

if Name_Match.name_match_by_set(s1, i):
                        # get id for white list
                        id_list.append(S1[nationality][date][i]['id'])
                        # get matched id in S2
                        temp_list.append(row['players.player_id'])
                        # id_dict[row['players.player_id']] = S1[nationality][date][i]['id']
                        id_dict.setdefault(row['players.player_id'],[]).append(S1[nationality][date][i]['id'])
                    elif Name_Match.name_match_by_set2(s1, i):
                        # get id for white list
                        id_list.append(S1[nationality][date][i]['id'])
                        # get matched id in S2
                        temp_list.append(row['players.player_id'])
                        # id_dict[row['players.player_id']] = S1[nationality][date][i]['id']
                        id_dict.setdefault(row['players.player_id'], []).append(S1[nationality][date][i]['id'])
                    elif Name_Match.name_match(s1,i):
                        # get id for white list
                        id_list.append(S1[nationality][date][i]['id'])
                        # get matched id in S2
                        temp_list.append(row['players.player_id'])
                        # id_dict[row['players.player_id']] = S1[nationality][date][i]['id']
                        id_dict.setdefault(row['players.player_id'], []).append(S1[nationality][date][i]['id'])

可是id集合也不彻底相等,因此接下来我对id_map进行处理,逐一遍历其值造成的列表的集合,将其中长度大于1的记录输出出来看看
代码以下:

print {k:id_dict[k] for k in id_dict if len(set(id_dict[k]))>=2}

输出结果以下(一共三条记录(2条s1中id对应同一个s1中的id)):

Mikel;Oyarzabal;Oyarzabal;19641;21/04/1997;Forward;Sociedad;Spain

351478,mikel-oyarzabal,,1997-04-21,Spain,19,1.80 m,Spain,Midfield - Left Wing,left,df-sportmanagement-gmbh,3734,real-sociedad,681,Jan 1. 2016,30.06.2021,

376590,mikel-oyarzabal-ugarte,,1997-04-21,,19,,Spain,Midfield,,,,delete-player,10194,-,-,
------------
Danilo;D'Ambrosio;D'Ambrosio;3004;09/09/1988;Defender;Inter;Italy

120767,dario-dambrosio,,1988-09-09,Italy,27,1.89 m,Italy,Defence - Right-Back,right,,,bassano-virtus,9690,Jan 30. 2016,-,

55769,danilo-dambrosio,,1988-09-09,Italy,27,1.80 m,Italy,Defence - Right-Back,right,tullio-tinti,1708,inter-milan,46,Jan 30. 2014,30.06.2018,Nike
-------------
Rodrigue Casimir;Ninga;Ninga;19220;17/05/1993;Forward;Montpellier;Chad

392810,casimir-ninga,,1993-05-17,Chad,22,1.86 m,Chad,Striker - Centre Forward,right,new-star-sport-promotion,1119,hsc-montpellier,969,Aug 31. 2015,30.06.2020,

210948,rodrigue-ninga,,1993-05-17,,22,,Chad,Striker,,,,elect-sport-fc,48525,-,-,

第一条是根据逆子集匹配获得的,然而除去第二条数据能够根据姓名或者球队进行再次匹配来处理外,其他的两条数据就目前所得的记录的信息是难以分别哪条是正确的;

先保留记录留到后续处理,至此第一阶段的匹配完成,接下来开始就剩余的black_list进行处理,在此以前我须要将第一阶段所得的记录单独提取出来做为第一类匹配成功数据集,沿用以前的代码,根据id将S1,S2中对应的记录分别写到white_list.csv(3662)和white_list2.csv(3906)中,上述记录数差异在于S2中存在id重复数据,这个如今没有影响,后续根据其余信息反过来对其进行清洗;而后black_list.csv(309),接下来另写程序对于'black_list'进行处理

第二阶段

  • 首先经过创建二级字典(国籍->姓名)来对以前修改后的姓名匹配原则进行匹配看看测试结果:
    black_list_match(ratio90%)

能够看到,首先匹配率比较低毕竟在高达90的匹配要求下加上两个数据源的姓名录入不规范尤为s1中很多姓名只是部分姓名;另外一方面仍是存在很多错误匹配的结果,因此指望用这种方式来进一步匹配效果不太好;
那么对于先前的本身匹配呢,预计会出现大量错误匹配,毕竟失去日期这一强力的限制后,加上s1中许多仅仅录入了姓或者名的球员信息很容易被判断为匹配:
black_list_match(subset)

果真,出现大量错误匹配,看来直接进行二级字典的匹配效果很不理想,因此从一开始创建三级字典来精确结果提升查找效率是颇有必要的;考虑仍构建三级字典,不过对于中间层日期适当放宽,仅取年-月或者仅仅取来构建三级字典进行匹配测试

三级字典(国籍->出生年份->姓名

在构造这种字典的过程当中我发现以前我所构造的三级字典的一个漏洞,即默认的认为人名是惟一的或者说在加上国籍出生日期的限制下的人名是惟一的,因此在构造三级字典的最里层的name时是默认的惟一的,因此采起的是覆盖的构造方法,毫无疑问对于数据量较大的数据源这种方法,因此我对此进行了修改,将最里层设置为列表,对于出现上述国籍出生日期姓名全相同的记录,存到列表中,这样就不会形成覆盖而丢失记录,新的字典构造代码以下:

# build specific nested dict from csv files, relax the 'date' key regulation(nationality->date of birth(only year)->name)
def build_dict3(source_file):
    new_dict = {}
    with open(source_file, 'rb')as csv_file:
        data = csv.DictReader(csv_file, delimiter=",")
        for row in data:
            # print row
            item = new_dict.get(row['nationality'], dict())
            # print item
            # sub_item = item.get(row['date of birth'], dict())
            year = str.split(row['date of birth'], '-')[0]
            # print year
            sub_item = item.get(year, dict())
            # print sub_item
            # sub_item[row['name']] = {k: row[k] for k in ('id', 'complete name', 'place of birth',
            #                                              'age', 'height', 'position', 'foot',
            #                                              "player's agent",'agent id','current club',
            #                                              'club id', 'in the team since', 'contract until',
            #                                              'outfitter')}
            # consider the condition more than 1 person in the same nationality,same birth year and even same name
            sub_item.setdefault(row['name'], []).append({k: row[k] for k in ('id', 'complete name', 'place of birth',
                                                                             'age', 'height', 'position', 'foot',
                                                                             "player's agent", 'agent id',
                                                                             'current club',
                                                                             'club id', 'in the team since',
                                                                             'contract until',
                                                                             'outfitter')})
            # sub_item[row['name']]={'id':row['id']}
            # print sub_item
            item[year] = sub_item
            # print item
            # print '++++++++++++++++++++++++++++++++++'
            new_dict[row['nationality']] = item
    # print '+++++++++++++++++++++++++++++++++++++'
    # print new_dict
    return new_dict

其中在抽取出生日期中的年份主要是利用字符串的分割以及避免重复而利用的setdefault()方法;
进一步的,我对于S1进行了姓名和id的统计及其集合的统计来看看是否有重复数据
stat

==========上方是对姓名的统计,下方是对id的统计,可见S1中姓名重复的状况仍是很多,id倒确实惟一,对应的统计代码以下:

# get certain column value of csv(for common csv file(',')),and judge if it's repeated
def get_column_value2(file,column_name):
    with open(file,'rb') as f:
        role_list=[]

        reader=csv.reader(f,delimiter=',')
        fieldnames=next(reader)
        reader=csv.DictReader(f,fieldnames=fieldnames,delimiter=',')
        for row in reader:
            # print row['players.role']
            role_list.append(row[column_name])

        role_set=set(role_list)
        print 'set statistics:',len(set(role_list))
        print '-------------'
        print 'list statistics',len(role_list)
        print '============='
        return list(role_set)

对于修改后的字典构建对第一阶段进行测试,结果以下:
modify_dict

能够看到其中确实存在一些记录按咱们的规则是重复的,经过对其输出的重复记录(9条)进行分析,咱们发现,大部分记录是相似这样的

Luke;Dreher;Dreher;20403;27/11/1998;Midfielder;C. Palace;England

432330,luke-dreher,,1998-11-27,,17,,England,,,,,delete-player,10194,-,-,
432293,luke-dreher,,1998-11-27,England,17,1.84 m,England,Midfield - Central Midfield,right,,,crystal-palace-u18,6950,-,-,

即其中存在delete-player的字眼,应该是同一球员,可是相似S2中存在同一球员不一样球队的情况,这种状况只能留在后面处理,毕竟也确实是同一个球员,能够理解为只是信息冗余了,另外一种状况是这样的:

Danilo;D'Ambrosio;D'Ambrosio;3004;09/09/1988;Defender;Inter;Italy

120767,dario-dambrosio,,1988-09-09,Italy,27,1.89 m,Italy,Defence - Right-Back,right,,,bassano-virtus,9690,Jan 30. 2016,-,
55769,danilo-dambrosio,,1988-09-09,Italy,27,1.80 m,Italy,Defence - Right-Back,right,tullio-tinti,1708,inter-milan,46,Jan 30. 2014,30.06.2018,Nike

匹配率在90%以上因此产生了重复,这个后面匹配完成后能够进一步清洗,还有一种难以分辨,考虑后期根据球队匹配配清晰

Rodrigue Casimir;Ninga;Ninga;19220;17/05/1993;Forward;Montpellier;Chad

392810,casimir-ninga,,1993-05-17,Chad,22,1.86 m,Chad,Striker - Centre Forward,right,new-star-sport-promotion,1119,hsc-montpellier,969,Aug 31. 2015,30.06.2020,
210948,rodrigue-ninga,,1993-05-17,,22,,Chad,Striker,,,,elect-sport-fc,48525,-,-,

放宽日期限制

在第二阶段,我试图放宽中间的出生日期的限制来对第一阶段为匹配的数据进行匹配(由于考虑到可能大部分数据是因为两边数据源的日期不一致所形成的)

  • 仅提取出生年份
    主要涉及的是一个对于出生日期中的字符串分割提取而后重构字典,代码以下:

def build_dict3(source_file):
    new_dict = {}
    with open(source_file, 'rb')as csv_file:
        data = csv.DictReader(csv_file, delimiter=",")
        for row in data:
            # print row
            item = new_dict.get(row['nationality'], dict())
            # print item
            # sub_item = item.get(row['date of birth'], dict())
            year = str.split(row['date of birth'], '-')[0]
            # print year
            sub_item = item.get(year, dict())
            # print sub_item
            # sub_item[row['name']] = {k: row[k] for k in ('id', 'complete name', 'place of birth',
            #                                              'age', 'height', 'position', 'foot',
            #                                              "player's agent",'agent id','current club',
            #                                              'club id', 'in the team since', 'contract until',
            #                                              'outfitter')}
            # consider the condition more than 1 person in the same nationality,same birth year and even same name
            sub_item.setdefault(row['name'], []).append({k: row[k] for k in ('id', 'complete name', 'place of birth',
                                                                             'age', 'height', 'position', 'foot',
                                                                             "player's agent", 'agent id',
                                                                             'current club',
                                                                             'club id', 'in the team since',
                                                                             'contract until',
                                                                             'outfitter')})
            # sub_item[row['name']]={'id':row['id']}
            # print sub_item
            item[year] = sub_item
            # print item
            # print '++++++++++++++++++++++++++++++++++'
            new_dict[row['nationality']] = item
    # print '+++++++++++++++++++++++++++++++++++++'
    # print new_dict
    return new_dict

其中核心部分是这里:

year = str.split(row['date of birth'], '-')[0]
            sub_item = item.get(year, dict())          
            sub_item.setdefault(row['name'], []).append({k: row[k] for k in ('id', 'complete name', 'place of birth',
                                                                             'age', 'height', 'position', 'foot',
                                                                             "player's agent", 'agent id',
                                                                             'current club',
                                                                             'club id', 'in the team since',
                                                                             'contract until',
                                                                             'outfitter')})

而后对第一阶段产生的black_list.csv进行测试,结果以下
year
乍一看彷佛一会儿解决了一半的不匹配情况,可是考虑到这样一下放宽可能会产生较多地重复匹配甚至错误匹配,因此故技重施导出以前S1中重复匹配S2中的数据记录,结果分析发现存在很多错误匹配的情形,因此这种匹配方法不太合适,那么提取年-月呢?
一样的此次是提取出生日期的年份+月份来进行过滤,代码核心就是用了一个字符串的切片,其他的和上面的相似就不贴了,测试结果以下:
year-month
可见一来匹配率不高,二来分析重复匹配数据发现竟然也存在错误匹配的记录(即同国籍,同出生年月并且姓名知足匹配要求的人(经过了子集判断或者姓名字段模糊匹配率在88%以上)),因此看来这么一条死路不太可行,因而咱们把目光投向了S2剩余的属性;

首先是球员位置,前面的分析发现这一属性较难匹配,须要结合足球知识手动构建映射字典来转换,后期没有办法的话再考虑;那么就只好看看俱乐部-球队的映射字典了;

构建俱乐部-球队映射字典颇费功夫,在对于两个数据源中对应俱乐部和球队的字段的分析过程当中我发现,s1中对于俱乐部的记录更为详尽,包括了同一只俱乐部下不一样‘梯队’的球队,例如:

s1中的

"celta-de-vigo"
"celta-vigo-b"
"celta-vigo-juvenil-a"
"celta-vigo-youth"

对应s2中的

Celta

此外就是s2中存在一些简写,因此须要手动匹配一些记录,例如

afc-bournemouth        ->      B'mouth

特殊字符须要处理,例如

1-fc-koln        ->        1. FC Köln

因此相似于以前对于姓名中字符的处理,代码以下:

# format the row value of 'team', eliminate "'", decode the Latin char, replace the space with '-'
def simple_format_for_team(str_val):
    l_val = re.findall(r"[\w']+", str.lower(Latin2ASCII.ud(str_val.decode('utf-8', 'ignore'))))
    l_val = [s.replace("'", ' ') for s in l_val]
    return '-'.join(l_val)

而对于球队名的映射,则须要参照以前国家名的映射,并且更为复杂,由于此处球队名处于中间层的位置,对于字典传递值的须要准确你想要传递的部分,再就是须要考虑重名的情形,由于前面所述存在多种s1的不一样梯队的同一俱乐部对应s2中的一个俱乐部,因此转换后须要判断当前俱乐部是否已存在,从而判断是建立仍是添加元素,这里还用到了列表的嵌套因此层级比较复杂;
并且在代码书写的过程当中还发现一个问题,就是可能经转换后s1与s2之间存在名称彻底一致的俱乐部,这个时候个人作法是判断其值的变量类型是字典仍是列表来进行不一样的操做,可是以来这样代码不够优雅,二来不是很符合《learning python》中所述的python做为动态语言不该该将变量类型固定的python编码思惟;可是一时却又想不出其余的好方法,并且这样一来后面对于数据源的匹配也须要进行分两种类型判断的操做:代码以下

def club2team(built_dict,club_team_dict):
    for row in built_dict:
        for sub_row in built_dict[row].keys():
            for key in club_team_dict:
                if sub_row==key:
                    list1=[]
                    if club_team_dict[sub_row] in built_dict[row].keys():
                        if isinstance(built_dict[row][club_team_dict[sub_row]],dict):
                            list1.append(built_dict[row][club_team_dict[sub_row]])
                        elif isinstance(built_dict[row][club_team_dict[sub_row]],list):
                            list1=list1+built_dict[row][club_team_dict[sub_row]]
                    list1.append(built_dict[row].pop(sub_row))
                    built_dict[row][club_team_dict[sub_row]]=list1
    return built_dict

可是这样进行最后的匹配发现匹配数量也只有80来条,仅仅达到零头,因此进一步分析发现问题仍是在姓名上面,而对数据的分析发现很多记录能够经过s1中的complete name来进行匹配(自己complete name是做为姓名匹配的最佳字段,几乎和s2中拼接起来的first_namelast_name一致,遗憾的是s1中的complete name并不彻底,大多数记录都是空值,只有少许名字出奇的长的人才会有这个字段的值),并且一样的对于前面第一阶段的姓名匹配加入complete name的辅助匹配,发现匹配率有所提高,代码以下:

# second data match by 3-level nested dict(nationality->club->name)
with open(src_file, 'rb') as src_csv:
    # reader = csv.DictReader(src_csv, delimiter=',')
    # ------------------if the delimiter for header is ',' while ';' for rows
    reader = csv.reader(src_csv, delimiter=',')
    fieldnames = next(reader)
    reader = csv.DictReader(src_csv, fieldnames=fieldnames, delimiter=';')

    for row in reader:
        total_list.append(row['players.player_id'])
        # eliminate the special Latin character in country name
        nationality = Country_Format.country_name_format(row['players.country'])
        team=Transfer_Team_Name.simple_format_for_team(row['players.team'])

        if nationality in S1_2:
            if team in S1_2[nationality]:
                s1 = row['players.first_name'] + ' ' + row['players.last_name']
                # s2 = S1_2[nationality][team].keys()
                s3=row['players.vis_name']      # or match by vis_name in s2
                for s2 in S1_2[nationality][team]:  # the structure changed by nested a list,traverse the team name
                    if isinstance(s2,dict):
                        s2_keys=s2.keys()
                        for i in s2_keys:
                            if Name_Match.name_match(s1, i):
                                for index in s2[i]:
                                    # print "index['id']=",index['id']
                                    id1_list.append(index['id'])
                                    id2_list.append(row['players.player_id'])
                                    id_dict.setdefault(row['players.player_id'], []).append(index['id'])
                            elif Name_Match.name_match(s3, i):
                                for index in s2[i]:
                                    id1_list.append(index['id'])
                                    id2_list.append(row['players.player_id'])
                                    id_dict.setdefault(row['players.player_id'], []).append(index['id'])
                    elif isinstance(s2,list):
                        s2_keys=[]
                        for key in s2:
                            s2_keys=s2_keys+key.keys()
                            # print key
                            # print '----------'
                        for i in s2_keys:
                            for key in s2:
                                if Name_Match.name_match(s1, i) and i in key.keys():
                                    for index in key[i]:
                                        # print index
                                        id1_list.append(index['id'])
                                        id2_list.append(row['players.player_id'])
                                        id_dict.setdefault(row['players.player_id'], []).append(index['id'])
                                elif Name_Match.name_match(s3, i) and i in key.keys():
                                    for index in key[i]:
                                        id1_list.append(index['id'])
                                        id2_list.append(row['players.player_id'])
                                        id_dict.setdefault(row['players.player_id'], []).append(index['id'])
        else:
            country_list.append(nationality)
            print row['players.first_name'] + ' ' + row['players.last_name']

其中对于姓名匹配我进行了修改,讲规则统一写到一个方法里,以下:

# put the name match rules together
def name_match(s1,s2):
    if name_match_by_fuzz(s1,s2)==True:
        return True
    elif name_match_by_set(s1,s2)==True:
        return True
    elif name_match_by_set2(s1,s2)==True:
        return True
    return False

接以前的实验记录

在加入complete name这一字段后获得以下实验结果第一阶段匹配率达到 95.99%,剩余159条记录未匹配而第二阶段则达到52.20%,剩余76条记录未匹配,最终是采起手工匹配,而其中有21条是匹配不到的

相关文章
相关标签/搜索