# Notebook from PayneLab/cptac
Path: notebooks/usecase09_clinical_outcomes.ipynb

# Use Case 9: Survival Analysis of Ovarian Cancer--PODXL, RAC2, and Tumor Stage

Through modern statistical methods, we can determine survival risk based on a variety of factors.  In this tutorial, we will walk through a small example of something you could do with our data to understand what factors relate with survival in various different types of cancer.  In this use case, we will be looking at Ovarian Cancer._____no_output_____## Step 1: Import Data and Dependencies_____no_output_____

import pandas as pd
import cptac
import cptac.utils as ut
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy
import lifelines
from lifelines import KaplanMeierFitter
from lifelines import CoxPHFitter
from lifelines.statistics import proportional_hazard_test
%matplotlib inline_____no_output_____ov = cptac.Ovarian()
clinical = ov.get_clinical()
proteomics = ov.get_proteomics()
follow_up = ov.get_followup()

## Step 2: Gather Data from CPTAC API
The Ovarian cancer dataset contains months of follow-up data, including whether a patient is still alive (Vital Status) at each follow-up period.  We will first merge the clinical and follow-up tables together for analysis. Then we will choose a few attributes to focus on, and narrow our dataframe to those attributes. While you could study a wide variety of factors related to survival, we will be focusing on tumor stage, grade and a proteins of interest listed below in *omics_genes*._____no_output_____First we will join the *clinical* and *proteomics* dataframes to contain protein data for proteins of interest, and clinical data for each patient in the same dataframe.  We will then drop the multiindex of that dataframe to allow us to join it with the *follow_up* dataframe._____no_output_____

cols = list(clinical.columns)
omics_genes = ['RAC2', 'PODXL']

clinical_and_protein = ov.join_metadata_to_omics(metadata_df_name="clinical",
                                                 omics_df_name="proteomics",
                                                 metadata_cols=cols,
                                                 omics_genes=omics_genes,
                                                 quiet=True)

clinical_and_protein = ut.reduce_multiindex(clinical_and_protein,
                                            levels_to_drop="Database_ID")_____no_output_____

Next, we will rename the foreign key ("PPID" -> "Patient_ID") on the follow_up table to allow us to easily join that data with the dataframe of clinical and protein data_____no_output_____

follow_up = follow_up.rename({'PPID' : 'Patient_ID'}, axis='columns')
clin_prot_follow = pd.merge(clinical_and_protein, follow_up, on = "Patient_ID")

#Determine columns to focus on, and create a subset to work with
columns_to_focus_on = ['Vital_Status',
                       'Days_Between_Collection_And_Last_Contact',
                       'Days_Between_Collection_And_Death',
                       'Tumor_Stage_Ovary_FIGO']

#This adds the protein data that we got from the clinical and proteomics join
#so that it will be present in our subset of data to work with
for i in range(len(omics_genes)):
    omics_genes[i] += '_proteomics'
    columns_to_focus_on.append(omics_genes[i])

focus_group = clin_prot_follow[columns_to_focus_on].copy().drop_duplicates()_____no_output_____

## Step 3: Kaplan Meier Plotting
Kaplan Meier plots show us the probability of some event occuring over a given length of time, based on some attribute(s).  Oftentimes, they are used to plot the probability of death for clinical attributes, however they could also be used in a variety of other contexts. _____no_output_____We will start by finding all patients that have died during the follow-up period and update the column to contain boolean values, where True denotes that the event occurred ('Deceased'), and False denotes that it did not ('Living').  We will then combine the two columns containing timeframe data ('Days_Between_Collection_And_Last_Contact', and 'Days_Between_Collection_And_Death'), to help us with plotting survival curves. These steps are necessary to fit the requirements of the *lifelines* package._____no_output_____

#Make the Vital Status column boolean
focus_group['Vital_Status'] = focus_group['Vital_Status'].replace('Living', False)
focus_group['Vital_Status'] = focus_group['Vital_Status'].replace('Deceased', True)
focus_group['Vital_Status'] = focus_group['Vital_Status'].astype('bool')_____no_output_____cols = ['Days_Between_Collection_And_Last_Contact', 'Days_Between_Collection_And_Death']

focus_group = focus_group.assign(Days_Until_Last_Contact_Or_Death=focus_group[cols].sum(1)).drop(cols, 1)_____no_output_____

