Tutoriel Python - Usage de la Base sur la Qualité et la Sécurité des Soins (BQSS)#

Ce notebook vise à montrer un usage type dont peut être l’objet BQSS, de façon programmatique grâce au langage Python.

Import des librairies et des données#

Les URLS des données sont récupérable sur la page de la Base sur la Qualité et la Sécurité des Soins sur la plateforme Data Gouv accessible via ce lien. La section Fichiers contient les données disponibles, et pour chaque fichier, une description est présente incluant notamment un URL stable qu’il est recommandé d’utiliser.

La base BQSS adoptant un format clé-valeurs, il est nécessaire de récupérer la table des valeurs d’intérêts (concernant la qualité et la sécurité des soins) ainsi que les métadonnées associées spécifiant ces valeurs et les données caractérisant les établissements de santé via les numéro FINESS.

import numpy as np
import pandas as pd  # version >= 1.4.2

# chargement du fichier valeurs.csv dans un dataframe pandas
valeurs_df = pd.read_csv(
    "https://www.data.gouv.fr/fr/datasets/r/da295ccc-2011-4995-b810-ad1f1a82a83f",
    low_memory=False,
)

# chargement du fichier metadata.csv dans un dataframe pandas
metadata_df = pd.read_csv(
    "https://www.data.gouv.fr/fr/datasets/r/e56fb5a5-5a74-4507-ba77-e0411d4aa234",
    low_memory=False,
)

# chargement du fichier finess.csv dans un dataframe pandas
finess_df = pd.read_csv(
    "https://www.data.gouv.fr/fr/datasets/r/48dadbce-56a8-4dc4-ac51-befcdfd82521",
    low_memory=False,
)

Observation des données#

Il est désormais possible de consulter les informations caractérisant ces données et en observer un échantillon.

Caractérisation des données valeurs_df#

valeurs_df.info()
valeurs_df.sample(n=5, random_state=187)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4473855 entries, 0 to 4473854
Data columns (total 10 columns):
 #   Column         Dtype  
---  ------         -----  
 0   annee          int64  
 1   finess         object 
 2   finess_type    object 
 3   key            object 
 4   value_boolean  object 
 5   value_string   object 
 6   value_integer  float64
 7   value_float    float64
 8   missing_value  object 
 9   value_date     object 
dtypes: float64(2), int64(1), object(7)
memory usage: 341.3+ MB
annee finess finess_type key value_boolean value_string value_integer value_float missing_value value_date
3359247 2023 330781121 geo psy_smhosp_cv_2021-cv_classe NaN NaN NaN NaN Non concerné NaN
49629 2021 400780367 geo filtre_heb_chir False NaN NaN NaN NaN NaN
3638447 2014 420780033 jur mco_hpp_surmin_2014-surmin_c_den_etbt NaN NaN NaN 60.0 NaN NaN
1962445 2021 010005239 geo had_dpa_pcd_2021-pcd_moyenne_regionale NaN NaN NaN NaN Non concerné NaN
4032125 2019 780018727 geo nb_rep_score_ovs_ajust NaN NaN 249.0 NaN NaN NaN

Un grand nombre de variables de nature différente sont regroupées dans cette table (d’où la table metadata venant spécifier la nature de chaque variables). Le nom des variables est stocké dans la colonne key.

Afin de pouvoir distinguer les différents types de valeurs (booléen, entier, décimal, chaîne de caractères, valeurs manquantes, dates), des colonnes dédiées leur sont attachées.

Ainsi, pour une variable donnée, un seul type est possible… :

  • sur l’ensemble de la classe (hormis la colonne missing_value)

  • pour chaque valeur (justifiant la nature lacunaire de ces colonnes, toutes les colonnes autre que celle prenant la valeur étant à NaN)

Caractérisation des données metadata_df#

