Определите, в каком полигоне находится точка, а затем примените имя этого полигона в качестве нового столбца к большому pandas кадру данных. - PullRequest
1 голос
/ 26 марта 2020

У меня есть большой фрейм данных, содержащий данные о местонахождении с разных кораблей по всему миру. imoNo - это идентификатор корабля. Ниже приведен пример фрейма данных: enter image description here, и вот код для его воспроизведения:

# 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, где плавает судно. Примерно так, как показано ниже: enter image description here

Для этого я нашел набор данных в формате .shp на этой ссылке (морские районы МГО v3), который выглядит следующим образом: enter image description here

Итак, способ, которым я делаю это, проходит через каждый (long, lat) набор данных кораблей, проверьте, какой полигон является этой парой внутри и, наконец, вернуть имя моря соответствующего многоугольника. Это мой код:

### 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) 

Однако это очень трудоемкий процесс. Я проверил локально на своем Ma c в первых 1000 строках ship_df, и для запуска потребовалось около 1 минуты. Если он будет расти линейно, мне понадобится около 14 дней для всего набора данных: -D.

Любая идея по оптимизации вышеуказанной функции будет принята.

Спасибо!

Ответы [ 2 ]

0 голосов
/ 17 апреля 2020

Наконец, у меня есть кое-что быстрее, чем первоначальный вопрос.

Во-первых, я создал многоугольник, описывающий ограничивающую рамку, используя информацию из набора данных морских районов МГО

# 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 (что намного быстрее, чем геометрия, поскольку это просто прямоугольник). Когда точка попадает в несколько ящиков (для граничащих морей), тогда и только тогда она смотрит на начальные многоугольники, чтобы найти правильное имя моря среди совпадающих ящиков (а не во всех многоугольниках).

# 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]

Этот момент значительно сократил время. Чтобы еще больше увеличить его, я использовал многопроцессорность для распараллеливания ядра процессора. Идея была взята из другого поста StackOverflow, который я сейчас не помню, но здесь приведен код.

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

Наконец, вместо использования функции apply для get_seaname (), я использовал ее для parallelize_dataframe () функция для запуска на всех доступных ядрах процессора:

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

Надеюсь, мое решение поможет и другим людям!

0 голосов
/ 26 марта 2020

Попробуйте это,

Сделайте Point из каждого лота длинным с 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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...