# Notebook from khozzy/phd
Path: book/chapters/2_selected_topics/23_real_value_challenge.ipynb

(section-topics-real)=
# Real-valued input challenge

Most real-world environments or datasets are represented using exclusively continue-valued inputs or alongside discrete attributes. Unfortunately, the described ALCS implementations were not initially designed for such representations. The fundamental modification required to overcome the limitation above is the adaptation of rule representation within the system. Despite the variety of possible approaches listed in this chapter, each comes with its own merits and perils.

Two significant concepts involving the discretization and interval encoding using dedicated alphabets will be pursued later, while the hindmost ones involving the neural network and fuzzy logic will be only mentioned. There are two main reasons for this decision:

1. drastic changes in rule representation requires significant modifications in existing and tightly coupled components. In some cases, some of them are not usable in their proposed form - like the ALP component in ACS systems needs a complete redefinition for other alphabet encodings,
2. rule interpretability is relevantly blemished.

Therefore, even if some approaches are more appealing for the problem, their usage violates principal ALCS virtue of transparency (creating a compact set of human-readable rules) or does not formally specify the behaviour of related inner mechanisms fulfilling the evolution and assessing each classifier. However, more detailed research is highly advised but will not be pursued herein._____no_output_____(section-topics-real-discretization)=
## Discretization
````{margin}
```{admonition} Ternary representation
A typical representation of a rule in LCS systems comprising three symbols - $\{0, 1, \#\}$
```
````

One of the approaches for handling continuous attributes is to partition them into a number of sub-ranges and treat each sub-range as a category. This process is known as _discretization_ and is used across many families of ML algorithms in order to create better models {cite}`elomaa2004efficient`. In the XCS family, the modification of XCSI {cite}`wilson2001compact`  adopted the algorithm for the integer domain. The modification was evaluated on the _Wisconsin Breast Cancer_ dataset and turned out to be competitive with other ML algorithms. Minding the nature of ALCS, the usage of such nominal values would also be the most straightforward approach. ALCS systems by design are not limited by _ternary representation_, therefore creating an arbitrary number of potential states is achievable. The first such implementation called rACS was proposed by Unold and Mianowski in 2016 {cite}`unold2016real` and tested successfully on [](section-topics-environments-corridor) and [](section-topics-environments-grid) environments. Both have a regular structure, meaning that the intervals are evenly distributed throughout the investigated range.

Unfortunately, the number of ways to discretize a continuous attribute is infinite. Kotsiantis in {cite}`kotsiantis2006discretization` takes a survey of possible discretization techniques, but in this work, the preferred method is to divide the search-space into $n$ equally spaced intervals, referred later as _bins_ or _buckets_. When the $n$ is large, system precision is increased, resulting obviously in the growth of the classifier population. Such a population can be further optimized by compacting the classifiers operating in the neighbouring space regions {cite}`wyatt2004building` {cite}`wilson2001compact`. On the contrary, when the $n$ is low, the system under-performs due to the inability of creating accurate classifiers.

The process of assigning a discrete value for each consecutive interval region within $[0, 1]$ range is depicted on Figure {numref}`{number} <discretization-fig>`.

:::{figure-md} discretization-fig
<img src="../../_static/discretization.png" width="75%">

Representation of allele in rACS as a natural number of the partition. Figure taken from {cite}`unold2016real`.
:::

Such a solution for real-value representation in a simple scenario does not require significant changes to any components and retain the human readability and interpretability of created rules. However, when an arbitrary number of bins for each observation attribute is used, certain modifications might be necessary (for example, additional restrictions for the GA cross-over operator)._____no_output_____(section-topics-real-interval)=
## Interval predicates
The discretization approach assumed that a distinct value of the condition attribute represents a fixed-length interval part of an input range. Another approach is to encode the input using custom hyper-rectangular boundaries described by half-open interval $[p_i, q_i)$, which matches the environment signal $x_i$ if $p_i \leq x_i < q_i$.

Such approach facilitates creation of more general and compact population size, due to arbitrarily interval ranges. In 1999 Wilson took the approach to adapt the XCS to automatically search for optimally decisive thresholds {cite}`wilson1999get`. His approach representation named _center-spread representation_ (CSR) used to represent an interval tuple $(c_i, s_i) \ \text{where} \ c_i, s_i \in \mathbb{R}$ where $c_i$ is the center of the interval, and $s_i$ its spread. The interval is therefore described in Equation {math:numref}`csr-eq`:

```{math}
:label: csr-eq

\begin{align}
p_i &= \text{min}(p_{min}, c_i - s_i) \\
q_i &= \text{max}(q_{max}, c_i + s_i)
\end{align}
```
His system, called XCSR, differs only at the input interface, mutation and covering operators. Preliminary tests revealed the weakness of the crude mutation operator. Moreover, the testing environment, which is very regular and therefore did not challenge the agent with interesting problems like noise or data contradiction.

In 2003 Stone and Bull thoroughly addressed those problems in {cite}`stone2003real` by introducing two new representations - ordered-bounded (OBR) and unordered-bounded (UBR). The OBR is an extension of Wilson XCSI {cite}`wilson2000mining` but enhanced with real-valued interval tuple $(l_i, u_i) \ \text{where} \ l_i, u_i \in \mathbb{R}$. Here $l_i$ and $u_i$ relates to lower and upper bounds, respectively. The encoding imposes $l_i < u_i$ ordering; therefore, all system components that might inadvertently change must be recognized and taken care of. This requirement was relaxed in UBR {cite}`wilson2001function`, thus the same interval can be encoded by both $(p_i, q_i)$ and $(q_i, p_i)$. The advantage is that no operator constraints are needed when the ordering restriction is violated, which constitutes a form of epistasis between $l_i$ and $u_i$, as their values are mutually dependent. The authors showed that CSR and OBR are biased in interval generation in bounded solution spaces. The UBR obviating limitations of OBR was assumed to yield better results in one of the testing problems and therefore considered superior to the other ones.

Finally, in 2005 Dam and Abbass {cite}`dam2005real` recognized that UBR changes the semantics of the chromosome by alternating between min and max genes. This discrepancy is challenging for the XCS because it disturbs the genetic process evolving the population of classifiers {cite}`holland1975adaptation` {cite}`goldberg1989`. They present the Min-Percentage representation of $(m_i, k_i) \ \text{where} \ m_i, k_i \in \mathbb{R}$ tuple, where the interval is determined by the Equation {math:numref}`mp-eq`, which compared with UBR did not provide any substantial improvements.

```{math}
:label: mp-eq

\begin{align}
p_i &= m_i \\
s_i &= k_i \cdot (k_{max} - p_i) \\
q_i &= m_i + s_i
\end{align}
```

It is also worth mentioning an important, subtly different, family of learning classifier systems handling real-valued input natively by _approximating functions_. The most popular implementation of XCSF {cite}`wilson2001function` {cite}`hamzeh2005evolutionary` computes the payoff value locally instead of learning its prediction through a gradient-descent type update. The classifier's condition covers the continuous input by an OBR interval. Enhanced with additional weights vector, updated by regression techniques, the output prediction can be calculated. The classifier structure was further simplified by eliminating the proposed action. Because of the lack of explicit state anticipation capabilities, the function approximation learning classifiers systems are not considered in this work._____no_output_____(section-topics-real-neural)=
## Neural networks
O’Hara and Bull experimented with representing the rule structure of an XCS classifier with two artificial neural networks, introducing the system named X-NCS {cite}`ohara2004prediction` {cite}`o2005building`. While the first one, called the _action-network_, determines the application condition of the classifiers replacing the condition-action part, the latter one - _anticipation network_ - forms the description of the predicted next state.

Both networks are fully connected multi-layered perceptrons (MLP) with the same nodes in their hidden layer. The input layer in both cases matches the observation state vector provided by the environment. In the action network, the size of an output layer is equal to the number of possible actions incremented with an extra node signifying a non-matching situation. Hence, the anticipation network is responsible for predicting the next state; the size of its output layer is equal to the input one. Figures {numref}`{number} <xncs-action-network>` and {numref}`{number} <xncs-anticipation-network>` visualize both topologies.

````{tabbed} Action Network
```{figure} ../../_static/graphs/xncs_action_network.gv.png
:name: xncs-action-network

The topology of the fully connected MLP of the network determines the agent's action based on the observed state (input layer). The number of output nodes equals the number of possible actions with an extra state representing a non-matching case. Figure adapted from {cite}`o2005building`.
```
````

````{tabbed} Anticipation Network
```{figure} ../../_static/graphs/xncs_anticipation_network.gv.png
:name: xncs-anticipation-network

The topology of fully connected MLP of the network determines the anticipated states using the observed environmental state (input layer). Specific output nodes refer to particular input nodes. Figure adapted from {cite}`o2005building`.
```
````

The system starts with an initial random population, containing the maximum number of classifiers considered in a particular experiment, as opposed to standard XCS {cite}`wilson1995classifier`. The network weights are randomly initialized in the range $[-1.0, 1.0]$ and are updated throughout the experiment using two search techniques - the local search performed by backpropagation complemented by the global sampling performed by GA.

In order to assess the general system error, all of the internal mechanisms remained unchanged except for the calculation of the rule’s absolute error, which is defined as the sum of prediction and lookahead error (measuring the correctness of the prediction).

The critical difference between the classic ternary representation is the lack of explicit wildcard symbol and hence no explicit pass-through of input to anticipations. A concept of a reliable classifier cannot be applied in X-NCS - the anticipation accuracy is based on a percentage of accurate anticipations per presentation.

The authors tested the extension in various configurations showing promising results, especially on discrete multistep problems. Due to novel rule representation, the X-NCS system is suited for real-valued data representation, but the conceptual differences make it difficult to compare it with other systems. Aspects like vague generalization metric, constant population size or the anticipation accuracy computation would require dedicated research solely on this implementation.

