2016-09-06 67 views
4

我有世界城市的名字組成的,以及國家一個熊貓據幀,哪個城市屬於,如何使用Python巧妙地匹配兩個數據框(使用熊貓或其他方式)?

city.head(3) 

    city country 
0 Qal eh-ye Now Afghanistan 
1 Chaghcharan Afghanistan 
2 Lashkar Gah Afghanistan 

和由世界大學中的其他地址的數據幀(如下所示):

df.head(3) 
    university 
0 Inst Huizhou, Huihzhou 516001, Guangdong, Peop... 
1 Guangxi Acad Sci, Nanning 530004, Guangxi, Peo... 
2 Shenzhen VisuCA Key Lab SIAT, Shenzhen, People... 

城市名稱的位置在行間不規則地分佈。我想將城市名稱與世界大學的地址相匹配。也就是說,我想知道每所大學所在的城市。希望匹配的城市名稱與每所大學的地址顯示在同一行。

我嘗試了以下方法,但它不起作用,因爲城市的位置在整個行中都是不規則的。

df['university'].str.split(',').str[0] 
+2

@fffchao歡迎您。現在如果你展示你試圖解決這個任務,那將會很棒。 – Ilya

回答

2

我會建議使用apply

city_list = city.tolist() 

def match_city(row): 
    for city in city_list: 
     if city in row['university']: return city 
    return 'None' 

df['city'] = df.apply(match_city, axis=1) 

我擔任了大學的數據的地址是不夠乾淨。如果您想進行更高級的匹配檢查,您可以調整match_city功能。

+0

謝謝。很好的答案。但是,如何防止部分匹配?例如,「紐約」的地址與約克匹配。由於城市系列實際上來自包括國家在內的數據框,哪些城市屬於哪個城市,是否可以考慮到各國信息進行匹配? – fffchao

+0

你的例子表明你的地址數據用逗號分隔。你只需要一個額外的步驟,把列表中的字符串'tolist()'返回並在每個逗號處再次分開它們。然後獲得一個列表清單,並且每個子清單中的其中一個元素應該是您的正確城市名稱。有了這個,你也可以在你的子列表中清晰地分隔你的國家名稱,以便進行匹配。 – Khris

2

爲了處理字符串的不一致結構,一個好的解決方案是使用正則表達式。我根據你的描述嘲笑了一些數據,並創建了一個從字符串中捕捉城市的功能。

在我的解決方案中,我使用numpy在沒有匹配的情況下輸出NaN值,但是您可以輕鬆地將其設置爲空字符串。我還包括一個測試用例,其中輸入爲空以顯示NaN結果。

import re 
import numpy as np 

data = ["Inst Huizhou, Huihzhou 516001, Guangdong, People's Republic of China", 
     "Guangxi Acad Sci, Nanning 530004, Guangxi, People's Republic of China", 
     "Shenzhen VisuCA Key Lab SIAT, Shenzhen, People's Republic of China", 
     "New York University, New York, New York 10012, United States of America", 
     ""] 
df = pd.DataFrame(data, columns = ['university']) 

def extract_city(row): 
    match = re.match('^[^,]*,([^,]*),', row) 
    if match: 
     city = re.sub('\d+', '', match.group(1)).strip() 
    else: 
     city = np.nan 
    return city 


df.university.apply(extract_city) 

下面是輸出:

0 Huihzhou 
1  Nanning 
2 Shenzhen 
3 New York 
4   NaN 
Name: university, dtype: object 
+0

謝謝。一些地址非常混亂,所以匹配的結果不是城市。例如,貴州省貴州省生物科學研究所,貴州省貴陽市550009,P「的地址爲」貴州生物研究所「,但」貴陽「爲城市名稱。還有一些其他類似的問題。 – fffchao

+0

您應該嘗試在提示中包含所有遇到問題的邊緣案例。根據您的原始問題,有兩種很好的解決方案,但是您已經添加了一些細節,以使我們的解決方案不完整。更好的是,只需包含您正在使用的數據的鏈接。 – shawnheide

+0

抱歉更新原始問題,這是我的第一個問題。大學地址的部分數據位於鏈接:https://www.dropbox.com/s/lxnx3h22yvi49c1/sample_univ_address.xlsx?dl = 0。再次感謝。 – fffchao

2

我的建議是,一些預處理降低地址到城市級別的信息(我們並不需要準確,但拼盡全力之後;如刪除數字等),然後根據文本相似性合併數據框。

您可能會考慮通常用於匹配單詞的文本相似性度量,如levenshtein距離或jaro-winkler。

這裏是例如用於文本相似性:

class DLDistance: 
    def __init__(self, s1): 
     self.s1 = s1 
     self.d = {} 
     self.lenstr1 = len(self.s1)  
     for i in xrange(-1,self.lenstr1+1): 
      self.d[(i,-1)] = i+1 

    def distance(self, s2): 
     lenstr2 = len(s2) 
     for j in xrange(-1,lenstr2+1): 
      self.d[(-1,j)] = j+1 

     for i in xrange(self.lenstr1): 
      for j in xrange(lenstr2): 
       if self.s1[i] == s2[j]: 
        cost = 0 
       else: 
        cost = 1 
       self.d[(i,j)] = min(
           self.d[(i-1,j)] + 1, # deletion 
           self.d[(i,j-1)] + 1, # insertion 
           self.d[(i-1,j-1)] + cost, # substitution 
          ) 
       if i and j and self.s1[i]==s2[j-1] and self.s1[i-1] == s2[j]: 
        self.d[(i,j)] = min (self.d[(i,j)], self.d[i-2,j-2] + cost) # transposition 

     return self.d[self.lenstr1-1,lenstr2-1] 