Next, we will create a general Kaplan Meier Plot of overall survival for our cohort, using the KaplanMeierFitter() from the *lifelines* package._____no_output_____

time = focus_group['Days_Until_Last_Contact_Or_Death']
status = focus_group['Vital_Status']

kmf = KaplanMeierFitter()
kmf.fit(time, event_observed = status)
kmf.plot()_____no_output_____

# Step 4 Prepare Data for Multivariate Kaplan Meier Plots and Cox's Proportional Hazard Test
We will now group our columns of interest into 3-4 distinct categories each, and assign them numeric values.  It is necessary for the requirements of the *lifelines* package that the categories are assigned numeric values (other data types, including category, are not compatible with the functions we will be using)._____no_output_____

df_genes = focus_group.copy()

#Here, we are separating the protein abundance values for each of our proteins
#of interest into 3 groups, based on relative abundance of the protein
for col in omics_genes:
    lower_25_filter = df_genes[col] <= df_genes[col].quantile(.25)
    upper_25_filter = df_genes[col] >= df_genes[col].quantile(.75)

    df_genes[col] = np.where(lower_25_filter, "Lower_25%", df_genes[col])
    df_genes[col] = np.where(upper_25_filter, "Upper_25%", df_genes[col])
    df_genes[col] = np.where(~lower_25_filter & ~upper_25_filter, "Middle_50%", df_genes[col])_____no_output_____#Here, we map numeric values to correspond with our 3 protein categories
proteomics_map = {"Lower_25%" : 1, "Middle_50%" : 2, "Upper_25%" : 3}
for gene in omics_genes:
    df_genes[gene] = df_genes[gene].map(proteomics_map)_____no_output_____#Here we map numeric values to corresponding tumor stages
figo_map = {"IIIC" : 3, "IV" : 4, "IIIB" : 3,
            "Not Reported/ Unknown" : np.nan,
            "IIIA" : 3, "III" : 3, "IC" : 1, "IIB" : 2}

df_genes['Tumor_Stage_Ovary_FIGO'] = df_genes['Tumor_Stage_Ovary_FIGO'].map(figo_map)_____no_output_____#Then we will drop missing values, as missing values
# will throw an error in the lifelines functions
df_clean = df_genes.dropna(axis=0, how='any').copy()_____no_output_____

Verify that your columns are the correct data types.  They may appear to be correct up front, but could actually be hidden as slightly different data types. The event of interest, in this case *Vital_Status* needs to contain boolean values, and all other columns in the table must be of a numeric type (either int64 or float64)._____no_output_____

for col in df_clean.columns:
    print(col, ":", df_clean[col].dtype)Vital_Status : bool
Tumor_Stage_Ovary_FIGO : float64
RAC2_proteomics : int64
PODXL_proteomics : int64
Days_Until_Last_Contact_Or_Death : float64
df_clean.head()_____no_output_____

# Step 5: Multivariate Survival Risk Plotting

With the CoxPHFitter from the lifelines package we can create covariate survival plots, as shown below.  The variables we are interested in exploring are Tumor Stage, RAC2 abundance, and PODXL  abundance._____no_output_____First we will fit our model to the data we have prepared using the CoxPHFitter() class from the lifelines module._____no_output_____

cph = CoxPHFitter()
cph.fit(df_clean, duration_col = "Days_Until_Last_Contact_Or_Death",
        event_col = "Vital_Status")_____no_output_____

Then we will plot each of the attributes to see how different levels of protein or different tumor stages affect survival outcomes in Ovarian Cancer patients._____no_output_____

attributes = ['Tumor_Stage_Ovary_FIGO', 'PODXL_proteomics', 'RAC2_proteomics']_____no_output_____for attribute in attributes:
    plot_title = "Ovarian Cancer Survival Risk: " + attribute
    cph.plot_partial_effects_on_outcome(attribute, [1,2,3], cmap='coolwarm',
                          title=plot_title)_____no_output_____

# Results
These different analyses tend to follow the baseline survival function, however, there are some differences in varying levels of each attribute. For example, FIGO Stage I tumors tend to have a higher survival rate over time comparatively to Stage III tumors.  We can explore these differences with the CoxPHFitter object's *print_summary* function (which prints out results for multivariate linear regression)._____no_output_____

