AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79126042
Accepted
Sebastian Meckovski
Sebastian Meckovski
Asked: 2024-10-25 22:03:01 +0800 CST2024-10-25 22:03:01 +0800 CST 2024-10-25 22:03:01 +0800 CST

Como remover eficientemente círculos sobrepostos do conjunto de dados

  • 772

Tenho um conjunto de dados de cerca de 20.000 registros que representam cidades globais com população > 20.000. Estimei o raio que descreve mais ou menos o tamanho da cidade. Não é exatamente preciso, mas para meus propósitos será o suficiente.

que estou carregando-o no meu objeto dataframe Panda. Abaixo está o exemplo

name_city,country_code,latitude,longitude,geohash,estimated_radius,population
Vitry-sur-Seine,FR,48.78716,2.40332,u09tw9qjc3v3,1000,81001
Vincennes,FR,48.8486,2.43769,u09tzkx5dr13,500,45923
Villeneuve-Saint-Georges,FR,48.73219,2.44925,u09tpxrmxdth,500,30881
Villejuif,FR,48.7939,2.35992,u09ttdwmn45z,500,48048
Vigneux-sur-Seine,FR,48.70291,2.41357,u09tnfje022n,500,26692
Versailles,FR,48.80359,2.13424,u09t8s6je2sd,1000,85416
Vélizy-Villacoublay,FR,48.78198,2.19395,u09t9bmxdspt,500,21741
Vanves,FR,48.82345,2.29025,u09tu059nwwp,500,26068
Thiais,FR,48.76496,2.3961,u09tqt2u3pmt,500,29724
Sèvres,FR,48.82292,2.21757,u09tdryy15un,500,23724
Sceaux,FR,48.77644,2.29026,u09tkp7xqgmw,500,21511
Saint-Mandé,FR,48.83864,2.41579,u09tyfz1eyre,500,21261
Saint-Cloud,FR,48.84598,2.20289,u09tfhhh7n9u,500,28839
Paris,FR,48.85341,2.3488,u09tvmqrep8n,12000,2138551
Orly,FR,48.74792,2.39253,u09tq6q1jyzt,500,20528
Montrouge,FR,48.8162,2.31393,u09tswsyyrpr,500,38708
Montreuil,FR,48.86415,2.44322,u09tzx7n71ub,2000,111240
Montgeron,FR,48.70543,2.45039,u09tpf83dnpn,500,22843
Meudon,FR,48.81381,2.235,u09tdy73p38y,500,44652
Massy,FR,48.72692,2.28301,u09t5yqqvupx,500,38768
Malakoff,FR,48.81999,2.29998,u09tsr6v13tr,500,29420
Maisons-Alfort,FR,48.81171,2.43945,u09txtbkg61z,1000,53964
Longjumeau,FR,48.69307,2.29431,u09th0q9tq1s,500,20771
Le Plessis-Robinson,FR,48.78889,2.27078,u09te9txch23,500,22510
Le Kremlin-Bicêtre,FR,48.81471,2.36073,u09ttwrn2crz,500,27867
Le Chesnay,FR,48.8222,2.12213,u09t8rc3cjwz,500,29154
La Celle-Saint-Cloud,FR,48.85029,2.14523,u09tbufje6p6,500,21539
Ivry-sur-Seine,FR,48.81568,2.38487,u09twq8egqrc,1000,57897
Issy-les-Moulineaux,FR,48.82104,2.27718,u09tezd5njkr,1000,61447
Fresnes,FR,48.75568,2.32241,u09tkgenkj6r,500,24803
Fontenay-aux-Roses,FR,48.79325,2.29275,u09ts4t92cn3,500,24680
Clamart,FR,48.80299,2.26692,u09tes6dp0dn,1000,51400
Choisy-le-Roi,FR,48.76846,2.41874,u09trn12bez7,500,35590
Chevilly-Larue,FR,48.76476,2.3503,u09tmmr7mfns,500,20125
Châtillon,FR,48.8024,2.29346,u09tshnn96xx,500,32383
Châtenay-Malabry,FR,48.76507,2.26655,u09t7t6mn7yj,500,32715
Charenton-le-Pont,FR,48.82209,2.41217,u09twzu3r9hq,500,30910
Cachan,FR,48.79632,2.33661,u09tt5j7nvqd,500,26540
Bagnolet,FR,48.86667,2.41667,u09tyzzubrxb,500,33504
Bagneux,FR,48.79565,2.30796,u09tsdbx727w,500,38900
Athis-Mons,FR,48.70522,2.39147,u09tn6t2mr16,500,31225
Alfortville,FR,48.80575,2.4204,u09txhf6p7jp,500,37290
Quinze-Vingts,FR,48.84656,2.37439,u09tyh0zz6c8,500,26265
Croulebarbe,FR,48.81003,2.35403,u09tttd5hc5f,500,20062
Gare,FR,48.83337,2.37513,u09ty1cdbxcq,1000,75580
Maison Blanche,FR,48.82586,2.3508,u09tv2rz1xgx,1000,64302

