如何用Python高效将350万条经纬度数据转换为邮政编码
一、解决SettingWithCopyWarning警告
你遇到的警告是因为df1_section = df1.iloc[:100]生成的是原DataFrame的视图而非独立副本,直接修改视图会触发警告。解决方法很简单,创建副本即可:
df1_section = df1.iloc[:100].copy() # 加.copy()生成独立副本 df1_section['start_zipcode'] = df1_section.apply(lambda x: get_zip_code(x, 'start_lat', 'start_lng'), axis=1)
二、提速处理350万条经纬度转邮编
原方法逐行调用Nominatim接口,不仅有网络延迟,还受Nominatim的请求频率限制(官方建议每秒不超过1-2次),350万条数据按这个速度要跑几十天,完全不现实。推荐以下几种优化方案,优先级从高到低:
1. 先去重再查询(最有效、零成本)
你的数据里大量重复经纬度(比如前4条完全一致),先提取唯一的经纬度对,只查询一次,再合并回原数据,能大幅减少请求量:
# 提取唯一经纬度对 unique_coords = df1[['start_lat', 'start_lng']].drop_duplicates().reset_index(drop=True) # 给唯一坐标查邮编 def get_zip_code(row): try: location = geolocator.reverse(f"{row['start_lat']}, {row['start_lng']}") return location.raw['address']['postcode'] except Exception as e: print(f"Error at {row}: {e}") return None unique_coords['start_zipcode'] = unique_coords.apply(get_zip_code, axis=1) # 合并回原DataFrame df1 = df1.merge(unique_coords, on=['start_lat', 'start_lng'], how='left')
如果你的数据里重复率很高(比如重复率90%),请求量直接从350万降到35万,效率提升10倍以上。
2. 使用异步请求并发处理
geopy支持异步调用,配合异步框架能同时发起多个请求,减少等待时间。注意要严格遵守Nominatim的请求限制,避免被封禁:
import asyncio from geopy.geocoders import Nominatim from geopy.extra.rate_limiter import AsyncRateLimiter async def async_get_zip(): geolocator = Nominatim(user_agent="my_app") # 设置每秒最多1次请求,符合Nominatim规则 reverse = AsyncRateLimiter(geolocator.reverse, min_delay_seconds=1) # 先处理唯一坐标(和方法1结合效果最佳) unique_coords = df1[['start_lat', 'start_lng']].drop_duplicates().reset_index(drop=True) # 异步批量查询 tasks = [] for idx, row in unique_coords.iterrows(): task = reverse(f"{row['start_lat']}, {row['start_lng']}") tasks.append(task) results = await asyncio.gather(*tasks) # 解析结果 unique_coords['start_zipcode'] = [ res.raw['address']['postcode'] if res and 'postcode' in res.raw['address'] else None for res in results ] # 合并回原数据 global df1 df1 = df1.merge(unique_coords, on=['start_lat', 'start_lng'], how='left') # 运行异步函数 asyncio.run(async_get_zip())
3. 本地空间匹配(适合超大数据量)
如果数据都是美国地区(从邮编格式看是美国),可以下载美国邮编边界Shapefile(比如US Census Bureau的TIGER/Line数据),用geopandas做本地空间匹配,完全不需要网络请求,速度极快:
import geopandas as gpd from shapely.geometry import Point # 1. 读取本地邮编边界文件(需要提前下载解压) zip_shp = gpd.read_file("tl_2023_us_zcta510.shp") # 文件名根据下载的版本调整 # 2. 将原DataFrame转为GeoDataFrame df1['geometry'] = df1.apply(lambda row: Point(row['start_lng'], row['start_lat']), axis=1) gdf = gpd.GeoDataFrame(df1, crs="EPSG:4326") # 3. 空间连接匹配邮编 # 先确保边界文件和数据的坐标系一致 zip_shp = zip_shp.to_crs("EPSG:4326") gdf = gdf.sjoin(zip_shp, how="left", predicate="within") # 提取邮编列(不同Shapefile的邮编列名可能是ZCTA5CE10或zipcode,根据文件调整) df1['start_zipcode'] = gdf['ZCTA5CE10']
这种方法处理350万条数据只需要几分钟,是大数据量下的最优解。
三、街道名称转邮编是否更快?
结论:不会更快,甚至可能更慢。
街道名称转邮编属于正向地理编码,和反向编码(经纬度转地址)一样需要调用API,同样受网络延迟和请求频率限制。而且街道名称可能存在歧义(比如重名街道、格式不规范),API需要更多时间解析,反而可能降低成功率和速度。除非你的街道名称是完全标准化的完整地址(包含城市、州),否则不建议用这种方法,还是优先用经纬度的优化方案。
内容的提问来源于stack exchange,提问作者then999

