Source code for cardinal_pythonlib.psychiatry.drugs

#!/usr/bin/env python
# cardinal_pythonlib/psychiatry/drugs.py

"""
===============================================================================

    Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).

    This file is part of cardinal_pythonlib.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        https://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

===============================================================================

**Drug information, with an emphasis on psychotropic drugs, including
translating specific to generic names.**

**Examples**

Test within Python:

.. code-block:: python

    from cardinal_pythonlib.psychiatry.drugs import *

    drug_name_to_generic("UNKNOWN")
    drug_name_to_generic("UNKNOWN", unknown_to_default=True)
    drug_names_to_generic([
        "citalopram", "Citalopram", "Cipramil", "Celexa",
        "olanzepine",  # typo
        "dextroamphetamine",
        "amitryptyline",
    ])

**Antidepressants**

As of 2018-07-01, this is a functional superset of the SLAM
antidepressant-finding SQL (see ``dep_med_v1``), though mainly a superset in
non-antidepressant respects; the only antidepressants it adds are:

- buproprion, maprotiline

The SLAM antidepressant finder finds:

- tricyclic (category)
- amitriptyline, clomipramine, dosulepin, doxepin, imipramine, lofepramine,
  nortriptyline, trimipramine
- mianserin, trazodone, phenelzine, isocarboxazid, tranylcypromine, moclobemide
- citalopram, escitalopram, fluoxetine, fluvoxamine, paroxetine, sertraline
- mirtazapine, reboxetine, venlafaxine, agomelatine, duloxetine
- flupentixol, tryptophan

Sorted, that is:

.. code-block:: none

    agomelatine
    amitriptyline
    citalopram
    clomipramine
    dosulepin
    doxepin
    duloxetine
    escitalopram
    fluoxetine
    flupentixol
    fluvoxamine
    imipramine
    isocarboxazid
    lofepramine
    mianserin
    mirtazapine
    moclobemide
    nortriptyline
    paroxetine
    phenelzine
    reboxetine
    sertraline
    tranylcypromine
    trazodone
    tricyclic
    trimipramine
    tryptophan
    venlafaxine

Compare that against the output of:

.. code-block:: python

    [x.generic_name for x in all_drugs_where(slam_antidepressant_finder=True,
                                             include_categories=True)]

**Using this code from R via reticulate**

Test within R:

.. code-block:: r

    # -------------------------------------------------------------------------
    # Load libraries
    # -------------------------------------------------------------------------

    RUN_ONCE_ONLY <- '
        library(devtools)
        devtools::install_github("rstudio/reticulate")  # get latest version
    '

    library(data.table)
    library(reticulate)

    # -------------------------------------------------------------------------
    # Set up reticulate
    # -------------------------------------------------------------------------

    VENV <- "~/dev/venvs/cardinal_pythonlib"  # or your preferred virtualenv
    PYTHON_EXECUTABLE <- ifelse(
        .Platform$OS.type == "windows",
        file.path(VENV, "Scripts", "python.exe"),  # Windows
        file.path(VENV, "bin", "python")  # Linux
    )
    reticulate::use_python(PYTHON_EXECUTABLE, required=TRUE)
    # ... it is CRITICAL to use required=TRUE, or it might fail silently

    # Unnecessary now reticulate::use_python() works:
    #
    # PYTHON_VERSION <- "python3.5"
    # CARDINAL_PYTHONLIB_BASEDIR <- ifelse(
    #     .Platform$OS.type == "windows",
    #     file.path(VENV, "lib", "site-packages/cardinal_pythonlib"),
    #     file.path(VENV, "lib", PYTHON_VERSION, "site-packages/cardinal_pythonlib")
    # )
    # reticulate::use_virtualenv(VENV, required=TRUE)
    #
    # cpl_fileops <- reticulate::import_from_path("fileops", CARDINAL_PYTHONLIB_BASEDIR)
    # cpl_drugs <- reticulate::import_from_path("drugs", file.path(CARDINAL_PYTHONLIB_BASEDIR, "psychiatry"))
    #
    # ... this is NOT WORKING properly; dotted imports via reticulate::import() fail; also, imports from
    # within the Python code fail even if you use reticulate::import_from_path(); this suggests the virtualenv is not set up
    # properly; use reticulate::use_python() instead.

    # -------------------------------------------------------------------------
    # Import Python modules
    # -------------------------------------------------------------------------

    cardinal_pythonlib <- reticulate::import("cardinal_pythonlib")
    cpl_fileops <- reticulate::import("cardinal_pythonlib.fileops")
    cpl_drugs <- reticulate::import("cardinal_pythonlib.psychiatry.drugs")

    # -------------------------------------------------------------------------
    # Do something useful
    # -------------------------------------------------------------------------

    testnames <- c("citalopram", "Cipramil", "Prozac", "fluoxetine")
    # Works for simple variables:
    cpl_drugs$drug_names_to_generic(testnames)

    # Also works for data table replacements:
    dt <- data.table(
        subject = c("Alice", "Bob", "Charles", "Dawn", "Egbert", "Flora"),
        drug = c("citalopram", "Cipramil", "Prozac", "fluoxetine", "Priadel", "Haldol")
    )
    dt[, drug_generic := cpl_drugs$drug_names_to_generic(drug)]
    dt[, is_antidepressant := cpl_drugs$drug_names_match_criteria(
            drug_generic,
            names_are_generic=TRUE,
            antidepressant=TRUE)]
    dt[, is_antidepressant_not_ssri := cpl_drugs$drug_names_match_criteria(
            drug_generic,
            names_are_generic=TRUE,
            antidepressant=TRUE,
            ssri=FALSE)]
    dt[, is_conventional_antidepressant := cpl_drugs$drug_names_match_criteria(
            drug_generic,
            names_are_generic=TRUE,
            conventional_antidepressant=TRUE)]
    dt[, slam_antidepressant_finder := cpl_drugs$drug_names_match_criteria(
            drug_generic,
            names_are_generic=TRUE,
            slam_antidepressant_finder=TRUE,
            include_categories=TRUE)]

**Use for SQL finding**

.. code-block:: python

    from typing import List
    from cardinal_pythonlib.psychiatry.drugs import *

    colname = "somecol"

    antidepressants = all_drugs_where(conventional_antidepressant=True)  # type: List[Drug]
    antidep_sql_parts = [drug.sql_column_like_drug(colname) for drug in antidepressants]
    antidep_sql = " OR ".join(antidep_sql_parts)

    antipsychotics = all_drugs_where(antipsychotic=True)  # type: List[Drug]
    antipsy_sql_parts = [drug.sql_column_like_drug(colname) for drug in antipsychotics]
    antipsy_sql = " OR ".join(antipsy_sql_parts)

    alldrugs = all_drugs_where()
    alldrug_sql_parts = [drug.sql_column_like_drug(colname) for drug in alldrugs]
    alldrug_sql = " OR ".join(alldrug_sql_parts)

    lithium = get_drug("lithium")
    lithium_sql = lithium.sql_column_like_drug(colname)
    # HOWEVER, NOTE THAT LITHIUM IS CURRENTLY OVER-INCLUSIVE and will include
    # lithium chloride for LiDCO measurement.

"""  # noqa

