<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Table of Contents
<a href='#motivation'>Motivation</a>
<a href='#constructor'>Constructing a dataset</a>
<a href='#attributes'>Attributes</a>
<a href='#access'>Accessing samples</a>
<a href='#iteration'>Iteration over samples</a>
<a href='#subsetselection'>Subset selection</a>
<a href='#serialization'>Saving/reloading a dataset (Serialization)</a>
<a href='#arithmetic'> Combining multiple datasets and arithmetic on useful subsets within datasets </a>
<a href='#portability'>Portability (e.g. with sklearn)</a>
<a id='motivation'></a>
Research data management for medical and machine learning datasets with pyradigm
Data structures provided by the pyradigm library are greatly suited for medical data, where each sample/subject/unit needs to be uniquely identified with a single identifier (denoted as subject ID or something similar depending on the field) that links multiple tables containing diverse types of data, such features extracted and prediction targets for machine learning analyses, covariates and demographics as well as needing to deal with multiple data types (numerical, categorical etc). 
Key-level correspondence across data, targets (either as labels 1 or 2, or as class names such as healthy and disease) and related tables (demographics and other meta data) is essential to maintain integrity of the datasets themselves as well as the subsequent analyses. This would help improve the provenance, as it is easy to encapsulate the relevant info and necessary history in the user-defined attribute and meta data features.
To provide a concrete examples, let's look at how a machine learning dataset is handled traditionally.
You typically have a matrix X of size n x p (n samples and p features), and a target vector y containing the target values (numerical or categorical or multi-output arrays). These X and y serve as training (and test set) for a classifier (like SVM) to fit the data X to match y as accurately as possible.
Let's get a little more concrete, and produce toy data for X and y
Step1:  Almost all the machine learning toolboxes take their input in this form
Step2:  The only major difference between the above two data structures is the data type of target i.e. categorical/discrete vs. continuous values. The ClassificationDataset accepts only categorical values for its target, often as strings ('healthy', 'disease', 'cat', 'chair' etc), whereas the RegressionDataset allows continuous floating point numbers (e.g. age, temperature, salary, weight etc). Depending on the target data type, the analyses (esp. in machine learning) changes dramatically in terms of which predictive model is employed, how they are optimized and what performance metrics are considered etc.
Let's get started with a classification dataset, which can be instantiated as shown below. We also give it a simple description
Step3:  These dataset have customised and convenient display methods, summarizing the important info. For example, just printing instance shows its content summary (typically count of samplets per target and a description if any).
Step4:  You can see the dataset some description attached to it, however we can also it is empty. This can be verified in a boolean context as shown below
Step7:  Let's add samples to this dataset which is when this dataset implementation becomes really handy. Before we do that, we will define some convenience routines defined to just illustrate a simple yet common use of this dataset.
Step8:  So now we have IO routines to read the data for us. Let's define where the data will come from
Step9:  This would obviously change for your applications, but this has sufficient properties to illustrate the point.
Let's look at what methods this dataset offers us
Step11:  That's a lot of methods and attributes to use, organize and retrieve datasets. 
So let's go through them by their usage sections.
Constructor
You can see there few methods such as .add_samplet(), .get_subset() etc. The most often used method is the .add_samplet(), which is key to constructing a pyradigm dataset.
To contruct a dataset, one typically starts with a list of subject IDs to be added - we create few random lists, each to be considered as a separate class
Step12:  The dataset can be populated by adding all subjects belonging to a one class (referred to by cls_id here), done by adding one samplet at a time, using the .add_samplet() method. Let's go ahead and add some samplets based on id lists we just created.
Note
Step13:  Nice. Isn't it?
So what's nice about this, you say? The simple fact that you are constructing a dataset as you read the data in its most elemental form (in the units of the dataset such as the subject ID in our neuroimaging application). You're done as soon as you're done reading the features from disk.
What's more - you can inspect the dataset in an intuitive manner, as shown below
Step14:  Even better, right? No more coding of several commands to get the complete and concise sense of the dataset.
<a id='attributes'></a>
Convenient attributes
If you would like, you can always get more specific information, such as
Step15:  If you'd like to take a look data inside for few subjects - shall we call it a glance?
Step16:  We can control the number of items to glance, by passing a number to dataset.glance() method
Step17:  Or you may be wondering what are the subject IDs in the dataset.. here they are
Step18:  These datasets offer all convenient methods and attributes you need. Besides it is quite easy to extend them to fit your needs and improve your workflow.
<a id='access'></a>
Accessing samples
Thanks to its design, data for a given samplet 'M0299' can simply be obtained by
Step19:  Like a Python dict, it raises an error if the key is not in the dataset
Step20:  A more graceful handling would be to use dataset.get to control what value to be returned in case the requested id is not found in the dataset.
Step21:  <a id='iteration'></a>
Iteration
Thanks to builtin iteration, we can easily iterate over all the samplets in the dataset
Step22:  Did you see that? It's so intuitive and natural! Such a clean traversal of dataset.
Thanks to the choice of the OrderedDict() to represent the data, classes and labels underneath, the order of sample addition is retained. Hence the correspondence across samples in the dataset not only key-wise (by the sample id), but also index-wise.
<a id='transform'></a>
Subject-wise transform
Quite often, we are interested in computing some statistics on data for a given subject (such as mean, or ROI-wise median). Typically this requires a loop, with some computation and organizing it in a new dataset! A simple routine pattern of usage, but can't avoided if you are still fiddling with representing your dataset in medieval matrices! 
Step23:  we can easily traverse the dataset to check the result
Step24:  As the transform accepts an arbitrary callable, we could do many more sophisticated things, such as access the subset of features e.g. cortical thickness for a particular region of interest (say posterior cingulate gyrus).
Step25:  Using this "mask" function, we can easily obtain features for an ROI
Step26:  We can verify that the new dataset does indeed have only 3 features, for the same subjects/classes
Step27:  Let's make a bar plot with the just computed numbers
Step28:  Remember as the original source of data was random, this has no units, property or meaning!
<a id='subsetselection'></a>
Subset selection
In addition to the structured way of obtaining the various properties of this dataset, this implementation really will come in handy when you have to slice and dice the dataset (with large number of classes and features) into smaller subsets (e.g. for binary classification). Let's see how we can retrieve the data for a single class
Step29:  That's it, obtaining the data for a given class is a simple call away.
Now let's see what it looks like
Step30:  Even with updated description automatically, to indicate its history. Let's see some data from controls
Step31:  We can also query a random subset of samples for manual inspection or cross-validation purposes. For example
Step32:  You can see which samplets were selected
Step33:  You can verify that it is indeed random by issuing another call
Step34:  Let's see how we can retrieve specific samples by their IDs (for which there are many use cases)
Step35:  So as simple as that.
Cross-validation
If you would like to develop a variant of cross-validation, and need to obtain a random split of the dataset to obtain training and test sets, it is as simple as
Step36:  This method returns two sets of sample ids corresponding to training set (which 50% of samples from all classes in the dataset) and the rest in test_set. Let's see what they have
Step37:  We can also get a train/test split by specifying an exact number of subjects we would like from each class (e.g. when you would like to avoid class imbalance in the training set)
Step38:  Let's see what the training set contains - we expect 3*3 =9 subjects 
Step39:  We can indeed verify that is the case, by creating a new smaller dataset from that list of ids and getting a summary
Step40:  Another programmatic way to look into different classes is this
Step41:  which returns all the classes that you could iterative over.
Using these two lists, we can easily obtain subset datasets, as illustrated below.
Step42:  How about selecting a subset of features from all samples?
Step43:  Great. Isn't it? You can also see the two-time-point history (initial subset in classes, followed by a subset in features).
<a id='serialization'></a>
Serialization
Once you have this dataset, you can save and load these trivially using your favourite serialization module. Let's do some pickling
Step44:  That's it - it is saved.
Let's reload it from disk and make sure we can indeed retrieve it
Step45:  We can check to see they are indeed one and the same
Step46:  <a id='arithmetic'></a>
Dataset Arithmetic
You might wonder how can you combine two different types of features ( thickness and shape ) from the dataset. Piece of cake, see below ...
To concatenat two datasets, first we make a second dataset
Step47:  How can you check if they are "functionally identical"? As in same keys, same data and classes for each key... Easy
Step48:  Now let's try the arithmetic
Step49:  Great. The add method recognized the identical set of keys and performed a horiz cat, as can be noticed by the twice the number of features in the combined dataset
Step50:  We can also do some removal in similar fashion
Step51:  Data structure is even producing a warning to let you know the resulting output would be empty! We can verify that
Step52:  <a id='portability'></a>
Portability
This is all well and good. How does it interact with other packages out there, you might ask? It is as simple as you can imagine
<ASSISTANT_TASK:>
Python Code:
import sys, os
import numpy as np
import matplotlib
%matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
n = 10 # number of samples
p = 3  # number of features
X = np.random.random([n, p]) # random data for illustration
y = [1]*5 + [2]*5            # random labels ...
np.set_printoptions(precision=2) # save some screen space
print('X : \n{}'.format(X))
print('y : \n{}'.format(y))
Explanation: Table of Contents
<a href='#motivation'>Motivation</a>
<a href='#constructor'>Constructing a dataset</a>
<a href='#attributes'>Attributes</a>
<a href='#access'>Accessing samples</a>
<a href='#iteration'>Iteration over samples</a>
<a href='#subsetselection'>Subset selection</a>
<a href='#serialization'>Saving/reloading a dataset (Serialization)</a>
<a href='#arithmetic'> Combining multiple datasets and arithmetic on useful subsets within datasets </a>
<a href='#portability'>Portability (e.g. with sklearn)</a>
<a id='motivation'></a>
Research data management for medical and machine learning datasets with pyradigm
Data structures provided by the pyradigm library are greatly suited for medical data, where each sample/subject/unit needs to be uniquely identified with a single identifier (denoted as subject ID or something similar depending on the field) that links multiple tables containing diverse types of data, such features extracted and prediction targets for machine learning analyses, covariates and demographics as well as needing to deal with multiple data types (numerical, categorical etc). 
Key-level correspondence across data, targets (either as labels 1 or 2, or as class names such as healthy and disease) and related tables (demographics and other meta data) is essential to maintain integrity of the datasets themselves as well as the subsequent analyses. This would help improve the provenance, as it is easy to encapsulate the relevant info and necessary history in the user-defined attribute and meta data features.
To provide a concrete examples, let's look at how a machine learning dataset is handled traditionally.
You typically have a matrix X of size n x p (n samples and p features), and a target vector y containing the target values (numerical or categorical or multi-output arrays). These X and y serve as training (and test set) for a classifier (like SVM) to fit the data X to match y as accurately as possible.
Let's get a little more concrete, and produce toy data for X and y:
End of explanation
from pyradigm import ClassificationDataset as ClfDataset
from pyradigm import RegressionDataset as RegrDataset
Explanation: Almost all the machine learning toolboxes take their input in this form: X and y, regardless of the original source that produced these features in the first place.
This might be fine if all you ever wanted to do is to extract some features, do some machine learning one-time and dispose these features away! 
But this is almost never the case!
Because it doesn't simply end there.
At a minimum, you often need to dissect the results and link them across different tables e.g. to figure out
 * which samples are misclassified, that can only be queried with their identifiers and not simply their row indices in X?
 * what are the characteristics of those samples?
 * what targets/labels/classes do they belong to?