if __name__ == '__main__': 
    base = u'abs' 
    cmpstrs = [u'abs', u'sdfbasz', u'asdf', u'hfghfg'] 
    dl = DLDistance(base) 

    for s in cmpstrs: 
     print "damerau_levenshtein" 
     print dl.distance(s) 

即使,它具有的計算複雜度較高的水平,因爲它計算N * M個距離測度的時間,其中N行中的第一數據幀,在M行。第二個數據框(爲了減少computatinal的複雜性,可以截斷誰只比較了該具有相同的第一個字符的行需要比較組)

Levenshtein距離:https://en.wikipedia.org/wiki/Levenshtein_distance

哈羅 - 溫克勒:https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance

1

我認爲一個簡單的想法是創建一個映射從任何單詞或任何地址的單詞序列到該單詞是完整地址的一部分,假設其中一個地址單詞是cites。在第二步中,我們將這與已有的已知城市集相匹配,並且任何不是已知城市的東西都會被丟棄。

從每個單字到地址的映射是簡單的:

def address_to_dict(address): 
    return {word: address for word in address.split(",")} 

而且我們可以很容易地擴展,以包括所述一組雙克,三克,...,從而使大學編碼用幾句話也收集起來。看到這裏的討論:Elegant N-gram Generation in Python

然後,我們可以應用此,我們必須獲得任何單詞一個盛大映射到完整的地址的每個地址:

word_to_address_mapping = pd.DataFrame(df.university.apply(address_to_dict).tolist()).stack() 

word_to_address_mapping = pd.DataFrame(word_to_address_mapping, 
             columns=["address"]) 
word_to_address_mapping.index = word_to_address_mapping.index.droplevel(level=0) 
word_to_address_mapping 

這就產生了這樣的事情:

enter image description here

所有你需要做的就是加入你的實際城市列表:這將自動放棄word_to_address_mapping這是不是一個已知的城市,並提供一個馬大學地址和他們的城市之間。

# the outer join here should ensure that several university in the 
# same city do not overwrite each other 
pd.merge(left=word_to_address_mapping, right=city, 
     left_index=True, right_on="city", 
     how="outer) 
0

部分匹配被阻止在下面的函數中。各國的信息在與城市匹配時也會考慮。要使用這個函數,需要將大學數據框分割成列表數據類型,使得每一塊地址都分成字符串列表。

In [22]: def get_city(univ_name_split): 
    ....:  # find country from university address 
    ....:  for name in univ_name_split: 
    ....:   if name in city['country'].values: 
    ....:    country = name 
    ....:  else: 
    ....:   country = None 

    ....:  if country: 
    ....:   cities = city[city.country == country].city.values 
    ....:  else: 
    ....:   cities = city['city'].values 

    ....:  # find city from university address 
    ....:  for name in univ_name_split: 
    ....:   if name in cities: 
    ....:    return name 
    ....:  else: 
    ....:   return None 
    ....:  


In [1]: import pandas as pd 

In [2]: city = pd.read_csv('city.csv') 

In [3]: df = pd.read_csv('university.csv') 

In [4]: # splitting university name and address 

In [5]: df_split = df['university'].str.split(',') 

In [6]: df_split = df_split.apply(lambda x:[i.strip() for i in x]) 

In [10]: df 
Out[10]: 
              university 
0 Kongu Engineering College, Perundurai, Erode, ... 
1   Anna University - Guindy, Chennai, India 
2 Birla Institute of Technology and Science, Pil... 

In [11]: df_split 
Out[11]: 
0 [Kongu Engineering College, Perundurai, Erode,... 
1   [Anna University - Guindy, Chennai, India] 
2 [Birla Institute of Technology and Science, Pi... 
Name: university, dtype: object 

In [12]: city 
Out[12]: 
     city country 
0 Bangalore India 
1  Chennai India 
2 Coimbatore India 
3  Delhi India 
4  Erode India 


#This function is shorter version of above function 
In [14]: def get_city(univ_name_split): 
    ....:  for name in univ_name_split: 
    ....:   if name in city['city'].values: 
    ....:    return name 
    ....:  else: 
    ....:   return None 
    ....:  

In [15]: df['city'] = df_split.apply(get_city) 

In [16]: df 
Out[16]: 
              university  city 
0 Kongu Engineering College, Perundurai, Erode, ... Erode 
1   Anna University - Guindy, Chennai, India Chennai 
2 Birla Institute of Technology and Science, Pil...  None 
0

我爲我的項目創建了一個小型庫,尤其是模糊連接。它可能不是最快的解決方案,但它可以幫助,隨時使用。 Link to my GitHub repo

相關問題