metadata_df.info()
metadata_df.sample(n=5, random_state=123)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 16 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   name               1000 non-null   object 
 1   title              1000 non-null   object 
 2   description        297 non-null    object 
 3   type               1000 non-null   object 
 4   source             1000 non-null   object 
 5   indicateur         787 non-null    object 
 6   secteur            787 non-null    object 
 7   theme              787 non-null    object 
 8   acronyme           719 non-null    object 
 9   version            728 non-null    float64
 10  variable           728 non-null    object 
 11  annee              728 non-null    float64
 12  ancienne_variable  728 non-null    object 
 13  url_rapport        582 non-null    object 
 14  type_metier        910 non-null    object 
 15  finess_type        1000 non-null   object 
dtypes: float64(2), object(14)
memory usage: 125.1+ KB
name title description type source indicateur secteur theme acronyme version variable annee ancienne_variable url_rapport type_metier finess_type
131 had_dpa_tdp_2015-tdp_c_etbt Valeur tenue du dossier patient NaN float IQSS dossier patient HAD_DPA_TDP_2015 HAD DPA TDP 2015.0 tdp_c_etbt 2015.0 tdp_c_etbt https://www.has-sante.fr/upload/docs/applicati... resultat mixte
203 mco_avc_env_2016-resultat_env Résultat du contrôle expertise neurovasculaire ENV Résultat du contrôle classe IQSS dossier patient MCO_AVC_ENV_2016 MCO AVC ENV 2016.0 resultat_env 2016.0 Resultat_ENV https://www.has-sante.fr/upload/docs/applicati... controle mixte
50 certification_ref_2021_score_objectif_2.3 Objectif 2.3 : Les équipes maîtrisent les risq... NaN float Certification v2021 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN mixte
585 mhs_ias_icsha_2018-icsha_classe Classe (Indicateur de consommation de solution... NaN classe IQSS questionnaire établissement MHS_IAS_ICSHA_2018 MHS IAS ICSHA 2018.0 icsha_classe 2018.0 classe_ias_icsha_v3_mhs https://pprod-web.has-sante.fr/upload/docs/app... classe mixte
138 had_dpa_tdp_2021-tdp_evolution Evolution de l'indicateur par rapport au recue... NaN int-string IQSS dossier patient HAD_DPA_TDP_2021 HAD DPA TDP 2021.0 tdp_evolution 2021.0 evol_dpa_tdp_v4_had https://www.has-sante.fr/upload/docs/applicati... evolution mixte

La colonne name de cette table permet de faire la jointure avec la table valeurs_df via la colonne key associée.

Les métadonnées permettent de compléter les informations associées aux variables, en particulier leur type (encodé implicitement dans valeur.csv, voir ci-dessus), leur source (provenant d’origines variées), leur secteur, leur theme et leur acronyme.

Pour une recherche rapide, vous pouvez aussi utiliser directement le fichier metadata.xlsx qui permet de faire des filtres rapides sur les différentes colonnes.

Caractérisation des données finess_df#

finess_df.info()
finess_df.sample(n=5, random_state=123)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2264973 entries, 0 to 2264972
Data columns (total 56 columns):
 #   Column                        Dtype  
---  ------                        -----  
 0   date_export                   object 
 1   num_finess_et                 object 
 2   num_finess_ej                 object 
 3   raison_sociale_et             object 
 4   raison_sociale_longue_et      object 
 5   complement_raison_sociale     object 
 6   complement_distribution       object 
 7   num_voie                      object 
 8   type_voie                     object 
 9   libelle_voie                  object 
 10  complement_voie               object 
 11  lieu_dit_bp                   object 
 12  commune                       float64
 13  departement                   object 
 14  libelle_departement           object 
 15  ligne_acheminement            object 
 16  telephone                     object 
 17  telecopie                     object 
 18  categorie_et                  int64  
 19  libelle_categorie_et          object 
 20  categorie_agregat_et          int64  
 21  libelle_categorie_agregat_et  object 
 22  siret                         float64
 23  code_ape                      object 
 24  code_mft                      float64
 25  libelle_mft                   object 
 26  code_sph                      float64
 27  libelle_sph                   object 
 28  date_ouverture                object 
 29  date_autorisation             object 
 30  date_maj                      object 
 31  num_uai                       object 
 32  coord_x_et                    float64
 33  coord_y_et                    float64
 34  source_coord_et               object 
 35  date_geocodage                object 
 36  region                        int64  
 37  libelle_region                object 
 38  code_officiel_geo             object 
 39  code_postal                   int64  
 40  libelle_routage               object 
 41  libelle_code_ape              object 
 42  ferme_cette_annee             bool   
 43  latitude                      float64
 44  longitude                     float64
 45  libelle_commune               object 
 46  adresse_postale_ligne_1       object 
 47  adresse_postale_ligne_2       object 
 48  raison_sociale_ej             object 
 49  raison_sociale_longue_ej      object 
 50  statut_juridique_ej           float64
 51  libelle_statut_juridique_ej   object 
 52  statut_juridique              object 
 53  type_etablissement            object 
 54  actif_qualiscope              bool   
 55  dernier_enregistrement        bool   
dtypes: bool(3), float64(9), int64(4), object(40)
memory usage: 922.3+ MB
date_export num_finess_et num_finess_ej raison_sociale_et raison_sociale_longue_et complement_raison_sociale complement_distribution num_voie type_voie libelle_voie ... adresse_postale_ligne_1 adresse_postale_ligne_2 raison_sociale_ej raison_sociale_longue_ej statut_juridique_ej libelle_statut_juridique_ej statut_juridique type_etablissement actif_qualiscope dernier_enregistrement
1134383 2006-12-31 590042222 590813044 MAISON DE QUARTIER DU MOULIN MAISONDE QUARTIER DU MOULIN NaN NaN 97 Avenue DE SUWALKI ... 97 Avenue De Suwalki 59760 GRANDE SYNTHE MAIRIE DE GRANDE SYNTHE MAIRIE DE GRANDE SYNTHE 3.0 Commune Public Public False False
466035 2020-12-31 830009817 830009791 SELARL PHARMACIE CARBONI SELARL PHARMACIE CARBONI NaN NaN 14 Rue CYRUS HUGUES ... 14 Rue Cyrus Hugues 83500 LA SEYNE SUR MER SELARL PHARMACIE CARBONI SELARL PHARMACIE CARBONI 85.0 S.E.L.A.R.L. Privé Privé False False
519304 2012-12-31 530028612 930019484 ESAT M.L.ET R. BURON ETABL. ET SERVICE D AIDE PAR LE TRAVAIL - M.L.... NaN NaN 10 Rue DE LA GRANGE ... 10 Rue De La Grange 53220 PONTMAIN LIGUE ADAPT DIMINUE PHYSIQUE TRAVAIL LIGUE POUR L'ADAPTATION DU DIMINUE PHYSIQUE AU... 61.0 Ass.L.1901 R.U.P. Privé à but non lucratif Privé à but non lucratif False False
1996209 2011-12-31 440045524 440045516 RELAIS PARENTAL RELAIS PARENTAL MAISON D ENFANTS A CARACTERE S... MAISON D ENFTS CARACTERE SOCIAL NaN 1 Rue D YPRES ... 1 Rue D Ypres 44600 ST NAZAIRE CROIX ROUGE FRANCAISE CROIX ROUGE FRANCAISE DELEGATION LOCALE 60.0 Ass.L.1901 non R.U.P Privé à but non lucratif Privé à but non lucratif False False
2137772 2015-12-31 350036174 350014999 S.AIDE A DOMICILE CHAP.JANSON SERVICE D'AIDE MENAGERE A DOMICILE NaN C.C.A.S. 15 Rue DU RELAIS ... 15 Rue Du Relais 35133 LA CHAPELLE JANSON C.C.A.S. CENTRE COMMUNAL D'ACTION SOCIALE 17.0 C.C.A.S. Public Public False False

5 rows × 56 columns

Cette table contient l’ensemble des données Finess historisées. Il y a une ligne par export et par Finess présent à la date de l’export. Pour un même Finess il ya donc très souvent plusieurs lignes.

Les colonnes qui s’avèrent souvent utiles sont:

  • num_finess_et: numéro finess géographique

  • num_finess_ej: numéro finess juridique

  • latitude et longitude: les coordonnées géographiques de l’établissement dans le référentiel WGS84

  • raison_sociale_et: le nom de l’établissement

  • dernier_enregistrement: indique si cette ligne est la dernière connue pour le finess en question

Préparation des données#

Récupération des clés associées à des catégories de valeurs#

Les cas d’usages pour lesquels la base est utilisé ne concernent souvant qu’un sous-ensemble des domaines présents dans la base. On peut par exemple vouloir récupérer l’historique complet des IQSS issus du questionnaire patient (ESATIS).

La mécanique pour sélectionner le sous ensemble de données d’interêt est la suivante:

  • Dans le fichier metadata.csv utiliser la colonne source pour identifier le domaine d’intérêt

  • Dans le fichier metadata.csv recupérer le nom des variables de la source donnée qui sont stockées dans la colonne name

  • Parmis ces variables, utiliser les colonne type_metier (pour les IQSS) et/ou description pour sélectionner les variables d’intérêt

  • Dans le fichier valeurs.csv sélection toutes les lignes dont la colonne key contient une variable du domaine d’intérêt

Ci-dessous un exemple de cette mécanique:

# Affichage des sources de données existantes
metadata_df["source"].value_counts()
source
IQSS dossier patient                665
SAE                                 123
IQSS questionnaire patient           59
IQSS PMSI                            42
Certification v2014                  37
Certification v2021                  22
IQSS questionnaire établissement     21
Certification v2025                  19
Activités ET                         12
Name: count, dtype: int64
# Récupération des noms des variables de certification v2014
certif_14_keys = metadata_df[metadata_df["source"] == "Certification v2014"][
    "name"
]

# Récupération des nom des variables esatis
esatis_keys = metadata_df[
    metadata_df["source"] == "IQSS questionnaire patient"
]["name"]
# Affichage des types métiers certification
# Les variables de certification n'ont pas de type métier
metadata_df.loc[
    metadata_df["name"].isin(certif_14_keys),
    ["name", "description", "type_metier"],
].head(5)
name description type_metier
0 certif_V2014_id_niveau Code du niveau de certification validé par le ... NaN
1 certif_date Date de délibération du collège de la HAS qui ... NaN
2 certif_V2014_id_demarche Code interne à la HAS. Il s'agit d'un code per... NaN
3 certif_V2014_decision_thematique_1 Décision finale pour la thématique : Managemen... NaN
4 certif_V2014_decision_thematique_2 Décision finale pour la thématique : Qualité d... NaN
# Affichage des types métiers esatis
metadata_df.loc[
    metadata_df["name"].isin(esatis_keys),
    ["name", "description", "type_metier"],
].head(5)
name description type_metier
806 nb_rep_score_all_rea_ajust Nombre de réponses concernant la satisfaction ... effectif_observe
807 score_all_rea_ajust Note ajustée de satisfaction globale classemen... score
808 score_all_rea_ajust_dp Note ajustée de satisfaction globale classemen... score_arrondi
809 score_ajust_esatis_region_48h Note ajustée de satisfaction globale des patie... moyenne_regionale
810 score_ajust_esatis_type_48h Note ajustée de satisfaction globale des patie... moyenne_nationale
# Affichage des variables de type score pour esatis
metadata_df.loc[
    metadata_df["name"].isin(esatis_keys)
    & (metadata_df["type_metier"] == "score"),
    ["name", "description", "type_metier"],
]
name description type_metier
807 score_all_rea_ajust Note ajustée de satisfaction globale classemen... score
814 score_accueil_rea_ajust Note ajustée concernant la satisfaction de l'a... score
816 score_pecinf_rea_ajust Note ajustée de satisfaction de la prise en ch... score
818 score_pecmed_rea_ajust Note ajustée de satisfaction de la prise en ch... score
820 score_chambre_rea_ajust Note ajustée de satisfaction de la chambre score
822 score_repas_rea_ajust Note ajustée de satisfaction des repas score
824 score_sortie_rea_ajust Note ajustée de satisfaction de la sortie score
825 taux_reco_brut_48h Pourcentage de patients recommandant certainem... score
828 score_all_ajust_ca Score de satisfaction globale ajusté score
834 score_avh_ajust Note ajustée de la satisfaction avant l'hospit... score
836 score_acc_ajust Note ajustée concernant la satisfaction accuei... score
838 score_pec_ajust Note ajustée de la satisfaction de la prise en... score
840 score_cer_ajust Note ajustée de la satisfaction chambre et rep... score
842 score_ovs_ajust Note ajustée de la satisfaction sortie et reto... score
844 taux_reco_brut_ca Pourcentage de patients recommandant certainem... score
847 score_all_ssr_ajust Score de satisfaction globale ajusté score
853 score_accueil_ssr_ajust Note ajustée concernant la satisfaction au niv... score
855 score_pec_ssr_ajust Note ajustée concernant la satisfaction au niv... score
857 score_lieu_ssr_ajust Note ajustée concernant la satisfaction au niv... score
859 score_repas_ssr_ajust Note ajustée concernant la satisfaction au niv... score
861 score_sortie_ssr_ajust Note ajustée concernant la satisfaction au niv... score
863 taux_reco_brut_ssr Pourcentage de patients recommandant certainem... score

Isolation des domaines de données#

Afin de pouvoir préparer et analyser un domaine de données, il est nécessaire de l’isoler en vue de traitements décrits plus bas. Pour des raisons de volumétrie, le présent tutoriel se restreint aux données esatis. Mais il est naturellement possible d’effectuer ces analyses sur un ensemble de domaines de données, voire sur tous les domaines (les mêmes opérations décrites ici peuvent leur être appliquées).

# Isolation du domaine de données esatis
esatis_df = valeurs_df[valeurs_df["key"].isin(esatis_keys)]

# Isolation du domaine de données certification v2014, pour illustration
certif_df = valeurs_df[valeurs_df["key"].isin(certif_14_keys)]

Pivot de la table esatis#

Actuellement, le format clé-valeur de la base induit que chaque valeur de chaque variable pour chacun des établissements est associée à une ligne. Nous souhaitons pourvoir avoir un regroupement des valeurs pour chaque établissement.

Pour cela, nous devons effectuer les opérations suivantes (cf. cellule de code ci-dessous) :

  • Effectuer un pivot à la table pour avoir une colonne par variable (par type), chaque ligne représentant un établissement pour une année donnée (puisque certaines valeurs sont collectées annuellement)

  • Chaque classe de valeur n’ayant qu’un type de valeur acceptable (hormis le type missing_value), toutes les colonnes des autres types seront remplis de NaN => il s’agit donc également de supprimer ces colonnes inutiles

  • Les colonnes des valeurs manquantes pour une clé sont fusionnées avec la colonne de valeurs de la clé correspondante pour ne pas perdre l’information correspondante

# Les colonnes de valeurs sont pivotées selon le colonne key, pour chaque triplet (annee, finess, finess_type)
esatis_df = esatis_df.pivot(
    index=["annee", "finess", "finess_type"], columns="key"
)

# Toutes les colonnes de valeurs liées à un type incorrect vis-à-vis du type attendu sont remplies par des NaN.
# Il s'agit ici de supprimer ces colonnes qui ne portent aucune information exploitable
esatis_df = esatis_df.dropna(axis=1, how="all")

# Les colonnes associées au type missing_value sont fusionnées avec leur colonne de valeur respective
# Cette étape est optionnelle si les valeurs manquantes sont sans intérêts pour l'étude
if "missing_value" in esatis_df:
    esatis_df = esatis_df.combine_first(esatis_df["missing_value"]).drop(
        columns="missing_value"
    )

# Les colonnes qui résultent de ces opérations ont plusieurs niveaux qui sont ici applatis pour n'en avoir plus qu'un
esatis_df = esatis_df.droplevel(0, axis=1)
# Les données d'années, de finess et de finess_type utilisées jusqu'ici comme index sont basculées en colonnes dans la table.
esatis_df = esatis_df.reset_index()

Un échantillon de la table résultante peut être observée ci-dessous, en notant qu’il manque des données pour de nombreux établissements sur certaines années car certains indicateurs n’étaient pas calculés à ce moment là.

esatis_df.sample(n=5, random_state=123)
key annee finess finess_type classement_48h classement_ca classement_ssr evolution_48h evolution_ca evolution_ssr nb_reco_brut_48h ... score_pec_ssr_ajust score_pecinf_rea_ajust score_pecmed_rea_ajust score_repas_rea_ajust score_repas_ssr_ajust score_sortie_rea_ajust score_sortie_ssr_ajust taux_reco_brut_48h taux_reco_brut_ca taux_reco_brut_ssr
6858 2021 860000025 geo B A NaN NaN NaN NaN 335.0 ... NaN 81.24 81.43 59.19 NaN 64.86 NaN 55.2 73.4 NaN
14871 2025 950000695 geo NaN NaN Données insuffisantes NaN NaN NaN NaN ... Données insuffisantes NaN NaN NaN Données insuffisantes NaN Données insuffisantes NaN NaN Données insuffisantes
1992 2017 750300014 geo Données insuffisantes NaN NaN 4-Non calculable NaN NaN NaN ... NaN Données insuffisantes Données insuffisantes Données insuffisantes NaN Données insuffisantes NaN NaN NaN NaN
13802 2025 490002037 geo B A NaN -1.0 1.0 NaN 354.0 ... NaN 82.99 82.04 60.41 NaN 68.12 NaN 59.6 68.1 NaN
1811 2017 620101501 geo B NaN NaN Non calculable NaN NaN NaN ... NaN 80.75 82.17 47.51 NaN 68.02 NaN NaN NaN NaN

5 rows × 62 columns

Exploitation des données#

Dans cette section, une analyse simple sur le domaine esatis est effectuée à des fins d’illustration quant à la valorisation de ces données.

Le cas d’usage de cette analyse est l’observation de l’évolution du positionnement national d’un établissement identifié (il s’agit ici de l’établissement associé au finess n°060000478) vis-à-vis des établissements de même catégorie (il s’agit ici de la catégorie Centres Hospitaliers, associée au code 1102) sur l’indicateur score_all_ajust_ca (correspondant au “score de satisfaction globale ajusté” en chirurgie ambulatoire).

Pour ce faire, il faut :

  • associer aux données esatis les données finess nécessaires à la jointure et à la catégorisation des établissements

  • calculer le classement de l’établissement (son rang absolu et le pourcentage associé) pour chaque année

  • représenter la distribution des établissements pour chaque année en positionnant l’établissement étudié

Ces étapes sont réalisées dans les deux sections ci-dessous.

Finalisation de la préparation des données#

# On ne prend que les informations des finess les plus récentes pour éviter les doublons à la jointure
finess_df = finess_df.loc[finess_df["dernier_enregistrement"]]

# Jointure entre les données esatis et les données Finess restreintes aux agrégats de catégories d'établissement
df = finess_df[
    [
        "num_finess_et",
        "categorie_agregat_et",
        "raison_sociale_et",
        "departement",
    ]
].merge(esatis_df, left_on="num_finess_et", right_on="finess", how="inner")
df.sample(5, random_state=123)
num_finess_et categorie_agregat_et raison_sociale_et departement annee finess finess_type classement_48h classement_ca classement_ssr ... score_pec_ssr_ajust score_pecinf_rea_ajust score_pecmed_rea_ajust score_repas_rea_ajust score_repas_ssr_ajust score_sortie_rea_ajust score_sortie_ssr_ajust taux_reco_brut_48h taux_reco_brut_ca taux_reco_brut_ssr
6858 560003980 1102 GHBS - HOPITAL DE KERBERNES 56 2025 560003980 geo NaN NaN Données insuffisantes ... Données insuffisantes NaN NaN NaN Données insuffisantes NaN Données insuffisantes NaN NaN Données insuffisantes
14870 620012948 1107 CLINIQUE MAHAUT DE TERMONDE 62 2024 620012948 geo NaN NaN Données insuffisantes ... Données insuffisantes NaN NaN NaN Données insuffisantes NaN Données insuffisantes NaN NaN Données insuffisantes
1992 170000061 1102 CSS DU CHATEAU MARLONGES - CHAMBON 17 2025 170000061 geo NaN NaN Données insuffisantes ... Données insuffisantes NaN NaN NaN Données insuffisantes NaN Données insuffisantes NaN NaN Données insuffisantes
13801 940300163 1107 CLINIQUE LES TOURNELLES 94 2025 940300163 geo Données insuffisantes NaN Données insuffisantes ... Données insuffisantes Données insuffisantes Données insuffisantes Données insuffisantes Données insuffisantes Données insuffisantes Données insuffisantes Données insuffisantes NaN Données insuffisantes
1811 140025123 1107 CRF DE CAEN 14 2023 140025123 geo NaN NaN C ... 76.78 NaN NaN NaN 58.35 NaN 61.76 NaN NaN 53.1

5 rows × 66 columns

# Seuls les centres hospitaliers sont conservés
df = df[df["categorie_agregat_et"] == 1102]

# Pour cette étude, seules les colonnes suivantes sont nécessaires pour la suite : annee, taux_reco_brut_ca, finess
# Par ailleurs, cet indicateur n'est collecté qu'a partir de 2019.
df = df.loc[
    df["annee"] >= 2019,
    [
        "annee",
        "finess",
        "raison_sociale_et",
        "departement",
        "score_all_ajust_ca",
    ],
]

# Les établissements sans valeurs pour l'indicateur sont retirés
df = df.dropna()

Un extrait de la table correspondante peut être observée :

df.sample(5, random_state=123)
annee finess raison_sociale_et departement score_all_ajust_ca
2409 2019 220000343 CENTRE HOSPITALIER GUINGAMP 22 72.85
1986 2021 170000038 CENTRE HOSPITALIER JONZAC 17 77.96
169 2020 020000535 CH CHAUNY 02 Données insuffisantes
10291 2019 740781224 HOPITAUX DU MONT BLANC SITE SALLANCHES 74 75.98
12358 2022 840018337 CH D'AVIGNON CHIRURGIE SITE APT 84 Données insuffisantes

Visualisation des résultats#

es_finess = "060000478"
axes = df.plot.hist(by="annee", bins=20, figsize=(10, 15), legend=False)
for i, annee in enumerate(np.sort(df["annee"].unique())):
    axes[i].set_xlim(0, 100)
    axes[i].set_ylim(0, 37)
    axes[i].set_ylabel("Fréquences", fontsize=12)
    df_tmp = (
        df[df["annee"] == annee]
        .sort_values("score_all_ajust_ca", ascending=False)
        .reset_index()
    )
    value = df_tmp.loc[
        df_tmp["finess"] == es_finess, "score_all_ajust_ca"
    ].values[0]
    axes[i].axvline(value, color="red")
    axes[i].text(value - 3.2, -4.5, str(value), color="red", fontsize=12)
    axes[i].text(
        value + 1, 38, "finess n°" + str(es_finess), color="red", fontsize=12
    )
    rank = df_tmp[df_tmp["finess"] == es_finess].index.item() + 1
    card_finess = df_tmp.shape[0]
    rank_ratio = 100 * rank / card_finess
    axes[i].text(
        5,
        30,
        f"classement : {rank} / {card_finess}\nposition (%) : {rank_ratio:,.1f}",
        backgroundcolor="lightgrey",
        color="red",
        fontsize=12,
        fontweight="roman",
    )
axes[2].set_xlabel(
    "score de satisfaction globale ajusté",
    fontsize=12,
    labelpad=15,
)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 2
      1 es_finess = "060000478"
----> 2 axes = df.plot.hist(by="annee", bins=20, figsize=(10, 15), legend=False)
      3 for i, annee in enumerate(np.sort(df["annee"].unique())):
      4     axes[i].set_xlim(0, 100)

File ~/.cache/pypoetry/virtualenvs/bqss-9TtSrW0h-py3.11/lib/python3.11/site-packages/pandas/plotting/_core.py:1409, in PlotAccessor.hist(self, by, bins, **kwargs)
   1349 def hist(
   1350     self, by: IndexLabel | None = None, bins: int = 10, **kwargs
   1351 ) -> PlotAccessor:
   1352     """
   1353     Draw one histogram of the DataFrame's columns.
   1354 
   (...)   1407         >>> ax = df.plot.hist(column=["age"], by="gender", figsize=(10, 8))
   1408     """
-> 1409     return self(kind="hist", by=by, bins=bins, **kwargs)

File ~/.cache/pypoetry/virtualenvs/bqss-9TtSrW0h-py3.11/lib/python3.11/site-packages/pandas/plotting/_core.py:1030, in PlotAccessor.__call__(self, *args, **kwargs)
   1027             label_name = label_kw or data.columns
   1028             data.columns = label_name
-> 1030 return plot_backend.plot(data, kind=kind, **kwargs)

File ~/.cache/pypoetry/virtualenvs/bqss-9TtSrW0h-py3.11/lib/python3.11/site-packages/pandas/plotting/_matplotlib/__init__.py:71, in plot(data, kind, **kwargs)
     69         kwargs["ax"] = getattr(ax, "left_ax", ax)
     70 plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71 plot_obj.generate()
     72 plot_obj.draw()
     73 return plot_obj.result

File ~/.cache/pypoetry/virtualenvs/bqss-9TtSrW0h-py3.11/lib/python3.11/site-packages/pandas/plotting/_matplotlib/core.py:499, in MPLPlot.generate(self)
    497 @final
    498 def generate(self) -> None:
--> 499     self._compute_plot_data()
    500     fig = self.fig
    501     self._make_plot(fig)

File ~/.cache/pypoetry/virtualenvs/bqss-9TtSrW0h-py3.11/lib/python3.11/site-packages/pandas/plotting/_matplotlib/core.py:698, in MPLPlot._compute_plot_data(self)
    696 # no non-numeric frames or series allowed
    697 if is_empty:
--> 698     raise TypeError("no numeric data to plot")
    700 self.data = numeric_data.apply(type(self)._convert_to_ndarray)

TypeError: no numeric data to plot

⚠️ Attention : L’analyse et l’interprétation des résultats des IQSS nécessite de bien prendre connaissance de leur définition.

Nous pouvons constater que l’établissement considéré dans cette étude a:

  • Amélioré son score (de 77.73 à 80.78)

  • Amélioré sa place dans la distribution de 54.6ème percentile à 24.3ème percentile