使用geopy和pandas创建带有坐标的新列。

18

我有一个df:

import pandas as pd
import numpy as np
import datetime as DT
import hmac
from geopy.geocoders import Nominatim
from geopy.distance import vincenty

df


     city_name  state_name  county_name
0    WASHINGTON  DC  DIST OF COLUMBIA
1    WASHINGTON  DC  DIST OF COLUMBIA
2    WASHINGTON  DC  DIST OF COLUMBIA
3    WASHINGTON  DC  DIST OF COLUMBIA
4    WASHINGTON  DC  DIST OF COLUMBIA
5    WASHINGTON  DC  DIST OF COLUMBIA
6    WASHINGTON  DC  DIST OF COLUMBIA
7    WASHINGTON  DC  DIST OF COLUMBIA
8    WASHINGTON  DC  DIST OF COLUMBIA
9    WASHINGTON  DC  DIST OF COLUMBIA

我希望能够获取下面数据框中任意一列的纬度和经度坐标。当涉及到单个位置的文档时,http://geopy.readthedocs.org/en/latest/#data的文档非常直观易懂。

>>> from geopy.geocoders import Nominatim
>>> geolocator = Nominatim()
>>> location = geolocator.geocode("175 5th Avenue NYC")
>>> print(location.address)
Flatiron Building, 175, 5th Avenue, Flatiron, New York, NYC, New York,     ...
>>> print((location.latitude, location.longitude))
(40.7410861, -73.9896297241625)
>>> print(location.raw)
{'place_id': '9167009604', 'type': 'attraction', ...}

然而,我希望将该函数应用于df中的每一行并创建一个新列。我尝试了以下方法
df['city_coord'] = geolocator.geocode(lambda row: 'state_name' (row))

但是我觉得我的代码缺少了一些东西,因为我得到了以下结果:

    city_name   state_name  county_name coordinates
0    WASHINGTON  DC  DIST OF COLUMBIA    None
1    WASHINGTON  DC  DIST OF COLUMBIA    None
2    WASHINGTON  DC  DIST OF COLUMBIA    None
3    WASHINGTON  DC  DIST OF COLUMBIA    None
4    WASHINGTON  DC  DIST OF COLUMBIA    None
5    WASHINGTON  DC  DIST OF COLUMBIA    None
6    WASHINGTON  DC  DIST OF COLUMBIA    None
7    WASHINGTON  DC  DIST OF COLUMBIA    None
8    WASHINGTON  DC  DIST OF COLUMBIA    None
9    WASHINGTON  DC  DIST OF COLUMBIA    None

我希望使用Lambda函数来实现类似以下内容的功能:
     city_name  state_name  county_name  city_coord
0    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
1    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
2    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
3    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
4    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
5    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
6    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
7    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
8    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456 
9    WASHINGTON  DC  DIST OF COLUMBIA    38.8949549, -77.0366456
10   GLYNCO      GA  GLYNN               31.2224512, -81.5101023

我非常感谢您的帮助。在获取坐标后,我想将它们绘制在地图上。如果有任何推荐的绘制坐标的资源,将不胜感激。谢谢。

2个回答

19

您可以调用 apply,并像以下方式传递您想要在每一行上执行的函数:

In [9]:

geolocator = Nominatim()
df['city_coord'] = df['state_name'].apply(geolocator.geocode)
df
Out[9]:
    city_name state_name       county_name  \
0  WASHINGTON         DC  DIST OF COLUMBIA   
1  WASHINGTON         DC  DIST OF COLUMBIA   

                                          city_coord  
0  (District of Columbia, United States of Americ...  
1  (District of Columbia, United States of Americ...  

您可以访问纬度和经度属性:

In [16]:

df['city_coord'] = df['city_coord'].apply(lambda x: (x.latitude, x.longitude))
df
Out[16]:
    city_name state_name       county_name                       city_coord
0  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)
1  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)

或者通过调用apply两次来一行完成:

In [17]:
df['city_coord'] = df['state_name'].apply(geolocator.geocode).apply(lambda x: (x.latitude, x.longitude))
df

Out[17]:
    city_name state_name       county_name                       city_coord
0  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)
1  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)

另外,你尝试的geolocator.geocode(lambda row: 'state_name' (row))没有起作用,这就是为什么你的一列中充满了None值。

编辑

@leb在这里提出了一个有趣的观点,如果你有很多重复的值,那么为每个唯一值进行地理编码将更加高效,然后添加此内容:

In [38]:
states = df['state_name'].unique()
d = dict(zip(states, pd.Series(states).apply(geolocator.geocode).apply(lambda x: (x.latitude, x.longitude))))
d

Out[38]:
{'DC': (38.8937154, -76.9877934586326)}

In [40]:    
df['city_coord'] = df['state_name'].map(d)
df

Out[40]:
    city_name state_name       county_name                       city_coord
0  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)
1  WASHINGTON         DC  DIST OF COLUMBIA  (38.8937154, -76.9877934586326)

所以上述代码使用 unique 获取所有唯一值,构建字典并调用 map 进行查找和添加坐标,这比逐行地进行地理编码更有效。


1
我仍然遇到这个错误:GeocoderTimedOut:服务超时。这是我自己的问题吗? - Dave
你是在我的原始代码还是优化版本中遇到了这个错误?如果地理编码超时,你可能需要分块处理数据。 - EdChum
在我的情况下,如果地址不在geopy数据库中,则相同的代码d = dict(zip(states,pd.Series(states).apply(geolocator.geocode).apply(lambda x:(x.latitude,x.longitude))))会失败并发送响应“None” - 你能建议如何处理它吗? - NoobVB

5

赞同并接受@EdChum的回答,我只想添加一些东西。他的方法完美无缺,但是从个人经验出发,我想分享一些事情:

处理地理编码时,如果有多个重复的城市/州组合,只发送一个以获取地理编码,然后将其复制到下面的其他行中,速度会更快:

对于大型数据,这非常有用,可以通过以下两种方式完成:

  1. 基于您的数据,因为行看起来完全重复,只有在您想要时才删除多余的行并对其中一个执行地理编码。这可以使用drop_duplicate完成
  2. 如果要保留所有行,请按城市/州组合分组,应用地理编码到第一个调用head(1)的行,然后复制到其余行。

原因是每次调用Nominatim时,即使在一行中排队相同的城市/州,也会存在一些小的延迟问题。当您的数据变得越来越大时,这个小的延迟会变得更糟,导致响应时间巨大并可能超时。

再次强调,这些都是亲身经历。如果现在没有好处,请在将来考虑。


1
这是一个有趣的观点,最好只获取唯一值,将其进行地理编码,然后合并回来,我会更新我的答案。 - EdChum
谢谢您的回复。非常有用的信息!虽然当我查看了[:5]行数据时,我收到了一个好的数据框。但是当我将该函数应用于所有(200,000条记录)时,我收到了超时错误。我将不得不进行分组,然后再应用。非常感谢。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接