Tableau périodique et spectres de raies

Dans chaque case d’un élément chimique, son spectre de raies d’émission.

Après avoir tracé de nombreuses versions du tableau périodique, et les spectres de raies de divers éléments chimiques, voici un projet qui combine les deux dans un document grand format.

Le site du NIST (National institute for standards & technology, USA), fournit les longueurs d’onde et les intensités des raies d’émission des éléments. Pour chaque élément, il faut remplir le formulaire, en demandant en fichier de sortie un format .csv, et télécharger le fichier en le nommant avec le symbole de l’élément (H.csv, He.csv, etc.)

Les fichiers peuvent être ouverts via votre tableur préféré, pour en comprendre le contenu :

Contenu du fichier H.csv ouvert avec LibreOffice.

Seules 2 colonnes nous intéressent :

  • obs_wl_vac(nm) : longueur d’onde en nanomètres
  • intens : intensité

Dans ce qui suit, on considère que les fichiers csv de tous les éléments souhaités sont stockés dans le répertoire contenant le script Python.

Un fichier supplémentaire est nécessaire : donnees_elements_chimiques.csv contient les symboles des éléments (strings) et ses coordonnées (col, lig) dans le tableau périodique à 18 colonnes. Ce fichier est de ma fabrication ; je l’ai généré par un script Python et le module mendeleev

Les modules Python nécessaires sont :

  • pandas : lecture de données dans les fichiers externes, et gestion de tableaux de données (DataFrames)
  • numpy : gestion de séries de données numériques
  • matplotlib : tracé de graphiques
  • PIL : insertion d’image (logo de la licence Creative Commons)

L’idée générale est de créer une grille de fenêtres graphiques comportant 18 colonnes et 6 lignes. Chaque élément sera affecté à l’une de ces fenêtres selon ses coordonnées. Les fenêtres inutiles sont rendues invisibles.

Appel des modules :

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlib as mpl # Normalisation des valeurs
from PIL import Image

Définition des paramètres utilisateur personnalisables :

plt.rcParams["font.family"] = "Roboto" # ou autre police installée

filtre = 0.05 # pour la fonctionn filtrageA() : filtre les intensités maximales (0 : non sélectif, 1 : très sélectif)
N_raies = 15 # pour la fonction filtrageB() : ne retient que le nombre N_raies de raies les + intenses (maximum)
epaisseur = 0.7 # épaisseur du trait vertical des raies
xmin, xmax = 400, 750 # limites en longueur d'onde

# les éléments à afficher doivent etre dans cette liste :
liste_symboles = [   'H',   'He',  'Li',   'Be',   'B',    'C',    'N',    'O',    'F',    'Ne',    'Na',    'Mg',    'Al',    'Si',    'P',    'S',    'Cl',    'Ar',    'K',    'Ca',    'Sc',    'Ti',    'V',    'Cr',    'Mn',    'Fe',    'Co',    'Ni',     'Cu',     'Zn',     'Ga',     'Ge',     'As',     'Se',     'Br',     'Kr',    'Rb',    'Sr',    'Y',    'Zr',    'Nb',    'Mo',    'Tc',    'Ru',    'Rh',    'Pd',    'Ag',    'Cd',    'In',    'Sn',    'Sb',    'Te',    'I',    'Xe',    'Cs',    'Ba',    'La',    'Hf',    'Ta',    'W',    'Re',    'Os',    'Ir',    'Pt',    'Au',    'Hg',    'Tl',    'Pb',    'Bi',    'Po',    'At',    'Rn', ]

Pour sélectionner des éléments (disposant d’un fichier csv associé), il suffira d’ajouter son symbole à cette liste.

Définition d’une fonction récupérant les coordonnées (col, lig) d’un élément d’après son symbole :

def coordonnees(symbole):
    """
    renvoie les coordonnées (tuple) d'un élément de symbole donné.
    """
    x = coord.loc[coord.symbole == symbole, 'col'].values[0]
    y = coord.loc[coord.symbole == symbole, 'lig'].values[0]
    return x, y

Fonction de nettoyage et premier filtrage des données. En effet, les fichiers obtenus via NIST possèdent quelques caractères gênants.

def nettoyage(data):
    """
    Nettoie les données d'un dataframe de l'élément.
    """
    data = data.replace('"','', regex=True) # suppression des guillemets
    data = data.replace('=','', regex=True) # suppression des "="
    data.int = pd.to_numeric(data['int'], errors='coerce') # conversion de nombres en flottants
    data.wl = pd.to_numeric(data['wl'], errors='coerce')
    data = data.loc[(data.wl >= xmin) & (data.wl <=xmax)] # sélection des longueurs d'onde
    return data

Fonction réalisant la limitation du nombre de raies. J’ai prévu deux fonctions ayant des critères différents. Sur le document final, j’ai utilisé la seconde :

