{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Feature Creation: MathematicalCombination\n",
"The MathematicalCombination() applies basic mathematical operations **[‘sum’, ‘prod’, ‘mean’, ‘std’, ‘max’, ‘min’]** to multiple features, returning one or more additional features as a result.\n",
"\n",
"For this demonstration, we use the UCI Wine Quality Dataset.\n",
"\n",
"The data is publicly available on **[UCI repository](https://archive.ics.uci.edu/ml/datasets/Wine+Quality)**\n",
"\n",
"P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis.\n",
"Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.metrics import (\n",
" accuracy_score,\n",
" roc_curve,\n",
" roc_auc_score,\n",
" classification_report,\n",
" confusion_matrix,\n",
")\n",
"from sklearn.pipeline import Pipeline as pipe\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"from feature_engine.creation import MathematicalCombination\n",
"from feature_engine.imputation import MeanMedianImputer\n",
"\n",
"pd.set_option('display.max_columns', None)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" fixed acidity | \n",
" volatile acidity | \n",
" citric acid | \n",
" residual sugar | \n",
" chlorides | \n",
" free sulfur dioxide | \n",
" total sulfur dioxide | \n",
" density | \n",
" pH | \n",
" sulphates | \n",
" alcohol | \n",
" quality | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 5 | \n",
"
\n",
" \n",
" 1 | \n",
" 7.8 | \n",
" 0.88 | \n",
" 0.00 | \n",
" 2.6 | \n",
" 0.098 | \n",
" 25.0 | \n",
" 67.0 | \n",
" 0.9968 | \n",
" 3.20 | \n",
" 0.68 | \n",
" 9.8 | \n",
" 5 | \n",
"
\n",
" \n",
" 2 | \n",
" 7.8 | \n",
" 0.76 | \n",
" 0.04 | \n",
" 2.3 | \n",
" 0.092 | \n",
" 15.0 | \n",
" 54.0 | \n",
" 0.9970 | \n",
" 3.26 | \n",
" 0.65 | \n",
" 9.8 | \n",
" 5 | \n",
"
\n",
" \n",
" 3 | \n",
" 11.2 | \n",
" 0.28 | \n",
" 0.56 | \n",
" 1.9 | \n",
" 0.075 | \n",
" 17.0 | \n",
" 60.0 | \n",
" 0.9980 | \n",
" 3.16 | \n",
" 0.58 | \n",
" 9.8 | \n",
" 6 | \n",
"
\n",
" \n",
" 4 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 5 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" fixed acidity volatile acidity citric acid residual sugar chlorides \\\n",
"0 7.4 0.70 0.00 1.9 0.076 \n",
"1 7.8 0.88 0.00 2.6 0.098 \n",
"2 7.8 0.76 0.04 2.3 0.092 \n",
"3 11.2 0.28 0.56 1.9 0.075 \n",
"4 7.4 0.70 0.00 1.9 0.076 \n",
"\n",
" free sulfur dioxide total sulfur dioxide density pH sulphates \\\n",
"0 11.0 34.0 0.9978 3.51 0.56 \n",
"1 25.0 67.0 0.9968 3.20 0.68 \n",
"2 15.0 54.0 0.9970 3.26 0.65 \n",
"3 17.0 60.0 0.9980 3.16 0.58 \n",
"4 11.0 34.0 0.9978 3.51 0.56 \n",
"\n",
" alcohol quality \n",
"0 9.4 5 \n",
"1 9.8 5 \n",
"2 9.8 5 \n",
"3 9.8 6 \n",
"4 9.4 5 "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Read data\n",
"data = pd.read_csv('winequality-red.csv', sep=';')\n",
"\n",
"data.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**This Data contains 11 features, all numerical, with no missing values.**"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" quality_range | \n",
" quality | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 0 | \n",
" 5 | \n",
"
\n",
" \n",
" 1 | \n",
" 0 | \n",
" 5 | \n",
"
\n",
" \n",
" 2 | \n",
" 0 | \n",
" 5 | \n",
"
\n",
" \n",
" 3 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
" 4 | \n",
" 0 | \n",
" 5 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" quality_range quality\n",
"0 0 5\n",
"1 0 5\n",
"2 0 5\n",
"3 1 6\n",
"4 0 5"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Let's transform the Target, i.e Wine Quality into a binary classification problem:\n",
"\n",
"bins = [0,5,10]\n",
"\n",
"labels = [0, 1] # 'low'=0, 'high'=1\n",
"\n",
"data['quality_range']= pd.cut(x=data['quality'], bins=bins, labels=labels)\n",
"\n",
"data[['quality_range','quality']].head(5)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# drop original target\n",
"\n",
"data.drop('quality', axis=1, inplace = True) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Sum and Mean Combinators:\n",
"Let's create two new variables:\n",
"- avg_acidity = mean(fixed acidity, volatile acidity)\n",
"- total_minerals = sum(Total sulfure dioxide, sulphates)\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# Create the Combinators\n",
"\n",
"math_combinator_mean = MathematicalCombination(\n",
" variables_to_combine=['fixed acidity', 'volatile acidity'],\n",
" math_operations = ['mean'],\n",
" new_variables_names = ['avg_acidity']\n",
")\n",
"\n",
"math_combinator_sum = MathematicalCombination(\n",
" variables_to_combine=['total sulfur dioxide', 'sulphates'],\n",
" math_operations = ['sum'],\n",
" new_variables_names = ['total_minerals']\n",
")\n",
"\n",
"# Fit the Mean Combinator on training data\n",
"math_combinator_mean.fit(data)\n",
"\n",
"# Transform the data\n",
"data_t = math_combinator_mean.transform(data)\n",
"\n",
"# We can combine both steps in a single call with \".fit_transform()\" methode\n",
"data_t = math_combinator_sum.fit_transform(data_t)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" fixed acidity | \n",
" volatile acidity | \n",
" citric acid | \n",
" residual sugar | \n",
" chlorides | \n",
" free sulfur dioxide | \n",
" total sulfur dioxide | \n",
" density | \n",
" pH | \n",
" sulphates | \n",
" alcohol | \n",
" quality_range | \n",
" avg_acidity | \n",
" total_minerals | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 0 | \n",
" 4.05 | \n",
" 34.56 | \n",
"
\n",
" \n",
" 1 | \n",
" 7.8 | \n",
" 0.88 | \n",
" 0.00 | \n",
" 2.6 | \n",
" 0.098 | \n",
" 25.0 | \n",
" 67.0 | \n",
" 0.9968 | \n",
" 3.20 | \n",
" 0.68 | \n",
" 9.8 | \n",
" 0 | \n",
" 4.34 | \n",
" 67.68 | \n",
"
\n",
" \n",
" 2 | \n",
" 7.8 | \n",
" 0.76 | \n",
" 0.04 | \n",
" 2.3 | \n",
" 0.092 | \n",
" 15.0 | \n",
" 54.0 | \n",
" 0.9970 | \n",
" 3.26 | \n",
" 0.65 | \n",
" 9.8 | \n",
" 0 | \n",
" 4.28 | \n",
" 54.65 | \n",
"
\n",
" \n",
" 3 | \n",
" 11.2 | \n",
" 0.28 | \n",
" 0.56 | \n",
" 1.9 | \n",
" 0.075 | \n",
" 17.0 | \n",
" 60.0 | \n",
" 0.9980 | \n",
" 3.16 | \n",
" 0.58 | \n",
" 9.8 | \n",
" 1 | \n",
" 5.74 | \n",
" 60.58 | \n",
"
\n",
" \n",
" 4 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 0 | \n",
" 4.05 | \n",
" 34.56 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" fixed acidity volatile acidity citric acid residual sugar chlorides \\\n",
"0 7.4 0.70 0.00 1.9 0.076 \n",
"1 7.8 0.88 0.00 2.6 0.098 \n",
"2 7.8 0.76 0.04 2.3 0.092 \n",
"3 11.2 0.28 0.56 1.9 0.075 \n",
"4 7.4 0.70 0.00 1.9 0.076 \n",
"\n",
" free sulfur dioxide total sulfur dioxide density pH sulphates \\\n",
"0 11.0 34.0 0.9978 3.51 0.56 \n",
"1 25.0 67.0 0.9968 3.20 0.68 \n",
"2 15.0 54.0 0.9970 3.26 0.65 \n",
"3 17.0 60.0 0.9980 3.16 0.58 \n",
"4 11.0 34.0 0.9978 3.51 0.56 \n",
"\n",
" alcohol quality_range avg_acidity total_minerals \n",
"0 9.4 0 4.05 34.56 \n",
"1 9.8 0 4.34 67.68 \n",
"2 9.8 0 4.28 54.65 \n",
"3 9.8 1 5.74 60.58 \n",
"4 9.4 0 4.05 34.56 "
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data_t.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can check the mappings between each new variable and the operation it's created with in the **combination_dict_**"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'avg_acidity': 'mean'}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"math_combinator_mean.combination_dict_"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['fixed acidity', 'volatile acidity']"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"math_combinator_mean.variables_to_combine"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Combine with more than 1 operation\n",
"\n",
"We can also combine the variables with more than 1 mathematical operation. And the transformer has the option to create variable names automatically."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# Create the Combinators\n",
"\n",
"multiple_combinator = MathematicalCombination(\n",
" variables_to_combine=['fixed acidity', 'volatile acidity'],\n",
" math_operations = ['mean', 'sum'],\n",
" new_variables_names = None\n",
")\n",
"\n",
"\n",
"# Fit the Combinator to the training data\n",
"multiple_combinator.fit(data)\n",
"\n",
"# Transform the data\n",
"data_t = multiple_combinator.transform(data)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" fixed acidity | \n",
" volatile acidity | \n",
" citric acid | \n",
" residual sugar | \n",
" chlorides | \n",
" free sulfur dioxide | \n",
" total sulfur dioxide | \n",
" density | \n",
" pH | \n",
" sulphates | \n",
" alcohol | \n",
" quality_range | \n",
" mean(fixed acidity-volatile acidity) | \n",
" sum(fixed acidity-volatile acidity) | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 0 | \n",
" 4.05 | \n",
" 8.10 | \n",
"
\n",
" \n",
" 1 | \n",
" 7.8 | \n",
" 0.88 | \n",
" 0.00 | \n",
" 2.6 | \n",
" 0.098 | \n",
" 25.0 | \n",
" 67.0 | \n",
" 0.9968 | \n",
" 3.20 | \n",
" 0.68 | \n",
" 9.8 | \n",
" 0 | \n",
" 4.34 | \n",
" 8.68 | \n",
"
\n",
" \n",
" 2 | \n",
" 7.8 | \n",
" 0.76 | \n",
" 0.04 | \n",
" 2.3 | \n",
" 0.092 | \n",
" 15.0 | \n",
" 54.0 | \n",
" 0.9970 | \n",
" 3.26 | \n",
" 0.65 | \n",
" 9.8 | \n",
" 0 | \n",
" 4.28 | \n",
" 8.56 | \n",
"
\n",
" \n",
" 3 | \n",
" 11.2 | \n",
" 0.28 | \n",
" 0.56 | \n",
" 1.9 | \n",
" 0.075 | \n",
" 17.0 | \n",
" 60.0 | \n",
" 0.9980 | \n",
" 3.16 | \n",
" 0.58 | \n",
" 9.8 | \n",
" 1 | \n",
" 5.74 | \n",
" 11.48 | \n",
"
\n",
" \n",
" 4 | \n",
" 7.4 | \n",
" 0.70 | \n",
" 0.00 | \n",
" 1.9 | \n",
" 0.076 | \n",
" 11.0 | \n",
" 34.0 | \n",
" 0.9978 | \n",
" 3.51 | \n",
" 0.56 | \n",
" 9.4 | \n",
" 0 | \n",
" 4.05 | \n",
" 8.10 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" fixed acidity volatile acidity citric acid residual sugar chlorides \\\n",
"0 7.4 0.70 0.00 1.9 0.076 \n",
"1 7.8 0.88 0.00 2.6 0.098 \n",
"2 7.8 0.76 0.04 2.3 0.092 \n",
"3 11.2 0.28 0.56 1.9 0.075 \n",
"4 7.4 0.70 0.00 1.9 0.076 \n",
"\n",
" free sulfur dioxide total sulfur dioxide density pH sulphates \\\n",
"0 11.0 34.0 0.9978 3.51 0.56 \n",
"1 25.0 67.0 0.9968 3.20 0.68 \n",
"2 15.0 54.0 0.9970 3.26 0.65 \n",
"3 17.0 60.0 0.9980 3.16 0.58 \n",
"4 11.0 34.0 0.9978 3.51 0.56 \n",
"\n",
" alcohol quality_range mean(fixed acidity-volatile acidity) \\\n",
"0 9.4 0 4.05 \n",
"1 9.8 0 4.34 \n",
"2 9.8 0 4.28 \n",
"3 9.8 1 5.74 \n",
"4 9.4 0 4.05 \n",
"\n",
" sum(fixed acidity-volatile acidity) \n",
"0 8.10 \n",
"1 8.68 \n",
"2 8.56 \n",
"3 11.48 \n",
"4 8.10 "
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Note the 2 additional variables at the end of the dataframe\n",
"data_t.head()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'mean(fixed acidity-volatile acidity)': 'mean',\n",
" 'sum(fixed acidity-volatile acidity)': 'sum'}"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# and here the variable names and the operation that was\n",
"# applied to create that variable\n",
"\n",
"multiple_combinator.combination_dict_"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Pipeline Example"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can put all these transformations into single pipeline:\n",
"\n",
"1. Create new variables\n",
"2. Scale features\n",
"3. Train a Logistic Regression model to predict wine quality\n",
"\n",
"See more on how to use Feature-engine within Scikit-learn Pipelines in these **[examples](https://github.com/solegalli/feature_engine/tree/master/examples/Pipelines)**"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((1439, 11), (160, 11))"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"X = data.drop(['quality_range'], axis=1)\n",
"\n",
"y = data.quality_range\n",
"\n",
"X_train, X_test, y_train, y_test = train_test_split(X,\n",
" y,\n",
" test_size=0.1,\n",
" random_state=0,\n",
" shuffle=True,\n",
" stratify=y\n",
" )\n",
"X_train.shape, X_test.shape"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"value_pipe = pipe([\n",
"\n",
" # Create the new features\n",
" ('math_combinator_mean', MathematicalCombination(variables_to_combine=['fixed acidity', 'volatile acidity'],\n",
" math_operations=['mean'],\n",
" new_variables_names=['avg_acidity'])),\n",
"\n",
" ('math_combinator_sum', MathematicalCombination(variables_to_combine=['total sulfur dioxide', 'sulphates'],\n",
" math_operations=['sum'],\n",
" new_variables_names=['total_minerals'])),\n",
"\n",
" # scale features\n",
" ('scaler', StandardScaler()),\n",
"\n",
" # LogisticRegression\n",
" ('LogisticRegression', LogisticRegression())\n",
"])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Pipeline(steps=[('math_combinator_mean',\n",
" MathematicalCombination(math_operations=['mean'],\n",
" new_variables_names=['avg_acidity'],\n",
" variables_to_combine=['fixed acidity',\n",
" 'volatile '\n",
" 'acidity'])),\n",
" ('math_combinator_sum',\n",
" MathematicalCombination(math_operations=['sum'],\n",
" new_variables_names=['total_minerals'],\n",
" variables_to_combine=['total sulfur '\n",
" 'dioxide',\n",
" 'sulphates'])),\n",
" ('scaler', StandardScaler()),\n",
" ('LogisticRegression', LogisticRegression())])"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"value_pipe.fit(X_train, y_train)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"pred_train = value_pipe.predict(X_train)\n",
"pred_test = value_pipe.predict(X_test)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LogisticRegression Model train accuracy score: 0.744266851980542\n",
"\n",
"LogisticRegression Model test accuracy score: 0.75\n"
]
}
],
"source": [
"print('Logistic Regression Model train accuracy score: {}'.format(\n",
" accuracy_score(y_train, pred_train)))\n",
"print()\n",
"print('Logistic Regression Model test accuracy score: {}'.format(\n",
" accuracy_score(y_test, pred_test)))"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LogisticRegression Model test classification report: \n",
"\n",
" precision recall f1-score support\n",
"\n",
" 0 0.73 0.73 0.73 74\n",
" 1 0.77 0.77 0.77 86\n",
"\n",
" accuracy 0.75 160\n",
" macro avg 0.75 0.75 0.75 160\n",
"weighted avg 0.75 0.75 0.75 160\n",
"\n"
]
}
],
"source": [
"print('Logistic Regression Model test classification report: \\n\\n {}'.format(\n",
" classification_report(y_test, pred_test)))"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAWgAAAEYCAYAAABxx2wUAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgsUlEQVR4nO3deZyd8/n/8ddbFiJKEpEEsdZOS9vYqoultZTYpaFIfVVo8S2toouq0lZpS1q6hCB8SSJ2WrFEUvzaZpMgElsRhIglISKRzMz1++P+DMc4M+dMzJxzn8z76XE/5tzb577mZFznc677c9+3IgIzM8ufVaodgJmZFecEbWaWU07QZmY55QRtZpZTTtBmZjnlBG1mllNO0Dkg6XlJIWmzaseSN5K2k3SbpFclLUnv1WhJ21U7thUhaS1JV0taIOltSddLWrvEPrunv49i0z0F2327mW1Oav/fzNpD52oH0NFJ2hXYOM0eCZxfvWjyJX1g/QeYDJwCLAA2B44APgvMrF50K+xGYAvgO0AD8FvgNuDLLezzCLBrk2UbAmOAu4tsvyewpGD+uRWM1arMCbr6jgQWkyWb3CRoSZ2AThGxrIphHAe8D+wXEe+nZQ8Af5Ok9j64pG4RsaT0lmW3tyuwN/DViHgwLZsLTJL0tYi4v9h+EfEO2QdVYVtfJkvwNxbZZUpEvNtWcVv1uMRRRSkJDgLuAK4Ctpa0fZHtviJpgqR309fiiZI+V7B+I0mjJL0h6T1Jj0k6Kq1r/Hq8XZM2J0q6qWD+GklTJR0s6QlgKbCzpHUlXSXpuVRieFrSBZK6Nmmvm6SLJM2R9H4qRfwmrbso7a8m+3xb0jJJ6zTzFvUAFhYk5w9Ek0tgJR0iaXKK8U1J/5C0UcH6PSVNkrRU0muS/ixpjYL1je/TPpLukPQucFlat2Eqq7yV3t97JG3ZTMwt2Q94rTE5p99jMvB8WtcaRwL/jIhXViAOqxFO0NW1B9AXGA3cBCwn+x/vA5J2B8andUOAbwIPAeun9X2AfwM7AmcAA4ERwAYrEM/GwEXAb8gSxvNAb+At4AfAvsDFZD3bPxXEKOB24LvA5cA3gHPTvpB9+GwCfLXJ8Y4D7oyI15uJ5xFgU0nDJG3TXNCSjgFuAf5L9oF3HPA0sE5avy0wDngDOCzFdhTZe97UCOBR4EBghKRewMPAlsBJqf3uwP2SuhXEMFHSxOZiTLYCniyyfHZaVxZJWwCfA0Y1s8l/JdVJekrSieW2azkUEZ6qNJElgwVA1zR/F/ACoIJt/g1MLVzWpI3fkJVI1m1m/e5AANs1WT4RuKlg/pq03Q4lYu5MltyWFsS9T9r3wBb2exgYWTC/KdlX9ANKHGtMajuAN4HrgAEF26wCzAVuaaGd0cAzZCWbxmWDUpu7NnmfLmmy7/npuL0KlvUE3gZOLlg2Hhhf4r27D7ityPL/A/7Vir+bnwPLCmMq+Hf4GVkZZT9gZPqdTq/237qnFZvcg66SVCI4FLg1PqzzjgY2Ip0QktQd2JkssTV3V6s9gXER8WobhDU3ImY0iVOSTpM0S9ISsp789cCqZCeqGmN4KyLuaKHtEcBhBWWFbwOvkfVsi4qIuoj4JrA9cA4wjSyx/lvS/mmzLYH1gKtbOPZOZO9zfcGym4E64EtNtv17k/mvkSXWdyR1ltQZWJRiGVAQ614RsVcLMbSlwcC9EfFW4cKIuCciLoiIeyPi7ogYQlaj/pkk/79eg/yPVj37kdVY/yGph6QeZL3a9/mwzNETENBS8l27xPrWeK3IstOA3wG3AgeRJbuT07rVWhHDjWQ95kGpJDIEuDYi6koFFRGPpcSzN1lCfhW4oODYlDj+ujT53VKyfhPo1WTbpu9Bb7Ky0vIm0x60voy0AFiryPKeaV1J6RzF1jRf3mjqJrLfceMyt7cc8SiO6mlMwmOLrDtC0mlk/9M2kCWY5rxZYv3S9LNrk+U9yWqyhYr10o8gK4X8tHFBkXpwqRiIiMWSRpP1nOeQ9b5b6vU2184LksYC3ys4NiWO/yrQp3BBOkG7Nll9/SOHaDL/FtlJ3GKjaxaVE3OBJyk+nG4rsqF25RhMNoTu9jK3jyY/rYa4B10FqXQxkKwXtEeT6QdkJw73jIjFwCTg2BaGlY0H9pHUt5n1L6efWxccfwPKPynVjaxXX+hbRWLoJemAEm2NIEtQvwD+ExHFTph9IJ0ALWZzPuzpPkVWgx7SQlOTgENSUm50KFkH5eESMY8HtgWeiIipTaanSuzb1N1AP0kflFUkDSCrxxcbz1zMYLITq+UOozuc7IN4TmsCtZyodhG8I05kJ9kC2LnIui5k/0Ndlea/QnZCaBxZUtmHLMEdkNavQ5aEnyZLUnuSlSDOLGhzCtnFCoeQjWKYRtarbHqScGqReC4i64V/Lx372tTWByceycow44B3yEaS7EmWxP9WpL2Zad+hZbxPfwIeJBs98VWy0SFXpv1/WOT9vB44ANgf+D3pZCJZgl0G3JnaGEr27WRcQRu7U/xkam/gRbKTtUelOAaRjVY5smC7kicJ03b3pPfvUOBgsg+Yh5psMwJ4tsi+u6QYD26m7ZuBs8jKZweQnVAN4NRq/817WrGp6gF0xCkliqdbWP9nYCGwapr/akpU76XlEygYbUF2YnFMSjrvkQ0TG1ywfjOy+vbilBAOovgojmIJeg2yUsRbaboy/c//kWRG1tP+HdmHxftkQ/R+VaS9C1KMa5bxPu2Sjv1M2ucN4F+Fv1vBtoeSffAsJSt7/B3YqGD9XmQ96aXA/PQer1GwvmiCTusaT0K+ln63F8hGXmxbsM1EYGIZv1OP1NZCsg+0G4DeTba5BnihyL6XFv5dFFn/6/Tv+x5ZGWQacEy1/949rfik9A9rVhGSJgNPRcQx1Y7FLO98ktAqItVa9yS7oObkEpubGU7QVjlTyL6e/zgiplQ5FrOa4BKHmVlOeZidmVlO5bbEsfiCo921t49Z65cTqh2C5VDdsrmf+Pazy994ruyc06X3pu1+u1twD9rMLLdy24M2M6uohvrS21SYE7SZGUB9yft2VZwTtJkZENFQ7RA+xgnazAygwQnazCyf3IM2M8spnyQ0M8sp96DNzPIpPIrDzCynfJLQzCynXOIwM8spnyQ0M8sp96DNzHLKJwnNzHLKJwnNzPIpwjVoM7N8cg3azCynXOIwM8sp96DNzHKqfnm1I/gYJ2gzM3CJw8wst1ziMDPLKfegzcxyygnazCyfIocnCVepdgBmZrkQDeVPJUjqIekmSU9Kmi1pV0m9JN0n6Zn0s2epdpygzcwgK3GUO5U2DBgXEVsB2wOzgbOB8RGxOTA+zbfICdrMDNqsBy1pLeArwAiAiFgWEQuBg4CRabORwMGlQnKCNjODVvWgJQ2VNLVgGlrQ0ibA68DVkqZLulJSd6BvRLyatpkH9C0Vkk8SmplBq8ZBR8RwYHgzqzsDnwdOjYhJkobRpJwRESEpSh3HPWgzM4C6uvKnlr0MvBwRk9L8TWQJ+zVJ6wKkn/NLNeQEbWYGbVaDjoh5wEuStkyL9gJmAXcAQ9KyIcDtpUJyicPMDNr6QpVTgesldQWeA44j6xDfKOl4YA4wqFQjTtBmZtCm9+KIiBnAgCKr9mpNO07QZmbgS73NzHLLd7MzM8up0qMzKs4J2swMIEoOS644J2gzM3AN2swst5ygzcxyyicJzcxyqr6+2hF8jBO0mRm4xGFmlltO0GZmOeUatJlZPkWDx0GbmeWTSxxmZjnlURxmZjnlHrSZWU45QVs5up1yCSxbSjQ0QEM9S6/6+QfrOu+8H6t+/Vss/v1JsOTdKkZpldS//3pcc9Uw+vTtTURw5ZXX86fLRtCzZw9GXf8XNtpoA+bMeYnBR53EwoVvVzvc2uSbJVm5llz3q48lYK3Zi06bfoaGt9+oUlRWLXV1dfzozPOYPmMma6zRncmTxnH/+AcZcuwgHpjwMBddfDln/uhkzjrzZH78k19XO9zalMMedLs9NFbSVpLOkvTHNJ0laev2Ol5H0PXrR7N8/OhcftJb+5o3bz7TZ8wE4N13F/Pkk8+w/nr9GDhwH669biwA1143lgMP3LeaYda2hih/qpB26UFLOgs4EhgNTE6L+wOjJI2OiAvb47grj2C1o84GgrpHHqBu+gQ6bfF5YtECGua/WO3grMo22qg/O2y/HZMmT6dvn97MmzcfyJJ43z69qxxdDetAoziOB7aNiOWFCyX9AXgCKJqgJQ0FhgL88cCd+J8dN2+n8PJt6cjziUULYPU1We1bZ9Hw5it02e1Alt7w22qHZlXWvfvq3DjmCn5wxrksWvTxcxDhb1crLDpQiaMBWK/I8nXTuqIiYnhEDIiIAR01OQNZcgZ47x3qn5pGpw23ZpUe69DthF/T7ZRL0Jq96PadC1D3taobqFVU586dGTvmCkaNupXbbrsbgNfmv0G/fn0A6NevD/Nff7OaIda2jlLiAE4Dxkt6BngpLdsQ2Aw4pZ2OuXLosipIsGwpdFmVTptsx/KHbuO9S07+YJNup1zCkhHneBRHB3PF8N8z+8lnuXTY8A+W3XXnvRx7zBFcdPHlHHvMEdx55z1VjLDGdZR7cUTEOElbADsB66fFc4EpEZG/Qk+OqPuarHrEadnrVTpRN/Nf1D/3WHWDsqrb7Ys7cszRh/PY47OYOuVeAM4550J+e/HljL7hrxz37SN58cWXGXzUSVWOtIbl8F4cymvNavEFR+czMKuqtX45odohWA7VLZurT9rG4p8PLjvndP/l6E98vHJ4HLSZGXScEoeZWc3JYYnDCdrMjHwOs3OCNjMD96DNzHLLCdrMLKc60KXeZmY1xc8kNDPLKydoM7Oc8igOM7Occg/azCynnKDNzPIp6l3iMDPLJ/egzczyqS2H2Ul6AVgE1AN1ETFAUi9gDLAx8AIwKCIWtNROuz001sysprT9E1X2iIgdImJAmj8bGB8RmwPj03yLnKDNzCB7GF+504o5CBiZXo8EDi61gxO0mRkQdQ1lT5KGSppaMA1t2hxwr6RpBev6RsSr6fU8oG+pmFyDNjODVvWMI2I4MLyFTb4UEXMl9QHuk/Rkk/1DUslaScketKSLJK0pqYuk8ZJel3R0yd/AzKyGREOUPZVsK2Ju+jkfuJXs+ayvSVoXIP2cX6qdckoce0fEO8ABZGceNwN+VMZ+Zma1o41q0JK6S/pU42tgb2AmcAcwJG02BLi9VEjllDgat9kfGBsRb0sVeV6imVnFtOEwu77ArSlPdgZuiIhxkqYAN0o6HpgDDCrVUDkJ+q5UP1kCfFfSOsDSFQ7dzCyP2uhCwoh4Dti+yPI3gb1a01bJBB0RZ0u6CHg7IuolvUc2XMTMbKURddWO4OPKOUm4OvA94C9p0XrAgOb3MDOrPdFQ/lQp5ZwkvBpYBnwxzc8FLmi3iMzMqqH9L1RptXIS9Kcj4iJgOUBEvAf4LKGZrVTy2IMu5yThMkndyK6MQdKngffbNSozswqrZOItVzkJ+lxgHLCBpOuB3YBvt2dQZmaVFvX5KwyUM4rjPkmPALuQlTa+HxFvtHtkZmYVVJM9aElfSS8XpZ/bSCIiHmy/sMzMKisaarAHzUcv616N7JryacCe7RKRmVkV1GQPOiIGFs5L2gC4tL0CMjOrhoja7EE39TKwdVsHYmZWTTXZg5b0J9IQO7Jx0zsAj7RjTGZmFddQi6M4gKkFr+uAURHx/9opHjOzqqjJk4QRMbLUNmZmta6mErSkx/mwtPGRVWRPbPlsu0VlZlZh0Wa3g247LfWgD6hYFGZmVVZTPeiImFPJQMzMqimPw+zKuR/0LpKmSHpX0jJJ9ZLeqURwZmaVUl+vsqdKKWcUx2XAYGAs2Y36jwW2aM+gzMwqrSZ70AAR8SzQKSLqI+JqYN/2DcvMrLKiQWVPlVJOD/o9SV2BGenZhK9SZmI3M6sVeRzF0WyilbRjenlM2u4UYDGwAXBY+4dmZlY5tdaDHi5pDWA02dWDs4DzKhOWmVll1TfkrzDQbEQR8TmysdB1wE2SHpV0tqSNKxWcmVmlRJQ/VUqLHxkR8VREnBcR25CN3lgLGC/J9+Iws5VKQ6jsqVLKut2opFWAPkBfoDswvz2DMjOrtDwOs2sxQUv6MnAkcDDwOFk9+vSIeLv9QzMzq5w8juJo6WZJLwFzyJLyLyKior3mtX45oZKHsxqx5JWHqh2CraQqWbooV0s96C/5fhxm1lHkcRSHb5ZkZkbxeytX24o8k9DMbKVTayUOM7MOo6ZGcTR5WOzHRMT/tktEZmZVkMOHerfYg57awjozs5VKUEM9aD8s1sw6krpaKnE0krQOcBawDbBa4/KI2LMd4zIzq6g89qDLGfh3PTAb2ITsbnYvAFPaMSYzs4praMVUKeUk6LUjYgSwPCL+GRH/A7j3bGYrlUBlT5VSToJenn6+Kml/SZ8DerVjTGZmFdfWPWhJnSRNl3RXmt9E0iRJz0oak55U1aJyEvQFktYCfgicAVwJnF5mjGZmNaEelT2V6ftk5eFGvwUuiYjNgAXA8aUaKJmgI+KuiHg7ImZGxB4R8YWIuKPcCM3MakGDyp9KkdQf2J+sQ4skkZWGb0qbjCS7S2iLyhnFcTVFLlhJtWgzs5VCQytqy5KGAkMLFg2PiOEF85cCZwKfSvNrAwsjoi7NvwysX+o45VzqfVfB69WAQ4BXytjPzKxmtOZmSSkZDy+2TtIBwPyImCZp908SU8kEHRE3Nzn4KODhT3JQM7O8acPhc7sBB0r6Blmndk1gGNBDUufUi+4PzC3V0IrcAHVzssdfmZmtNBqksqeWRMSPI6J/RGwMDAYeiIhvAROAw9NmQ4DbS8VUTg16ER/t/c8ju7LQzGylUd/+hzgLGC3pAmA6MKLUDuWUOD5Vahszs1pXzuiM1oqIicDE9Po5YKfW7F+yxCFpfDnLzMxqWQMqe6qUlu4HvRqwOtBbUk/4IKo1KWN4iJlZLam1R16dCJwGrAdM48ME/Q5wWfuGZWZWWe1R4vikWrof9DBgmKRTI+JPFYzJzKzi8vhElXKG2TVI6tE4I6mnpO+1X0hmZpVXr/KnSiknQZ8QEQsbZyJiAXBCu0VkZlYFebwfdDmXeneSpIgIyG6hB5S8TZ6ZWS3JY4mjnAQ9Dhgj6W9p/sS0zMxspZHDRxKWlaDPIrtr03fT/H3AFe0WkZlZFeSxB13O/aAbIuKvEXF4RBwOzAI8qsPMVir1rZgqpZweNOkxV0cCg4DngVvaMygzs0qrqXHQkrYgS8pHAm8AYwBFxB4Vis3MrGLyWOJoqQf9JPAQcEBEPAsgyc8iNLOVUh4TdEs16EOBV4EJkq6QtBdU8C4hZmYVFK2YKqXZBB0Rt0XEYGArshtNnwb0kfQXSXtXKD4zs4poy4fGtpVyRnEsjogbImIg2WNapuMb9pvZSqZmR3E0Spd5N/uwRDOzWtWQwxuOtipBm5mtrPJ4ktAJ2syM2rthv5lZh+EetJlZTtUpf31oJ2gzM1ziMDPLLZc4zMxyysPszMxyKn/p2QnazAxwicPMLLfqc9iHdoI2M8M9aDOz3Ar3oM3M8imPPeiStxu1yurffz3uv3csjz06gUdnPMCppxwPQM+ePRj3j1HMfuJhxv1jFD16rFXlSK2S3ln0Lqf/9AIGHnkCA48ayoyZswG4fuztDDzyBA761on8/vIRVY6ytjUQZU+V4h50ztTV1fGjM89j+oyZrLFGdyZPGsf94x9kyLGDeGDCw1x08eWc+aOTOevMk/nxT35d7XCtQi689K/stvMALvnVz1i+fDlLlr7P5GmPMuHh/3DzyMvp2rUrby5YWO0wa1r+ChzuQefOvHnzmT5jJgDvvruYJ598hvXX68fAgftw7XVjAbj2urEceOC+1QzTKmjRu4uZ9uhMDhu4DwBdunRhzU+twZjb/s7xRw+ia9euAKzds0cVo6x9dUTZU6W4B51jG23Unx22345Jk6fTt09v5s2bD2RJvG+f3lWOzipl7ivz6NljLX72qz/w1LPPsc2Wm3P2aSfxwotzmfboTP44fCSrdu3CD0/5Dp/Zestqh1uz8niSsOI9aEnHtbBuqKSpkqY2NCyuZFi507376tw45gp+cMa5LFr07sfWR+Tvj8naR119PbOffpZvHrI/N11zOd26rcaI626kvr6ed95ZxA3DL+GHJ3+HM875jf8uPoGGVkyVUo0Sx3nNrYiI4RExICIGrLJK90rGlCudO3dm7JgrGDXqVm677W4AXpv/Bv369QGgX78+zH/9zWqGaBXUr09v+q7Tm89uuxUAe+/+JWY9/Sx9+/Tma1/dDUl8ZpstkcSChW9XOdraFa34r1LaJUFLeqyZ6XGgb3scc2VyxfDfM/vJZ7l02IePfrzrzns59pgjADj2mCO48857qhWeVVjvtXvRr886PD/nZQD+M20Gn954Q/b88q5MfuRRAF548WWW19XR06N7Vlgee9Bqj69Ekl4D9gEWNF0F/Csi1ivVRueu63fI72q7fXFH/jnxNh57fBYNDdlbcM45FzJp8nRG3/BXNthgfV588WUGH3USCzrgWfslrzxU7RCq4smn/8vPLxzG8rrlbLDeupz/k9NZvdtq/OzXl/DUM8/RpUtnzjjlO+z8hR2qHWpVdOm9qT5pG0dvdGjZOef/5tzyiY9XjvZK0COAqyPi4SLrboiIo0q10VETtLWsoyZoa1lbJOijNjqk7Jxzw5xbmz2epNWAB4FVyQZi3BQR50raBBgNrA1MA46JiGUtHaddShwRcXyx5JzWlUzOZmaV1oY16PeBPSNie2AHYF9JuwC/BS6JiM3IqgvHl2rI46DNzGi7GnRkGodedUlTAHsCN6XlI4GDS8XkBG1mRtte6i2pk6QZwHzgPuC/wMKIqEubvAysX6odJ2gzM1pX4ii8ZiNNQz/SVkR9ROwA9Ad2ArZakZh8JaGZGVDfigETETEcGF7GdgslTQB2BXpI6px60f2BuaX2dw/azIy2K3FIWkdSj/S6G/B1YDYwATg8bTYEuL1UTO5Bm5nRphegrAuMlNSJrBN8Y0TcJWkWMFrSBcB0oOT9YZ2gzcxou5slRcRjwOeKLH+OrB5dNidoMzOo6I34y+UEbWZGPu8Q6QRtZgbUuwdtZpZPLnGYmeWUSxxmZjnlHrSZWU7l8ZmETtBmZrTuUu9KcYI2M8MlDjOz3HKCNjPLKY/iMDPLKfegzcxyyqM4zMxyqj7a8IajbcQJ2swM16DNzHLLNWgzs5xyDdrMLKcaXOIwM8sn96DNzHLKozjMzHLKJQ4zs5xyicPMLKfcgzYzyyn3oM3Mcqo+6qsdwsc4QZuZ4Uu9zcxyy5d6m5nllHvQZmY55VEcZmY55VEcZmY55Uu9zcxyyjVoM7Occg3azCyn3IM2M8spj4M2M8sp96DNzHLKozjMzHLKJwnNzHIqjyWOVaodgJlZHkQr/muJpA0kTZA0S9ITkr6flveSdJ+kZ9LPnqVicoI2MyPrQZc7lVAH/DAitgF2AU6WtA1wNjA+IjYHxqf5FjlBm5mR1aDLnVoSEa9GxCPp9SJgNrA+cBAwMm02Eji4VEzKY93FPkrS0IgYXu04LF/8d1E9koYCQwsWDS/2byFpY+BBYDvgxYjokZYLWNA43+xxnKDzT9LUiBhQ7TgsX/x3kW+S1gD+CfwqIm6RtLAwIUtaEBEt1qFd4jAza2OSugA3A9dHxC1p8WuS1k3r1wXml2rHCdrMrA2l8sUIYHZE/KFg1R3AkPR6CHB7qbY8Dro2uM5oxfjvIp92A44BHpc0Iy37CXAhcKOk44E5wKBSDbkGbWaWUy5xmJnllBO0mVlOOUHnnKR9JT0l6VlJJa88spWfpKskzZc0s9qxWPtygs4xSZ2Ay4H9gG2AI9Mlo9axXQPsW+0grP05QefbTsCzEfFcRCwDRpNdLmodWEQ8CLxV7Tis/TlB59v6wEsF8y+nZWbWAThBm5nllBN0vs0FNiiY75+WmVkH4ASdb1OAzSVtIqkrMJjsclEz6wCcoHMsIuqAU4B7yO4pe2NEPFHdqKzaJI0C/g1sKenldOmwrYR8qbeZWU65B21mllNO0GZmOeUEbWaWU07QZmY55QRtZpZTTtD2EZLqJc2QNFPSWEmrf4K2rpF0eHp9ZUs3epK0u6QvrsAxXpDUu8myqyWd2GTZwZLuLidWs7xwgramlkTEDhGxHbAMOKlwpaQVekxaRHwnIma1sMnuQKsTdDNGkV3UU2hwWm5WM5ygrSUPAZul3u1Dku4AZknqJOliSVMkPdbYW1XmsnT/6vuBPo0NSZooaUB6va+kRyQ9Kmm8pI3JPghOT733L0taR9LN6RhTJO2W9l1b0r2SnpB0JaAicY8Htip4gnJ34GvAbZJ+ntqbKWl4esDnRxT2yiUNkDSxsZ10L+bJkqZLOigt3zYtm5Hej83b4s03c4K2olJPeT/g8bTo88D3I2IL4Hjg7YjYEdgROEHSJsAhwJZk964+liI9YknrAFcAh0XE9sAREfEC8FfgktR7fwgYluZ3BA4DrkxNnAs8HBHbArcCGzY9RkTUkz3yvvGhnAOBiRHxDnBZROyYviF0Aw5oxdvyU+CBiNgJ2AO4OCX/k4BhEbEDMIDsroNmn5if6m1NdSt4EvFDZI+P/yIwOSKeT8v3Bj5bULNdC9gc+AowKiXIVyQ9UKT9XYAHG9uKiObua/w1YJuCDu6aktZIxzg07ft3SQua2X8U8DuyRD8YuC4t30PSmcDqQC/gCeDOZtpoam/gQElnpPnVyD4g/g38VFJ/4JaIeKbM9sxa5ARtTS1JPcEPpCS5uHARcGpE3NNku2+0YRyrALtExNIisZTjX8C6krYn+4AZLGk14M/AgIh4SdIvyJJsU3V8+O2ycL3Iev5PNdl+tqRJwP7APySdGBHFPpzMWsUlDlsR9wDfldQFQNIW6av+g8A3U416XbIyQFP/Ab6SSiJI6pWWLwI+VbDdvcCpjTOSdkgvHwSOSsv2A3oWCzCym8yMAUYCd6dE35hs30i98eZGbbwAfCG9PqzJ731qY91a0ufSz02B5yLij8DtwGebadesVZygbUVcCcwCHkkPLv0b2bexW4Fn0rpryb76f0REvA4MBW6R9ChZEoWszHBI40lC4H+BAemk2yw+HE1yHlmCf4Ks1PFiC3GOArZPP4mIhWT175lkyXZKM/udBwyTNBWoL1h+PtAFeCwd//y0fBAwM5WGtku/u9kn5rvZmZnllHvQZmY55QRtZpZTTtBmZjnlBG1mllNO0GZmOeUEbWaWU07QZmY59f8B0iDtcFf3u/gAAAAASUVORK5CYII=\n",
"text/plain": [
"