import re
from typing import Dict, List, Optional, Pattern, Union

from cardinal_pythonlib.sql.literals import sql_string_literal


# =============================================================================
# Regex constants
# =============================================================================

WILDCARD = ".*"  # if re.DOTALL is set, this also matches newlines
WB = WORD_BOUNDARY = r"\b"


# =============================================================================
# Class to capture drug information
# =============================================================================


[docs]class Drug(object): """ Class to describe a specific drug, or a drug category. Also embodies knowledge about brand names and common misspellings. See the :const:`DRUGS` list for example of use. """ def __init__( self, # Names generic: Union[str, List[str]], alternatives: List[str] = None, category_not_drug: bool = False, add_preceding_wildcards: bool = True, add_preceding_word_boundary: bool = True, add_following_wildcards: bool = True, # Psychiatry psychotropic: bool = None, # special; can be used as override if False # noqa antidepressant: bool = False, conventional_antidepressant: bool = False, ssri: bool = False, non_ssri_modern_antidepressant: bool = False, tricyclic_antidepressant: bool = False, tetracyclic_and_related_antidepressant: bool = False, monoamine_oxidase_inhibitor: bool = False, antipsychotic: bool = False, first_generation_antipsychotic: bool = False, second_generation_antipsychotic: bool = False, stimulant: bool = False, anticholinergic: bool = False, benzodiazepine: bool = False, z_drug: bool = False, non_benzodiazepine_anxiolytic: bool = False, gaba_a_functional_agonist: bool = False, gaba_b_functional_agonist: bool = False, mood_stabilizer: bool = False, # Endocrinology antidiabetic: bool = False, sulfonylurea: bool = False, biguanide: bool = False, glifozin: bool = False, glp1_agonist: bool = False, dpp4_inhibitor: bool = False, meglitinide: bool = False, thiazolidinedione: bool = False, # Cardiovascular cardiovascular: bool = False, beta_blocker: bool = False, ace_inhibitor: bool = False, statin: bool = False, # Respiratory respiratory: bool = False, beta_agonist: bool = False, # Gastrointestinal gastrointestinal: bool = False, proton_pump_inhibitor: bool = False, nonsteroidal_anti_inflammatory: bool = False, # Nutritional vitamin: bool = False, # Special flags: slam_antidepressant_finder: bool = False, ) -> None: # noinspection PyUnresolvedReferences """ Initialize and determine/store category knowledge. ``alternatives`` can include regexes (as text). We add front/back wildcards by default; this handles all situations like "depot X", etc. We also add a preceding word boundary (after the wildcard); thus the usual transformation is ``XXX`` -> ``.*\bXXX.*``. Args: generic: generic name, or list of names alternatives: can include regexes (as text) category_not_drug: is this a drug category, not a specific drug? add_preceding_wildcards: when making a regex (etc.), add a wildcard to the start of all possibilities (generic + alternative names) that don't already have one? add_preceding_word_boundary: when making a regex (etc.), add word boundaries to the start of all possibilities (generic + alternative names) that don't already have one? add_following_wildcards: when making a regex (etc.), add a wildcard to the end of all possibilities (generic + alternative names) that don't already have one? psychotropic: a psychotropic drug? antidepressant: an antidepressant? conventional_antidepressant: a traditional antidepressant? ssri: a selective serotonin reuptake inhibitor (SSRI)? non_ssri_modern_antidepressant: a non-SSRI "modern" antidepressant? tricyclic_antidepressant: a tricyclic? tetracyclic_and_related_antidepressant: a tetracyclic or related? monoamine_oxidase_inhibitor: a MAO-I? antipsychotic: an antipsychotic? first_generation_antipsychotic: an FGA? second_generation_antipsychotic: an SGA? stimulant: a psychostimulant? anticholinergic: an anticholinergic? benzodiazepine: a benzodiazepine? z_drug: a "Z" drug (e.g. zopiclone, zolpidem, ...) non_benzodiazepine_anxiolytic: a non-BZ anxiolytic? gaba_a_functional_agonist: a GABA-A functional agonist? gaba_b_functional_agonist: a GABA-B functional agonist? mood_stabilizer: a "mood stabilizer"? antidiabetic: treats diabetes? sulfonylurea: a sulfonylurea (sulphonylurea), for diabetes? biguanide: a biguanide, for diabetes? glifozin: a glifozin, for diabetes? glp1_agonist: a GLP-1 agonist, for diabetes? dpp4_inhibitor: a DPP4 inhibitor, for diabetes? meglitinide: a meglitinide, for diabetes? thiazolidinedione: a thiazolidinedione, for diabetes? cardiovascular: a cardiovascular drug? beta_blocker: a beta adrenoceptor antagonist? ace_inhibitor: an ACE inhibitor? statin: a statin? respiratory: a respiratory drug? beta_agonist: a beta adrenoceptor agonist? gastrointestinal: a gastrointestinal drug? proton_pump_inhibitor: a PPI? nonsteroidal_anti_inflammatory: an NSAID? vitamin: a vitamin? slam_antidepressant_finder: a drug found by the SLAM antidepressant-finding code? (A bit specialized, this one!) Attributes: mixture (bool): is this a mixture of more than one drug? Will be set if more than one generic name is given. all_generics (List[str]): list of all generic names in lower case generic_name: generic name (or combination name like ``a_with_b`` for mixtures of ``a`` and ``b``) regex: compiled case-insensitive regular expression to match possible names """ self.add_preceding_word_boundary = add_preceding_word_boundary self.add_preceding_wildcards = add_preceding_wildcards self.add_following_wildcards = add_following_wildcards # --------------------------------------------------------------------- # Name handling # --------------------------------------------------------------------- if isinstance(generic, list): self.mixture = True self.all_generics = [x.lower().strip() for x in generic] self.generic_name = "_with_".join(self.all_generics) elif isinstance(generic, str): self.mixture = False self.generic_name = generic.lower().strip() self.all_generics = [self.generic_name] else: raise ValueError(f"Bad generic_name: {generic!r}") self.alternatives = alternatives or [] # type: List[str] self._regex_text = None # type: Optional[str] self._regex = None # type: Optional[Pattern] self._sql_like_fragments = None # type: Optional[List[str]] # --------------------------------------------------------------------- # Things we know about psychotropics # --------------------------------------------------------------------- if ( ssri or non_ssri_modern_antidepressant or tricyclic_antidepressant or tetracyclic_and_related_antidepressant or monoamine_oxidase_inhibitor ): conventional_antidepressant = True if conventional_antidepressant: antidepressant = True if first_generation_antipsychotic or second_generation_antipsychotic: antipsychotic = True if benzodiazepine or z_drug: gaba_a_functional_agonist = True if ( antidepressant or antipsychotic or stimulant or anticholinergic or gaba_a_functional_agonist or gaba_b_functional_agonist or mood_stabilizer ) and (psychotropic is not False): psychotropic = True if psychotropic is None: psychotropic = False # --------------------------------------------------------------------- # Things we know about other drugs # --------------------------------------------------------------------- if ( sulfonylurea or biguanide or glifozin or glp1_agonist or dpp4_inhibitor or meglitinide or thiazolidinedione ): antidiabetic = True if beta_blocker or ace_inhibitor: cardiovascular = True # --------------------------------------------------------------------- # Store category knowledge # --------------------------------------------------------------------- self.category_not_drug = category_not_drug self.psychotropic = psychotropic self.antidepressant = antidepressant self.conventional_antidepressant = conventional_antidepressant self.ssri = ssri self.non_ssri_modern_antidepressant = non_ssri_modern_antidepressant self.tricyclic = tricyclic_antidepressant self.tetracyclic_and_related_antidepressant = ( tetracyclic_and_related_antidepressant ) self.monoamine_oxidase_inhibitor = monoamine_oxidase_inhibitor self.antipsychotic = antipsychotic self.first_generation_antipsychotic = first_generation_antipsychotic self.second_generation_antipsychotic = second_generation_antipsychotic self.stimulant = stimulant self.anticholinergic = anticholinergic self.benzodiazepine = benzodiazepine self.z_drug = z_drug self.gaba_a_functional_agonist = gaba_a_functional_agonist self.gaba_b_functional_agonist = gaba_b_functional_agonist self.non_benzodiazepine_anxiolytic = non_benzodiazepine_anxiolytic self.mood_stabilizer = mood_stabilizer self.antidiabetic = antidiabetic self.sulfonylurea = sulfonylurea self.biguanide = biguanide self.cardiovascular = cardiovascular self.beta_blocker = beta_blocker self.ace_inhibitor = ace_inhibitor self.statin = statin self.respiratory = respiratory self.beta_agonist = beta_agonist self.gastrointestinal = gastrointestinal self.proton_pump_inhibitor = proton_pump_inhibitor self.nonsteroidal_anti_inflammatory = nonsteroidal_anti_inflammatory self.vitamin = vitamin # --------------------------------------------------------------------- # Store other flags # --------------------------------------------------------------------- self.slam_antidepressant_finder = slam_antidepressant_finder @property def regex_text(self) -> str: """ Return regex text (yet to be compiled) for this drug. """ if self._regex_text is None: possibilities = [] # type: List[str] for p in list(set(self.all_generics + self.alternatives)): if self.add_preceding_word_boundary and not p.startswith(WB): p = WB + p if self.add_preceding_wildcards and not p.startswith(WILDCARD): p = WILDCARD + p if self.add_following_wildcards and not p.endswith(WILDCARD): p = p + WILDCARD possibilities.append(p) self._regex_text = "|".join("(?:" + x + ")" for x in possibilities) return self._regex_text @property def regex(self) -> Pattern: """ Returns a compiled regex for this drug. """ if self._regex is None: self._regex = re.compile( self.regex_text, re.IGNORECASE | re.DOTALL ) return self._regex
[docs] @staticmethod def regex_to_sql_like( regex_text: str, single_wildcard: str = "_", zero_or_more_wildcard: str = "%", ) -> List[str]: """ Converts regular expression text to a reasonably close fragment for the SQL ``LIKE`` operator. NOT PERFECT, but works for current built-in regular expressions. Args: regex_text: regular expression text to work with single_wildcard: SQL single wildcard, typically an underscore zero_or_more_wildcard: SQL "zero/one/many" wildcard, probably always a percent symbol Returns: string for an SQL string literal Raises: :exc:`ValueError` for some regex text that it doesn't understand properly """ def append_to_all(new_content: str) -> None: nonlocal results results = [r + new_content for r in results] def split_and_append(new_options: List[str]) -> None: nonlocal results newresults = [] # type: List[str] for option in new_options: newresults.extend([r + option for r in results]) results = newresults def deduplicate_wildcards(text: str) -> str: while zero_or_more_wildcard + zero_or_more_wildcard in text: text = text.replace( zero_or_more_wildcard + zero_or_more_wildcard, zero_or_more_wildcard, ) return text # Basic processing working = regex_text # strings are immutable results = [zero_or_more_wildcard] # start with a wildcard while working: if working.startswith(".*"): # e.g. ".*ozapi" append_to_all(zero_or_more_wildcard) working = working[2:] elif working.startswith("["): # e.g. "[io]peridol" close_bracket = working.index("]") # may raise bracketed = working[1:close_bracket] option_groups = bracketed.split("|") options = [c for group in option_groups for c in group] split_and_append(options) working = working[close_bracket + 1 :] elif len(working) > 1 and working[1] == "?": # e.g. "r?azole" split_and_append(["", working[0]]) # ... regex "optional character" # ... SQL: some results with a single wildcard, some without working = working[2:] elif working.startswith("."): # single character wildcard append_to_all(single_wildcard) working = working[1:] else: append_to_all(working[0]) working = working[1:] append_to_all(zero_or_more_wildcard) # end with a wildcard # Remove any duplicate (consecutive) % wildcards: results = [deduplicate_wildcards(r) for r in results] # Done return results
@property def sql_like_fragments(self) -> List[str]: """ Returns all the string literals to which a database column should be compared using the SQL ``LIKE`` operator, to match this drug. This isn't as accurate as the regex, but ``LIKE`` can do less. ``LIKE`` uses the wildcards ``?`` and ``%``. """ if self._sql_like_fragments is None: self._sql_like_fragments = [] for p in list(set(self.all_generics + self.alternatives)): self._sql_like_fragments.extend(self.regex_to_sql_like(p)) return self._sql_like_fragments
[docs] def name_matches(self, name: str) -> bool: """ Detects whether the name that's passed matches our knowledge of any of things that this drug might be called: generic name, brand name(s), common misspellings. The parameter should be pre-stripped of edge whitespace. """ return bool(self.regex.match(name))
[docs] def sql_column_like_drug(self, column_name: str) -> str: """ Returns SQL like .. code-block:: sql (column_name LIKE '%drugname1%' OR column_name LIKE '%drugname2%') for the drug names that this Drug object knows about. Args: column_name: column name, pre-escaped if necessary Returns: SQL fragment as above """ clauses = [ f"{column_name} LIKE {sql_string_literal(f)}" for f in self.sql_like_fragments ] return f"({' OR '.join(clauses)})"
# Source data. DRUGS = [ # In comments below: (*) misspelling, capitalized for brand name, (~) # hybrid generic/brand name, (+) old name. # ------------------------------------------------------------------------- # SSRIs # ------------------------------------------------------------------------- Drug( "citalopram", ["Cipramil", "Celexa"], ssri=True, slam_antidepressant_finder=True, ), Drug( "escitalopram", ["Cipralex", "Lexapro"], ssri=True, slam_antidepressant_finder=True, ), Drug( "fluoxetine", ["Prozac", "Bellzac", "Oxactin", "Prozep", "Sarafem", "fluox.*"], # CPFT 2013: "fluoxetine Dec" ssri=True, slam_antidepressant_finder=True, ), Drug( "fluvoxamine", ["Luvox", "Faverin", "fluvoxamine.*"], # e.g. "fluvoxamine maleate" ssri=True, slam_antidepressant_finder=True, ), Drug( "paroxetine", ["Seroxat", "Paxil"], # there are other brands elsewhere... ssri=True, slam_antidepressant_finder=True, ), Drug( "sertraline", ["Lustral", "Zoloft", "Bellsert"], # NOT Seretra (cf. SLAM code, see email to self 2016-12-02); Seretra = # seratrodast = for asthma ssri=True, slam_antidepressant_finder=True, ), # ------------------------------------------------------------------------- # FIRST-GENERATION ANTIPSYCHOTICS # ------------------------------------------------------------------------- Drug("benperidol", ["Anquil"], first_generation_antipsychotic=True), Drug("chlorpromazine", ["Largactil"], first_generation_antipsychotic=True), Drug( "flupentixol", ["Depixol", "Fluanxol", "flupent.*", "Depixol.*"], # e.g. flupenthixol, flupenthixol decanoate, flupentixol decanoate first_generation_antipsychotic=True, antidepressant=True, slam_antidepressant_finder=True, ), Drug( "fluphenazine", ["Modecate", "fluphen.*", "Modecate.*"], first_generation_antipsychotic=True, ), Drug( "haloperidol", [ "Haldol", "Serenase", "hal[io]p.*", "Dozi.*", "Hald.*", "Serena.*", # NB Serenase, Serenace. # CPFT 2013: haloperidol, haloperidol decanoate, Haldol, Haldol # decanoate, Serenase. ], first_generation_antipsychotic=True, ), Drug("levomepromazine", ["Nozinan"], first_generation_antipsychotic=True), Drug("pericyazine", first_generation_antipsychotic=True), Drug("perphenazine", ["Fentazin"], first_generation_antipsychotic=True), Drug( ["amitriptyline", "perphenazine"], ["Triptafen"], # special tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug("pimozide", ["Orap"], first_generation_antipsychotic=True), Drug( "pipotiazine", ["pipot.*", "Piport.*"], # ... actually (CPFT 2013): pipotiazine, Piportil first_generation_antipsychotic=True, ), Drug( "prochlorperazine", ["Stemetil"], first_generation_antipsychotic=True ), Drug("promazine", first_generation_antipsychotic=True), Drug( "sulpiride", ["Dolmatil", "Sulpor"], first_generation_antipsychotic=True, ), Drug( "trifluoperazine", ["Stelazine"], first_generation_antipsychotic=True ), Drug( "zuclopenthixol", ["zuclop.*", "Clopix.*", "Acc?uphase"], # ... actually (CPFT 2013): zuclopenthixol, zuclopenthixol acetate, # zuclopenthixol decanoate, Clopixol, Clopixol Decanoate, Acuphase first_generation_antipsychotic=True, ), # ------------------------------------------------------------------------- # SECOND-GENERATION ANTIPSYCHOTICS # ------------------------------------------------------------------------- Drug( "amisulpride", ["amisulp.*", "Solian"], # ... actually (CPFT 2013): amisulpiride(*), amisulpride, Solian second_generation_antipsychotic=True, ), Drug( "aripiprazole", ["Abilify", "ari?pr?ipr?azol.*"], second_generation_antipsychotic=True, ), Drug( "asenapine", ["Saphris", "Sycrest"], second_generation_antipsychotic=True, ), Drug( "clozapine", ["cloz.*", "Denz.*", "Zapon.*"], # ... actually (CPFT 2013): clozapine, Clozaril, clozepine(*) second_generation_antipsychotic=True, ), Drug( "iloperidone", ["Fanapt", "Fanapta", "Zomaril"], second_generation_antipsychotic=True, ), Drug("lurasidone", ["Latuda"], second_generation_antipsychotic=True), Drug( "olanzapine", ["olanz.*", "Zalast.*", "Zyprex.*", "Zypad.*"], # ... actually (CPFT 2013): olanzapine, olanzapine embonate, # olanz(*), olanzepine(*), olanzapin(*), Zyprexa second_generation_antipsychotic=True, ), Drug( "paliperidone", ["Invega", "Xeplion"], second_generation_antipsychotic=True, ), Drug( "quetiapine", ["quet.*", "Seroquel"], # ... actually (CPFT 2013): quetiapine, quetiepine(*), Seroquel second_generation_antipsychotic=True, ), Drug( "risperidone", ["risp.*", "Consta"], # ... actually (CPFT 2013): risperidone, risperadone(*), Risperidone # Consta (~), Risperdal, Risperdal Consta second_generation_antipsychotic=True, ), Drug( "sertindole", ["Serdolect", "Serlect"], second_generation_antipsychotic=True, ), Drug("ziprasidone", second_generation_antipsychotic=True), Drug( "zotepine", # not in UK ["Nipolept", "Losizopilon", "Lodopin", "Setous"], second_generation_antipsychotic=True, ), # ------------------------------------------------------------------------- # STIMULANTS # ------------------------------------------------------------------------- Drug( "amfetamine", [".*am[ph|f]etamine.*", "Adderall"], # ... actually (CPFT 2013): dextroamphetamine(+), dexamfetamine stimulant=True, ), Drug( "methylphenidate", ["Ritalin", "Concerta.*", "Equasym.*", "Medikinet.*"], # ... actually (CPFT 2013): methylphenidate, Ritalin, Concerta stimulant=True, ), Drug("modafinil", ["Provigil"], stimulant=True), # ------------------------------------------------------------------------- # ANTICHOLINERGICS # ------------------------------------------------------------------------- Drug("benztropine", ["benzatropine"], anticholinergic=True), Drug("orphenadrine", ["Biorphen", "Disipal"], anticholinergic=True), Drug("procyclidine", ["Arpicolin", "Kemadrin"], anticholinergic=True), Drug("trihexyphenidyl", ["Broflex"], anticholinergic=True), # ------------------------------------------------------------------------- # OTHER MODERN ANTIDEPRESSANTS # ------------------------------------------------------------------------- Drug( "agomelatine", ["Valdoxan"], non_ssri_modern_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "bupropion", ["Zyban"], non_ssri_modern_antidepressant=True # antidepressant license in US, smoking cessation in UK ), Drug( "duloxetine", ["Cymbalta", "Yentreve", "duloxat.*"], non_ssri_modern_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "mirtazapine", ["mirtaz.*", "mirtazepine", "Zispin", "Mirza"], # ... actually (CPFT 2013): mirtazapine, mirtazepine(*), "mirtazapine # Dec" (?) non_ssri_modern_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "reboxetine", ["Edronax", "reboxat.*"], non_ssri_modern_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "tryptophan", ["Optimax"], non_ssri_modern_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "venlafaxine", ["venla.*", "Eff?exor.*"], # ... actually (CPFT 2013): venlafaxine, venlafaxine XL, non_ssri_modern_antidepressant=True, # though obviously an SSRI too... slam_antidepressant_finder=True, ), # ------------------------------------------------------------------------- # TRICYCLIC AND RELATED ANTIDEPRESSANTS # ------------------------------------------------------------------------- Drug( "tricyclic_antidepressant", ["tricyclic.*", "tca" + WB], tricyclic_antidepressant=True, slam_antidepressant_finder=True, category_not_drug=True, ), Drug( "amitriptyline", ["amitr[i|y]pt[i|y]l[i|y]n.*", "Vanatrip", "Elavil", "Endep"], # ... actually (CPFT 2013): amitriptyline, amitriptiline(*), # amitryptyline(*) # Triptafen = amitriptyline + perphenazine; see above. tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "clomipramine", ["Anafranil.*"], tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "dosulepin", ["dothiepin", "Prothiaden"], # ... actually (CPFT 2013): dosulepin, dothiepin(+) tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "doxepin", ["Sinepin", "Sinequan", "Sinepin", "Xepin"], # Xepin is cream only tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "imipramine", ["Tofranil"], tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "lofepramine", ["Lomont"], tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "nortriptyline", ["nortr.*", "Allegron", "Pamelor", "Aventyl"], # ... actually (CPFT 2013): nortriptyline, nortryptiline(*) tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "trimipramine", ["Surmontil"], tricyclic_antidepressant=True, slam_antidepressant_finder=True, ), # ------------------------------------------------------------------------- # TETRACYCLIC-RELATED ANTIDEPRESSANTS # ------------------------------------------------------------------------- Drug( "mianserin", tetracyclic_and_related_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "trazodone", ["Molipaxin"], tetracyclic_and_related_antidepressant=True, slam_antidepressant_finder=True, ), Drug( "nefazodone", # discontinued for hepatotoxicity? But apparently still used in 2014 # in the UK: http://www.bbc.co.uk/news/uk-25745824 ["Dutonin", "Nefadar", "Serzone"], tetracyclic_and_related_antidepressant=True # brand names from https://en.wikipedia.org/wiki/Nefazodone # ... yup, still a trickle, mostly from Islington: # https://openprescribing.net/chemical/0403040T0/ ), Drug( "maprotiline", ["Ludiomil"], tetracyclic_and_related_antidepressant=True, ), # ------------------------------------------------------------------------- # MAOIs # ------------------------------------------------------------------------- Drug( "phenelzine", ["phenylethylhydrazine", "Alazin", "Nardil"], monoamine_oxidase_inhibitor=True, slam_antidepressant_finder=True # - SLAM code (see e-mail to self 2016-12-02) also has %Alazin%; not sure # noqa # that's right; see also # http://www.druglib.com/activeingredient/phenelzine/ # - oh, yes, it is right: # https://www.pharmacompass.com/active-pharmaceutical-ingredients/alazin # noqa # - phenylethylhydrazine is a synonym; see # http://www.minclinic.ru/drugs/drugs_eng/B/Beta-phenylethylhydrazine.html # noqa ), # not included: pheniprazine Drug( "isocarboxazid", monoamine_oxidase_inhibitor=True, slam_antidepressant_finder=True, ), Drug( "moclobemide", ["Manerix"], monoamine_oxidase_inhibitor=True, slam_antidepressant_finder=True, ), Drug( "tranylcypromine", ["Parnate"], monoamine_oxidase_inhibitor=True, slam_antidepressant_finder=True, ), # ------------------------------------------------------------------------- # BENZODIAZEPINES # ------------------------------------------------------------------------- Drug( "benzodiazepine", ["benzodiazepine.*"], benzodiazepine=True, category_not_drug=True, ), Drug("alprazolam", benzodiazepine=True), Drug("chlordiazepoxide", benzodiazepine=True), Drug("clobazam", benzodiazepine=True), Drug("clonazepam", ["Rivotril"], benzodiazepine=True), Drug( "diazepam", ["diaz.*", "Valium"], # ... actually (CPFT 2013): diazepam, diazapam(*), diazapem(*), Valium benzodiazepine=True, ), Drug("flurazepam", ["Dalmane"], benzodiazepine=True), Drug("loprazolam", benzodiazepine=True), Drug("lorazepam", ["Ativan"], benzodiazepine=True), Drug("lormetazepam", benzodiazepine=True), Drug("midazolam", ["Hypnovel"], benzodiazepine=True), Drug("nitrazepam", benzodiazepine=True), Drug("oxazepam", benzodiazepine=True), Drug("temazepam", benzodiazepine=True), # ------------------------------------------------------------------------- # Z-DRUGS # ------------------------------------------------------------------------- Drug("zaleplon", ["Sonata"], z_drug=True), Drug( "zolpidem", ["zolpidem.*", "Stilnoct"], # ... actually (CPFT 2013): zolpidem, zolpidem tartrate z_drug=True, ), Drug("zopiclone", ["Zimovane"], z_drug=True), # ------------------------------------------------------------------------- # OTHER GABA MODULATORS # ------------------------------------------------------------------------- Drug( "baclofen", [ "Lioresal", "Lyflex", "Bacfen", "Baclof", "Bacmax", "Chinofen", "Parafon", "Riclofen", "Spinofen", "Spinospas", "Tefsole", "Gablofen", "Kemstro", ], gaba_b_functional_agonist=True, ), # ------------------------------------------------------------------------- # OTHER ANXIOLYTICS # ------------------------------------------------------------------------- Drug("buspirone", ["Buspar"], non_benzodiazepine_anxiolytic=True), # ------------------------------------------------------------------------- # OTHER ANTIMANIC # ------------------------------------------------------------------------- Drug( "carbamazepine", ["Carbagen.*", "Tegretol.*"], # also Tegretol Prolonged Release (formerly Tegretol Retard) # ... actually (CPFT 2013): carbamazepine, Tegretol mood_stabilizer=True, ), Drug( "valproate", [".*valp.*", "Epilim.*", "Episenta", "Epival", "Convulex", "Depakote"], # ... also semisodium valproate # ... actually (CPFT 2013): sodium valproate [chrono], valproic acid, # valproate, sodium valproate, sodium valporate(*), sodium valporate(*) # chrono, Depakote mood_stabilizer=True, ), Drug( "lithium", ["lithium.*", "Camcolit", "Liskonum", "Priadel", "Li-Liquid"], # ... actually (CPFT 2013): lithium, lithium carbonate, lithium citrate # (curious: Priadel must be being changed to lithium...) antidepressant=True, mood_stabilizer=True, ), # ------------------------------------------------------------------------- # OTHER FOR BIPOLAR/UNIPOLAR DEPRESSION # ------------------------------------------------------------------------- Drug( "lamotrigine", ["lamotrigine.*", "Lamictal"], mood_stabilizer=True, antidepressant=True, ), Drug( "triiodothyronine", ["tri-iodothyronine", "liothyronine", "Cytomel"], antidepressant=True, ), # ------------------------------------------------------------------------- # GENERAL MEDICINE: DIABETES # ------------------------------------------------------------------------- Drug("glibenclamide", sulfonylurea=True), Drug( "gliclazide", ["Zicron", "Diamicron.*", "Dacadis.*", "Vitile.*"], sulfonylurea=True, ), Drug("glimepiride", ["Amaryl"], sulfonylurea=True), Drug("glipizide", ["Minodiab"], sulfonylurea=True), Drug("tolbutamide", sulfonylurea=True), Drug("metformin", ["metformin.*", "Glucophage.*"], biguanide=True), Drug("acarbose", ["Glucobay"], antidiabetic=True), Drug("dapagliflozin", ["Forxiga"], glifozin=True), Drug("exenatide", ["Byetta", "Bydureon"], glp1_agonist=True), Drug("linagliptin", ["Trajenta"], dpp4_inhibitor=True), Drug( ["linagliptin", "metformin"], ["Jentadueto"], biguanide=True, dpp4_inhibitor=True, ), Drug("liraglutide", ["Victoza"], glp1_agonist=True), Drug("lixisenatide", ["Lyxumia"], glp1_agonist=True), Drug("nateglinide", ["Starlix"], meglitinide=True), Drug("pioglitazone", ["Actos"], thiazolidinedione=True), Drug( ["pioglitazone", "metformin"], ["Competact"], thiazolidinedione=True, biguanide=True, ), Drug("repaglinide", ["Prandin"], meglitinide=True), Drug("saxagliptin", ["Onglyza"], dpp4_inhibitor=True), Drug( ["saxagliptin", "metformin"], ["Komboglyze"], dpp4_inhibitor=True, biguanide=True, ), Drug("sitagliptin", ["Januvia"], dpp4_inhibitor=True), Drug( ["sitagliptin", "metformin"], ["Janumet"], dpp4_inhibitor=True, biguanide=True, ), Drug("vildagliptin", ["Galvus"], dpp4_inhibitor=True), Drug( ["vildagliptin", "metformin"], ["Eucreas"], dpp4_inhibitor=True, biguanide=True, ), Drug( "insulin", # Insulin. Covering the BNF categories: # INSULIN # INSULIN ASPART # INSULIN GLULISINE # INSULIN LISPRO # INSULIN DEGLUDEC # INSULIN DETEMIR # INSULIN GLARGINE # INSULIN ZINC SUSPENSION # ISOPHANE INSULIN # PROTAMINE ZINC INSULIN # BIPHASIC INSULIN ASPART # BIPHASIC INSULIN LISPRO # BIPHASIC ISOPHANE INSULIN [ ".*insulin.*", ".*aspart.*", ".*glulisine.*", ".*lispro.*", ".*degludec.*", ".*detemir.*", ".*glargine.*", ".*Hypurin.*", ".*Actrapid.*", ".*Humulin.*", ".*Insuman.*", ".*Novorapid.*", ".*Apidra.*", ".*Humalog.*", ".*Tresiba.*", ".*Levemir.*", ".*Lantus.*", ".*Insulatard.*", ".*NovoMix.*", ], antidiabetic=True, ), # ------------------------------------------------------------------------- # GENERAL MEDICINE: CARDIOVASCULAR # ------------------------------------------------------------------------- Drug("aspirin", cardiovascular=True), Drug("atenolol", beta_blocker=True), # ACE inhibitors (selected) Drug("lisinopril", ace_inhibitor=True), Drug("ramipril", ace_inhibitor=True), # Statins Drug("atorvastatin", ["Lipitor"], statin=True), Drug("fluvastatin", ["Lescol.*"], statin=True), Drug("pravastatin", ["Lipostat"], statin=True), Drug("rosuvastatin", ["Crestor"], statin=True), Drug("simvastatin", ["Zocor"], statin=True), Drug(["simvastatin", "ezetimibe"], ["Inegy"], statin=True), # ------------------------------------------------------------------------- # GENERAL MEDICINE: RESPIRATORY # ------------------------------------------------------------------------- Drug( "salbutamol", ["salbut.*", "vent.*"], # ... actually (CPFT 2013): salbutamol respiratory=True, beta_agonist=True, ), # ------------------------------------------------------------------------- # GENERAL MEDICINE: GASTROINTESTINAL # ------------------------------------------------------------------------- Drug( "lactulose", ["lactul.*", "Duphal.*", "Lactug.*", "laevol.*"], # ... actually (CPFT 2013): lactulose gastrointestinal=True, ), Drug("lansoprazole", proton_pump_inhibitor=True), Drug("omeprazole", proton_pump_inhibitor=True), Drug("senna", gastrointestinal=True), # ------------------------------------------------------------------------- # GENERAL MEDICINE: OTHER # ------------------------------------------------------------------------- Drug("ibuprofen", nonsteroidal_anti_inflammatory=True), Drug("levothyroxine"), Drug("paracetamol"), Drug("thiamine", vitamin=True), # ------------------------------------------------------------------------- # MAYBE ADD: # - OPIOIDS # - clonidine # - cloral betaine # - ?domperidone # - donepezil # - gabapentin # - hyoscine # - Keppra = levetiracetam # - linezolid (as it's an MAOI) # - memantine # - methyldopa # - ?metoclopramide # - nicotine # - pregabalin # - promethazine # - ropinirole # - rotigotine # - selegiline # - topiramate # ------------------------------------------------------------------------- ] # type: List[Drug] # ============================================================================= # High-speed lookup versions of the original constants # ============================================================================= DRUGS_BY_GENERIC_NAME = {d.generic_name: d for d in DRUGS} # ============================================================================= # Get drug object by name # =============================================================================
[docs]def get_drug( drug_name: str, name_is_generic: bool = False, include_categories: bool = False, ) -> Optional[Drug]: """ Converts a drug name to a :class:`.Drug` class. If you already have the generic name, you can get the Drug more efficiently by setting ``name_is_generic=True``. Set ``include_categories=True`` to include drug categories (such as tricyclics) as well as individual drugs. """ drug_name = drug_name.strip().lower() if name_is_generic: drug = DRUGS_BY_GENERIC_NAME.get(drug_name) # type: Optional[Drug] if ( drug is not None and drug.category_not_drug and not include_categories ): return None return drug else: for d in DRUGS: if d.name_matches(drug_name): return d return None
# ============================================================================= # Convert drug names to generic equivalents # =============================================================================
[docs]def drug_name_to_generic( drug_name: str, unknown_to_default: bool = False, default: str = None, include_categories: bool = False, ) -> str: """ Converts a drug name to the name of its generic equivalent. """ drug = get_drug(drug_name, include_categories=include_categories) if drug is not None: return drug.generic_name return default if unknown_to_default else drug_name
[docs]def drug_names_to_generic( drugs: List[str], unknown_to_default: bool = False, default: str = None, include_categories: bool = False, ) -> List[str]: """ Converts a list of drug names to their generic equivalents. The arguments are as for :func:`drug_name_to_generic` but this function handles a list of drug names rather than a single one. Note in passing the following conversion of blank-type representations from R via ``reticulate``, when using e.g. the ``default`` parameter and storing results in a ``data.table()`` character column: .. code-block:: none ------------------------------ ---------------- To Python Back from Python ------------------------------ ---------------- [not passed, so Python None] "NULL" NULL "NULL" NA_character_ "NA" NA TRUE (logical) ------------------------------ ---------------- """ return [ drug_name_to_generic( drug, unknown_to_default=unknown_to_default, default=default, include_categories=include_categories, ) for drug in drugs ]
# ============================================================================= # Check drugs against criteria # =============================================================================
[docs]def drug_matches_criteria(drug: Drug, **criteria: Dict[str, bool]) -> bool: """ Determines whether a drug, passed as an instance of :class:`.Drug`, matches the specified criteria. Args: drug: a :class:`.Drug` instance criteria: ``name=value`` pairs to match against the attributes of the :class:`Drug` class. For example, you can include keyword arguments like ``antidepressant=True``. """ for attribute, value in criteria.items(): if getattr(drug, attribute) != value: return False return True
[docs]def all_drugs_where( sort=True, include_categories: bool = False, **criteria: Dict[str, bool] ) -> List[Drug]: """ Find all drugs matching the specified criteria (see :func:`drug_matches_criteria`). If ``include_categories`` is true, then drug categories (like "tricyclics") are included as well as individual drugs. Pass keyword arguments such as .. code-block:: python from cardinal_pythonlib.psychiatry.drugs import * non_ssri_antidep = all_drugs_where(antidepressant=True, ssri=False) print([d.generic_name for d in non_ssri_antidep]) conventional_antidep = all_drugs_where(conventional_antidepressant=True) print([d.generic_name for d in conventional_antidep]) """ matching_drugs = [] # type: List[Drug] for drug in DRUGS: if drug.category_not_drug and not include_categories: continue if drug_matches_criteria(drug, **criteria): matching_drugs.append(drug) if sort: matching_drugs.sort(key=lambda d: d.generic_name) return matching_drugs
[docs]def drug_name_matches_criteria( drug_name: str, name_is_generic: bool = False, include_categories: bool = False, **criteria: Dict[str, bool], ) -> bool: """ Establish whether a single drug, passed by name, matches the specified criteria. See :func:`drug_matches_criteria`. """ drug = get_drug(drug_name, name_is_generic) if drug is None: return False if drug.category_not_drug and not include_categories: return False return drug_matches_criteria(drug, **criteria)
[docs]def drug_names_match_criteria( drug_names: List[str], names_are_generic: bool = False, include_categories: bool = False, **criteria: Dict[str, bool], ) -> List[bool]: """ Establish whether multiple drugs, passed as a list of drug names, each matches the specified criteria. See :func:`drug_matches_criteria`. """ return [ drug_name_matches_criteria( dn, name_is_generic=names_are_generic, include_categories=include_categories, **criteria, ) for dn in drug_names ]