import pandas as pd import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import make_interp_spline, BSpline # === Chargement des données === chemin_fichier = "data.xlsx" df = pd.read_excel(chemin_fichier) # === Filtrer les sources === df = df[df["type"].str.lower() == "source"] df = df.dropna(subset=["c25°C ", "Nom"]) # === Couleurs par source === couleurs_sources = { "Bullac": "#D77D00", "Corn": "#A1C935", "Bual": "#F5D200", "La diège": "#9271EA", "Ayrissac": "#EB64C3", "Pito": "#D52117", "Ressel": "#1DC6C3", "Marchepied": "#3FB94D", "Anglades": "#0084C9", "Liauzu": "#431D84", "Pescalerie": "#D8676B", "Sagne": "tab:purple", } # === Calcul des fréquences normalisées === freq_absolue = df.groupby(["Nom", "c25°C "]).size().reset_index(name="frequence") freq_totale = freq_absolue.groupby("Nom")["frequence"].transform("sum") freq_absolue["frequence_normalisee"] = freq_absolue["frequence"] / freq_totale # === Sources à afficher dans l'ordre souhaité === noms_ordonnes = [ "Sagne", "Ressel", "Pito", "Pescalerie", "Marchepied", "Liauzu", "La diège", "Corn", "Bullac", "Bual", "Ayrissac", "Anglades" ] sources_disponibles = set(freq_absolue["Nom"].unique()) noms_valides = [nom for nom in noms_ordonnes if nom in sources_disponibles] # === Graphique 3D === fig = plt.figure(figsize=(20, 12)) ax = fig.add_subplot(111, projection='3d') y_spacing = 4.0 for idx, nom in enumerate(reversed(noms_valides)): df_nom = freq_absolue[freq_absolue["Nom"] == nom].sort_values("c25°C ") if df_nom.empty: continue xs = df_nom["c25°C "].to_numpy() ys = df_nom["frequence_normalisee"].to_numpy() color = couleurs_sources.get(nom, "gray") y_val = (len(noms_valides) - 1 - idx) * y_spacing # === Courbe type histogramme === x_curve = [] y_curve = [] for i in range(len(xs)): x_curve.extend([xs[i] - 0.5, xs[i], xs[i] + 0.5]) y_curve.extend([0, ys[i], 0]) # Relier à la suivante si proche if i < len(xs) - 1 and xs[i + 1] - xs[i] <= 1.5: x_curve.extend([xs[i] + 0.5, xs[i + 1] - 0.5]) y_curve.extend([0, 0]) # X_Y_Spline = make_interp_spline(x_curve, y_curve) # X_ = np.linspace(x_curve.min(), x_curve.max(), 500) # Y_ = X_Y_Spline(X_) # ax.plot(X_, [y_val] * len(Y_), Y_, color=color, linewidth=2, alpha=0.8) res = {key: value for key, value in zip(x_curve, y_curve)} x_curve = list(res.keys()) y_curve = list(res.values()) # Interpolation pour lisser la courbe spline = make_interp_spline(x_curve, y_curve, k=1) x_curve = np.linspace(min(x_curve), max(x_curve), 100) y_curve = spline(x_curve) ax.plot(x_curve, [y_val] * len(x_curve), y_curve, color=color, linewidth=2, alpha=0.8) # Nom de la source à gauche ax.text( x=freq_absolue["c25°C "].min() - 30, y=y_val, z=0, s=nom, color=color, fontsize=10, ha='right', va='center' ) # === Configuration du graphique === ax.set_yticks([]) ax.set_xlabel("Conductivité (c25°C, µS/cm)", labelpad=15) ax.set_ylabel("") ax.set_zlabel("Fréquence normalisée", labelpad=10) ax.set_title("Courbes 3D type histogrammes par source", pad=20) ax.set_xlim(freq_absolue["c25°C "].min() - 35, freq_absolue["c25°C "].max() + 10) ax.set_ylim(-y_spacing, len(noms_valides) * y_spacing) ax.set_zlim(0, 0.4) ax.view_init(elev=25, azim=-140) # ax.autoscale() plt.tight_layout() plt.show()