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]