Simulação de Monte-carlo em R para avaliação de risco de localização de armazém

A simulação de Monte-carlo é uma técnica muito popular quando se trata de avaliação de risco. Em posts anteriores, apresentei implementações de simulações de monte-carlo em Python e R. I, por exemplo, usei a simulação de Monte-carlo para avaliar o risco associado à evolução dos preços de commodities e ações. Também demonstrei como animar simulações de monte-carlo em R usando ggplot2 e gganimate. Neste artigo, implementarei uma simulação de Monte-carlo para avaliar o risco de custo de um local de depósito que está sendo considerado por meu conselho de diretores fictício. Identificarei uma localização de armazém ideal estimada, aplicando a abordagem do centro de massa. Em seguida, usarei a simulação de Monte-carlo para avaliar os riscos de custo associados à alocação de depósito no centro de massa. Ou seja, neste post, implementarei um workflow como o ilustrado na figura abaixo.

Exemplo simples de cadeia de suprimentos para fins de demonstração

Com o propósito de demonstrar a simulação de Monte-carlo para avaliação de risco de localização de armazém, irei considerar uma rede de cadeia de abastecimento simples com abastecimento direto de fornecedores, um único centro de distribuição central (armazém) e entrega direta aos clientes. O modelo de rede assumido é ilustrado na figura abaixo.

Nesta estrutura da cadeia de abastecimento, considero, para este exemplo, a quantidade expedida de produtos (itens) como a unidade de medida relevante como variável principal que explica os custos de transporte. Como também irei explicar mais tarde, dependendo do cliente e dos dados disponíveis, unidades de medida relevantes ao modelar o impacto sobre os custos variáveis de transporte podem, por exemplo, ser peso ou volume de remessas, quantidade de produtos despachados ou receita de vendas e despesas de compra.

Neste exemplo, assumirei que os volumes de envio são uma unidade de medida relevante. Esses dados irão, por exemplo, estar disponível ao realocar um warehouse existente ou ao usar e extrapolar dados de redes de distribuição já existentes em outra região com propriedades e portfólios de produtos semelhantes.

Configurando dataframe com clientes artificiais e locais de fornecedores, bem como demanda aleatória


No segmento de código abaixo, configurei o dataframe contendo as localizações do fornecedor e do cliente, bem como o volume de envio esperado de ou para cada local. Para uma análise do centro de massa (centro de gravidade) deste tipo, a direção do fluxo não é relevante, ou seja, nenhuma distinção entre os volumes de envio dos fornecedores e dos clientes é feita. Isso se baseia em uma forte suposição. Nomeadamente, os preços de envio por unidade de volume não diferem muito do lado das vendas (para os clientes) em comparação com o lado das compras (dos fornecedores). Esta é uma suposição forte que, na realidade, muitas vezes não será cumprida.

O motivo pelo qual essa suposição frequentemente não é totalmente aplicável em problemas do mundo real é que geralmente há diferenças estruturais no fluxo de material de entrada e saída. Por exemplo, um centro de distribuição pode receber remessas em lotes maiores – como, por exemplo, carregamentos de contêineres cheios. As remessas de saída serão, em muitos casos, no entanto, menores e, por exemplo, assumir a forma de remessas de encomendas. Embora as remessas de encomendas e as entregas expressas estejam fortemente relacionadas à distância de transporte, os contêineres de frete marítimo ou ferroviário de entrada podem não depender das distâncias. Nesses casos, é necessário ajustar os pesos em um dos lados (do lado da venda ou do lado da compra). Por exemplo, o frete marítimo pode ter que ser excluído de uma análise do centro de gravidade.

Para este exemplo, no entanto, vamos supor que os volumes de expedição de entrada e saída tenham estruturas de custo semelhantes e ambos dependem da distância de transporte.

# criando modelo de dataframe vazio
df = as.data.frame(matrix(nrow=1000,ncol=4))
# nomeando cabeçalhos de dataframe
colnames(df) = c("longitude",
                 "latitude",
                 "volume",
                 "type")
# as primeiras 500 entradas serão fornecedores, as outras 500 entradas representam clientes
for(i in 1:1000){
  if(i > 500){
    df$type[i] = "customer"
  }else{
    df$type[i] = "supplier"
  }
  df$latitude[i] = rnorm(n=1,
               mean = 50,
               sd = 1)[1]
  df$longitude[i] = rnorm(n=1,
                       mean=15,
                       sd=1)[1]
  df$volume[i] = rnorm(n=1,
                    mean=100,
                    sd=25)[1]
}

