<h1>Solution Overview</h1>

<p>Okay, I would approach the problem from multiple directions. There are some great suggestions here and if I were you I would use an ensemble of those approaches (majority voting, predicting label which is agreed upon by more than 50% of classifiers in your binary case).</p>

<p><strong>I'm thinking about following approaches:</strong></p>

<ul>
<li><strong>Active learning</strong> (example approach provided by me below)</li>
<li><a href="https://stackoverflow.com/a/54757134/10886420"><strong>MediaWiki backlinks</strong></a> provided as an answer by <a href="https://stackoverflow.com/users/10317656/tavoglc">@TavoGC</a></li>
<li><strong>SPARQL</strong> ancestral categories provided as a comment to your question by <a href="https://stackoverflow.com/users/7879193/stanislav-kralin">@Stanislav Kralin</a> and/or <a href="https://stackoverflow.com/a/54781366/10886420">parent categories</a> provided by <a href="https://stackoverflow.com/users/10554298/meena-nagarajan">@Meena Nagarajan</a> (those two could be an ensemble on their own based on their differences, but for that you would have to contact both creators and compare their results).</li>
</ul>

<p>This way 2 out of three would have to agree a certain concept is a medical one, which minimizes chance of an error further.</p>

<p>While we're at it I would argue <strong>against</strong> approach presented by <a href="https://stackoverflow.com/users/10953776/anand-v-singh">@ananand_v.singh</a> in <a href="https://stackoverflow.com/a/54721431/10886420">this answer</a>, because:</p>

<ul>
<li>distance metric <strong>should not</strong> be euclidean, cosine similarity is much better metric (used by, e.g. <a href="https://spacy.io/" rel="noreferrer">spaCy</a>) as it does not take into account magnitude of the vectors (and it shouldn't, that's how word2vec or GloVe were trained)</li>
<li>many artificial clusters would be created if I understood correctly, while we only need two: medicine and non-medicine one. Furthermore, centroid of medicine <strong>is not</strong> centered on the medicine itself. This poses additional problems, say centroid is moved far away from the medicine and other words like, say, <code>computer</code> or <code>human</code> (or any other not-fitting in your opinion into medicine) might get into the cluster.</li>
<li>it's hard to evaluate results, even more so, the matter is strictly subjective. Furthermore word vectors are hard to visualize and understand (casting them into lower dimensions [2D/3D] using PCA/TSNE/similar for so many words, would give us totally non-sensical results [yeah, I have tried to do it, PCA gets around 5% explained variance for your longer dataset, really, really low]).</li>
</ul>

<p>Based on the problems highlighted above I have come up with solution using <a href="https://en.wikipedia.org/wiki/Active_learning_(machine_learning)" rel="noreferrer">active learning</a>, which is pretty forgotten approach to such problems.</p>

<h1>Active Learning approach</h1>