And all this info needs to be obtained
 * without having to write lots of code connecting few non-obvious links to disparate sources of data (numerical features X, and sample identifiers in a CSV file) to find the relevant info
 * without having to track down who or which method originally produced these features
 * how the previous colleage or student organized the whole dataset, if you haven't generated the features yourself from scratch
And if you are like me, you would be thinking about how would you organize your workflow such that the aforementioned tasks can be accomplished with ease.
This pyradigm data structure is the result of years of refinement to conquer the above complex workflow and related scenarios with ease. By always organizing the extracted features in a dictionary with keyed-in by their samplet ID, along with other important info such as target values, other attributes and associated meta-data. This, by definition, preserves the integrity of the data (inability to incorrectly label samples etc).
With user-defined attributes, this data structure allows the capture of provenance info in a way that is meaningful to the user.
NOTE: we define a samplet to be a single row in X, to distinguish from a sample that is sometimes incorrectly used to refer both a single instance and an entire "sample" / dataset.
An example application is shown below, touching upon the following topics:
<a href='#motivation'>Motivation</a>
<a href='#constructor'>Constructing a dataset</a>
<a href='#attributes'>Attributes</a>
<a href='#access'>Accessing samples</a>
<a href='#iteration'>Iteration over samples</a>
<a href='#subsetselection'>Subset selection</a>
<a href='#serialization'>Saving/reloading a dataset (Serialization)</a>
<a href='#arithmetic'> Combining multiple datasets and arithmetic on useful subsets within datasets </a>
<a href='#portability'>Portability (e.g. with sklearn)</a>
Improting the necessary modules and our fancy class definition:
End of explanation
dataset = ClfDataset()
dataset.description = 'ADNI1: cortical thickness features from Freesurfer v6.0, QCed.'
Explanation: The only major difference between the above two data structures is the data type of target i.e. categorical/discrete vs. continuous values. The ClassificationDataset accepts only categorical values for its target, often as strings ('healthy', 'disease', 'cat', 'chair' etc), whereas the RegressionDataset allows continuous floating point numbers (e.g. age, temperature, salary, weight etc). Depending on the target data type, the analyses (esp. in machine learning) changes dramatically in terms of which predictive model is employed, how they are optimized and what performance metrics are considered etc.
Let's get started with a classification dataset, which can be instantiated as shown below. We also give it a simple description:
End of explanation
dataset
Explanation: These dataset have customised and convenient display methods, summarizing the important info. For example, just printing instance shows its content summary (typically count of samplets per target and a description if any).
End of explanation
bool(dataset)
Explanation: You can see the dataset some description attached to it, however we can also it is empty. This can be verified in a boolean context as shown below:
End of explanation
def read_thickness(path):
    Dummy function to minic a data reader.
    # in your actural routine, this might be:
    #   pysurfer.read_thickness(path).values()
    return np.random.random(2)