O dataframe criado artificialmente agora deve estar pronto para uso e totalmente preenchido. Como você deve ter notado, eu povoava fornecedores e clientes com localizações normalmente distribuídas aleatoriamente em torno de longitude 50 e latitude 15. O volume de remessa por fornecedor ou cliente também segue uma distribuição aleatória. Os dados do mundo real certamente parecerão diferentes, dependendo do caso de uso específico (conjunto de dados).

Escolha de uma localização de armazém ideal com base na abordagem do centro de massa

Em um post anterior, já demonstrei a abordagem do centro de massa como uma abordagem de otimização simples para localizar um armazém com base em sua proximidade com os clientes (ou fornecedores, ou ambos). Em outras palavras, esta é uma abordagem heurística para a alocação ideal do armazém. A abordagem do centro de massa é comumente usada em, por exemplo, Engenharia Mecânica. A imagem abaixo ilustra como o centro de massa é derivado de objetos. Esses objetos podem ser objetos físicos ou elementos abstratos, como, por exemplo, demanda do cliente. A massa, nesse caso, representativa da respectiva procura do cliente.

Conforme ilustrado na figura abaixo, o centro de massa, ou centro de gravidade, muda quando há uma mudança nos pesos baseados na localização (gravidade).

A abordagem do centro de massa é baseada em algumas suposições simplificadoras. Essas suposições são as seguintes:

  • Os custos de transporte por remessa estão fortemente correlacionados com a distância de transporte
  • A correlação é considerada linear
  • A distância de transporte pode ser aproximada com normas de distância euclidiana, ou seja, linhas retas
  • Quaisquer diferenças estruturais nas classes de envio, por ex. remessa prioritária vs. remessa econômica, pode ser ajustada ajustando os fatores de ponderação relativos baseados na categoria

O centro de massa também é conhecido como centro de gravidade. A abordagem também é bem explicada com fórmulas. Aqui eu mostro a fórmula para calcular a média ponderada x-coordenante do centro de gravidade (x sendo a longitude).

Da mesma forma, posso expressar a coordenada y – média ponderada do centro de massa (latitude) com a mesma fórmula:

Essas fórmulas calculam, conforme declarado, a média ponderada das coordenadas x e y do centro de massa, respectivamente. A definição de pesos, por exemplo, pode basear-se na quantidade de unidades expedidas, no valor das receitas de vendas e despesas de compra ou na quantidade ou equivalentes de peso sendo ou a serem expedidos. Dependendo dos dados disponíveis no cliente submetido a uma análise de alocação de armazém deste tipo, uma dessas definições de peso será a mais adequada para escolher.

Nas linhas de código a seguir, obtenho o centro de massa, representando a localização ideal do armazém neste exemplo. Em outras palavras: eu implemento as duas fórmulas para calcular as coordenadas x e y médias ponderadas, ponderadas pela demanda de vendas e quantidade de oferta da quantidade de oferta de origem (fornecedores) e quantidade de demanda de sumidouro (clientes). A única diferença é que me refiro a x como longitude e y como latitude.

# função deriva o centro de massa, retornando um vetor com duas entradas
# valor de retorno não. 1: coordenada de longitude do centro de massa
# valor de retorno não. 2: latitude coordiante do centro de massa
center_of_mass = function(df){
  longitude = sum(df$volume*df$longitude)/sum(df$volume)
  latitude = sum(df$volume*df$latitude)/sum(df$volume)
  return(c(as.numeric(longitude),as.numeric(latitude)))
}

Tendo implementado as fórmulas na forma de funções em R, agora as aplico para derivar o centro de massa para este exemplo. O centro de massa será considerado para representar a localização ideal do armazém.

# aplica a função de cálculo do centro de massa
com = center_of_mass(df)

Eu adiciono o centro de massa derivado ao dataframe geral. Este dataframe será usado posteriormente como entrada para visualizações baseadas em ggplot2:

# adicione mais uma linha com o centro de massa como uma entrada separada
df[1001,] = c(com[1],
              com[2],
              200.0, # para tornar este ponto um pouco maior no gráfico de dispersão baseado em ggplot2
              "CoM") # significa "Centro de Massa"