<p>In this subset of machine learning, when we have a hard time coming up with an exact algorithm (like what does it mean for a term to be a part of <code>medical</code> category), we ask human "expert" (doesn't actually have to be expert) to provide some answers.</p>

<h2>Knowledge encoding</h2>

<p>As <a href="https://stackoverflow.com/users/10953776/anand-v-singh">anand_v.singh</a> pointed out, word vectors are one of the most promising approach and I will use it here as well (differently though, and IMO in a much cleaner and easier fashion).</p>

<p>I'm not going to repeat his points in my answer, so I will add my two cents:</p>

<ul>
<li><strong>Do not</strong> use contextualized word-embeddings as currently available state of the art (e.g. <a href="https://arxiv.org/pdf/1810.04805.pdf" rel="noreferrer">BERT</a>)</li>
<li>Check how many of your concepts have <strong>no representation</strong> (e.g. is represented as a vector of zeros). It should be checked (and is checked in my code,, there will be further discussion when the time comes) and you may use the embedding which has most of them present. </li>
</ul>

<h3>Measuring similarity using <em>spaCy</em></h3>

<p>This class measures similarity between <code>medicine</code> encoded as spaCy's GloVe word vector and every other concept.</p>

<pre><code>class Similarity:
    def __init__(self, centroid, nlp, n_threads: int, batch_size: int):
        # In our case it will be medicine
        self.centroid = centroid

        # spaCy's Language model (english), which will be used to return similarity to
        # centroid of each concept
        self.nlp = nlp
        self.n_threads: int = n_threads
        self.batch_size: int = batch_size

        self.missing: typing.List[int] = []

    def __call__(self, concepts):
        concepts_similarity = []
        # nlp.pipe is faster for many documents and can work in parallel (not blocked by GIL)
        for i, concept in enumerate(
            self.nlp.pipe(
                concepts, n_threads=self.n_threads, batch_size=self.batch_size
            )
        ):
            if concept.has_vector:
                concepts_similarity.append(self.centroid.similarity(concept))
            else:
                # If document has no vector, it's assumed to be totally dissimilar to centroid
                concepts_similarity.append(-1)
                self.missing.append(i)

        return np.array(concepts_similarity)
</code></pre>

<p>This code will return a number for each concept measuring how similar it is to centroid. Furthermore, it records indices of concepts missing their representation. It might be called like this:</p>

<pre><code>import json
import typing

import numpy as np
import spacy

nlp = spacy.load("en_vectors_web_lg")

centroid = nlp("medicine")

concepts = json.load(open("concepts_new.txt"))
concepts_similarity = Similarity(centroid, nlp, n_threads=-1, batch_size=4096)(
    concepts
)
</code></pre>

<p>You may substitute you data in place of <code>new_concepts.json</code>.</p>

<p>Look at <a href="https://spacy.io/usage/models" rel="noreferrer">spacy.load</a> and notice I have used <a href="https://spacy.io/models/en#en_vectors_web_lg" rel="noreferrer"><code>en_vectors_web_lg</code></a>. It consists of <strong>685.000 unique word vectors</strong> (which is a lot), and may work out of the box for your case. You have to download it separately after installing spaCy, more info provided in the links above.</p>

<p><strong>Additionally</strong> you may want to use <strong>multiple centroid words</strong>, e.g. add words like <code>disease</code> or <code>health</code> and average their word vectors. I'm not sure whether that would affect positively your case though.</p>

<p><strong>Other possibility</strong> might be to use multiple centroids and calculate similiarity between each concept and multiple of centroids. We may have a few thresholds in such case, this is likely to remove some <a href="https://en.wikipedia.org/wiki/False_positives_and_false_negatives" rel="noreferrer">false positives</a>, but may miss some terms which one could consider to be similar to <code>medicine</code>. Furthermore it would complicate the case much more, but if your results are unsatisfactory you should consider two options above (and only if those are, don't jump into this approach without previous thought).</p>

<p>Now, we have a rough measure of concept's similarity. But <strong>what does it mean</strong> that a certain concept has 0.1 positive similarity to medicine? Is it a concept one should classify as medical? Or maybe that's too far away already?</p>

<h2>Asking expert</h2>

<p>To get a threshold (below it terms will be considered non medical), it's easiest to ask a human to classify some of the concepts for us (and that's what active learning is about). Yeah, I know it's a really simple form of active learning, but I would consider it such anyway.</p>

<p>I have written a class with <code>sklearn-like</code> interface asking human to classify concepts until optimal threshold (or maximum number of iterations) is reached.</p>

<pre><code>class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        max_steps: int,
        samples: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.max_steps: int = max_steps
        self.samples: int = samples
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1
</code></pre>

<ul>
<li><code>samples</code> argument describes how many examples will be shown to an expert during each iteration (it is the maximum, it will return less if samples were already asked for or there is not enough of them to show).</li>
<li><code>step</code> represents the drop of threshold (we start at 1 meaning perfect similarity) in each iteration.</li>
<li><code>change_multiplier</code> - if an expert answers concepts are not related (or mostly unrelated, as multiple of them are returned), step is multiplied by this floating point number. It is used to pinpoint exact threshold between <code>step</code> changes at each iteration.</li>
<li>concepts are sorted based on their similarity (the more similar a concept is, the higher)</li>
</ul>

<p>Function below asks expert for an opinion and find optimal threshold based on his answers.</p>

<pre><code>def _ask_expert(self, available_concepts_indices):
    # Get random concepts (the ones above the threshold)
    concepts_to_show = set(
        np.random.choice(
            available_concepts_indices, len(available_concepts_indices)
        ).tolist()
    )
    # Remove those already presented to an expert
    concepts_to_show = concepts_to_show - self._checked_concepts
    self._checked_concepts.update(concepts_to_show)
    # Print message for an expert and concepts to be classified
    if concepts_to_show:
        print("\nAre those concepts related to medicine?\n")
        print(
            "\n".join(
                f"{i}. {concept}"
                for i, concept in enumerate(
                    self.concepts[list(concepts_to_show)[: self.samples]]
                )
            ),
            "\n",
        )
        return input("[y]es / [n]o / [any]quit ")
    return "y"
</code></pre>

<p>Example question looks like this:</p>

<pre><code>Are those concepts related to medicine?                                                      

0. anesthetic drug                                                                                                                                                                         
1. child and adolescent psychiatry                                                                                                                                                         
2. tertiary care center                                                     
3. sex therapy                           
4. drug design                                                                                                                                                                             
5. pain disorder                                                      
6. psychiatric rehabilitation                                                                                                                                                              
7. combined oral contraceptive                                
8. family practitioner committee                           
9. cancer family syndrome                          
10. social psychology                                                                                                                                                                      
11. drug sale                                                                                                           
12. blood system                                                                        

[y]es / [n]o / [any]quit y
</code></pre>

<p>... parsing an answer from expert:</p>

<pre><code># True - keep asking, False - stop the algorithm
def _parse_expert_decision(self, decision) -&gt; bool:
    if decision.lower() == "y":
        # You can't go higher as current threshold is related to medicine
        self._max_threshold = self.threshold_
        if self.threshold_ - self.step &lt; self._min_threshold:
            return False
        # Lower the threshold
        self.threshold_ -= self.step
        return True
    if decision.lower() == "n":
        # You can't got lower than this, as current threshold is not related to medicine already
        self._min_threshold = self.threshold_
        # Multiply threshold to pinpoint exact spot
        self.step *= self.change_multiplier
        if self.threshold_ + self.step &lt; self._max_threshold:
            return False
        # Lower the threshold
        self.threshold_ += self.step
        return True
    return False
</code></pre>

<p>And finally whole code code of <code>ActiveLearner</code>, which finds optimal threshold of similiarity accordingly to expert:</p>

<pre><code>class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        samples: int,
        max_steps: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.samples: int = samples
        self.max_steps: int = max_steps
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1

    def _ask_expert(self, available_concepts_indices):
        # Get random concepts (the ones above the threshold)
        concepts_to_show = set(
            np.random.choice(
                available_concepts_indices, len(available_concepts_indices)
            ).tolist()
        )
        # Remove those already presented to an expert
        concepts_to_show = concepts_to_show - self._checked_concepts
        self._checked_concepts.update(concepts_to_show)
        # Print message for an expert and concepts to be classified
        if concepts_to_show:
            print("\nAre those concepts related to medicine?\n")
            print(
                "\n".join(
                    f"{i}. {concept}"
                    for i, concept in enumerate(
                        self.concepts[list(concepts_to_show)[: self.samples]]
                    )
                ),
                "\n",
            )
            return input("[y]es / [n]o / [any]quit ")
        return "y"

    # True - keep asking, False - stop the algorithm
    def _parse_expert_decision(self, decision) -&gt; bool:
        if decision.lower() == "y":
            # You can't go higher as current threshold is related to medicine
            self._max_threshold = self.threshold_
            if self.threshold_ - self.step &lt; self._min_threshold:
                return False
            # Lower the threshold
            self.threshold_ -= self.step
            return True
        if decision.lower() == "n":
            # You can't got lower than this, as current threshold is not related to medicine already
            self._min_threshold = self.threshold_
            # Multiply threshold to pinpoint exact spot
            self.step *= self.change_multiplier
            if self.threshold_ + self.step &lt; self._max_threshold:
                return False
            # Lower the threshold
            self.threshold_ += self.step
            return True
        return False

    def fit(self):
        for _ in range(self.max_steps):
            available_concepts_indices = np.nonzero(
                self.concepts_similarity &gt;= self.threshold_
            )[0]
            if available_concepts_indices.size != 0:
                decision = self._ask_expert(available_concepts_indices)
                if not self._parse_expert_decision(decision):
                    break
            else:
                self.threshold_ -= self.step
        return self
</code></pre>

<p>All in all, you would have to answer some questions manually but this approach is <strong>way more</strong> accurate in my opinion. </p>

<p>Furthermore, you don't have to go through all of the samples, just a small subset of it. You may decide how many samples constitute a medical term (whether 40 medical samples and 10 non-medical samples shown, should still be considered medical?), which let's you fine-tune this approach to your preferences. If there is an outlier (say, 1 sample out of 50 is non-medical), I would consider the threshold to still be valid.</p>

<p><strong>Once again:</strong> This approach should be mixed with others in order to minimalize the chance for wrong classification.</p>

<h2>Classifier</h2>

<p>When we obtain the threshold from expert, classification would be instantenous, here is a simple class for classification:</p>

<pre><code>class Classifier:
    def __init__(self, centroid, threshold: float):
        self.centroid = centroid
        self.threshold: float = threshold

    def predict(self, concepts_pipe):
        predictions = []
        for concept in concepts_pipe:
            predictions.append(self.centroid.similarity(concept) &gt; self.threshold)
        return predictions
</code></pre>

<p>And for brevity, here is the final source code:</p>

<pre><code>import json
import typing

import numpy as np
import spacy


class Similarity:
    def __init__(self, centroid, nlp, n_threads: int, batch_size: int):
        # In our case it will be medicine
        self.centroid = centroid

        # spaCy's Language model (english), which will be used to return similarity to
        # centroid of each concept
        self.nlp = nlp
        self.n_threads: int = n_threads
        self.batch_size: int = batch_size

        self.missing: typing.List[int] = []

    def __call__(self, concepts):
        concepts_similarity = []
        # nlp.pipe is faster for many documents and can work in parallel (not blocked by GIL)
        for i, concept in enumerate(
            self.nlp.pipe(
                concepts, n_threads=self.n_threads, batch_size=self.batch_size
            )
        ):
            if concept.has_vector:
                concepts_similarity.append(self.centroid.similarity(concept))
            else:
                # If document has no vector, it's assumed to be totally dissimilar to centroid
                concepts_similarity.append(-1)
                self.missing.append(i)

        return np.array(concepts_similarity)


class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        samples: int,
        max_steps: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.samples: int = samples
        self.max_steps: int = max_steps
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1

    def _ask_expert(self, available_concepts_indices):
        # Get random concepts (the ones above the threshold)
        concepts_to_show = set(
            np.random.choice(
                available_concepts_indices, len(available_concepts_indices)
            ).tolist()
        )
        # Remove those already presented to an expert
        concepts_to_show = concepts_to_show - self._checked_concepts
        self._checked_concepts.update(concepts_to_show)
        # Print message for an expert and concepts to be classified
        if concepts_to_show:
            print("\nAre those concepts related to medicine?\n")
            print(
                "\n".join(
                    f"{i}. {concept}"
                    for i, concept in enumerate(
                        self.concepts[list(concepts_to_show)[: self.samples]]
                    )
                ),
                "\n",
            )
            return input("[y]es / [n]o / [any]quit ")
        return "y"

    # True - keep asking, False - stop the algorithm
    def _parse_expert_decision(self, decision) -&gt; bool:
        if decision.lower() == "y":
            # You can't go higher as current threshold is related to medicine
            self._max_threshold = self.threshold_
            if self.threshold_ - self.step &lt; self._min_threshold:
                return False
            # Lower the threshold
            self.threshold_ -= self.step
            return True
        if decision.lower() == "n":
            # You can't got lower than this, as current threshold is not related to medicine already
            self._min_threshold = self.threshold_
            # Multiply threshold to pinpoint exact spot
            self.step *= self.change_multiplier
            if self.threshold_ + self.step &lt; self._max_threshold:
                return False
            # Lower the threshold
            self.threshold_ += self.step
            return True
        return False

    def fit(self):
        for _ in range(self.max_steps):
            available_concepts_indices = np.nonzero(
                self.concepts_similarity &gt;= self.threshold_
            )[0]
            if available_concepts_indices.size != 0:
                decision = self._ask_expert(available_concepts_indices)
                if not self._parse_expert_decision(decision):
                    break
            else:
                self.threshold_ -= self.step
        return self


class Classifier:
    def __init__(self, centroid, threshold: float):
        self.centroid = centroid
        self.threshold: float = threshold

    def predict(self, concepts_pipe):
        predictions = []
        for concept in concepts_pipe:
            predictions.append(self.centroid.similarity(concept) &gt; self.threshold)
        return predictions


if __name__ == "__main__":
    nlp = spacy.load("en_vectors_web_lg")

    centroid = nlp("medicine")

    concepts = json.load(open("concepts_new.txt"))
    concepts_similarity = Similarity(centroid, nlp, n_threads=-1, batch_size=4096)(
        concepts
    )

    learner = ActiveLearner(
        np.array(concepts), concepts_similarity, samples=20, max_steps=50
    ).fit()
    print(f"Found threshold {learner.threshold_}\n")

    classifier = Classifier(centroid, learner.threshold_)
    pipe = nlp.pipe(concepts, n_threads=-1, batch_size=4096)
    predictions = classifier.predict(pipe)
    print(
        "\n".join(
            f"{concept}: {label}"
            for concept, label in zip(concepts[20:40], predictions[20:40])
        )
    )
</code></pre>

<p>After answering some questions, with threshold 0.1 (everything between <code>[-1, 0.1)</code> is considered non-medical, while <code>[0.1, 1]</code> is considered medical) I got the following results:</p>

<pre><code>kartagener s syndrome: True
summer season: True
taq: False
atypical neuroleptic: True
anterior cingulate: False
acute respiratory distress syndrome: True
circularity: False
mutase: False
adrenergic blocking drug: True
systematic desensitization: True
the turning point: True
9l: False
pyridazine: False
bisoprolol: False
trq: False
propylhexedrine: False
type 18: True
darpp 32: False
rickettsia conorii: False
sport shoe: True
</code></pre>

<p>As you can see this approach is far from perfect, so the last section described possible improvements:</p>

<h1>Possible improvements</h1>

<p>As mentioned in the beginning using my approach mixed with other answers would probably leave out ideas like <code>sport shoe</code> belonging to <code>medicine</code> out and active learning approach would be more of a decisive vote in case of a draw between two heuristics mentioned above.</p>

<p>We could create an active learning ensemble as well. Instead of one threshold, say 0.1, we would use multiple of them (either increasing or decreasing), let's say those are <code>0.1, 0.2, 0.3, 0.4, 0.5</code>.</p>

<p>Let's say <code>sport shoe</code> gets, for each threshold it's respective <code>True/False</code> like this:</p>

<p><code>True True False False False</code>,</p>

<p>Making a majority voting we would mark it <code>non-medical</code> by 3 out of 2 votes. Furthermore, too strict threshold would me mitigated as well if thresholds below it out-vote it (case if <code>True/False</code> would look like this: <code>True True True False False</code>).</p>

<p><strong>Final possible improvement I came up with</strong>: In the code above I'm using <code>Doc</code> vector, which is a mean of word vectors creating the concept. Say one word is missing (vectors consisting of zeros), in such case, it would be pushed further away from <code>medicine</code> centroid. You may not want that (as some niche medical terms [abbreviations like <code>gpv</code> or others] might be missing their representation), in such case you could average only those vectors which are different from zero. </p>

<p>I know this post is quite lengthy, so if you have any questions post them below.</p>
How to group wikipedia categories in python?
<p>For each concept of my dataset I have stored the corresponding wikipedia categories. For example, consider the following 5 concepts and their corresponding wikipedia categories.</p>

<ul>
<li>hypertriglyceridemia: <code>['Category:Lipid metabolism disorders', 'Category:Medical conditions related to obesity']</code></li>
<li>enzyme inhibitor: <code>['Category:Enzyme inhibitors', 'Category:Medicinal chemistry', 'Category:Metabolism']</code></li>
<li>bypass surgery: <code>['Category:Surgery stubs', 'Category:Surgical procedures and techniques']</code></li>
<li>perth: <code>['Category:1829 establishments in Australia', 'Category:Australian capital cities', 'Category:Metropolitan areas of Australia', 'Category:Perth, Western Australia', 'Category:Populated places established in 1829']</code></li>
<li>climate: <code>['Category:Climate', 'Category:Climatology', 'Category:Meteorological concepts']</code></li>
</ul>

<p>As you can see, the first three concepts belong to medical domain (whereas the remaining two terms are not medical terms).</p>

<p>More precisely, I want to divide my concepts as medical and non-medical. However, it is very difficult to divide the concepts using the categories alone. For example, even though the two concepts <code>enzyme inhibitor</code> and <code>bypass surgery</code> are in medical domain, their categories are very different to each other.</p>

<p>Therefore, I would like to know if there is a way to obtain the <code>parent category</code> of the categories (for example, the categories of <code>enzyme inhibitor</code> and <code>bypass surgery</code> belong to <code>medical</code> parent category)</p>

<p>I am currently using <code>pymediawiki</code> and <code>pywikibot</code>. However, I am not restricted to only those two libraries and happy to have solutions using other libraries as well.</p>

<p><strong>EDIT</strong></p>

<p>As suggested by @IlmariKaronen I am also using the <code>categories of categories</code> and the results I got is as follows (T<em>he small font near the <code>category</code> is the <code>categories of the category</code></em>). 
<a href="https://i.stack.imgur.com/oSPla.png" rel="noreferrer"><img src="https://i.stack.imgur.com/oSPla.png" alt="enter image description here"></a></p>

<p>However, I still could not find a way to use these category details to decide if a given term is a medical or non-medical.</p>

<p>Moreover, as pointed by @IlmariKaronen using <code>Wikiproject</code> details can be potential. However, it seems like the <code>Medicine</code> wikiproject do not seem to have all the medical terms. Therefore we also need to check other wikiprojects as well.</p>

<p><strong>EDIT:</strong> 
My current code of extracting categories from wikipedia concepts is as follows. This could be done using <code>pywikibot</code> or <code>pymediawiki</code> as follows.</p>

<ol>
<li><p>Using the librarary <code>pymediawiki</code></p>

<p>import mediawiki as pw</p>

<pre><code>p = wikipedia.page('enzyme inhibitor')
print(p.categories)
</code></pre></li>
<li><p>Using the library <code>pywikibot</code></p>

<pre><code>import pywikibot as pw

site = pw.Site('en', 'wikipedia')

print([
    cat.title()
    for cat in pw.Page(site, 'support-vector machine').categories()
    if 'hidden' not in cat.categoryinfo
])
</code></pre></li>
</ol>

<p>The categories of categories can also be done in the same way as shown in the answer by @IlmariKaronen.</p>

<p>If you are looking for longer list of concepts for testing I have mentioned more examples below.</p>

<pre><code>['juvenile chronic arthritis', 'climate', 'alexidine', 'mouthrinse', 'sialosis', 'australia', 'artificial neural network', 'ricinoleic acid', 'bromosulfophthalein', 'myelosclerosis', 'hydrochloride salt', 'cycasin', 'aldosterone antagonist', 'fungal growth', 'describe', 'liver resection', 'coffee table', 'natural language processing', 'infratemporal fossa', 'social withdrawal', 'information retrieval', 'monday', 'menthol', 'overturn', 'prevailing', 'spline function', 'acinic cell carcinoma', 'furth', 'hepatic protein', 'blistering', 'prefixation', 'january', 'cardiopulmonary receptor', 'extracorporeal membrane oxygenation', 'clinodactyly', 'melancholic', 'chlorpromazine hydrochloride', 'level of evidence', 'washington state', 'cat', 'newyork', 'year elevan', 'trituration', 'gold alloy', 'hexoprenaline', 'second molar', 'novice', 'oxygen radical', 'subscription', 'ordinate', 'approximal', 'spongiosis', 'ribothymidine', 'body of evidence', 'vpb', 'porins', 'musculocutaneous']
</code></pre>

<p>For a very long list please check the link below. <a href="https://docs.google.com/document/d/1BYllMyDlw-Rb4uMh89VjLml2Bl9Y7oUlopM-Z4F6pN0/edit?usp=sharing" rel="noreferrer">https://docs.google.com/document/d/1BYllMyDlw-Rb4uMh89VjLml2Bl9Y7oUlopM-Z4F6pN0/edit?usp=sharing</a></p>

<p><strong>NOTE: I am not expecting the solution to work 100% (if the proposed algorithm is able to detect many of the medical concepts that is enough for me)</strong></p>

<p>I am happy to provide more details if needed.</p>