cph.print_summary(model="untransformed variables", decimals=3)_____no_output_____

# Step 6 Cox's Proportional Hazard Test
With the *proportional_hazard_test* function, we can now perform Cox's Proportional Hazard Test on the data to determine how each attribute contributes to our cohort's overall survival.  This is shown by the hazard ratio in the column labeled *-log2(p)* below.  In general, a hazard ratio of 1 suggests that an attribute has no effect on overall survival.  A ratio less than 1 suggests that an attribute contributes to lower survival risk.  A ratio greater than 1 suggests that an attribute contributes to higher survival risk._____no_output_____

results = proportional_hazard_test(cph, df_clean, time_transform='rank')
results.print_summary(decimals=3, model="untransformed variables")_____no_output_____

Below, we show confidence intervals for each of the hazard ratios.  Since both bars include the log(HR) of 1.0 and both of their p-values were greater than 0.05, there is insufficient evidence to suggest that a specific Histologic Grade or Tumor Stage is connected with negative clinical outcomes of death or the development of a new tumor *in our cohort of Ovarian cancer tumors*._____no_output_____

cph.plot()
plt.tight_layout()_____no_output_____

# Closing Remarks
It is important to note that there are relatively few patients who died in our cohort (7 out of 88), which is good, but with such a small sample size of death events, it is difficult to conclude with certainty that these features are not more or less connected with survival.  Perhaps a sample of patients with more deaths might have different results.  Alternatively, studying an event with more negative outcomes (such as tumor recurrence) may also provide more data to work with._____no_output_____

df_clean['Vital_Status'].value_counts()_____no_output_____

It is also important to note that the confidence intervals for these ratios are very large, especially since hazard ratios are standardly shown on a log-scale._____no_output_____

cph.confidence_intervals______no_output_____

This is just one example of how you might use Survival Analysis to learn more about different types of cancer, and how clinical and/or genetic attributes contribute to likelihood of survival. There are many other clinical and genetic attributes, as well as several other cancer types, that can be explored using a similar process to that above. In particular, lung cancer and ovarian cancer have a larger number of negative outcomes per cohort, and would be good to look into further. _____no_output_____


# Notebook from HarveySouth/BrawlStars-Statistics
Path: ProjectOutline.ipynb

# Step 1: Import necessary packages_____no_output_____

from decouple import config
import brawlstats as brawl
import pandas as pd_____no_output_____

# Step 2: Connect to the API and get my playerdata_____no_output_____

try:
    TOKEN = config('TOKEN')
    client = brawl.Client(TOKEN)
    player = client.get_player("2GGYGV8YQ")
    print("Connected successfully and returned player: ",player.name)
except:
    print("an error occured with connection")Connected successfully and returned player:  Zuki

# Step 3: Put the playerdata into a usable data structure_____no_output_____

df = pd.DataFrame(player.brawlers)#read the brawlers list from my player data
df.drop(columns=['id'], inplace=True)#drop the brawler id columns
df = df[['name','trophies','gadgets','power', 'highest_trophies',   'rank', 'star_powers', ]]#reorder columns
for index, row in df.iterrows():#loop through the dataframe
    dicList = row["gadgets"]#get the list from the column
    for i in range(len(dicList)):#loop through the list
        if(type(dicList[i])!= str):#if the list item is a dictionary and not a string
            item = dicList[i]['name']#get the name from the dictionary
            dicList[i] = item#replace the list with just the name
    df.at[index,'gadgets'] = dicList#update the dataframe with the new list
    powerList = row["star_powers"]#DO THE SAME FOR GADGETS
    for i in range(len(powerList)):
        if(type(powerList[i])!= str):
            item = powerList[i]['name']
            powerList[i] = item
    df.at[index,'star_powers'] = powerList
df.sort_values(by=['trophies'], ascending=False)#sort by trophies, highest-lowest
_____no_output_____

