Criando mapas interativos com a biblioteca Folium
O Folium é uma biblioteca Python construída sobre o Leaflet.js. É usado para visualizar dados por meio de mapas interativos, visualização de mapas coropléticos, permite marcação nos dados e também suporta sobreposições de imagem, vídeo GeoJSON e TopoJSON.
Neste post, irei demonstrar de modo simples como essa poderosíssima biblioteca funciona, exploraremos um dataset contendo os dados de commodities do estado de Karnataka na Índia e com essas informações construiremos mapas e poderemos ver todo o poder e flexibilidade do Folium na prática.
1 - Importando as bibliotecas necessárias
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as pximport folium
2 - Obtendo a base de dados
Aqui nesta etapa é onde iremos importar nossa base de dados, escolha um dataset que seja de seu interesse. Neste caso, considerei os dados do mercado agrícola de Karnataka no ano de 2012.
df = pd.read_excel("CommMktArrivals2012.xls")
3 - Preparando os dados
Nesse dataset existem 156 tipos de commodities, consideraremos apenas commodities cujas unidades estão em Quintal, de modo que outras commodities como côco e gado ficarão de fora. Também não utilizaremos todas as colunas. Portanto, filtraremos apenas as colunas necessárias. A função ‘sum’ é usada para obter a soma de todos os itens (nosso foco está na ‘Chegada’) com base nos agrupamentos.
#Filtrando os dados
df1 = df.groupby(['Produto', 'Distrito', 'Unidade'], as_index=False)
df1 = df1.sum().filter(["Produto", "Distrito", "Chegada", "Unidade"])
df_quintal = df1.query("Unidade == 'Quintal '")
df_quintal.head()
Observe que “Chegada” aqui se refere à chegada de mercadorias em um local para venda, de várias fontes, como aldeias, depósitos, e etc. As mercadorias que serão deixadas de fora da análise são:
df_fora_quintal = df1[df1["Unidade"]!="Quintal "]
df_fora_quintal.Produto.unique()
4 - Latitude e longitude
Sabemos que nem todos os conjuntos de dados vêm com valores geográficos. Portanto, crie um arquivo Excel e preencha os distritos com seus valores de latitude e longitude.
karnataka_geo = pd.read_excel("karnataka_latlong.xlsx")
Agora, mescle este arquivo com a base de dados que importamos anteriormente.
df_merged = pd.merge(df_quintal, karnataka_geo)
df_merged.head()
A partir de agora usaremos esses dados “df_merged” contendo as duas colunas adicionais para plotarmos os gráficos no Folium.
5 - Obtendo os dados Geojson
Geojson é um formato popular para representar características geográficas. É um arquivo Json que contém estruturas poligonais de estados, distritos, e etc.
Atente-se de que a coluna-chave nos dados geojson, como “Nome do distrito” ou “id”, corresponda aos dados originais. Se não, modifique-os para corresponder.
state_geo = 'kar.json'
6 - Criando um gráfico de barras para visualizar as 10 principais commodities
Isso é para plotar um gráfico com as dez principais commodities, com os critérios de chegadas mais altas. Agrupamos os elementos por mercadoria, unidade e os classificamos em ordem decrescente de quantidade de chegada.
dfg = df_merged.groupby(['Produto','Unidade'], as_index=False)top10_df = dfg.sum().sort_values(by='Chegada',ascending=False).head(10)top10_df
Em seguida, usaremos o gráfico de barras interativo da biblioteca Plotly para plotar os dados. Como podemos ver, os principais produtos do estado de Karnataka são arroz, milho, cebola, arroz, batata, algodão, gengibre verde, tomate, amendoim e tur.
#Gráfico de Barras com Plotly
fig = px.bar(top10_df, x='Produto', y='Chegada', color='Produto', title='Top 10 Principais Produtos em Karnataka')
fig.show()
7 - Criando um mapa básico no Folium
Como já mencionado, o Folium é uma biblioteca muito poderosa e pode ser usada para visualizar dados geográficos.
# Criação de um mapa básico no folium
m = folium.Map(location=[15,75],
zoom_start=5.5, tiles='Stamen Toner')
m.save('map.html')m
Clique no mapa para amplia-lo, deste modo será possível obter uma melhor visualização.
8 - Adicionando marcadores de circulo
O marcador de círculo pode ser adicionado usando a função folium.circle (). Filtraremos os dados de chegada do arroz e os vincularemos ao círculo.
paddy = df_merged[df_merged["Produto"]=="Paddy"]
data = paddyfor i in range(0,len(data)):
folium.Circle(
location=[data.iloc[i]['Latitude'], data.iloc[i]['Longitude']],
geo_data="Karnataka",
popup=str(data.iloc[i]['Distrito'])+":"+str(data.iloc[i]['Chegada']),
radius=float(data.iloc[i]['Chegada']/75),
color='crimson',
fill=True,
fill_color='blue'
).add_to(m)
m.save('paddy.html')
m
Como podemos ver, o marcador de círculo pode não ser a melhor representação para este cenário. Como o raio do círculo é o valor de chegada e não indica os limites apropriados para os distritos. Dessa forma utilizaremos o mapa coroplético.
9 - Criando um mapa Coroplético
Mapa coroplético é um tipo de mapa temático no qual áreas (como distritos ou estados) são coloridas com diferentes formas de uma cor correspondente à densidade / quantidade dos dados aos quais está vinculada.
Criaremos um mapa coroplético utilizando o método folium.Choropleth().
m = folium.Map([15, 74], zoom_start=6,tiles='cartodbpositron')arecanut = df_merged[df_merged["Produto"]=="Arecanut"]
state_data = arecanutchoropleth = folium.Choropleth(
geo_data = state_geo,
name = 'choropleth',
data = state_data,
columns = ['Distrito', 'Chegada'],
key_on = 'feature.properties.NAME_2',
fill_color = 'YlGn',
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = 'Chegada Arecanut (em Quintal)',
highlight=True,
line_color='black'
).add_to(m)folium.LayerControl(collapsed=True).add_to(m)
m.save('coropletico.html')
m
10 - Adicionando rótulos ao mapa coroplético com GeoJsonTooltip
Iremos utilizar o método GeoJsonTooltip para colocar rótulos no nosso mapa, desse modo o mapa ficará mais intuitivo.
#Adicionando nome nos distritos
choropleth.geojson.add_child(
folium.features.GeoJsonTooltip(['NAME_2'],labels=False)
)
m.save('mapa.html')
m
11 - Exibindo vários dados usando grupo de recursos e controle de camada
Como podemos ver acima, pudemos visualizar o mapa da chegada de castanha (Arecanut) em Karnataka. Mas e se quisermos ver arroz ou milho? Devemos criar mapas separados para cada um? Existe uma maneira de visualizar várias mercadorias em um único mapa? A resposta é sim, podemos visualizar todos esses produtos em um mesmo mapa utilizando o método FeatureGroup.
Vários recursos podem ser passados para um único grupo de recursos para agrupá-los. Então vários grupos de recursos podem ser criados e adicionados ao controle de camada. Isso nos dá a opção de alternar entre diferentes visualizações.
from branca.colormap import linearm = folium.Map([15, 74], zoom_start=6,tiles=None,overlay=False)#top 10
paddy = df_merged[df_merged["Produto"]=="Paddy"]
maize = df_merged[df_merged["Produto"]=="Maize"]
onion = df_merged[df_merged["Produto"]=="Onion"]
rice = df_merged[df_merged["Produto"]=="Rice"]
potato = df_merged[df_merged["Produto"]=="Potato"]cotton = df_merged[df_merged["Produto"]=="Cotton"]
greenginger = df_merged[df_merged["Produto"]=="Green Ginger"]
tomato = df_merged[df_merged["Produto"]=="Tomato"]
arecanut = df_merged[df_merged["Produto"]=="Arecanut"]
tur = df_merged[df_merged["Produto"]=="Banana"]# feature groups
feature_group0 = folium.FeatureGroup(name='paddy',overlay=False).add_to(m)
feature_group1= folium.FeatureGroup(name='maize',overlay=False).add_to(m)
feature_group2 = folium.FeatureGroup(name='onion',overlay=False).add_to(m)
feature_group3= folium.FeatureGroup(name='rice',overlay=False).add_to(m)
feature_group4 = folium.FeatureGroup(name='potato',overlay=False).add_to(m)
feature_group5 = folium.FeatureGroup(name='cotton',overlay=False).add_to(m)
feature_group6 = folium.FeatureGroup(name='Green Ginger',overlay=False).add_to(m)
feature_group7 = folium.FeatureGroup(name='Tomato',overlay=False).add_to(m)
feature_group8 = folium.FeatureGroup(name='Arecanut',overlay=False).add_to(m)
feature_group9 = folium.FeatureGroup(name='Tur',overlay=False).add_to(m)fs = [feature_group0,feature_group1,feature_group2,feature_group3,feature_group4,feature_group5,
feature_group6,feature_group7,feature_group8,feature_group9]
commodities = [paddy,maize,onion,rice,potato,cotton,greenginger,tomato,arecanut,tur]
for i in range(len(commodities)):
choropleth1 = folium.Choropleth(
geo_data = state_geo,
name = 'choropleth',
data = commodities[i],
columns = ['Distrito', 'Chegada'],
key_on = 'feature.properties.NAME_2',
fill_color = 'YlGn',
nan_fill_color = "black",
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = 'Chegada ( Quintal)',
highlight = True,
line_color = 'black').geojson.add_to(fs[i])#geojson for labels
geojson1 = folium.GeoJson(data=state_geo,
name='karnataka district',
smooth_factor=2,
style_function=lambda x: {'color':'black','fillColor':'transparent','weight':0.5},
tooltip=folium.GeoJsonTooltip(fields=['NAME_2'],
labels=False,
sticky=True),
highlight_function=lambda x: {'weight':3,'fillColor':'grey'},
).add_to(choropleth1)colormap = linear.YlGn_09.scale(
df_merged.Chegada.min(),
df_merged.Chegada.max()).to_step(10)
colormap.caption = 'Chegada (quintal)'
colormap.add_to(m)folium.TileLayer('cartodbdark_matter',overlay=True,name="dark mode").add_to(m)
folium.TileLayer('cartodbpositron',overlay=True,name="light mode").add_to(m)folium.LayerControl(collapsed=False).add_to(m)
m.save('topten_commodities.html')
m
12 - Calculando a porcentagem de commodities em um distrito
Esta etapa não é necessária para visualização, mas os dados podem ser usados para insights.
#Porcentagem de Arroz no distrito de Raichur
paddy = df_merged[df_merged["Produto"]=="Paddy"]
paddy[paddy["Distrito"]=="Raichur"].Chegada/paddy.Chegada.sum() *100#Porcentagem de Tomates no distrito de Kolar
tomate = df_merged[df_merged["Produto"]=="Tomato"]
tomate[tomate["Distrito"]=="Kolar"].Chegada/tomato.Chegada.sum() *100
13 - Calculando as maiores mercadorias em cada distrito
Um número enorme de mercadorias chega a cada distrito, precisamos encontrar aquele com maior valor de chegada.
Podemos usar os dados max_commodity resultantes para colocar o ícone de mercadoria relevante para cada distrito na próxima etapa.
max_produto = {}#criando lista com distritos de valores únicos
distritos = df_merged["Distrito"].unique()for el in distritos:
#filtra os dados de cada distrito
df_dis = df_merged[df_merged["Distrito"]==el]
#obtendo os produtos de cada distrito
max_distrito = df_dis[df_dis.Chegada == df_dis.Chegada.max()].Produto.iloc[0]
max_produto[df_dis["Distrito"].iloc[0]] = max_distritomax_produto
14 - Utilizando marcadores e utilizando ícones personalizados
Ícones personalizados podem ser criados usando alguns arquivos de imagem e posteriormente usados como marcadores. Cada ícone é obtido dinamicamente, dependendo do maior nome de mercadoria de cada distrito.
m = folium.Map(location=[df_merged['Latitude'].mean()+1,df_merged['Longitude'].mean()],
zoom_start=6.5)
data_cm = karnataka_geo
geojson = folium.GeoJson(data=state_geo,
smooth_factor=2,
style_function=lambda x: {'color':'black','fillColor':'green','weight':1},
tooltip=folium.GeoJsonTooltip(fields=['NAME_2'],
labels=False,
sticky=True),
highlight_function=lambda x: {'weight':3,'fillColor':'grey'},
).add_to(m)
#marcador e ícone
for i in range(0,len(data_cm)):
#alterando o nome do arquivo dependendo da quantidade de mercadoria de cada distrito
icon_image = "icons/"+str(max_produto[data_cm.iloc[i]['Distrito']])+".png"
icon = folium.CustomIcon(
icon_image,
icon_size=(30, 30),
icon_anchor=(15, 15),
popup_anchor=(-3, -76)
)
folium.Marker(
location=[data_cm.iloc[i]['Latitude'], data_cm.iloc[i]['Longitude']],
icon=icon,
radius=float(15000.0),
popup=str(data_cm.iloc[i]['Distrito'])+":"+str(max_produto[data_cm.iloc[i]['Distrito']])
).add_to(m)
folium.LayerControl(collapsed=True).add_to(m)
m.save('icon_commodity.html')
m
Considerações Finais
Neste post, aprendemos como usar o Folium para mapas interativos, preparar os dados usando Pandas, usar dados Geojson, criar mapa coroplético e adicionar rótulos, usar várias camadas de dados e adicionar marcadores personalizados. O projeto utilizado neste post pode ser encontrado aqui no meu Github. Para obter mais informações sobre o Folium, confira o site ofical clicando aqui.