如何使用Python智能匹配两个数据帧(使用pandas或其他方法)?

时间:2016-09-06 15:56:19

标签: python pandas dataframe match

我有一个大熊猫数据框,由世界各城市的名称以及城市所属的国家/地区组成,

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]

6 个答案:

答案 0 :(得分: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功能。

答案 1 :(得分: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

答案 2 :(得分:2)

我的建议是经过一些预处理后,将地址减少到城市级信息(我们不需要准确,但要尽力;删除数字等),然后合并基于的数据帧文本的相似之处。

您可以考虑文本相似性度量,例如levenshtein distance或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行。(为了降低计算复杂性,可以截断设置通过仅比较具有相同第一个字符的行来进行比较

levenshtein距离:https://en.wikipedia.org/wiki/Levenshtein_distance

jaro-winkler:https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance

答案 3 :(得分:1)

我认为一个简单的想法是创建从任何地址的任何单词或单词序列到单词所属的完整地址的映射,假设其中一个地址单词是引用。在第二步中,我们将其与您拥有的一组已知城市相匹配,并且任何不是已知城市的东西都会被丢弃。

从每个单词到地址的映射非常简单:

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)

答案 4 :(得分: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

答案 5 :(得分:0)

我为我的项目创建了一个小型库,特别是模糊连接。它可能不是最快的解决方案,但它可能有所帮助,随意使用。 Link to my GitHub repo