# certifique-se de que todas as colunas numéricas sejam realmente numéricas
df$latitude = as.numeric(df$latitude)
df$longitude = as.numeric(df$longitude)
df$volume = as.numeric(df$volume)

Visualização da localização ideal do armazém com base na abordagem do centro de massa

Posso visualizar o centro de massa, ou seja, a localização ideal do armazém sugerida, usando um gráfico de dispersão ggplot2.

# importing ggplot2 para visualização de gráfico de dispersão
library(ggplot2)
# mostra o centro de gravidade usando o gráfico de dispersão
plot = ggplot() + 
  geom_point(data = df[-1001,], mapping = aes(x = longitude, 
                           y = latitude,
                           color = type,
                           size = volume),alpha=0.05) +
  geom_point(data=df[1001,], 
             mapping = aes(x=longitude,
                           y=latitude,
                           color=type,
                           size=volume)) +
  scale_color_manual(values=c("black","red","blue")) +
  xlab("Longitude") +
  ylab("Latitude") +
  ggtitle("Center of mass visualization with ggplot2 scatter plot") +
  labs(color = "Type", 
       size = "Shipping volume")
# gráfico de exibição
plot

Posso visualizar isso em um mapa também, usando o ggmap. Já introduzi o ggmap em outras postagens do blog. Abaixo está o gráfico de pontos que criei usando ggmap em R (uma estrutura baseada em ggplot2 para plotagem baseada em mapa).

# visualizar gráfico de dispersão usando ggmap
library(ggmap)
mapObj = get_stamenmap(
      bbox = c(left = 5, 
               bottom = 45,
               right = 25,
               top = 55),
      maptype = "toner-background",
      zoom = 4)

mapatt = attributes(mapObj)
mapObj = matrix(adjustcolor(mapObj, alpha.f = 0.2), nrow = nrow(mapObj))
attributes(mapObj) = mapatt
mapObj = ggmap(mapObj)
mapObj = mapObj + 
  geom_point(data = df[-1001,], mapping = aes(x = longitude, 
                           y = latitude,
                           color = type,
                           size = volume),alpha=0.05) +
  geom_point(data=df[1001,], 
             mapping = aes(x=longitude,
                           y=latitude,
                           color=type,
                           size=volume)) +
  scale_color_manual(values=c("black","red","blue")) +
  xlab("Longitude") +
  ylab("Latitude") +
  ggtitle("Center of mass visualization with ggmap point plot") +
  labs(color = "Type", 
       size = "Shipping volume")
mapObj

As diferenças de cores podem irritar alguns analistas, pois podem criar alguma confusão em relação à densidade de volume. Portanto, também se pode considerar um lote com clientes e fornecedores sendo coloridos da mesma cor.

mapObj = get_stamenmap(
      bbox = c(left = 5, 
               bottom = 45,
               right = 25,
               top = 55),
      maptype = "toner-background",
      zoom = 4)
mapatt = attributes(mapObj)
mapObj = matrix(adjustcolor(mapObj, alpha.f = 0.2), nrow = nrow(mapObj))
attributes(mapObj) = mapatt
mapObj = ggmap(mapObj)
mapObj = mapObj + 
  geom_point(data = df[-1001,], mapping = aes(x = longitude, 
                           y = latitude,
                           color = type,
                           size = volume),alpha=0.03) +
  geom_point(data=df[1001,], 
             mapping = aes(x=longitude,
                           y=latitude,
                           color=type,
                           size=volume)) +
  scale_color_manual(values=c("black","red","red")) +
  xlab("Longitude") +
  ylab("Latitude") +
  ggtitle("Center of mass visualization with ggmap point plot") +
  labs(color = "Type", 
       size = "Shipping volume")
mapObj

Outra abordagem de visualização seria usar a embalagem do folheto em R para mapeamento térmico da distribuição do volume de transporte e adicionar um marcador no centro de gravidade, ou seja, centro de massa. Isso é o que eu faço nas linhas de código abaixo. O folheto informativo contém leaflet.js.