def get_features(work_dir, subj_id):
    Returns the whole brain cortical thickness for a given subject ID.
    # extension to identify the data file; this could be .curv, anything else you choose
    ext_thickness = '.thickness'
    thickness = dict()
    for hemi in ['lh', 'rh']:
        path_thickness = os.path.join(work_dir, subj_id, hemi + ext_thickness)
        thickness[hemi] = read_thickness(path_thickness)
    # concatenating them to build a whole brain feature set
    thickness_wb = np.concatenate([thickness['lh'], thickness['rh']])
    return thickness_wb
Explanation: Let's add samples to this dataset which is when this dataset implementation becomes really handy. Before we do that, we will define some convenience routines defined to just illustrate a simple yet common use of this dataset.
End of explanation
work_dir = '/project/ADNI/FreesurferThickness_v4p3'
class_set = ['Control', 'Alzheimer', 'MCI']
class_sizes = [7, 8, 6]
Explanation: So now we have IO routines to read the data for us. Let's define where the data will come from:
End of explanation
[mm for mm in dir(dataset) if not mm.startswith('_') ]
Explanation: This would obviously change for your applications, but this has sufficient properties to illustrate the point.
Let's look at what methods this dataset offers us:
End of explanation
import random
random.seed(42)
def get_id_list(class_name, size=10):
    Generates a random ID list. 
        
    return ['{}{:04d}'.format(class_name[0],np.random.randint(50*size)) for _ in range(size)]