Abaixo está a representação visual da amostra de dados: insira a descrição da imagem aqui

Meu objetivo é encontrar um algoritmo eficiente que remova os círculos que se cruzam e mantenha apenas aquele com o maior population.

Minha abordagem inicial foi determinar quais círculos estão se cruzando usando a fórmula de Haversine. O problema era que, para verificar cada registro em busca de interseções com outros, ele precisava percorrer todo o conjunto de dados. A complexidade de tempo disso era muito alta.

Minha segunda abordagem foi segregar o conjunto de dados por código de país e executar as comparações por blocos:

  def _remove_intersecting_circles_for_country(df_country):
    """Helper function to remove intersections within a single country."""
    indices_to_remove = set()
    for i in range(len(df_country)):
      for j in range(i + 1, len(df_country)):
        distance = haversine(df_country['latitude'].iloc[i], df_country['longitude'].iloc[i],
                            df_country['latitude'].iloc[j], df_country['longitude'].iloc[j])
        if distance < df_country['estimated_radius'].iloc[i] + df_country['estimated_radius'].iloc[j]:
          if df_country['population'].iloc[i] < df_country['population'].iloc[j]:
            indices_to_remove.add(df_country.index[i]) 
          else:
            indices_to_remove.add(df_country.index[j])
    return indices_to_remove

  all_indices_to_remove = set()
  for country_code in df['country_code'].unique():
    df_country = df[df['country_code'] == country_code]
    indices_to_remove = _remove_intersecting_circles_for_country(df_country)
    all_indices_to_remove.update(indices_to_remove)

  new_df = df.drop(index=all_indices_to_remove)
  return new_df

Isso melhorou significativamente o desempenho porque para verificar cada registro, precisamos apenas verificar todos os registros com o mesmo country_code. Mas isso ainda faz muitas comparações desnecessárias

python
  • 3 3 respostas
  • 83 Views

