import os
import pandas as pd
import numpy as np
from idmtools.entities import IAnalyzer
from idmtools.entities.simulation import Simulation
from idmtools.analysis.analyze_manager import AnalyzeManager
from idmtools.core import ItemType
from idmtools.core.platform_factory import Platform
Guides d’analyse
Analyser les résultats d’une collection de simulations.
La plupart des fichiers de sortie générés par EMOD sont au format json, et les autres sont des csv. Dans emodpy
, les fonctions Analyzer facilitent l’extraction d’informations à partir des fichiers de sortie bruts d’EMOD pour produire des résultats sous forme de csv ou de figures.
Modules requis
D’autres modules tels que datetime
peuvent également être utiles, en fonction du type de sortie et des manipulations souhaitées.
Structure de base d’un analyseur
La classe de l’analyseur doit être définie en utilisant un nom significatif, par exemple InsetChartAnalyzer
pour un analyseur qui traite les sorties InsetChart.json
. Une class en Python permet de construire des objets personnalisés avec des attributs et des fonctions associés. Chaque classe commence par une définition des paramètres personnalisés et des attributs des objets via __init__
et self
. Cela nécessite quelques modifications entre les expériences de simulation, mais tend à rester relativement inchangé au sein de la même configuration d’expérience.
La fonction filter
est optionnelle. Elle permet à l’analyseur de n’analyser qu’un sous-ensemble de simulations dans une expérience en filtrant sur la base des tags de simulation. Par exemple, l’utilisateur peut demander à l’analyseur de n’analyser que les simulations où la balise SMC_Coverage
a la valeur 1 (s’il s’agit d’une balise spécifiée par l’utilisateur lors de la construction de son expérience), ou les simulations qui ont réussi. Cette fonctionnalité peut être utile lors du débogage de grandes expériences. Si la fonction filter
n’est pas spécifiée, toutes les simulations sont ciblées pour l’analyse.
La fonction map
est une fonction personnalisée appliquée pour extraire les données du fichier de sortie EMOD et doit être modifiée le plus souvent en fonction des différents types de fichiers de sortie EMOD et des besoins de l’utilisateur.
Enfin, reduce
vérifie les données extraites, agrège les données de plusieurs simulations dans la même expérience, puis enregistre ou trace les données. La vérification de la simulation reste en grande partie la même d’une simulation à l’autre, tandis que le traitement des données de simulation est très variable d’un projet à l’autre et dépend des résultats souhaités.
class InsetChartAnalyzer(IAnalyzer) : ...
# 1 - Définition des paramètres personnalisés et des attributs de l'objet
def __init__(self, ...)
# Facultatif
def filter(self, simulation) :
...# 2 - Extraction et sélection des données à partir des fichiers de sortie json
def map(self, data, simulation) :
...# 3 - Vérifier les données extraites, puis les sauvegarder ou les tracer
def reduce(self, all_data) :
...
De plus amples informations sur la structure générale et le fonctionnement des analyseurs qui travaillent avec les données de sortie EMOD sont disponibles dans la documentation idmtools.
Analyser un InsetChart
L’analyseur InsetChartAnalyzer est utilisé pour expliquer la structure de l’analyseur en détail.
Configuration de la classe de l’analyseur et définition des variables
Il n’est pas nécessaire de comprendre en profondeur les principes fondamentaux de Python, il suffit de savoir ce que fait chaque ligne et ce qu’il faut modifier. - Les deux premières lignes incluant __init__(self,...)
et super
sont nécessaires dans chaque classe d’analyseur. Assurez-vous de mettre à jour le nom de l’analyseur dans super
. - La seconde ligne filenames=["output/InsetChart.json"]
définit le fichier de sortie EMOD à analyser. Il est écrit dans une liste afin que les analyseurs aient la possibilité de combiner des données provenant de plusieurs fichiers. En général, nous utilisons un fichier de sortie par analyseur. - Les lignes suivantes qui commencent par self
attachent chaque argument que l’utilisateur a passé à l’analyseur (expt_name
, etc) à la classe de l’analyseur via self
. Cela permet un accès facile à n’importe laquelle de ces valeurs depuis n’importe quelle fonction de l’analyseur via l’objet self
. - Cet analyseur permet à l’utilisateur de spécifier les paramètres expt_name
, sweep_variables
, channels
, et start_year
. En général, nous utilisons expt_name
et sweep_variables
dans tous les analyseurs que nous écrivons, tandis que les autres sont spécifiques à cet analyseur particulier. Tous ces paramètres peuvent être modifiés ou étendus avec des paramètres supplémentaires si nécessaire, selon les besoins de l’utilisateur. - Ces paramètres permettent à l’analyseur de prendre en compte des valeurs spécifiques à l’expérience, par exemple la simulation start_year
est utilisée pour convertir les pas de temps en valeurs temporelles, car nous exécutons généralement EMOD en temps de simulation plutôt qu’en temps calendaire. - Le paramètre expt_name
permet à l’utilisateur de spécifier le nom de l’expérience. Nous utilisons souvent le nom de l’expérience dans les noms de fichiers des sorties de l’analyseur, par exemple les csv agrégés et les figures. - Le paramètre sweep_variables
est une liste de balises de simulation de l’expérience que l’utilisateur souhaite attacher à chaque simulation. Par exemple, Run_Number
pour suivre la graine aléatoire, ou SMC_Coverage
si l’expérience balaie la couverture du SMC. - Le paramètre channels
est optionnel car il prend des valeurs par défaut s’il n’est pas spécifié. Il est inclus dans cet analyseur pour que l’utilisateur ait la flexibilité d’extraire différents canaux de InsetChart.json si nécessaire. Si les mêmes canaux sont toujours utilisés, on peut plutôt coder en dur les noms des canaux désirés dans self.channels
et supprimer l’argument optionnel.
class MonthlyInsetChartAnalyzer(IAnalyzer) :
def __init__(self, expt_name, sweep_variables=None, channels=None, working_dir=".", start_year=2022) :
super(MonthlyInsetChartAnalyzer, self).__init__(working_dir=working_dir, filenames=["output/InsetChart.json"])
self.sweep_variables = sweep_variables or ["Run_Number"]
self.channels = channels or ['Statistical Population', 'New Clinical Cases', 'New Severe Cases', 'PfHRP2 Prevalence']
self.expt_name = expt_name
self.start_year = start_year
Cartographier les données de simulation
La fonction map
est une fonction personnalisée qui changera le plus lors de l’adaptation d’un analyseur à différentes sorties EMOD. Ne changez pas la définition de la fonction (la ligne commençant par def map()
).
La sortie EMOD du (des) fichier(s) de sortie demandé(s) est stockée dans data
. La première activité de map()
est donc d’extraire les données désirées de data
. data
est un dictionnaire dont les clés sont les noms de fichiers stockés dans self.filenames
et les valeurs sont le contenu de chaque fichier.
Dans cet exemple utilisant InsetChart.json
, nous lisons les données du fichier json, en ne gardant que les canaux qui ont été spécifiés dans self.channels
, et nous les convertissons en un dataframe pandas. Le dataframe aura une colonne pour chaque canal, et chaque ligne est la valeur du canal pour chaque timetep de la simulation.
Ensuite, nous voulons convertir les pas de temps (numéro de ligne) en dates de calendrier. Ce sont les 5 lignes suivantes. Nous copions simdata.index
dans simdata['Time']
et créons des variables supplémentaires pour Day, Month et Year qui sont plus faciles à utiliser, ainsi qu’une colonne date
qui est un objet datetime.date
.
Enfin, les variables de balayage correspondant aux balises de simulation de l’expérience sont attachées, le dataframe est renvoyé, et le dataframe renvoyé est automatiquement transmis à l’étape suivante et finale de l’analyseur. Il n’est pas nécessaire de renvoyer un cadre de données, mais il est nécessaire de renvoyer quelque chose : les données d’intérêt de la simulation.
def map(self, data, simulation) :
= pd.DataFrame({x : data[self.filenames[0]]['Channels'][x]['Data'] for x in self.inset_channels})
simdata 'Temps'] = simdata.index
simdata['Day'] = simdata['Time'] % 365
simdata['Month'] = simdata['Day'].apply(lambda x : self.monthparser((x + 1) % 365))
simdata['Year'] = simdata['Time'].apply(lambda x : int(x / 365) + self.start_year)
simdata['date'] = simdata.apply(lambda x : datetime.date(int(x['Year']), int(x['Month']), 1), axis=1)
simdata[for sweep_var in self.sweep_variables :
if sweep_var in simulation.tags.keys() :
= simulation.tags[sweep_var]
simdata[sweep_var] return simdata
Réduire
Cette partie vérifie les données de simulation renvoyées par map()
et agrège les données de toutes les simulations de l’expérience dans le cadre de données adf
. Dans cet exemple, l’analyseur enregistre les résultats dans le sous-dossier spécifié working_dir/expt_name. D’autres analyseurs peuvent utiliser la fonction reduce()
pour tracer et sauvegarder une figure.
Nous ne modifions généralement pas les 4 premières lignes de reduce()
(création de selected
et vérification qu’il contient des données). Si map()
renvoie un dataframe, la ligne adf = ...
peut également rester inchangée. Tout ce qui suit doit être adapté aux besoins de l’utilisateur.
def map(self, all_data) :
= [data for sim, data in all_data.items()]
selected if len(selected) == 0 :
print("Aucune donnée n'a été renvoyée... Sortie...")
retour= pd.concat(selected).reset_index(drop=True)
adf if not os.path.exists(os.path.join(self.working_dir, self.expt_name)) :
self.working_dir, self.expt_name))
os.mkdir(os.path.join(self.working_dir, self.expt_name, 'All_Age_Monthly_Cases.csv'), index=False) adf.to_csv(os.path.join(
Extensions optionnelles de l’analyseur et fonctions d’aide
Par exemple, sélectionner seulement les simulations avec SMC_Coverage
à 0.5 :
def filter(self, simulation) :
return simulation.tags["SMC_Coverage"] == 0.5
Fonction d’aide pour convertir les mois.
@classmethod
def monthparser(self, x) :
if x == 0 :
return 12
else :
return datetime.datetime.strptime(str(x), '%j').month