# Step 4: Use the playerdata to find some meaningful information_____no_output_____### Meaningful information no.1: try to find who has a lower trophy count than expected which indicates which brawlers will be easier to gain trophies on._____no_output_____in order to find the brawlers with low trophies, I have to define what the expected trophy count should be:
since power 10 is the maximum power, and is only achieved with the existence of a star power, we can say that a hero with a gadget and is power 10 should be able to acheieve the maximum trophie count (550 trophies is the limit of trophies as extras are removed every few weeks for rewards). we can then arbritrarily say that a brawler with ((powerlevel-1)+(1 if the brawler has a gadget)) x 10% is the percentage of trophies that a brawler is expected to have out of 550_____no_output_____

#edit the dataframe to contain only necessary information
maxTrophies = 550
estimateDF = df.copy()
estimateDF.drop(columns=['star_powers','rank','highest_trophies'], inplace=True)
for index, row in estimateDF.iterrows():#loop through the dataframe
    gagExist = 0
    gList = row["gadgets"]#get the list from the column
    if(len(gList)>0):
        gagExist = 1
    estimateDF.at[index,'gadgets'] = gagExist
#find the estimate and insert it into the dataframe
for index, row in estimateDF.iterrows():
    powerLevel = row['power']
    gagExist = row["gadgets"]
    troph = row['trophies']
    estimate = ((powerLevel-1)+gagExist)/10
    trophyEstimate = estimate * maxTrophies
    estimateDF.at[index,'trophyEstimate'] = trophyEstimate
    estimateDF.at[index,'trophyError'] = troph-trophyEstimate
estimateDF.sort_values(by=['trophyError'])#sort by error, most-least
    _____no_output_____


# Notebook from bmorris3/gj1214_hst
Path: gj1214_spot_occultations_gp.ipynb

<code>
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from astropy.table import Table
from fleck import Star, generate_spots
import astropy.units as u
from scipy.optimize import fmin_powell, fmin_l_bfgs_b
from celerite import terms, GP
from celerite.modeling import Model_____no_output_____names = ['BJD_TDB', 'flux', 'err', 'visit', 'orbit', 'spatial_scan']
vis12 = Table.read('data/whitelc_vis12.txt', format='ascii', delimiter=' ',
                   names=names)
vis13 = Table.read('data/whitelc_vis13.txt', format='ascii', delimiter=' ',
                   names=names)_____no_output_____from batman import TransitModel, TransitParams

p = TransitParams()
p.per = 1.58040464894
p.t0 = 2455320.535733
p.inc = 89.1
p.a = 15.23
p.w = 90
p.ecc = 0
p.u = [0.28]
p.limb_dark = 'linear'
p.rp = 0.013490**0.5_____no_output_____
def gauss(p, x):
    amp, t0, sigma = p
    return amp * np.exp(-0.5 * (x - t0)**2 / sigma**2)

visnum = [12, 13]
detrended_times = []
detrended_phase = []
detrended_flux = []
detrended_errors = []
spotted_flux = []
spot_params = []

initps = [[59, -20],
          [59, -20]]
bounds = [[0, 90],
          [-35, 0],
          ]#[0.05, 0.25]]
skip = 4
tau = 4.24e-3
spot_contrast= 0.8
spot_radius = 0.2

for vis, num, initp in zip([vis12, vis13], visnum, initps):

    s1 = (vis['spatial_scan'][skip:] == 0).astype(float)
    s2 = (vis['spatial_scan'][skip:] == 1).astype(float)
    orbit_inds = np.array([i == vis['orbit'][skip:] for i in np.unique(vis['orbit'])]).astype(float)
    times, fluxes, errors = vis['BJD_TDB'].data[skip:].copy(), vis['flux'].data[skip:].copy(), 1.0 * vis['err'].data[skip:].copy()

    errors = errors / np.median(fluxes)
    fluxes /= np.median(fluxes)

    transit_model = TransitModel(p, times, supersample_factor=10,
                                 exp_time=times[1]-times[0]).light_curve(p)

    t = np.array([(times - times[i.astype(bool)].mean()) * i for i in orbit_inds])
