<jupyter_start><jupyter_text>Classification by Wine Type Wine DataData from http://archive.ics.uci.edu/ml/datasets/Wine+Quality Citations<NAME>. and <NAME>. (2017). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml/index.php]. Irvine, CA: University of California, School of Information and Computer Science.<NAME>, <NAME>, <NAME>, <NAME> and <NAME>. Modeling wine preferences by data mining from physicochemical properties.In Decision Support Systems, Elsevier, 47(4):547-553. ISSN: 0167-9236.Available at:- [@Elsevier](http://dx.doi.org/10.1016/j.dss.2009.05.016)- [Pre-press (pdf)](http://www3.dsi.uminho.pt/pcortez/winequality09.pdf)- [bib](http://www3.dsi.uminho.pt/pcortez/dss09.bib) Setup<jupyter_code>%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns<jupyter_output><empty_output><jupyter_text>Read in the data:<jupyter_code>red_wine = pd.read_csv('data/winequality-red.csv')
white_wine = pd.read_csv('data/winequality-white.csv', sep=';')<jupyter_output><empty_output><jupyter_text>EDA<jupyter_code>white_wine.head()
red_wine.head()<jupyter_output><empty_output><jupyter_text>Looking at quality scores<jupyter_code>def plot_quality_scores(df, kind):
    ax = df.quality.value_counts().sort_index().plot.barh(
        title=f'{kind.title()} Wine Quality Scores', figsize=(12, 3)
    )
    ax.axes.invert_yaxis()
    for bar in ax.patches:
        ax.text(
            bar.get_width(), 
            bar.get_y() + bar.get_height()/2, 
            f'{bar.get_width()/df.shape[0]:.1%}',
            verticalalignment='center'
        )
    plt.xlabel('count of wines')
    plt.ylabel('quality score')

    for spine in ['top', 'right']:
        ax.spines[spine].set_visible(False)

    return ax

plot_quality_scores(white_wine, 'white')
plot_quality_scores(red_wine, 'red')<jupyter_output><empty_output><jupyter_text>Combining red and white wine data<jupyter_code>wine = pd.concat([
    white_wine.assign(kind='white'), red_wine.assign(kind='red')
])
wine.sample(5, random_state=10)<jupyter_output><empty_output><jupyter_text>No null data:<jupyter_code>wine.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
Int64Index: 6497 entries, 0 to 1598
Data columns (total 13 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   fixed acidity         6497 non-null   float64
 1   volatile acidity      6497 non-null   float64
 2   citric acid           6497 non-null   float64
 3   residual sugar        6497 non-null   float64
 4   chlorides             6497 non-null   float64
 5   free sulfur dioxide   6497 non-null   float64
 6   total sulfur dioxide  6497 non-null   float64
 7   density               6497 non-null   float64
 8   pH                    6497 non-null   float64
 9   sulphates             6497 non-null   float64
 10  alcohol               6497 non-null   float64
 11  quality               6497 non-null   int64  
 12  kind                  6497 non-null   object 
dtypes: float64(11), int64(1), object(1)
memory usage: 710.6+ KB<jupyter_text>We have more whites than reds:<jupyter_code>wine.kind.value_counts()<jupyter_output><empty_output><jupyter_text>We want to understand if chemical properties can be used to determine wine type. Unfortunately, `describe()` gives a very long output, so we need a visualization to compare the wines this way:<jupyter_code>wine.drop(columns='quality').groupby('kind').describe()<jupyter_output><empty_output><jupyter_text>How do chemical properties of the wine correlate to each other and the wine type?It's important to perform an in-depth exploration of the data before modeling. This includes consulting domain experts, looking for correlations between variables, examining distributions, etc. The visualizations covered in chapters 5 and 6 will prove indispensible for this process. One such visualization is the heatmap. In order to predict if the wine is red or white, we would look for correlations between chemical properties and wine type. We would also try to see if there is a difference in the distribution of our variables for white versus red wines. Some other helpful plot types include box plots, pair plots, and the scatter matrix.<jupyter_code>fig = plt.figure(figsize=(7, 7))
sns.heatmap(
    wine.drop(columns='quality').assign(
        is_red=lambda x: np.where(x.kind == 'red', 1, 0)
    ).corr(), 
    cbar_kws={'shrink': 0.8},
    center=0, vmin=-1, vmax=1,
    square=True, annot=True, fmt='.1g'
)<jupyter_output><empty_output><jupyter_text>Comparison of Red and White Wines by Their Chemical PropertiesThis visualization will be easier to digest than the output of `describe()`:<jupyter_code>import math

chemical_properties = [col for col in wine.columns if col not in ['quality', 'kind']]
melted = wine.drop(columns='quality').melt(id_vars=['kind'])

fig, axes = plt.subplots(math.ceil(len(chemical_properties) / 4), 4, figsize=(15, 10))
axes = axes.flatten()

for prop, ax in zip(chemical_properties, axes):
    sns.boxplot(
        data=melted[melted.variable.isin([prop])], 
        x='variable', y='value', hue='kind', ax=ax
    ).set_xlabel('')
    
# remove the extra subplots
for ax in axes[len(chemical_properties):]:
    ax.remove()

plt.suptitle('Comparing Chemical Properties of Red and White Wines')
plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Classification of Red and White Wines1. separate x and y2. get the training and testing set<jupyter_code>from sklearn.model_selection import train_test_split

# 1
wine_y = np.where(wine.kind == 'red', 1, 0)
wine_X = wine.drop(columns=['quality', 'kind'])

# 2
w_X_train, w_X_test, w_y_train, w_y_test = train_test_split(
    wine_X, wine_y, test_size=0.25, random_state=0, stratify=wine_y
)<jupyter_output><empty_output><jupyter_text>3. build a pipeline with standard scaler followed by logistic regression and fit the model<jupyter_code>from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

white_or_red = Pipeline([
    ('scale', StandardScaler()), 
    ('lr', LogisticRegression(random_state=0))
]).fit(w_X_train, w_y_train)<jupyter_output><empty_output><jupyter_text>4. make predictions<jupyter_code>kind_preds = white_or_red.predict(w_X_test)<jupyter_output><empty_output><jupyter_text>5. evaluate predictionsWe can use a confusion matrix to see how the model's predictions align with the actual class labels. The model only made 13 incorrect predictions; we will look into these in chapter 10:<jupyter_code>from ml_utils.classification import confusion_matrix_visual

confusion_matrix_visual(w_y_test, kind_preds, ['white', 'red'])<jupyter_output><empty_output><jupyter_text>Precision, recall, and $F_1$ score all look good with this model:<jupyter_code>from sklearn.metrics import classification_report
print(classification_report(w_y_test, kind_preds))<jupyter_output>precision    recall  f1-score   support

           0       0.99      1.00      0.99      1225
           1       0.99      0.98      0.98       400

    accuracy                           0.99      1625
   macro avg       0.99      0.99      0.99      1625
weighted avg       0.99      0.99      0.99      1625<jupyter_text>Another way to use the confusion matrix is with sensitivity and specificity:<jupyter_code>from visual_aids import ml_viz
ml_viz.portion_of_confusion_matrix_considered({'sensitivity', 'specificity'})<jupyter_output><empty_output><jupyter_text>Sensitivity-specificity plots plot sensitivity (TPR) versus 1-specificity (FPR) and are another way to evaluate performance. They include all sections of the confusion matrix, which is why in cases of class balance, they are optimistic of performance. These plots are also called ROC curves. ROC CurvesVisualize model performance using true positive rates and false positive rates. The area under the curve is in the range [0, 1] with 1 being the best. This visualization allows us to compare our model to the baseline of random guessing (the diagonal line with AUC of 0.5), as well as, other models:<jupyter_code>ml_viz.roc_curve()<jupyter_output><empty_output><jupyter_text>This model performs very well, the area under the curve (AUC) is nearly 1:<jupyter_code>from ml_utils.classification import plot_roc

plot_roc(w_y_test, white_or_red.predict_proba(w_X_test)[:,1])<jupyter_output><empty_output><jupyter_text>Precision-recall curvesWhen faced with class imbalance, we use precision-recall curves since ROC curves will be optimistic of model performance. AP is the weighted average precision and AUC is the area under the curve once again in the range [0, 1]. The baseline is now the percentage of observations belonging to the positive class. Values below this line are worse than random:<jupyter_code>from ml_utils.classification import plot_pr_curve

plot_pr_curve(w_y_test, white_or_red.predict_proba(w_X_test)[:,1])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09875444839857651, 'fraction_numerical': 0.030805160142348756, 'mean_word_length': 4.3150118203309695, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '82637', 'license': 'MIT', 'n_tokens_mistral': 3097, 'n_tokens_neox': 2809, 'n_words': 831, 'path': 'ch_09/wine.ipynb', 'repo_name': 'piotrv/DA-Pandas-Stefanie-Molin', 'size': '9020'}