# importing leaflet and leaflet.extras me permitirá fazer um mapa de calor
library(leaflet)
library(leaflet.extras)
library(magrittr)
# define o centro do mapa
lat_center = df$latitude[1001]
long_center = df$longitude[1001]
# criar um mapa de calor para a intensidade de busca de hambúrguer
heatmap = df %>%
  leaflet() %>% 
  addTiles() %>% 
  setView(long_center,lat_center,6.15) %>%
  addHeatmap(lng=~longitude,lat=~latitude,intensity=~volume,max=100,radius=13,blur=20) %>%
  addMarkers(lng=as.numeric(com[1]),lat=as.numeric(com[2]))
# exibir mapa de calor com centro de gravidade
heatmap

Cálculo dos custos de transporte com base na distância euclidiana e no volume de envio

Prosseguirei implementando uma função em R que calcula todos os custos de transporte esperados. Neste exemplo, vou me concentrar em um exemplo altamente simplificado em que simplesmente multiplico a distância euclidiana pelo volume de remessa. Em problemas do mundo real, pode-se, por exemplo, considere uma abordagem zoneada (por exemplo, a FedEx baseia seus preços de envio de encomendas em zonas).

A distância euclidiana é o comprimento de uma linha reta entre dois pontos em um plano 2D. Abaixo segue a implementação da função de custo personalizada.

# função auxiliar para calcular a métrica de distância euclidiana
euclidean_distance = function(longitude,latitude,long_com,lat_com){
  long_distances = (longitude-long_com[1])^2
  lat_distances = (latitude-lat_com[1])^2
  return((long_distances+lat_distances)*0.5)
}
# função de cálculo de custo de transporte
transport_costs = function(df){
  long_com = df$longitude[nrow(df)]
  lat_com = df$latitude[nrow(df)]
  euclideans = euclidean_distance(df$latitude[-nrow(df)],df$latitude[-nrow(df)],long_com,lat_com)
  costs = euclideans*df$volume[-nrow(df)]
  return(sum(costs))
}

Agora aplicarei a função de cálculo de custo de transporte para ver a soma total dos custos de transporte esperados para o depósito localizado no centro de massa. Eu expresso os custos em milhões.

transport_costs(df)/1000000
## [1] 60.8644

Aplicação de simulação de monte-carlo para avaliar os riscos de custos associados à localização de armazém escolhida

Os dados, especialmente os dados de oferta e demanda, estão sujeitos à volatilidade, sazonalidade e incerteza. A simulação de Monte-carlo, uma das técnicas populares usadas por analistas de SCM, é um método apropriado para avaliação de risco. Nesse caso, há risco envolvido na seleção de um local para um novo depósito. Quão sensíveis são os custos de transporte se escolhermos este local? Quanto mais altos podem ser os custos de transporte, em comparação com os custos de transporte calculados acima?

Posso aplicar uma simulação de Monte-carlo aplicando a repetição do experimento, aplicando distribuições normais aos volumes de remessa por cliente e fornecedor. Vou repetir o cenário de oferta e demanda (ou seja, a distribuição dos volumes de remessa) 1.000 vezes. Para cada execução, calcularei os custos de transporte resultantes e armazenarei o resultado de cada execução em um dataframe de resultados.

# dataframe para armazenar resultados de simulação de Monte-carlo
results = as.data.frame(matrix(nrow=10000,ncol=2))
colnames(results) = c("iteration","costs")
# executar 10.000 iterações da simulação Monte-carlo para avaliar a incerteza / custo
for(i in 1:10000){
  df$volume[-1001] = rnorm(n=1000,
                    mean=100,
                    sd=25)
  results$iteration[i] = i
  results$costs[i] = transport_costs(df)/1000000
}
# representa os resultados da simulação de Monte-carlo na forma de um histograma
ggplot() + geom_histogram(data = results, 
                          mapping = aes(x=costs),
                          fill = "red",
                          bins = 25) +
  ggtitle("Simulated transport cost sensitivity at center of gravity (center of mass)") + 
  xlab("Simulated transport costs") +
  ylab("Absolute frequency")

Isso completa meu exemplo sobre como aplicar a simulação de Monte-carlo para risco de custo de alocação de depósito e avaliação de sensibilidade. Em outras postagens, também demonstrei como se pode, por exemplo, a simulação animate Monte-carlo é executada com gganimado em R, assim como demonstrei, por exemplo, visualização de distribuição de demanda espacial em Python com pacotes como folheto, ou em R com pacotes como deckgl.

Leave a Reply

Deixe um comentário

O seu endereço de e-mail não será publicado.

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

Close

Meta