Interestingly, two years later, a similar system evolved. The authors take advantage of the idea of both the function mapping {cite}`wilson2002classifiers` and the neural anticipation {cite}`o2005building`. The classifier structure was extended with a parametrized anticipatory function $an_f$, which is trained using supervised learning based on the current state $\sigma_t$ and the next state $\sigma_{t+1}$. The proposed XCS with anticipation mappings (XCSAM) system {cite}`bull2007anticipation` does not use the lookahead error and focuses on the value of actions. The authors claim that the anticipatory capabilities are obtained as the "side effects". Because of the lack of any dedicated prediction components, the anticipatory burden falls on the function $an_f$. Preliminary experiments showed that both X-NCS and XCSAM obtain comparable results on discrete multistep environments, however both of the systems can be generalized to problems involving continuous inputs {cite}`lanzi2005xcs`._____no_output_____(section-topics-real-fuzzy)=
## Fuzzy representation
The Michigan-style genetic fuzzy rule-based system {cite}`cord2001genetic` is a machine learning system that employs linguistic rules and fuzzy sets in its representation and an evolutionary algorithm for rule discovery. Casillas proposed relevant modifications to the XCS and introduced a new modification called Fuzzy-XCS {cite}`casillas2004fuzzy`. The newly created system was capable of dealing with continuous states and actions while maintaining maximal generalization. A similar solution was also proposed later by Bonarini in {cite}`bonarini2007fixcs`.

Kondziela undertook an approach to create a fuzzy variant of ACS in 2021 {cite}`kondziela2021facs`. The presented idea modified the system comprising four major elements {cite}`rudnik2011koncepcja`:

- _fuzzification_ - assigns set membership of current environmental perception,
- _inference_ - aggregates the results by comparing the membership functions with collected knowledge,
- _knowledge-store_ - stores population of classifiers where representing rules using the _Fuzzy Inference System_ (Mamandi) of `IF..AND..THEN` statements {cite}`zadeh1996fuzzy`,
- _defuzzification_ - provides a single value response obtained from the inference phase determining the final action value.

