Page 14 sur 25
Work with shapefiles (without Geopandas)
Here we will use numpy and basemap.
[code]pip install numpy
pip install basemap[/code]
To install basemap on WIndows, download a package here, according your versions, and install it with wheel.
Thanks to ramiro.org. I got the code below on his website, I just adapted it.
Suppose you have an Excel file with a country field, you get count and map them with a country shape. The country names in the Excel file and in the shape must be the same.
[code]import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from mpl_toolkits.basemap import Basemap
from PIL import Image, ImageOps
shp_simple_countries = r'C:/Users/Downloads/simple_countries/simple_countries'
inputExcelFile = r'C:/Users/Downloads/My file.xslx'
df = pd.read_excel(inputExcelFile, sheet_name='Export', engine='openpyxl', usecols=['ID', 'My Country Field'])
# Count the countries
df_Country_count = pd.DataFrame(df.groupby(['My Country Field'], dropna=True).size(), columns=['Total']).sort_values(['Total'], ascending=False).reset_index()
df_Country_count = df_Country_count.fillna('Unknow')
df_Country_count['Percent'] = (df_Country_count['Total'] / df_Country_count['Total'].sum()) * 100
df_Country_count['Percent'] = df_Country_count['Percent'].round(decimals=2)
# Set countries as index
df_Country_count.set_index('My Country Field', inplace=True)
my_values = df_Country_count['Percent']
num_colors = 30
cm = plt.get_cmap('Blues')
scheme = [cm(i / num_colors) for i in range(num_colors)]
my_range = np.linspace(my_values.min(), my_values.max(), num_colors)
# -1 TO AVOID SEARCHS IN A PANDAS DATA-FRAME INCLUDING START AND STOP VALUE (I think ...)
df_Country_count['Percent'] = np.digitize(my_values, my_range) - 1
map1 = plt.figure(figsize=(14, 8))
ax = map1.add_subplot(111, frame_on=False)
map1.suptitle('Countries', fontsize=30, y=.95)
m = Basemap(lon_0=0, projection='robin')
m.drawmapboundary(color='w')
m.readshapefile(shp_simple_countries, 'units', color='#444444', linewidth=.2, default_encoding='iso-8859-15')
# Create the chloro map
for info, shape in zip(m.units_info, m.units):
shp_ctry = info['COUNTRY_HB']
if shp_ctry not in df_Country_count.index:
color = '#dddddd'
else:
color = scheme[df_Country_count.loc[shp_ctry]['Percent']]
# patches = [Polygon(np.array(shape), True)]
patches = [Polygon(np.array(shape))]
pc = PatchCollection(patches)
pc.set_facecolor(color)
ax.add_collection(pc)
# Cover up Antarctica so legend can be placed over it
ax.axhspan(0, 1000 * 1800, facecolor='w', edgecolor='w', zorder=2)
# Draw color legend
ax_legend = map1.add_axes([0.2, 0.14, 0.6, 0.03], zorder=3)
cmap = mpl.colors.ListedColormap(scheme)
# cb = mpl.colorbar.ColorbarBase(ax_legend, cmap=cmap, ticks=my_range, boundaries=my_range, orientation='horizontal')
# cb.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
# cb.ax.tick_params(labelsize=7)
# cb.set_label('Percentage', rotation=0)
# cb.remove()
# Créer une barre de couleur pour la légende
# Définir les labels des pays sur l'axe des x
norm = mpl.colors.Normalize(vmin=min(my_range), vmax=max(my_range))
cbar = plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap, norm=norm), ax_legend, ticks=my_range, boundaries=my_range, orientation='horizontal')
cbar.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
cbar.ax.tick_params(labelsize=7)
cbar.set_label('PourcentageX', rotation=0)
# Set the map footer
# description = 'Bla bla bla'
# plt.annotate(description, xy=(-.8, -3.2), size=14, xycoords='axes fraction')
map1.savefig('C:/Users/Downloads/mymap1.png', dpi=100, bbox_inches='tight')
plt.show()
plt.clf()
im = Image.open('C:/Users/Downloads/mymap1.png')
bordered = ImageOps.expand(im, border=1, fill=(0, 0, 0))
bordered.save('C:/Users/Downloads/mymap1.png')[/code]
Zoom on France
[code]map1.suptitle('Académies', fontsize=20, y=.87)
m = Basemap(projection='merc', resolution='l', \
llcrnrlon=-7, # Longitude minimale : étend vers l'est \
llcrnrlat=39.5, # Latitude minimale : étend vers le sud \
urcrnrlon=13, # Longitude maximale : étend vers l'ouest \
urcrnrlat=52) # Latitude maximale : étend vers le nord[/code]
Add labels or POI
[code]ax.text(0.05, # Vers la droite
0.59, # Vers le haut
'Guadeloupe', fontsize=10, ha='center', transform=ax.transAxes)[/code]
Another example
If you use the shape attached named France-Departements-Deformation.shp:
[code]############## Carte df_DepEtablissement
# Set academies as index
df_DepEtablissement.set_index('nom_departement_etablissement', inplace=True)
my_values = df_DepEtablissement['Pourcentage']
num_colors = 30
cm = plt.get_cmap('Blues')
scheme = [cm(i / num_colors) for i in range(num_colors)]
my_range = np.linspace(my_values.min(), my_values.max(), num_colors)
# -1 TO AVOID SEARCHS IN A PANDAS DATA-FRAME INCLUDING START AND STOP VALUE (I think ...)
df_DepEtablissement['Pourcentage'] = np.digitize(my_values, my_range) - 1
map2 = plt.figure(figsize=(14, 10))
ax = map2.add_subplot(111, frame_on=False)
map2.suptitle('Départements', fontsize=20, y=.87)
m = Basemap(projection='merc', resolution='l',
llcrnrlon=-9, \ # Longitude minimale : étend vers l'est
llcrnrlat=39.5, \ # Latitude minimale : étend vers le sud
urcrnrlon=15, \ # Longitude maximale : étend vers l'ouest
urcrnrlat=52) \ # Latitude maximale : étend vers le nord
m.drawmapboundary(color='w')
m.readshapefile(shp_departements, 'units', color='#444444', linewidth=.2, default_encoding='utf-8')
# Create the chloro map
for info, shape in zip(m.units_info, m.units):
shp_departements = info['dep_name']
if shp_departements not in df_DepEtablissement.index:
color = '#dddddd'
else:
color = scheme[df_DepEtablissement.loc[shp_departements]['Pourcentage']]
# patches = [Polygon(np.array(shape), True)]
patches = [Polygon(np.array(shape))]
pc = PatchCollection(patches)
pc.set_facecolor(color)
ax.add_collection(pc)
# Cover up Antarctica so legend can be placed over it
# ax.axhspan(0, 1000 * 1800, facecolor='w', edgecolor='w', zorder=2)
# Draw color legend
ax_legend = map2.add_axes([0.2, 0.14, 0.6, 0.03], zorder=3)
cmap = mpl.colors.ListedColormap(scheme)
# cb = mpl.colorbar.ColorbarBase(ax_legend, cmap=cmap, ticks=my_range, boundaries=my_range, orientation='horizontal')
# cb.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
# cb.ax.tick_params(labelsize=7)
# cb.set_label('Pourcentage', rotation=0)
# cb.remove()
# Créer une barre de couleur pour la légende
# Définir les labels des pays sur l'axe des x
norm = mpl.colors.Normalize(vmin=min(my_range), vmax=max(my_range))
cbar = plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap, norm=norm), ax_legend, ticks=my_range, boundaries=my_range, orientation='horizontal')
cbar.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
cbar.ax.tick_params(labelsize=7)
cbar.set_label('PourcentageX', rotation=0)
ax.text(0.125, 0.565,'Guadeloupe', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.175, 0.46, 'Martinique', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.1, 0.18, 'Guyane', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.42, 0.155, 'Mayotte', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.6, 0.155, 'La Réunion', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.73, 0.15, 'Corse', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.83, 0.41, 'Nouvelle-Calédonie', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.8, 0.515, 'Polynésie Française', fontsize=10, ha='center', transform=ax.transAxes)
ax.text(0.836, 0.69, 'Inconnu', fontsize=8, ha='center', transform=ax.transAxes)
ax.text(0.86, 0.903, 'Étranger', fontsize=8, ha='center', transform=ax.transAxes)
map2.savefig(downloadsDir + 'mymap2.png', dpi=80, bbox_inches='tight')
# plt.show()
# plt.clf()
im = Image.open(downloadsDir + 'mymap2.png')
bordered = ImageOps.expand(im, border=1, fill=(0, 0, 0))
bordered.save(downloadsDir + 'mymap2.png')
# INSERT IN EXCEL
img = openpyxl.drawing.image.Image(downloadsDir+'mymap2.png')
img.anchor = 'E4'
workbook['Départements établissements'].add_image(img)
workbook.save(statsFile)
################# REMOVE PICTURES
os.remove(downloadsDir + 'mymap2.png')[/code]