def filtrageA(data):
    """
    filtre les raies selon l'intensité comparée à la raie la + intense.
    """
    data = data[data['int'] >= filtre * np.max(data.int)] # filtre selon la gamme de longueurs d'onde
    return data

def filtrageB(data):
    """
    filtre les raies les plus intenses (nombre de raies : N_raies).
    """
    data = data.sort_values(by=['int'],ascending = False)
    data = data[:N_raies]
    return data

Fonction réalisant le tracé des raies pour un élément donné.

def trace(data,axe, symb):
    """
    data : le DataFrame (tableau de données) d'un élément.
    axe : la fenetre graphique correspondante, dans la grille.
    symb : symbole de l'élément (string)
    """
    axe.set_facecolor('black')
    axe.set_xlim(xmin,xmax)
    for L in data.wl:
        # axe.axvline(L, lw = epaisseur, color = cmap(norm(L))) # ligne verticale sur toute la hauteur
        Y = data.loc[data.wl == L, 'int'].values[0] # récupération de l'intensité de la raie
        axe.plot([L,L],[0,Y], lw = epaisseur, color = cmap(norm(L))) # intensité
    axe.text(0.01,0.99,symb,transform = axe.transAxes, fontsize=20, c = 'white',
             va='top')
    ymin, ymax = axe.get_ylim()
    axe.set_ylim(0, ymax)

Pour chaque longueur d’onde, on trace une ligne verticale, dont la couleur correspond à celle de la lumière à cette longueur d’onde. Pour cela on utilise le colormap ‘turbo’. Soit la raie occupe toute la hauteur du spectre, soit sa hauteur est selon son intensité.

Fonction mettant en forme les fenêtres graphiques vides, soit que les raies sont en dehors du domaine considéré, soit qu’il n’y a pas de raies.

def axevide(axe, symb):
    """
    met en forme l'axe pour un élément sans raies.
    """
    axe.set_facecolor('midnightblue')
    axe.text(0.01,0.99,symb,transform = axe.transAxes, fontsize=20, c = 'white',
             va = 'top')

Définition du colormap dont les couleurs reproduisent la gamme du visible, du violet au rouge. On fixe manuellement la première couleur à la longueur d’onde 400 nm, et la dernière à 750 nm, par une fonction de normalisation.

cmap = plt.get_cmap('turbo') # gamme de couleurs du visible
norm = mpl.colors.Normalize(vmin=400,vmax=750)

Création d’une figure et d’une grilles d’axes (figure de format A3, les dimensions sont à rentrer en pouces) :

fig, axes = plt.subplots(nrows=6, ncols=18, figsize = (59.4 / 2.54, 42 / 2.54), tight_layout = True)

Première mise en forme des axes :

# suppression des graduations :
for x in range(axes.shape[1]):
    for y in range(axes.shape[0]):
        axes[y, x].set_xticks([])
        axes[y, x].set_yticks([])

# effacement des fenetres graphiques inutiles :
for x in range(1,17):
    for y in range(1):
        axes[y, x].axis('off')
for x in range(2,12):
    for y in range(1, 3):
        axes[y, x].axis('off')

Ensuite vient le coeur du script, avec une boucle parcourant la liste des symboles des éléments demandés :

# boucle sur tous les éléments de la liste :
for symb in liste_symboles:
    x, y = coordonnees(symb)
    axe = axes[y-1, x-1]
    df = pd.read_csv(symb + '.csv', sep = ',', usecols=[2, 4], names=['wl', 'int'], skiprows=1)
    df = nettoyage(df)
    # df = filtrageA(df)
    df = filtrageB(df)
    if len(df) == 0 or np.isnan(np.min(df.int)) :# si pas de raies à afficher
        axevide(axe, symb)
    else:
        trace(df, axe, symb)

Fin du script :

titre = "tableau périodique\nspectres de raies d'émission"
fig.text(0.45, 0.9, titre, fontsize = 45, transform = fig.transFigure,
         ha='center', va='center',
         fontweight='bold')

axe = axes[2, 2]
axe.text(0.0,0.0, f"de {xmin} à \n{xmax} nm", transform = axe.transAxes,
         ha='left', va='bottom', fontsize=12)

# logo Creative Commons et signature :
axe = axes[2, 11]
ax2 = axe.inset_axes([0, -0.025, 1, 0.2], transform=axe.transAxes)# création d'un axe dans un autre, pour accueillir le logo
ax2.set(xticks=[], yticks=[])
img = np.asarray(Image.open('by-nc-sa.png'))
imgplot = ax2.imshow(img)
axe.text(0.7,0.18, "David Alberto\n(www.astrolabe-science.fr)", transform = axe.transAxes,
          ha='left', va='bottom', fontsize=10, rotation = 90)

fig.savefig("tableauPeriodSpectres.pdf")
fig.savefig("tableauPeriodSpectres.png", dpi=250)

Vous trouverez dans ce fichier compressé les fichiers csv et le script Python.

Le fichier image en haute résolution :

Soyez le premier à commenter

Laisser un commentaire