Explanation: That's a lot of methods and attributes to use, organize and retrieve datasets. 
So let's go through them by their usage sections.
Constructor
You can see there few methods such as .add_samplet(), .get_subset() etc. The most often used method is the .add_samplet(), which is key to constructing a pyradigm dataset.
To contruct a dataset, one typically starts with a list of subject IDs to be added - we create few random lists, each to be considered as a separate class:
End of explanation
for class_index, class_id in enumerate(class_set):
    print('Adding class {:>5}'.format(class_id))
    target_list = get_id_list(class_id,class_sizes[class_index])
    for subj_id in target_list:
        print('\t reading subject {:>7}'.format(subj_id))
        thickness_wb = get_features(work_dir, subj_id)
        # adding the sample to the dataset
        dataset.add_samplet(subj_id, thickness_wb, class_id)
Explanation: The dataset can be populated by adding all subjects belonging to a one class (referred to by cls_id here), done by adding one samplet at a time, using the .add_samplet() method. Let's go ahead and add some samplets based on id lists we just created.
Note: samplet here refers to a single in the sample feature matrix X. This new term samplet is defined to distinguish individual row elements of X from X itself and minimized confusion.
End of explanation
dataset
Explanation: Nice. Isn't it?
So what's nice about this, you say? The simple fact that you are constructing a dataset as you read the data in its most elemental form (in the units of the dataset such as the subject ID in our neuroimaging application). You're done as soon as you're done reading the features from disk.
What's more - you can inspect the dataset in an intuitive manner, as shown below:
End of explanation
dataset.num_samplets
dataset.num_features
dataset.target_set
dataset.target_sizes
dataset.target_sizes['Control']
Explanation: Even better, right? No more coding of several commands to get the complete and concise sense of the dataset.
<a id='attributes'></a>
Convenient attributes
If you would like, you can always get more specific information, such as:
End of explanation
dataset.glance()
Explanation: If you'd like to take a look data inside for few subjects - shall we call it a glance?
End of explanation
dataset.glance(2)
Explanation: We can control the number of items to glance, by passing a number to dataset.glance() method:
End of explanation
dataset.samplet_ids
Explanation: Or you may be wondering what are the subject IDs in the dataset.. here they are:
End of explanation
dataset['M0022']
Explanation: These datasets offer all convenient methods and attributes you need. Besides it is quite easy to extend them to fit your needs and improve your workflow.
<a id='access'></a>
Accessing samples
Thanks to its design, data for a given samplet 'M0299' can simply be obtained by:
End of explanation
dataset['dlfjdjf']
Explanation: Like a Python dict, it raises an error if the key is not in the dataset:
End of explanation
dataset.get('dkfjd', np.nan)
Explanation: A more graceful handling would be to use dataset.get to control what value to be returned in case the requested id is not found in the dataset.
End of explanation
for samplet, features in dataset:
    print("{} : {:>10} : {}".format(sample, dataset.targets[samplet], features))
