确定一个点在哪个多边形中,然后将该多边形的名称作为新列应用于大型 pandas 数据框

Determine in which polygon a point is, and then apply the name of that polygon as a new column to a large pandas dataframe

我有一个大型数据框,其中包含来自世界各地各种船只的位置数据。 imoNo是船号。以下是数据框的示例: 这是重现它的代码:

# intialise data of lists. 
ships_data = {'imoNo':[9321483, 9321483, 9321483, 9321483, 9321483], 
                    'Timestamp':['2020-02-22 00:00:00', '2020-02-22 00:10:00', '2020-02-22 00:20:00', '2020-02-22 00:30:00', '2020-02-22 00:40:00'],
                    'Position Longitude':[127.814598, 127.805634, 127.805519, 127.808548, 127.812370],
                    'Position Latitude':[33.800232, 33.801899, 33.798885, 33.795799, 33.792931]} 

# Create DataFrame 
ships_df = pd.DataFrame(ships_data) 

我需要做的是在数据框的末尾添加一列,该列将标识 sea_name 船只航行的位置。像下面这样的东西:

为了到达那里,我在 this link (IHO Sea Areas v3) 找到了一个 .shp 格式的数据集,如下所示:

所以,我的方法是遍历船舶数据集的每个(经度,纬度),检查其中的那对多边形,最后 return 匹配的海洋名称多边形。这是我的代码:

### Load libraries
import numpy as np
import pandas as pd
import geopandas as gp
import shapely.speedups
from shapely.geometry import Point, Polygon
shapely.speedups.enable()

### Check and map lon lat pair with sea name
def get_seaname(long,lat):
    pnt = Point(long,lat)
    for i,j in enumerate(iho_df.geometry):
        if pnt.within(j):
            return iho_df.NAME.iloc[i]


### Apply the above function to the dataframe
ships_df['sea_name'] = ships_df.apply(lambda x: get_seaname(x['Position Longitude'], x['Position Latitude']), axis=1) 

但是,这是一个非常耗时的过程。我在我的 Mac 上本地测试了 ships_df 的前 1000 行,运行 花了大约 1 分钟。如果它线性增长,那么整个数据集我将需要大约 14 天 :-D.

任何优化上述功能的想法将不胜感激。

谢谢!

试试这个,

用apply在每个经纬度上做点(无法实现更快的方法,欢迎帮忙)

import numpy as np
import pandas as pd
import geopandas as gp
import shapely.speedups
from shapely.geometry import Point, Polygon
shapely.speedups.enable()

# I am still uncomfortable with this. More ideas on speeding up this part are welcome
ships_df['point'] = ships_df.apply(lambda x: Point(x['Position Longitude'], x['Position Latitude']), axis=1)

现在将您的函数矢量化以处理 Point

def get_seaname(pnt:Point):
    for i,j in enumerate(iho_df.geometry):
        if pnt.within(j):
            return iho_df.NAME.iloc[i]

既然您的方法适用于单个点,请将点列转换为 Point 对象的向量并向量化您的方法

get_seaname = np.vectorize(get_seaname)

ships_df['sea_name'] = pd.Series(get_seaname(ships_df['point'].values))

终于,我有比最初问题更快的东西了。

首先,我使用来自 IHO 海域数据集的信息创建了一个描述边界框的多边形

# Create a bbox polygon
iho_df['bbox'] = iho_df.apply(lambda x: Polygon([(x['min_X'], x['min_Y']), (x['min_X'], x['max_Y']), (x['max_X'], x['max_Y']), (x['max_X'], x['min_Y'])]), axis=1)

然后,我更改了函数,以便首先查看 bbox(这比 geometry 快很多,因为它只是一个矩形)。当一个点落在多个框内(对于边界海域)时,然后并且只有在那时它才会查看初始多边形以在匹配框(而不是在所有多边形内)中找到正确的海洋名称。

# Function that checks and maps lon lat pair with sea name
def get_seaname(long,lat):
    pnt = Point(long,lat)
    names = []
    # Check within each bbox first to note the polygons to look at
    for i,j in enumerate(iho_df.bbox):
        if pnt.within(j):
            names.append(iho_df.NAME.iloc[i])
    # Return nan for no return
    if len(names)==0:
        return np.nan
    # Return the single name of the sea 
    elif len(names)==1:
        return names[0]
    # Run the pnt.within() only for the polygons within the collected sea names
    else:
        limitizez_df = iho_df[iho_df['NAME'].isin(names)].reset_index(drop=True)
        for k,n in enumerate(limitizez_df.geometry):
            if pnt.within(n):
                return limitizez_df.NAME.iloc[k]

这个大大缩短了时间。为了进一步提升它,我使用多处理在 CPU 核心之间进行并行化。这个想法来自另一个 Whosebug post 我现在不记得了,但这是代码。

import multiprocessing as mp

# Function that parallelizes the apply function among the cores of the CPU
def parallelize_dataframe(df, func, n_cores):
    df_split = np.array_split(df, n_cores)
    pool = Pool(n_cores)
    df = pd.concat(pool.map(func, df_split))
    pool.close()
    pool.join()
    return df

# Function that adds a sea_name column in the main dataframe
def add_features(df):
    # Apply the function
    df['sea_name'] = df.apply(lambda x: get_seaname(x['Position Longitude'], x['Position Latitude']), axis=1)
    return df

最后,我没有使用 get_seaname() 的应用函数,而是将它用于 parallelize_dataframe() 函数 运行 所有可用的 CPU核心数:

### Apply the above function to the dataframe
ships_df = parallelize_dataframe(ships_df, add_features, n_cores=mp.cpu_count())

我希望我的解决方案也能帮助到其他人!