#     t = np.array([(times - times[i.astype(bool)][0]) * i for i in orbit_inds])

    phase = ((times - p.t0) % p.per) / p.per
    phase[phase >= 0.5] -= 1

    Omega_0 = np.diag(errors**2)
    inv_Omega_0 = np.linalg.inv(Omega_0)
    s = Star(spot_contrast, u_ld=[0, 0.28], phases=detrended_phase, rotation_period=125)

    X = np.vstack([1-np.exp(-t/tau), orbit_inds[0:3], # t,
                   s1, s2, transit_model]).T
    XOX_inv = np.linalg.inv(X.T @ inv_Omega_0 @ X)
    c0 = XOX_inv @ X.T @ inv_Omega_0 @ fluxes

    initp = {"c{0:d}".format(i): c0[i] for i in range(len(c0))}

    # Define the model
    class MeanModel(Model):
        parameter_names = ["c{0:d}".format(i) for i in range(len(c0))]

        def get_value(self, t):
            c = np.array([getattr(self, 'c{0:d}'.format(i))
                          for i in range(len(c0))])
            return X @ c

    kernel = terms.Matern32Term(log_sigma=-20, log_rho=-2)
    gp = GP(kernel, mean=MeanModel(**initp), fit_mean=True, fit_white_noise=False)
    gp.compute(times, errors)

    # Define a cost function
    def neg_log_like(params):
        gp.set_parameter_vector(params)
        return -gp.log_likelihood(fluxes)

    all_initp = np.concatenate([[-10, -2], c0])
    bestp = fmin_powell(neg_log_like, all_initp)
    gp.set_parameter_vector(bestp)

    t = np.linspace(times.min(), times.max(), 1000)
    mu, var = gp.predict(fluxes, times, return_var=True)
    std = np.sqrt(var)

    fig, ax = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
    ax[0].plot(times, fluxes, '.', color='k')
    ax[0].fill_between(times, mu-std, mu+std, color='silver', alpha=0.2)
    ax[0].set_ylabel('Uncorrected flux')
    ax[0].grid(ls=':', color='silver')

    ax[1].errorbar(times, 1e6 * (fluxes - mu), 1e6 * errors,
                   fmt='o', color='k', ecolor='silver')
    ax[1].fill_between(times, 1e6 * (fluxes - mu-std), 1e6 * (fluxes - mu+std), color='silver', alpha=0.2)

    ax[1].set_ylim([-800, 800])
    ax[1].set_ylabel('Spot Model [ppm]')
    ax[1].set_xlabel('Time')
    ax[1].grid(ls=':', color='silver')

    for axis in ax:
        for sp in ['right', 'top']:
            axis.spines[sp].set_visible(False)
#     fig.savefig('plots/visit{0}.pdf'.format(num), bbox_inches='tight')

    plt.show()
    spot_params.append(bestp)
    detrended_phase.append(phase)
    detrended_times.append(times)

    detrended_errors.append(errors)Optimization terminated successfully.
         Current function value: -483.428089
         Iterations: 10
         Function evaluations: 1286

</code>


# Notebook from saurabhr/pythonBasics
Path: 5_Modules.ipynb

# Modules _____no_output_____A module can be imported by another program to make use of its functionality. This is how we can use the Python standard
library as well._____no_output_____

import sys
print('The command line arguments are:')
for i in sys.argv:
    print(i)
print('\n\nThe PYTHONPATH is', sys.path, '\n')The command line arguments are:
C:\Users\saurabh\Anaconda3\lib\site-packages\ipykernel_launcher.py
-f
C:\Users\saurabh\AppData\Roaming\jupyter\runtime\kernel-3ce25783-ccc3-4ca5-81e3-fe1f92c8bfd7.json

The PYTHONPATH is ['C:\\Users\\saurabh', 'C:\\Users\\saurabh\\Anaconda3\\python37.zip', 'C:\\Users\\saurabh\\Anaconda3\\DLLs', 'C:\\Users\\saurabh\\Anaconda3\\lib', 'C:\\Users\\saurabh\\Anaconda3', '', 'C:\\Users\\saurabh\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\saurabh\\Anaconda3\\lib\\site-packages', 'C:\\Users\\saurabh\\Anaconda3\\lib\\site-packages\\win32', 'C:\\Users\\saurabh\\Anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\saurabh\\Anaconda3\\lib\\site-packages\\Pythonwin', 'C:\\Users\\saurabh\\Anaconda3\\lib\\site-packages\\IPython\\extensions', 'C:\\Users\\saurabh\\.ipython']