Explanation: <a id='iteration'></a>
Iteration
Thanks to builtin iteration, we can easily iterate over all the samplets in the dataset:
End of explanation
mean_data = dataset.transform(np.mean)
mean_data.description = 'mean values per subject'
mean_data
Explanation: Did you see that? It's so intuitive and natural! Such a clean traversal of dataset.
Thanks to the choice of the OrderedDict() to represent the data, classes and labels underneath, the order of sample addition is retained. Hence the correspondence across samples in the dataset not only key-wise (by the sample id), but also index-wise.
<a id='transform'></a>
Subject-wise transform
Quite often, we are interested in computing some statistics on data for a given subject (such as mean, or ROI-wise median). Typically this requires a loop, with some computation and organizing it in a new dataset! A simple routine pattern of usage, but can't avoided if you are still fiddling with representing your dataset in medieval matrices! :).
If you organized your dataset in a pyradigm, such computation is trivial, thanks to builtin implementation of transform method. The mean value for each subject can be computed and organized in a new dataset, with an intuitive and single line:
End of explanation
for samplet, val in mean_data:
    print("{} : {:>10} : {:.3f}".format(samplet, mean_data.targets[samplet], val))
Explanation: we can easily traverse the dataset to check the result:
End of explanation
# let's make a toy function to return the indices for the ROI
def get_ROI_indices(x): return x[:3] 
Explanation: As the transform accepts an arbitrary callable, we could do many more sophisticated things, such as access the subset of features e.g. cortical thickness for a particular region of interest (say posterior cingulate gyrus).
End of explanation
pcg = dataset.transform(get_ROI_indices)
Explanation: Using this "mask" function, we can easily obtain features for an ROI
End of explanation
pcg
pcg.num_features
Explanation: We can verify that the new dataset does indeed have only 3 features, for the same subjects/classes:
End of explanation
data, lbl, keys = pcg.data_and_targets()
n, bins, patches = plt.hist(data)
Explanation: Let's make a bar plot with the just computed numbers:
End of explanation
ctrl = dataset.get_class('Control')
Explanation: Remember as the original source of data was random, this has no units, property or meaning!
<a id='subsetselection'></a>
Subset selection
In addition to the structured way of obtaining the various properties of this dataset, this implementation really will come in handy when you have to slice and dice the dataset (with large number of classes and features) into smaller subsets (e.g. for binary classification). Let's see how we can retrieve the data for a single class:
End of explanation
ctrl
Explanation: That's it, obtaining the data for a given class is a simple call away.
Now let's see what it looks like:
End of explanation
ctrl.glance(2)
Explanation: Even with updated description automatically, to indicate its history. Let's see some data from controls:
End of explanation
random_subset = dataset.random_subset(perc_in_class=0.3)
random_subset
Explanation: We can also query a random subset of samples for manual inspection or cross-validation purposes. For example:
End of explanation
random_subset.samplet_ids
Explanation: You can see which samplets were selected:
End of explanation
# supplying a new seed everytime to ensure randomization
from datetime import datetime
dataset.random_subset(perc_in_class=0.3).samplet_ids
Explanation: You can verify that it is indeed random by issuing another call:
End of explanation
data = dataset.get_subset(dataset.samplet_ids[1:20])
data
Explanation: Let's see how we can retrieve specific samples by their IDs (for which there are many use cases):
End of explanation
train_set, test_set = dataset.train_test_split_ids( train_perc = 0.5)
Explanation: So as simple as that.
Cross-validation
If you would like to develop a variant of cross-validation, and need to obtain a random split of the dataset to obtain training and test sets, it is as simple as:
End of explanation
train_set, test_set
Explanation: This method returns two sets of sample ids corresponding to training set (which 50% of samples from all classes in the dataset) and the rest in test_set. Let's see what they have:
End of explanation
train_set, test_set = dataset.train_test_split_ids( count_per_class = 3)
Explanation: We can also get a train/test split by specifying an exact number of subjects we would like from each class (e.g. when you would like to avoid class imbalance in the training set):
End of explanation
train_set
Explanation: Let's see what the training set contains - we expect 3*3 =9 subjects :
End of explanation
training_dataset = dataset.get_subset(train_set)
training_dataset
Explanation: We can indeed verify that is the case, by creating a new smaller dataset from that list of ids and getting a summary:
End of explanation
target_set, target_sizes = training_dataset.summarize()
target_set, target_sizes
Explanation: Another programmatic way to look into different classes is this:
End of explanation
dataset
binary_dataset = dataset.get_class(['Control','Alzheimer'])
binary_dataset
Explanation: which returns all the classes that you could iterative over.
Using these two lists, we can easily obtain subset datasets, as illustrated below.
End of explanation
binary_dataset.get_feature_subset(range(2))
Explanation: How about selecting a subset of features from all samples?
End of explanation
from pathlib import Path
out_file = Path('.') / 'Freesurfer_thickness_v4p3.PyradigmDataset.pkl'
binary_dataset.save(out_file)
Explanation: Great. Isn't it? You can also see the two-time-point history (initial subset in classes, followed by a subset in features).
<a id='serialization'></a>
Serialization
Once you have this dataset, you can save and load these trivially using your favourite serialization module. Let's do some pickling:
End of explanation
reloaded = ClfDataset(out_file) # another form of the constructor!
reloaded
Explanation: That's it - it is saved.
Let's reload it from disk and make sure we can indeed retrieve it:
End of explanation
binary_dataset == reloaded
Explanation: We can check to see they are indeed one and the same:
End of explanation
dataset_two = ClfDataset(in_dataset=dataset) # yet another constructor: in its copy form!
Explanation: <a id='arithmetic'></a>
Dataset Arithmetic
You might wonder how can you combine two different types of features ( thickness and shape ) from the dataset. Piece of cake, see below ...
To concatenat two datasets, first we make a second dataset:
End of explanation
dataset_two == dataset
Explanation: How can you check if they are "functionally identical"? As in same keys, same data and classes for each key... Easy:
End of explanation
combined = dataset + dataset_two
Explanation: Now let's try the arithmetic:
End of explanation
combined
Explanation: Great. The add method recognized the identical set of keys and performed a horiz cat, as can be noticed by the twice the number of features in the combined dataset:
End of explanation
smaller = combined - dataset
Explanation: We can also do some removal in similar fashion:
End of explanation
bool(smaller)
Explanation: Data structure is even producing a warning to let you know the resulting output would be empty! We can verify that:
End of explanation
from sklearn import svm
clf = svm.SVC(gamma=0.001, C=100.)
data_matrix, target, sample_ids = binary_dataset.data_and_targets()
clf.fit(data_matrix, target)
Explanation: <a id='portability'></a>
Portability
This is all well and good. How does it interact with other packages out there, you might ask? It is as simple as you can imagine:
End of explanation
<END_TASK>