用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條是匹配不到的

相關文章
相關標籤/搜索