diff --git a/module2/exo5/exo5_python_fr.org b/module2/exo5/exo5_python_fr.org new file mode 100644 index 0000000000000000000000000000000000000000..99402df00054ca882a9da05417bb89a4cfc15eb1 --- /dev/null +++ b/module2/exo5/exo5_python_fr.org @@ -0,0 +1,440 @@ +#+TITLE: Analyse du risque de défaillance des joints toriques de la navette Challenger +#+AUTHOR: Arnaud Legrand +#+LANGUAGE: fr + +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: + +#+LATEX_HEADER: \usepackage{a4} +#+LATEX_HEADER: \usepackage[french]{babel} + +# #+PROPERTY: header-args :session :exports both + +Le 27 Janvier 1986, veille du décollage de la navette /Challenger/, eu +lieu une télé-conférence de trois heures entre les ingénieurs de la +Morton Thiokol (constructeur d'un des moteurs) et de la NASA. La +discussion portait principalement sur les conséquences de la +température prévue au moment du décollage de 31°F (juste en dessous de +0°C) sur le succès du vol et en particulier sur la performance des +joints toriques utilisés dans les moteurs. En effet, aucun test +n'avait été effectué à cette température. + +L'étude qui suit reprend donc une partie des analyses effectuées cette +nuit là et dont l'objectif était d'évaluer l'influence potentielle de +la température et de la pression à laquelle sont soumis les joints +toriques sur leur probabilité de dysfonctionnement. Pour cela, nous +disposons des résultats des expériences réalisées par les ingénieurs +de la NASA durant les 6 années précédant le lancement de la navette +Challenger. + + +* Première analyse + +** Chargement des données +Nous commençons donc par charger ces données: +#+begin_src python :results value :session first :exports both +import numpy as np +import pandas as pd +data = pd.read_csv("shuttle.csv") +data +#+end_src + +#+RESULTS: +#+begin_example + Date Count Temperature Pressure Malfunction +0 4/12/81 6 66 50 0 +1 11/12/81 6 70 50 1 +2 3/22/82 6 69 50 0 +3 11/11/82 6 68 50 0 +4 4/04/83 6 67 50 0 +5 6/18/82 6 72 50 0 +6 8/30/83 6 73 100 0 +7 11/28/83 6 70 100 0 +8 2/03/84 6 57 200 1 +9 4/06/84 6 63 200 1 +10 8/30/84 6 70 200 1 +11 10/05/84 6 78 200 0 +12 11/08/84 6 67 200 0 +13 1/24/85 6 53 200 2 +14 4/12/85 6 67 200 0 +15 4/29/85 6 75 200 0 +16 6/17/85 6 70 200 0 +17 7/29/85 6 81 200 0 +18 8/27/85 6 76 200 0 +19 10/03/85 6 79 200 0 +20 10/30/85 6 75 200 2 +21 11/26/85 6 76 200 0 +22 1/12/86 6 58 200 1 +#+end_example + +Le jeu de données nous indique la date de l'essai, le nombre de joints +toriques mesurés (il y en a 6 sur le lançeur principal), la +température (en Fahrenheit) et la pression (en psi), et enfin le +nombre de dysfonctionnements relevés. + +** Inspection graphique des données +Les vols où aucun incident n'est relevé n'apportant aucune information +sur l'influence de la température ou de la pression sur les +dysfonctionnements, nous nous concentrons sur les expériences où au +moins un joint a été défectueux. + +#+begin_src python :results value :session first :exports both +data = data[data.Malfunction>0] +data +#+end_src + +#+RESULTS: +: Date Count Temperature Pressure Malfunction +: 1 11/12/81 6 70 50 1 +: 8 2/03/84 6 57 200 1 +: 9 4/06/84 6 63 200 1 +: 10 8/30/84 6 70 200 1 +: 13 1/24/85 6 53 200 2 +: 20 10/30/85 6 75 200 2 +: 22 1/12/86 6 58 200 1 + +Très bien, nous avons une variabilité de température importante mais +la pression est quasiment toujours égale à 200, ce qui devrait +simplifier l'analyse. + +Comment la fréquence d'échecs varie-t-elle avec la température ? +#+begin_src python :results output file :var matplot_lib_filename="freq_temp_python.png" :exports both :session first +import matplotlib.pyplot as plt + +plt.clf() +data["Frequency"]=data.Malfunction/data.Count +data.plot(x="Temperature",y="Frequency",kind="scatter",ylim=[0,1]) +plt.grid(True) + +plt.savefig(matplot_lib_filename) +print(matplot_lib_filename) +#+end_src + +#+RESULTS: +[[file:freq_temp_python.png]] + +À première vue, ce n'est pas flagrant mais bon, essayons quand même +d'estimer l'impact de la température $t$ sur la probabilité de +dysfonctionnements d'un joint. + +** Estimation de l'influence de la température + +Supposons que chacun des 6 joints toriques est endommagé avec la même +probabilité et indépendamment des autres et que cette probabilité ne +dépend que de la température. Si on note $p(t)$ cette probabilité, le +nombre de joints $D$ dysfonctionnant lorsque l'on effectue le vol à +température $t$ suit une loi binomiale de paramètre $n=6$ et +$p=p(t)$. Pour relier $p(t)$ à $t$, on va donc effectuer une +régression logistique. + +#+begin_src python :results value :session first :exports both +import statsmodels.api as sm + +data["Success"]=data.Count-data.Malfunction +data["Intercept"]=1 + + +# logit_model=sm.Logit(data["Frequency"],data[["Intercept","Temperature"]]).fit() +logmodel=sm.GLM(data['Frequency'], data[['Intercept','Temperature']], family=sm.families.Binomial(sm.families.links.logit)).fit() + +logmodel.summary() +#+end_src + +#+RESULTS: +#+begin_example + Generalized Linear Model Regression Results +============================================================================== +Dep. Variable: Frequency No. Observations: 7 +Model: GLM Df Residuals: 5 +Model Family: Binomial Df Model: 1 +Link Function: logit Scale: 1.0 +Method: IRLS Log-Likelihood: -3.6370 +Date: Fri, 20 Jul 2018 Deviance: 3.3763 +Time: 16:56:08 Pearson chi2: 0.236 +No. Iterations: 5 +=============================================================================== + coef std err z P>|z| [0.025 0.975] +------------------------------------------------------------------------------- +Intercept -1.3895 7.828 -0.178 0.859 -16.732 13.953 +Temperature 0.0014 0.122 0.012 0.991 -0.238 0.240 +=============================================================================== +#+end_example + +L'estimateur le plus probable du paramètre de température est 0.0014 +et l'erreur standard de cet estimateur est de 0.122, autrement dit on +ne peut pas distinguer d'impact particulier et il faut prendre nos +estimations avec des pincettes. + +** Estimation de la probabilité de dysfonctionnant des joints toriques +La température prévue le jour du décollage est de 31°F. Essayons +d'estimer la probabilité de dysfonctionnement des joints toriques à +cette température à partir du modèle que nous venons de construire: + +#+begin_src python :results output file :var matplot_lib_filename="proba_estimate_python.png" :exports both :session first +import matplotlib.pyplot as plt + +data_pred = pd.DataFrame({'Temperature': np.linspace(start=30, stop=90, num=121), 'Intercept': 1}) +data_pred['Frequency'] = logmodel.predict(data_pred[['Intercept','Temperature']]) +data_pred.plot(x="Temperature",y="Frequency",kind="line",ylim=[0,1]) +plt.scatter(x=data["Temperature"],y=data["Frequency"]) +plt.grid(True) + +plt.savefig(matplot_lib_filename) +print(matplot_lib_filename) +#+end_src + +#+RESULTS: +[[file:proba_estimate_python.png]] + +Comme on pouvait s'attendre au vu des données initiales, la +température n'a pas d'impact notable sur la probabilité d'échec des +joints toriques. Elle sera d'environ 0.2, comme dans les essais +précédents où nous il y a eu défaillance d'au moins un joint. Revenons +à l'ensemble des données initiales pour estimer la probabilité de +défaillance d'un joint: + +#+begin_src python :results output :session first :exports both +data = pd.read_csv("shuttle.csv") +print(np.sum(data.Malfunction)/np.sum(data.Count)) +#+end_src + +#+RESULTS: +: 0.06521739130434782 + +Cette probabilité est donc d'environ $p=0.065$, sachant qu'il existe +un joint primaire un joint secondaire sur chacune des trois parties du +lançeur, la probabilité de défaillance des deux joints d'un lançeur +est de $p^2 \approx 0.00425$. La probabilité de défaillance d'un des +lançeur est donc de $1-(1-p^2)^3 \approx 1.2%$. Ça serait vraiment +pas de chance... Tout est sous contrôle, le décollage peut donc avoir +lieu demain comme prévu. + +Seulement, le lendemain, la navette Challenger explosera et emportera +avec elle ses sept membres d'équipages. L'opinion publique est +fortement touchée et lors de l'enquête qui suivra, la fiabilité des +joints toriques sera directement mise en cause. Au delà des problèmes +de communication interne à la NASA qui sont pour beaucoup dans ce +fiasco, l'analyse précédente comporte (au moins) un petit +problème... Saurez-vous le trouver ? Vous êtes libre de modifier cette +analyse et de regarder ce jeu de données sous tous les angles afin +d'expliquer ce qui ne va pas. + + + +* Nouvelle analyse + +Nous commençons donc par charger ces données: +#+begin_src python :results value :session second :exports both +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import statsmodels.api as sm + +data = pd.read_csv("shuttle.csv") +data +#+end_src + +#+RESULTS: +#+begin_example + Date Count Temperature Pressure Malfunction +0 4/12/81 6 66 50 0 +1 11/12/81 6 70 50 1 +2 3/22/82 6 69 50 0 +3 11/11/82 6 68 50 0 +4 4/04/83 6 67 50 0 +5 6/18/82 6 72 50 0 +6 8/30/83 6 73 100 0 +7 11/28/83 6 70 100 0 +8 2/03/84 6 57 200 1 +9 4/06/84 6 63 200 1 +10 8/30/84 6 70 200 1 +11 10/05/84 6 78 200 0 +12 11/08/84 6 67 200 0 +13 1/24/85 6 53 200 2 +14 4/12/85 6 67 200 0 +15 4/29/85 6 75 200 0 +16 6/17/85 6 70 200 0 +17 7/29/85 6 81 200 0 +18 8/27/85 6 76 200 0 +19 10/03/85 6 79 200 0 +20 10/30/85 6 75 200 2 +21 11/26/85 6 76 200 0 +22 1/12/86 6 58 200 1 +#+end_example + + +Tracons, pour ces 3 jeux de données, le nombre de Malfunction en fonction de la température sur le même graphique, une couleur par pression. On le nomme "P_temp_python.png". + +#+begin_src python :results output file :var matplot_lib_filename="P_temp_python.png" :exports both :session second + +dataP50 = data[data.Pressure==50] +dataP100 = data[data.Pressure==100] +dataP200 = data[data.Pressure==200] + +plt.clf() +dataP50.plot(x="Temperature",y="Malfunction",kind="scatter",ylim=[-1,6],color='blue') +dataP100.plot(x="Temperature",y="Malfunction",kind="scatter",ylim=[-1,6],color='red',ax=plt.gca()) +dataP200.plot(x="Temperature",y="Malfunction",kind="scatter",ylim=[-1,6],color='green',ax=plt.gca()) +plt.grid(True) +plt.savefig(matplot_lib_filename) +print(matplot_lib_filename) +#+end_src + +#+RESULTS: +[[file:P_temp_python.png]] + + +** Regression logistique + +Nous allons maintenant effectuer 4 régressions logistiques + +*** Régression logistique sur les données P50 + +#+begin_src python :results value :session second :exports both + +# Filtrer les données et créer une copie +dataP50 = data[data.Pressure == 50].copy() + +# Calcul des colonnes nécessaires +dataP50["Success"] = dataP50["Count"] - dataP50["Malfunction"] +dataP50["Intercept"] = 1 + +# Création du modèle avec une instance de sm.families.links.Logit() +logmodelP50 = sm.GLM( + dataP50['Malfunction'], + dataP50[['Intercept', 'Temperature']], + family=sm.families.Binomial(link=sm.families.links.Logit()) +).fit() + +# Résumé du modèle +logmodelP50.summary() + +# Générer des données pour la prédiction (de 25 à 90) +data_pred50 = pd.DataFrame({ + 'Temperature': np.linspace(25, 90, 100), + 'Intercept': 1 +}) + +# Ajouter les prédictions au DataFrame +data_pred50['Malfunction_Prob'] = logmodelP50.predict(data_pred50[['Intercept', 'Temperature']]) + +# Tracer les points observés +plt.figure() # Nouvelle figure +plt.scatter(dataP50['Temperature'], dataP50['Malfunction'], color='blue', label='Observations') + +# Tracer la courbe de régression logistique +plt.plot(data_pred50['Temperature'], data_pred50['Malfunction_Prob'], color='red', label='Régression logistique') + +# Ajouter des labels et une légende +plt.xlabel('Température') +plt.ylabel('Probabilité de dysfonctionnement') +plt.title('Régression logistique sur les données P50') +plt.legend() +plt.grid(True) + +# Créer une image regression_logistique_P50.png +plt.savefig("regression_logistique_P50.png") +print("regression_logistique_P50.png") +#+end_src + +#+RESULTS: +[[file:regression_logistique_P50.png]] + + +*** Régression logistique sur les données P200 + +#+begin_src python :results value :session second :exports both +#+begin_src python :results value :session second :exports both +# Filtrer les données et créer une copie +dataP200 = data[data.Pressure == 200].copy() + +# Calcul des colonnes nécessaires +dataP200["Success"] = dataP200["Count"] - dataP200["Malfunction"] +dataP200["Intercept"] = 1 +dataP200["Proportion"] = dataP200["Malfunction"] / dataP200["Count"] + +# Création du modèle avec une instance de sm.families.links.Logit() +logmodelP200 = sm.GLM( + dataP200['Proportion'], + dataP200[['Intercept', 'Temperature']], + family=sm.families.Binomial(link=sm.families.links.Logit()) +).fit() + +# Résumé du modèle +logmodelP200.summary() + +# Générer des données pour la prédiction (de 25 à 90) +data_pred200 = pd.DataFrame({ + 'Temperature': np.linspace(25, 90, 100), + 'Intercept': 1 +}) +data_pred200['Proportion_Pred'] = logmodelP200.predict(data_pred200[['Intercept', 'Temperature']]) + +# Tracer les points observés +plt.figure() # Nouvelle figure +plt.scatter(dataP200['Temperature'], dataP200['Proportion'], color='blue', label='Observations') + +# Tracer la courbe de régression logistique +plt.plot(data_pred200['Temperature'], data_pred200['Proportion_Pred'], color='red', label='Régression logistique') + +# Ajouter des labels et une légende +plt.xlabel('Température') +plt.ylabel('Proportion de dysfonctionnements') +plt.title('Régression logistique sur les données P200') +plt.legend() +plt.grid(True) + +# Créer une image regression_logistique_P200.png +plt.savefig("regression_logistique_P200.png") +print("regression_logistique_P200.png") +#+end_src + + + +*** Régression logistique sur toutes données + +#+begin_src python :results value :session second :exports both +# Filtrer les données et créer une copie +dataAll = data.copy() +# Calcul des colonnes nécessaires +dataAll["Success"] = dataAll["Count"] - dataAll["Malfunction"] +dataAll["Intercept"] = 1 +# Création du modèle avec une instance de sm.families.links.Logit() +logmodelAll = sm.GLM( + dataAll['Malfunction'], + dataAll[['Intercept', 'Temperature']], + family=sm.families.Binomial(link=sm.families.links.Logit()) +).fit() +# Résumé du modèle +logmodelAll.summary() +# Générer des données pour la prédiction (de 25 à 90) +data_predAll = pd.DataFrame({ + 'Temperature': np.linspace(25, 90, 100), + 'Intercept': 1 +}) +# Ajouter les prédictions au DataFrame +data_predAll['Malfunction_Prob'] = logmodelAll.predict(data_predAll[['Intercept', 'Temperature']]) +# Tracer les points observés +plt.figure() # Nouvelle figure +plt.scatter(dataAll['Temperature'], dataAll['Malfunction'], color='blue', label='Observations') +# Tracer la courbe de régression logistique +plt.plot(data_predAll['Temperature'], data_predAll['Malfunction_Prob'], color='red', label='Régression logistique') +# Ajouter des labels et une légende +plt.xlabel('Température') +plt.ylabel('Probabilité de dysfonctionnement') +plt.title('Régression logistique sur toutes les données') +# Ajouter une légende +plt.legend() +# Créer une image regression_logistique_All.png +plt.savefig("regression_logistique_All.png") +print("regression_logistique_All.png") +#+end_src + +#+RESULTS: +[[file:regression_logistique_All.png]] + +