Index de l'article

Work with shapefiles (without Geopandas)

Here we will use numpy and basemap.

pip install numpy
pip install basemap

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.

  1. import matplotlib as mpl
  2. import matplotlib.pyplot as plt
  3. import numpy as np
  4. import pandas as pd
  5.  
  6. from matplotlib.patches import Polygon
  7. from matplotlib.collections import PatchCollection
  8. from mpl_toolkits.basemap import Basemap
  9.  
  10. from PIL import Image, ImageOps
  11.  
  12. shp_simple_countries = r'C:/Users/Downloads/simple_countries/simple_countries'
  13.  
  14. inputExcelFile = r'C:/Users/Downloads/My file.xslx'
  15.  
  16. df = pd.read_excel(inputExcelFile, sheet_name='Export', engine='openpyxl', usecols=['ID', 'My Country Field'])
  17.  
  18. # Count the countries
  19. df_Country_count = pd.DataFrame(df.groupby(['My Country Field'], dropna=True).size(), columns=['Total']).sort_values(['Total'], ascending=False).reset_index()
  20. df_Country_count = df_Country_count.fillna('Unknow')
  21.  
  22. df_Country_count['Percent'] = (df_Country_count['Total'] / df_Country_count['Total'].sum()) * 100
  23. df_Country_count['Percent'] = df_Country_count['Percent'].round(decimals=2)
  24.  
  25. # Set countries as index
  26. df_Country_count.set_index('My Country Field', inplace=True)
  27.  
  28. my_values = df_Country_count['Percent']
  29.  
  30. num_colors = 30
  31. cm = plt.get_cmap('Blues')
  32.  
  33. scheme = [cm(i / num_colors) for i in range(num_colors)]
  34. my_range = np.linspace(my_values.min(), my_values.max(), num_colors)
  35.  
  36. # -1 TO AVOID SEARCHS IN A PANDAS DATA-FRAME INCLUDING START AND STOP VALUE (I think ...)
  37. df_Country_count['Percent'] = np.digitize(my_values, my_range) - 1
  38.  
  39. map1 = plt.figure(figsize=(14, 8))
  40. ax = map1.add_subplot(111, frame_on=False)
  41.  
  42. map1.suptitle('Countries', fontsize=30, y=.95)
  43.  
  44. m = Basemap(lon_0=0, projection='robin')
  45. m.drawmapboundary(color='w')
  46.  
  47. m.readshapefile(shp_simple_countries, 'units', color='#444444', linewidth=.2, default_encoding='iso-8859-15')
  48.  
  49. # Create the chloro map
  50. for info, shape in zip(m.units_info, m.units):
  51. shp_ctry = info['COUNTRY_HB']
  52. if shp_ctry not in df_Country_count.index:
  53. color = '#dddddd'
  54. else:
  55. color = scheme[df_Country_count.loc[shp_ctry]['Percent']]
  56.  
  57. # patches = [Polygon(np.array(shape), True)]
  58. patches = [Polygon(np.array(shape))]
  59. pc = PatchCollection(patches)
  60. pc.set_facecolor(color)
  61. ax.add_collection(pc)
  62.  
  63. # Cover up Antarctica so legend can be placed over it
  64. ax.axhspan(0, 1000 * 1800, facecolor='w', edgecolor='w', zorder=2)
  65.  
  66. # Draw color legend
  67. ax_legend = map1.add_axes([0.2, 0.14, 0.6, 0.03], zorder=3)
  68. cmap = mpl.colors.ListedColormap(scheme)
  69.  
  70. # cb = mpl.colorbar.ColorbarBase(ax_legend, cmap=cmap, ticks=my_range, boundaries=my_range, orientation='horizontal')
  71. # cb.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
  72. # cb.ax.tick_params(labelsize=7)
  73. # cb.set_label('Percentage', rotation=0)
  74. # cb.remove()
  75.  
  76. # Créer une barre de couleur pour la légende
  77. # Définir les labels des pays sur l'axe des x
  78. norm = mpl.colors.Normalize(vmin=min(my_range), vmax=max(my_range))
  79. cbar = plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap, norm=norm), ax_legend, ticks=my_range, boundaries=my_range, orientation='horizontal')
  80. cbar.ax.set_xticklabels([str(round(i, 1)) for i in my_range])
  81. cbar.ax.tick_params(labelsize=7)
  82. cbar.set_label('PourcentageX', rotation=0)
  83.  
  84. # Set the map footer
  85. # description = 'Bla bla bla'
  86. # plt.annotate(description, xy=(-.8, -3.2), size=14, xycoords='axes fraction')
  87.  
  88. map1.savefig('C:/Users/Downloads/mymap1.png', dpi=100, bbox_inches='tight')
  89.  
  90. plt.show()
  91. plt.clf()
  92.  
  93. im = Image.open('C:/Users/Downloads/mymap1.png')
  94. bordered = ImageOps.expand(im, border=1, fill=(0, 0, 0))
  95. bordered.save('C:/Users/Downloads/mymap1.png')

Zoom on France

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

Add labels or POI

ax.text(0.05, # Vers la droite
0.59, # Vers le haut
'Guadeloupe', fontsize=10, ha='center', transform=ax.transAxes)

Another example

If you use the shape attached named France-Departements-Deformation.shp:

############## 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')

 

Liens ou pièces jointes
Télécharger ce fichier (France-Departements-Deformation.zip)France-Departements-Deformation.zip[France-Departements-Deformation]335 Ko
Télécharger ce fichier (simple_countries.zip)simple_countries.zip[simple_countries]1880 Ko