As the first step the vector of environment signal determines set memberships using predefined functions {cite}`casillas2007fuzzy` {cite}`bonarini2007fixcs`. Then using the rule representation described by Equation {math:numref}`fuzzy-eq` the match set is formed. Each input value $X_i$ is equal to a linguistic set of $\tilde{A_i} = \{ A_{i1} \lor \dots \lor A_{il} \}$ meaning that classifier internally consists of a rule described with $\{0, 1, \# \}$ symbols (ternary alphabet).

```{math}
:label: fuzzy-eq

\mathbf{IF}\ X_1\ \text{is}\ \tilde{A_1}\ \text{and}\ \dots \ X_n\ \text{is}\ \tilde{A_n}\ \mathbf{THEN}\ Y\ \text{is}\ B
```

In the next step, the action set $[A]$ is formed in the same way as in traditional ACS, but the final action selection procedure differs - it is proposed by taking advantage of each rule's membership function values, and the _Center of Gravity_ method for defuzzification {cite}`kondziela2021facs`.

Preliminary tests made on multistep, discrete maze environments showed that fuzzy ACS implementation successfully predicted more than 75% of encountered situations and maintained a limited number of reliable classifiers (although both metrics were highly oscillating). The author did not report any other operational performance indicators.

The usage of fuzzy logic enables the system to handle the real-valued input naturally. The apparent impediment is the requirement to specify membership functions for each environmental perception upfront. The selection of optimal values is complicated {cite}`bonarini1999fuzzy`, and further increases the number of overall system tunable parameters. The other identified flaw is the GA phase which is not suited for new representation. Both the mutation and cross-over operators should be reviewed accordingly.
_____no_output_____


# Notebook from phiamazon/amazon-sagemaker-examples
Path: aws_sagemaker_studio/sagemaker_neo_compilation_jobs/xgboost_customer_churn/xgboost_customer_churn_neo_studio.ipynb

# Customer Churn Prediction with XGBoost
_**Using Gradient Boosted Trees to Predict Mobile Customer Departure**_

---

---

## Contents

1. [Background](#Background)
1. [Setup](#Setup)
1. [Data](#Data)
1. [Train](#Train)
1. [Host](#Host)
  1. [Evaluate](#Evaluate)
  1. [Relative cost of errors](#Relative-cost-of-errors)
1. [Extensions](#Extensions)

---

## Background

_This notebook has been adapted from an [AWS blog post](https://aws.amazon.com/blogs/ai/predicting-customer-churn-with-amazon-machine-learning/)_

Losing customers is costly for any business.  Identifying unhappy customers early on gives you a chance to offer them incentives to stay.  This notebook describes using machine learning (ML) for the automated identification of unhappy customers, also known as customer churn prediction. ML models rarely give perfect predictions though, so this notebook is also about how to incorporate the relative costs of prediction mistakes when determining the financial outcome of using ML.

We use an example of churn that is familiar to all of us–leaving a mobile phone operator.  Seems like I can always find fault with my provider du jour! And if my provider knows that I’m thinking of leaving, it can offer timely incentives–I can always use a phone upgrade or perhaps have a new feature activated–and I might just stick around. Incentives are often much more cost effective than losing and reacquiring a customer.

---

Make sure you selected `Python 3 (Data Science)` kernel.

## Setup

_This notebook was created and tested on an ml.m4.xlarge notebook instance._

Let's start by specifying:

- The S3 bucket and prefix that you want to use for training and model data.  This should be within the same region as the Notebook Instance, training, and hosting.
- The IAM role arn used to give training and hosting access to your data. See the documentation for how to create these.  Note, if more than one role is required for notebook instances, training, and/or hosting, please replace the boto regexp with a the appropriate full IAM role arn string(s)._____no_output_____

%cd /root/amazon-sagemaker-examples/aws_sagemaker_studio/sagemaker_neo_compilation_jobs/xgboost_customer_churn_____no_output_____!apt-get install unzip_____no_output_____# Define IAM role
import boto3
import re
import sagemaker

role = sagemaker.get_execution_role()
sess = sagemaker.Session()
bucket = sess.default_bucket()
prefix = 'DEMO-xgboost-churn'_____no_output_____

Next, we'll import the Python libraries we'll need for the remainder of the exercise._____no_output_____

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import io
import os
import sys
import time
import json
from IPython.display import display
from time import strftime, gmtime
import sagemaker
from sagemaker.predictor import csv_serializer_____no_output_____

---
## Data

Mobile operators have historical records on which customers ultimately ended up churning and which continued using the service. We can use this historical information to construct an ML model of one mobile operator’s churn using a process called training. After training the model, we can pass the profile information of an arbitrary customer (the same profile information that we used to train the model) to the model, and have the model predict whether this customer is going to churn. Of course, we expect the model to make mistakes–after all, predicting the future is tricky business! But I’ll also show how to deal with prediction errors.

The dataset we use is publicly available and was mentioned in the book [Discovering Knowledge in Data](https://www.amazon.com/dp/0470908742/) by Daniel T. Larose. It is attributed by the author to the University of California Irvine Repository of Machine Learning Datasets.  Let's download and read that dataset in now:_____no_output_____

!wget http://dataminingconsultant.com/DKD2e_data_sets.zip
!unzip -o DKD2e_data_sets.zip_____no_output_____churn = pd.read_csv('./Data sets/churn.txt')
pd.set_option('display.max_columns', 500)
churn_____no_output_____

By modern standards, it’s a relatively small dataset, with only 3,333 records, where each record uses 21 attributes to describe the profile of a customer of an unknown US mobile operator. The attributes are:

- `State`: the US state in which the customer resides, indicated by a two-letter abbreviation; for example, OH or NJ
- `Account Length`: the number of days that this account has been active
- `Area Code`: the three-digit area code of the corresponding customer’s phone number
- `Phone`: the remaining seven-digit phone number
- `Int’l Plan`: whether the customer has an international calling plan: yes/no
- `VMail Plan`: whether the customer has a voice mail feature: yes/no
- `VMail Message`: presumably the average number of voice mail messages per month
- `Day Mins`: the total number of calling minutes used during the day
- `Day Calls`: the total number of calls placed during the day
- `Day Charge`: the billed cost of daytime calls
- `Eve Mins, Eve Calls, Eve Charge`: the billed cost for calls placed during the evening
- `Night Mins`, `Night Calls`, `Night Charge`: the billed cost for calls placed during nighttime
- `Intl Mins`, `Intl Calls`, `Intl Charge`: the billed cost for international calls
- `CustServ Calls`: the number of calls placed to Customer Service
- `Churn?`: whether the customer left the service: true/false

The last attribute, `Churn?`, is known as the target attribute–the attribute that we want the ML model to predict.  Because the target attribute is binary, our model will be performing binary prediction, also known as binary classification.

Let's begin exploring the data:_____no_output_____

# Frequency tables for each categorical feature
for column in churn.select_dtypes(include=['object']).columns:
    display(pd.crosstab(index=churn[column], columns='% observations', normalize='columns'))

# Histograms for each numeric features
display(churn.describe())
%matplotlib inline
hist = churn.hist(bins=30, sharey=True, figsize=(10, 10))_____no_output_____

We can see immediately that:
- `State` appears to be quite evenly distributed
- `Phone` takes on too many unique values to be of any practical use.  It's possible parsing out the prefix could have some value, but without more context on how these are allocated, we should avoid using it.
- Only 14% of customers churned, so there is some class imabalance, but nothing extreme.
- Most of the numeric features are surprisingly nicely distributed, with many showing bell-like gaussianity.  `VMail Message` being a notable exception (and `Area Code` showing up as a feature we should convert to non-numeric)._____no_output_____

churn = churn.drop('Phone', axis=1)
churn['Area Code'] = churn['Area Code'].astype(object)_____no_output_____

Next let's look at the relationship between each of the features and our target variable._____no_output_____

for column in churn.select_dtypes(include=['object']).columns:
    if column != 'Churn?':
        display(pd.crosstab(index=churn[column], columns=churn['Churn?'], normalize='columns'))

for column in churn.select_dtypes(exclude=['object']).columns:
    print(column)
    hist = churn[[column, 'Churn?']].hist(by='Churn?', bins=30)
    plt.show()_____no_output_____

Interestingly we see that churners appear:
- Fairly evenly distributed geographically
- More likely to have an international plan
- Less likely to have a voicemail plan
- To exhibit some bimodality in daily minutes (either higher or lower than the average for non-churners)
- To have a larger number of customer service calls (which makes sense as we'd expect customers who experience lots of problems may be more likely to churn)

In addition, we see that churners take on very similar distributions for features like `Day Mins` and `Day Charge`.  That's not surprising as we'd expect minutes spent talking to correlate with charges.  Let's dig deeper into the relationships between our features._____no_output_____

display(churn.corr())
pd.plotting.scatter_matrix(churn, figsize=(12, 12))
plt.show()_____no_output_____

We see several features that essentially have 100% correlation with one another.  Including these feature pairs in some machine learning algorithms can create catastrophic problems, while in others it will only introduce minor redundancy and bias.  Let's remove one feature from each of the highly correlated pairs: Day Charge from the pair with Day Mins, Night Charge from the pair with Night Mins, Intl Charge from the pair with Intl Mins:_____no_output_____

churn = churn.drop(['Day Charge', 'Eve Charge', 'Night Charge', 'Intl Charge'], axis=1)_____no_output_____

Now that we've cleaned up our dataset, let's determine which algorithm to use.  As mentioned above, there appear to be some variables where both high and low (but not intermediate) values are predictive of churn.  In order to accommodate this in an algorithm like linear regression, we'd need to generate polynomial (or bucketed) terms.  Instead, let's attempt to model this problem using gradient boosted trees.  Amazon SageMaker provides an XGBoost container that we can use to train in a managed, distributed setting, and then host as a real-time prediction endpoint.  XGBoost uses gradient boosted trees which naturally account for non-linear relationships between features and the target variable, as well as accommodating complex interactions between features.

Amazon SageMaker XGBoost can train on data in either a CSV or LibSVM format.  For this example, we'll stick with CSV.  It should:
- Have the predictor variable in the first column
- Not have a header row

But first, let's convert our categorical features into numeric features._____no_output_____

model_data = pd.get_dummies(churn)
model_data = pd.concat([model_data['Churn?_True.'], model_data.drop(['Churn?_False.', 'Churn?_True.'], axis=1)], axis=1)_____no_output_____

And now let's split the data into training, validation, and test sets.  This will help prevent us from overfitting the model, and allow us to test the models accuracy on data it hasn't already seen._____no_output_____

train_data, validation_data, test_data = np.split(model_data.sample(frac=1, random_state=1729), [int(0.7 * len(model_data)), int(0.9 * len(model_data))])
train_data.to_csv('train.csv', header=False, index=False)
validation_data.to_csv('validation.csv', header=False, index=False)_____no_output_____

Now we'll upload these files to S3._____no_output_____

boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train/train.csv')).upload_file('train.csv')
boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'validation/validation.csv')).upload_file('validation.csv')_____no_output_____

---
## Train

Moving onto training, first we'll need to specify the locations of the XGBoost algorithm containers._____no_output_____

from sagemaker.amazon.amazon_estimator import get_image_uri
container = get_image_uri(boto3.Session().region_name, 'xgboost')_____no_output_____

Then, because we're training with the CSV file format, we'll create `s3_input`s that our training function can use as a pointer to the files in S3._____no_output_____

s3_input_train = sagemaker.s3_input(s3_data='s3://{}/{}/train'.format(bucket, prefix), content_type='csv')
s3_input_validation = sagemaker.s3_input(s3_data='s3://{}/{}/validation/'.format(bucket, prefix), content_type='csv')_____no_output_____

Now, we can specify a few parameters like what type of training instances we'd like to use and how many, as well as our XGBoost hyperparameters.  A few key hyperparameters are:
- `max_depth` controls how deep each tree within the algorithm can be built.  Deeper trees can lead to better fit, but are more computationally expensive and can lead to overfitting.  There is typically some trade-off in model performance that needs to be explored between a large number of shallow trees and a smaller number of deeper trees.
- `subsample` controls sampling of the training data.  This technique can help reduce overfitting, but setting it too low can also starve the model of data.
- `num_round` controls the number of boosting rounds.  This is essentially the subsequent models that are trained using the residuals of previous iterations.  Again, more rounds should produce a better fit on the training data, but can be computationally expensive or lead to overfitting.
- `eta` controls how aggressive each round of boosting is.  Larger values lead to more conservative boosting.
- `gamma` controls how aggressively trees are grown.  Larger values lead to more conservative models.

More detail on XGBoost's hyperparmeters can be found on their GitHub [page](https://github.com/dmlc/xgboost/blob/master/doc/parameter.md)._____no_output_____

sess = sagemaker.Session()

xgb = sagemaker.estimator.Estimator(container,
                                    role,
                                    train_instance_count=1,
                                    train_instance_type='ml.m4.xlarge',
                                    output_path='s3://{}/{}/output'.format(bucket, prefix),
                                    sagemaker_session=sess)
xgb.set_hyperparameters(max_depth=5,
                        eta=0.2,
                        gamma=4,
                        min_child_weight=6,
                        subsample=0.8,
                        silent=0,
                        objective='binary:logistic',
                        num_round=100)

xgb.fit({'train': s3_input_train, 'validation': s3_input_validation}) _____no_output_____

---
## Host

Now that we've trained the algorithm, let's create a model and deploy it to a hosted endpoint._____no_output_____

xgb_predictor = xgb.deploy(initial_instance_count=1,
                           instance_type='ml.m4.xlarge')_____no_output_____

### Evaluate

Now that we have a hosted endpoint running, we can make real-time predictions from our model very easily, simply by making an http POST request.  But first, we'll need to setup serializers and deserializers for passing our `test_data` NumPy arrays to the model behind the endpoint._____no_output_____

xgb_predictor.content_type = 'text/csv'
xgb_predictor.serializer = csv_serializer
xgb_predictor.deserializer = None_____no_output_____

Now, we'll use a simple function to:
1. Loop over our test dataset
1. Split it into mini-batches of rows
1. Convert those mini-batchs to CSV string payloads
1. Retrieve mini-batch predictions by invoking the XGBoost endpoint
1. Collect predictions and convert from the CSV output our model provides into a NumPy array_____no_output_____

def predict(data, rows=500):
    split_array = np.array_split(data, int(data.shape[0] / float(rows) + 1))
    predictions = ''
    for array in split_array:
        predictions = ','.join([predictions, xgb_predictor.predict(array).decode('utf-8')])

    return np.fromstring(predictions[1:], sep=',')

predictions = predict(test_data.values[:, 1:])_____no_output_____

There are many ways to compare the performance of a machine learning model, but let's start by simply by comparing actual to predicted values.  In this case, we're simply predicting whether the customer churned (`1`) or not (`0`), which produces a simple confusion matrix._____no_output_____

pd.crosstab(index=test_data.iloc[:, 0], columns=np.round(predictions), rownames=['actual'], colnames=['predictions'])_____no_output_____

_Note, due to randomized elements of the algorithm, you results may differ slightly._

Of the 48 churners, we've correctly predicted 39 of them (true positives). And, we incorrectly predicted 4 customers would churn who then ended up not doing so (false positives).  There are also 9 customers who ended up churning, that we predicted would not (false negatives).

An important point here is that because of the `np.round()` function above we are using a simple threshold (or cutoff) of 0.5.  Our predictions from `xgboost` come out as continuous values between 0 and 1 and we force them into the binary classes that we began with.  However, because a customer that churns is expected to cost the company more than proactively trying to retain a customer who we think might churn, we should consider adjusting this cutoff.  That will almost certainly increase the number of false positives, but it can also be expected to increase the number of true positives and reduce the number of false negatives.

To get a rough intuition here, let's look at the continuous values of our predictions._____no_output_____

plt.hist(predictions)
plt.show()_____no_output_____

The continuous valued predictions coming from our model tend to skew toward 0 or 1, but there is sufficient mass between 0.1 and 0.9 that adjusting the cutoff should indeed shift a number of customers' predictions.  For example..._____no_output_____

pd.crosstab(index=test_data.iloc[:, 0], columns=np.where(predictions > 0.3, 1, 0))_____no_output_____

We can see that changing the cutoff from 0.5 to 0.3 results in 1 more true positives, 3 more false positives, and 1 fewer false negatives.  The numbers are small overall here, but that's 6-10% of customers overall that are shifting because of a change to the cutoff.  Was this the right decision?  We may end up retaining 3 extra customers, but we also unnecessarily incentivized 5 more customers who would have stayed.  Determining optimal cutoffs is a key step in properly applying machine learning in a real-world setting.  Let's discuss this more broadly and then apply a specific, hypothetical solution for our current problem.

### Relative cost of errors

Any practical binary classification problem is likely to produce a similarly sensitive cutoff. That by itself isn’t a problem. After all, if the scores for two classes are really easy to separate, the problem probably isn’t very hard to begin with and might even be solvable with simple rules instead of ML.

More important, if I put an ML model into production, there are costs associated with the model erroneously assigning false positives and false negatives. I also need to look at similar costs associated with correct predictions of true positives and true negatives.  Because the choice of the cutoff affects all four of these statistics, I need to consider the relative costs to the business for each of these four outcomes for each prediction.

#### Assigning costs

What are the costs for our problem of mobile operator churn? The costs, of course, depend on the specific actions that the business takes. Let's make some assumptions here.

First, assign the true negatives the cost of \$0. Our model essentially correctly identified a happy customer in this case, and we don’t need to do anything.

False negatives are the most problematic, because they incorrectly predict that a churning customer will stay. We lose the customer and will have to pay all the costs of acquiring a replacement customer, including foregone revenue, advertising costs, administrative costs, point of sale costs, and likely a phone hardware subsidy. A quick search on the Internet reveals that such costs typically run in the hundreds of dollars so, for the purposes of this example, let's assume \$500. This is the cost of false negatives.

Finally, for customers that our model identifies as churning, let's assume a retention incentive in the amount of \\$100. If my provider offered me such a concession, I’d certainly think twice before leaving. This is the cost of both true positive and false positive outcomes. In the case of false positives (the customer is happy, but the model mistakenly predicted churn), we will “waste” the \\$100 concession. We probably could have spent that \\$100 more effectively, but it's possible we increased the loyalty of an already loyal customer, so that’s not so bad._____no_output_____#### Finding the optimal cutoff

It’s clear that false negatives are substantially more costly than false positives. Instead of optimizing for error based on the number of customers, we should be minimizing a cost function that looks like this:

```txt
$500 * FN(C) + $0 * TN(C) + $100 * FP(C) + $100 * TP(C)
```

FN(C) means that the false negative percentage is a function of the cutoff, C, and similar for TN, FP, and TP.  We need to find the cutoff, C, where the result of the expression is smallest.

A straightforward way to do this, is to simply run a simulation over a large number of possible cutoffs.  We test 100 possible values in the for loop below._____no_output_____

cutoffs = np.arange(0.01, 1, 0.01)
costs = []
for c in cutoffs:
    costs.append(np.sum(np.sum(np.array([[0, 100], [500, 100]]) *
                               pd.crosstab(index=test_data.iloc[:, 0],
                                           columns=np.where(predictions > c, 1, 0)))))

costs = np.array(costs)
plt.plot(cutoffs, costs)
plt.show()
print('Cost is minimized near a cutoff of:', cutoffs[np.argmin(costs)], 'for a cost of:', np.min(costs))_____no_output_____

The above chart shows how picking a threshold too low results in costs skyrocketing as all customers are given a retention incentive.  Meanwhile, setting the threshold too high results in too many lost customers, which ultimately grows to be nearly as costly.  The overall cost can be minimized at \\$ 8400 by setting the cutoff to 0.46, which is substantially better than the
\\$ 20k+ I would expect to lose by not taking any action._____no_output_____---
## Extensions

This notebook showcased how to build a model that predicts whether a customer is likely to churn, and then how to optimally set a threshold that accounts for the cost of true positives, false positives, and false negatives.  There are several means of extending it including:
- Some customers who receive retention incentives will still churn.  Including a probability of churning despite receiving an incentive in our cost function would provide a better ROI on our retention programs.
- Customers who switch to a lower-priced plan or who deactivate a paid feature represent different kinds of churn that could be modeled separately.
- Modeling the evolution of customer behavior. If usage is dropping and the number of calls placed to Customer Service is increasing, you are more likely to experience churn then if the trend is the opposite. A customer profile should incorporate behavior trends.
- Actual training data and monetary cost assignments could be more complex.
- Multiple models for each type of churn could be needed.

Regardless of additional complexity, similar principles described in this notebook are likely apply._____no_output_____### Optimizing model for prediction using Neo API
Neo API allows to optimize our model for a specific hardware type. When calling `compile_model()` function, we specify the target instance family (C5) as well as the S3 bucket to which the compiled model would be stored.

**Important. If the following command result in a permission error, scroll up and locate the value of execution role returned by `get_execution_role()`. The role must have access to the S3 bucket specified in ``output_path``.**_____no_output_____

output_path = '/'.join(xgb.output_path.split('/')[:-1])
compiled_model = xgb.compile_model(target_instance_family='ml_c5',
                                   input_shape={'data':[1, 69]},
                                   role=role,
                                   framework='xgboost',
                                   framework_version='0.7',
                                   output_path=output_path)_____no_output_____

### Creating an inference Endpoint

We can deploy this compiled model, note that we need to use the same instance that the target we used for compilation. This creates a SageMaker endpoint that we can use to perform inference.

The arguments to the ``deploy`` function allow us to set the number and type of instances that will be used for the Endpoint. Make sure to choose an instance for which you have compiled your model, so in our case  `ml_c5`. Neo API uses a special runtime (DLR runtime), in which our optimzed model will run._____no_output_____

# known issue: need to manually specify endpoint name
compiled_model.name = 'deployed-xgboost-customer-churn'
# There is a known issue where SageMaker SDK locates the incorrect docker image URI for XGBoost
# For now, we manually set Image URI
compiled_model.image = get_image_uri(sess.boto_region_name, 'xgboost-neo', repo_version='latest')
compiled_predictor = compiled_model.deploy(initial_instance_count = 1, instance_type = 'ml.c5.4xlarge')_____no_output_____

### Making an inference request
The compiled model accepts CSV content type:_____no_output_____

compiled_predictor.content_type = 'text/csv'
compiled_predictor.serializer = csv_serializer
compiled_predictor.deserializer = None_____no_output_____def optimized_predict(data, rows=500):
    split_array = np.array_split(data, int(len(data) / float(rows) + 1))
    predictions = ''
    for array in split_array:
        predictions = ','.join([predictions, compiled_predictor.predict(array).decode('utf-8')])

    return np.fromstring(predictions[1:], sep=',')

predictions = optimized_predict(test_data.values[:, 1:])_____no_output_____

### (Optional) Clean-up

If you're ready to be done with this notebook, please run the cell below.  This will remove the hosted endpoint you created and avoid any charges from a stray instance being left on._____no_output_____

sagemaker.Session().delete_endpoint(xgb_predictor.endpoint)
sagemaker.Session().delete_endpoint(compiled_predictor.endpoint)_____no_output_____


# Notebook from PDBeurope/PDBe_Programming
Path: search_interface/notebooks/search_introduction.ipynb

# Introduction_____no_output_____The new search service under development at PDBe is powered by Apache Solr.

A pre-release version of user interface is available here: http://wwwdev.ebi.ac.uk/pdbe/entry/search/index

For programmatic usage, a Solr instance is available here: http://wwwdev.ebi.ac.uk/pdbe/search/pdb

Please note that the search service will be released in 2015 - at that point, it would be better to use URLs similar to those above, but hosted from www instead of wwwdev._____no_output_____# Getting started_____no_output_____To avoid writing long Solr URLs by hand and having to encode them etc., we will use a Solr client library called mysolr. It is pretty lightweight and easy to install, e.g. I installed it on my Redhat (Enterprise 6.6) machine as follows:

easy_install mysolr==0.7

There are many such client libraries available for python as well as other languages._____no_output_____Let us now make a simple query - let us look for a PDB entry._____no_output_____

PDBE_SOLR_URL = "http://www.ebi.ac.uk/pdbe/search/pdb"
# or https://www.ebi.ac.uk/pdbe/search/pdb/select?rows=0&q=status:REL&wt=json

from mysolr import Solr
solr = Solr(PDBE_SOLR_URL, version=4)

response = solr.search(q='status:REL', rows=0)

documents = response.documents
print("Number of results:", len(documents))

#fields = response.documents[0].keys()
#print("Number of fields in the documents:", [len(rd.keys()) for rd in documents])

response.raw_contentNumber of results: 0

There are 3 documents in Solr response for a single PDB id, and each has >75 fields. At this juncture, it is essential to understand what the document represents and contains before proceeding further._____no_output_____# Entity document_____no_output_____PDBe Solr instance serves documents based on polymeric entities in PDB entries, i.e. each document indexed by Solr represents polymeric molecules of type protein, sugar, DNA, RNA or DNA/RNA hybrid. This is why for entry 2qk9 we get 3 documents in the response, each representing the protein, RNA and DNA molecule in that entry.

Fields in PDBe's entity-based Solr document cover a wide range of properties, such as entry's experimental details, details of deposition and primary publication, entity's taxonomy, entry's quality, entity's cross references to UniProt and popular domain databases, biological assembly, etc. They are documented here: http://wwwdev.ebi.ac.uk/pdbe/api/doc/search.html_____no_output_____# Solr features_____no_output_____It is also useful now to understand a little more about Solr querying. Solr has a rich and complex query syntax, described at http://wiki.apache.org/solr/CommonQueryParameters and elsewhere.

The fields of immediate relevance to us in this tutorial are:
 * q - the query itself. There is a lot of flexibility in describing a query, e.g. fields, wildcards, case-insensitivity, logical operators, ranges, etc.
 * rows - number of results returned by Solr. Needs to be explicitly set in mysolr because it defaults to 10. Useful if only part of results are desired.
 * fl - fields returned in each document. This is useful to reduce the size of response.

Solr capabilities combined with the wide-ranging description in entity document can help us write really powerful Solr queries to find precisely the entries or polymers of interest.

_____no_output_____# Examples_____no_output_____Now let us write a query to find entities containing a Pfam domain called "Lipocalin" in X-ray entries of decent resolution (1Å - 2Å)._____no_output_____

def join_with_AND(query_params) :
    '''convenience function to create query string with AND'''
    return " AND ".join(["%s:%s" % (k,v) for k,v in query_params.items()])

def execute_solr_query(query, query_fields) :
    '''convenience function'''
    query["q"] = join_with_AND(query_fields) # add q
    response = solr.search(**query)
    documents = response.documents
    print("Found %d matching entities in %d entries." % (len(documents), len({rd["pdb_id"] for rd in documents})))
    return documents

query_detail = {
    "pfam_name"  : "Lipocalin",
    "resolution" : "[1 TO 2]",
}
query = {
    "rows" : pow(10,8), # i.e. all matching documents are required in response
    "fl"   : "pdb_id, entity_id", # restrict the returned documents to these fields only
}

docs = execute_solr_query(query, query_detail)Found 292 matching entities in 292 entries.

Let us narrow down to proteins of human origin._____no_output_____

query_detail = {
    "pfam_name"  : "Lipocalin",
    "resolution" : "[1 TO 2]",
    "tax_id"     : "9606",
}
query = {
    "rows" : pow(10,8), # i.e. all matching documents are required in response
    "fl"   : "pdb_id, entity_id", # restrict the returned documents to these fields only
}

docs = execute_solr_query(query, query_detail)Found 171 matching entities in 171 entries.

Let us look for entries deposited by Kleywegt._____no_output_____

query_detail = {
    "pfam_name"     : "Lipocalin",
    "resolution"    : "[1 TO 2]",
    "tax_id"        : "9606",
    "entry_authors" : "*Kleywegt*",
}
query = {
    "rows" : pow(10,8), # i.e. all matching documents are required in response
    "fl"   : "pdb_id, entity_id", # restrict the returned documents to these fields only
}

docs = execute_solr_query(query, query_detail)Found 2 matching entities in 2 entries.

# Your turn!_____no_output_____Can you now query PDBe Solr instance to find entries that match the following criteria?
* entries published in Nature and containing transmembrane protein.
* number of SCOP domain families in entries that have homo-tetramer as the most likely assembly._____no_output_____


# Notebook from fjossinet/RNArtistCore
Path: rnartistcore_demo.ipynb

# RNArtistCore demo

-----

If you haven't used one of these notebooks before, they're basically web pages in which you can write, edit, and run live code. They're meant to encourage experimentation, so don't feel nervous. Just try running a few cells and see what happens!.

    Some tips:

Code cells have boxes around them. When you hover over them an  icon appears.

To run a code cell either click the  icon, or click on the cell and then hit Shift+Enter. The Shift+Enter combo will also move you to the next cell, so it's a quick way to work through the notebook.

While a cell is running a * appears in the square brackets next to the cell. Once the cell has finished running the asterix will be replaced with a number.

In most cases you'll want to start from the top of notebook and work your way down running each cell in turn. Later cells might depend on the results of earlier ones.

To edit a code cell, just click on it and type stuff. Remember to run the cell once you've finished editing.

----_____no_output_____# Introduction

To be able run this notebook efficiently, you need:

* to install [Docker Desktop](https://www.docker.com/products/docker-desktop) on your computer, run it and install the container [fjossinet/rnartistcore](https://hub.docker.com/r/fjossinet/rnartistcore) by typing: ```docker pull fjossinet/rnartistcore```
* In the directory containing this notebook, you need to download and save the script [rnartistcore_docker.sh](https://raw.githubusercontent.com/fjossinet/RNArtistCore/master/rnartistcore_docker.sh). Make it executable_____no_output_____### RNA 2D described from scratch in the script file_____no_output_____

s = '''rnartist {
  file = "/docker/demo1.svg"
  ss {
    rna {
      sequence = "CAACAUCAUACGUACUGCGCCCAAGCGUAACGCGAACACCACGAGUGGUGACUGGUGCUUG"
    }
    bracket_notation =
      "(((..(((..(((..(((((....)))))..)))..(((((....)))))..)))...)))"
  }
  theme {
    details_lvl = 5

    color {
      type="A"
      value = "#A0ECF5"
    }

    color {
      type="a"
      value = "black"
    }

    color {
      type="U"
      value = "#9157E5"
    }

    color {
      type="G"
      value = "darkgreen"
    }

    color {
      type="C"
      value = "#E557E5"
    }

  }
}
'''_____no_output_____%store s >demo1.ktsWriting 's' (str) to file 'demo1.kts'.
# we ask rnartistcore to draw the 2D as described in the script file_____no_output_____!./rnartistcore_docker.sh $PWD/demo1.ktsWARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jetbrains.kotlin.com.intellij.util.ReflectionUtil (file:/RNArtistCore/target/rnartistcore-0.2.7-SNAPSHOT-jar-with-dependencies.jar) to method java.util.ResourceBundle.setParent(java.util.ResourceBundle)
WARNING: Please consider reporting this to the maintainers of org.jetbrains.kotlin.com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
#we display the 2D saved in an SVG file
from IPython.core.display import SVG #<-- this line only needs to be run once per notebook
# the output file name is like: filename defined in the script + molecular chain name (default name is 'A').
SVG(filename='demo1_A.svg')_____no_output_____

### RNA 2D described in a vienna file_____no_output_____

s = '''rnartist {
  file = "/docker/demo2.svg"
  ss {
      vienna {
        file = "samples/rna.vienna"
      }
  }
  theme {
    details_lvl = 5

    color {
      type="A"
      value = "#A0ECF5"
    }

    color {
      type="a"
      value = "black"
    }

    color {
      type="U"
      value = "#9157E5"
    }

    color {
      type="G"
      value = "darkgreen"
    }

    color {
      type="C"
      value = "#E557E5"
    }

  }
}
'''_____no_output_____%store s >demo2.ktsWriting 's' (str) to file 'demo2.kts'.
!./rnartistcore_docker.sh $PWD/demo2.ktsWARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jetbrains.kotlin.com.intellij.util.ReflectionUtil (file:/RNArtistCore/target/rnartistcore-0.2.7-SNAPSHOT-jar-with-dependencies.jar) to method java.util.ResourceBundle.setParent(java.util.ResourceBundle)
WARNING: Please consider reporting this to the maintainers of org.jetbrains.kotlin.com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
SVG(filename='demo2_A.svg')_____no_output_____

### RNA 2D inferred from a 3D structure described in a PDB file (PDBID: 1GID)

Download and save the [PDB file 1GID](https://files.rcsb.org/download/1GID.pdb) in your directory before to run the cells below._____no_output_____

!curl "https://files.rcsb.org/download/1GID.pdb" > 1gid.pdb  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  590k    0  590k    0     0   594k      0 --:--:-- --:--:-- --:--:--  593k
s = '''rnartist {
  file = "/docker/demo3.svg"
  ss {
      pdb {
        name = "B"
        file = "/docker/1gid.pdb"
      }
  }
  theme {
    details_lvl = 5

    color {
      type="A"
      value = "#A0ECF5"
    }

    color {
      type="a"
      value = "black"
    }

    color {
      type="U"
      value = "#9157E5"
    }

    color {
      type="G"
      value = "darkgreen"
    }

    color {
      type="C"
      value = "#E557E5"
    }

  }
}
'''_____no_output_____%store s >demo3.ktsWriting 's' (str) to file 'demo3.kts'.
!./rnartistcore_docker.sh $PWD/demo3.ktsWARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jetbrains.kotlin.com.intellij.util.ReflectionUtil (file:/RNArtistCore/target/rnartistcore-0.2.7-SNAPSHOT-jar-with-dependencies.jar) to method java.util.ResourceBundle.setParent(java.util.ResourceBundle)
WARNING: Please consider reporting this to the maintainers of org.jetbrains.kotlin.com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
SVG(filename='demo3_B.svg')_____no_output_____


# Notebook from ExoCTK/Intro-to-ExoCTK
Path: Intro to ExoCTK.ipynb

# Introduction to ExoCTK
Authors: Jules Fowler, Joe Filippazzo, Matthew Bourque, Kevin Stevenson

The Exoplanet Characterization Tool Kit (ExoCTK) is an open source Python 3.5+ package that serves as a one-stop-shop for observation planning, data reduction, and scientific analysis of transiting exoplanet systems.

In this notebook, we will cover the installation of the software as well as an end-to-end example that demonstrates the core functionalities of each tool provided by the ExoCTK package.

# Installation
You must first have a working installation of ``anaconda`` or ``miniconda`` for Python 3.  If you do not yet have this on your system, you can visit the following links for download and installation instructions:

- [Anaconda](https://www.anaconda.com/download/)
- [Miniconda](https://conda.io/en/latest/miniconda.html)

To obtain the latest `exoctk` package with the necessary environment files, you should clone the repository directly from GitHub:

```
git clone https://github.com/ExoCTK/exoctk.git
cd exoctk
```

To obtain the `exoctk` data, visit the shared [ExoCTK DropBox](https://www.dropbox.com/sh/p9c49urdbkmn5rz/AACFIjeAphOPqcCLtqd1v_W9a?dl=0), download the folders, and unzip them into a directory called `exoctk_data/`.  You may need to download  `modelgrid` seperately because it's the largest of the bunch and some computers have trouble with large .zip files.  Next, export an environment variable for `EXOCTK_DATA`. For Mac OS/Linux, add the line

```
export EXOCTK_DATA='/path/to/your/unzipped/directory/exoctk_data/'
```

to your `.bashrc` or `.bash_profile`. Don't forget to execute your `.bashrc` or `.bash_profile` to implement the changes in your terminal.

```
source ~/.bash_profile
```

For Windows, add an evironment variable using System Utility.

You can install the ExoCTK `conda` environment via the `env/environment-<PYTHON_VERSION>.yml` files (relative to the parent directory of where the `exoctk` repository was installed).  Note that there are separate environment files for each version of `python` that `exoctk` supports.  For this demo, we will be using version 3.6. Ensure that `conda` is up to date, create the `exoctk` environment with Python version 3.6, and activate it with:

```
conda update conda
conda activate base
conda env create -f env/environment-3.6.yml
conda activate exoctk-3.6
conda install ipykernel
python -m ipykernel install --user --name exoctk-3.6 --display-name "exoctk-3.6"
```

Finally, install `exoctk` by running the `exoctk` setup script:

```
python setup.py install
```
# Kernel
Before getting started, make sure that you are using the `exoctk-3.6` kernel be selecting it via `Kernel` -> `Change kernel`.

# Imports
Here are a few basic imports we'll need later on..._____no_output_____

import os
import numpy as np
from bokeh.io import output_notebook
from bokeh.plotting import show
output_notebook()_____no_output_____

# Ask a compelling science question
Successful proposals require a well-posed science question with testable hypothesis.  For this end-to-end demonstration, let's ask a simple question: "Where's the methane?"  More specifically, we want to know if planets with Teq < 1000 have methane in their atmospheres, as predicted by chemical equilibrium models.

# Find the best target that answers your question
Next, let's use the [Exo.MAST Observability Table](https://catalogs.mast.stsci.edu/eaot) to find the highest signal-to-noise target in transmission that is likely to have methane.  Applying the filter: `Teq < 1000 K` and sorting on `K-Band Transmission SNR` shows that WASP-107b is our best target.

# Transiting exoplanet system example
Let's grab all of WASP-107's relevant system parameters using the [ExoMAST](https://exo.mast.stsci.edu) service lookup in ExoCTK.  There are a lot of parameters, so let's just print the planet name and temperature._____no_output_____

import os

# Get the orbital parameters
from exoctk.utils import get_target_data
wasp107b_params, url = get_target_data('WASP-107b')
print(wasp107b_params['planet_name'], wasp107b_params['Tp'])_____no_output_____

# Observation Planning with the Groups and Integrations Calculator
This section will describe planning an observation, including calculating the correct number of groups and Integrations to enter into the Astronomers Proposal Tool (APT)._____no_output_____## Groups and Integrations

To plan an observation with APT, an observer needs to calculate the groups and integerations required for their observation. Each JWST observation consists of some amount of overhead (time to reset the detector, etc), and a discrete number of integrations, each made up of 'n' frames. For time-series observations, there is always only one frame per group, so each integration also has 'n' groups.  The total observation time is determined by the frame time of your given instrument and mode, the number of groups per integration, and the number of integrations in your exposure.  An observation with too many groups per integration will saturate the detector, but too few groups per integration will lead to inefficient observations (i.e. low duty cycles).

This part of the demo will teach you how to calculate the groups and integrations of our target, WASP 107b, and send it through APT. _____no_output_____

# Imports for Groups and Integrations
import astropy.units as u
import json

from exoctk.groups_integrations.groups_integrations import perform_calculation

# Specify path to data file
EXOCTK_DATA = os.environ.get('EXOCTK_DATA')
if EXOCTK_DATA == '':
    GROUPS_INTEGRATIONS_DIR = '/path/to/your/local/copy/integrations_groups_data.json'
else:
    GROUPS_INTEGRATIONS_DIR = GROUPS_INTEGRATIONS_DIR = os.path.join(EXOCTK_DATA, 'groups_integrations/groups_integrations.json')_____no_output_____# Open the file
with open(GROUPS_INTEGRATIONS_DIR) as f:
    parameter_space = json.load(f)

# Print the target acquisition (TA) and science observation modes
modes = [('science', 'sci_sat'), ('TA', 'ta_sat')]
for mode in modes:
    print('\n')
    print('Available modes for {} :'.format(mode[0]))
    print('------------------------')
    inss = list(parameter_space[mode[1]].keys())
    print('Instruments : {}'.format(inss))
    for ins in inss:
        filts = list(parameter_space[mode[1]][ins].keys())
        print('Filters for {} : {}'.format(ins, filts))
        subs = list(parameter_space[mode[1]][ins][filts[0]].keys())
        print('Subarrays for {} : {}'.format(ins, subs))

# Print the available Phoenix models
print('\n')
print('Phoenix model keys :')
print('---------------------')
print(list(parameter_space['ta_sat'][inss[-1]][filts[0]][subs[0]].keys()))

print('\n')
print('Magnitude sampling :')
print('--------------------')
print(parameter_space['mags'])_____no_output_____# Initialize a dictionary of inputs
# When working with the webform, this is created for you
# This information for WASP 107b is available at : https://exo.mast.stsci.edu/exomast_planet.html?planet=WASP19b
parameters = {}

# Source parameters
parameters['mag'] = wasp107b_params['Kmag'] # Stellar mangnitude
parameters['band'] = 'k' # only K band vega mag for now
parameters['mod'] = 'k5v' # Phoenix model per last section

# Observation specifics
obs_time = wasp107b_params['transit_duration']*u.d.to('h')*3 + 1 # 3*transit duration, converted to hours, plus an extra hour of time
parameters['obs_time'] = obs_time
parameters['n_group'] = 'optimize' # 'optimize', or positive integer

# Detector setup -- within the modes of the last section
parameters['ins'] = 'niriss'
# For science observation
parameters['filt'] = 'soss'
parameters['subarray'] = 'substrip256'
# And target acquisition
parameters['filt_ta'] = 'f480m'
parameters['subarray_ta'] = 'im'

# Saturation level
parameters['sat_mode'] = 'well' # 'well', for full well fraction, or 'counts'
parameters['sat_max'] = .80 # < 1 for fullwell, and a positive integer always

# And feed in the data file
parameters['infile'] = GROUPS_INTEGRATIONS_DIR
input_dict = parameters.copy()_____no_output_____# Bookeeping for new/old parameters
inputs = list(parameters.keys())

# Perform the calculation
results = perform_calculation(parameters)
for key in results:
    if key in inputs:
        if key == 'infile':
            # hackers
            print('The input of infile was REDACTED!')
        else:
            print('The input of {} was {}.'.format(key, results[key]))
    else:
        print('The result of {} was {}'.format(key, results[key]))_____no_output_____# So let's make a nice printed summary
print('The total time for science + TA observation scheme is {}-{} hours.'.format(
    results['t_duration']+results['t_duration_ta_max'], results['t_duration']+results['t_duration_ta_min']))
print('You need {} groups and {} integrations for the science observation.'.format(
    results['n_group'], results['n_int']))
print('You need between {} and {} groups for target acquisition.'.format(
    results['max_ta_groups'], results['min_ta_groups']))
print('We estimate your science observation will reach at most {} counts -- how close were we to your cutoff of {}?'.format(
    results['max_sat_prediction'], results['sat_max']))
print('With this observation scheme, {}% of the observation will be spent acquiring data.'.format(results['obs_eff']*100))_____no_output_____

# Visibility and Contamination Overlap
Use ExoCTK’s Visibility and Contamination Overlap tool to determine when the target can be observed and at what position angles.

Let's do this part online at https://exoctk.stsci.edu/contam_visibility.

WASP-107 is visible twice per year for about 7 weeks each.

There are two ranges of position angle for which WASP-107 can be observed with NIRISS/SOSS: 99-121 degrees and 285-307 degrees.  Fortunately, there are no nearby stars that could contaminate the spectrum at these position angles._____no_output_____# Phase Constraint
Use ExoCTK’s Phase Constraint tool to calculate the range of acceptable start times (in units of orbital phase)_____no_output_____

from exoctk.phase_constraint_overlap.phase_constraint_overlap import phase_overlap_constraint
minphase,maxphase = phase_overlap_constraint("WASP-107b", period=None, t0=None, obs_duration=obs_time, window_size=1.)

print(minphase,maxphase)_____no_output_____

## Now we can put this data into APT.
Please open your APT application now ..._____no_output_____# Generate Limb Darkening Coefficients
To generate limb darkening coefficients for our host star, we can use the `exoctk.limb_darkening` tool. We can create an `LDC` object using a stellar intensity grid from the Phoenix ACES or Kurucz ATLAS9 models (provided in the `EXOCTK_DATA` zip file)._____no_output_____

from exoctk.limb_darkening.limb_darkening_fit import LDC
from exoctk.modelgrid import ModelGrid
from exoctk.utils import MODELGRID_DIR

atlas9 = ModelGrid(os.path.join(MODELGRID_DIR, 'ATLAS9'), resolution=700)
ld = LDC(atlas9)_____no_output_____

Next, we want to generate our limb darkening coefficients using a particular grism throughput using the `svo_filters` package. For this example, we'll use the GR700XD grism to simulate some NIRISS SOSS mode observations of our target, split into 15 equal wavelength bins._____no_output_____

from svo_filters import Filter
gr700xd = Filter('NIRISS.GR700XD.1', n_bins=15)
gr700xd.plot()_____no_output_____

Finally, we can calculate our limb darkening coefficients by passing the host star effective temperature, surface gravity, and metalicity to the `calculate` method. We will also provide the limb darkening profile name and the bandpass we wish to consider:_____no_output_____

teff = wasp107b_params['Teff']
logg = wasp107b_params['stellar_gravity']
feh = wasp107b_params['Fe/H']
print(teff, logg, feh)
ld.calculate(teff, logg, feh, 'quadratic', bandpass=gr700xd)
ld.results.pprint()_____no_output_____


# Notebook from mimc7580/diff_register
Path: notebooks/04_24_18_linking_cell_trajectory_data-Copy1.ipynb

<code>
import numpy as np
import pandas as pd
import os
import diff_classifier.aws as aws
import diff_classifier.features as ft
import diff_classifier.msd as msd
import diff_classifier.heatmaps as hm
import diff_classifier.imagej as ij
import boto3
import os.path as op
import cloudknot as ck_____no_output_____to_track = {}
knot = {}
result_futures = {}
start_knot = 918 #Must be unique number for every run on Cloudknot.

slices = ['1', '2']
regions = [1, 2, 3]
videos = [1, 2, 3, 4, 5]
folder = 'Tissue_Studies/04_23_18_Registration_Test/tracking' #Folder in AWS S3 containing files to be analyzed

_____no_output_____def download_split_track_msds(prefix):
    """
    1. Checks to see if features file exists.
    2. If not, checks to see if image partitioning has occured.
    3. If yes, checks to see if tracking has occured.
    4. Regardless, tracks, calculates MSDs and features.
    """

    import matplotlib as mpl
    #mpl.use('Agg')
    import diff_classifier.aws as aws
    import diff_classifier.utils as ut
    import diff_classifier.msd as msd
    import diff_classifier.features as ft
    import diff_classifier.imagej as ij
    import diff_classifier.heatmaps as hm

    from scipy.spatial import Voronoi
    import scipy.stats as stats
    from shapely.geometry import Point
    from shapely.geometry.polygon import Polygon
    import matplotlib.cm as cm
    import os
    import os.path as op
    import glob
    import numpy as np
    import numpy.ma as ma
    import pandas as pd
    import boto3

    #Splitting section
    ###############################################################################################
    remote_folder = "Tissue_Studies/04_23_18_Registration_Test/tracking"
    local_folder = os.getcwd()
    ires = 512
    frames = 651
    filename = '{}.tif'.format(prefix)
    remote_name = remote_folder+'/'+filename
    local_name = local_folder+'/'+filename

    msd_file = 'msd_{}.csv'.format(prefix)
    ft_file = 'features_{}.csv'.format(prefix)

    s3 = boto3.client('s3')

    names = []
    for i in range(0, 4):
        for j in range(0, 4):
            names.append('{}_{}_{}.tif'.format(prefix, i, j))

    try:
        obj = s3.head_object(Bucket='ccurtis.data', Key=remote_folder+'/'+ft_file)
    except:

        try:
            for name in names:
                aws.download_s3(remote_folder+'/'+name, name, bucket_name='ccurtis.data')
        except:
            aws.download_s3(remote_name, local_name, bucket_name='ccurtis.data')
            names = ij.partition_im(local_name)

            names = []
            for i in range(0, 4):
                for j in range(0, 4):
                    names.append('{}_{}_{}.tif'.format(prefix, i, j))
            for name in names:
                aws.upload_s3(name, remote_folder+'/'+name, bucket_name='ccurtis.data')
                print("Done with splitting.  Should output file of name {}".format(remote_folder+'/'+name))

        #Tracking section
        ################################################################################################
        names = []
        for i in range(0, 4):
                for j in range(0, 4):
                    names.append('{}_{}_{}.tif'.format(prefix, i, j))

        for name in names:
            outfile = 'Traj_' + name.split('.')[0] + '.csv'
            local_im = op.join(local_folder, name)

            row = int(name.split('.')[0].split('_')[4])
            col = int(name.split('.')[0].split('_')[5])

            try:
                aws.download_s3(remote_folder+'/'+outfile, outfile, bucket_name='ccurtis.data')
            except:
                test_intensity = ij.mean_intensity(local_im)
                if test_intensity > 500:
                    quality = 245
                else:
                    quality = 0.1

                if row==3:
                    y = 485
                else:
                    y = 511

                ij.track(local_im, outfile, template=None, fiji_bin=None, radius=5.0, threshold=0.01,
                         do_median_filtering=True, quality=quality, x=511, y=y, ylo=1, median_intensity=300.0, snr=0.0,
                         linking_max_distance=8.0, gap_closing_max_distance=15.0, max_frame_gap=9,
                         track_displacement=0.0)

                aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name='ccurtis.data')
            print("Done with tracking.  Should output file of name {}".format(remote_folder+'/'+outfile))

        #MSD and features section
        #################################################################################################
        files_to_big = False
        size_limit = 10

        for name in names:
            outfile = 'Traj_' + name.split('.')[0] + '.csv'
            local_im = name
            file_size_MB = op.getsize(local_im)/1000000
            if file_size_MB > size_limit:
                file_to_big = True

        if files_to_big:
            print('One or more of the {} trajectory files exceeds {}MB in size.  Will not continue with MSD calculations.'.format(
                  prefix, size_limit))
        else:
            counter = 0
            for name in names:
                row = int(name.split('.')[0].split('_')[4])
                col = int(name.split('.')[0].split('_')[5])

                filename = "Traj_{}_{}_{}.csv".format(prefix, row, col)
                local_name = local_folder+'/'+filename

                if counter == 0:
                    to_add = ut.csv_to_pd(local_name)
                    to_add['X'] = to_add['X'] + ires*col
                    to_add['Y'] = ires - to_add['Y'] + ires*(3-row)
                    merged = msd.all_msds2(to_add, frames=frames)
                else:

                    if merged.shape[0] > 0:
                        to_add = ut.csv_to_pd(local_name)
                        to_add['X'] = to_add['X'] + ires*col
                        to_add['Y'] = ires - to_add['Y'] + ires*(3-row)
                        to_add['Track_ID'] = to_add['Track_ID'] + max(merged['Track_ID']) + 1
                    else:
                        to_add = ut.csv_to_pd(local_name)
                        to_add['X'] = to_add['X'] + ires*col
                        to_add['Y'] = ires - to_add['Y'] + ires*(3-row)
                        to_add['Track_ID'] = to_add['Track_ID']

                    merged = merged.append(msd.all_msds2(to_add, frames=frames))
                    print('Done calculating MSDs for row {} and col {}'.format(row, col))
                counter = counter + 1

            merged.to_csv(msd_file)
            aws.upload_s3(msd_file, remote_folder+'/'+msd_file, bucket_name='ccurtis.data')
            merged_ft = ft.calculate_features(merged)
            merged_ft.to_csv(ft_file)

            aws.upload_s3(ft_file, remote_folder+'/'+ft_file, bucket_name='ccurtis.data')

            #Plots
            features = ('AR', 'D_fit', 'alpha', 'MSD_ratio', 'Track_ID', 'X', 'Y', 'asymmetry1', 'asymmetry2', 'asymmetry3',
                        'boundedness', 'efficiency', 'elongation', 'fractal_dim', 'frames', 'kurtosis', 'straightness', 'trappedness')

            die = {'features': features,
                   'vmin': vmin,
                   'vmax': vmax}
            di = pd.DataFrame(data=die)

            hm.plot_trajectories(prefix, remote_folder=remote_folder, bucket='ccurtis.data')
            try:
                hm.plot_histogram(prefix, remote_folder=remote_folder, bucket='ccurtis.data')
            except ValueError:
                print("Couldn't plot histogram.")
            hm.plot_particles_in_frame(prefix, remote_folder=remote_folder, bucket='ccurtis.data')
            gmean1, gSEM1 = hm.plot_individual_msds(prefix, alpha=0.05, remote_folder=remote_folder, bucket='ccurtis.data')

    for filename in glob.glob('./100*'):
        os.remove(filename)_____no_output_____def test_cloudknot(prefix):
    print('The run {} was successful'.format(prefix))_____no_output_____github_installs=('https://github.com/ccurtis7/diff_classifier.git')
my_image = ck.DockerImage(func=download_split_track_msds, base_image='arokem/python3-fiji:0.3', github_installs=github_installs)_____no_output_____docker_file = open(my_image.docker_path)
docker_string = docker_file.read()
docker_file.close()

req = open(op.join(op.split(my_image.docker_path)[0], 'requirements.txt'))
req_string = req.read()
req.close()

new_req = req_string[0:req_string.find('\n')-4]+'5.28'+ req_string[req_string.find('\n'):]
req_overwrite = open(op.join(op.split(my_image.docker_path)[0], 'requirements.txt'), 'w')
req_overwrite.write(new_req)
req_overwrite.close()_____no_output_____new_req_____no_output_____#Test Docker Image
my_image.build("0.1", image_name="test_image")_____no_output_____ck.clobber(name='diff_classifier_904')_____no_output_____to_track = []
for slic in slices:
    for region in regions:
        for video in videos:
            prefix = '100nm_S{}_XY{}_{}'.format(slic, region, video)
            to_track.append(prefix)

test_length = len(to_track)
print('Number of nodes to be loaded: {}'.format(test_length))

knot = ck.Knot(name='diff_classifier_{}'.format(start_knot),
               docker_image = my_image,
               memory = 32000,
               resource_type = "SPOT",
               bid_percentage = 100,
               image_id = 'ami-0de34a0a338c1051b',
               pars_policies=('AmazonS3FullAccess',))

result_futures = knot.map(to_track)
start_knot = start_knot + 1
print('Next knot name: {}'.format(start_knot))Number of nodes to be loaded: 30
Next knot name: 919
knot.clobber()_____no_output_____prefix = '100nm_S1_XY2_5'
download_split_track_msds(prefix)_____no_output_____folder = '/home/ubuntu/source/diff-classifier/notebooks'
fname = 'features_P1_S1_L_0000.csv'
file = '{}/{}'.format(folder, fname)
features = pd.read_csv(file, index_col='Unnamed: 0')_____no_output_____import xml.etree.ElementTree as ET_____no_output_____slices = ['1', '2']
regions = [1, 2, 3]
videos = [1, 2, 3, 4, 5]

for slic in slices:
    for region in regions:
        for video in videos:
            prefix = '100nm_S{}_XY{}_{}'.format(slic, region, video)
            download_split_track_msds(prefix)_____no_output_____remote_folder = "Tissue_Studies/04_23_18_Registration_Test/tracking"
local_folder = os.getcwd()
ires = 512
frames = 651
filename = '{}.tif'.format(prefix)
remote_name = remote_folder+'/'+filename
local_name = local_folder+'/'+filename

msd_file = 'msd_{}.csv'.format(prefix)
ft_file = 'features_{}.csv'.format(prefix)

s3 = boto3.client('s3')

names = []
for i in range(0, 4):
    for j in range(0, 4):
        names.append('{}_{}_{}.tif'.format(prefix, i, j))

try:
    obj = s3.head_object(Bucket='ccurtis.data', Key=remote_folder+'/'+ft_file)
except:

    try:
        for name in names:
            aws.download_s3(remote_folder+'/'+name, name, bucket_name='ccurtis.data')
    except:
        aws.download_s3(remote_name, local_name, bucket_name='ccurtis.data')
        names = ij.partition_im(local_name)
        names = []
        for i in range(0, 4):
            for j in range(0, 4):
                names.append('{}_{}_{}.tif'.format(prefix, i, j))
        for name in names:
            aws.upload_s3(name, remote_folder+'/'+name, bucket_name='ccurtis.data')
            print("Done with splitting.  Should output file of name {}".format(remote_folder+'/'+name))_____no_output_____!rm 100*_____no_output_____!rm feat*
!rm msd*
!rm Traj*_____no_output_____prefix = '100nm_S1_XY2_5_1_1'
local_folder = '.'
name = "{}.tif".format(prefix)
outfile = 'Traj_' + name.split('.')[0] + '.csv'
local_im = op.join(local_folder, name)
quality = 0
y=511
ij.track(local_im, outfile, template=None, fiji_bin=None, radius=5, threshold=0.1,
                         do_median_filtering=True, quality=quality, x=511, y=y, ylo=1, median_intensity=300.0, snr=0.0,
                         linking_max_distance=10.0, gap_closing_max_distance=12.0, max_frame_gap=9,
                         track_displacement=0.0)_____no_output_____local_im_____no_output_____prefix = '100nm_S1_XY2_5_1_2'
filename = '{}.tif'.format(prefix)
fname = "Tissue_Studies/04_23_18_Registration_Test/tracking/{}".format(filename)

aws.download_s3(fname, filename, bucket_name='ccurtis.data')_____no_output_____local_folder = '.'
name = "{}.tif".format(prefix)
outfile = 'Traj_' + name.split('.')[0] + '.csv'
local_im = op.join(local_folder, name)
quality = 0.1
y=511
ij.track(local_im, outfile, template=None, fiji_bin=None, radius=5.0, threshold=0.01,
                         do_median_filtering=False, quality=quality, x=511, y=y, ylo=1, median_intensity=300.0, snr=0.0,
                         linking_max_distance=8.0, gap_closing_max_distance=15.0, max_frame_gap=9,
                         track_displacement=0.0)_____no_output_____
</code>


# Notebook from tetov/MAS-1920
Path: T1/02_fun_with_flags/02_fun_w_flags-atj.ipynb

<code>
import matplotlib.pyplot as plt
import math
import matplotlib.patches as mpatches
import numpy as np

class Polygon:
    def __init__(self, xlist: list, ylist: list, color=[1,0,0]):
        self.xlist = xlist
        self.ylist = ylist
        self.color = color
    def display(self):
        plt.fill(self.xlist, self.ylist, c=self.color)

def rect(x1: float,y1: float,x2: float, y2: float, c) -> object:
    r=Polygon([x1,x2,x2,x1], [y1,y1,y2,y2], c)
    return r

def display_flag(canvas: tuple, objects: list, **kwargs):
    plt.figure(figsize=canvas)

    if 'debug' in kwargs and kwargs['debug']:
        plt.grid(which='both')
    else:
        plt.axis('off')

    for o in objects:
        plt.fill(o.xlist, o.ylist, c=o.color)

def star(xpos: float, ypos: float, radius: float, resolution: int, scale_factor: float, color = [1,1,1]) -> object:
    xc,yc = [],[]

    if resolution % 2 == 1:
        resolution += 1

    angle = 2 * math.pi / resolution

    for i in range(resolution):

        loop_radius = radius
        if i % 2 == 1:
            loop_radius *= scale_factor

        xc.append(xpos+math.cos(i*angle) * loop_radius)
        yc.append(ypos+math.sin(i*angle) * loop_radius)

    return Polygon(xc, yc, color)

def arc_patch(center: tuple, radius: float, theta1: float, theta2: float, resolution=50) -> list:
    theta = np.linspace(np.radians(theta1), np.radians(theta2), resolution)
    points = np.vstack((radius*np.cos(theta) + center[0],
                        radius*np.sin(theta) + center[1]))
    return points.tolist()_____no_output_____"""
A blue field charged with a yellow Nordic cross that extends to the edges;
the vertical part of the cross is shifted to the hoist side.
Dimensions: 5:2:9 horizontally and 4:2:4 vertically.
"""

sweden = []

sweden.append(rect(0,0,16,10,'#006aa7'))
sweden.append(rect(4,0,6,10,'#fecc00'))
sweden.append(rect(0,4,16,6,'#fecc00'))

sweden.append(star(10,8,2,18,.3,'#ffcc00'))

display_flag((16,10), sweden)_____no_output_____'''
The city flag of Portland, Oregon,
consists of a green field on which is placed a white four-pointed star (a truncated hypocycloid)
from which radiate blue stripes, each bordered by L-shaped yellow elements.
Narrow white fimbriations separate the blue and yellow elements from each other and from the green background.
The official ordinance specifies a height of 3 feet and a length of 5 feet.

https://99percentinvisible.org/episode/vexillonaire/
'''

portland_99pi = []

white = '#ffffff'
yellow = '#f3c120'
blue = '#4579b3'

max_x=16
max_y=10

width_white = 3

offset_yellow = .25
width_yellow = .5

offset_blue = offset_yellow

# bg
portland_99pi.append(rect(0,0,max_x,max_y,'#027f45'))

# white horisontal
portland_99pi.append(rect(0,3,6,3+width_white,white))
portland_99pi.append(rect(6,4,max_x,4+width_white,white))

# white vertical
portland_99pi.append(rect(5,4,5+width_white,max_y,white))
portland_99pi.append(rect(6,0,6+width_white,5,white))

# yellow horisontal
portland_99pi.append(rect(0,3+width_white-offset_yellow,5+offset_yellow+width_yellow,3+width_white-offset_yellow-width_yellow,yellow))
portland_99pi.append(rect(0, 3+offset_yellow, 6+width_yellow, 3+offset_yellow+width_yellow, yellow))

portland_99pi.append(rect(8-width_yellow, 7-offset_yellow, max_x, 7-offset_yellow-width_yellow, yellow))
portland_99pi.append(rect(9-width_yellow, 4+offset_yellow, max_x, 4 +offset_yellow+width_yellow, yellow))

# yellow vertical
portland_99pi.append(rect(5+offset_yellow,3+width_white-offset_yellow,5+offset_yellow+width_yellow,max_y,yellow))
portland_99pi.append(rect(5+width_white-offset_yellow-width_yellow,4+width_white-offset_yellow-width_yellow, 5+width_white-offset_yellow ,max_y,yellow))

portland_99pi.append(rect(6+offset_yellow,0,6+offset_yellow+width_yellow,3+offset_yellow+width_yellow,yellow))
portland_99pi.append(rect(6+width_white-width_yellow-offset_yellow,0,6+width_white-offset_yellow,4+offset_yellow+width_yellow,yellow))

# blue parts done as one polygon
blue_polygon_pts = []

blue_polygon_pts.append([0, 3+offset_yellow+width_yellow+offset_blue])
blue_polygon_pts.append([0, 3+width_white-offset_yellow-width_yellow-offset_blue])
blue_polygon_pts.append([5+offset_yellow+width_yellow+offset_blue, 3+width_white-offset_yellow-width_yellow-offset_blue])
blue_polygon_pts.append([5+offset_yellow+width_yellow+offset_blue, max_y])
blue_polygon_pts.append([5+width_white-offset_yellow-width_yellow-offset_blue, max_y])
blue_polygon_pts.append([5+width_white-offset_yellow-width_yellow-offset_blue, 4+width_white-offset_yellow-width_yellow-offset_blue])
blue_polygon_pts.append([max_x,4+width_white-offset_yellow-width_yellow-offset_blue])
blue_polygon_pts.append([max_x,4+offset_yellow+width_yellow+offset_blue])
blue_polygon_pts.append([6+width_white-offset_yellow-width_yellow-offset_blue,4+offset_yellow+width_yellow+offset_blue])
blue_polygon_pts.append([6+width_white-offset_yellow-width_yellow-offset_blue,0])
blue_polygon_pts.append([6+offset_yellow+width_yellow+offset_blue,0])
blue_polygon_pts.append([6+offset_yellow+width_yellow+offset_blue,3+offset_yellow+width_yellow+offset_blue])

blue_polygon_x_list = [n[0] for n in blue_polygon_pts]
blue_polygon_y_list = [n[1] for n in blue_polygon_pts]

blue_polygon = Polygon(blue_polygon_x_list, blue_polygon_y_list, blue)

portland_99pi.append(blue_polygon)

# White patch in middle

patch_args = []
patch_args.append([(6,4), 1, 90, 0])
patch_args.append([(6,6), 1, 270, 360])
patch_args.append([(8,4), 1, 90, 180])
patch_args.append([(8,6), 1, 180, 270])

for arc in patch_args:
    x_coords, y_coords = arc_patch(*arc)

    # Add centroid
    x_coords.append(7)
    y_coords.append(5)

    portland_99pi.append(Polygon(x_coords, y_coords, '#ffffff'))

display_flag((max_x,max_y), portland_99pi, debug=False)

_____no_output_____
</code>