Skip to content

02-SVM-Project-Exercise

Updated
17 Sept 2026
3 min read
6 sections
Page 78 of 117
Python & Machine Learning

Support Vector Machines

Exercise

Fraud in Wine

Wine fraud relates to the commercial aspects of wine. The most prevalent type of fraud is one where wines are adulterated, usually with the addition of cheaper products (e.g. juices) and sometimes with harmful chemicals and sweeteners (compensating for color or flavor).

Counterfeiting and the relabelling of inferior and cheaper wines to more expensive brands is another common type of wine fraud.

Project Goals

A distribution company that was recently a victim of fraud has completed an audit of various samples of wine through the use of chemical analysis on samples. The distribution company specializes in exporting extremely high quality, expensive wines, but was defrauded by a supplier who was attempting to pass off cheap, low quality wine as higher grade wine. The distribution company has hired you to attempt to create a machine learning model that can help detect low quality (a.k.a "fraud") wine samples. They want to know if it is even possible to detect such a difference.

Data Source: P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis. Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009.



TASK: Your overall goal is to use the wine dataset shown below to develop a machine learning model that attempts to predict if a wine is "Legit" or "Fraud" based on various chemical features. Complete the tasks below to follow along with the project.



Complete the Tasks in bold

TASK: Run the cells below to import the libraries and load the dataset.

python
df = pd.read_csv("../DATA/wine_fraud.csv")
python
df.head()
Out · table
fixed acidity
volatile acidity
citric acid
residual sugar
chlorides
78 more lines
free sulfur dioxide
total sulfur dioxide
density
pH
sulphates
alcohol
quality
type
0
7.4
0.70
0.00
1.9
0.076
11.0
34.0
0.9978
3.51
0.56
9.4
Legit
red
1
7.8
0.88
0.00
2.6
0.098
25.0
67.0
0.9968
3.20
0.68
9.8
Legit
red
2
7.8
0.76
0.04
2.3
0.092
15.0
54.0
0.9970
3.26
0.65
9.8
Legit
red
3
11.2
0.28
0.56
1.9
0.075
17.0
60.0
0.9980
3.16
0.58
9.8
Legit
red
4
7.4
0.70
0.00
1.9
0.076
11.0
34.0
0.9978
3.51
0.56
9.4
Legit
red

TASK: What are the unique variables in the target column we are trying to predict (quality)?

python
df.columns
Out · result
Index(['fixed acidity', 'volatile acidity', 'citric acid', 'residual sugar',
       'chlorides', 'free sulfur dioxide', 'total sulfur dioxide', 'density',
       'pH', 'sulphates', 'alcohol', 'quality', 'type'],
      dtype='object')
python
df.quality.unique()
Out · result
array(['Legit', 'Fraud'], dtype=object)

TASK: Create a countplot that displays the count per category of Legit vs Fraud. Is the label/target balanced or unbalanced?

python
sns.countplot(x='quality',hue='quality',data=df)
Out · result
<Axes: xlabel='quality', ylabel='count'>
Out · plot
Output 1

*TASK: Let's find out if there is a difference between red and white wine when it comes to fraud. Create a countplot that has the wine type on the x axis with the hue separating columns by Fraud vs Legit.*

python
sns.countplot(x='type',hue='quality',data=df)
Out · result
<Axes: xlabel='type', ylabel='count'>
Out · plot
Output 2

TASK: What percentage of red wines are Fraud? What percentage of white wines are fraud?

python
reds = df[df['type']=='red']
whites = df[df['type']=='white']
python
len(reds[reds['quality']=='Fraud']) / len(reds) * 100
Out · result
3.9399624765478425
python
len(whites[whites['quality']=='Fraud']) / len(whites) * 100
Out · result
3.7362188648427925

TASK: Calculate the correlation between the various features and the "quality" column. To do this you may need to map the column to 0 and 1 instead of a string.

python
df['Fraud']= df['quality'].map({'Legit':0,'Fraud':1})
python
df.corr(numeric_only=True)['Fraud']
Out · result
fixed acidity           0.021794
volatile acidity        0.151228
citric acid            -0.061789
residual sugar         -0.048756
chlorides               0.034499
8 more lines
free sulfur dioxide    -0.085204
total sulfur dioxide   -0.035252
density                 0.016351
pH                      0.020107
sulphates              -0.034046
alcohol                -0.051141
Fraud                   1.000000
Name: Fraud, dtype: float64

TASK: Create a bar plot of the correlation values to Fraudlent wine.

python
df.corr(numeric_only=True)['Fraud'][:-1].sort_values().plot(kind='bar')
Out · result
<Axes: >
Out · plot
Output 3

TASK: Create a clustermap with seaborn to explore the relationships between variables.

python
sns.clustermap(df.corr(numeric_only=True) ,annot=True)
Out · result
<seaborn.matrix.ClusterGrid at 0x126aa3f80>
Out · plot
Output 4

Machine Learning Model

TASK: Convert the categorical column "type" from a string or "red" or "white" to dummy variables:

python
whites = df[df['type']=='white']
reds = df[df['type']=='red']
python
df['type'] = pd.get_dummies(df['type'],drop_first=True)
df = df.drop('Fraud',axis=1)

TASK: Separate out the data into X features and y target label ("quality" column)

python
X = df.drop('quality',axis=1)
y = df['quality']

TASK: Perform a Train|Test split on the data, with a 10% test size. Note: The solution uses a random state of 101

python
from sklearn.model_selection import train_test_split


X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,random_state=101)

TASK: Scale the X train and X test data.

python
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

scaled_X_train = scaler.fit_transform(X_train)
scaled_X_test = scaler.transform(X_test)
Out · error
ValueError: could not convert string to float: 'red'
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/var/folders/41/x856_7w17ss36djfjfjccwcr0000gn/T/ipykernel_54804/2359573251.py in ?()
      1 from sklearn.preprocessing import StandardScaler
89 more lines
      2 
      3 scaler = StandardScaler()
      4 
----> 5 scaled_X_train = scaler.fit_transform(X_train)
      6 scaled_X_test = scaler.transform(X_test)

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_set_output.py in ?(self, X, *args, **kwargs)
    314     @wraps(f)
    315     def wrapped(self, X, *args, **kwargs):
--> 316         data_to_wrap = f(self, X, *args, **kwargs)
    317         if isinstance(data_to_wrap, tuple):
    318             # only wrap the first output for cross decomposition
    319             return_tuple = (

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py in ?(self, X, y, **fit_params)
    890                 )
    891 
    892         if y is None:
    893             # fit method of arity 1 (unsupervised transformation)
--> 894             return self.fit(X, **fit_params).transform(X)
    895         else:
    896             # fit method of arity 2 (supervised transformation)
    897             return self.fit(X, y, **fit_params).transform(X)

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/preprocessing/_data.py in ?(self, X, y, sample_weight)
    903             Fitted scaler.
    904         """
    905         # Reset internal state before fitting
    906         self._reset()
--> 907         return self.partial_fit(X, y, sample_weight)

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py in ?(estimator, *args, **kwargs)
   1361                 skip_parameter_validation=(
   1362                     prefer_skip_nested_validation or global_skip_validation
   1363                 )
   1364             ):
-> 1365                 return fit_method(estimator, *args, **kwargs)

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/preprocessing/_data.py in ?(self, X, y, sample_weight)
    939         self : object
    940             Fitted scaler.
    941         """
    942         first_call = not hasattr(self, "n_samples_seen_")
--> 943         X = validate_data(
    944             self,
    945             X,
    946             accept_sparse=("csr", "csc"),

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/validation.py in ?(_estimator, X, y, reset, validate_separately, skip_check_array, **check_params)
   2950             out = y
   2951         else:
   2952             out = X, y
   2953     elif not no_val_X and no_val_y:
-> 2954         out = check_array(X, input_name="X", **check_params)
   2955     elif no_val_X and not no_val_y:
   2956         out = _check_y(y, **check_params)
   2957     else:

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/validation.py in ?(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_writeable, force_all_finite, ensure_all_finite, ensure_non_negative, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
   1050                         )
   1051                     array = xp.astype(array, dtype, copy=False)
   1052                 else:
   1053                     array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)
-> 1054             except ComplexWarning as complex_warning:
   1055                 raise ValueError(
   1056                     "Complex data not supported\n{}\n".format(array)
   1057                 ) from complex_warning

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_array_api.py in ?(array, dtype, order, copy, xp, device)
    753         # Use NumPy API to support order
    754         if copy is True:
    755             array = numpy.array(array, order=order, dtype=dtype)
    756         else:
--> 757             array = numpy.asarray(array, order=order, dtype=dtype)
    758 
    759         # At this point array is a NumPy ndarray. We convert it to an array
    760         # container that is consistent with the input's namespace.

~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/pandas/core/generic.py in ?(self, dtype, copy)
   2149     def __array__(
   2150         self, dtype: npt.DTypeLike | None = None, copy: bool_t | None = None
   2151     ) -> np.ndarray:
   2152         values = self._values
-> 2153         arr = np.asarray(values, dtype=dtype)
   2154         if (
   2155             astype_is_view(values.dtype, arr.dtype)
   2156             and using_copy_on_write()

ValueError: could not convert string to float: 'red'

TASK: Create an instance of a Support Vector Machine classifier. Previously we have left this model "blank", (e.g. with no parameters). However, we already know that the classes are unbalanced, in an attempt to help alleviate this issue, we can automatically adjust weights inversely proportional to class frequencies in the input data with a argument call in the SVC() call. Check out the documentation for SVC online and look up what the argument\parameter is.

python
from sklearn.svm import SVC

model = SVC()

TASK: Use a GridSearchCV to run a grid search for the best C and gamma parameters.

python
from sklearn.model_selection import GridSearchCV


param_grid = {'C':[0.1,1,10,100],'gamma':['scaler', 'auto']}
grid = GridSearchCV(model,param_grid)
python
grid.fit(X_train,y_train)
Out · stderr
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_validation.py:516: FitFailedWarning: 
20 fits failed out of a total of 40.
The score on these train-test partitions for these parameters will be set to nan.
If these failures are not expected, you can try to debug them by setting error_score='raise'.
18 more lines
Below are more details about the failures:
--------------------------------------------------------------------------------
20 fits failed with the following error:
Traceback (most recent call last):
  File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_validation.py", line 859, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py", line 1358, in wrapper
    estimator._validate_params()
  File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py", line 471, in _validate_params
    validate_parameter_constraints(
  File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_param_validation.py", line 98, in validate_parameter_constraints
    raise InvalidParameterError(
sklearn.utils._param_validation.InvalidParameterError: The 'gamma' parameter of SVC must be a str among {'auto', 'scale'} or a float in the range [0.0, inf). Got 'scaler' instead.

  warnings.warn(some_fits_failed_message, FitFailedWarning)
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1135: UserWarning: One or more of the test scores are non-finite: [       nan 0.96254495        nan 0.96237386        nan 0.96220292
        nan 0.95399311]
  warnings.warn(
Out · table
GridSearchCV(estimator=SVC(),
param_grid={'C': [0.1, 1, 10, 100], 'gamma': ['scaler', 'auto']})In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV
?Documentation for GridSearchCViFitted
56 more lines
Parameters
estimator
SVC()
param_grid
{'C': [0.1, 1, ...], 'gamma': ['scaler', 'auto']}
scoring
None
n_jobs
None
refit
True
cv
None
verbose
0
pre_dispatch
'2*n_jobs'
error_score
nan
return_train_score
False
best_estimator_: SVC
SVC(C=0.1, gamma='auto')
SVC
?Documentation for SVC
Parameters
C
0.1
kernel
'rbf'
degree
3
gamma
'auto'
coef0
0.0
shrinking
True
probability
False
tol
0.001
cache_size
200
class_weight
None
verbose
False
max_iter
-1
decision_function_shape
'ovr'
break_ties
False
random_state
None
python
grid.best_params_
Out · result
{'C': 0.1, 'gamma': 'auto'}

TASK: Display the confusion matrix and classification report for your model.

python
from sklearn.metrics import confusion_matrix,classification_report

pred = grid.predict(X_test)

print(confusion_matrix(y_test,pred))
Out · stdout
[[  0  27]
 [  0 623]]
python
print(classification_report(y_test,pred))
Out · stdout
              precision    recall  f1-score   support

       Fraud       0.00      0.00      0.00        27
       Legit       0.96      1.00      0.98       623
3 more lines
    accuracy                           0.96       650
   macro avg       0.48      0.50      0.49       650
weighted avg       0.92      0.96      0.94       650
Out · stderr
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
1 more line
  _warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
python
from sklearn.metrics import ConfusionMatrixDisplay

ConfusionMatrixDisplay.from_estimator(grid, X_test, y_test)
Out · result
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x12648c6e0>
Out · plot
Output 5

TASK: Finally, think about how well this model performed, would you suggest using it? Realistically will this work?

python
# ANSWER: View the solutions video for full discussion on this.