First, we import the sys module using the import statement. Basically, this translates to us telling Python that we want to use
this module. The sys module contains functionality related to the Python interpreter and its environment i.e. the system._____no_output_____When Python executes the import sys statement, it looks for the sys module._____no_output_____When Python executes the import sys statement, it looks for the sys module. In this case, it is one of the built-in modules,
and hence Python knows where to find it.
If it was not a compiled module i.e. a module written in Python, then the Python interpreter will search for it in the directories
listed in its sys.path variable. If the module is found, then the statements in the body of that module are run and the module is
made available for you to use. Note that the initialization is done only the first time that we import a module.
The argv variable in the sys module is accessed using the dotted notation i.e. sys.argv . It clearly indicates that this name is
part of the sys module. Another advantage of this approach is that the name does not clash with any argv variable used in
your program._____no_output_____Specifically, the sys.argv contains the
list of command line arguments i.e. the arguments passed to your program using the command line._____no_output_____The sys.path contains the list of directory names where modules are imported from. Observe that the first string in sys.path
is empty - this empty string indicates that the current directory is also part of the sys.path which is same as the PYTHONPATH
environment variable. This means that you can directly import modules located in the current directory. Otherwise, you will have
to place your module in one of the directories listed in sys.path .
Note that the current directory is the directory from which the program is launched. Run import os; print(os.getcwd()) to
find out the current directory of your program._____no_output_____Importing a module is a relatively costly affair, so Python does some tricks to make it faster. One way is to create byte-compiled
files with the extension .pyc which is an intermediate form that Python transforms the program into._____no_output_____This .pyc file is useful when you import the module the next time from a different program - it
will be much faster since a portion of the processing required in importing a module is already done. Also, these byte-compiled
files are platform-independent._____no_output_____NOTE: These .pyc files are usually created in the same directory as the corresponding .py files. If Python does not have
permission to write to files in that directory, then the .pyc files will not be created._____no_output_____# 1) from ....import statement_____no_output_____If you want to directly import the argv variable into your program (to avoid typing the sys. everytime for it), then you can
use the from sys import argv statement.
WARNING: In general, avoid using the from..import statement, use the import statement instead. This is because
your program will avoid name clashes and will be more readable._____no_output_____

from math import sqrt
print("Square root of 16 is", sqrt(16))Square root of 16 is 4.0

# 2) a module's \_\_name\_\______no_output_____

if __name__ == '__main__':
    print('This program is being run by itself')
else:
    print('I am being imported from another module')This program is being run by itself
import module_using_nameI am being imported from another module

when this code is imported it will print the else string_____no_output_____Every Python module has its \_\_name\_\_ defined. If this is '\_\_main\_\_' , that implies that the module is being run standalone by
the user and we can take appropriate actions._____no_output_____# 3) Making your Own Modules_____no_output_____

#make a .py file with following file named as mymodule.py
"""
def say_hi():
    print('Hi, this is mymodule speaking.')
__version__ = '0.1'
"""_____no_output_____import mymodule_____no_output_____mymodule.say_hi()Hi, this is mymodule speaking.
mymodule.__version_______no_output_____from mymodule import say_hi, __version__
say_hi()
print('Version', __version__)Hi, this is mymodule speaking.
Version 0.1
from mymodule import *_____no_output_____

This will import all public names such as say_hi but would not import \_\_version\_\_ because it starts with double underscores.
WARNING: Remember that you should avoid using import-star, i.e. from mymodule import * ._____no_output_____# 4) dir() function_____no_output_____The built-in dir() function returns the list of names defined by an object. If the object is a module, this list includes functions,
classes and variables, defined inside that module._____no_output_____This function can accept arguments. If the argument is the name of a module, the function returns the list of names from that
specified module. If there is no argument, the function returns the list of names from the current module_____no_output_____

dir()_____no_output_____dir(sys)_____no_output_____

Note that the dir() function works on any object. For example, run dir(str) for the attributes of the str (string) class.
There is also a vars() function which can potentially give you the attributes and their values, but it will not work for all cases._____no_output_____# 5) Packages_____no_output_____Packages are just folders of modules with a special __init__.py file that indicates to Python that this folder is special because it
contains Python modules._____no_output_____\- \<some folder present in the sys.path\>/

\- world/

    - __init__.py

    - asia/

        - __init__.py

        - india/

            - __init__.py

            - foo.py

    - africa/

        - __init__.py

        - madagascar/

            - __init__.py

            - bar.py_____no_output_____