3 respostas

  • Voted
  1. Best Answer
    Pieter
    2024-10-26T05:56:41+08:002024-10-26T05:56:41+08:00

    Depois de ter os círculos como polígonos, determinar as interseções entre os polígonos será muito rápido se você usar um índice espacial para fazer isso.

    Então, você pode:

    • bufferize os pontos para círculos. No WGS84 isso seria impreciso, então o buffering precisa ser feito por meio de um sidestep para uma projeção equidistante.
    • calcular quais círculos se cruzam pode ser feito muito rápido usando um índice espacial. Por exemplo, geopandas.sjoin usa um índice espacial por baixo dos panos.
    • agora você pode determinar quais círculos cruzam um círculo que representa uma cidade com uma população maior e removê-los.

    Resultado (vermelho: cidades originais, azul: cidades mantidas):

    visualização do resultado

    Código:

    from pathlib import Path
    import geopandas as gpd
    from matplotlib import pyplot as plt
    from pyproj import CRS, Transformer
    from shapely.geometry import Point
    from shapely.ops import transform
    
    
    def geodesic_point_buffer(lat, lon, distance):
        # Azimuthal equidistant projection
        aeqd_proj = CRS.from_proj4(
            f"+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0")
        tfmr = Transformer.from_proj(aeqd_proj, aeqd_proj.geodetic_crs)
        buf = Point(0, 0).buffer(distance)  # distance in metres
        return transform(tfmr.transform, buf)
    
    # Read the csv file with data
    #csv_path = Path(__file__).resolve().with_suffix(".csv")
    csv_path = "https://raw.githubusercontent.com/theroggy/pysnippets/refs/heads/main/pysnippets/stackoverflow_questions/2024/Q4/2024-10-25_circles.csv"
    points_df = gpd.read_file(csv_path, autodetect_type=True)
    
    # Convert the points to circles by buffering them
    points_buffer_gdf = gpd.GeoDataFrame(
        points_df,
        geometry=points_df.apply(
            lambda row : geodesic_point_buffer(row.latitude, row.longitude, row.estimated_radius), axis=1
        ),
        crs=4326,
    )
    
    # Determine the intersecting city buffers (result includes self-intersections)
    intersecting_gdf = points_buffer_gdf.sjoin(points_buffer_gdf)
    
    # Get all city buffers that intersect a city with a larger population
    intersecting_larger_population_df = intersecting_gdf.loc[
        intersecting_gdf.population_left < intersecting_gdf.population_right
    ]
    
    # Remove the city buffers that intersect with a larger population city buffer
    result_gdf = points_buffer_gdf[
        ~points_buffer_gdf.index.isin(intersecting_larger_population_df.index)
    ]
    
    # Plot result
    ax = points_buffer_gdf.boundary.plot(color="red")
    result_gdf.boundary.plot(color="blue", ax=ax)
    plt.show()
    
    • 2
  2. mosfet
    2024-10-26T00:14:26+08:002024-10-26T00:14:26+08:00

    Da representação visual, a primeira coisa que me veio à mente foi usar uma abordagem de processamento de imagem com contornos mais externos (e mapear coordenadas geográficas para coordenadas de pixel). Não tenho certeza se é adequado para conjuntos de dados muito grandes com espaçamentos muito grandes entre cidades (mas por que alguém deveria ver se Lyon se sobrepõe a Paris?), mas nas amostras fornecidas perto de Paris, é vezes mais rápido do que a solução atual.

    Estamos em O(n log(n)) + O(n) para complexidade de tempo comparado com o O(n^2 atual). No entanto, a principal compensação pode ser a criação de imagem com resolução suficiente. Seu processamento de contorno deve ser razoável considerando o OpenCV C++ otimizado sob o capô

    import numpy as np
    import cv2
    import pandas as pd
    from typing import Tuple
    from io import StringIO
    
    def detect_non_overlapping_circles(df: pd.DataFrame, image_size:Tuple[int, int] = (128, 128)):
        img = np.zeros(image_size, dtype=np.uint8) # Black image
    
        lat_min, lat_max = df['latitude'].min(), df['latitude'].max()
        lon_min, lon_max = df['longitude'].min(), df['longitude'].max()
    
        def geo_to_pixel(lat: float, lon: float):
            x = int((lon - lon_min) / (lon_max - lon_min) * (image_size[0] - 100) + 50)
            y = int((lat - lat_min) / (lat_max - lat_min) * (image_size[1] - 100) + 50)
            return (x, y)
    
        # Convert radius to pixels based on the image size and coordinate range
        def radius_to_pixel(radius_meters: float):
            # Using an approximate conversion factor
            earth_circumference = 40075000  # meters
            meters_per_degree = earth_circumference / 360
            lat_range = lat_max - lat_min
            pixels_per_meter = (image_size[1] - 100) / (lat_range * meters_per_degree)
            return int(radius_meters * pixels_per_meter)
    
        df_sorted = df.sort_values('population', ascending=False)
    
        kept_indices = []
    
        for idx, row in df_sorted.iterrows():
            test_img = img.copy()
        
            # Current circle
            center = geo_to_pixel(row['latitude'], row['longitude'])
            radius = radius_to_pixel(row['estimated_radius'])
        
            # Find outermost contour at each time
            cv2.circle(test_img, center, radius, 255, -1)    
            contours, _ = cv2.findContours(test_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
            # If exactly one contour, this circle dont overlap with any one
            if len(contours) == len(kept_indices) + 1:
                kept_indices.append(idx)
                img = test_img  # Update the main image
    
        return df.loc[kept_indices]
    
    def main(df):
        result_df = detect_non_overlapping_circles(df)
        
        print(f"Original number of cities: {len(df)}")
        print(f"Cities after removing overlaps: {len(result_df)}")
        print(result_df[['name_city', 'population', 'estimated_radius']].to_string())
    
        return result_df
    
    # Not sure if you're working with csv but from what provided
    data = 
    """name_city,country_code,latitude,longitude,geohash,estimated_radius,population
    Vitry-sur-Seine,FR,48.78716,2.40332,u09tw9qjc3v3,1000,81001
    Vincennes,FR,48.8486,2.43769,u09tzkx5dr13,500,45923
    Villeneuve-Saint-Georges,FR,48.73219,2.44925,u09tpxrmxdth,500,30881
    Villejuif,FR,48.7939,2.35992,u09ttdwmn45z,500,48048
    Vigneux-sur-Seine,FR,48.70291,2.41357,u09tnfje022n,500,26692
    """ # ...
    
    df = pd.read_csv(StringIO(data))
    df.dropna()
    
    proc_df = main(df) 
    

    Com o csv fornecido e o espaçamento da cidade, funcionou bem até (128, 128) na resolução da imagem. Na minha máquina, trabalhar em uma imagem (1024, 1024) ainda era mais rápido no tempo de execução do que_remove_intersecting_circles_for_country

    • 1
  3. Jim Mischel
    2024-10-26T04:23:00+08:002024-10-26T04:23:00+08:00

    Uma maneira comum de reduzir a complexidade é sobrepor uma grade em cima do seu mapa. Por exemplo, sobreponha uma grade de quadrados que tenham 100 milhas de lado. Crie uma estrutura de dados que lhe dirá:

    1. Em que quadrado da grade uma cidade está (no centro da cidade)
    2. Quais cidades (novamente, o centro do círculo da cidade) estão localizadas dentro desse quadrado da grade

    Supondo que nenhuma cidade tenha mais de 100 milhas de raio, uma cidade só pode cruzar cidades cujos centros são:

    • Dentro de sua própria grade quadrada
    • Em um quadrado de grade adjacente
    • No caso de grandes cidades, adjacente a um quadrado de grade adjacente.

    Na ilustração abaixo, a grade marcada com '0' é a grade que contém a cidade que você está examinando no momento. As únicas cidades com as quais ela pode possivelmente cruzar são aquelas em posições de grade marcadas com '1' ou '2'. Novamente, assumindo que o tamanho da sua grade é maior do que o tamanho de qualquer cidade individual.

        X X X X X X X
        X 2 2 2 2 2 X
        X 2 1 1 1 2 X
        X 2 1 O 1 2 X
        X 2 1 1 1 2 X
        X 2 2 2 2 2 X
        X X X X X X X        
    

    Isso reduz muito o número de cidades que você precisa olhar quando estiver tentando determinar sobreposições. Em vez de 20.000 cidades, você terá... uma dúzia? Cem?

    É um pequeno gasto adicional construir essa grade para que você possa agrupar seus itens, mas o benefício no desempenho é enorme.

    Pode fazer sentido, quando você estiver construindo essa estrutura de dados, incluir para cada quadrado da grade não apenas quais cidades estão centralizadas nele, mas também quais cidades o invadem. Então , se "Smallville" estiver no quadrado da grade que você rotular, por exemplo, C2, e sua circunferência invadir C3, então C2 deve conter uma entrada que diga, na verdade, "Smallville está localizada em C2." C3 tem uma entrada que diz, "Smallville invade o quadrado da grade C3."

    Essa informação é bem fácil de computar em tempo de execução quando você precisa dela, no entanto. Talvez mantê-la por perto seja desnecessário.

    Atualizar

    Pensando bem, como você tem a lat/lon para cada cidade, você já tem a maior parte do que precisa para construir a grade. Faça sua grade com um grau de latitude por um grau de longitude. No equador, isso lhe dá quadrados de aproximadamente 69 milhas (111 km) quadrados. Isso se comprime conforme você se aproxima dos polos, mas não deve importar. Para determinar a qual grade uma cidade pertence, você apenas corta as partes decimais da sua lat/lon e indexa na grade. Você pode precisar de alguma normalização dos números (ou seja, converter de -180...180 para 0...360).

    De qualquer forma, isso permite reduzir o problema a um tamanho muito mais administrável.

    • 0

relate perguntas

  • Como divido o loop for em 3 quadros de dados individuais?

  • Como verificar se todas as colunas flutuantes em um Pandas DataFrame são aproximadamente iguais ou próximas

  • Como funciona o "load_dataset", já que não está detectando arquivos de exemplo?

  • Por que a comparação de string pandas.eval() retorna False

  • Python tkinter/ ttkboostrap dateentry não funciona quando no estado somente leitura

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve