Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calibrando numero aproximoado #36

Open
rafapereirabr opened this issue Feb 25, 2025 · 1 comment
Open

Calibrando numero aproximoado #36

rafapereirabr opened this issue Feb 25, 2025 · 1 comment
Milestone

Comments

@rafapereirabr
Copy link
Member

rafapereirabr commented Feb 25, 2025

contexto

Quando o numero do endereço de input não está presente na base de busca (cnefe), nós fazemos uma aproximação. Atualmente, essa aproximação é feita como um média das coordenadas do cnefe poderada pela diferença entre o numero de input e os numeros presentes no cnefe. Os resultados são razoáveis.

problema

No entanto, seria importante dar um peso maior para diferenças pequenas. Por exemplo, se o numero de input é 5 e o cnefe tem um numero 8, as coordenadas do ponto 8 deveriam ter um peso maior do que do ponto com número 100, por exemplo. Atualmente, o peso de diferenças grandes é descontado, mas apenas de maneira linear. Seria bom colocar um expoente de decaimento para dar ainda mais peso para numeros proximos.

solucao

O codigo do pacote desta operação é todo em {duckdb} e SQL. Mas para ilustrar a problema, apresento abaixo o codigo em R mesmo. O calculo atual é feito dessta maneira.

output <- df |>
  group_by(id) |>
  summarize(
    numero_input = first(numero_input),
    lat = sum((1/abs(numero_input - numero) * lat)) / sum(1/abs(numero_input - numero)),
    lon = sum((1/abs(numero_input - numero) * lon)) / sum(1/abs(numero_input - numero))
    )

Minha sugestão é elevar o a difença ao cubo. Assim:

output <- df |>
  group_by(id) |>
  summarize(
    numero_input = first(numero_input),
    lat = sum((1/abs(numero_input - numero)^3 * lat)) / sum(1/abs(numero_input - numero)^3),
    lon = sum((1/abs(numero_input - numero)^3 * lon)) / sum(1/abs(numero_input - numero)^3)
    )

reprex

Aqui um reprex que gera um exemplo dentro da funçao check_approx(). A função recebe um numero_input e um expoente expp. Quando expp = 1, o resultado da função é exatamente o comportamento do geocodebr hoje em dia.

library(dplyr)
library(sf)
library(sfheaders)
library(arrow)
library(mapview)

tudo <- geocodebr::listar_dados_cache()
tudo <- tudo[7]

cnefe <- arrow::open_dataset( tudo ) |>
  dplyr::filter(estado == 'RJ') |>
  dplyr::filter(municipio == "RIO DE JANEIRO") |>
  dplyr::filter(logradouro == "AVENIDA MINISTRO IVAN LINS") |>
  dplyr::collect()


check_approx <- function(numero_input, expp){

input <- data.frame(
  id = 1,
  logradouro = 'AVENIDA MINISTRO IVAN LINS',
  numero_input = numero_input,
  localidade = "BARRA DA TIJUCA",
  cep = '22620-110'
  )

df <- left_join(input, cnefe, by = c('logradouro', 'localidade', 'cep'))


output <- df |>
  group_by(id) |>
  summarize(
    numero_input = first(numero_input),
    lat = sum((1/abs(numero_input - numero)^expp * lat)) / sum(1/abs(numero_input - numero)^expp),
    lon = sum((1/abs(numero_input - numero)^expp * lon)) / sum(1/abs(numero_input - numero)^expp)
    )

sf_cnefe <- sfheaders::sf_point(
  obj = cnefe,
  x = 'lon',
  y = 'lat',
  keep = TRUE
)

sf_output <- sfheaders::sf_point(
  obj = output,
  x = 'lon',
  y = 'lat',
  keep = TRUE
)


sf::st_crs(sf_cnefe) <- 4674
sf::st_crs(sf_output) <- 4674

mapp <- mapview::mapview(sf_cnefe, zcol='numero') +
  mapview(sf_output, col.regions = "red")

return(mapp)
}

check_approx(numero_input = 6, expp = 1)
check_approx(numero_input = 6, expp = 3)

check_approx(numero_input = 760, expp = 1)
check_approx(numero_input = 760, expp = 3)

check_approx(numero_input = 400, expp = 1)
check_approx(numero_input = 400, expp = 3)

Os exemplos acima mostram duas coisas.

  1. A localização do ponto aproximado é muito mais proxima do correto quando se usa expp = 3,
  2. Essa diferença observada em (1) é mais pronunciada quando o input é um numero proximo do inicio ou do fim da rua. Para um input com numero proximo do numero mediano, o expp praticamente nao afeta o resultado.
@rafapereirabr rafapereirabr added this to the v0.2.0 milestone Feb 25, 2025
@rafapereirabr
Copy link
Member Author

Essa aqui foi a sugestão do @antrologos, de implementar uma interpolacao linear. Agora eu preciso ajustar o codigo para nosso caso, e testar se isso afeta muito a performance de tmepo de processamento.

tudo <- geocodebr::listar_dados_cache()
tudo <- tudo[7]

cnefe <- arrow::open_dataset( tudo ) |>
  dplyr::filter(estado == 'RJ') |>
  dplyr::filter(municipio == "RIO DE JANEIRO") |>
  dplyr::filter(logradouro == "AVENIDA MINISTRO IVAN LINS") |>
  dplyr::collect()


input <- data.frame(
  id = 1,
  logradouro = 'AVENIDA MINISTRO IVAN LINS',
  numero_input = 5,
  localidade = "BARRA DA TIJUCA",
  cep = '22620-110'
)


df <- left_join(input, cnefe, by = c('logradouro', 'localidade', 'cep'))

con <- geocodebr:::create_geocodebr_db()


duckdb::dbWriteTable(con, "data_frame_ruas", df, overwrite = TRUE, temporary = TRUE)


approximacao_sql <- function(numero_input, con) {
  query <- sprintf("
    WITH bounds AS (
      SELECT 
        (SELECT MIN(numero) FROM data_frame_ruas) AS min_num,
        (SELECT MAX(numero) FROM data_frame_ruas) AS max_num,
        (SELECT MAX(numero) FROM data_frame_ruas WHERE numero <= %f) AS lower_num,
        (SELECT MIN(numero) FROM data_frame_ruas WHERE numero >= %f) AS upper_num
    ),
    values_cte AS (
      SELECT 
        (SELECT lat FROM data_frame_ruas ORDER BY numero ASC LIMIT 1) AS min_lat,
        (SELECT lon FROM data_frame_ruas ORDER BY numero ASC LIMIT 1) AS min_lon,
        (SELECT lat FROM data_frame_ruas ORDER BY numero DESC LIMIT 1) AS max_lat,
        (SELECT lon FROM data_frame_ruas ORDER BY numero DESC LIMIT 1) AS max_lon,
        (SELECT lat FROM data_frame_ruas WHERE numero = (SELECT MAX(numero) FROM data_frame_ruas WHERE numero <= %f)) AS lower_lat,
        (SELECT lon FROM data_frame_ruas WHERE numero = (SELECT MAX(numero) FROM data_frame_ruas WHERE numero <= %f)) AS lower_lon,
        (SELECT lat FROM data_frame_ruas WHERE numero = (SELECT MIN(numero) FROM data_frame_ruas WHERE numero >= %f)) AS upper_lat,
        (SELECT lon FROM data_frame_ruas WHERE numero = (SELECT MIN(numero) FROM data_frame_ruas WHERE numero >= %f)) AS upper_lon
    ),
    interp AS (
      SELECT 
        b.min_num, b.max_num, b.lower_num, b.upper_num,
        v.min_lat, v.min_lon, v.max_lat, v.max_lon,
        v.lower_lat, v.lower_lon, v.upper_lat, v.upper_lon
      FROM bounds b, values_cte v
    )
    SELECT
      CASE
        WHEN %f <= min_num THEN min_lat
        WHEN %f >= max_num THEN max_lat
        WHEN lower_num = upper_num THEN lower_lat
        ELSE lower_lat + ((%f - lower_num) * (upper_lat - lower_lat)) / (upper_num - lower_num)
      END AS lat,
      CASE
        WHEN %f <= min_num THEN min_lon
        WHEN %f >= max_num THEN max_lon
        WHEN lower_num = upper_num THEN lower_lon
        ELSE lower_lon + ((%f - lower_num) * (upper_lon - lower_lon)) / (upper_num - lower_num)
      END AS lon
    FROM interp
    LIMIT 1;
  ",
                   numero_input, numero_input,           # For lower_num and upper_num in bounds
                   numero_input, numero_input, numero_input, numero_input,  # For lower_lat, lower_lon, upper_lat, upper_lon in values_cte
                   numero_input, numero_input, numero_input,  # For lat: conditions and numerator in interpolation
                   numero_input, numero_input, numero_input   # For lon: conditions and numerator in interpolation
  )
  
  res <- DBI::dbGetQuery(con, query)
  return(as.matrix(res))
}


approximacao_sql(100, con)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant